mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-24 11:20:41 +03:00
Compare commits
112 Commits
v1.0.0-bet
...
issue/68
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4eeefab6c6 | ||
|
|
a2108c3d91 | ||
|
|
4f5e9a6c30 | ||
|
|
1aa51d15f0 | ||
|
|
3d8847a508 | ||
|
|
4fa49ae915 | ||
|
|
a42cb193a3 | ||
|
|
50ffaf4d01 | ||
|
|
804ff64a71 | ||
|
|
c2f6842f64 | ||
|
|
455d0543f1 | ||
|
|
97a4601150 | ||
|
|
2cc4a42905 | ||
|
|
5353d38302 | ||
|
|
df88d33af4 | ||
|
|
906d4a7d28 | ||
|
|
9ba8c1df9b | ||
|
|
0a6b846230 | ||
|
|
aff9f4ea37 | ||
|
|
f2754c3cb1 | ||
|
|
8b86707150 | ||
|
|
233fd50b2b | ||
|
|
3a76fb1c79 | ||
|
|
e76630b7f3 | ||
|
|
0f798e5edb | ||
|
|
a9c7eeddde | ||
|
|
11feef4f8c | ||
|
|
bebc410eda | ||
|
|
ec0fa3c913 | ||
|
|
4574821ed8 | ||
|
|
d71b1a4710 | ||
|
|
d9922e86f2 | ||
|
|
9d9e4adbbd | ||
|
|
6b104e3331 | ||
|
|
f2eca17480 | ||
|
|
4c41e659b5 | ||
|
|
6238d6dd08 | ||
|
|
c45e4edcf6 | ||
|
|
4273880126 | ||
|
|
d86843977b | ||
|
|
5b468c21ae | ||
|
|
68ef0a1d0a | ||
|
|
f6122f919a | ||
|
|
4f10ec2c20 | ||
|
|
45fcc2a09a | ||
|
|
19c7b4d044 | ||
|
|
7c8b204d13 | ||
|
|
343a5b81bc | ||
|
|
755632f9d5 | ||
|
|
e7ee86a914 | ||
|
|
eb0bad82e6 | ||
|
|
48fd3cc84e | ||
|
|
d4484f5254 | ||
|
|
958e8fac8a | ||
|
|
7986505b99 | ||
|
|
ba5b81cf2d | ||
|
|
32018399b6 | ||
|
|
48f4c35d0b | ||
|
|
f699fd5459 | ||
|
|
05573f64dd | ||
|
|
3fa714c7e3 | ||
|
|
69acc48b5e | ||
|
|
9d9720a5a2 | ||
|
|
ff261f9c99 | ||
|
|
c7bea4f024 | ||
|
|
738707b251 | ||
|
|
a8699704de | ||
|
|
d9dc932e07 | ||
|
|
f57232bc23 | ||
|
|
b24ff8bb26 | ||
|
|
eff8e509ef | ||
|
|
6976fa8989 | ||
|
|
2b58160da9 | ||
|
|
44e0dd3e09 | ||
|
|
8ea57bc46b | ||
|
|
459d90e8d1 | ||
|
|
a97341f6f2 | ||
|
|
375a64a38e | ||
|
|
7c25fc2a9d | ||
|
|
1a26f5ce9e | ||
|
|
b47f40bd13 | ||
|
|
bcb7240ed2 | ||
|
|
405aa857b1 | ||
|
|
0bd2935885 | ||
|
|
4cba1540ac | ||
|
|
30051ab0d7 | ||
|
|
e15d515f71 | ||
|
|
36e2fa8e2b | ||
|
|
510ae3c0df | ||
|
|
f5edb34873 | ||
|
|
0895bd2be5 | ||
|
|
17bc3de0d0 | ||
|
|
4319f24f51 | ||
|
|
9ad113bc61 | ||
|
|
beadce96f6 | ||
|
|
b1d7d210fc | ||
|
|
32e96dc0a6 | ||
|
|
b16a11c365 | ||
|
|
81f218ddc9 | ||
|
|
be6f79521e | ||
|
|
551175862e | ||
|
|
dbad5ae9c7 | ||
|
|
768a45bbb8 | ||
|
|
bbe09ced1d | ||
|
|
2e7c534a3b | ||
|
|
71684dc517 | ||
|
|
4dbb55059d | ||
|
|
f480a271b3 | ||
|
|
9c81c0ce18 | ||
|
|
b220c582cc | ||
|
|
29f8ac625a | ||
|
|
5b6c462943 |
9
.github/docker/entrypoint.sh
vendored
9
.github/docker/entrypoint.sh
vendored
@@ -28,7 +28,6 @@ fi
|
||||
|
||||
mkdir /pelican-data/database
|
||||
ln -s /pelican-data/.env /var/www/html/
|
||||
chown -h www-data:www-data /var/www/html/.env
|
||||
ln -s /pelican-data/database/database.sqlite /var/www/html/database/
|
||||
|
||||
if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then
|
||||
@@ -52,14 +51,14 @@ crond -L /var/log/crond -l 5
|
||||
export SUPERVISORD_CADDY=false
|
||||
|
||||
## disable caddy if SKIP_CADDY is set
|
||||
if [[ "${SKIP_CADDY:-}" == "true" ]]; then
|
||||
echo "Starting PHP-FPM only"
|
||||
else
|
||||
if [[ -z $SKIP_CADDY ]]; then
|
||||
echo "Starting PHP-FPM and Caddy"
|
||||
export SUPERVISORD_CADDY=true
|
||||
else
|
||||
echo "Starting PHP-FPM only"
|
||||
fi
|
||||
|
||||
chown -R www-data:www-data /pelican-data/.env /pelican-data/database
|
||||
chown -R www-data:www-data . /pelican-data/.env /pelican-data/database
|
||||
|
||||
echo "Starting Supervisord"
|
||||
exec "$@"
|
||||
|
||||
4
.github/docker/supervisord.conf
vendored
4
.github/docker/supervisord.conf
vendored
@@ -1,7 +1,5 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
username=dummy
|
||||
password=dummy
|
||||
|
||||
[supervisord]
|
||||
logfile=/var/log/supervisord/supervisord.log ; supervisord log file
|
||||
@@ -20,8 +18,6 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
username=dummy
|
||||
password=dummy
|
||||
|
||||
[program:php-fpm]
|
||||
command=/usr/local/sbin/php-fpm -F
|
||||
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
database: ["mariadb:10.3", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
|
||||
6
.github/workflows/docker-publish.yml
vendored
6
.github/workflows/docker-publish.yml
vendored
@@ -9,10 +9,6 @@ on:
|
||||
types:
|
||||
- published
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push
|
||||
@@ -30,7 +26,7 @@ jobs:
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
images: ghcr.io/pelican-dev/panel
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
|
||||
@@ -38,8 +38,7 @@ RUN touch .env
|
||||
RUN composer install --no-dev --optimize-autoloader
|
||||
|
||||
# Set file permissions
|
||||
RUN chmod -R 755 storage bootstrap/cache \
|
||||
&& chown -R www-data:www-data ./
|
||||
RUN chmod -R 755 storage bootstrap/cache
|
||||
|
||||
# Add scheduler to cron
|
||||
RUN echo "* * * * * php /var/www/html/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data -
|
||||
|
||||
45
app/Casts/EndpointCollection.php
Normal file
45
app/Casts/EndpointCollection.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\Models\Objects\Endpoint;
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class EndpointCollection implements Castable
|
||||
{
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class() implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
if (!isset($attributes[$key])) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
$data = json_decode($attributes[$key], true);
|
||||
|
||||
return (new Collection($data))->map(function ($value) {
|
||||
return new Endpoint($value);
|
||||
});
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
if (!is_array($value) && !$value instanceof Collection) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
if (!$value instanceof Collection) {
|
||||
$value = new Collection($value);
|
||||
}
|
||||
|
||||
return [
|
||||
'ports' => $value->toJson(),
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ class InfoCommand extends Command
|
||||
{
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', $this->versionService->currentPanelVersion()],
|
||||
['Latest Version', $this->versionService->latestPanelVersion()],
|
||||
['Panel Version', $this->versionService->versionData()['version']],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
], 'compact');
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ProcessRunnableCommand extends Command
|
||||
{
|
||||
@@ -23,7 +24,7 @@ class ProcessRunnableCommand extends Command
|
||||
->whereRelation('server', fn (Builder $builder) => $builder->whereNull('status'))
|
||||
->where('is_active', true)
|
||||
->where('is_processing', false)
|
||||
->where('next_run_at', '<=', now('UTC')->toDateTimeString())
|
||||
->where('next_run_at', '<=', Carbon::now()->toDateTimeString())
|
||||
->get();
|
||||
|
||||
if ($schedules->count() < 1) {
|
||||
|
||||
@@ -65,7 +65,6 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
$powerRepository = $this->powerRepository;
|
||||
// @phpstan-ignore-next-line
|
||||
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
|
||||
$bar->clear();
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Jobs\NodeStatistics;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Database\Console\PruneCommand;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
@@ -49,9 +48,5 @@ class Kernel extends ConsoleKernel
|
||||
if (config('activity.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
|
||||
}
|
||||
|
||||
if (config('panel.webhook.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [Webhook::class]])->daily();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +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 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';
|
||||
case json = 'json';
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,4 @@ enum RolePermissionModels: string
|
||||
case Role = 'role';
|
||||
case Server = 'server';
|
||||
case User = 'user';
|
||||
case Webhook = 'webhook';
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ActivityLogged extends Event
|
||||
{
|
||||
public function __construct(public ActivityLog $model) {}
|
||||
public function __construct(public ActivityLog $model)
|
||||
{
|
||||
}
|
||||
|
||||
public function is(string $event): bool
|
||||
{
|
||||
|
||||
@@ -7,5 +7,7 @@ use App\Events\Event;
|
||||
|
||||
class DirectLogin extends Event
|
||||
{
|
||||
public function __construct(public User $user, public bool $remember) {}
|
||||
public function __construct(public User $user, public bool $remember)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,7 @@ class FailedCaptcha extends Event
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public string $ip, public ?string $message) {}
|
||||
public function __construct(public string $ip, public string $domain)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,7 @@ class FailedPasswordReset extends Event
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public string $ip, public string $email) {}
|
||||
public function __construct(public string $ip, public string $email)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@ use App\Events\Event;
|
||||
|
||||
class ProvidedAuthenticationToken extends Event
|
||||
{
|
||||
public function __construct(public User $user, public bool $recovery = false) {}
|
||||
public function __construct(public User $user, public bool $recovery = false)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
abstract class Event {}
|
||||
abstract class Event
|
||||
{
|
||||
}
|
||||
|
||||
19
app/Events/Server/Created.php
Normal file
19
app/Events/Server/Created.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Server/Creating.php
Normal file
19
app/Events/Server/Creating.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Server/Deleted.php
Normal file
19
app/Events/Server/Deleted.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Server/Deleting.php
Normal file
19
app/Events/Server/Deleting.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,7 @@ class Installed extends Event
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server) {}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
19
app/Events/Server/Saved.php
Normal file
19
app/Events/Server/Saved.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Saved extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Server/Saving.php
Normal file
19
app/Events/Server/Saving.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Saving extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Server/Updated.php
Normal file
19
app/Events/Server/Updated.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Server/Updating.php
Normal file
19
app/Events/Server/Updating.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Subuser/Created.php
Normal file
19
app/Events/Subuser/Created.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Subuser/Creating.php
Normal file
19
app/Events/Subuser/Creating.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Subuser/Deleted.php
Normal file
19
app/Events/Subuser/Deleted.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/Subuser/Deleting.php
Normal file
19
app/Events/Subuser/Deleting.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/User/Created.php
Normal file
19
app/Events/User/Created.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/User/Creating.php
Normal file
19
app/Events/User/Creating.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/User/Deleted.php
Normal file
19
app/Events/User/Deleted.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/Events/User/Deleting.php
Normal file
19
app/Events/User/Deleting.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
7
app/Exceptions/AccountNotFoundException.php
Normal file
7
app/Exceptions/AccountNotFoundException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class AccountNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
7
app/Exceptions/AutoDeploymentException.php
Normal file
7
app/Exceptions/AutoDeploymentException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class AutoDeploymentException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Http\Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Container\Container;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
@@ -66,6 +67,9 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
|
||||
|
||||
return redirect()->back()->withInput();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Http\Base;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class InvalidPasswordProvidedException extends DisplayException {}
|
||||
class InvalidPasswordProvidedException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ use GuzzleHttp\Exception\GuzzleException;
|
||||
use App\Exceptions\DisplayException;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
|
||||
/**
|
||||
* @method \GuzzleHttp\Exception\GuzzleException getPrevious()
|
||||
*/
|
||||
class DaemonConnectionException extends DisplayException
|
||||
{
|
||||
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Http\Server;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class FileTypeNotEditableException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -2,4 +2,6 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class PanelException extends \Exception {}
|
||||
class PanelException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository\Daemon;
|
||||
|
||||
use App\Exceptions\Repository\RepositoryException;
|
||||
|
||||
class InvalidPowerSignalException extends RepositoryException
|
||||
{
|
||||
}
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Repository;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class DuplicateDatabaseNameException extends DisplayException {}
|
||||
class DuplicateDatabaseNameException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
9
app/Exceptions/Repository/RepositoryException.php
Normal file
9
app/Exceptions/Repository/RepositoryException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class RepositoryException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class AutoAllocationNotEnabledException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* AutoAllocationNotEnabledException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'Server auto-allocation is not enabled for this instance.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class CidrOutOfRangeException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* CidrOutOfRangeException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(trans('exceptions.allocations.cidr_out_of_range'));
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class InvalidPortMappingException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* InvalidPortMappingException constructor.
|
||||
*/
|
||||
public function __construct(mixed $port)
|
||||
{
|
||||
parent::__construct(trans('exceptions.allocations.invalid_mapping', ['port' => $port]));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoAutoAllocationSpaceAvailableException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* NoAutoAllocationSpaceAvailableException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'Cannot assign additional allocation: no more space available on node.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class PortOutOfRangeException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* PortOutOfRangeException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(trans('exceptions.allocations.port_out_of_range'));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class ServerUsingAllocationException extends DisplayException {}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class TooManyPortsInRangeException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* TooManyPortsInRangeException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(trans('exceptions.allocations.too_many_ports'));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Deployment;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoViableAllocationException extends DisplayException {}
|
||||
9
app/Exceptions/Service/Egg/BadJsonFormatException.php
Normal file
9
app/Exceptions/Service/Egg/BadJsonFormatException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class BadJsonFormatException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class HasChildrenException extends DisplayException {}
|
||||
class HasChildrenException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoParentConfigurationFoundException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Egg\Variable;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class BadValidationRuleException extends DisplayException {}
|
||||
class BadValidationRuleException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Egg\Variable;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class ReservedVariableNameException extends DisplayException {}
|
||||
class ReservedVariableNameException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class InvalidFileUploadException extends DisplayException {}
|
||||
class InvalidFileUploadException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Node;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class ConfigurationNotPersistedException extends DisplayException {}
|
||||
class ConfigurationNotPersistedException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Schedule\Task;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class TaskIntervalTooLongException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Server;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class RequiredVariableMissingException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Subuser;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class ServerSubuserExistsException extends DisplayException {}
|
||||
class ServerSubuserExistsException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Subuser;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class UserIsServerOwnerException extends DisplayException {}
|
||||
class UserIsServerOwnerException extends DisplayException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Transformer;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class InvalidTransformerLevelException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -27,7 +27,9 @@ class BackupManager
|
||||
/**
|
||||
* BackupManager constructor.
|
||||
*/
|
||||
public function __construct(protected Application $app) {}
|
||||
public function __construct(protected Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a backup adapter instance.
|
||||
|
||||
13
app/Extensions/Facades/Theme.php
Normal file
13
app/Extensions/Facades/Theme.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Theme extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'extensions.themes';
|
||||
}
|
||||
}
|
||||
21
app/Extensions/Themes/Theme.php
Normal file
21
app/Extensions/Themes/Theme.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Themes;
|
||||
|
||||
class Theme
|
||||
{
|
||||
public function js(string $path): string
|
||||
{
|
||||
return sprintf('<script src="%s"></script>' . PHP_EOL, $this->getUrl($path));
|
||||
}
|
||||
|
||||
public function css(string $path): string
|
||||
{
|
||||
return sprintf('<link media="all" type="text/css" rel="stylesheet" href="%s"/>' . PHP_EOL, $this->getUrl($path));
|
||||
}
|
||||
|
||||
protected function getUrl(string $path): string
|
||||
{
|
||||
return '/themes/panel/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
14
app/Facades/LogBatch.php
Normal file
14
app/Facades/LogBatch.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use App\Services\Activity\ActivityLogBatchService;
|
||||
|
||||
class LogBatch extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return ActivityLogBatchService::class;
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
|
||||
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Node;
|
||||
use App\Services\Allocations\AssignmentService;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
/**
|
||||
* @method Node getOwnerRecord()
|
||||
*/
|
||||
class AllocationsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'allocations';
|
||||
|
||||
protected static ?string $icon = 'tabler-plug-connected';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('ip')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('ip')
|
||||
|
||||
// Non Primary Allocations
|
||||
// ->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->id !== $allocation->server?->allocation_id)
|
||||
|
||||
// All assigned allocations
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->server_id === null)
|
||||
->paginationPageOptions(['10', '20', '50', '100', '200', '500', '1000'])
|
||||
->searchable()
|
||||
->selectCurrentPageOnly() //Prevent people from trying to nuke 30,000 ports at once.... -,-
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
TextColumn::make('port')
|
||||
->searchable()
|
||||
->label('Port'),
|
||||
TextColumn::make('server.name')
|
||||
->label('Server')
|
||||
->icon('tabler-brand-docker')
|
||||
->visibleFrom('md')
|
||||
->searchable()
|
||||
->url(fn (Allocation $allocation): string => $allocation->server ? route('filament.admin.resources.servers.edit', ['record' => $allocation->server]) : ''),
|
||||
TextInputColumn::make('ip_alias')
|
||||
->searchable()
|
||||
->label('Alias'),
|
||||
TextInputColumn::make('ip')
|
||||
->searchable()
|
||||
->label('IP'),
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\Action::make('create new allocation')->label('Create Allocations')
|
||||
->form(fn () => [
|
||||
TextInput::make('allocation_ip')
|
||||
->datalist($this->getOwnerRecord()->ipAddresses())
|
||||
->label('IP Address')
|
||||
->inlineLabel()
|
||||
->ipv4()
|
||||
->helperText("Usually your machine's public IP unless you are port forwarding.")
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
->label('Alias')
|
||||
->inlineLabel()
|
||||
->default(null)
|
||||
->helperText('Optional display name to help you remember what these are.')
|
||||
->required(false),
|
||||
TagsInput::make('allocation_ports')
|
||||
->placeholder('Examples: 27015, 27017-27019')
|
||||
->helperText(new HtmlString('
|
||||
These are the ports that users can connect to this Server through.
|
||||
<br />
|
||||
You would have to port forward these on your home network.
|
||||
'))
|
||||
->label('Ports')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$ports = collect();
|
||||
$update = false;
|
||||
foreach ($state as $portEntry) {
|
||||
if (!str_contains($portEntry, '-')) {
|
||||
if (is_numeric($portEntry)) {
|
||||
$ports->push((int) $portEntry);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not add non numerical ports
|
||||
$update = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$update = true;
|
||||
[$start, $end] = explode('-', $portEntry);
|
||||
if (!is_numeric($start) || !is_numeric($end)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = max((int) $start, 0);
|
||||
$end = min((int) $end, 2 ** 16 - 1);
|
||||
foreach (range($start, $end) as $i) {
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
|
||||
$uniquePorts = $ports->unique()->values();
|
||||
if ($ports->count() > $uniquePorts->count()) {
|
||||
$update = true;
|
||||
$ports = $uniquePorts;
|
||||
}
|
||||
|
||||
$sortedPorts = $ports->sort()->values();
|
||||
if ($sortedPorts->all() !== $ports->all()) {
|
||||
$update = true;
|
||||
$ports = $sortedPorts;
|
||||
}
|
||||
|
||||
$ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values();
|
||||
|
||||
if ($update) {
|
||||
$set('allocation_ports', $ports->all());
|
||||
}
|
||||
})
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord(), $data)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('delete allocation')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ServerResource\RelationManagers;
|
||||
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Server;
|
||||
use App\Services\Allocations\AssignmentService;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\AssociateAction;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
/**
|
||||
* @method Server getOwnerRecord()
|
||||
*/
|
||||
class AllocationsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'allocations';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('ip')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->selectCurrentPageOnly()
|
||||
->recordTitleAttribute('ip')
|
||||
->recordTitle(fn (Allocation $allocation) => "$allocation->ip:$allocation->port")
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $record) => $record->id !== $this->getOwnerRecord()->allocation_id)
|
||||
->inverseRelationship('server')
|
||||
->columns([
|
||||
TextColumn::make('ip')->label('IP'),
|
||||
TextColumn::make('port')->label('Port'),
|
||||
TextInputColumn::make('ip_alias')->label('Alias'),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
default => 'tabler-star',
|
||||
})
|
||||
->color(fn ($state) => match ($state) {
|
||||
true => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords())
|
||||
->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id)
|
||||
->label('Primary'),
|
||||
])
|
||||
->actions([
|
||||
Action::make('make-primary')
|
||||
->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords())
|
||||
->label(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id ? '' : 'Make Primary'),
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()->label('Create Allocation')
|
||||
->createAnother(false)
|
||||
->form(fn () => [
|
||||
TextInput::make('allocation_ip')
|
||||
->datalist($this->getOwnerRecord()->node->ipAddresses())
|
||||
->label('IP Address')
|
||||
->inlineLabel()
|
||||
->ipv4()
|
||||
->helperText("Usually your machine's public IP unless you are port forwarding.")
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
->label('Alias')
|
||||
->inlineLabel()
|
||||
->default(null)
|
||||
->helperText('Optional display name to help you remember what these are.')
|
||||
->required(false),
|
||||
TagsInput::make('allocation_ports')
|
||||
->placeholder('Examples: 27015, 27017-27019')
|
||||
->helperText(new HtmlString('
|
||||
These are the ports that users can connect to this Server through.
|
||||
<br />
|
||||
You would have to port forward these on your home network.
|
||||
'))
|
||||
->label('Ports')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$ports = collect();
|
||||
$update = false;
|
||||
foreach ($state as $portEntry) {
|
||||
if (!str_contains($portEntry, '-')) {
|
||||
if (is_numeric($portEntry)) {
|
||||
$ports->push((int) $portEntry);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not add non numerical ports
|
||||
$update = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$update = true;
|
||||
[$start, $end] = explode('-', $portEntry);
|
||||
if (!is_numeric($start) || !is_numeric($end)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = max((int) $start, 0);
|
||||
$end = min((int) $end, 2 ** 16 - 1);
|
||||
foreach (range($start, $end) as $i) {
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
|
||||
$uniquePorts = $ports->unique()->values();
|
||||
if ($ports->count() > $uniquePorts->count()) {
|
||||
$update = true;
|
||||
$ports = $uniquePorts;
|
||||
}
|
||||
|
||||
$sortedPorts = $ports->sort()->values();
|
||||
if ($sortedPorts->all() !== $ports->all()) {
|
||||
$update = true;
|
||||
$ports = $sortedPorts;
|
||||
}
|
||||
|
||||
$ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values();
|
||||
|
||||
if ($update) {
|
||||
$set('allocation_ports', $ports->all());
|
||||
}
|
||||
})
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())),
|
||||
AssociateAction::make()
|
||||
->multiple()
|
||||
->associateAnother(false)
|
||||
->preloadRecordSelect()
|
||||
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
|
||||
->label('Add Allocation'),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DissociateBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
class WebhookResource extends Resource
|
||||
{
|
||||
protected static ?string $model = WebhookConfiguration::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-webhook';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static ?string $label = 'Webhooks';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListWebhookConfigurations::route('/'),
|
||||
'create' => Pages\CreateWebhookConfiguration::route('/create'),
|
||||
'edit' => Pages\EditWebhookConfiguration::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateWebhookConfiguration extends CreateRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('endpoint')
|
||||
->activeUrl()
|
||||
->required(),
|
||||
TextInput::make('description')
|
||||
->required(),
|
||||
CheckboxList::make('events')
|
||||
->lazy()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->gridDirection('row')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditWebhookConfiguration extends EditRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('endpoint')
|
||||
->label('Endpoint')
|
||||
->activeUrl()
|
||||
->required(),
|
||||
TextInput::make('description')
|
||||
->label('Description')
|
||||
->required(),
|
||||
CheckboxList::make('events')
|
||||
->label('Events')
|
||||
->lazy()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->gridDirection('row')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
->label('Delete')
|
||||
->modalHeading('Are you sure you want to delete this?')
|
||||
->modalDescription('')
|
||||
->modalSubmitActionLabel('Delete'),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
|
||||
class ListWebhookConfigurations extends ListRecords
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('description')
|
||||
->label('Description'),
|
||||
TextColumn::make('endpoint')
|
||||
->label('Endpoint'),
|
||||
])
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->label('Delete'),
|
||||
EditAction::make()
|
||||
->label('Edit'),
|
||||
])
|
||||
->emptyStateIcon('tabler-webhook')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading('No Webhooks')
|
||||
->emptyStateActions([
|
||||
CreateAction::make('create')
|
||||
->label('Create Webhook')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
->label('Create Webhook')
|
||||
->hidden(fn () => WebhookConfiguration::count() <= 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\App\Resources;
|
||||
|
||||
use App\Filament\App\Resources\ServerResource\Pages;
|
||||
use App\Models\Server;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
class ServerResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Server::class;
|
||||
|
||||
protected static ?string $slug = '/';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListServers::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\App\Resources\ServerResource\Pages;
|
||||
|
||||
use App\Filament\App\Resources\ServerResource;
|
||||
use App\Filament\Server\Pages\Console;
|
||||
use App\Models\Server;
|
||||
use App\Tables\Columns\ServerEntryColumn;
|
||||
use Carbon\CarbonInterface;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Columns\Layout\Stack;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class ListServers extends ListRecords
|
||||
{
|
||||
protected static string $resource = ServerResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$baseQuery = auth()->user()->can('viewList server') ? Server::query() : auth()->user()->accessibleServers();
|
||||
|
||||
return $table
|
||||
->paginated(false)
|
||||
->query(fn () => $baseQuery)
|
||||
->poll('15s')
|
||||
->columns([
|
||||
Stack::make([
|
||||
ServerEntryColumn::make('server_entry')
|
||||
->searchable(['name']),
|
||||
]),
|
||||
])
|
||||
->contentGrid([
|
||||
'default' => 1,
|
||||
'xl' => 2,
|
||||
])
|
||||
->recordUrl(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
|
||||
->emptyStateIcon('tabler-brand-docker')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading('You don\'t have access to any servers!')
|
||||
->filters([
|
||||
TernaryFilter::make('only_my_servers')
|
||||
->label('Owned by')
|
||||
->placeholder('All servers')
|
||||
->trueLabel('My Servers')
|
||||
->falseLabel('Others\' Servers')
|
||||
->default()
|
||||
->queries(
|
||||
true: fn (Builder $query) => $query->where('owner_id', auth()->user()->id),
|
||||
false: fn (Builder $query) => $query->whereNot('owner_id', auth()->user()->id),
|
||||
blank: fn (Builder $query) => $query,
|
||||
),
|
||||
SelectFilter::make('egg')
|
||||
->relationship('egg', 'name', fn (Builder $query) => $query->whereIn('id', $baseQuery->pluck('egg_id')))
|
||||
->searchable()
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function uptime(Server $server): string
|
||||
{
|
||||
$uptime = Arr::get($server->resources(), 'uptime', 0);
|
||||
|
||||
if ($uptime === 0) {
|
||||
return 'Offline';
|
||||
}
|
||||
|
||||
return now()->subMillis($uptime)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 2);
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function cpu(Server $server): string
|
||||
{
|
||||
$cpu = Number::format(Arr::get($server->resources(), 'cpu_absolute', 0), maxPrecision: 2, locale: auth()->user()->language) . '%';
|
||||
$max = Number::format($server->cpu, locale: auth()->user()->language) . '%';
|
||||
|
||||
return $cpu . ($server->cpu > 0 ? ' Of ' . $max : '');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function memory(Server $server): string
|
||||
{
|
||||
$latestMemoryUsed = Arr::get($server->resources(), 'memory_bytes', 0);
|
||||
$totalMemory = Arr::get($server->resources(), 'memory_limit_bytes', 0);
|
||||
|
||||
$used = config('panel.use_binary_prefix')
|
||||
? Number::format($latestMemoryUsed / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($latestMemoryUsed / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
|
||||
if ($totalMemory === 0) {
|
||||
$total = config('panel.use_binary_prefix')
|
||||
? Number::format($server->memory / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($server->memory / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
} else {
|
||||
$total = config('panel.use_binary_prefix')
|
||||
? Number::format($totalMemory / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($totalMemory / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
}
|
||||
|
||||
return $used . ($server->memory > 0 ? ' Of ' . $total : '');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function disk(Server $server): string
|
||||
{
|
||||
$usedDisk = Arr::get($server->resources(), 'disk_bytes', 0);
|
||||
|
||||
$used = config('panel.use_binary_prefix')
|
||||
? Number::format($usedDisk / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($usedDisk / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
|
||||
$total = config('panel.use_binary_prefix')
|
||||
? Number::format($server->disk / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($server->disk / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
|
||||
return $used . ($server->disk > 0 ? ' Of ' . $total : '');
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Auth;
|
||||
|
||||
use Coderflex\FilamentTurnstile\Forms\Components\Turnstile;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Pages\Auth\Login as BaseLogin;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class Login extends BaseLogin
|
||||
{
|
||||
protected function getForms(): array
|
||||
{
|
||||
return [
|
||||
'form' => $this->form(
|
||||
$this->makeForm()
|
||||
->schema([
|
||||
$this->getLoginFormComponent(),
|
||||
$this->getPasswordFormComponent(),
|
||||
$this->getRememberFormComponent(),
|
||||
$this->getOAuthFormComponent(),
|
||||
Turnstile::make('captcha')
|
||||
->hidden(!config('turnstile.turnstile_enabled'))
|
||||
->validationMessages([
|
||||
'required' => config('turnstile.error_messages.turnstile_check_message'),
|
||||
]),
|
||||
])
|
||||
->statePath('data'),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function throwFailureValidationException(): never
|
||||
{
|
||||
$this->dispatch('reset-captcha');
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'data.login' => __('filament-panels::pages/auth/login.messages.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getLoginFormComponent(): Component
|
||||
{
|
||||
return TextInput::make('login')
|
||||
->label('Login')
|
||||
->required()
|
||||
->autocomplete()
|
||||
->autofocus()
|
||||
->extraInputAttributes(['tabindex' => 1]);
|
||||
}
|
||||
|
||||
protected function getOAuthFormComponent(): Component
|
||||
{
|
||||
$actions = [];
|
||||
|
||||
foreach (config('auth.oauth') as $name => $data) {
|
||||
if (!$data['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$actions[] = Action::make("oauth_$name")
|
||||
->label(Str::title($name))
|
||||
->icon($data['icon'])
|
||||
->color($data['color'])
|
||||
->url(route('auth.oauth.redirect', ['driver' => $name], false));
|
||||
}
|
||||
|
||||
return Actions::make($actions);
|
||||
}
|
||||
|
||||
protected function getCredentialsFromFormData(array $data): array
|
||||
{
|
||||
$loginType = filter_var($data['login'], FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
|
||||
|
||||
return [
|
||||
$loginType => mb_strtolower($data['login']),
|
||||
'password' => $data['password'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\NodeResource\Pages\CreateNode;
|
||||
use App\Filament\Admin\Resources\NodeResource\Pages\ListNodes;
|
||||
use App\Filament\Resources\NodeResource\Pages\ListNodes;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
@@ -40,8 +39,8 @@ class Dashboard extends Page
|
||||
{
|
||||
return [
|
||||
'inDevelopment' => config('app.version') === 'canary',
|
||||
'version' => $this->softwareVersionService->currentPanelVersion(),
|
||||
'latestVersion' => $this->softwareVersionService->latestPanelVersion(),
|
||||
'version' => $this->softwareVersionService->versionData()['version'],
|
||||
'latestVersion' => $this->softwareVersionService->getPanel(),
|
||||
'isLatest' => $this->softwareVersionService->isLatestPanel(),
|
||||
'eggsCount' => Egg::query()->count(),
|
||||
'nodesList' => ListNodes::getUrl(),
|
||||
@@ -66,13 +65,13 @@ class Dashboard extends Page
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-first-node.button_label'))
|
||||
->icon('tabler-server-2')
|
||||
->url(CreateNode::getUrl()),
|
||||
->url(route('filament.admin.resources.nodes.create')),
|
||||
],
|
||||
'supportActions' => [
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-support.button_donate'))
|
||||
->icon('tabler-cash')
|
||||
->url('https://pelican.dev/donate', true)
|
||||
->url($this->softwareVersionService->getDonations(), true)
|
||||
->color('success'),
|
||||
],
|
||||
'helpActions' => [
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Filament\Pages\Installer;
|
||||
|
||||
use App\Filament\Admin\Pages\Dashboard;
|
||||
use App\Filament\Pages\Installer\Steps\CacheStep;
|
||||
use App\Filament\Pages\Dashboard;
|
||||
use App\Filament\Pages\Installer\Steps\AdminUserStep;
|
||||
use App\Filament\Pages\Installer\Steps\CompletedStep;
|
||||
use App\Filament\Pages\Installer\Steps\DatabaseStep;
|
||||
use App\Filament\Pages\Installer\Steps\EnvironmentStep;
|
||||
use App\Filament\Pages\Installer\Steps\QueueStep;
|
||||
use App\Filament\Pages\Installer\Steps\RedisStep;
|
||||
use App\Filament\Pages\Installer\Steps\RequirementsStep;
|
||||
use App\Filament\Pages\Installer\Steps\SessionStep;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Traits\CheckMigrationsTrait;
|
||||
@@ -19,10 +19,12 @@ use Filament\Forms\Components\Wizard;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\SimplePage;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
@@ -40,9 +42,11 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
|
||||
protected static string $view = 'filament.pages.installer';
|
||||
|
||||
private User $user;
|
||||
|
||||
public function getMaxWidth(): MaxWidth|string
|
||||
{
|
||||
return config('panel.filament.display-width', 'screen-2xl');
|
||||
return MaxWidth::SevenExtraLarge;
|
||||
}
|
||||
|
||||
public static function isInstalled(): bool
|
||||
@@ -65,9 +69,10 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
RequirementsStep::make(),
|
||||
EnvironmentStep::make($this),
|
||||
DatabaseStep::make($this),
|
||||
CacheStep::make($this),
|
||||
QueueStep::make($this),
|
||||
SessionStep::make(),
|
||||
RedisStep::make($this)
|
||||
->hidden(fn (Get $get) => $get('env_general.SESSION_DRIVER') != 'redis' && $get('env_general.QUEUE_CONNECTION') != 'redis' && $get('env_general.CACHE_STORE') != 'redis'),
|
||||
AdminUserStep::make($this),
|
||||
CompletedStep::make(),
|
||||
])
|
||||
->persistStepInQueryString()
|
||||
->nextAction(fn (Action $action) => $action->keyBindings('enter'))
|
||||
@@ -89,33 +94,23 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
return 'data';
|
||||
}
|
||||
|
||||
public function submit(UserCreationService $userCreationService): void
|
||||
public function submit(): RedirectResponse
|
||||
{
|
||||
try {
|
||||
// Disable installer
|
||||
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
|
||||
// Disable installer
|
||||
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
|
||||
|
||||
// Run migrations
|
||||
$this->runMigrations();
|
||||
// Login user
|
||||
$this->user ??= User::all()->filter(fn ($user) => $user->isRootAdmin())->first();
|
||||
auth()->guard()->login($this->user, true);
|
||||
|
||||
// Create admin user & login
|
||||
$user = $this->createAdminUser($userCreationService);
|
||||
auth()->guard()->login($user, true);
|
||||
|
||||
// Write session data at the very end to avoid "page expired" errors
|
||||
$this->writeToEnv('env_session');
|
||||
|
||||
// Redirect to admin panel
|
||||
$this->redirect(Dashboard::getUrl());
|
||||
} catch (Halt) {
|
||||
}
|
||||
// Redirect to admin panel
|
||||
return redirect(Dashboard::getUrl());
|
||||
}
|
||||
|
||||
public function writeToEnv(string $key): void
|
||||
{
|
||||
try {
|
||||
$variables = array_get($this->data, $key);
|
||||
$variables = array_filter($variables); // Filter array to remove NULL values
|
||||
$this->writeToEnvironment($variables);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
@@ -133,12 +128,13 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
Artisan::call('config:clear');
|
||||
}
|
||||
|
||||
public function runMigrations(): void
|
||||
public function runMigrations(string $driver): void
|
||||
{
|
||||
try {
|
||||
Artisan::call('migrate', [
|
||||
'--force' => true,
|
||||
'--seed' => true,
|
||||
'--database' => $driver,
|
||||
]);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
@@ -164,13 +160,12 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
}
|
||||
}
|
||||
|
||||
public function createAdminUser(UserCreationService $userCreationService): User
|
||||
public function createAdminUser(UserCreationService $userCreationService): void
|
||||
{
|
||||
try {
|
||||
$userData = array_get($this->data, 'user');
|
||||
$userData['root_admin'] = true;
|
||||
|
||||
return $userCreationService->handle($userData);
|
||||
$this->user = $userCreationService->handle($userData);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
|
||||
34
app/Filament/Pages/Installer/Steps/AdminUserStep.php
Normal file
34
app/Filament/Pages/Installer/Steps/AdminUserStep.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
|
||||
class AdminUserStep
|
||||
{
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('user')
|
||||
->label('Admin User')
|
||||
->schema([
|
||||
TextInput::make('user.email')
|
||||
->label('Admin E-Mail')
|
||||
->required()
|
||||
->email()
|
||||
->placeholder('admin@example.com'),
|
||||
TextInput::make('user.username')
|
||||
->label('Admin Username')
|
||||
->required()
|
||||
->placeholder('admin'),
|
||||
TextInput::make('user.password')
|
||||
->label('Admin Password')
|
||||
->required()
|
||||
->password()
|
||||
->revealable(),
|
||||
])
|
||||
->afterValidation(fn (UserCreationService $service) => $installer->createAdminUser($service));
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Redis\RedisManager;
|
||||
|
||||
class CacheStep
|
||||
{
|
||||
public const CACHE_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('cache')
|
||||
->label('Cache')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_cache.CACHE_STORE')
|
||||
->label('Cache Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for caching. We recommend "Filesystem".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::CACHE_DRIVERS)
|
||||
->default(config('cache.default'))
|
||||
->columnSpanFull()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
if ($state !== 'redis') {
|
||||
$set('env_cache.REDIS_HOST', null);
|
||||
$set('env_cache.REDIS_PORT', null);
|
||||
$set('env_cache.REDIS_USERNAME', null);
|
||||
$set('env_cache.REDIS_PASSWORD', null);
|
||||
} else {
|
||||
$set('env_cache.REDIS_HOST', $get('env_cache.REDIS_HOST') ?? '127.0.0.1');
|
||||
$set('env_cache.REDIS_PORT', $get('env_cache.REDIS_PORT') ?? '6379');
|
||||
$set('env_cache.REDIS_USERNAME', null);
|
||||
}
|
||||
}),
|
||||
TextInput::make('env_cache.REDIS_HOST')
|
||||
->label('Redis Host')
|
||||
->placeholder('127.0.0.1')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your redis server. Make sure it is reachable.')
|
||||
->required(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.host') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_PORT')
|
||||
->label('Redis Port')
|
||||
->placeholder('6379')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your redis server.')
|
||||
->required(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.port') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_USERNAME')
|
||||
->label('Redis Username')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your redis user. Can be empty')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.username') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_PASSWORD')
|
||||
->label('Redis Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password for your redis user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.password') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
])
|
||||
->afterValidation(function (Get $get, Application $app) use ($installer) {
|
||||
$driver = $get('env_cache.CACHE_STORE');
|
||||
|
||||
if (!self::testConnection($app, $driver, $get('env_cache.REDIS_HOST'), $get('env_cache.REDIS_PORT'), $get('env_cache.REDIS_USERNAME'), $get('env_cache.REDIS_PASSWORD'))) {
|
||||
throw new Halt('Redis connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_cache');
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(Application $app, string $driver, ?string $host, null|string|int $port, ?string $username, ?string $password): bool
|
||||
{
|
||||
if ($driver !== 'redis') {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$redis = new RedisManager($app, 'predis', [
|
||||
'default' => [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
],
|
||||
]);
|
||||
|
||||
$redis->connection()->command('ping');
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Redis connection failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
34
app/Filament/Pages/Installer/Steps/CompletedStep.php
Normal file
34
app/Filament/Pages/Installer/Steps/CompletedStep.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class CompletedStep
|
||||
{
|
||||
public static function make(): Step
|
||||
{
|
||||
return Step::make('complete')
|
||||
->label('Setup complete')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString('The setup is nearly complete!<br>As last step you need to create a new cronjob that runs every minute to process specific tasks, such as session cleanup and scheduled tasks, and also create a queue worker.')),
|
||||
TextInput::make('crontab')
|
||||
->label(new HtmlString('Run the following command to setup your crontab. Note that <code>www-data</code> is your webserver user. On some systems this username might be different!'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -'),
|
||||
TextInput::make('queueService')
|
||||
->label(new HtmlString('To setup the queue worker service you simply have to run the following command.'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('sudo php ' . base_path() . '/artisan p:environment:queue-service'),
|
||||
Placeholder::make('')
|
||||
->content('After you finished these two last tasks you can click on "Finish" and use your new panel! Have fun!'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -5,102 +5,74 @@ namespace App\Filament\Pages\Installer\Steps;
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DatabaseStep
|
||||
{
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('database')
|
||||
->label('Database')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_database.DB_CONNECTION')
|
||||
->label('Database Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::DATABASE_DRIVERS)
|
||||
->default(config('database.default'))
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
$set('env_database.DB_DATABASE', $state === 'sqlite' ? 'database.sqlite' : 'panel');
|
||||
|
||||
if ($state === 'sqlite') {
|
||||
$set('env_database.DB_HOST', null);
|
||||
$set('env_database.DB_PORT', null);
|
||||
$set('env_database.DB_USERNAME', null);
|
||||
$set('env_database.DB_PASSWORD', null);
|
||||
} else {
|
||||
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
|
||||
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '3306');
|
||||
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
|
||||
}
|
||||
}),
|
||||
TextInput::make('env_database.DB_DATABASE')
|
||||
->label(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
|
||||
->placeholder(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')
|
||||
->label(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
|
||||
->columnSpanFull()
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
|
||||
->hintIconTooltip(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
|
||||
->required()
|
||||
->default('database.sqlite'),
|
||||
->default(fn (Get $get) => env('DB_DATABASE', $get('env_general.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')),
|
||||
TextInput::make('env_database.DB_HOST')
|
||||
->label('Database Host')
|
||||
->placeholder('127.0.0.1')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your database. Make sure it is reachable.')
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_HOST', '127.0.0.1') : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_PORT')
|
||||
->label('Database Port')
|
||||
->placeholder('3306')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your database.')
|
||||
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_PORT', 3306) : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_USERNAME')
|
||||
->label('Database Username')
|
||||
->placeholder('pelican')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your database user.')
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_USERNAME', 'pelican') : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_PASSWORD')
|
||||
->label('Database Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password of your database user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_PASSWORD') : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
])
|
||||
->afterValidation(function (Get $get) use ($installer) {
|
||||
$driver = $get('env_database.DB_CONNECTION');
|
||||
$driver = $get('env_general.DB_CONNECTION');
|
||||
|
||||
if (!self::testConnection($driver, $get('env_database.DB_HOST'), $get('env_database.DB_PORT'), $get('env_database.DB_DATABASE'), $get('env_database.DB_USERNAME'), $get('env_database.DB_PASSWORD'))) {
|
||||
throw new Halt('Database connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_database');
|
||||
|
||||
$installer->runMigrations($driver);
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(string $driver, ?string $host, null|string|int $port, ?string $database, ?string $username, ?string $password): bool
|
||||
private static function testConnection(string $driver, string $host, string $port, string $database, string $username, string $password): bool
|
||||
{
|
||||
if ($driver === 'sqlite') {
|
||||
return true;
|
||||
|
||||
@@ -3,12 +3,40 @@
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Set;
|
||||
|
||||
class EnvironmentStep
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
public const CACHE_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public const SESSION_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'database' => 'Database',
|
||||
'cookie' => 'Cookie',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public const QUEUE_DRIVERS = [
|
||||
'database' => 'Database',
|
||||
'sync' => 'Sync',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('environment')
|
||||
@@ -26,26 +54,44 @@ class EnvironmentStep
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('This will be the URL you access your Panel from.')
|
||||
->required()
|
||||
->default(url('')),
|
||||
Fieldset::make('adminuser')
|
||||
->label('Admin User')
|
||||
->columns(3)
|
||||
->schema([
|
||||
TextInput::make('user.email')
|
||||
->label('E-Mail')
|
||||
->required()
|
||||
->email()
|
||||
->placeholder('admin@example.com'),
|
||||
TextInput::make('user.username')
|
||||
->label('Username')
|
||||
->required()
|
||||
->placeholder('admin'),
|
||||
TextInput::make('user.password')
|
||||
->label('Password')
|
||||
->required()
|
||||
->password()
|
||||
->revealable(),
|
||||
]),
|
||||
->default(url(''))
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('env_general.SESSION_SECURE_COOKIE', str_starts_with($state, 'https://') ? 'true' : 'false')),
|
||||
TextInput::make('env_general.SESSION_SECURE_COOKIE')
|
||||
->hidden()
|
||||
->default(str_starts_with(url(''), 'https://') ? 'true' : 'false'),
|
||||
ToggleButtons::make('env_general.CACHE_STORE')
|
||||
->label('Cache Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for caching. We recommend "Filesystem".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::CACHE_DRIVERS)
|
||||
->default(config('cache.default', 'file')),
|
||||
ToggleButtons::make('env_general.SESSION_DRIVER')
|
||||
->label('Session Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::SESSION_DRIVERS)
|
||||
->default(config('session.driver', 'file')),
|
||||
ToggleButtons::make('env_general.QUEUE_CONNECTION')
|
||||
->label('Queue Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::QUEUE_DRIVERS)
|
||||
->default(config('queue.default', 'database')),
|
||||
ToggleButtons::make('env_general.DB_CONNECTION')
|
||||
->label('Database Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::DATABASE_DRIVERS)
|
||||
->default(config('database.default', 'sqlite')),
|
||||
])
|
||||
->afterValidation(fn () => $installer->writeToEnv('env_general'));
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class QueueStep
|
||||
{
|
||||
public const QUEUE_DRIVERS = [
|
||||
'database' => 'Database',
|
||||
'redis' => 'Redis',
|
||||
'sync' => 'Sync',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('queue')
|
||||
->label('Queue')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_queue.QUEUE_CONNECTION')
|
||||
->label('Queue Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::QUEUE_DRIVERS)
|
||||
->disableOptionWhen(fn ($value, Get $get) => $value === 'redis' && $get('env_cache.CACHE_STORE') !== 'redis')
|
||||
->default(config('queue.default')),
|
||||
Toggle::make('done')
|
||||
->label('I have done both steps below.')
|
||||
->accepted(fn () => !file_exists('/.dockerenv'))
|
||||
->inline(false)
|
||||
->validationMessages([
|
||||
'accepted' => 'You need to do both steps before continuing!',
|
||||
])
|
||||
->hidden(fn () => file_exists('/.dockerenv')),
|
||||
TextInput::make('crontab')
|
||||
->label(new HtmlString('Run the following command to set up your crontab. Note that <code>www-data</code> is your webserver user. On some systems this username might be different!'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -')
|
||||
->hidden(fn () => file_exists('/.dockerenv'))
|
||||
->columnSpanFull(),
|
||||
TextInput::make('queueService')
|
||||
->label(new HtmlString('To setup the queue worker service you simply have to run the following command.'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('sudo php ' . base_path() . '/artisan p:environment:queue-service')
|
||||
->hidden(fn () => file_exists('/.dockerenv'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->afterValidation(fn () => $installer->writeToEnv('env_queue'));
|
||||
}
|
||||
}
|
||||
82
app/Filament/Pages/Installer/Steps/RedisStep.php
Normal file
82
app/Filament/Pages/Installer/Steps/RedisStep.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class RedisStep
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('redis')
|
||||
->label('Redis')
|
||||
->columns()
|
||||
->schema([
|
||||
TextInput::make('env_redis.REDIS_HOST')
|
||||
->label('Redis Host')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your redis server. Make sure it is reachable.')
|
||||
->required()
|
||||
->default(config('database.redis.default.host')),
|
||||
TextInput::make('env_redis.REDIS_PORT')
|
||||
->label('Redis Port')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your redis server.')
|
||||
->required()
|
||||
->default(config('database.redis.default.port')),
|
||||
TextInput::make('env_redis.REDIS_USERNAME')
|
||||
->label('Redis Username')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your redis user. Can be empty')
|
||||
->default(config('database.redis.default.username')),
|
||||
TextInput::make('env_redis.REDIS_PASSWORD')
|
||||
->label('Redis Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password for your redis user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(config('database.redis.default.password')),
|
||||
])
|
||||
->afterValidation(function (Get $get) use ($installer) {
|
||||
if (!self::testConnection($get('env_redis.REDIS_HOST'), $get('env_redis.REDIS_PORT'), $get('env_redis.REDIS_USERNAME'), $get('env_redis.REDIS_PASSWORD'))) {
|
||||
throw new Halt('Redis connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_redis');
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(string $host, string $port, string $username, string $password): bool
|
||||
{
|
||||
try {
|
||||
config()->set('database.redis._panel_install_test', [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
Redis::connection('_panel_install_test')->command('ping');
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Redis connection failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
|
||||
class SessionStep
|
||||
{
|
||||
public const SESSION_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'database' => 'Database',
|
||||
'cookie' => 'Cookie',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public static function make(): Step
|
||||
{
|
||||
return Step::make('session')
|
||||
->label('Session')
|
||||
->schema([
|
||||
ToggleButtons::make('env_session.SESSION_DRIVER')
|
||||
->label('Session Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::SESSION_DRIVERS)
|
||||
->disableOptionWhen(fn ($value, Get $get) => $value === 'redis' && $get('env_cache.CACHE_STORE') !== 'redis')
|
||||
->default(config('session.driver')),
|
||||
TextInput::make('env_session.SESSION_SECURE_COOKIE')
|
||||
->hidden()
|
||||
->default(request()->isSecure()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
@@ -8,9 +8,7 @@ use App\Traits\EnvironmentWriterTrait;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Actions\Action as FormAction;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
@@ -23,14 +21,11 @@ use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Concerns\HasUnsavedDataChangesAlert;
|
||||
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
/**
|
||||
* @property Form $form
|
||||
@@ -38,6 +33,7 @@ use Illuminate\Support\HtmlString;
|
||||
class Settings extends Page implements HasForms
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use HasUnsavedDataChangesAlert;
|
||||
use InteractsWithForms;
|
||||
use InteractsWithHeaderActions;
|
||||
|
||||
@@ -71,11 +67,10 @@ class Settings extends Page implements HasForms
|
||||
->label('General')
|
||||
->icon('tabler-home')
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('captcha')
|
||||
->label('Captcha')
|
||||
Tab::make('recaptcha')
|
||||
->label('reCAPTCHA')
|
||||
->icon('tabler-shield')
|
||||
->schema($this->captchaSettings())
|
||||
->columns(3),
|
||||
->schema($this->recaptchaSettings()),
|
||||
Tab::make('mail')
|
||||
->label('Mail')
|
||||
->icon('tabler-mail')
|
||||
@@ -151,7 +146,7 @@ class Settings extends Page implements HasForms
|
||||
->separator()
|
||||
->splitKeys(['Tab', ' '])
|
||||
->placeholder('New IP or IP Range')
|
||||
->default(env('TRUSTED_PROXIES', implode(',', config('trustedproxy.proxies'))))
|
||||
->default(env('TRUSTED_PROXIES', config('trustedproxy.proxies')))
|
||||
->hintActions([
|
||||
FormAction::make('clear')
|
||||
->label('Clear')
|
||||
@@ -164,76 +159,56 @@ class Settings extends Page implements HasForms
|
||||
->label('Set to Cloudflare IPs')
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->action(function (Client $client, Set $set) {
|
||||
$ips = collect();
|
||||
try {
|
||||
$response = $client->request(
|
||||
'GET',
|
||||
'https://api.cloudflare.com/client/v4/ips',
|
||||
config('panel.guzzle')
|
||||
);
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$result = json_decode($response->getBody(), true)['result'];
|
||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||
$ips->push(...data_get($result, $value));
|
||||
}
|
||||
$ips->unique();
|
||||
}
|
||||
} catch (GuzzleException $e) {
|
||||
}
|
||||
|
||||
$set('TRUSTED_PROXIES', $ips->values()->all());
|
||||
}),
|
||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [
|
||||
'173.245.48.0/20',
|
||||
'103.21.244.0/22',
|
||||
'103.22.200.0/22',
|
||||
'103.31.4.0/22',
|
||||
'141.101.64.0/18',
|
||||
'108.162.192.0/18',
|
||||
'190.93.240.0/20',
|
||||
'188.114.96.0/20',
|
||||
'197.234.240.0/22',
|
||||
'198.41.128.0/17',
|
||||
'162.158.0.0/15',
|
||||
'104.16.0.0/13',
|
||||
'104.24.0.0/14',
|
||||
'172.64.0.0/13',
|
||||
'131.0.72.0/22',
|
||||
])),
|
||||
]),
|
||||
Select::make('FILAMENT_WIDTH')
|
||||
->label('Display Width')
|
||||
->native(false)
|
||||
->options(MaxWidth::class)
|
||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||
];
|
||||
}
|
||||
|
||||
private function captchaSettings(): array
|
||||
private function recaptchaSettings(): array
|
||||
{
|
||||
return [
|
||||
Toggle::make('TURNSTILE_ENABLED')
|
||||
->label('Enable Turnstile Captcha?')
|
||||
Toggle::make('RECAPTCHA_ENABLED')
|
||||
->label('Enable reCAPTCHA?')
|
||||
->inline(false)
|
||||
->columnSpan(1)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_ENABLED', (bool) $state))
|
||||
->default(env('TURNSTILE_ENABLED', config('turnstile.turnstile_enabled'))),
|
||||
Placeholder::make('info')
|
||||
->columnSpan(2)
|
||||
->content(new HtmlString('<p>You can generate the keys on your <u><a href="https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key" target="_blank">Cloudflare Dashboard</a></u>. A Cloudflare account is required.</p>')),
|
||||
TextInput::make('TURNSTILE_SITE_KEY')
|
||||
->label('Site Key')
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('RECAPTCHA_ENABLED', (bool) $state))
|
||||
->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))),
|
||||
TextInput::make('RECAPTCHA_DOMAIN')
|
||||
->label('Domain')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->default(env('TURNSTILE_SITE_KEY', config('turnstile.turnstile_site_key')))
|
||||
->placeholder('1x00000000000000000000AA'),
|
||||
TextInput::make('TURNSTILE_SECRET_KEY')
|
||||
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||
->default(env('RECAPTCHA_DOMAIN', config('recaptcha.domain'))),
|
||||
TextInput::make('RECAPTCHA_WEBSITE_KEY')
|
||||
->label('Website Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||
->default(env('RECAPTCHA_WEBSITE_KEY', config('recaptcha.website_key'))),
|
||||
TextInput::make('RECAPTCHA_SECRET_KEY')
|
||||
->label('Secret Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->default(env('TURNSTILE_SECRET_KEY', config('turnstile.secret_key')))
|
||||
->placeholder('1x0000000000000000000000000000000AA'),
|
||||
Toggle::make('TURNSTILE_VERIFY_DOMAIN')
|
||||
->label('Verify domain?')
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_VERIFY_DOMAIN', (bool) $state))
|
||||
->default(env('TURNSTILE_VERIFY_DOMAIN', config('turnstile.turnstile_verify_domain'))),
|
||||
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||
->default(env('RECAPTCHA_SECRET_KEY', config('recaptcha.secret_key'))),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -565,21 +540,7 @@ class Settings extends Page implements HasForms
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_EDITABLE_SERVER_DESCRIPTIONS', (bool) $state))
|
||||
->default(env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', config('panel.editable_server_descriptions'))),
|
||||
]),
|
||||
Section::make('Webhook')
|
||||
->description('Configure how often old webhook logs should be pruned.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('APP_WEBHOOK_PRUNE_DAYS')
|
||||
->label('Prune age')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(365)
|
||||
->suffix('Days')
|
||||
->default(env('APP_WEBHOOK_PRUNE_DAYS', config('panel.webhook.prune_days'))),
|
||||
]),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@@ -588,6 +549,11 @@ class Settings extends Page implements HasForms
|
||||
return 'data';
|
||||
}
|
||||
|
||||
protected function hasUnsavedDataChangesAlert(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
@@ -601,6 +567,8 @@ class Settings extends Page implements HasForms
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('queue:restart');
|
||||
|
||||
$this->rememberData();
|
||||
|
||||
$this->redirect($this->getUrl());
|
||||
|
||||
Notification::make()
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
use App\Filament\Resources\ApiKeyResource\Pages;
|
||||
use App\Models\ApiKey;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -19,7 +19,7 @@ class ApiKeyResource extends Resource
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::where('key_type', ApiKey::TYPE_APPLICATION)->count() ?: null;
|
||||
return static::getModel()::where('key_type', '2')->count() ?: null;
|
||||
}
|
||||
|
||||
public static function canEdit(Model $record): bool
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
namespace App\Filament\Resources\ApiKeyResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Filament\Resources\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
@@ -11,7 +11,6 @@ use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CreateApiKey extends CreateRecord
|
||||
{
|
||||
@@ -42,7 +41,7 @@ class CreateApiKey extends CreateRecord
|
||||
'md' => 2,
|
||||
])
|
||||
->schema(
|
||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
collect(ApiKey::RESOURCES)->map(fn ($resource) => ToggleButtons::make("r_$resource")
|
||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||
->options([
|
||||
0 => 'None',
|
||||
@@ -88,20 +87,4 @@ class CreateApiKey extends CreateRecord
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$permissions = [];
|
||||
|
||||
foreach (ApiKey::getPermissionList() as $permission) {
|
||||
if (isset($data['permissions_' . $permission])) {
|
||||
$permissions[$permission] = intval($data['permissions_' . $permission]);
|
||||
unset($data['permissions_' . $permission]);
|
||||
}
|
||||
}
|
||||
|
||||
$data['permissions'] = $permissions;
|
||||
|
||||
return parent::handleRecordCreation($data);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
namespace App\Filament\Resources\ApiKeyResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Filament\Resources\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
@@ -36,13 +35,15 @@ class ListApiKeys extends ListRecords
|
||||
->hidden()
|
||||
->searchable(),
|
||||
|
||||
DateTimeColumn::make('last_used_at')
|
||||
TextColumn::make('last_used_at')
|
||||
->label('Last Used')
|
||||
->placeholder('Not Used')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
DateTimeColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('user.username')
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
use App\Filament\Resources\DatabaseHostResource\Pages;
|
||||
use App\Models\DatabaseHost;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Resources\DatabaseHostResource;
|
||||
use App\Models\Objects\Endpoint;
|
||||
use App\Services\Databases\Hosts\HostCreationService;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
@@ -11,7 +14,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PDOException;
|
||||
|
||||
@@ -58,7 +60,7 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->numeric()
|
||||
->default(3306)
|
||||
->minValue(0)
|
||||
->maxValue(65535),
|
||||
->maxValue(Endpoint::PORT_CEIL),
|
||||
TextInput::make('max_databases')
|
||||
->label('Max databases')
|
||||
->helpertext('Blank is unlimited.')
|
||||
@@ -78,13 +80,12 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->revealable()
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
Select::make('node_ids')
|
||||
->multiple()
|
||||
Select::make('node_id')
|
||||
->searchable()
|
||||
->preload()
|
||||
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
|
||||
->label('Linked Nodes')
|
||||
->relationship('nodes', 'name'),
|
||||
->label('Linked Node')
|
||||
->relationship('node', 'name'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -103,18 +104,21 @@ class CreateDatabaseHost extends CreateRecord
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
try {
|
||||
return $this->service->handle($data);
|
||||
} catch (PDOException $exception) {
|
||||
return $this->service->handle($data);
|
||||
}
|
||||
|
||||
public function exception(Exception $e, Closure $stopPropagation): void
|
||||
{
|
||||
if ($e instanceof PDOException) {
|
||||
Notification::make()
|
||||
->title('Error connecting to database host')
|
||||
->body($exception->getMessage())
|
||||
->body($e->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-database')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
throw new Halt();
|
||||
$stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
|
||||
use App\Filament\Resources\DatabaseHostResource;
|
||||
use App\Filament\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Objects\Endpoint;
|
||||
use App\Services\Databases\Hosts\HostUpdateService;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Section;
|
||||
@@ -14,7 +17,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PDOException;
|
||||
|
||||
@@ -54,7 +56,7 @@ class EditDatabaseHost extends EditRecord
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->maxValue(65535),
|
||||
->maxValue(Endpoint::PORT_CEIL),
|
||||
TextInput::make('max_databases')
|
||||
->label('Max databases')
|
||||
->helpertext('Blank is unlimited.')
|
||||
@@ -73,13 +75,12 @@ class EditDatabaseHost extends EditRecord
|
||||
->password()
|
||||
->revealable()
|
||||
->maxLength(255),
|
||||
Select::make('nodes')
|
||||
->multiple()
|
||||
Select::make('node_id')
|
||||
->searchable()
|
||||
->preload()
|
||||
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
|
||||
->label('Linked Nodes')
|
||||
->relationship('nodes', 'name'),
|
||||
->label('Linked Node')
|
||||
->relationship('node', 'name'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -101,13 +102,9 @@ class EditDatabaseHost extends EditRecord
|
||||
|
||||
public function getRelationManagers(): array
|
||||
{
|
||||
if (DatabasesRelationManager::canViewForRecord($this->getRecord(), static::class)) {
|
||||
return [
|
||||
DatabasesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
return [
|
||||
DatabasesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
protected function handleRecordUpdate(Model $record, array $data): Model
|
||||
@@ -116,18 +113,21 @@ class EditDatabaseHost extends EditRecord
|
||||
return $record;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->hostUpdateService->handle($record, $data);
|
||||
} catch (PDOException $exception) {
|
||||
return $this->hostUpdateService->handle($record, $data);
|
||||
}
|
||||
|
||||
public function exception(Exception $e, Closure $stopPropagation): void
|
||||
{
|
||||
if ($e instanceof PDOException) {
|
||||
Notification::make()
|
||||
->title('Error connecting to database host')
|
||||
->body($exception->getMessage())
|
||||
->body($e->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-database')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
throw new Halt();
|
||||
$stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Resources\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
@@ -36,9 +36,8 @@ class ListDatabaseHosts extends ListRecords
|
||||
->counts('databases')
|
||||
->icon('tabler-database')
|
||||
->label('Databases'),
|
||||
TextColumn::make('nodes.name')
|
||||
TextColumn::make('node.name')
|
||||
->icon('tabler-server-2')
|
||||
->badge()
|
||||
->placeholder('No Nodes')
|
||||
->sortable(),
|
||||
])
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
||||
namespace App\Filament\Resources\DatabaseHostResource\RelationManagers;
|
||||
|
||||
use App\Models\Database;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
@@ -24,30 +23,21 @@ class DatabasesRelationManager extends RelationManager
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('database')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('database')->columnSpanFull(),
|
||||
TextInput::make('username'),
|
||||
TextInput::make('password')
|
||||
->password()
|
||||
->revealable()
|
||||
->hintAction(
|
||||
Action::make('rotate')
|
||||
->icon('tabler-refresh')
|
||||
->requiresConfirmation()
|
||||
->action(fn (DatabasePasswordService $service, Database $database, $set, $get) => $this->rotatePassword($service, $database, $set, $get))
|
||||
->authorize(fn (Database $database) => auth()->user()->can('update database', $database))
|
||||
)
|
||||
->formatStateUsing(fn (Database $database) => $database->password),
|
||||
TextInput::make('remote')
|
||||
->label('Connections From')
|
||||
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
|
||||
TextInput::make('max_connections')
|
||||
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
|
||||
TextInput::make('remote')->label('Connections From'),
|
||||
TextInput::make('max_connections'),
|
||||
TextInput::make('JDBC')
|
||||
->label('JDBC Connection String')
|
||||
->columnSpanFull()
|
||||
->password()
|
||||
->revealable()
|
||||
->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
|
||||
]);
|
||||
}
|
||||
@@ -57,25 +47,18 @@ class DatabasesRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('servers')
|
||||
->columns([
|
||||
TextColumn::make('database')
|
||||
->icon('tabler-database'),
|
||||
TextColumn::make('username')
|
||||
->icon('tabler-user'),
|
||||
TextColumn::make('remote')
|
||||
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
|
||||
TextColumn::make('database')->icon('tabler-database'),
|
||||
TextColumn::make('username')->icon('tabler-user'),
|
||||
TextColumn::make('remote'),
|
||||
TextColumn::make('server.name')
|
||||
->icon('tabler-brand-docker')
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
TextColumn::make('max_connections')
|
||||
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
|
||||
DateTimeColumn::make('created_at'),
|
||||
TextColumn::make('max_connections'),
|
||||
TextColumn::make('created_at')->dateTime(),
|
||||
])
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->authorize(fn (Database $database) => auth()->user()->can('delete database', $database)),
|
||||
ViewAction::make()
|
||||
->color('primary')
|
||||
->hidden(fn () => !auth()->user()->can('viewList database')),
|
||||
DeleteAction::make(),
|
||||
ViewAction::make()->color('primary'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseResource\Pages;
|
||||
use App\Filament\Resources\DatabaseResource\Pages;
|
||||
use App\Models\Database;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
|
||||
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseResource;
|
||||
use App\Filament\Resources\DatabaseResource;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
|
||||
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseResource;
|
||||
use App\Filament\Resources\DatabaseResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user