Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
60ab10b296 | ||
![]() |
9184512fa1 | ||
![]() |
a2cc6611ce | ||
![]() |
3d2390dbcc | ||
![]() |
d5d50d4150 | ||
![]() |
cba8717188 | ||
![]() |
df4543a079 | ||
![]() |
8dc99e6390 | ||
![]() |
8f1ec20e96 | ||
![]() |
61dcb9a3ba | ||
![]() |
0e34886d7e | ||
![]() |
806820592f | ||
![]() |
1900c04b71 | ||
![]() |
32eb1abd4a | ||
![]() |
47557021fd | ||
![]() |
2ef81eae1a | ||
![]() |
420730ba1f | ||
![]() |
925ab26fb4 | ||
![]() |
2952e22619 | ||
![]() |
079eaed010 | ||
![]() |
6671d45651 | ||
![]() |
3543b4773a | ||
![]() |
02f788a659 | ||
![]() |
7ace3978d8 | ||
![]() |
8f277aaca0 | ||
![]() |
76451fa0ad | ||
![]() |
0104a08ba4 | ||
![]() |
5eff006843 | ||
![]() |
a8241bf9f3 | ||
![]() |
4aae2562ea | ||
![]() |
42db5b328a | ||
![]() |
bc4dfb3e92 | ||
![]() |
3b9c81534f | ||
![]() |
f31aa78f6f | ||
![]() |
b5ebd544f4 | ||
![]() |
c77a37ec89 | ||
![]() |
4d78e5dcd1 | ||
![]() |
15075b6ab8 | ||
![]() |
a8f233e204 | ||
![]() |
795cad43b9 | ||
![]() |
46934d7a85 | ||
![]() |
06067f375c | ||
![]() |
d1df53c683 | ||
![]() |
b03d2cf919 | ||
![]() |
27a8423f55 | ||
![]() |
ad70934430 | ||
![]() |
900f8d0fe1 | ||
![]() |
6a4ac515a7 | ||
![]() |
7c315ac995 | ||
![]() |
49e9440e0f | ||
![]() |
02e3e43f1e | ||
![]() |
8eddef6f04 | ||
![]() |
d2f1936bbf | ||
![]() |
36863f94c0 | ||
![]() |
75863c50d1 | ||
![]() |
ec0727b406 | ||
![]() |
5b2e9d94ca | ||
![]() |
8840d109ef | ||
![]() |
71225bd2dc | ||
![]() |
bab8ec6e18 | ||
![]() |
d307a2095b | ||
![]() |
a777f4e0ff | ||
![]() |
86a71afc6c | ||
![]() |
88943563c7 | ||
![]() |
20071a64fa | ||
![]() |
d0d3418e03 | ||
![]() |
083e3dc62a | ||
![]() |
d7e60f2456 | ||
![]() |
38e746240d | ||
![]() |
986063dce4 | ||
![]() |
71d0326cb2 | ||
![]() |
62ca53eeaf | ||
![]() |
9f2305f351 | ||
![]() |
340d1b543c | ||
![]() |
61098b11f2 | ||
![]() |
4d03d6b948 | ||
![]() |
1f67054777 | ||
![]() |
4a9814f16c | ||
![]() |
e0697d3288 | ||
![]() |
d165da20ec | ||
![]() |
ae27b179fe | ||
![]() |
1113ffe0f7 | ||
![]() |
5531bc0ba1 | ||
![]() |
a3819122db | ||
![]() |
c5528a61f3 | ||
![]() |
5a7c6ac6e5 | ||
![]() |
5e8cccef19 | ||
![]() |
0ccb248d91 | ||
![]() |
514d961c24 | ||
![]() |
f8e802afcd | ||
![]() |
556551b4f3 | ||
![]() |
23ddded61e | ||
![]() |
c5aa8a3980 | ||
![]() |
21ac75efae | ||
![]() |
9655700cde | ||
![]() |
c9b7e979c0 | ||
![]() |
77a3b0640d | ||
![]() |
de4cb38766 | ||
![]() |
74bd7f9991 | ||
![]() |
ba7f814300 | ||
![]() |
cdcd1c521e | ||
![]() |
4d0aabe91e | ||
![]() |
68f72b9b4d | ||
![]() |
dca37ccc95 | ||
![]() |
6a088d0c4f | ||
![]() |
7731f16b0f | ||
![]() |
9a1e7de4ae |
@ -64,10 +64,9 @@ body:
|
|||||||
label: Error Logs
|
label: Error Logs
|
||||||
description: |
|
description: |
|
||||||
Run the following command to collect logs on your system.
|
Run the following command to collect logs on your system.
|
||||||
|
Wings: `sudo wings diagnostics --hastebin-url=https://logs.pelican.dev`
|
||||||
Wings: `sudo wings diagnostics`
|
Panel: `tail -n 300 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl --data-binary @- https://logs.pelican.dev`
|
||||||
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl -X POST -F 'c=@-' paste.pelistuff.com`
|
placeholder: "https://logs.pelican.dev/c17f750e"
|
||||||
placeholder: "https://pelipaste.com/a1h6z"
|
|
||||||
render: bash
|
render: bash
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- dev
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -13,7 +14,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18, 20]
|
node-version: [20, 22]
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -4,6 +4,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@ -39,38 +41,38 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
||||||
- name: Create release branch and bump version
|
# - name: Create release branch and bump version
|
||||||
env:
|
# env:
|
||||||
REF: ${{ github.ref }}
|
# REF: ${{ github.ref }}
|
||||||
run: |
|
# run: |
|
||||||
BRANCH=release/${REF:10}
|
# BRANCH=release/${REF:10}
|
||||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
# git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
# git config --local user.name "github-actions[bot]"
|
||||||
git checkout -b $BRANCH
|
# git checkout -b $BRANCH
|
||||||
git push -u origin $BRANCH
|
# git push -u origin $BRANCH
|
||||||
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php
|
# sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php
|
||||||
git add config/app.php
|
# git add config/app.php
|
||||||
git commit -m "ci(release): bump version"
|
# git commit -m "ci(release): bump version"
|
||||||
git push
|
# git push
|
||||||
|
|
||||||
- name: Create release archive
|
- name: Create release archive
|
||||||
run: |
|
run: |
|
||||||
rm -rf node_modules vendor tests CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml shell.nix
|
rm -rf node_modules vendor tests CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml shell.nix
|
||||||
tar -czf panel.tar.gz * .env.example
|
tar -czf panel.tar.gz * .env.example
|
||||||
|
|
||||||
- name: Create checksum
|
#- name: Create checksum
|
||||||
run: |
|
# run: |
|
||||||
SUM=`sha256sum panel.tar.gz`
|
# SUM=`sha256sum panel.tar.gz`
|
||||||
echo $SUM > checksum.txt
|
# echo $SUM > checksum.txt
|
||||||
|
|
||||||
- name: Create release
|
#- name: Create release
|
||||||
id: create_release
|
# id: create_release
|
||||||
uses: softprops/action-gh-release@v2
|
# uses: softprops/action-gh-release@v2
|
||||||
env:
|
# env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
# with:
|
||||||
draft: true
|
# draft: true
|
||||||
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
# prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||||
files: |
|
# files: |
|
||||||
panel.tar.gz
|
# panel.tar.gz
|
||||||
checksum.txt
|
# checksum.txt
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,6 +24,5 @@ yarn-error.log
|
|||||||
|
|
||||||
public/assets/manifest.json
|
public/assets/manifest.json
|
||||||
/database/*.sqlite*
|
/database/*.sqlite*
|
||||||
filament-monaco-editor/
|
|
||||||
_ide_helper*
|
_ide_helper*
|
||||||
/.phpstorm.meta.php
|
/.phpstorm.meta.php
|
||||||
|
14
Dockerfile
14
Dockerfile
@ -63,8 +63,8 @@ FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS fin
|
|||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
# Install additional required libraries
|
# Install additional required libraries
|
||||||
RUN apk update && apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
caddy ca-certificates supervisor supercronic
|
caddy ca-certificates supervisor supercronic fcgi
|
||||||
|
|
||||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||||
@ -85,7 +85,8 @@ RUN chown root:www-data ./ \
|
|||||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||||
# Allow www-data write permissions where necessary
|
# 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 \
|
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord
|
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||||
|
&& chown -R www-data: /usr/local/etc/php/
|
||||||
|
|
||||||
# Configure Supervisor
|
# Configure Supervisor
|
||||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||||
@ -93,10 +94,11 @@ 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/supercronic/crontab
|
||||||
|
|
||||||
COPY docker/entrypoint.sh ./docker/entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
|
COPY docker/healthcheck.sh /healthcheck.sh
|
||||||
|
|
||||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD curl -f http://localhost/up || exit 1
|
CMD /bin/ash /healthcheck.sh
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
|
|
||||||
@ -104,5 +106,5 @@ VOLUME /pelican-data
|
|||||||
|
|
||||||
USER www-data
|
USER www-data
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ]
|
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||||
|
@ -67,8 +67,8 @@ FROM --platform=$TARGETOS/$TARGETARCH base AS final
|
|||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
# Install additional required libraries
|
# Install additional required libraries
|
||||||
RUN apk update && apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
caddy ca-certificates supervisor supercronic
|
caddy ca-certificates supervisor supercronic fcgi coreutils
|
||||||
|
|
||||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||||
@ -89,7 +89,8 @@ RUN chown root:www-data ./ \
|
|||||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||||
# Allow www-data write permissions where necessary
|
# 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 \
|
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord
|
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||||
|
&& chown -R www-data: /usr/local/etc/php/
|
||||||
|
|
||||||
# Configure Supervisor
|
# Configure Supervisor
|
||||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||||
@ -97,10 +98,11 @@ 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/supercronic/crontab
|
||||||
|
|
||||||
COPY docker/entrypoint.sh ./docker/entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
|
COPY docker/healthcheck.sh /healthcheck.sh
|
||||||
|
|
||||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD curl -f http://localhost/up || exit 1
|
CMD /bin/ash /healthcheck.sh
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
|
|
||||||
@ -108,5 +110,5 @@ VOLUME /pelican-data
|
|||||||
|
|
||||||
USER www-data
|
USER www-data
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ]
|
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Egg;
|
namespace App\Console\Commands\Egg;
|
||||||
|
|
||||||
|
use App\Enums\EggFormat;
|
||||||
use App\Models\Egg;
|
use App\Models\Egg;
|
||||||
use App\Services\Eggs\Sharing\EggExporterService;
|
use App\Services\Eggs\Sharing\EggExporterService;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use JsonException;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class CheckEggUpdatesCommand extends Command
|
class CheckEggUpdatesCommand extends Command
|
||||||
{
|
{
|
||||||
@ -23,6 +26,9 @@ class CheckEggUpdatesCommand extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)) {
|
||||||
@ -31,22 +37,26 @@ class CheckEggUpdatesCommand extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$currentJson = json_decode($exporterService->handle($egg->id));
|
$ext = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||||
unset($currentJson->exported_at);
|
$isYaml = in_array($ext, ['yaml', 'yml']);
|
||||||
|
|
||||||
$updatedEgg = file_get_contents($egg->update_url);
|
$local = $isYaml
|
||||||
assert($updatedEgg !== false);
|
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||||
$updatedJson = json_decode($updatedEgg);
|
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
||||||
unset($updatedJson->exported_at);
|
|
||||||
|
|
||||||
if (md5(json_encode($currentJson, JSON_THROW_ON_ERROR)) === md5(json_encode($updatedJson, JSON_THROW_ON_ERROR))) {
|
$remote = file_get_contents($egg->update_url);
|
||||||
$this->info("$egg->name: Up-to-date");
|
assert($remote !== false);
|
||||||
cache()->put("eggs.$egg->uuid.update", false, now()->addHour());
|
|
||||||
|
|
||||||
return;
|
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||||
}
|
|
||||||
|
|
||||||
$this->warn("$egg->name: Found update");
|
unset($local['exported_at'], $remote['exported_at']);
|
||||||
cache()->put("eggs.$egg->uuid.update", true, now()->addHour());
|
|
||||||
|
$localHash = md5(json_encode($local, JSON_THROW_ON_ERROR));
|
||||||
|
$remoteHash = md5(json_encode($remote, JSON_THROW_ON_ERROR));
|
||||||
|
|
||||||
|
$status = $localHash === $remoteHash ? 'Up-to-date' : 'Found update';
|
||||||
|
$this->{($localHash === $remoteHash) ? 'info' : 'warn'}("$egg->name: $status");
|
||||||
|
|
||||||
|
cache()->put("eggs.$egg->uuid.update", $localHash !== $remoteHash, now()->addHour());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Egg;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class UpdateEggIndexCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:egg:update-index';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = file_get_contents('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json');
|
||||||
|
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->error($exception->getMessage());
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = [];
|
||||||
|
foreach ($data['nests'] as $nest) {
|
||||||
|
$nestName = $nest['nest_type'];
|
||||||
|
|
||||||
|
$this->info("Nest: $nestName");
|
||||||
|
|
||||||
|
$nestEggs = [];
|
||||||
|
foreach ($nest['Eggs'] as $egg) {
|
||||||
|
$eggName = $egg['egg']['name'];
|
||||||
|
|
||||||
|
$this->comment("Egg: $eggName");
|
||||||
|
|
||||||
|
$nestEggs[$egg['download_url']] = $eggName;
|
||||||
|
}
|
||||||
|
$index[$nestName] = $nestEggs;
|
||||||
|
|
||||||
|
$this->info('');
|
||||||
|
}
|
||||||
|
|
||||||
|
cache()->forever('eggs.index', $index);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Environment;
|
namespace App\Console\Commands\Environment;
|
||||||
|
|
||||||
|
use PDOException;
|
||||||
use App\Traits\EnvironmentWriterTrait;
|
use App\Traits\EnvironmentWriterTrait;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
use Illuminate\Contracts\Console\Kernel;
|
||||||
@ -105,7 +106,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->database->connection('_panel_command_test')->getPdo();
|
$this->database->connection('_panel_command_test')->getPdo();
|
||||||
} catch (\PDOException $exception) {
|
} catch (PDOException $exception) {
|
||||||
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||||
|
|
||||||
@ -165,7 +166,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->database->connection('_panel_command_test')->getPdo();
|
$this->database->connection('_panel_command_test')->getPdo();
|
||||||
} catch (\PDOException $exception) {
|
} catch (PDOException $exception) {
|
||||||
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Environment;
|
namespace App\Console\Commands\Environment;
|
||||||
|
|
||||||
|
use App\Exceptions\PanelException;
|
||||||
use App\Traits\EnvironmentWriterTrait;
|
use App\Traits\EnvironmentWriterTrait;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ class EmailSettingsCommand extends Command
|
|||||||
/**
|
/**
|
||||||
* Handle command execution.
|
* Handle command execution.
|
||||||
*
|
*
|
||||||
* @throws \App\Exceptions\PanelException
|
* @throws PanelException
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Maintenance;
|
namespace App\Console\Commands\Maintenance;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
use App\Models\Backup;
|
use App\Models\Backup;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
@ -16,7 +17,7 @@ class PruneOrphanedBackupsCommand extends Command
|
|||||||
{
|
{
|
||||||
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
||||||
if (!$since || !is_digit($since)) {
|
if (!$since || !is_digit($since)) {
|
||||||
throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = Backup::query()
|
$query = Backup::query()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Node;
|
namespace App\Console\Commands\Node;
|
||||||
|
|
||||||
|
use App\Exceptions\Model\DataValidationException;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ class MakeNodeCommand extends Command
|
|||||||
/**
|
/**
|
||||||
* Handle the command execution process.
|
* Handle the command execution process.
|
||||||
*
|
*
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
* @throws DataValidationException
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ class NodeConfigurationCommand extends Command
|
|||||||
{
|
{
|
||||||
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
|
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
|
||||||
|
|
||||||
/** @var \App\Models\Node $node */
|
/** @var Node $node */
|
||||||
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
||||||
$this->error(trans('commands.node_config.error_not_exist'));
|
$this->error(trans('commands.node_config.error_not_exist'));
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ class ProcessRunnableCommand extends Command
|
|||||||
} catch (Throwable $exception) {
|
} catch (Throwable $exception) {
|
||||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||||
|
|
||||||
$this->error(trans('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage());
|
$this->error(trans('commands.schedule.process.error_message', ['schedules' => " #$schedule->id: " . $exception->getMessage()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
namespace App\Console\Commands\Server;
|
namespace App\Console\Commands\Server;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class BulkPowerActionCommand extends Command
|
class BulkPowerActionCommand extends Command
|
||||||
@ -19,7 +19,7 @@ class BulkPowerActionCommand extends Command
|
|||||||
|
|
||||||
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
||||||
|
|
||||||
public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator): void
|
public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
|
||||||
{
|
{
|
||||||
$action = $this->argument('action');
|
$action = $this->argument('action');
|
||||||
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
||||||
@ -52,7 +52,7 @@ class BulkPowerActionCommand extends Command
|
|||||||
|
|
||||||
$bar = $this->output->createProgressBar($count);
|
$bar = $this->output->createProgressBar($count);
|
||||||
|
|
||||||
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $powerRepository, &$bar): mixed {
|
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
|
||||||
$bar->clear();
|
$bar->clear();
|
||||||
|
|
||||||
if (!$server instanceof Server) {
|
if (!$server instanceof Server) {
|
||||||
@ -60,7 +60,7 @@ class BulkPowerActionCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$powerRepository->setServer($server)->send($action);
|
$serverRepository->setServer($server)->power($action);
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
$this->output->error(trans('command/messages.server.power.action_failed', [
|
$this->output->error(trans('command/messages.server.power.action_failed', [
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use App\Console\Kernel;
|
use App\Console\Kernel;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
@ -28,7 +31,7 @@ class UpgradeCommand extends Command
|
|||||||
* This places the application in maintenance mode as well while the commands
|
* This places the application in maintenance mode as well while the commands
|
||||||
* are being executed.
|
* are being executed.
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@ -129,9 +132,9 @@ class UpgradeCommand extends Command
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @var \Illuminate\Foundation\Application $app */
|
/** @var Application $app */
|
||||||
$app = require __DIR__ . '/../../../bootstrap/app.php';
|
$app = require __DIR__ . '/../../../bootstrap/app.php';
|
||||||
/** @var \App\Console\Kernel $kernel */
|
/** @var Kernel $kernel */
|
||||||
$kernel = $app->make(Kernel::class);
|
$kernel = $app->make(Kernel::class);
|
||||||
$kernel->bootstrap();
|
$kernel->bootstrap();
|
||||||
$this->setLaravel($app);
|
$this->setLaravel($app);
|
||||||
@ -174,7 +177,7 @@ class UpgradeCommand extends Command
|
|||||||
$this->info(trans('commands.upgrade.success'));
|
$this->info(trans('commands.upgrade.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function withProgress(ProgressBar $bar, \Closure $callback): void
|
protected function withProgress(ProgressBar $bar, Closure $callback): void
|
||||||
{
|
{
|
||||||
$bar->clear();
|
$bar->clear();
|
||||||
$callback();
|
$callback();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\User;
|
namespace App\Console\Commands\User;
|
||||||
|
|
||||||
|
use App\Exceptions\Model\DataValidationException;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
@ -14,20 +15,22 @@ class DisableTwoFactorCommand extends Command
|
|||||||
/**
|
/**
|
||||||
* Handle command execution process.
|
* Handle command execution process.
|
||||||
*
|
*
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
* @throws DataValidationException
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
if ($this->input->isInteractive()) {
|
if ($this->input->isInteractive()) {
|
||||||
$this->output->warning(trans('command/messages.user.2fa_help_text.0') . trans('command/messages.user.2fa_help_text.1'));
|
$this->output->warning(trans('command/messages.user.2fa_help_text'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
||||||
|
|
||||||
$user = User::query()->where('email', $email)->firstOrFail();
|
$user = User::where('email', $email)->firstOrFail();
|
||||||
$user->use_totp = false;
|
$user->update([
|
||||||
$user->totp_secret = null;
|
'mfa_app_secret' => null,
|
||||||
$user->save();
|
'mfa_app_recovery_codes' => null,
|
||||||
|
'mfa_email_enabled' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
|
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\User;
|
namespace App\Console\Commands\User;
|
||||||
|
|
||||||
|
use App\Exceptions\Model\DataValidationException;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use App\Services\Users\UserCreationService;
|
use App\Services\Users\UserCreationService;
|
||||||
@ -25,7 +26,7 @@ class MakeUserCommand extends Command
|
|||||||
* Handle command request to create a new user.
|
* Handle command request to create a new user.
|
||||||
*
|
*
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
* @throws DataValidationException
|
||||||
*/
|
*/
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
||||||
|
use App\Console\Commands\Egg\UpdateEggIndexCommand;
|
||||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||||
@ -41,7 +42,9 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||||
$schedule->command(PruneImagesCommand::class)->daily();
|
$schedule->command(PruneImagesCommand::class)->daily();
|
||||||
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
|
|
||||||
|
$schedule->command(CheckEggUpdatesCommand::class)->daily();
|
||||||
|
$schedule->command(UpdateEggIndexCommand::class)->daily();
|
||||||
|
|
||||||
if (config('backups.prune_age')) {
|
if (config('backups.prune_age')) {
|
||||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||||
|
@ -32,6 +32,6 @@ enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
|||||||
|
|
||||||
public function getLabel(): string
|
public function getLabel(): string
|
||||||
{
|
{
|
||||||
return str($this->value)->headline();
|
return trans('server/backup.backup_status.' . $this->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
|||||||
|
|
||||||
public function getLabel(): string
|
public function getLabel(): string
|
||||||
{
|
{
|
||||||
return str($this->value)->title();
|
return trans('server/console.status.' . $this->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isOffline(): bool
|
public function isOffline(): bool
|
||||||
|
37
app/Enums/CustomizationKey.php
Normal file
37
app/Enums/CustomizationKey.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum CustomizationKey: string
|
||||||
|
{
|
||||||
|
case ConsoleRows = 'console_rows';
|
||||||
|
case ConsoleFont = 'console_font';
|
||||||
|
case ConsoleFontSize = 'console_font_size';
|
||||||
|
case ConsoleGraphPeriod = 'console_graph_period';
|
||||||
|
case TopNavigation = 'top_navigation';
|
||||||
|
case DashboardLayout = 'dashboard_layout';
|
||||||
|
|
||||||
|
public function getDefaultValue(): string|int|bool
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::ConsoleRows => 30,
|
||||||
|
self::ConsoleFont => 'monospace',
|
||||||
|
self::ConsoleFontSize => 14,
|
||||||
|
self::ConsoleGraphPeriod => 30,
|
||||||
|
self::TopNavigation => false,
|
||||||
|
self::DashboardLayout => 'grid',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, string|int|bool> */
|
||||||
|
public static function getDefaultCustomization(): array
|
||||||
|
{
|
||||||
|
$default = [];
|
||||||
|
|
||||||
|
foreach (self::cases() as $key) {
|
||||||
|
$default[$key->value] = $key->getDefaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
@ -1,141 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Enums;
|
|
||||||
|
|
||||||
use Filament\Support\Contracts\HasLabel;
|
|
||||||
|
|
||||||
enum EditorLanguages: string implements HasLabel
|
|
||||||
{
|
|
||||||
case plaintext = 'plaintext';
|
|
||||||
case abap = 'abap';
|
|
||||||
case apex = 'apex';
|
|
||||||
case azcali = 'azcali';
|
|
||||||
case bat = 'bat';
|
|
||||||
case bicep = 'bicep';
|
|
||||||
case cameligo = 'cameligo';
|
|
||||||
case coljure = 'coljure';
|
|
||||||
case coffeescript = 'coffeescript';
|
|
||||||
case c = 'c';
|
|
||||||
case cpp = 'cpp';
|
|
||||||
case csharp = 'csharp';
|
|
||||||
case csp = 'csp';
|
|
||||||
case css = 'css';
|
|
||||||
case cypher = 'cypher';
|
|
||||||
case dart = 'dart';
|
|
||||||
case dockerfile = 'dockerfile';
|
|
||||||
case ecl = 'ecl';
|
|
||||||
case elixir = 'elixir';
|
|
||||||
case flow9 = 'flow9';
|
|
||||||
case fsharp = 'fsharp';
|
|
||||||
case go = 'go';
|
|
||||||
case graphql = 'graphql';
|
|
||||||
case handlebars = 'handlebars';
|
|
||||||
case hcl = 'hcl';
|
|
||||||
case html = 'html';
|
|
||||||
case ini = 'ini';
|
|
||||||
case java = 'java';
|
|
||||||
case javascript = 'javascript';
|
|
||||||
case julia = 'julia';
|
|
||||||
case json = 'json';
|
|
||||||
case kotlin = 'kotlin';
|
|
||||||
case less = 'less';
|
|
||||||
case lexon = 'lexon';
|
|
||||||
case lua = 'lua';
|
|
||||||
case liquid = 'liquid';
|
|
||||||
case m3 = 'm3';
|
|
||||||
case markdown = 'markdown';
|
|
||||||
case mdx = 'mdx';
|
|
||||||
case mips = 'mips';
|
|
||||||
case msdax = 'msdax';
|
|
||||||
case mysql = 'mysql';
|
|
||||||
case objectivec = 'objective-c';
|
|
||||||
case pascal = 'pascal';
|
|
||||||
case pascaligo = 'pascaligo';
|
|
||||||
case perl = 'perl';
|
|
||||||
case pgsql = 'pgsql';
|
|
||||||
case php = 'php';
|
|
||||||
case pla = 'pla';
|
|
||||||
case postiats = 'postiats';
|
|
||||||
case powerquery = 'powerquery';
|
|
||||||
case powershell = 'powershell';
|
|
||||||
case proto = 'proto';
|
|
||||||
case pug = 'pug';
|
|
||||||
case python = 'python';
|
|
||||||
case qsharp = 'qsharp';
|
|
||||||
case r = 'r';
|
|
||||||
case razor = 'razor';
|
|
||||||
case redis = 'redis';
|
|
||||||
case redshift = 'redshift';
|
|
||||||
case restructuredtext = 'restructuredtext';
|
|
||||||
case ruby = 'ruby';
|
|
||||||
case rust = 'rust';
|
|
||||||
case sb = 'sb';
|
|
||||||
case scala = 'scala';
|
|
||||||
case scheme = 'scheme';
|
|
||||||
case scss = 'scss';
|
|
||||||
case shell = 'shell';
|
|
||||||
case sol = 'sol';
|
|
||||||
case aes = 'aes';
|
|
||||||
case sparql = 'sparql';
|
|
||||||
case sql = 'sql';
|
|
||||||
case st = 'st';
|
|
||||||
case swift = 'swift';
|
|
||||||
case systemverilog = 'systemverilog';
|
|
||||||
case verilog = 'verilog';
|
|
||||||
case tcl = 'tcl';
|
|
||||||
case twig = 'twig';
|
|
||||||
case typescript = 'typescript';
|
|
||||||
case typespec = 'typespec';
|
|
||||||
case vb = 'vb';
|
|
||||||
case wgsl = 'wgsl';
|
|
||||||
case xml = 'xml';
|
|
||||||
case yaml = 'yaml';
|
|
||||||
|
|
||||||
public static function fromWithAlias(string $match): self
|
|
||||||
{
|
|
||||||
return match ($match) {
|
|
||||||
'h' => self::c,
|
|
||||||
|
|
||||||
'cc', 'hpp' => self::cpp,
|
|
||||||
|
|
||||||
'cs' => self::csharp,
|
|
||||||
|
|
||||||
'class' => self::java,
|
|
||||||
|
|
||||||
'htm' => self::html,
|
|
||||||
|
|
||||||
'js', 'mjs', 'cjs' => self::javascript,
|
|
||||||
|
|
||||||
'kt', 'kts' => self::kotlin,
|
|
||||||
|
|
||||||
'md' => self::markdown,
|
|
||||||
|
|
||||||
'm' => self::objectivec,
|
|
||||||
|
|
||||||
'pl', 'pm' => self::perl,
|
|
||||||
|
|
||||||
'php3', 'php4', 'php5', 'phtml' => self::php,
|
|
||||||
|
|
||||||
'py', 'pyc', 'pyo', 'pyi' => self::python,
|
|
||||||
|
|
||||||
'rdata', 'rds' => self::r,
|
|
||||||
|
|
||||||
'rb', 'erb' => self::ruby,
|
|
||||||
|
|
||||||
'sc' => self::scala,
|
|
||||||
|
|
||||||
'sh', 'zsh' => self::shell,
|
|
||||||
|
|
||||||
'ts', 'tsx' => self::typescript,
|
|
||||||
|
|
||||||
'yml' => self::yaml,
|
|
||||||
|
|
||||||
default => self::tryFrom($match) ?? self::plaintext,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLabel(): string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
}
|
|
9
app/Enums/EggFormat.php
Normal file
9
app/Enums/EggFormat.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum EggFormat: string
|
||||||
|
{
|
||||||
|
case YAML = 'yaml';
|
||||||
|
case JSON = 'json';
|
||||||
|
}
|
27
app/Enums/ScheduleStatus.php
Normal file
27
app/Enums/ScheduleStatus.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum ScheduleStatus: string implements HasColor, HasLabel
|
||||||
|
{
|
||||||
|
case Inactive = 'inactive';
|
||||||
|
case Processing = 'processing';
|
||||||
|
case Active = 'active';
|
||||||
|
|
||||||
|
public function getColor(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Inactive => 'danger',
|
||||||
|
self::Processing => 'warning',
|
||||||
|
self::Active => 'success',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('server/schedule.schedule_status.' . $this->value);
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,50 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
enum ServerResourceType
|
use App\Models\Server;
|
||||||
|
|
||||||
|
enum ServerResourceType: string
|
||||||
{
|
{
|
||||||
case Unit;
|
case Uptime = 'uptime';
|
||||||
case Percentage;
|
case CPU = 'cpu_absolute';
|
||||||
case Time;
|
case Memory = 'memory_bytes';
|
||||||
|
case Disk = 'disk_bytes';
|
||||||
|
|
||||||
|
case CPULimit = 'cpu';
|
||||||
|
case MemoryLimit = 'memory';
|
||||||
|
case DiskLimit = 'disk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int resource amount in bytes
|
||||||
|
*/
|
||||||
|
public function getResourceAmount(Server $server): int
|
||||||
|
{
|
||||||
|
if ($this->isLimit()) {
|
||||||
|
$resourceAmount = $server->{$this->value} ?? 0;
|
||||||
|
|
||||||
|
if (!$this->isPercentage()) {
|
||||||
|
// Our limits are entered as MiB/ MB so we need to convert them to bytes
|
||||||
|
$resourceAmount *= config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resourceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $server->retrieveResources()[$this->value] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLimit(): bool
|
||||||
|
{
|
||||||
|
return $this === ServerResourceType::CPULimit || $this === ServerResourceType::MemoryLimit || $this === ServerResourceType::DiskLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTime(): bool
|
||||||
|
{
|
||||||
|
return $this === ServerResourceType::Uptime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPercentage(): bool
|
||||||
|
{
|
||||||
|
return $this === ServerResourceType::CPU || $this === ServerResourceType::CPULimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ use Filament\Support\Contracts\HasLabel;
|
|||||||
|
|
||||||
enum ServerState: string implements HasColor, HasIcon, HasLabel
|
enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||||
{
|
{
|
||||||
case Normal = 'normal';
|
|
||||||
case Installing = 'installing';
|
case Installing = 'installing';
|
||||||
case InstallFailed = 'install_failed';
|
case InstallFailed = 'install_failed';
|
||||||
case ReinstallFailed = 'reinstall_failed';
|
case ReinstallFailed = 'reinstall_failed';
|
||||||
@ -18,7 +17,6 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
|||||||
public function getIcon(): string
|
public function getIcon(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Normal => 'tabler-heart',
|
|
||||||
self::Installing => 'tabler-heart-bolt',
|
self::Installing => 'tabler-heart-bolt',
|
||||||
self::InstallFailed => 'tabler-heart-x',
|
self::InstallFailed => 'tabler-heart-x',
|
||||||
self::ReinstallFailed => 'tabler-heart-x',
|
self::ReinstallFailed => 'tabler-heart-x',
|
||||||
@ -31,14 +29,13 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
|||||||
{
|
{
|
||||||
if ($hex) {
|
if ($hex) {
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Normal, self::Installing, self::RestoringBackup => '#2563EB',
|
self::Installing, self::RestoringBackup => '#2563EB',
|
||||||
self::Suspended => '#D97706',
|
self::Suspended => '#D97706',
|
||||||
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Normal => 'primary',
|
|
||||||
self::Installing => 'primary',
|
self::Installing => 'primary',
|
||||||
self::InstallFailed => 'danger',
|
self::InstallFailed => 'danger',
|
||||||
self::ReinstallFailed => 'danger',
|
self::ReinstallFailed => 'danger',
|
||||||
|
11
app/Enums/StartupVariableType.php
Normal file
11
app/Enums/StartupVariableType.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum StartupVariableType: string
|
||||||
|
{
|
||||||
|
case Text = 'text';
|
||||||
|
case Number = 'number';
|
||||||
|
case Select = 'select';
|
||||||
|
case Toggle = 'toggle';
|
||||||
|
}
|
34
app/Enums/WebhookType.php
Normal file
34
app/Enums/WebhookType.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
|
||||||
|
enum WebhookType: string implements HasColor, HasIcon, HasLabel
|
||||||
|
{
|
||||||
|
case Regular = 'regular';
|
||||||
|
case Discord = 'discord';
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/webhook.' . $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColor(): ?string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Regular => null,
|
||||||
|
self::Discord => 'blurple',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Regular => 'tabler-world-www',
|
||||||
|
self::Discord => 'tabler-brand-discord',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@ -28,7 +29,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
|||||||
/**
|
/**
|
||||||
* DisplayException constructor.
|
* DisplayException constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
public function __construct(string $message, ?Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
}
|
}
|
||||||
@ -79,11 +80,11 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
|||||||
* Log the exception to the logs using the defined error level only if the previous
|
* Log the exception to the logs using the defined error level only if the previous
|
||||||
* exception is set.
|
* exception is set.
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function report(): void
|
public function report(): void
|
||||||
{
|
{
|
||||||
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
|
if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use PDOException;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@ -79,7 +82,7 @@ class Handler extends ExceptionHandler
|
|||||||
$this->dontReport = [];
|
$this->dontReport = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->reportable(function (\PDOException $ex) {
|
$this->reportable(function (PDOException $ex) {
|
||||||
$ex = $this->generateCleanedExceptionStack($ex);
|
$ex = $this->generateCleanedExceptionStack($ex);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ class Handler extends ExceptionHandler
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateCleanedExceptionStack(\Throwable $exception): string
|
private function generateCleanedExceptionStack(Throwable $exception): string
|
||||||
{
|
{
|
||||||
$cleanedStack = '';
|
$cleanedStack = '';
|
||||||
foreach ($exception->getTrace() as $index => $item) {
|
foreach ($exception->getTrace() as $index => $item) {
|
||||||
@ -117,11 +120,11 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Render an exception into an HTTP response.
|
* Render an exception into an HTTP response.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function render($request, \Throwable $e): Response
|
public function render($request, Throwable $e): Response
|
||||||
{
|
{
|
||||||
$connections = $this->container->make(Connection::class);
|
$connections = $this->container->make(Connection::class);
|
||||||
|
|
||||||
@ -143,7 +146,7 @@ class Handler extends ExceptionHandler
|
|||||||
* Transform a validation exception into a consistent format to be returned for
|
* Transform a validation exception into a consistent format to be returned for
|
||||||
* calls to the API.
|
* calls to the API.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*/
|
*/
|
||||||
public function invalidJson($request, ValidationException $exception): JsonResponse
|
public function invalidJson($request, ValidationException $exception): JsonResponse
|
||||||
{
|
{
|
||||||
@ -249,7 +252,7 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Return an array of exceptions that should not be reported.
|
* Return an array of exceptions that should not be reported.
|
||||||
*/
|
*/
|
||||||
public static function isReportable(\Exception $exception): bool
|
public static function isReportable(Exception $exception): bool
|
||||||
{
|
{
|
||||||
return (new self(Container::getInstance()))->shouldReport($exception);
|
return (new self(Container::getInstance()))->shouldReport($exception);
|
||||||
}
|
}
|
||||||
@ -257,7 +260,7 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Convert an authentication exception into an unauthenticated response.
|
* Convert an authentication exception into an unauthenticated response.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*/
|
*/
|
||||||
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
|
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
|
||||||
{
|
{
|
||||||
@ -291,7 +294,7 @@ class Handler extends ExceptionHandler
|
|||||||
*
|
*
|
||||||
* @return array<mixed>
|
* @return array<mixed>
|
||||||
*/
|
*/
|
||||||
public static function toArray(\Throwable $e): array
|
public static function toArray(Throwable $e): array
|
||||||
{
|
{
|
||||||
return self::exceptionToArray($e);
|
return self::exceptionToArray($e);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions\Http;
|
namespace App\Exceptions\Http;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ class HttpForbiddenException extends HttpException
|
|||||||
/**
|
/**
|
||||||
* HttpForbiddenException constructor.
|
* HttpForbiddenException constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(?string $message = null, ?\Throwable $previous = null)
|
public function __construct(?string $message = null, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
|
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions\Http\Server;
|
namespace App\Exceptions\Http\Server;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
use App\Enums\ServerState;
|
use App\Enums\ServerState;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||||
@ -12,7 +13,7 @@ class ServerStateConflictException extends ConflictHttpException
|
|||||||
* Exception thrown when the server is in an unsupported state for API access or
|
* Exception thrown when the server is in an unsupported state for API access or
|
||||||
* certain operations within the codebase.
|
* certain operations within the codebase.
|
||||||
*/
|
*/
|
||||||
public function __construct(Server $server, ?\Throwable $previous = null)
|
public function __construct(Server $server, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
$message = 'This server is currently in an unsupported state, please try again later.';
|
$message = 'This server is currently in an unsupported state, please try again later.';
|
||||||
if ($server->isSuspended()) {
|
if ($server->isSuspended()) {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exceptions\Http;
|
|
||||||
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|
||||||
|
|
||||||
class TwoFactorAuthRequiredException extends HttpException implements HttpExceptionInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* TwoFactorAuthRequiredException constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(?\Throwable $previous = null)
|
|
||||||
{
|
|
||||||
parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
|
||||||
use Spatie\Ignition\Contracts\Solution;
|
use Spatie\Ignition\Contracts\Solution;
|
||||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||||
|
|
||||||
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution
|
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
|
||||||
{
|
{
|
||||||
public function getSolution(): Solution
|
public function getSolution(): Solution
|
||||||
{
|
{
|
||||||
return new Solutions\ManifestDoesNotExistSolution();
|
return new ManifestDoesNotExistSolution();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
class PanelException extends \Exception {}
|
use Exception;
|
||||||
|
|
||||||
|
class PanelException extends Exception {}
|
||||||
|
7
app/Exceptions/Repository/FileExistsException.php
Normal file
7
app/Exceptions/Repository/FileExistsException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions\Repository;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FileExistsException extends Exception {}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions\Service\Deployment;
|
||||||
|
|
||||||
|
use App\Exceptions\DisplayException;
|
||||||
|
|
||||||
|
class NoViableNodeException extends DisplayException {}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions\Service;
|
namespace App\Exceptions\Service;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
use App\Exceptions\DisplayException;
|
use App\Exceptions\DisplayException;
|
||||||
|
|
||||||
class ServiceLimitExceededException extends DisplayException
|
class ServiceLimitExceededException extends DisplayException
|
||||||
@ -10,7 +11,7 @@ class ServiceLimitExceededException extends DisplayException
|
|||||||
* Exception thrown when something goes over a defined limit, such as allocated
|
* Exception thrown when something goes over a defined limit, such as allocated
|
||||||
* ports, tasks, databases, etc.
|
* ports, tasks, databases, etc.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, ?\Throwable $previous = null)
|
public function __construct(string $message, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exceptions\Service\User;
|
|
||||||
|
|
||||||
use App\Exceptions\DisplayException;
|
|
||||||
|
|
||||||
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
|
||||||
{
|
|
||||||
public string $title = 'Invalid 2FA Code';
|
|
||||||
|
|
||||||
public string $icon = 'tabler-2fa';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct('The provided two-factor authentication token was not valid.');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\Avatar;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
abstract class AvatarProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, static>
|
|
||||||
*/
|
|
||||||
protected static array $providers = [];
|
|
||||||
|
|
||||||
public static function getProvider(string $id): ?self
|
|
||||||
{
|
|
||||||
return Arr::get(static::$providers, $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, static>
|
|
||||||
*/
|
|
||||||
public static function getAll(): array
|
|
||||||
{
|
|
||||||
return static::$providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
static::$providers[$this->getId()] = $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function getId(): string;
|
|
||||||
|
|
||||||
abstract public function get(User $user): ?string;
|
|
||||||
|
|
||||||
public function getName(): string
|
|
||||||
{
|
|
||||||
return Str::title($this->getId());
|
|
||||||
}
|
|
||||||
}
|
|
14
app/Extensions/Avatar/AvatarSchemaInterface.php
Normal file
14
app/Extensions/Avatar/AvatarSchemaInterface.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Avatar;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
interface AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
public function get(User $user): ?string;
|
||||||
|
}
|
55
app/Extensions/Avatar/AvatarService.php
Normal file
55
app/Extensions/Avatar/AvatarService.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Avatar;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class AvatarService
|
||||||
|
{
|
||||||
|
/** @var AvatarSchemaInterface[] */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly bool $allowUploadedAvatars,
|
||||||
|
private readonly string $activeSchema,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function get(string $id): ?AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveSchema(): ?AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
return $this->get($this->activeSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvatarUrl(User $user): ?string
|
||||||
|
{
|
||||||
|
if ($this->allowUploadedAvatars) {
|
||||||
|
$path = "avatars/$user->id.png";
|
||||||
|
|
||||||
|
if (Storage::disk('public')->exists($path)) {
|
||||||
|
return Storage::url($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getActiveSchema()?->get($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(AvatarSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, string> */
|
||||||
|
public function getMappings(): array
|
||||||
|
{
|
||||||
|
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Avatar\Providers;
|
namespace App\Extensions\Avatar\Schemas;
|
||||||
|
|
||||||
use App\Extensions\Avatar\AvatarProvider;
|
use App\Extensions\Avatar\AvatarSchemaInterface;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
|
||||||
class GravatarProvider extends AvatarProvider
|
class GravatarSchema implements AvatarSchemaInterface
|
||||||
{
|
{
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
{
|
{
|
||||||
return 'gravatar';
|
return 'gravatar';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'Gravatar';
|
||||||
|
}
|
||||||
|
|
||||||
public function get(User $user): string
|
public function get(User $user): string
|
||||||
{
|
{
|
||||||
return 'https://gravatar.com/avatar/' . md5($user->email);
|
return 'https://gravatar.com/avatar/' . md5($user->email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(): self
|
|
||||||
{
|
|
||||||
return new self();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Avatar\Providers;
|
namespace App\Extensions\Avatar\Schemas;
|
||||||
|
|
||||||
use App\Extensions\Avatar\AvatarProvider;
|
use App\Extensions\Avatar\AvatarSchemaInterface;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
|
||||||
class UiAvatarsProvider extends AvatarProvider
|
class UiAvatarsSchema implements AvatarSchemaInterface
|
||||||
{
|
{
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
{
|
{
|
||||||
@ -22,9 +22,4 @@ class UiAvatarsProvider extends AvatarProvider
|
|||||||
// UI Avatars is the default of filament so just return null here
|
// UI Avatars is the default of filament so just return null here
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(): self
|
|
||||||
{
|
|
||||||
return new self();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Backups;
|
namespace App\Extensions\Backups;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Aws\S3\S3Client;
|
use Aws\S3\S3Client;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
@ -64,7 +65,7 @@ class BackupManager
|
|||||||
$config = $this->getConfig($name);
|
$config = $this->getConfig($name);
|
||||||
|
|
||||||
if (empty($config['adapter'])) {
|
if (empty($config['adapter'])) {
|
||||||
throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$adapter = $config['adapter'];
|
$adapter = $config['adapter'];
|
||||||
@ -82,7 +83,7 @@ class BackupManager
|
|||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \InvalidArgumentException("Adapter [$adapter] is not supported.");
|
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
48
app/Extensions/Captcha/CaptchaService.php
Normal file
48
app/Extensions/Captcha/CaptchaService.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha;
|
||||||
|
|
||||||
|
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class CaptchaService
|
||||||
|
{
|
||||||
|
/** @var array<string, CaptchaSchemaInterface> */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CaptchaSchemaInterface[]
|
||||||
|
*/
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $id): ?CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(CaptchaSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config()->set('captcha.' . Str::lower($schema->getId()), $schema->getConfig());
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Collection<CaptchaSchemaInterface> */
|
||||||
|
public function getActiveSchemas(): Collection
|
||||||
|
{
|
||||||
|
return collect($this->schemas)
|
||||||
|
->filter(fn (CaptchaSchemaInterface $schema) => $schema->isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveSchema(): ?CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
return $this->getActiveSchemas()->first();
|
||||||
|
}
|
||||||
|
}
|
@ -1,118 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\Captcha\Providers;
|
|
||||||
|
|
||||||
use Filament\Forms\Components\Component;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
abstract class CaptchaProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, static>
|
|
||||||
*/
|
|
||||||
protected static array $providers = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return self|static[]
|
|
||||||
*/
|
|
||||||
public static function get(?string $id = null): array|self
|
|
||||||
{
|
|
||||||
return $id ? static::$providers[$id] : static::$providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
if (array_key_exists($this->getId(), static::$providers)) {
|
|
||||||
if (!$this->app->runningUnitTests()) {
|
|
||||||
logger()->warning("Tried to create duplicate Captcha provider with id '{$this->getId()}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
config()->set('captcha.' . Str::lower($this->getId()), $this->getConfig());
|
|
||||||
|
|
||||||
static::$providers[$this->getId()] = $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function getId(): string;
|
|
||||||
|
|
||||||
abstract public function getComponent(): Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string|string[]|bool|null>
|
|
||||||
*/
|
|
||||||
public function getConfig(): array
|
|
||||||
{
|
|
||||||
$id = Str::upper($this->getId());
|
|
||||||
|
|
||||||
return [
|
|
||||||
'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
|
|
||||||
'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Component[]
|
|
||||||
*/
|
|
||||||
public function getSettingsForm(): array
|
|
||||||
{
|
|
||||||
$id = Str::upper($this->getId());
|
|
||||||
|
|
||||||
return [
|
|
||||||
TextInput::make("CAPTCHA_{$id}_SITE_KEY")
|
|
||||||
->label('Site Key')
|
|
||||||
->placeholder('Site Key')
|
|
||||||
->columnSpan(2)
|
|
||||||
->required()
|
|
||||||
->password()
|
|
||||||
->revealable()
|
|
||||||
->autocomplete(false)
|
|
||||||
->default(env("CAPTCHA_{$id}_SITE_KEY")),
|
|
||||||
TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
|
|
||||||
->label('Secret Key')
|
|
||||||
->placeholder('Secret Key')
|
|
||||||
->columnSpan(2)
|
|
||||||
->required()
|
|
||||||
->password()
|
|
||||||
->revealable()
|
|
||||||
->autocomplete(false)
|
|
||||||
->default(env("CAPTCHA_{$id}_SECRET_KEY")),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string
|
|
||||||
{
|
|
||||||
return Str::title($this->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): ?string
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEnabled(): bool
|
|
||||||
{
|
|
||||||
$id = Str::upper($this->getId());
|
|
||||||
|
|
||||||
return env("CAPTCHA_{$id}_ENABLED", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string|bool>
|
|
||||||
*/
|
|
||||||
public function validateResponse(?string $captchaResponse = null): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'validateResponse not defined',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\Captcha\Providers;
|
|
||||||
|
|
||||||
use App\Filament\Components\Forms\Fields\TurnstileCaptcha;
|
|
||||||
use Exception;
|
|
||||||
use Filament\Forms\Components\Component;
|
|
||||||
use Filament\Forms\Components\Placeholder;
|
|
||||||
use Filament\Forms\Components\Toggle;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\HtmlString;
|
|
||||||
|
|
||||||
class TurnstileProvider extends CaptchaProvider
|
|
||||||
{
|
|
||||||
public function getId(): string
|
|
||||||
{
|
|
||||||
return 'turnstile';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getComponent(): Component
|
|
||||||
{
|
|
||||||
return TurnstileCaptcha::make('turnstile');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string|string[]|bool|null>
|
|
||||||
*/
|
|
||||||
public function getConfig(): array
|
|
||||||
{
|
|
||||||
return array_merge(parent::getConfig(), [
|
|
||||||
'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Component[]
|
|
||||||
*/
|
|
||||||
public function getSettingsForm(): array
|
|
||||||
{
|
|
||||||
return array_merge(parent::getSettingsForm(), [
|
|
||||||
Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
|
|
||||||
->label(trans('admin/setting.captcha.verify'))
|
|
||||||
->columnSpan(2)
|
|
||||||
->inline(false)
|
|
||||||
->onIcon('tabler-check')
|
|
||||||
->offIcon('tabler-x')
|
|
||||||
->onColor('success')
|
|
||||||
->offColor('danger')
|
|
||||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
|
||||||
Placeholder::make('info')
|
|
||||||
->label(trans('admin/setting.captcha.info_label'))
|
|
||||||
->columnSpan(2)
|
|
||||||
->content(new HtmlString(trans('admin/setting.captcha.info'))),
|
|
||||||
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): string
|
|
||||||
{
|
|
||||||
return 'tabler-brand-cloudflare';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string|bool>
|
|
||||||
*/
|
|
||||||
public function validateResponse(?string $captchaResponse = null): array
|
|
||||||
{
|
|
||||||
$captchaResponse ??= request()->get('cf-turnstile-response');
|
|
||||||
|
|
||||||
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
|
|
||||||
throw new Exception('Turnstile secret key is not defined.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = Http::asJson()
|
|
||||||
->timeout(15)
|
|
||||||
->connectTimeout(5)
|
|
||||||
->throw()
|
|
||||||
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
|
||||||
'secret' => $secret,
|
|
||||||
'response' => $captchaResponse,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return count($response->json()) ? $response->json() : [
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Unknown error occurred, please try again',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
|
|
||||||
{
|
|
||||||
if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$requestUrl ??= request()->url;
|
|
||||||
$requestUrl = parse_url($requestUrl);
|
|
||||||
|
|
||||||
return $hostname === array_get($requestUrl, 'host');
|
|
||||||
}
|
|
||||||
}
|
|
59
app/Extensions/Captcha/Schemas/BaseSchema.php
Normal file
59
app/Extensions/Captcha/Schemas/BaseSchema.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
abstract class BaseSchema
|
||||||
|
{
|
||||||
|
abstract public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return Str::upper($this->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
|
||||||
|
'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Component[]
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
TextInput::make("CAPTCHA_{$id}_SITE_KEY")
|
||||||
|
->label('Site Key')
|
||||||
|
->placeholder('Site Key')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->autocomplete(false)
|
||||||
|
->default(env("CAPTCHA_{$id}_SITE_KEY")),
|
||||||
|
TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
|
||||||
|
->label('Secret Key')
|
||||||
|
->placeholder('Secret Key')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->autocomplete(false)
|
||||||
|
->default(env("CAPTCHA_{$id}_SECRET_KEY")),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
30
app/Extensions/Captcha/Schemas/CaptchaSchemaInterface.php
Normal file
30
app/Extensions/Captcha/Schemas/CaptchaSchemaInterface.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
|
||||||
|
interface CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getConfig(): array;
|
||||||
|
|
||||||
|
public function isEnabled(): bool;
|
||||||
|
|
||||||
|
public function getFormComponent(): Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Component[]
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array;
|
||||||
|
|
||||||
|
public function getIcon(): ?string;
|
||||||
|
|
||||||
|
public function validateResponse(?string $captchaResponse = null): void;
|
||||||
|
}
|
@ -1,11 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Components\Forms\Fields;
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
use App\Rules\ValidTurnstileCaptcha;
|
|
||||||
use Filament\Forms\Components\Field;
|
use Filament\Forms\Components\Field;
|
||||||
|
|
||||||
class TurnstileCaptcha extends Field
|
class Component extends Field
|
||||||
{
|
{
|
||||||
protected string $viewIdentifier = 'turnstile';
|
protected string $viewIdentifier = 'turnstile';
|
||||||
|
|
||||||
@ -19,8 +18,6 @@ class TurnstileCaptcha extends Field
|
|||||||
|
|
||||||
$this->required();
|
$this->required();
|
||||||
|
|
||||||
$this->after(function (TurnstileCaptcha $component) {
|
$this->rule(new Rule());
|
||||||
$component->rule(new ValidTurnstileCaptcha());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
23
app/Extensions/Captcha/Schemas/Turnstile/Rule.php
Normal file
23
app/Extensions/Captcha/Schemas/Turnstile/Rule.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
|
use App\Extensions\Captcha\CaptchaService;
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
|
class Rule implements ValidationRule
|
||||||
|
{
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
App::call(fn (CaptchaService $service) => $service->get('turnstile')->validateResponse($value));
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
$fail('Captcha validation failed: ' . $exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
app/Extensions/Captcha/Schemas/Turnstile/TurnstileSchema.php
Normal file
117
app/Extensions/Captcha/Schemas/Turnstile/TurnstileSchema.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
|
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||||
|
use App\Extensions\Captcha\Schemas\BaseSchema;
|
||||||
|
use Exception;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
|
class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return 'turnstile';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEnabled(): bool
|
||||||
|
{
|
||||||
|
return env('CAPTCHA_TURNSTILE_ENABLED', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormComponent(): Component
|
||||||
|
{
|
||||||
|
return Component::make('turnstile');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
return array_merge(parent::getConfig(), [
|
||||||
|
'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Filament\Support\Components\Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array
|
||||||
|
{
|
||||||
|
return array_merge(parent::getSettingsForm(), [
|
||||||
|
Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
|
||||||
|
->label(trans('admin/setting.captcha.verify'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||||
|
TextEntry::make('info')
|
||||||
|
->label(trans('admin/setting.captcha.info_label'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->state(new HtmlString(trans('admin/setting.captcha.info'))),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): ?string
|
||||||
|
{
|
||||||
|
return 'tabler-brand-cloudflare';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function validateResponse(?string $captchaResponse = null): void
|
||||||
|
{
|
||||||
|
$captchaResponse ??= request()->get('cf-turnstile-response');
|
||||||
|
|
||||||
|
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
|
||||||
|
throw new Exception('Turnstile secret key is not defined.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = Http::asJson()
|
||||||
|
->timeout(15)
|
||||||
|
->connectTimeout(5)
|
||||||
|
->throw()
|
||||||
|
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
||||||
|
'secret' => $secret,
|
||||||
|
'response' => $captchaResponse,
|
||||||
|
])
|
||||||
|
->json();
|
||||||
|
|
||||||
|
if (!$response['success']) {
|
||||||
|
match ($response['error-codes'][0] ?? null) {
|
||||||
|
'missing-input-secret' => throw new Exception('The secret parameter was not passed.'),
|
||||||
|
'invalid-input-secret' => throw new Exception('The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.'),
|
||||||
|
'missing-input-response' => throw new Exception('The response parameter (token) was not passed.'),
|
||||||
|
'invalid-input-response' => throw new Exception('The response parameter (token) is invalid or has expired.'),
|
||||||
|
'bad-request' => throw new Exception('The request was rejected because it was malformed.'),
|
||||||
|
'timeout-or-duplicate' => throw new Exception('The response parameter (token) has already been validated before.'),
|
||||||
|
default => throw new Exception('An internal error happened while validating the response.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->verifyDomain($response['hostname'] ?? '')) {
|
||||||
|
throw new Exception('Domain verification failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyDomain(string $hostname): bool
|
||||||
|
{
|
||||||
|
if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestUrl = parse_url(request()->url());
|
||||||
|
|
||||||
|
return $hostname === array_get($requestUrl, 'host');
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\Features;
|
|
||||||
|
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
|
|
||||||
abstract class FeatureProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, static>
|
|
||||||
*/
|
|
||||||
protected static array $providers = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $id
|
|
||||||
* @return self|static[]
|
|
||||||
*/
|
|
||||||
public static function getProviders(string|array|null $id = null): array|self
|
|
||||||
{
|
|
||||||
if (is_array($id)) {
|
|
||||||
return array_intersect_key(static::$providers, array_flip($id));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $id ? static::$providers[$id] : static::$providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
if (array_key_exists($this->getId(), static::$providers)) {
|
|
||||||
if (!$this->app->runningUnitTests()) {
|
|
||||||
logger()->warning("Tried to create duplicate Feature provider with id '{$this->getId()}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static::$providers[$this->getId()] = $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function getId(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A matching subset string (case-insensitive) from the console output
|
|
||||||
*
|
|
||||||
* @return array<string>
|
|
||||||
*/
|
|
||||||
abstract public function getListeners(): array;
|
|
||||||
|
|
||||||
abstract public function getAction(): Action;
|
|
||||||
}
|
|
15
app/Extensions/Features/FeatureSchemaInterface.php
Normal file
15
app/Extensions/Features/FeatureSchemaInterface.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Features;
|
||||||
|
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
|
||||||
|
interface FeatureSchemaInterface
|
||||||
|
{
|
||||||
|
/** @return string[] */
|
||||||
|
public function getListeners(): array;
|
||||||
|
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getAction(): Action;
|
||||||
|
}
|
52
app/Extensions/Features/FeatureService.php
Normal file
52
app/Extensions/Features/FeatureService.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Features;
|
||||||
|
|
||||||
|
class FeatureService
|
||||||
|
{
|
||||||
|
/** @var FeatureSchemaInterface[] */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FeatureSchemaInterface[]
|
||||||
|
*/
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $id): ?FeatureSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ?string[] $features
|
||||||
|
* @return FeatureSchemaInterface[]
|
||||||
|
*/
|
||||||
|
public function getActiveSchemas(?array $features = []): array
|
||||||
|
{
|
||||||
|
return collect($this->schemas)->only($features)->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(FeatureSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ?string[] $features
|
||||||
|
* @return array<string, array<string>>
|
||||||
|
*/
|
||||||
|
public function getMappings(?array $features = []): array
|
||||||
|
{
|
||||||
|
return collect($this->getActiveSchemas($features))
|
||||||
|
->mapWithKeys(fn (FeatureSchemaInterface $schema) => [
|
||||||
|
$schema->getId() => $schema->getListeners(),
|
||||||
|
])->all();
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Features;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use App\Facades\Activity;
|
use App\Facades\Activity;
|
||||||
use App\Models\Permission;
|
use App\Models\Permission;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServerVariable;
|
use App\Models\ServerVariable;
|
||||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Forms\Components\Placeholder;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class GSLToken extends FeatureProvider
|
class GSLTokenSchema implements FeatureSchemaInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string> */
|
/** @return array<string> */
|
||||||
public function getListeners(): array
|
public function getListeners(): array
|
||||||
{
|
{
|
||||||
@ -41,6 +36,9 @@ class GSLToken extends FeatureProvider
|
|||||||
return 'gsl_token';
|
return 'gsl_token';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public function getAction(): Action
|
public function getAction(): Action
|
||||||
{
|
{
|
||||||
/** @var Server $server */
|
/** @var Server $server */
|
||||||
@ -56,9 +54,9 @@ class GSLToken extends FeatureProvider
|
|||||||
->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')
|
||||||
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||||
->form([
|
->schema([
|
||||||
Placeholder::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.'))),
|
||||||
TextInput::make('gsltoken')
|
TextInput::make('gsltoken')
|
||||||
->label('GSL Token')
|
->label('GSL Token')
|
||||||
@ -75,13 +73,12 @@ class GSLToken extends FeatureProvider
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
->hintIcon('tabler-code')
|
->hintIcon('tabler-code', fn () => implode('|', $serverVariable->variable->rules))
|
||||||
->label(fn () => $serverVariable->variable->name)
|
->label(fn () => $serverVariable->variable->name)
|
||||||
->hintIconTooltip(fn () => implode('|', $serverVariable->variable->rules))
|
|
||||||
->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),
|
||||||
])
|
])
|
||||||
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server, $serverVariable) {
|
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server, $serverVariable) {
|
||||||
/** @var Server $server */
|
/** @var Server $server */
|
||||||
$server = Filament::getTenant();
|
$server = Filament::getTenant();
|
||||||
try {
|
try {
|
||||||
@ -103,7 +100,7 @@ class GSLToken extends FeatureProvider
|
|||||||
->log();
|
->log();
|
||||||
}
|
}
|
||||||
|
|
||||||
$powerRepository->setServer($server)->send('restart');
|
$serverRepository->setServer($server)->power('restart');
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('GSL Token updated')
|
->title('GSL Token updated')
|
||||||
@ -119,9 +116,4 @@ class GSLToken extends FeatureProvider
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,26 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Features;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use App\Facades\Activity;
|
use App\Facades\Activity;
|
||||||
use App\Models\Permission;
|
use App\Models\Permission;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Forms\Components\Placeholder;
|
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
|
|
||||||
class JavaVersion extends FeatureProvider
|
class JavaVersionSchema implements FeatureSchemaInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string> */
|
/** @return array<string> */
|
||||||
public function getListeners(): array
|
public function getListeners(): array
|
||||||
{
|
{
|
||||||
@ -49,9 +44,9 @@ class JavaVersion extends FeatureProvider
|
|||||||
->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')
|
||||||
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
||||||
->form([
|
->schema([
|
||||||
Placeholder::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.'),
|
||||||
Select::make('image')
|
Select::make('image')
|
||||||
->label('Docker Image')
|
->label('Docker Image')
|
||||||
@ -64,7 +59,7 @@ class JavaVersion extends FeatureProvider
|
|||||||
->preload()
|
->preload()
|
||||||
->native(false),
|
->native(false),
|
||||||
])
|
])
|
||||||
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server) {
|
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server) {
|
||||||
try {
|
try {
|
||||||
$new = $data['image'];
|
$new = $data['image'];
|
||||||
$original = $server->image;
|
$original = $server->image;
|
||||||
@ -76,7 +71,7 @@ class JavaVersion extends FeatureProvider
|
|||||||
->log();
|
->log();
|
||||||
}
|
}
|
||||||
|
|
||||||
$powerRepository->setServer($server)->send('restart');
|
$serverRepository->setServer($server)->power('restart');
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Docker image updated')
|
->title('Docker image updated')
|
||||||
@ -92,9 +87,4 @@ class JavaVersion extends FeatureProvider
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,25 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Features;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Repositories\Daemon\DaemonFileRepository;
|
use App\Repositories\Daemon\DaemonFileRepository;
|
||||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class MinecraftEula extends FeatureProvider
|
class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string> */
|
/** @return array<string> */
|
||||||
public function getListeners(): array
|
public function getListeners(): array
|
||||||
{
|
{
|
||||||
@ -40,14 +35,14 @@ class MinecraftEula extends FeatureProvider
|
|||||||
->modalHeading('Minecraft EULA')
|
->modalHeading('Minecraft EULA')
|
||||||
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
|
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
|
||||||
->modalSubmitActionLabel('I Accept')
|
->modalSubmitActionLabel('I Accept')
|
||||||
->action(function (DaemonFileRepository $fileRepository, DaemonPowerRepository $powerRepository) {
|
->action(function (DaemonFileRepository $fileRepository, DaemonServerRepository $serverRepository) {
|
||||||
try {
|
try {
|
||||||
/** @var Server $server */
|
/** @var Server $server */
|
||||||
$server = Filament::getTenant();
|
$server = Filament::getTenant();
|
||||||
|
|
||||||
$fileRepository->setServer($server)->putContent('eula.txt', 'eula=true');
|
$fileRepository->setServer($server)->putContent('eula.txt', 'eula=true');
|
||||||
|
|
||||||
$powerRepository->setServer($server)->send('restart');
|
$serverRepository->setServer($server)->power('restart');
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Minecraft EULA accepted')
|
->title('Minecraft EULA accepted')
|
||||||
@ -63,9 +58,4 @@ class MinecraftEula extends FeatureProvider
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,19 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Features;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class PIDLimit extends FeatureProvider
|
class PIDLimitSchema implements FeatureSchemaInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string> */
|
/** @return array<string> */
|
||||||
public function getListeners(): array
|
public function getListeners(): array
|
||||||
{
|
{
|
||||||
@ -68,9 +63,4 @@ class PIDLimit extends FeatureProvider
|
|||||||
->modalCancelActionLabel('Close')
|
->modalCancelActionLabel('Close')
|
||||||
->action(fn () => null);
|
->action(fn () => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,19 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\Features;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class SteamDiskSpace extends FeatureProvider
|
class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string> */
|
/** @return array<string> */
|
||||||
public function getListeners(): array
|
public function getListeners(): array
|
||||||
{
|
{
|
||||||
@ -56,9 +51,4 @@ class SteamDiskSpace extends FeatureProvider
|
|||||||
->modalCancelActionLabel('Close')
|
->modalCancelActionLabel('Close')
|
||||||
->action(fn () => null);
|
->action(fn () => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ use App\Models\ApiKey;
|
|||||||
use Laravel\Sanctum\NewAccessToken as SanctumAccessToken;
|
use Laravel\Sanctum\NewAccessToken as SanctumAccessToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property \App\Models\ApiKey $accessToken
|
* @property ApiKey $accessToken
|
||||||
*/
|
*/
|
||||||
class NewAccessToken extends SanctumAccessToken
|
class NewAccessToken extends SanctumAccessToken
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Lcobucci\JWT\Encoding;
|
namespace App\Extensions\Lcobucci\JWT\Encoding;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
use Lcobucci\JWT\ClaimsFormatter;
|
use Lcobucci\JWT\ClaimsFormatter;
|
||||||
use Lcobucci\JWT\Token\RegisteredClaims;
|
use Lcobucci\JWT\Token\RegisteredClaims;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ final class TimestampDates implements ClaimsFormatter
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert($claims[$claim] instanceof \DateTimeImmutable);
|
assert($claims[$claim] instanceof DateTimeImmutable);
|
||||||
$claims[$claim] = $claims[$claim]->getTimestamp();
|
$claims[$claim] = $claims[$claim]->getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
app/Extensions/OAuth/OAuthSchemaInterface.php
Normal file
39
app/Extensions/OAuth/OAuthSchemaInterface.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\OAuth;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
|
||||||
|
interface OAuthSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
public function getConfigKey(): string;
|
||||||
|
|
||||||
|
/** @return ?class-string */
|
||||||
|
public function getSocialiteProvider(): ?string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getServiceConfig(): array;
|
||||||
|
|
||||||
|
/** @return Component[] */
|
||||||
|
public function getSettingsForm(): array;
|
||||||
|
|
||||||
|
/** @return Step[] */
|
||||||
|
public function getSetupSteps(): array;
|
||||||
|
|
||||||
|
public function getIcon(): ?string;
|
||||||
|
|
||||||
|
public function getHexColor(): ?string;
|
||||||
|
|
||||||
|
public function isEnabled(): bool;
|
||||||
|
|
||||||
|
public function shouldCreateMissingUsers(): bool;
|
||||||
|
|
||||||
|
public function shouldLinkMissingUsers(): bool;
|
||||||
|
}
|
46
app/Extensions/OAuth/OAuthService.php
Normal file
46
app/Extensions/OAuth/OAuthService.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\OAuth;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||||
|
|
||||||
|
class OAuthService
|
||||||
|
{
|
||||||
|
/** @var OAuthSchemaInterface[] */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
/** @return OAuthSchemaInterface[] */
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $id): ?OAuthSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return OAuthSchemaInterface[] */
|
||||||
|
public function getEnabled(): array
|
||||||
|
{
|
||||||
|
return collect($this->schemas)
|
||||||
|
->filter(fn (OAuthSchemaInterface $schema) => $schema->isEnabled())
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(OAuthSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config()->set('services.' . $schema->getId(), array_merge($schema->getServiceConfig(), ['redirect' => '/auth/oauth/callback/' . $schema->getId()]));
|
||||||
|
|
||||||
|
if ($schema->getSocialiteProvider()) {
|
||||||
|
Event::listen(fn (SocialiteWasCalled $event) => $event->extendSocialite($schema->getId(), $schema->getSocialiteProvider()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
|
|
||||||
final class CommonProvider extends OAuthProvider
|
|
||||||
{
|
|
||||||
protected function __construct(protected Application $app, private string $id, private ?string $providerClass, private ?string $icon, private ?string $hexColor)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getProviderClass(): ?string
|
|
||||||
{
|
|
||||||
return $this->providerClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): ?string
|
|
||||||
{
|
|
||||||
return $this->icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHexColor(): ?string
|
|
||||||
{
|
|
||||||
return $this->hexColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function register(Application $app, string $id, ?string $providerClass = null, ?string $icon = null, ?string $hexColor = null): static
|
|
||||||
{
|
|
||||||
return new self($app, $id, $providerClass, $icon, $hexColor);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
|
||||||
|
|
||||||
use Filament\Forms\Components\Placeholder;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Forms\Components\Wizard\Step;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
|
||||||
use Illuminate\Support\HtmlString;
|
|
||||||
use SocialiteProviders\Discord\Provider;
|
|
||||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
|
||||||
|
|
||||||
final class DiscordProvider extends OAuthProvider
|
|
||||||
{
|
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string
|
|
||||||
{
|
|
||||||
return 'discord';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getProviderClass(): string
|
|
||||||
{
|
|
||||||
return Provider::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSetupSteps(): array
|
|
||||||
{
|
|
||||||
return array_merge([
|
|
||||||
Step::make('Register new Discord OAuth App')
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('')
|
|
||||||
->content(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</x-filament::link> and click on <b>New Application</b>. Enter a <b>Name</b> (e.g. your panel name) and click on <b>Create</b>.</p><p>Copy the <b>Client ID</b> and the <b>Client Secret</b> from the OAuth2 tab, you will need them in the final step.</p>'))),
|
|
||||||
Placeholder::make('')
|
|
||||||
->content(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
|
|
||||||
TextInput::make('_noenv_callback')
|
|
||||||
->label('Redirect URL')
|
|
||||||
->dehydrated()
|
|
||||||
->disabled()
|
|
||||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
|
||||||
->formatStateUsing(fn () => url('/auth/oauth/callback/discord')),
|
|
||||||
]),
|
|
||||||
], parent::getSetupSteps());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): string
|
|
||||||
{
|
|
||||||
return 'tabler-brand-discord-f';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHexColor(): string
|
|
||||||
{
|
|
||||||
return '#5865F2';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
|
||||||
|
|
||||||
use Filament\Forms\Components\Placeholder;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Forms\Components\Wizard\Step;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
|
||||||
use Illuminate\Support\HtmlString;
|
|
||||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
|
||||||
|
|
||||||
final class GithubProvider extends OAuthProvider
|
|
||||||
{
|
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string
|
|
||||||
{
|
|
||||||
return 'github';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSetupSteps(): array
|
|
||||||
{
|
|
||||||
return array_merge([
|
|
||||||
Step::make('Register new Github OAuth App')
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('')
|
|
||||||
->content(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
|
||||||
TextInput::make('_noenv_callback')
|
|
||||||
->label('Authorization callback URL')
|
|
||||||
->dehydrated()
|
|
||||||
->disabled()
|
|
||||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
|
||||||
->default(fn () => url('/auth/oauth/callback/github')),
|
|
||||||
Placeholder::make('')
|
|
||||||
->content(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
|
|
||||||
]),
|
|
||||||
Step::make('Create Client Secret')
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('')
|
|
||||||
->content(new HtmlString('<p>Once you registered your app, generate a new <b>Client Secret</b>.</p><p>You will also need the <b>Client ID</b>.</p>')),
|
|
||||||
]),
|
|
||||||
], parent::getSetupSteps());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): string
|
|
||||||
{
|
|
||||||
return 'tabler-brand-github-f';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHexColor(): string
|
|
||||||
{
|
|
||||||
return '#4078c0';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
|
||||||
|
|
||||||
use Filament\Forms\Components\Component;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Forms\Components\Wizard\Step;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Event;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
|
||||||
|
|
||||||
abstract class OAuthProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, static>
|
|
||||||
*/
|
|
||||||
protected static array $providers = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return self|static[]
|
|
||||||
*/
|
|
||||||
public static function get(?string $id = null): array|self
|
|
||||||
{
|
|
||||||
return $id ? static::$providers[$id] : static::$providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
if (array_key_exists($this->getId(), static::$providers)) {
|
|
||||||
if (!$this->app->runningUnitTests()) {
|
|
||||||
logger()->warning("Tried to create duplicate OAuth provider with id '{$this->getId()}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
config()->set('services.' . $this->getId(), array_merge($this->getServiceConfig(), ['redirect' => '/auth/oauth/callback/' . $this->getId()]));
|
|
||||||
|
|
||||||
if ($this->getProviderClass()) {
|
|
||||||
Event::listen(function (SocialiteWasCalled $event) {
|
|
||||||
$event->extendSocialite($this->getId(), $this->getProviderClass());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static::$providers[$this->getId()] = $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function getId(): string;
|
|
||||||
|
|
||||||
public function getProviderClass(): ?string
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string|string[]|bool|null>
|
|
||||||
*/
|
|
||||||
public function getServiceConfig(): array
|
|
||||||
{
|
|
||||||
$id = Str::upper($this->getId());
|
|
||||||
|
|
||||||
return [
|
|
||||||
'client_id' => env("OAUTH_{$id}_CLIENT_ID"),
|
|
||||||
'client_secret' => env("OAUTH_{$id}_CLIENT_SECRET"),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Component[]
|
|
||||||
*/
|
|
||||||
public function getSettingsForm(): array
|
|
||||||
{
|
|
||||||
$id = Str::upper($this->getId());
|
|
||||||
|
|
||||||
return [
|
|
||||||
TextInput::make("OAUTH_{$id}_CLIENT_ID")
|
|
||||||
->label('Client ID')
|
|
||||||
->placeholder('Client ID')
|
|
||||||
->columnSpan(2)
|
|
||||||
->required()
|
|
||||||
->password()
|
|
||||||
->revealable()
|
|
||||||
->autocomplete(false)
|
|
||||||
->default(env("OAUTH_{$id}_CLIENT_ID")),
|
|
||||||
TextInput::make("OAUTH_{$id}_CLIENT_SECRET")
|
|
||||||
->label('Client Secret')
|
|
||||||
->placeholder('Client Secret')
|
|
||||||
->columnSpan(2)
|
|
||||||
->required()
|
|
||||||
->password()
|
|
||||||
->revealable()
|
|
||||||
->autocomplete(false)
|
|
||||||
->default(env("OAUTH_{$id}_CLIENT_SECRET")),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Step[]
|
|
||||||
*/
|
|
||||||
public function getSetupSteps(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Step::make('OAuth Config')
|
|
||||||
->columns(4)
|
|
||||||
->schema($this->getSettingsForm()),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string
|
|
||||||
{
|
|
||||||
return Str::title($this->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): ?string
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHexColor(): ?string
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEnabled(): bool
|
|
||||||
{
|
|
||||||
$id = Str::upper($this->getId());
|
|
||||||
|
|
||||||
return env("OAUTH_{$id}_ENABLED", false);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
use Filament\Forms\Components\ColorPicker;
|
use Filament\Forms\Components\ColorPicker;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use SocialiteProviders\Authentik\Provider;
|
use SocialiteProviders\Authentik\Provider;
|
||||||
|
|
||||||
final class AuthentikProvider extends OAuthProvider
|
final class AuthentikSchema extends OAuthSchema
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
{
|
{
|
||||||
return 'authentik';
|
return 'authentik';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProviderClass(): string
|
public function getSocialiteProvider(): string
|
||||||
{
|
{
|
||||||
return Provider::class;
|
return Provider::class;
|
||||||
}
|
}
|
||||||
@ -66,9 +60,4 @@ final class AuthentikProvider extends OAuthProvider
|
|||||||
{
|
{
|
||||||
return env('OAUTH_AUTHENTIK_DISPLAY_COLOR', '#fd4b2d');
|
return env('OAUTH_AUTHENTIK_DISPLAY_COLOR', '#fd4b2d');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
39
app/Extensions/OAuth/Schemas/CommonSchema.php
Normal file
39
app/Extensions/OAuth/Schemas/CommonSchema.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
final class CommonSchema extends OAuthSchema
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $id,
|
||||||
|
private readonly ?string $name = null,
|
||||||
|
private readonly ?string $configName = null,
|
||||||
|
private readonly ?string $icon = null,
|
||||||
|
private readonly ?string $hexColor = null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name ?? parent::getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfigKey(): string
|
||||||
|
{
|
||||||
|
return $this->configName ?? parent::getConfigKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): ?string
|
||||||
|
{
|
||||||
|
return $this->icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHexColor(): ?string
|
||||||
|
{
|
||||||
|
return $this->hexColor;
|
||||||
|
}
|
||||||
|
}
|
54
app/Extensions/OAuth/Schemas/DiscordSchema.php
Normal file
54
app/Extensions/OAuth/Schemas/DiscordSchema.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
use SocialiteProviders\Discord\Provider;
|
||||||
|
|
||||||
|
final class DiscordSchema extends OAuthSchema
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return 'discord';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSocialiteProvider(): string
|
||||||
|
{
|
||||||
|
return Provider::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSetupSteps(): array
|
||||||
|
{
|
||||||
|
return array_merge([
|
||||||
|
Step::make('Register new Discord OAuth App')
|
||||||
|
->schema([
|
||||||
|
TextEntry::make('create_application')
|
||||||
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</x-filament::link> and click on <b>New Application</b>. Enter a <b>Name</b> (e.g. your panel name) and click on <b>Create</b>.</p><p>Copy the <b>Client ID</b> and the <b>Client Secret</b> from the OAuth2 tab, you will need them in the final step.</p>'))),
|
||||||
|
TextEntry::make('set_redirect')
|
||||||
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
|
||||||
|
TextInput::make('_noenv_callback')
|
||||||
|
->label('Redirect URL')
|
||||||
|
->dehydrated()
|
||||||
|
->disabled()
|
||||||
|
->hintCopy()
|
||||||
|
->formatStateUsing(fn () => url('/auth/oauth/callback/discord')),
|
||||||
|
]),
|
||||||
|
], parent::getSetupSteps());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return 'tabler-brand-discord-f';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHexColor(): string
|
||||||
|
{
|
||||||
|
return '#5865F2';
|
||||||
|
}
|
||||||
|
}
|
54
app/Extensions/OAuth/Schemas/GithubSchema.php
Normal file
54
app/Extensions/OAuth/Schemas/GithubSchema.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
|
final class GithubSchema extends OAuthSchema
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return 'github';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSetupSteps(): array
|
||||||
|
{
|
||||||
|
return array_merge([
|
||||||
|
Step::make('Register new Github OAuth App')
|
||||||
|
->schema([
|
||||||
|
TextEntry::make('create_application')
|
||||||
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||||
|
TextInput::make('_noenv_callback')
|
||||||
|
->label('Authorization callback URL')
|
||||||
|
->dehydrated()
|
||||||
|
->disabled()
|
||||||
|
->hintCopy()
|
||||||
|
->default(fn () => url('/auth/oauth/callback/github')),
|
||||||
|
TextEntry::make('register_application')
|
||||||
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
|
||||||
|
]),
|
||||||
|
Step::make('Create Client Secret')
|
||||||
|
->schema([
|
||||||
|
TextEntry::make('create_client_secret')
|
||||||
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString('<p>Once you registered your app, generate a new <b>Client Secret</b>.</p><p>You will also need the <b>Client ID</b>.</p>')),
|
||||||
|
]),
|
||||||
|
], parent::getSetupSteps());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return 'tabler-brand-github-f';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHexColor(): string
|
||||||
|
{
|
||||||
|
return '#4078c0';
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
use Filament\Forms\Components\Placeholder;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Wizard\Step;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
|
||||||
|
|
||||||
final class GitlabProvider extends OAuthProvider
|
final class GitlabSchema extends OAuthSchema
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
{
|
{
|
||||||
return 'gitlab';
|
return 'gitlab';
|
||||||
@ -47,13 +40,14 @@ final class GitlabProvider extends OAuthProvider
|
|||||||
return array_merge([
|
return array_merge([
|
||||||
Step::make('Register new Gitlab OAuth App')
|
Step::make('Register new Gitlab OAuth App')
|
||||||
->schema([
|
->schema([
|
||||||
Placeholder::make('')
|
TextEntry::make('register_application')
|
||||||
->content(new HtmlString(Blade::render('Check out the <x-filament::link href="https://docs.gitlab.com/integration/oauth_provider/" target="_blank">Gitlab docs</x-filament::link> on how to create the oauth app.'))),
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(Blade::render('Check out the <x-filament::link href="https://docs.gitlab.com/integration/oauth_provider/" target="_blank">Gitlab docs</x-filament::link> on how to create the oauth app.'))),
|
||||||
TextInput::make('_noenv_callback')
|
TextInput::make('_noenv_callback')
|
||||||
->label('Redirect URI')
|
->label('Redirect URI')
|
||||||
->dehydrated()
|
->dehydrated()
|
||||||
->disabled()
|
->disabled()
|
||||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
->hintCopy()
|
||||||
->default(fn () => url('/auth/oauth/callback/gitlab')),
|
->default(fn () => url('/auth/oauth/callback/gitlab')),
|
||||||
]),
|
]),
|
||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
@ -68,9 +62,4 @@ final class GitlabProvider extends OAuthProvider
|
|||||||
{
|
{
|
||||||
return '#fca326';
|
return '#fca326';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
137
app/Extensions/OAuth/Schemas/OAuthSchema.php
Normal file
137
app/Extensions/OAuth/Schemas/OAuthSchema.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\OAuth\OAuthSchemaInterface;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
abstract class OAuthSchema implements OAuthSchemaInterface
|
||||||
|
{
|
||||||
|
abstract public function getId(): string;
|
||||||
|
|
||||||
|
public function getSocialiteProvider(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServiceConfig(): array
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
'client_id' => env("OAUTH_{$id}_CLIENT_ID"),
|
||||||
|
'client_secret' => env("OAUTH_{$id}_CLIENT_SECRET"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Component[]
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
TextInput::make("OAUTH_{$id}_CLIENT_ID")
|
||||||
|
->label('Client ID')
|
||||||
|
->placeholder('Client ID')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->autocomplete(false)
|
||||||
|
->default(env("OAUTH_{$id}_CLIENT_ID")),
|
||||||
|
TextInput::make("OAUTH_{$id}_CLIENT_SECRET")
|
||||||
|
->label('Client Secret')
|
||||||
|
->placeholder('Client Secret')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->autocomplete(false)
|
||||||
|
->default(env("OAUTH_{$id}_CLIENT_SECRET")),
|
||||||
|
Toggle::make("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")
|
||||||
|
->label(trans('admin/setting.oauth.create_missing_users'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->formatStateUsing(fn ($state) => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", (bool) $state))
|
||||||
|
->default(env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")),
|
||||||
|
Toggle::make("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS")
|
||||||
|
->label(trans('admin/setting.oauth.link_missing_users'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->formatStateUsing(fn ($state) => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS", (bool) $state))
|
||||||
|
->default(env("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS")),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Step[]
|
||||||
|
*/
|
||||||
|
public function getSetupSteps(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Step::make('OAuth Config')
|
||||||
|
->columns(4)
|
||||||
|
->schema($this->getSettingsForm()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return Str::title($this->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfigKey(): string
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return "OAUTH_{$id}_ENABLED";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHexColor(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEnabled(): bool
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return env("OAUTH_{$id}_ENABLED", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldCreateMissingUsers(): bool
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldLinkMissingUsers(): bool
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return env("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS", false);
|
||||||
|
}
|
||||||
|
}
|
@ -1,28 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Extensions\OAuth\Providers;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
use Filament\Forms\Components\Placeholder;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Wizard\Step;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
use SocialiteProviders\Steam\Provider;
|
use SocialiteProviders\Steam\Provider;
|
||||||
|
|
||||||
final class SteamProvider extends OAuthProvider
|
final class SteamSchema extends OAuthSchema
|
||||||
{
|
{
|
||||||
public function __construct(protected Application $app)
|
|
||||||
{
|
|
||||||
parent::__construct($app);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
{
|
{
|
||||||
return 'steam';
|
return 'steam';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProviderClass(): string
|
public function getSocialiteProvider(): string
|
||||||
{
|
{
|
||||||
return Provider::class;
|
return Provider::class;
|
||||||
}
|
}
|
||||||
@ -58,8 +52,9 @@ final class SteamProvider extends OAuthProvider
|
|||||||
return array_merge([
|
return array_merge([
|
||||||
Step::make('Create API Key')
|
Step::make('Create API Key')
|
||||||
->schema([
|
->schema([
|
||||||
Placeholder::make('')
|
TextEntry::make('create_api_key')
|
||||||
->content(new HtmlString(Blade::render('Visit <x-filament::link href="https://steamcommunity.com/dev/apikey" target="_blank">https://steamcommunity.com/dev/apikey</x-filament::link> to generate an API key.'))),
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(Blade::render('Visit <x-filament::link href="https://steamcommunity.com/dev/apikey" target="_blank">https://steamcommunity.com/dev/apikey</x-filament::link> to generate an API key.'))),
|
||||||
]),
|
]),
|
||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
@ -73,9 +68,4 @@ final class SteamProvider extends OAuthProvider
|
|||||||
{
|
{
|
||||||
return '#00adee';
|
return '#00adee';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(Application $app): self
|
|
||||||
{
|
|
||||||
return new self($app);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Spatie\Fractalistic;
|
namespace App\Extensions\Spatie\Fractalistic;
|
||||||
|
|
||||||
|
use Spatie\Fractalistic\Exceptions\InvalidTransformation;
|
||||||
|
use Spatie\Fractalistic\Exceptions\NoTransformerSpecified;
|
||||||
use League\Fractal\Scope;
|
use League\Fractal\Scope;
|
||||||
use League\Fractal\TransformerAbstract;
|
use League\Fractal\TransformerAbstract;
|
||||||
use Spatie\Fractal\Fractal as SpatieFractal;
|
use Spatie\Fractal\Fractal as SpatieFractal;
|
||||||
@ -14,8 +16,8 @@ class Fractal extends SpatieFractal
|
|||||||
/**
|
/**
|
||||||
* Create fractal data.
|
* Create fractal data.
|
||||||
*
|
*
|
||||||
* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
|
* @throws InvalidTransformation
|
||||||
* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
|
* @throws NoTransformerSpecified
|
||||||
*/
|
*/
|
||||||
public function createData(): Scope
|
public function createData(): Scope
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ use Filament\Pages\Dashboard as BaseDashboard;
|
|||||||
|
|
||||||
class Dashboard extends BaseDashboard
|
class Dashboard extends BaseDashboard
|
||||||
{
|
{
|
||||||
protected static ?string $navigationIcon = 'tabler-layout-dashboard';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layout-dashboard';
|
||||||
|
|
||||||
private SoftwareVersionService $softwareVersionService;
|
private SoftwareVersionService $softwareVersionService;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ class Dashboard extends BaseDashboard
|
|||||||
$this->softwareVersionService = $softwareVersionService;
|
$this->softwareVersionService = $softwareVersionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getColumns(): int
|
public function getColumns(): int|array
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ use Spatie\Health\ResultStores\ResultStore;
|
|||||||
|
|
||||||
class Health extends Page
|
class Health extends Page
|
||||||
{
|
{
|
||||||
protected static ?string $navigationIcon = 'tabler-heart';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-heart';
|
||||||
|
|
||||||
protected static string $view = 'filament.pages.health';
|
protected string $view = 'filament.pages.health';
|
||||||
|
|
||||||
/** @var array<string, string> */
|
/** @var array<string, string> */
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
@ -123,7 +123,7 @@ class Health extends Page
|
|||||||
return $carry;
|
return $carry;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return trans('admin/health.checks.failed') . implode(', ', $failedNames);
|
return trans('admin/health.checks.failed', ['checks' => implode(', ', $failedNames)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getNavigationIcon(): string
|
public static function getNavigationIcon(): string
|
||||||
|
@ -2,50 +2,51 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Pages;
|
namespace App\Filament\Admin\Pages;
|
||||||
|
|
||||||
use App\Extensions\Avatar\AvatarProvider;
|
use App\Extensions\Avatar\AvatarService;
|
||||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
|
use App\Extensions\Captcha\CaptchaService;
|
||||||
use App\Extensions\OAuth\Providers\OAuthProvider;
|
use App\Extensions\OAuth\OAuthService;
|
||||||
use App\Models\Backup;
|
use App\Models\Backup;
|
||||||
use App\Notifications\MailTested;
|
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 BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Forms\Components\Actions;
|
|
||||||
use Filament\Forms\Components\Actions\Action as FormAction;
|
|
||||||
use Filament\Forms\Components\Component;
|
|
||||||
use Filament\Forms\Components\FileUpload;
|
use Filament\Forms\Components\FileUpload;
|
||||||
use Filament\Forms\Components\Group;
|
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
use Filament\Forms\Components\Section;
|
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\Tabs;
|
|
||||||
use Filament\Forms\Components\Tabs\Tab;
|
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\TagsInput;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Forms\Components\ToggleButtons;
|
use Filament\Forms\Components\ToggleButtons;
|
||||||
use Filament\Forms\Concerns\InteractsWithForms;
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
use Filament\Forms\Contracts\HasForms;
|
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Forms\Get;
|
|
||||||
use Filament\Forms\Set;
|
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||||
use Filament\Pages\Page;
|
use Filament\Pages\Page;
|
||||||
use Filament\Support\Enums\MaxWidth;
|
use Filament\Schemas\Components\Actions;
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
use Filament\Schemas\Components\Group;
|
||||||
|
use Filament\Schemas\Components\Section;
|
||||||
|
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||||
|
use Filament\Schemas\Components\Tabs;
|
||||||
|
use Filament\Schemas\Components\Tabs\Tab;
|
||||||
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
|
use Filament\Support\Enums\Width;
|
||||||
use Illuminate\Http\Client\Factory;
|
use Illuminate\Http\Client\Factory;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Filament\Schemas\Contracts\HasSchemas;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Form $form
|
* @property Schema $form
|
||||||
*/
|
*/
|
||||||
class Settings extends Page implements HasForms
|
class Settings extends Page implements HasSchemas
|
||||||
{
|
{
|
||||||
use CanCustomizeHeaderActions, InteractsWithHeaderActions {
|
use CanCustomizeHeaderActions, InteractsWithHeaderActions {
|
||||||
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
||||||
@ -54,9 +55,15 @@ class Settings extends Page implements HasForms
|
|||||||
use EnvironmentWriterTrait;
|
use EnvironmentWriterTrait;
|
||||||
use InteractsWithForms;
|
use InteractsWithForms;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'tabler-settings';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
|
||||||
|
|
||||||
protected static string $view = 'filament.pages.settings';
|
protected string $view = 'filament.pages.settings';
|
||||||
|
|
||||||
|
protected OAuthService $oauthService;
|
||||||
|
|
||||||
|
protected AvatarService $avatarService;
|
||||||
|
|
||||||
|
protected CaptchaService $captchaService;
|
||||||
|
|
||||||
/** @var array<mixed>|null */
|
/** @var array<mixed>|null */
|
||||||
public ?array $data = [];
|
public ?array $data = [];
|
||||||
@ -66,6 +73,13 @@ class Settings extends Page implements HasForms
|
|||||||
$this->form->fill();
|
$this->form->fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService): void
|
||||||
|
{
|
||||||
|
$this->oauthService = $oauthService;
|
||||||
|
$this->avatarService = $avatarService;
|
||||||
|
$this->captchaService = $captchaService;
|
||||||
|
}
|
||||||
|
|
||||||
public static function canAccess(): bool
|
public static function canAccess(): bool
|
||||||
{
|
{
|
||||||
return auth()->user()->can('view settings');
|
return auth()->user()->can('view settings');
|
||||||
@ -81,6 +95,11 @@ class Settings extends Page implements HasForms
|
|||||||
return trans('admin/setting.title');
|
return trans('admin/setting.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<Component>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
protected function getFormSchema(): array
|
protected function getFormSchema(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -97,7 +116,7 @@ class Settings extends Page implements HasForms
|
|||||||
->label(trans('admin/setting.navigation.captcha'))
|
->label(trans('admin/setting.navigation.captcha'))
|
||||||
->icon('tabler-shield')
|
->icon('tabler-shield')
|
||||||
->schema($this->captchaSettings())
|
->schema($this->captchaSettings())
|
||||||
->columns(3),
|
->columns(1),
|
||||||
Tab::make('mail')
|
Tab::make('mail')
|
||||||
->label(trans('admin/setting.navigation.mail'))
|
->label(trans('admin/setting.navigation.mail'))
|
||||||
->icon('tabler-mail')
|
->icon('tabler-mail')
|
||||||
@ -106,10 +125,11 @@ class Settings extends Page implements HasForms
|
|||||||
->label(trans('admin/setting.navigation.backup'))
|
->label(trans('admin/setting.navigation.backup'))
|
||||||
->icon('tabler-box')
|
->icon('tabler-box')
|
||||||
->schema($this->backupSettings()),
|
->schema($this->backupSettings()),
|
||||||
Tab::make('OAuth')
|
Tab::make('oauth')
|
||||||
->label(trans('admin/setting.navigation.oauth'))
|
->label(trans('admin/setting.navigation.oauth'))
|
||||||
->icon('tabler-brand-oauth')
|
->icon('tabler-brand-oauth')
|
||||||
->schema($this->oauthSettings()),
|
->schema($this->oauthSettings())
|
||||||
|
->columns(1),
|
||||||
Tab::make('misc')
|
Tab::make('misc')
|
||||||
->label(trans('admin/setting.navigation.misc'))
|
->label(trans('admin/setting.navigation.misc'))
|
||||||
->icon('tabler-tool')
|
->icon('tabler-tool')
|
||||||
@ -118,7 +138,9 @@ class Settings extends Page implements HasForms
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Component[] */
|
/** @return Component[]
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
private function generalSettings(): array
|
private function generalSettings(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -131,14 +153,12 @@ class Settings extends Page implements HasForms
|
|||||||
->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')
|
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_logo_help'))
|
||||||
->hintIconTooltip(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')
|
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_favicon_help'))
|
||||||
->hintIconTooltip(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'),
|
||||||
@ -153,19 +173,8 @@ class Settings extends Page implements HasForms
|
|||||||
->offIcon('tabler-x')
|
->offIcon('tabler-x')
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
->stateCast(new BooleanStateCast(false))
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
|
|
||||||
->default(env('APP_DEBUG', config('app.debug'))),
|
->default(env('APP_DEBUG', config('app.debug'))),
|
||||||
ToggleButtons::make('FILAMENT_TOP_NAVIGATION')
|
|
||||||
->label(trans('admin/setting.general.navigation'))
|
|
||||||
->inline()
|
|
||||||
->options([
|
|
||||||
false => trans('admin/setting.general.sidebar'),
|
|
||||||
true => trans('admin/setting.general.topbar'),
|
|
||||||
])
|
|
||||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
|
||||||
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
|
||||||
]),
|
]),
|
||||||
Group::make()
|
Group::make()
|
||||||
->columns(2)
|
->columns(2)
|
||||||
@ -173,7 +182,7 @@ class Settings extends Page implements HasForms
|
|||||||
Select::make('FILAMENT_AVATAR_PROVIDER')
|
Select::make('FILAMENT_AVATAR_PROVIDER')
|
||||||
->label(trans('admin/setting.general.avatar_provider'))
|
->label(trans('admin/setting.general.avatar_provider'))
|
||||||
->native(false)
|
->native(false)
|
||||||
->options(collect(AvatarProvider::getAll())->mapWithKeys(fn ($provider) => [$provider->getId() => $provider->getName()]))
|
->options($this->avatarService->getMappings())
|
||||||
->selectablePlaceholder(false)
|
->selectablePlaceholder(false)
|
||||||
->default(env('FILAMENT_AVATAR_PROVIDER', config('panel.filament.avatar-provider'))),
|
->default(env('FILAMENT_AVATAR_PROVIDER', config('panel.filament.avatar-provider'))),
|
||||||
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
|
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
|
||||||
@ -183,19 +192,17 @@ class Settings extends Page implements HasForms
|
|||||||
->offIcon('tabler-x')
|
->offIcon('tabler-x')
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->formatStateUsing(fn ($state) => (bool) $state)
|
->stateCast(new BooleanStateCast(false))
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_UPLOADABLE_AVATARS', (bool) $state))
|
|
||||||
->default(env('FILAMENT_UPLOADABLE_AVATARS', config('panel.filament.uploadable-avatars'))),
|
->default(env('FILAMENT_UPLOADABLE_AVATARS', config('panel.filament.uploadable-avatars'))),
|
||||||
]),
|
]),
|
||||||
ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
|
ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
|
||||||
->label(trans('admin/setting.general.unit_prefix'))
|
->label(trans('admin/setting.general.unit_prefix'))
|
||||||
->inline()
|
->inline()
|
||||||
->options([
|
->options([
|
||||||
false => trans('admin/setting.general.decimal_prefix'),
|
0 => trans('admin/setting.general.decimal_prefix'),
|
||||||
true => trans('admin/setting.general.binary_prefix'),
|
1 => trans('admin/setting.general.binary_prefix'),
|
||||||
])
|
])
|
||||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
->stateCast(new BooleanStateCast(false, true))
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_USE_BINARY_PREFIX', (bool) $state))
|
|
||||||
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
|
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
|
||||||
ToggleButtons::make('APP_2FA_REQUIRED')
|
ToggleButtons::make('APP_2FA_REQUIRED')
|
||||||
->label(trans('admin/setting.general.2fa_requirement'))
|
->label(trans('admin/setting.general.2fa_requirement'))
|
||||||
@ -211,7 +218,7 @@ class Settings extends Page implements HasForms
|
|||||||
Select::make('FILAMENT_WIDTH')
|
Select::make('FILAMENT_WIDTH')
|
||||||
->label(trans('admin/setting.general.display_width'))
|
->label(trans('admin/setting.general.display_width'))
|
||||||
->native(false)
|
->native(false)
|
||||||
->options(MaxWidth::class)
|
->options(Width::class)
|
||||||
->selectablePlaceholder(false)
|
->selectablePlaceholder(false)
|
||||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||||
TagsInput::make('TRUSTED_PROXIES')
|
TagsInput::make('TRUSTED_PROXIES')
|
||||||
@ -221,14 +228,14 @@ class Settings extends Page implements HasForms
|
|||||||
->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([
|
||||||
FormAction::make('clear')
|
Action::make('clear')
|
||||||
->label(trans('admin/setting.general.clear'))
|
->label(trans('admin/setting.general.clear'))
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->icon('tabler-trash')
|
->icon('tabler-trash')
|
||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->authorize(fn () => auth()->user()->can('update settings'))
|
->authorize(fn () => auth()->user()->can('update settings'))
|
||||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||||
FormAction::make('cloudflare')
|
Action::make('cloudflare')
|
||||||
->label(trans('admin/setting.general.set_to_cf'))
|
->label(trans('admin/setting.general.set_to_cf'))
|
||||||
->icon('tabler-brand-cloudflare')
|
->icon('tabler-brand-cloudflare')
|
||||||
->authorize(fn () => auth()->user()->can('update settings'))
|
->authorize(fn () => auth()->user()->can('update settings'))
|
||||||
@ -259,45 +266,39 @@ class Settings extends Page implements HasForms
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Component[]
|
* @return Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function captchaSettings(): array
|
private function captchaSettings(): array
|
||||||
{
|
{
|
||||||
$formFields = [];
|
$formFields = [];
|
||||||
|
|
||||||
$captchaProviders = CaptchaProvider::get();
|
$captchaSchemas = $this->captchaService->getAll();
|
||||||
foreach ($captchaProviders as $captchaProvider) {
|
foreach ($captchaSchemas as $schema) {
|
||||||
$id = Str::upper($captchaProvider->getId());
|
$id = Str::upper($schema->getId());
|
||||||
$name = Str::title($captchaProvider->getId());
|
|
||||||
|
|
||||||
$formFields[] = Section::make($name)
|
$formFields[] = Section::make($schema->getName())
|
||||||
->columns(5)
|
->columns(5)
|
||||||
->icon($captchaProvider->getIcon() ?? 'tabler-shield')
|
->icon($schema->getIcon() ?? 'tabler-shield')
|
||||||
->collapsed(fn () => !env("CAPTCHA_{$id}_ENABLED", false))
|
->collapsed(fn () => !$schema->isEnabled())
|
||||||
->collapsible()
|
->collapsible()
|
||||||
->schema([
|
->schema([
|
||||||
Hidden::make("CAPTCHA_{$id}_ENABLED")
|
Hidden::make("CAPTCHA_{$id}_ENABLED")
|
||||||
->live()
|
->live()
|
||||||
->default(env("CAPTCHA_{$id}_ENABLED")),
|
->default(env("CAPTCHA_{$id}_ENABLED")),
|
||||||
Actions::make([
|
Actions::make([
|
||||||
FormAction::make("disable_captcha_$id")
|
Action::make("disable_captcha_$id")
|
||||||
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
|
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
|
||||||
->label(trans('admin/setting.captcha.disable'))
|
->label(trans('admin/setting.captcha.disable'))
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->action(function (Set $set) use ($id) {
|
->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", false)),
|
||||||
$set("CAPTCHA_{$id}_ENABLED", false);
|
Action::make("enable_captcha_$id")
|
||||||
}),
|
|
||||||
FormAction::make("enable_captcha_$id")
|
|
||||||
->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED"))
|
->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED"))
|
||||||
->label(trans('admin/setting.captcha.enable'))
|
->label(trans('admin/setting.captcha.enable'))
|
||||||
->color('success')
|
->color('success')
|
||||||
->action(function (Set $set) use ($id, $captchaProviders) {
|
->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", true)),
|
||||||
foreach ($captchaProviders as $captchaProvider) {
|
|
||||||
$loopId = Str::upper($captchaProvider->getId());
|
|
||||||
$set("CAPTCHA_{$loopId}_ENABLED", $loopId === $id);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
])->columnSpan(1),
|
])->columnSpan(1),
|
||||||
Group::make($captchaProvider->getSettingsForm())
|
Group::make($schema->getSettingsForm())
|
||||||
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
|
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
|
||||||
->columns(4)
|
->columns(4)
|
||||||
->columnSpan(4),
|
->columnSpan(4),
|
||||||
@ -309,6 +310,8 @@ class Settings extends Page implements HasForms
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Component[]
|
* @return Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function mailSettings(): array
|
private function mailSettings(): array
|
||||||
{
|
{
|
||||||
@ -328,7 +331,7 @@ class Settings extends Page implements HasForms
|
|||||||
->live()
|
->live()
|
||||||
->default(env('MAIL_MAILER', config('mail.default')))
|
->default(env('MAIL_MAILER', config('mail.default')))
|
||||||
->hintAction(
|
->hintAction(
|
||||||
FormAction::make('test')
|
Action::make('test')
|
||||||
->label(trans('admin/setting.mail.test_mail'))
|
->label(trans('admin/setting.mail.test_mail'))
|
||||||
->icon('tabler-send')
|
->icon('tabler-send')
|
||||||
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||||
@ -386,6 +389,7 @@ class Settings extends Page implements HasForms
|
|||||||
Section::make(trans('admin/setting.mail.from_settings'))
|
Section::make(trans('admin/setting.mail.from_settings'))
|
||||||
->description(trans('admin/setting.mail.from_settings_help'))
|
->description(trans('admin/setting.mail.from_settings_help'))
|
||||||
->columns()
|
->columns()
|
||||||
|
->columnSpanFull()
|
||||||
->schema([
|
->schema([
|
||||||
TextInput::make('MAIL_FROM_ADDRESS')
|
TextInput::make('MAIL_FROM_ADDRESS')
|
||||||
->label(trans('admin/setting.mail.from_address'))
|
->label(trans('admin/setting.mail.from_address'))
|
||||||
@ -399,6 +403,7 @@ class Settings extends Page implements HasForms
|
|||||||
]),
|
]),
|
||||||
Section::make(trans('admin/setting.mail.smtp.smtp_title'))
|
Section::make(trans('admin/setting.mail.smtp.smtp_title'))
|
||||||
->columns()
|
->columns()
|
||||||
|
->columnSpanFull()
|
||||||
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp')
|
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp')
|
||||||
->schema([
|
->schema([
|
||||||
TextInput::make('MAIL_HOST')
|
TextInput::make('MAIL_HOST')
|
||||||
@ -435,6 +440,7 @@ class Settings extends Page implements HasForms
|
|||||||
]),
|
]),
|
||||||
Section::make(trans('admin/setting.mail.mailgun.mailgun_title'))
|
Section::make(trans('admin/setting.mail.mailgun.mailgun_title'))
|
||||||
->columns()
|
->columns()
|
||||||
|
->columnSpanFull()
|
||||||
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun')
|
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun')
|
||||||
->schema([
|
->schema([
|
||||||
TextInput::make('MAILGUN_DOMAIN')
|
TextInput::make('MAILGUN_DOMAIN')
|
||||||
@ -455,6 +461,8 @@ class Settings extends Page implements HasForms
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Component[]
|
* @return Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function backupSettings(): array
|
private function backupSettings(): array
|
||||||
{
|
{
|
||||||
@ -472,6 +480,7 @@ class Settings extends Page implements HasForms
|
|||||||
Section::make(trans('admin/setting.backup.throttle'))
|
Section::make(trans('admin/setting.backup.throttle'))
|
||||||
->description(trans('admin/setting.backup.throttle_help'))
|
->description(trans('admin/setting.backup.throttle_help'))
|
||||||
->columns()
|
->columns()
|
||||||
|
->columnSpanFull()
|
||||||
->schema([
|
->schema([
|
||||||
TextInput::make('BACKUP_THROTTLE_LIMIT')
|
TextInput::make('BACKUP_THROTTLE_LIMIT')
|
||||||
->label(trans('admin/setting.backup.limit'))
|
->label(trans('admin/setting.backup.limit'))
|
||||||
@ -519,8 +528,7 @@ class Settings extends Page implements HasForms
|
|||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
->stateCast(new BooleanStateCast(false))
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('AWS_USE_PATH_STYLE_ENDPOINT', (bool) $state))
|
|
||||||
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
@ -528,44 +536,44 @@ class Settings extends Page implements HasForms
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Component[]
|
* @return Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function oauthSettings(): array
|
private function oauthSettings(): array
|
||||||
{
|
{
|
||||||
$formFields = [];
|
$formFields = [];
|
||||||
|
|
||||||
$oauthProviders = OAuthProvider::get();
|
$oauthSchemas = $this->oauthService->getAll();
|
||||||
foreach ($oauthProviders as $oauthProvider) {
|
foreach ($oauthSchemas as $schema) {
|
||||||
$id = Str::upper($oauthProvider->getId());
|
$id = Str::upper($schema->getId());
|
||||||
$name = Str::title($oauthProvider->getId());
|
$key = $schema->getConfigKey();
|
||||||
|
|
||||||
$formFields[] = Section::make($name)
|
$formFields[] = Section::make($schema->getName())
|
||||||
->columns(5)
|
->columns(5)
|
||||||
->icon($oauthProvider->getIcon() ?? 'tabler-brand-oauth')
|
->icon($schema->getIcon() ?? 'tabler-brand-oauth')
|
||||||
->collapsed(fn () => !env("OAUTH_{$id}_ENABLED", false))
|
->collapsed(fn () => !env($key, false))
|
||||||
->collapsible()
|
->collapsible()
|
||||||
->schema([
|
->schema([
|
||||||
Hidden::make("OAUTH_{$id}_ENABLED")
|
Hidden::make($key)
|
||||||
->live()
|
->live()
|
||||||
->default(env("OAUTH_{$id}_ENABLED")),
|
->default(env($key)),
|
||||||
Actions::make([
|
Actions::make([
|
||||||
FormAction::make("disable_oauth_$id")
|
Action::make("disable_oauth_$id")
|
||||||
->visible(fn (Get $get) => $get("OAUTH_{$id}_ENABLED"))
|
->visible(fn (Get $get) => $get($key))
|
||||||
->label(trans('admin/setting.oauth.disable'))
|
->label(trans('admin/setting.oauth.disable'))
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->action(function (Set $set) use ($id) {
|
->action(fn (Set $set) => $set($key, false)),
|
||||||
$set("OAUTH_{$id}_ENABLED", false);
|
Action::make("enable_oauth_$id")
|
||||||
}),
|
->visible(fn (Get $get) => !$get($key))
|
||||||
FormAction::make("enable_oauth_$id")
|
|
||||||
->visible(fn (Get $get) => !$get("OAUTH_{$id}_ENABLED"))
|
|
||||||
->label(trans('admin/setting.oauth.enable'))
|
->label(trans('admin/setting.oauth.enable'))
|
||||||
->color('success')
|
->color('success')
|
||||||
->steps($oauthProvider->getSetupSteps())
|
->steps($schema->getSetupSteps())
|
||||||
->modalHeading(trans('admin/setting.oauth.enable') . ' ' . $name)
|
->modalHeading(trans('admin/setting.oauth.enable_schema', ['schema' => $schema->getName()]))
|
||||||
->modalSubmitActionLabel(trans('admin/setting.oauth.enable'))
|
->modalSubmitActionLabel(trans('admin/setting.oauth.enable'))
|
||||||
->modalCancelAction(false)
|
->modalCancelAction(false)
|
||||||
->action(function ($data, Set $set) use ($id) {
|
->action(function ($data, Set $set) use ($key) {
|
||||||
$data = array_merge([
|
$data = array_merge([
|
||||||
"OAUTH_{$id}_ENABLED" => 'true',
|
$key => 'true',
|
||||||
], $data);
|
], $data);
|
||||||
|
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
@ -573,8 +581,8 @@ class Settings extends Page implements HasForms
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])->columnSpan(1),
|
])->columnSpan(1),
|
||||||
Group::make($oauthProvider->getSettingsForm())
|
Group::make($schema->getSettingsForm())
|
||||||
->visible(fn (Get $get) => $get("OAUTH_{$id}_ENABLED"))
|
->visible(fn (Get $get) => $get($key))
|
||||||
->columns(4)
|
->columns(4)
|
||||||
->columnSpan(4),
|
->columnSpan(4),
|
||||||
]);
|
]);
|
||||||
@ -585,6 +593,8 @@ class Settings extends Page implements HasForms
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Component[]
|
* @return Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function miscSettings(): array
|
private function miscSettings(): array
|
||||||
{
|
{
|
||||||
@ -603,8 +613,7 @@ class Settings extends Page implements HasForms
|
|||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
->stateCast(new BooleanStateCast(false))
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_CLIENT_ALLOCATIONS_ENABLED', (bool) $state))
|
|
||||||
->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))),
|
->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))),
|
||||||
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START')
|
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START')
|
||||||
->label(trans('admin/setting.misc.auto_allocation.start'))
|
->label(trans('admin/setting.misc.auto_allocation.start'))
|
||||||
@ -695,8 +704,7 @@ class Settings extends Page implements HasForms
|
|||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
->stateCast(new BooleanStateCast(false))
|
||||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_ACTIVITY_HIDE_ADMIN', (bool) $state))
|
|
||||||
->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))),
|
->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))),
|
||||||
]),
|
]),
|
||||||
Section::make(trans('admin/setting.misc.api.title'))
|
Section::make(trans('admin/setting.misc.api.title'))
|
||||||
@ -774,8 +782,19 @@ class Settings extends Page implements HasForms
|
|||||||
$data = $this->form->getState();
|
$data = $this->form->getState();
|
||||||
unset($data['ConsoleFonts']);
|
unset($data['ConsoleFonts']);
|
||||||
|
|
||||||
// Convert bools to a string, so they are correctly written to the .env file
|
$data = array_map(function ($value) {
|
||||||
$data = array_map(fn ($value) => is_bool($value) ? ($value ? 'true' : 'false') : $value, $data);
|
// Convert bools to a string, so they are correctly written to the .env file
|
||||||
|
if (is_bool($value)) {
|
||||||
|
return $value ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert enum to its value
|
||||||
|
if ($value instanceof BackedEnum) {
|
||||||
|
return $value->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}, $data);
|
||||||
|
|
||||||
$this->writeToEnvironment($data);
|
$this->writeToEnvironment($data);
|
||||||
|
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources;
|
namespace App\Filament\Admin\Resources\ApiKeys;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
use Filament\Schemas\Schema;
|
||||||
use App\Filament\Admin\Resources\UserResource\Pages\EditUser;
|
use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
|
||||||
|
use App\Filament\Admin\Resources\ApiKeys\Pages\CreateApiKey;
|
||||||
|
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use App\Traits\Filament\CanCustomizePages;
|
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 Filament\Forms\Components\Fieldset;
|
use Exception;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\TagsInput;
|
||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\ToggleButtons;
|
use Filament\Forms\Components\ToggleButtons;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Pages\PageRegistration;
|
use Filament\Resources\Pages\PageRegistration;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables\Actions\CreateAction;
|
use Filament\Schemas\Components\Fieldset;
|
||||||
use Filament\Tables\Actions\DeleteAction;
|
|
||||||
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;
|
||||||
@ -32,7 +34,7 @@ class ApiKeyResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = ApiKey::class;
|
protected static ?string $model = ApiKey::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'tabler-key';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-key';
|
||||||
|
|
||||||
public static function getNavigationLabel(): string
|
public static function getNavigationLabel(): string
|
||||||
{
|
{
|
||||||
@ -66,6 +68,9 @@ class ApiKeyResource extends Resource
|
|||||||
return trans('admin/dashboard.advanced');
|
return trans('admin/dashboard.advanced');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public static function defaultTable(Table $table): Table
|
public static function defaultTable(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
@ -74,7 +79,7 @@ class ApiKeyResource extends Resource
|
|||||||
->label(trans('admin/apikey.table.key'))
|
->label(trans('admin/apikey.table.key'))
|
||||||
->icon('tabler-clipboard-text')
|
->icon('tabler-clipboard-text')
|
||||||
->state(fn (ApiKey $key) => $key->identifier . $key->token)
|
->state(fn (ApiKey $key) => $key->identifier . $key->token)
|
||||||
->copyable(),
|
->copyable(fn () => request()->isSecure()),
|
||||||
TextColumn::make('memo')
|
TextColumn::make('memo')
|
||||||
->label(trans('admin/apikey.table.description'))
|
->label(trans('admin/apikey.table.description'))
|
||||||
->wrap()
|
->wrap()
|
||||||
@ -88,30 +93,28 @@ class ApiKeyResource extends Resource
|
|||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('user.username')
|
TextColumn::make('user.username')
|
||||||
->label(trans('admin/apikey.table.created_by'))
|
->label(trans('admin/apikey.table.created_by'))
|
||||||
->icon('tabler-user')
|
|
||||||
->url(fn (ApiKey $apiKey) => auth()->user()->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
->url(fn (ApiKey $apiKey) => auth()->user()->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-key')
|
->emptyStateIcon('tabler-key')
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/apikey.empty_table'))
|
->emptyStateHeading(trans('admin/apikey.empty'))
|
||||||
->emptyStateActions([
|
->emptyStateActions([
|
||||||
CreateAction::make(),
|
CreateAction::make(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function defaultForm(Form $form): Form
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function defaultForm(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Fieldset::make('Permissions')
|
Fieldset::make('Permissions')
|
||||||
->columns([
|
->columnSpanFull()
|
||||||
'default' => 1,
|
|
||||||
'sm' => 1,
|
|
||||||
'md' => 2,
|
|
||||||
])
|
|
||||||
->schema(
|
->schema(
|
||||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||||
@ -156,8 +159,8 @@ class ApiKeyResource extends Resource
|
|||||||
public static function getDefaultPages(): array
|
public static function getDefaultPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListApiKeys::route('/'),
|
'index' => ListApiKeys::route('/'),
|
||||||
'create' => Pages\CreateApiKey::route('/create'),
|
'create' => CreateApiKey::route('/create'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
@ -10,6 +10,7 @@ use Filament\Actions\Action;
|
|||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class CreateApiKey extends CreateRecord
|
class CreateApiKey extends CreateRecord
|
||||||
{
|
{
|
||||||
@ -36,7 +37,7 @@ class CreateApiKey extends CreateRecord
|
|||||||
protected function handleRecordCreation(array $data): Model
|
protected function handleRecordCreation(array $data): Model
|
||||||
{
|
{
|
||||||
$data['identifier'] = ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION);
|
$data['identifier'] = ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION);
|
||||||
$data['token'] = str_random(ApiKey::KEY_LENGTH);
|
$data['token'] = Str::random(ApiKey::KEY_LENGTH);
|
||||||
$data['user_id'] = auth()->user()->id;
|
$data['user_id'] = auth()->user()->id;
|
||||||
$data['key_type'] = ApiKey::TYPE_APPLICATION;
|
$data['key_type'] = ApiKey::TYPE_APPLICATION;
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
@ -1,26 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources;
|
namespace App\Filament\Admin\Resources\DatabaseHosts;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
use App\Filament\Admin\Resources\DatabaseHosts\RelationManagers\DatabasesRelationManager;
|
||||||
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ListDatabaseHosts;
|
||||||
|
use App\Filament\Admin\Resources\DatabaseHosts\Pages\CreateDatabaseHost;
|
||||||
|
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ViewDatabaseHost;
|
||||||
|
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use App\Traits\Filament\CanCustomizePages;
|
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 Filament\Forms\Components\Section;
|
use Exception;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Forms\Set;
|
|
||||||
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;
|
||||||
use Filament\Tables\Actions\CreateAction;
|
use Filament\Schemas\Components\Section;
|
||||||
use Filament\Tables\Actions\DeleteBulkAction;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Tables\Actions\EditAction;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\ViewAction;
|
|
||||||
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 +38,7 @@ class DatabaseHostResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = DatabaseHost::class;
|
protected static ?string $model = DatabaseHost::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'tabler-database';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
|
||||||
|
|
||||||
protected static ?string $recordTitleAttribute = 'name';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
@ -63,6 +67,9 @@ class DatabaseHostResource extends Resource
|
|||||||
return trans('admin/dashboard.advanced');
|
return trans('admin/dashboard.advanced');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public static function defaultTable(Table $table): Table
|
public static function defaultTable(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
@ -77,15 +84,13 @@ class DatabaseHostResource extends Resource
|
|||||||
->label(trans('admin/databasehost.table.username')),
|
->label(trans('admin/databasehost.table.username')),
|
||||||
TextColumn::make('databases_count')
|
TextColumn::make('databases_count')
|
||||||
->counts('databases')
|
->counts('databases')
|
||||||
->icon('tabler-database')
|
|
||||||
->label(trans('admin/databasehost.databases')),
|
->label(trans('admin/databasehost.databases')),
|
||||||
TextColumn::make('nodes.name')
|
TextColumn::make('nodes.name')
|
||||||
->icon('tabler-server-2')
|
|
||||||
->badge()
|
->badge()
|
||||||
->placeholder(trans('admin/databasehost.no_nodes')),
|
->placeholder(trans('admin/databasehost.no_nodes')),
|
||||||
])
|
])
|
||||||
->checkIfRecordIsSelectableUsing(fn (DatabaseHost $databaseHost) => !$databaseHost->databases_count)
|
->checkIfRecordIsSelectableUsing(fn (DatabaseHost $databaseHost) => !$databaseHost->databases_count)
|
||||||
->actions([
|
->recordActions([
|
||||||
ViewAction::make()
|
ViewAction::make()
|
||||||
->hidden(fn ($record) => static::canEdit($record)),
|
->hidden(fn ($record) => static::canEdit($record)),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
@ -101,11 +106,15 @@ class DatabaseHostResource extends Resource
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function defaultForm(Form $form): Form
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function defaultForm(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Section::make()
|
Section::make()
|
||||||
|
->columnSpanFull()
|
||||||
->columns([
|
->columns([
|
||||||
'default' => 2,
|
'default' => 2,
|
||||||
'sm' => 3,
|
'sm' => 3,
|
||||||
@ -132,7 +141,7 @@ class DatabaseHostResource extends Resource
|
|||||||
->maxValue(65535),
|
->maxValue(65535),
|
||||||
TextInput::make('max_databases')
|
TextInput::make('max_databases')
|
||||||
->label(trans('admin/databasehost.max_database'))
|
->label(trans('admin/databasehost.max_database'))
|
||||||
->helpertext(trans('admin/databasehost.max_databases_help'))
|
->helperText(trans('admin/databasehost.max_databases_help'))
|
||||||
->numeric(),
|
->numeric(),
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label(trans('admin/databasehost.display_name'))
|
->label(trans('admin/databasehost.display_name'))
|
||||||
@ -166,7 +175,7 @@ class DatabaseHostResource extends Resource
|
|||||||
public static function getDefaultRelations(): array
|
public static function getDefaultRelations(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
RelationManagers\DatabasesRelationManager::class,
|
DatabasesRelationManager::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,10 +183,10 @@ class DatabaseHostResource extends Resource
|
|||||||
public static function getDefaultPages(): array
|
public static function getDefaultPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListDatabaseHosts::route('/'),
|
'index' => ListDatabaseHosts::route('/'),
|
||||||
'create' => Pages\CreateDatabaseHost::route('/create'),
|
'create' => CreateDatabaseHost::route('/create'),
|
||||||
'view' => Pages\ViewDatabaseHost::route('/{record}'),
|
'view' => ViewDatabaseHost::route('/{record}'),
|
||||||
'edit' => Pages\EditDatabaseHost::route('/{record}/edit'),
|
'edit' => EditDatabaseHost::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -1,30 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Services\Databases\Hosts\HostCreationService;
|
use App\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 Filament\Forms\Components\Fieldset;
|
use Exception;
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
use Filament\Forms\Components\Placeholder;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Forms\Components\Wizard\Step;
|
use Filament\Schemas\Components\Fieldset;
|
||||||
use Filament\Forms\Get;
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
use Filament\Forms\Set;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
|
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
|
||||||
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
use Filament\Support\Exceptions\Halt;
|
use Filament\Support\Exceptions\Halt;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use PDOException;
|
use PDOException;
|
||||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
use Throwable;
|
||||||
|
|
||||||
class CreateDatabaseHost extends CreateRecord
|
class CreateDatabaseHost extends CreateRecord
|
||||||
{
|
{
|
||||||
@ -43,15 +44,18 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
$this->service = $service;
|
$this->service = $service;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Step[] */
|
/** @return Step[]
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public function getSteps(): array
|
public function getSteps(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Step::make(trans('admin/databasehost.setup.preparations'))
|
Step::make(trans('admin/databasehost.setup.preparations'))
|
||||||
->columns()
|
->columns()
|
||||||
->schema([
|
->schema([
|
||||||
Placeholder::make('')
|
TextEntry::make('setup')
|
||||||
->content(trans('admin/databasehost.setup.note')),
|
->hiddenLabel()
|
||||||
|
->state(trans('admin/databasehost.setup.note')),
|
||||||
Toggle::make('different_server')
|
Toggle::make('different_server')
|
||||||
->label(new HtmlString(trans('admin/databasehost.setup.different_server')))
|
->label(new HtmlString(trans('admin/databasehost.setup.different_server')))
|
||||||
->dehydrated(false)
|
->dehydrated(false)
|
||||||
@ -84,31 +88,34 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
->schema([
|
->schema([
|
||||||
Fieldset::make(trans('admin/databasehost.setup.database_user'))
|
Fieldset::make(trans('admin/databasehost.setup.database_user'))
|
||||||
->schema([
|
->schema([
|
||||||
Placeholder::make('')
|
TextEntry::make('cli_login')
|
||||||
->content(new HtmlString(trans('admin/databasehost.setup.cli_login')))
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(trans('admin/databasehost.setup.cli_login')))
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
TextInput::make('create_user')
|
TextInput::make('create_user')
|
||||||
->label(trans('admin/databasehost.setup.command_create_user'))
|
->label(trans('admin/databasehost.setup.command_create_user'))
|
||||||
->default(fn (Get $get) => "CREATE USER '{$get('username')}'@'{$get('panel_ip')}' IDENTIFIED BY '{$get('password')}';")
|
->default(fn (Get $get) => "CREATE USER '{$get('username')}'@'{$get('panel_ip')}' IDENTIFIED BY '{$get('password')}';")
|
||||||
->disabled()
|
->disabled()
|
||||||
->dehydrated(false)
|
->dehydrated(false)
|
||||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
->copyable(fn () => request()->isSecure())
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
TextInput::make('assign_permissions')
|
TextInput::make('assign_permissions')
|
||||||
->label(trans('admin/databasehost.setup.command_assign_permissions'))
|
->label(trans('admin/databasehost.setup.command_assign_permissions'))
|
||||||
->default(fn (Get $get) => "GRANT ALL PRIVILEGES ON *.* TO '{$get('username')}'@'{$get('panel_ip')}' WITH GRANT OPTION;")
|
->default(fn (Get $get) => "GRANT ALL PRIVILEGES ON *.* TO '{$get('username')}'@'{$get('panel_ip')}' WITH GRANT OPTION;")
|
||||||
->disabled()
|
->disabled()
|
||||||
->dehydrated(false)
|
->dehydrated(false)
|
||||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
->copyable(fn () => request()->isSecure())
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
Placeholder::make('')
|
TextEntry::make('cli_exit')
|
||||||
->content(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]),
|
]),
|
||||||
Fieldset::make(trans('admin/databasehost.setup.external_access'))
|
Fieldset::make(trans('admin/databasehost.setup.external_access'))
|
||||||
->schema([
|
->schema([
|
||||||
Placeholder::make('')
|
TextEntry::make('allow_external_access')
|
||||||
->content(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
|
->hiddenLabel()
|
||||||
|
->state(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
@ -136,7 +143,7 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
->maxValue(65535),
|
->maxValue(65535),
|
||||||
TextInput::make('max_databases')
|
TextInput::make('max_databases')
|
||||||
->label(trans('admin/databasehost.max_database'))
|
->label(trans('admin/databasehost.max_database'))
|
||||||
->helpertext(trans('admin/databasehost.max_databases_help'))
|
->helperText(trans('admin/databasehost.max_databases_help'))
|
||||||
->placeholder(trans('admin/databasehost.unlimited'))
|
->placeholder(trans('admin/databasehost.unlimited'))
|
||||||
->numeric(),
|
->numeric(),
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
@ -155,6 +162,10 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Halt
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
protected function handleRecordCreation(array $data): Model
|
protected function handleRecordCreation(array $data): Model
|
||||||
{
|
{
|
||||||
try {
|
try {
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use App\Services\Databases\Hosts\HostUpdateService;
|
use App\Services\Databases\Hosts\HostUpdateService;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\RelationManagers;
|
||||||
|
|
||||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
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;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Tables\Actions\DeleteAction;
|
|
||||||
use Filament\Tables\Actions\ViewAction;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@ -17,10 +17,10 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
{
|
{
|
||||||
protected static string $relationship = 'databases';
|
protected static string $relationship = 'databases';
|
||||||
|
|
||||||
public function form(Form $form): Form
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
TextInput::make('database')
|
TextInput::make('database')
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
TextInput::make('username')
|
TextInput::make('username')
|
||||||
@ -49,19 +49,16 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
public function table(Table $table): Table
|
public function table(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->recordTitleAttribute('servers')
|
->recordTitleAttribute('database')
|
||||||
->heading('')
|
->heading('')
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('database')
|
TextColumn::make('database'),
|
||||||
->icon('tabler-database'),
|
|
||||||
TextColumn::make('username')
|
TextColumn::make('username')
|
||||||
->label(trans('admin/databasehost.table.username'))
|
->label(trans('admin/databasehost.table.username')),
|
||||||
->icon('tabler-user'),
|
|
||||||
TextColumn::make('remote')
|
TextColumn::make('remote')
|
||||||
->label(trans('admin/databasehost.table.remote'))
|
->label(trans('admin/databasehost.table.remote'))
|
||||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||||
TextColumn::make('server.name')
|
TextColumn::make('server.name')
|
||||||
->icon('tabler-brand-docker')
|
|
||||||
->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'))
|
||||||
@ -69,12 +66,10 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
DateTimeColumn::make('created_at')
|
DateTimeColumn::make('created_at')
|
||||||
->label(trans('admin/databasehost.table.created_at')),
|
->label(trans('admin/databasehost.table.created_at')),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
DeleteAction::make()
|
|
||||||
->authorize(fn (Database $database) => auth()->user()->can('delete', $database)),
|
|
||||||
ViewAction::make()
|
ViewAction::make()
|
||||||
->color('primary')
|
->color('primary'),
|
||||||
->hidden(fn () => !auth()->user()->can('viewAny', Database::class)),
|
DeleteAction::make(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources;
|
namespace App\Filament\Admin\Resources\Eggs;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\EggResource\Pages;
|
use App\Enums\CustomizationKey;
|
||||||
use App\Filament\Admin\Resources\EggResource\RelationManagers;
|
use App\Filament\Admin\Resources\Eggs\RelationManagers\ServersRelationManager;
|
||||||
|
use App\Filament\Admin\Resources\Eggs\Pages\ListEggs;
|
||||||
|
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
|
||||||
|
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||||
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;
|
||||||
@ -18,18 +21,18 @@ class EggResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = Egg::class;
|
protected static ?string $model = Egg::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'tabler-eggs';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-eggs';
|
||||||
|
|
||||||
protected static ?string $recordTitleAttribute = 'name';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
public static function getNavigationBadge(): ?string
|
public static function getNavigationBadge(): ?string
|
||||||
{
|
{
|
||||||
return static::getModel()::count() ?: null;
|
return ($count = static::getModel()::count()) > 0 ? (string) $count : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getNavigationGroup(): ?string
|
public static function getNavigationGroup(): ?string
|
||||||
{
|
{
|
||||||
return config('panel.filament.top-navigation', false) ? null : trans('admin/dashboard.server');
|
return auth()->user()->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getNavigationLabel(): string
|
public static function getNavigationLabel(): string
|
||||||
@ -56,7 +59,7 @@ class EggResource extends Resource
|
|||||||
public static function getDefaultRelations(): array
|
public static function getDefaultRelations(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
RelationManagers\ServersRelationManager::class,
|
ServersRelationManager::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +67,9 @@ class EggResource extends Resource
|
|||||||
public static function getDefaultPages(): array
|
public static function getDefaultPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListEggs::route('/'),
|
'index' => ListEggs::route('/'),
|
||||||
'create' => Pages\CreateEgg::route('/create'),
|
'create' => CreateEgg::route('/create'),
|
||||||
'edit' => Pages\EditEgg::route('/{record}/edit'),
|
'edit' => EditEgg::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||||
|
|
||||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
use Exception;
|
||||||
use App\Filament\Admin\Resources\EggResource;
|
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||||
use App\Models\EggVariable;
|
use App\Models\EggVariable;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
@ -11,24 +11,25 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
|||||||
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\Fieldset;
|
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;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\Tabs;
|
|
||||||
use Filament\Forms\Components\Tabs\Tab;
|
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\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\Forms\Form;
|
|
||||||
use Filament\Forms\Get;
|
|
||||||
use Filament\Forms\Set;
|
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
use Filament\Schemas\Components\Fieldset;
|
||||||
|
use Filament\Schemas\Components\Tabs;
|
||||||
|
use Filament\Schemas\Components\Tabs\Tab;
|
||||||
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use 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;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
|
||||||
class CreateEgg extends CreateRecord
|
class CreateEgg extends CreateRecord
|
||||||
{
|
{
|
||||||
@ -52,12 +53,16 @@ class CreateEgg extends CreateRecord
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function form(Form $form): Form
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Tabs::make()->tabs([
|
Tabs::make()->tabs([
|
||||||
Tab::make(trans('admin/egg.tabs.configuration'))
|
Tab::make('configuration')
|
||||||
|
->label(trans('admin/egg.tabs.configuration'))
|
||||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||||
->schema([
|
->schema([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
@ -97,8 +102,7 @@ class CreateEgg extends CreateRecord
|
|||||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||||
Toggle::make('force_outgoing_ip')
|
Toggle::make('force_outgoing_ip')
|
||||||
->label(trans('admin/egg.force_ip'))
|
->label(trans('admin/egg.force_ip'))
|
||||||
->hintIcon('tabler-question-mark')
|
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
|
||||||
->hintIconTooltip(trans('admin/egg.force_ip_help')),
|
|
||||||
Hidden::make('script_is_privileged')
|
Hidden::make('script_is_privileged')
|
||||||
->default(1),
|
->default(1),
|
||||||
TagsInput::make('tags')
|
TagsInput::make('tags')
|
||||||
@ -106,8 +110,7 @@ class CreateEgg extends CreateRecord
|
|||||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||||
TextInput::make('update_url')
|
TextInput::make('update_url')
|
||||||
->label(trans('admin/egg.update_url'))
|
->label(trans('admin/egg.update_url'))
|
||||||
->hintIcon('tabler-question-mark')
|
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
||||||
->hintIconTooltip(trans('admin/egg.update_url_help'))
|
|
||||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||||
->url(),
|
->url(),
|
||||||
KeyValue::make('docker_images')
|
KeyValue::make('docker_images')
|
||||||
@ -123,7 +126,8 @@ class CreateEgg extends CreateRecord
|
|||||||
->helperText(trans('admin/egg.docker_help')),
|
->helperText(trans('admin/egg.docker_help')),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Tab::make(trans('admin/egg.tabs.process_management'))
|
Tab::make('process_management')
|
||||||
|
->label(trans('admin/egg.tabs.process_management'))
|
||||||
->columns()
|
->columns()
|
||||||
->schema([
|
->schema([
|
||||||
CopyFrom::make('copy_process_from')
|
CopyFrom::make('copy_process_from')
|
||||||
@ -146,15 +150,15 @@ class CreateEgg extends CreateRecord
|
|||||||
->default('{}')
|
->default('{}')
|
||||||
->helperText(trans('admin/egg.log_config_help')),
|
->helperText(trans('admin/egg.log_config_help')),
|
||||||
]),
|
]),
|
||||||
Tab::make(trans('admin/egg.tabs.egg_variables'))
|
Tab::make('egg_variables')
|
||||||
|
->label(trans('admin/egg.tabs.egg_variables'))
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->schema([
|
->schema([
|
||||||
Repeater::make('variables')
|
Repeater::make('variables')
|
||||||
->label('')
|
->hiddenLabel()
|
||||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||||
->grid()
|
->grid()
|
||||||
->relationship('variables')
|
->relationship('variables')
|
||||||
->name('name')
|
|
||||||
->reorderable()->orderColumn()
|
->reorderable()->orderColumn()
|
||||||
->collapsible()->collapsed()
|
->collapsible()->collapsed()
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
@ -186,7 +190,7 @@ class CreateEgg extends CreateRecord
|
|||||||
->maxLength(255)
|
->maxLength(255)
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||||
->validationMessages([
|
->validationMessages([
|
||||||
'unique' => trans('admin/egg.error_unique'),
|
'unique' => trans('admin/egg.error_unique'),
|
||||||
])
|
])
|
||||||
@ -197,9 +201,8 @@ class CreateEgg extends CreateRecord
|
|||||||
->maxLength(255)
|
->maxLength(255)
|
||||||
->prefix('{{')
|
->prefix('{{')
|
||||||
->suffix('}}')
|
->suffix('}}')
|
||||||
->hintIcon('tabler-code')
|
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
||||||
->hintIconTooltip(fn ($state) => "{{{$state}}}")
|
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
|
||||||
->rules(EggVariable::getRulesForField('env_variable'))
|
->rules(EggVariable::getRulesForField('env_variable'))
|
||||||
->validationMessages([
|
->validationMessages([
|
||||||
'unique' => trans('admin/egg.error_unique'),
|
'unique' => trans('admin/egg.error_unique'),
|
||||||
@ -207,7 +210,7 @@ class CreateEgg extends CreateRecord
|
|||||||
'*' => trans('admin/egg.error_reserved'),
|
'*' => trans('admin/egg.error_reserved'),
|
||||||
])
|
])
|
||||||
->required(),
|
->required(),
|
||||||
TextInput::make('default_value')->label(trans('admin/egg.default_value'))->maxLength(255),
|
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||||
->schema([
|
->schema([
|
||||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||||
@ -239,7 +242,8 @@ class CreateEgg extends CreateRecord
|
|||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
Tab::make(trans('admin/egg.tabs.install_script'))
|
Tab::make('install_script')
|
||||||
|
->label(trans('admin/egg.tabs.install_script'))
|
||||||
->columns(3)
|
->columns(3)
|
||||||
->schema([
|
->schema([
|
||||||
CopyFrom::make('copy_script_from')
|
CopyFrom::make('copy_script_from')
|
||||||
@ -254,15 +258,16 @@ class CreateEgg extends CreateRecord
|
|||||||
->native(false)
|
->native(false)
|
||||||
->selectablePlaceholder(false)
|
->selectablePlaceholder(false)
|
||||||
->default('bash')
|
->default('bash')
|
||||||
->options(['bash', 'ash', '/bin/bash'])
|
->options([
|
||||||
|
'bash' => 'bash',
|
||||||
|
'ash' => 'ash',
|
||||||
|
'/bin/bash' => '/bin/bash',
|
||||||
|
])
|
||||||
->required(),
|
->required(),
|
||||||
MonacoEditor::make('script_install')
|
CodeEditor::make('script_install')
|
||||||
->label(trans('admin/egg.script_install'))
|
->label(trans('admin/egg.script_install'))
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->fontSize('16px')
|
->lazy(),
|
||||||
->language('shell')
|
|
||||||
->lazy()
|
|
||||||
->view('filament.plugins.monaco-editor'),
|
|
||||||
]),
|
]),
|
||||||
|
|
||||||
])->columnSpanFull()->persistTabInQueryString(),
|
])->columnSpanFull()->persistTabInQueryString(),
|
@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||||
|
|
||||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
use Exception;
|
||||||
use App\Filament\Admin\Resources\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;
|
||||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||||
@ -15,22 +15,23 @@ 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\Fieldset;
|
use Filament\Forms\Components\CodeEditor;
|
||||||
|
use Filament\Schemas\Components\Fieldset;
|
||||||
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;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\Tabs;
|
|
||||||
use Filament\Forms\Components\Tabs\Tab;
|
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\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\Forms\Form;
|
use Filament\Schemas\Components\Tabs;
|
||||||
use Filament\Forms\Get;
|
use Filament\Schemas\Components\Tabs\Tab;
|
||||||
use Filament\Forms\Set;
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Illuminate\Validation\Rules\Unique;
|
use Illuminate\Validation\Rules\Unique;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
|
||||||
class EditEgg extends EditRecord
|
class EditEgg extends EditRecord
|
||||||
{
|
{
|
||||||
@ -39,12 +40,16 @@ class EditEgg extends EditRecord
|
|||||||
|
|
||||||
protected static string $resource = EggResource::class;
|
protected static string $resource = EggResource::class;
|
||||||
|
|
||||||
public function form(Form $form): Form
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Tabs::make()->tabs([
|
Tabs::make()->tabs([
|
||||||
Tab::make(trans('admin/egg.tabs.configuration'))
|
Tab::make('configuration')
|
||||||
|
->label(trans('admin/egg.tabs.configuration'))
|
||||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||||
->icon('tabler-egg')
|
->icon('tabler-egg')
|
||||||
->schema([
|
->schema([
|
||||||
@ -92,8 +97,7 @@ class EditEgg extends EditRecord
|
|||||||
Toggle::make('force_outgoing_ip')
|
Toggle::make('force_outgoing_ip')
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->label(trans('admin/egg.force_ip'))
|
->label(trans('admin/egg.force_ip'))
|
||||||
->hintIcon('tabler-question-mark')
|
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
|
||||||
->hintIconTooltip(trans('admin/egg.force_ip_help')),
|
|
||||||
Hidden::make('script_is_privileged')
|
Hidden::make('script_is_privileged')
|
||||||
->helperText('The docker images available to servers using this egg.'),
|
->helperText('The docker images available to servers using this egg.'),
|
||||||
TagsInput::make('tags')
|
TagsInput::make('tags')
|
||||||
@ -102,8 +106,7 @@ class EditEgg extends EditRecord
|
|||||||
TextInput::make('update_url')
|
TextInput::make('update_url')
|
||||||
->label(trans('admin/egg.update_url'))
|
->label(trans('admin/egg.update_url'))
|
||||||
->url()
|
->url()
|
||||||
->hintIcon('tabler-question-mark')
|
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
||||||
->hintIconTooltip(trans('admin/egg.update_url_help'))
|
|
||||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||||
KeyValue::make('docker_images')
|
KeyValue::make('docker_images')
|
||||||
->label(trans('admin/egg.docker_images'))
|
->label(trans('admin/egg.docker_images'))
|
||||||
@ -115,7 +118,8 @@ class EditEgg extends EditRecord
|
|||||||
->valueLabel(trans('admin/egg.docker_uri'))
|
->valueLabel(trans('admin/egg.docker_uri'))
|
||||||
->helperText(trans('admin/egg.docker_help')),
|
->helperText(trans('admin/egg.docker_help')),
|
||||||
]),
|
]),
|
||||||
Tab::make(trans('admin/egg.tabs.process_management'))
|
Tab::make('process_management')
|
||||||
|
->label(trans('admin/egg.tabs.process_management'))
|
||||||
->columns()
|
->columns()
|
||||||
->icon('tabler-server-cog')
|
->icon('tabler-server-cog')
|
||||||
->schema([
|
->schema([
|
||||||
@ -135,15 +139,15 @@ class EditEgg extends EditRecord
|
|||||||
->label(trans('admin/egg.log_config'))
|
->label(trans('admin/egg.log_config'))
|
||||||
->helperText(trans('admin/egg.log_config_help')),
|
->helperText(trans('admin/egg.log_config_help')),
|
||||||
]),
|
]),
|
||||||
Tab::make(trans('admin/egg.tabs.egg_variables'))
|
Tab::make('egg_variables')
|
||||||
|
->label(trans('admin/egg.tabs.egg_variables'))
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->icon('tabler-variable')
|
->icon('tabler-variable')
|
||||||
->schema([
|
->schema([
|
||||||
Repeater::make('variables')
|
Repeater::make('variables')
|
||||||
->label('')
|
->hiddenLabel()
|
||||||
->grid()
|
->grid()
|
||||||
->relationship('variables')
|
->relationship('variables')
|
||||||
->name('name')
|
|
||||||
->reorderable()
|
->reorderable()
|
||||||
->collapsible()->collapsed()
|
->collapsible()->collapsed()
|
||||||
->orderColumn()
|
->orderColumn()
|
||||||
@ -175,7 +179,7 @@ class EditEgg extends EditRecord
|
|||||||
->maxLength(255)
|
->maxLength(255)
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||||
->validationMessages([
|
->validationMessages([
|
||||||
'unique' => trans('admin/egg.error_unique'),
|
'unique' => trans('admin/egg.error_unique'),
|
||||||
])
|
])
|
||||||
@ -186,9 +190,8 @@ class EditEgg extends EditRecord
|
|||||||
->maxLength(255)
|
->maxLength(255)
|
||||||
->prefix('{{')
|
->prefix('{{')
|
||||||
->suffix('}}')
|
->suffix('}}')
|
||||||
->hintIcon('tabler-code')
|
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
||||||
->hintIconTooltip(fn ($state) => "{{{$state}}}")
|
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
|
||||||
->rules(EggVariable::getRulesForField('env_variable'))
|
->rules(EggVariable::getRulesForField('env_variable'))
|
||||||
->validationMessages([
|
->validationMessages([
|
||||||
'unique' => trans('admin/egg.error_unique'),
|
'unique' => trans('admin/egg.error_unique'),
|
||||||
@ -196,7 +199,7 @@ class EditEgg extends EditRecord
|
|||||||
'*' => trans('admin/egg.error_reserved'),
|
'*' => trans('admin/egg.error_reserved'),
|
||||||
])
|
])
|
||||||
->required(),
|
->required(),
|
||||||
TextInput::make('default_value')->label(trans('admin/egg.default_value'))->maxLength(255),
|
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||||
->schema([
|
->schema([
|
||||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||||
@ -228,7 +231,8 @@ class EditEgg extends EditRecord
|
|||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
Tab::make(trans('admin/egg.tabs.install_script'))
|
Tab::make('install_script')
|
||||||
|
->label(trans('admin/egg.tabs.install_script'))
|
||||||
->columns(3)
|
->columns(3)
|
||||||
->icon('tabler-file-download')
|
->icon('tabler-file-download')
|
||||||
->schema([
|
->schema([
|
||||||
@ -243,15 +247,15 @@ class EditEgg extends EditRecord
|
|||||||
->label(trans('admin/egg.script_entry'))
|
->label(trans('admin/egg.script_entry'))
|
||||||
->native(false)
|
->native(false)
|
||||||
->selectablePlaceholder(false)
|
->selectablePlaceholder(false)
|
||||||
->options(['bash', 'ash', '/bin/bash'])
|
->options([
|
||||||
|
'bash' => 'bash',
|
||||||
|
'ash' => 'ash',
|
||||||
|
'/bin/bash' => '/bin/bash',
|
||||||
|
])
|
||||||
->required(),
|
->required(),
|
||||||
MonacoEditor::make('script_install')
|
CodeEditor::make('script_install')
|
||||||
->label(trans('admin/egg.script_install'))
|
->hiddenLabel()
|
||||||
->placeholderText('')
|
->columnSpanFull(),
|
||||||
->columnSpanFull()
|
|
||||||
->fontSize('16px')
|
|
||||||
->language('shell')
|
|
||||||
->view('filament.plugins.monaco-editor'),
|
|
||||||
]),
|
]),
|
||||||
])->columnSpanFull()->persistTabInQueryString(),
|
])->columnSpanFull()->persistTabInQueryString(),
|
||||||
]);
|
]);
|
@ -1,28 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\EggResource;
|
use Exception;
|
||||||
use App\Filament\Components\Actions\ImportEggAction as ImportEggHeaderAction;
|
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||||
use App\Filament\Components\Tables\Actions\ExportEggAction;
|
use App\Filament\Components\Actions\ExportEggAction;
|
||||||
use App\Filament\Components\Tables\Actions\ImportEggAction;
|
use App\Filament\Components\Actions\ImportEggAction;
|
||||||
use App\Filament\Components\Tables\Actions\UpdateEggAction;
|
use App\Filament\Components\Actions\UpdateEggAction;
|
||||||
use App\Filament\Components\Tables\Actions\UpdateEggBulkAction;
|
use App\Filament\Components\Actions\UpdateEggBulkAction;
|
||||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||||
use App\Models\Egg;
|
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 Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Actions\CreateAction as CreateHeaderAction;
|
use Filament\Actions\CreateAction;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ReplicateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Tables\Actions\CreateAction;
|
|
||||||
use Filament\Tables\Actions\DeleteBulkAction;
|
|
||||||
use Filament\Tables\Actions\EditAction;
|
|
||||||
use Filament\Tables\Actions\ReplicateAction;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ListEggs extends ListRecords
|
class ListEggs extends ListRecords
|
||||||
@ -32,6 +30,9 @@ class ListEggs extends ListRecords
|
|||||||
|
|
||||||
protected static string $resource = EggResource::class;
|
protected static string $resource = EggResource::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public function table(Table $table): Table
|
public function table(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
@ -43,17 +44,15 @@ class ListEggs extends ListRecords
|
|||||||
->hidden(),
|
->hidden(),
|
||||||
TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->label(trans('admin/egg.name'))
|
->label(trans('admin/egg.name'))
|
||||||
->icon('tabler-egg')
|
|
||||||
->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)
|
||||||
->wrap()
|
->wrap()
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('servers_count')
|
TextColumn::make('servers_count')
|
||||||
->counts('servers')
|
->counts('servers')
|
||||||
->icon('tabler-server')
|
|
||||||
->label(trans('admin/egg.servers')),
|
->label(trans('admin/egg.servers')),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
EditAction::make()
|
EditAction::make()
|
||||||
->iconButton()
|
->iconButton()
|
||||||
->tooltip(trans('filament-actions::edit.single.label')),
|
->tooltip(trans('filament-actions::edit.single.label')),
|
||||||
@ -62,7 +61,7 @@ class ListEggs extends ListRecords
|
|||||||
->tooltip(trans('filament-actions::export.modal.actions.export.label')),
|
->tooltip(trans('filament-actions::export.modal.actions.export.label')),
|
||||||
UpdateEggAction::make()
|
UpdateEggAction::make()
|
||||||
->iconButton()
|
->iconButton()
|
||||||
->tooltip(trans('admin/egg.update')),
|
->tooltip(trans_choice('admin/egg.update', 1)),
|
||||||
ReplicateAction::make()
|
ReplicateAction::make()
|
||||||
->iconButton()
|
->iconButton()
|
||||||
->tooltip(trans('filament-actions::replicate.single.label'))
|
->tooltip(trans('filament-actions::replicate.single.label'))
|
||||||
@ -78,15 +77,15 @@ class ListEggs extends ListRecords
|
|||||||
])
|
])
|
||||||
->groupedBulkActions([
|
->groupedBulkActions([
|
||||||
DeleteBulkAction::make()
|
DeleteBulkAction::make()
|
||||||
->before(fn (DeleteBulkAction $action, Collection $records) => $action->records($records->filter(function ($egg) {
|
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
||||||
/** @var Egg $egg */
|
/** @var Egg $egg */
|
||||||
return $egg->servers_count <= 0;
|
return $egg->servers_count <= 0;
|
||||||
}))),
|
})),
|
||||||
UpdateEggBulkAction::make()
|
UpdateEggBulkAction::make()
|
||||||
->before(fn (UpdateEggBulkAction $action, Collection $records) => $action->records($records->filter(function ($egg) {
|
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
||||||
/** @var Egg $egg */
|
/** @var Egg $egg */
|
||||||
return cache()->get("eggs.$egg->uuid.update", false);
|
return cache()->get("eggs.$egg->uuid.update", false);
|
||||||
}))),
|
})),
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-eggs')
|
->emptyStateIcon('tabler-eggs')
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
@ -102,13 +101,15 @@ class ListEggs extends ListRecords
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<Action|ActionGroup> */
|
/** @return array<Action|ActionGroup>
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
ImportEggHeaderAction::make()
|
ImportEggAction::make()
|
||||||
->multiple(),
|
->multiple(),
|
||||||
CreateHeaderAction::make(),
|
CreateAction::make(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\EggResource\RelationManagers;
|
namespace App\Filament\Admin\Resources\Eggs\RelationManagers;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
@ -22,24 +22,22 @@ class ServersRelationManager extends RelationManager
|
|||||||
->heading(trans('admin/egg.servers'))
|
->heading(trans('admin/egg.servers'))
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('user.username')
|
TextColumn::make('user.username')
|
||||||
->label('Owner')
|
->label(trans('admin/server.owner'))
|
||||||
->icon('tabler-user')
|
|
||||||
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->label(trans('admin/server.name'))
|
->label(trans('admin/server.name'))
|
||||||
->icon('tabler-brand-docker')
|
|
||||||
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
|
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('node.name')
|
TextColumn::make('node.name')
|
||||||
->icon('tabler-server-2')
|
|
||||||
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])),
|
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])),
|
||||||
TextColumn::make('image')
|
TextColumn::make('image')
|
||||||
->label(trans('admin/server.docker_image')),
|
->label(trans('admin/server.docker_image')),
|
||||||
SelectColumn::make('allocation.id')
|
SelectColumn::make('allocation.id')
|
||||||
->label(trans('admin/server.primary_allocation'))
|
->label(trans('admin/server.primary_allocation'))
|
||||||
->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address])
|
->disabled()
|
||||||
->selectablePlaceholder(false)
|
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||||
|
->placeholder(trans('admin/server.none'))
|
||||||
->sortable(),
|
->sortable(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
@ -1,26 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources;
|
namespace App\Filament\Admin\Resources\Mounts;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\MountResource\Pages;
|
use App\Filament\Admin\Resources\Mounts\Pages\ListMounts;
|
||||||
|
use App\Filament\Admin\Resources\Mounts\Pages\CreateMount;
|
||||||
|
use App\Filament\Admin\Resources\Mounts\Pages\ViewMount;
|
||||||
|
use App\Filament\Admin\Resources\Mounts\Pages\EditMount;
|
||||||
|
use Exception;
|
||||||
use App\Models\Mount;
|
use App\Models\Mount;
|
||||||
use App\Traits\Filament\CanCustomizePages;
|
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 Filament\Forms\Components\Group;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Forms\Components\Section;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\ToggleButtons;
|
use Filament\Forms\Components\ToggleButtons;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Pages\PageRegistration;
|
use Filament\Resources\Pages\PageRegistration;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables\Actions\CreateAction;
|
use Filament\Schemas\Components\Group;
|
||||||
use Filament\Tables\Actions\DeleteBulkAction;
|
use Filament\Schemas\Components\Section;
|
||||||
use Filament\Tables\Actions\EditAction;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\ViewAction;
|
|
||||||
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 +38,7 @@ class MountResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = Mount::class;
|
protected static ?string $model = Mount::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'tabler-layers-linked';
|
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layers-linked';
|
||||||
|
|
||||||
protected static ?string $recordTitleAttribute = 'name';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
@ -63,6 +67,9 @@ class MountResource extends Resource
|
|||||||
return trans('admin/dashboard.advanced');
|
return trans('admin/dashboard.advanced');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public static function defaultTable(Table $table): Table
|
public static function defaultTable(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
@ -72,12 +79,10 @@ class MountResource extends Resource
|
|||||||
->description(fn (Mount $mount) => "$mount->source -> $mount->target")
|
->description(fn (Mount $mount) => "$mount->source -> $mount->target")
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('eggs.name')
|
TextColumn::make('eggs.name')
|
||||||
->icon('tabler-eggs')
|
|
||||||
->label(trans('admin/mount.eggs'))
|
->label(trans('admin/mount.eggs'))
|
||||||
->badge()
|
->badge()
|
||||||
->placeholder(trans('admin/mount.table.all_eggs')),
|
->placeholder(trans('admin/mount.table.all_eggs')),
|
||||||
TextColumn::make('nodes.name')
|
TextColumn::make('nodes.name')
|
||||||
->icon('tabler-server-2')
|
|
||||||
->label(trans('admin/mount.nodes'))
|
->label(trans('admin/mount.nodes'))
|
||||||
->badge()
|
->badge()
|
||||||
->placeholder(trans('admin/mount.table.all_nodes')),
|
->placeholder(trans('admin/mount.table.all_nodes')),
|
||||||
@ -88,7 +93,7 @@ class MountResource extends Resource
|
|||||||
->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')),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
ViewAction::make()
|
ViewAction::make()
|
||||||
->hidden(fn ($record) => static::canEdit($record)),
|
->hidden(fn ($record) => static::canEdit($record)),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
@ -104,10 +109,13 @@ class MountResource extends Resource
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function defaultForm(Form $form): Form
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function defaultForm(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Section::make()->schema([
|
Section::make()->schema([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label(trans('admin/mount.name'))
|
->label(trans('admin/mount.name'))
|
||||||
@ -176,10 +184,10 @@ class MountResource extends Resource
|
|||||||
public static function getDefaultPages(): array
|
public static function getDefaultPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListMounts::route('/'),
|
'index' => ListMounts::route('/'),
|
||||||
'create' => Pages\CreateMount::route('/create'),
|
'create' => CreateMount::route('/create'),
|
||||||
'view' => Pages\ViewMount::route('/{record}'),
|
'view' => ViewMount::route('/{record}'),
|
||||||
'edit' => Pages\EditMount::route('/{record}/edit'),
|
'edit' => EditMount::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\MountResource;
|
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\MountResource;
|
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\MountResource;
|
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||||
use App\Models\Mount;
|
use App\Models\Mount;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user