Compare commits

..

44 Commits

Author SHA1 Message Date
Lance Pioch
2bfc788e13 Allow searching for port when associating allocations (#801) 2024-12-08 16:24:00 -05:00
Lance Pioch
839ff96271 Fix power buttons (#799) 2024-12-08 16:19:15 -05:00
Lance Pioch
5d2b892eab Better IP addresses (#800)
* Unique ip addresses

* Only ipv4 addresses for now

* Switch to selects
2024-12-08 16:19:04 -05:00
MartinOscar
c953b97009 Force width (#798) 2024-12-08 20:27:16 +01:00
MartinOscar
9716b1e64d Only allow one * (#797) 2024-12-08 20:23:37 +01:00
Boy132
8358e410dc Move installer to correct namespace (#795) 2024-12-08 19:57:00 +01:00
Boy132
f6c586bf5b Add persistFiltersInSession to server list (#796) 2024-12-08 19:14:56 +01:00
Charles
feadaa2caf Add Kill button to console (#791)
* Add Kill button to console

* Add confirm, and warning
2024-12-08 12:01:44 -05:00
Charles
23246eb134 Fix #784 (#790)
* Remove +1

* Update app/Filament/Server/Pages/Settings.php

---------

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-12-08 11:47:01 -05:00
Charles
6921c8b350 Fix power actions (#789) 2024-12-08 17:34:47 +01:00
Charles
8cc91b0747 Small updates (#787)
* increase action size on console

* fix layout on create database
2024-12-08 11:19:35 -05:00
Charles
157fa45234 Fix forever expanding code editor (#782)
* Update CSS

* Update Placeholder
2024-12-07 22:44:13 -05:00
Charles
fd5016809a Enable Global Search (#783) 2024-12-07 22:43:44 -05:00
Lance Pioch
a0f5ef13d6 Show login failure message (#781)
* Show login failure message

* Update resources/scripts/components/auth/LoginContainer.tsx

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>

---------

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-12-07 22:03:56 -05:00
Lance Pioch
67f1e91236 Fix Issue 763 (#780)
* Fix path to moved component

* Fix actual issue #763
2024-12-07 20:44:41 -05:00
Charles
cc3a7a2d0d Update Server Console, Again... (#776)
* More console changes

* Update Console Page

* Edit console input

* oops

* Remove failed attempt of clearing input when server offline

* Update File Editor to match console

* a touch more style

* Show not-allowed on read-only input

* round bottom corners of command input

* Move custom css to file
2024-12-07 19:22:18 -05:00
Charles
d908fb9a9d Remove unsaved changes alert (#778)
* Remove unsaved changes alert

* Remove this also

* Remove this also...
2024-12-07 11:39:00 -05:00
Boy132
6b96c9dbda Fix admin discover (#777) 2024-12-07 17:06:57 +01:00
Boy132
e27f23b1b6 Move admin pages & resources into own namespace (#741)
* move admin pages & resources into own namespace

* fix imports for resource pages
2024-12-07 15:51:27 +01:00
Charles
4ad2997566 Update database creation (#775)
Updates database creation flow to account for new database host to many nodes change.
2024-12-06 22:46:36 -05:00
pelican-vehikl
7e7f0be7df Allow Database Hosts to have multiple Nodes (#767)
* WIP

* Update laravel and migrations

* WIP

* fix tests

* Update composer

* Fix transformer

* Fix filament pages

* WIP

* Update DatabaseHostTransformer

* fix: tests

* pint this files pls

* resolve merge better

* Update migration

* Update Migration, Again

* Update down migration

---------

Co-authored-by: Vehikl <go@vehikl.com>
2024-12-06 20:24:30 -05:00
Boy132
5b3ae995e6 Show full client api key after creation (#771)
* show notification when api key is created

* remove hardcoded redirect url
2024-12-06 16:31:58 -05:00
Charles
2a34795ab1 More console changes (#774) 2024-12-06 16:21:05 -05:00
Charles
d3da1b0a58 Update Server Console, Address Overflows (#764)
* Update Console

Updates console to be more better <3.

Light Mode still needs some love, haven't figured that out with filaments light/dark options yet as it does not use the "bright<color>" colors...

* Add overflow to... Everything?

* Oops, Add Name label back

* Actually handle Transfer Status & remove useless switch

* Use switch case

* Readonly command input if server can't receive one

* lint

* Update app/Filament/Server/Widgets/ServerConsole.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* Use filament::icon instead of raw svg

* Update resources/views/filament/components/server-console.blade.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2024-12-06 09:46:10 -05:00
Boy132
5317f97870 Display roles as badge in user list (#772) 2024-12-06 13:02:37 +01:00
Boy132
b50acfdba2 Add config value for display width to other pages (#770) 2024-12-06 09:45:06 +01:00
Charles
066bdbdf78 Server Listing tweaks. (#760)
* Server Listing tweaks.

* Use filament::icon instead of raw svg & add hover title

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-12-05 08:34:47 -05:00
MartinOscar
8103ba6338 Remove unique (#765) 2024-12-05 08:55:04 +01:00
Boy132
44b879215f Add filters to server list (#762)
* add server owner filter

* add egg filter

* replace SelectFilter with TernaryFilter
2024-12-05 08:31:34 +01:00
Charles
d2a7d7708c Add Display Width setting (#759)
Allow users to pick how "wide" the panel is.
2024-12-04 09:41:47 -05:00
Boy132
efc37dd45a Hide sidebar on server list (#761) 2024-12-04 09:50:49 +01:00
MartinOscar
09eac71f05 Delete subuser on owner change (#748)
* Delete subuser on owner change

* Move logic to Model
2024-12-03 23:55:02 +01:00
Boy132
6d42a15ec3 Handle token expiring and token expired websocket events (#755)
* handle `token expiring` and `token expired` events

* fix "getToken"

* Move logic to Widget instead of blade & add user check

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-12-03 23:54:40 +01:00
Boy132
bbfdee356b Fix timezone for file timestamps (#757) 2024-12-03 14:27:06 +01:00
Charles
994852ca00 Tweak EditNode layout for mobile (#752)
* Tweak EditNode layout for mobile

* Replace hidden with toggle
2024-12-03 05:30:04 -05:00
MartinOscar
141baeb035 Empty array if user->oauth is null (#754) 2024-12-03 08:48:47 +01:00
Boy132
bd51191da6 Add role permissions for webhooks (#742) 2024-12-02 23:53:35 +01:00
Boy132
1337767049 Small changes for new client area (#751)
* add placeholder to allocation notes

* add button to open server in admin area

* use new client area for "console" button on EditServer

* hide schedule presets on view

* use arrow functions for auth checks

* add placeholder to schedules last run

* change icon of "open in admin"

* fix parentheses
2024-12-02 22:27:35 +01:00
Boy132
918ba02075 Remove exception methods because of memory bombing (#750)
* remove exception methods

* throw Halt instead of return

* manually throw Halt to make phpstan happy
2024-12-02 22:27:25 +01:00
Charles
c6977e57c8 Fix Subuser issues. (#747)
* Better Error handling

* Remove unique, make email lowercase in request

* Remove 'kill' option, not used.

* Prevent users from editing them selves

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-12-01 20:17:27 -05:00
MartinOscar
6d1c153d09 Add config panel.editable_server_descriptions check (#734)
* Add config panel.editable_server_descriptions check

* Hide the field rather then disabling it
2024-12-01 23:27:07 +01:00
Boy132
e5433b7aab Auto update resources on server list (#737)
* auto update resources on server list

* use Arr::get helper
2024-12-01 18:12:58 +01:00
Boy132
355810c549 Combine status & uptime, add address on ServerOverview (#739) 2024-12-01 18:12:28 +01:00
Boy132
4fd1937c54 Hide global search button for now (#738) 2024-12-01 18:04:24 +01:00
108 changed files with 779 additions and 402 deletions

View File

@@ -13,4 +13,5 @@ enum RolePermissionModels: string
case Role = 'role';
case Server = 'server';
case User = 'user';
case Webhook = 'webhook';
}

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Pages;
namespace App\Filament\Admin\Pages;
use App\Filament\Resources\NodeResource\Pages\CreateNode;
use App\Filament\Resources\NodeResource\Pages\ListNodes;
use App\Filament\Admin\Resources\NodeResource\Pages\CreateNode;
use App\Filament\Admin\Resources\NodeResource\Pages\ListNodes;
use App\Models\Egg;
use App\Models\Node;
use App\Models\Server;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Pages;
namespace App\Filament\Admin\Pages;
use App\Models\Backup;
use App\Notifications\MailTested;
@@ -10,6 +10,7 @@ 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;
@@ -22,9 +23,9 @@ 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;
@@ -37,7 +38,6 @@ use Illuminate\Support\HtmlString;
class Settings extends Page implements HasForms
{
use EnvironmentWriterTrait;
use HasUnsavedDataChangesAlert;
use InteractsWithForms;
use InteractsWithHeaderActions;
@@ -185,6 +185,11 @@ class Settings extends Page implements HasForms
$set('TRUSTED_PROXIES', $ips->values()->all());
}),
]),
Select::make('FILAMENT_WIDTH')
->label('Display Width')
->native(false)
->options(MaxWidth::class)
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
];
}
@@ -583,11 +588,6 @@ class Settings extends Page implements HasForms
return 'data';
}
protected function hasUnsavedDataChangesAlert(): bool
{
return true;
}
public function save(): void
{
try {
@@ -601,8 +601,6 @@ class Settings extends Page implements HasForms
Artisan::call('config:clear');
Artisan::call('queue:restart');
$this->rememberData();
$this->redirect($this->getUrl());
Notification::make()

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\ApiKeyResource\Pages;
use App\Filament\Admin\Resources\ApiKeyResource\Pages;
use App\Models\ApiKey;
use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Model;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\ApiKeyResource\Pages;
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
use App\Filament\Resources\ApiKeyResource;
use App\Filament\Admin\Resources\ApiKeyResource;
use App\Models\ApiKey;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\ApiKeyResource\Pages;
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
use App\Filament\Resources\ApiKeyResource;
use App\Filament\Admin\Resources\ApiKeyResource;
use App\Models\ApiKey;
use App\Tables\Columns\DateTimeColumn;
use Filament\Actions;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\DatabaseHostResource\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource\Pages;
use App\Models\DatabaseHost;
use Filament\Resources\Resource;

View File

@@ -1,11 +1,9 @@
<?php
namespace App\Filament\Resources\DatabaseHostResource\Pages;
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
use App\Filament\Resources\DatabaseHostResource;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Services\Databases\Hosts\HostCreationService;
use Closure;
use Exception;
use Filament\Forms;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
@@ -13,6 +11,7 @@ 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;
@@ -79,13 +78,13 @@ class CreateDatabaseHost extends CreateRecord
->revealable()
->maxLength(255)
->required(),
Select::make('node_id')
Select::make('node_ids')
->multiple()
->searchable()
->preload()
->unique()
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
->label('Linked Node')
->relationship('node', 'name'),
->label('Linked Nodes')
->relationship('nodes', 'name'),
]),
]);
}
@@ -104,21 +103,18 @@ class CreateDatabaseHost extends CreateRecord
protected function handleRecordCreation(array $data): Model
{
return $this->service->handle($data);
}
public function exception(Exception $e, Closure $stopPropagation): void
{
if ($e instanceof PDOException) {
try {
return $this->service->handle($data);
} catch (PDOException $exception) {
Notification::make()
->title('Error connecting to database host')
->body($e->getMessage())
->body($exception->getMessage())
->color('danger')
->icon('tabler-database')
->danger()
->send();
$stopPropagation();
throw new Halt();
}
}
}

View File

@@ -1,13 +1,11 @@
<?php
namespace App\Filament\Resources\DatabaseHostResource\Pages;
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
use App\Filament\Resources\DatabaseHostResource;
use App\Filament\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
use App\Models\DatabaseHost;
use App\Services\Databases\Hosts\HostUpdateService;
use Closure;
use Exception;
use Filament\Actions;
use Filament\Forms;
use Filament\Forms\Components\Section;
@@ -16,6 +14,7 @@ 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;
@@ -74,13 +73,13 @@ class EditDatabaseHost extends EditRecord
->password()
->revealable()
->maxLength(255),
Select::make('node_id')
Select::make('nodes')
->multiple()
->searchable()
->preload()
->unique()
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
->label('Linked Node')
->relationship('node', 'name'),
->label('Linked Nodes')
->relationship('nodes', 'name'),
]),
]);
}
@@ -117,21 +116,18 @@ class EditDatabaseHost extends EditRecord
return $record;
}
return $this->hostUpdateService->handle($record, $data);
}
public function exception(Exception $e, Closure $stopPropagation): void
{
if ($e instanceof PDOException) {
try {
return $this->hostUpdateService->handle($record, $data);
} catch (PDOException $exception) {
Notification::make()
->title('Error connecting to database host')
->body($e->getMessage())
->body($exception->getMessage())
->color('danger')
->icon('tabler-database')
->danger()
->send();
$stopPropagation();
throw new Halt();
}
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\DatabaseHostResource\Pages;
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
use App\Filament\Resources\DatabaseHostResource;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Models\DatabaseHost;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@@ -36,8 +36,9 @@ class ListDatabaseHosts extends ListRecords
->counts('databases')
->icon('tabler-database')
->label('Databases'),
TextColumn::make('node.name')
TextColumn::make('nodes.name')
->icon('tabler-server-2')
->badge()
->placeholder('No Nodes')
->sortable(),
])

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\DatabaseHostResource\RelationManagers;
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
use App\Models\Database;
use App\Services\Databases\DatabasePasswordService;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\DatabaseResource\Pages;
use App\Filament\Admin\Resources\DatabaseResource\Pages;
use App\Models\Database;
use Filament\Resources\Resource;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\DatabaseResource\Pages;
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
use App\Filament\Resources\DatabaseResource;
use App\Filament\Admin\Resources\DatabaseResource;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\DatabaseResource\Pages;
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
use App\Filament\Resources\DatabaseResource;
use App\Filament\Admin\Resources\DatabaseResource;
use Filament\Actions;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\DatabaseResource\Pages;
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
use App\Filament\Resources\DatabaseResource;
use App\Filament\Admin\Resources\DatabaseResource;
use App\Tables\Columns\DateTimeColumn;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\EggResource\Pages;
use App\Filament\Admin\Resources\EggResource\Pages;
use App\Models\Egg;
use Filament\Resources\Resource;

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Resources\EggResource\Pages;
namespace App\Filament\Admin\Resources\EggResource\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Filament\Resources\EggResource;
use App\Filament\Admin\Resources\EggResource;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;

View File

@@ -1,10 +1,10 @@
<?php
namespace App\Filament\Resources\EggResource\Pages;
namespace App\Filament\Admin\Resources\EggResource\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Filament\Resources\EggResource;
use App\Filament\Resources\EggResource\RelationManagers\ServersRelationManager;
use App\Filament\Admin\Resources\EggResource;
use App\Filament\Admin\Resources\EggResource\RelationManagers\ServersRelationManager;
use App\Models\Egg;
use App\Services\Eggs\Sharing\EggExporterService;
use App\Services\Eggs\Sharing\EggImporterService;
@@ -229,6 +229,7 @@ class EditEgg extends EditRecord
->default('ash'),
MonacoEditor::make('script_install')
->label('Install Script')
->placeholderText('')
->columnSpanFull()
->fontSize('16px')
->language('shell')

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\EggResource\Pages;
namespace App\Filament\Admin\Resources\EggResource\Pages;
use App\Filament\Resources\EggResource;
use App\Filament\Admin\Resources\EggResource;
use App\Models\Egg;
use App\Services\Eggs\Sharing\EggExporterService;
use App\Services\Eggs\Sharing\EggImporterService;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\EggResource\RelationManagers;
namespace App\Filament\Admin\Resources\EggResource\RelationManagers;
use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\MountResource\Pages;
use App\Filament\Admin\Resources\MountResource\Pages;
use App\Models\Mount;
use Filament\Resources\Resource;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\MountResource\Pages;
namespace App\Filament\Admin\Resources\MountResource\Pages;
use App\Filament\Resources\MountResource;
use App\Filament\Admin\Resources\MountResource;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Section;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\MountResource\Pages;
namespace App\Filament\Admin\Resources\MountResource\Pages;
use App\Filament\Resources\MountResource;
use App\Filament\Admin\Resources\MountResource;
use Filament\Actions;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Section;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\MountResource\Pages;
namespace App\Filament\Admin\Resources\MountResource\Pages;
use App\Filament\Resources\MountResource;
use App\Filament\Admin\Resources\MountResource;
use App\Models\Mount;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,10 +1,9 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\NodeResource\Pages;
use App\Filament\Resources\NodeResource\RelationManagers\AllocationsRelationManager;
use App\Filament\Resources\NodeResource\RelationManagers\NodesRelationManager;
use App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\NodeResource\RelationManagers;
use App\Models\Node;
use Filament\Resources\Resource;
@@ -24,8 +23,8 @@ class NodeResource extends Resource
public static function getRelations(): array
{
return [
AllocationsRelationManager::class,
NodesRelationManager::class,
RelationManagers\AllocationsRelationManager::class,
RelationManagers\NodesRelationManager::class,
];
}

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\NodeResource\Pages;
namespace App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Resources\NodeResource;
use App\Filament\Admin\Resources\NodeResource;
use Filament\Forms;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\NodeResource\Pages;
namespace App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Resources\NodeResource;
use App\Filament\Admin\Resources\NodeResource;
use App\Models\Node;
use App\Services\Helpers\SoftwareVersionService;
use App\Services\Nodes\NodeAutoDeployService;
@@ -48,7 +48,12 @@ class EditNode extends EditRecord
Tab::make('')
->label('Overview')
->icon('tabler-chart-area-line-filled')
->columns(6)
->columns([
'default' => 4,
'sm' => 2,
'md' => 4,
'lg' => 4,
])
->schema([
Fieldset::make()
->label('Node Information')
@@ -67,8 +72,20 @@ class EditNode extends EditRecord
->label('Kernel')
->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? 'Unknown'),
]),
View::make('filament.components.node-cpu-chart')->columnSpan(3),
View::make('filament.components.node-memory-chart')->columnSpan(3),
View::make('filament.components.node-cpu-chart')
->columnSpan([
'default' => 4,
'sm' => 1,
'md' => 2,
'lg' => 2,
]),
View::make('filament.components.node-memory-chart')
->columnSpan([
'default' => 4,
'sm' => 1,
'md' => 2,
'lg' => 2,
]),
// TODO: Make purdy View::make('filament.components.node-storage-chart')->columnSpan(3),
]),
Tab::make('Basic Settings')

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\NodeResource\Pages;
namespace App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Resources\NodeResource;
use App\Filament\Admin\Resources\NodeResource;
use App\Models\Node;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,10 +1,11 @@
<?php
namespace App\Filament\Resources\NodeResource\RelationManagers;
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
use App\Models\Allocation;
use App\Models\Node;
use App\Services\Allocations\AssignmentService;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -47,16 +48,20 @@ class AllocationsRelationManager extends RelationManager
// 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'),
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')
@@ -69,8 +74,8 @@ class AllocationsRelationManager extends RelationManager
->headerActions([
Tables\Actions\Action::make('create new allocation')->label('Create Allocations')
->form(fn () => [
TextInput::make('allocation_ip')
->datalist($this->getOwnerRecord()->ipAddresses())
Select::make('allocation_ip')
->options(collect($this->getOwnerRecord()->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->label('IP Address')
->inlineLabel()
->ipv4()

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\NodeResource\RelationManagers;
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\NodeResource\Widgets;
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
use App\Models\Node;
use Carbon\Carbon;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\NodeResource\Widgets;
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
use App\Models\Node;
use Carbon\Carbon;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\NodeResource\Widgets;
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
use App\Models\Node;
use Filament\Widgets\ChartWidget;

View File

@@ -1,10 +1,10 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Enums\RolePermissionModels;
use App\Enums\RolePermissionPrefixes;
use App\Filament\Resources\RoleResource\Pages;
use App\Filament\Admin\Resources\RoleResource\Pages;
use App\Models\Role;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\CheckboxList;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\RoleResource\Pages;
namespace App\Filament\Admin\Resources\RoleResource\Pages;
use App\Filament\Resources\RoleResource;
use App\Filament\Admin\Resources\RoleResource;
use App\Models\Role;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\RoleResource\Pages;
namespace App\Filament\Admin\Resources\RoleResource\Pages;
use App\Filament\Resources\RoleResource;
use App\Filament\Admin\Resources\RoleResource;
use App\Models\Role;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\RoleResource\Pages;
namespace App\Filament\Admin\Resources\RoleResource\Pages;
use App\Filament\Resources\RoleResource;
use App\Filament\Admin\Resources\RoleResource;
use App\Models\Role;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\ServerResource\Pages;
use App\Filament\Admin\Resources\ServerResource\Pages;
use App\Models\Server;
use Filament\Resources\Resource;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
namespace App\Filament\Admin\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource;
use App\Filament\Admin\Resources\ServerResource;
use App\Models\Allocation;
use App\Models\Egg;
use App\Models\Node;
@@ -192,13 +192,12 @@ class CreateServer extends CreateRecord
->whereNull('server_id'),
)
->createOptionForm(fn (Get $get) => [
TextInput::make('allocation_ip')
->datalist(Node::find($get('node_id'))?->ipAddresses() ?? [])
Select::make('allocation_ip')
->options(collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->label('IP Address')
->inlineLabel()
->ipv4()
->helperText("Usually your machine's public IP unless you are port forwarding.")
// ->selectablePlaceholder(false)
->required(),
TextInput::make('allocation_alias')
->label('Alias')

View File

@@ -1,11 +1,12 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
namespace App\Filament\Admin\Resources\ServerResource\Pages;
use App\Enums\ContainerStatus;
use App\Enums\ServerState;
use App\Filament\Resources\ServerResource;
use App\Filament\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Admin\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
use App\Filament\Server\Pages\Console;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg;
@@ -706,7 +707,8 @@ class EditServer extends EditRecord
->label('Database Host')
->required()
->placeholder('Select Database Host')
->relationship('node.databaseHosts', 'name')
->relationship('node.databaseHosts', 'name',
fn (Builder $query, Server $server) => $query->whereRelation('nodes', 'nodes.id', $server->node_id))
->default(fn () => (DatabaseHost::query()->first())?->id)
->selectablePlaceholder(false),
TextInput::make('database')
@@ -870,7 +872,7 @@ class EditServer extends EditRecord
Actions\Action::make('console')
->label('Console')
->icon('tabler-terminal')
->url(fn (Server $server) => "/server/$server->uuid_short"),
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)),
$this->getSaveFormAction()->formId('form'),
];

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
namespace App\Filament\Admin\Resources\ServerResource\Pages;
use App\Filament\Server\Pages\Console;
use App\Filament\Resources\ServerResource;
use App\Filament\Admin\Resources\ServerResource;
use App\Models\Server;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,10 +1,11 @@
<?php
namespace App\Filament\Resources\ServerResource\RelationManagers;
namespace App\Filament\Admin\Resources\ServerResource\RelationManagers;
use App\Models\Allocation;
use App\Models\Server;
use App\Services\Allocations\AssignmentService;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -71,8 +72,8 @@ class AllocationsRelationManager extends RelationManager
CreateAction::make()->label('Create Allocation')
->createAnother(false)
->form(fn () => [
TextInput::make('allocation_ip')
->datalist($this->getOwnerRecord()->node->ipAddresses())
Select::make('allocation_ip')
->options(collect($this->getOwnerRecord()->node->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->label('IP Address')
->inlineLabel()
->ipv4()
@@ -151,6 +152,7 @@ class AllocationsRelationManager extends RelationManager
->associateAnother(false)
->preloadRecordSelect()
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
->recordSelectSearchColumns(['ip', 'port'])
->label('Add Allocation'),
])
->bulkActions([

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource\RelationManagers\ServersRelationManager;
use App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource\RelationManagers;
use App\Models\User;
use Filament\Resources\Resource;
@@ -23,7 +23,7 @@ class UserResource extends Resource
public static function getRelations(): array
{
return [
ServersRelationManager::class,
RelationManagers\ServersRelationManager::class,
];
}

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\UserResource\Pages;
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource;
use App\Filament\Admin\Resources\UserResource;
use App\Models\Role;
use App\Models\User;
use Filament\Actions\DeleteAction;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\UserResource\Pages;
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource;
use App\Filament\Admin\Resources\UserResource;
use App\Models\Role;
use App\Models\User;
use App\Services\Users\UserCreationService;
@@ -50,12 +50,13 @@ class ListUsers extends ListRecords
->label('2FA')
->visibleFrom('lg')
->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off')
->boolean()->sortable(),
TextColumn::make('roles_count')
->counts('roles')
->icon('tabler-users-group')
->boolean()
->sortable(),
TextColumn::make('roles.name')
->label('Roles')
->formatStateUsing(fn (User $user, $state) => $state . ($user->isRootAdmin() ? ' (Root Admin)' : '')),
->badge()
->icon('tabler-users-group')
->placeholder('No roles'),
TextColumn::make('servers_count')
->counts('servers')
->icon('tabler-server')
@@ -65,7 +66,6 @@ class ListUsers extends ListRecords
->label('Subusers')
->counts('subusers')
->icon('tabler-users'),
// ->formatStateUsing(fn (string $state, $record): string => (string) ($record->servers_count + $record->subusers_count))
])
->actions([
EditAction::make(),

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\UserResource\RelationManagers;
namespace App\Filament\Admin\Resources\UserResource\RelationManagers;
use App\Enums\ServerState;
use App\Models\Server;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources;
namespace App\Filament\Admin\Resources;
use App\Filament\Resources\WebhookResource\Pages;
use App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Models\WebhookConfiguration;
use Filament\Resources\Resource;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\WebhookResource\Pages;
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Filament\Resources\WebhookResource;
use App\Filament\Admin\Resources\WebhookResource;
use App\Models\WebhookConfiguration;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput;

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Resources\WebhookResource\Pages;
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Models\WebhookConfiguration;
use App\Filament\Resources\WebhookResource;
use App\Filament\Admin\Resources\WebhookResource;
use Filament\Actions;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Resources\WebhookResource\Pages;
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Filament\Resources\WebhookResource;
use App\Filament\Admin\Resources\WebhookResource;
use App\Models\WebhookConfiguration;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -9,7 +9,11 @@ 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
@@ -18,9 +22,12 @@ class ListServers extends ListRecords
public function table(Table $table): Table
{
$baseQuery = auth()->user()->can('viewList server') ? Server::query() : auth()->user()->accessibleServers();
return $table
->paginated(false)
->query(fn () => auth()->user()->can('viewList server') ? Server::query() : auth()->user()->accessibleServers())
->query(fn () => $baseQuery)
->poll('15s')
->columns([
Stack::make([
ServerEntryColumn::make('server_entry')
@@ -34,13 +41,31 @@ class ListServers extends ListRecords
->recordUrl(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
->emptyStateIcon('tabler-brand-docker')
->emptyStateDescription('')
->emptyStateHeading('You don\'t have access to any servers!');
->emptyStateHeading('You don\'t have access to any servers!')
->persistFiltersInSession()
->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 = collect(cache()->get("servers.{$server->id}.uptime"))->last() ?? 0;
$uptime = Arr::get($server->resources(), 'uptime', 0);
if ($uptime === 0) {
return 'Offline';
@@ -52,7 +77,7 @@ class ListServers extends ListRecords
// @phpstan-ignore-next-line
private function cpu(Server $server): string
{
$cpu = Number::format(collect(cache()->get("servers.{$server->id}.cpu_absolute"))->last() ?? 0, maxPrecision: 2, locale: auth()->user()->language) . '%';
$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 : '');
@@ -61,8 +86,8 @@ class ListServers extends ListRecords
// @phpstan-ignore-next-line
private function memory(Server $server): string
{
$latestMemoryUsed = collect(cache()->get("servers.{$server->id}.memory_bytes"))->last() ?? 0;
$totalMemory = collect(cache()->get("servers.{$server->id}.memory_limit_bytes"))->last() ?? 0;
$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'
@@ -84,7 +109,7 @@ class ListServers extends ListRecords
// @phpstan-ignore-next-line
private function disk(Server $server): string
{
$usedDisk = collect(cache()->get("servers.{$server->id}.disk_bytes"))->last() ?? 0;
$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'

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Resources\UserResource\Pages;
namespace App\Filament\Pages\Auth;
use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
use App\Facades\Activity;
@@ -14,9 +14,7 @@ use chillerlan\QRCode\Common\EccLevel;
use chillerlan\QRCode\Common\Version;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Closure;
use DateTimeZone;
use Exception;
use Filament\Forms\Components\Actions;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
@@ -31,7 +29,9 @@ use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
use Filament\Pages\Auth\EditProfile as BaseEditProfile;
use Filament\Support\Enums\MaxWidth;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
@@ -43,7 +43,7 @@ use Laravel\Socialite\Facades\Socialite;
/**
* @method User getUser()
*/
class EditProfile extends \Filament\Pages\Auth\EditProfile
class EditProfile extends BaseEditProfile
{
private ToggleTwoFactorService $toggleTwoFactorService;
@@ -54,7 +54,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
public function getMaxWidth(): MaxWidth|string
{
return MaxWidth::SevenExtraLarge;
return config('panel.filament.display-width', 'screen-2xl');
}
protected function getForms(): array
@@ -142,7 +142,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
continue;
}
$unlink = array_key_exists($name, $this->getUser()->oauth);
$unlink = array_key_exists($name, $this->getUser()->oauth ?? []);
$providers[] = Action::make("oauth_$name")
->label(($unlink ? 'Unlink ' : 'Link ') . Str::title($name))
@@ -272,16 +272,25 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
])->headerActions([
Action::make('Create')
->disabled(fn (Get $get) => $get('description') === null)
->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab']))
->successRedirectUrl(self::getUrl(['tab' => '-api-keys-tab']))
->action(function (Get $get, Action $action, User $user) {
$token = $user->createToken(
$get('description'),
$get('allowed_ips'),
);
Activity::event('user:api-key.create')
->subject($token->accessToken)
->property('identifier', $token->accessToken->identifier)
->log();
Notification::make()
->title('API Key created')
->body($token->accessToken->identifier . $token->plainTextToken)
->persistent()
->success()
->send();
$action->success();
}),
]),
@@ -352,7 +361,19 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
}
if ($token = $data['2fa-disable-code'] ?? null) {
$this->toggleTwoFactorService->handle($record, $token, false);
try {
$this->toggleTwoFactorService->handle($record, $token, false);
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
Notification::make()
->title('Invalid 2FA Code')
->body($exception->getMessage())
->color('danger')
->icon('tabler-2fa')
->danger()
->send();
throw new Halt();
}
cache()->forget("users.$record->id.2fa.state");
}
@@ -360,21 +381,6 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
return parent::handleRecordUpdate($record, $data);
}
public function exception(Exception $e, Closure $stopPropagation): void
{
if ($e instanceof TwoFactorAuthenticationTokenInvalid) {
Notification::make()
->title('Invalid 2FA Code')
->body($e->getMessage())
->color('danger')
->icon('tabler-2fa')
->danger()
->send();
$stopPropagation();
}
}
protected function getFormActions(): array
{
return [];

View File

@@ -2,6 +2,7 @@
namespace App\Filament\Server\Pages;
use App\Enums\ContainerStatus;
use App\Filament\Server\Widgets\ServerConsole;
use App\Filament\Server\Widgets\ServerCpuChart;
use App\Filament\Server\Widgets\ServerMemoryChart;
@@ -11,6 +12,8 @@ use App\Models\Server;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Pages\Page;
use Filament\Support\Enums\ActionSize;
use Livewire\Attributes\On;
class Console extends Page
{
@@ -20,6 +23,8 @@ class Console extends Page
protected static string $view = 'filament.server.pages.console';
public ContainerStatus $status = ContainerStatus::Missing;
public function getWidgetData(): array
{
return [
@@ -49,6 +54,16 @@ class Console extends Page
return 3;
}
#[On('powerChanged')]
public function powerChanged(string $state): void
{
$this->status = ContainerStatus::from($state);
$this->cachedHeaderActions = [];
$this->cacheHeaderActions();
}
protected function getHeaderActions(): array
{
/** @var Server $server */
@@ -57,16 +72,30 @@ class Console extends Page
return [
Action::make('start')
->color('primary')
->size(ActionSize::ExtraLarge)
->action(fn () => $this->dispatch('setServerState', state: 'start'))
->disabled(fn () => $server->isInConflictState()),
->disabled(fn () => $server->isInConflictState() || in_array($this->status, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting])),
Action::make('restart')
->color('gray')
->size(ActionSize::ExtraLarge)
->action(fn () => $this->dispatch('setServerState', state: 'restart'))
->disabled(fn () => $server->isInConflictState() || $server->retrieveStatus() == 'offline'),
->disabled(fn () => $server->isInConflictState() || $this->status !== ContainerStatus::Running),
Action::make('stop')
->color('danger')
->size(ActionSize::ExtraLarge)
->action(fn () => $this->dispatch('setServerState', state: 'stop'))
->disabled(fn () => $server->isInConflictState() || $server->retrieveStatus() == 'offline'),
->hidden(fn () => in_array($this->status, [ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Starting]))
->disabled(fn () => $server->isInConflictState() || in_array($this->status, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline])),
Action::make('kill')
->color('danger')
->requiresConfirmation()
->modalHeading('Do you wish to kill this server?')
->modalDescription('This can result in data corruption and/or data loss!')
->modalSubmitActionLabel('Kill Server')
->size(ActionSize::ExtraLarge)
->action(fn () => $this->dispatch('setServerState', state: 'kill'))
->hidden(fn () => $server->isInConflictState() || in_array($this->status, [ContainerStatus::Running, ContainerStatus::Restarting, ContainerStatus::Offline, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Exited, ContainerStatus::Created]))
->disabled(fn () => $server->isInConflictState() || $this->status === ContainerStatus::Offline),
];
}
}

View File

@@ -53,7 +53,7 @@ class Settings extends ServerFormPage
->schema([
TextInput::make('name')
->label('Server Name')
->disabled(!auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
->disabled(fn () => !auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
->required()
->columnSpan([
'default' => 1,
@@ -65,7 +65,8 @@ class Settings extends ServerFormPage
->afterStateUpdated(fn ($state, Server $server) => $this->updateName($state, $server)),
Textarea::make('description')
->label('Server Description')
->disabled(!auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
->hidden(!config('panel.editable_server_descriptions'))
->disabled(fn () => !auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
->columnSpan([
'default' => 1,
'sm' => 2,
@@ -112,7 +113,7 @@ class Settings extends ServerFormPage
->label('Allocation Limit')
->columnSpan(1)
->disabled()
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No additional Allocations can be created' : $server->allocations->count() . ' of ' . ($state + 1)),
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No additional Allocations can be created' : $server->allocations->count() . ' of ' . $state),
]),
]),
Section::make('Node Information')
@@ -167,7 +168,7 @@ class Settings extends ServerFormPage
->footerActions([
Action::make('reinstall')
->color('danger')
->disabled(!auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server))
->disabled(fn () => !auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server))
->label('Reinstall')
->requiresConfirmation()
->modalHeading('Are you sure you want to reinstall the server?')
@@ -239,7 +240,7 @@ class Settings extends ServerFormPage
public function updateDescription(string $description, Server $server): void
{
abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server), 403);
abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server) && config('panel.editable_server_descriptions'), 403);
$original = $server->description;

View File

@@ -62,7 +62,7 @@ class Startup extends ServerFormPage
->label('Docker Image')
->live()
->visible(fn (Server $server) => in_array($server->image, $server->egg->docker_images))
->disabled(!auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
->disabled(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
->afterStateUpdated(function ($state, Server $server) {
$original = $server->image;
$server->forceFill(['image' => $state])->saveOrFail();
@@ -97,7 +97,7 @@ class Startup extends ServerFormPage
->label('')
->relationship('viewableServerVariables')
->grid()
->disabled(!auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
->disabled(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
->reorderable(false)->addable(false)->deletable(false)
->schema(function () {
$text = TextInput::make('variable_value')

View File

@@ -36,7 +36,8 @@ class ListAllocations extends ListRecords
TextColumn::make('port'),
TextInputColumn::make('notes')
->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server))
->label('Notes'),
->label('Notes')
->placeholder('No Notes'),
IconColumn::make('primary')
->icon(fn ($state) => match ($state) {
true => 'tabler-star-filled',

View File

@@ -14,6 +14,7 @@ use Filament\Actions\CreateAction;
use Filament\Facades\Filament;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Get;
@@ -101,10 +102,16 @@ class ListDatabases extends ListRecords
->createAnother(false)
->form([
Grid::make()
->columns(3)
->columns(2)
->schema([
TextInput::make('database')
Select::make('database_host_id')
->label('Database Host')
->columnSpan(2)
->required()
->placeholder('Select Database Host')
->options(fn () => $server->node->databaseHosts->mapWithKeys(fn (DatabaseHost $databaseHost) => [$databaseHost->id => $databaseHost->name])),
TextInput::make('database')
->columnSpan(1)
->label('Database Name')
->prefix('s'. $server->id . '_')
->hintIcon('tabler-question-mark')
@@ -119,8 +126,6 @@ class ListDatabases extends ListRecords
if (empty($data['database'])) {
$data['database'] = str_random(12);
}
$data['database_host_id'] = DatabaseHost::where('node_id', $server->node_id)->first()->id;
$data['database'] = 's'. $server->id . '_' . $data['database'];
$service->create($server, $data);

View File

@@ -70,7 +70,7 @@ class EditFiles extends Page
->footerActions([
Action::make('save')
->label('Save Changes')
->authorize(auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->icon('tabler-device-floppy')
->keyBindings('mod+s')
->action(function () use ($server) {
@@ -102,6 +102,7 @@ class EditFiles extends Page
->schema([
MonacoEditor::make('editor')
->label('')
->placeholderText('')
->formatStateUsing(function () use ($server) {
// @phpstan-ignore-next-line
return app(DaemonFileRepository::class)

View File

@@ -545,6 +545,7 @@ class ListFiles extends ListRecords
->form([
TextInput::make('searchTerm')
->placeholder('Enter a search term, e.g. *.txt')
->regex('/^[^*]*\*?[^*]*$/')
->minLength(3),
])
->action(fn ($data) => redirect(SearchFiles::getUrl([

View File

@@ -111,6 +111,7 @@ class ScheduleResource extends Resource
->default('*')
->required(),
Section::make('Presets')
->hiddenOn('view')
->schema([
Actions::make([
Action::make('hourly')

View File

@@ -33,6 +33,7 @@ class ListSchedules extends ListRecords
->sortable(),
DateTimeColumn::make('last_run_at')
->label('Last run')
->placeholder('Never')
->since()
->sortable(),
DateTimeColumn::make('next_run_at')

View File

@@ -103,7 +103,7 @@ class UserResource extends Resource
}),
EditAction::make()
->label('Edit User')
->hidden(fn (User $user) => auth()->user()->id === $user->id)
->authorize(fn () => auth()->user()->can(Permission::ACTION_USER_UPDATE, $server))
->modalHeading(fn (User $user) => 'Editing ' . $user->email)
->action(function (array $data, SubuserUpdateService $subuserUpdateService, User $user) use ($server) {

View File

@@ -6,6 +6,7 @@ use App\Filament\Server\Resources\UserResource;
use App\Models\Permission;
use App\Models\Server;
use App\Services\Subusers\SubuserCreationService;
use Exception;
use Filament\Actions;
use Filament\Facades\Filament;
use Filament\Forms\Components\Actions as assignAll;
@@ -53,8 +54,7 @@ class ListUsers extends ListRecords
'md' => 4,
'lg' => 5,
])
->required()
->unique(),
->required(),
assignAll::make([
Action::make('assignAll')
->label('Assign All')
@@ -65,7 +65,6 @@ class ListUsers extends ListRecords
'start',
'stop',
'restart',
'kill',
],
'user' => [
'read',
@@ -365,18 +364,28 @@ class ListUsers extends ListRecords
->modalHeading('Invite User')
->modalSubmitActionLabel('Invite')
->action(function (array $data, SubuserCreationService $service) use ($server) {
$email = $data['email'];
$email = strtolower($data['email']);
if (in_array('console', $data['control'])) {
$data['websocket'][0] = 'connect';
}
$permissions = collect($data)->forget('email')->map(fn ($permissions, $key) => collect($permissions)->map(fn ($permission) => "$key.$permission"))->flatten()->all();
$service->handle($server, $email, $permissions);
Notification::make()
->title('User Invited!')
->success()
->send();
$permissions = collect($data)->forget('email')->map(fn ($permissions, $key) => collect($permissions)->map(fn ($permission) => "$key.$permission"))->flatten()->all();
try {
$service->handle($server, $email, $permissions);
Notification::make()
->title('User Invited!')
->success()
->send();
} catch (Exception $exception) {
Notification::make()
->title('Failed')
->body($exception->getMessage())
->danger()
->send();
}
return redirect(self::getUrl(tenant: $server));
}),

View File

@@ -2,8 +2,12 @@
namespace App\Filament\Server\Widgets;
use App\Exceptions\Http\HttpForbiddenException;
use App\Models\Permission;
use App\Models\Server;
use App\Models\User;
use App\Services\Nodes\NodeJWTService;
use App\Services\Servers\GetUserPermissionsService;
use Filament\Widgets\Widget;
use Illuminate\Support\Arr;
use Livewire\Attributes\On;
@@ -26,6 +30,38 @@ class ServerConsole extends Widget
public string $input = '';
protected function getToken(): string
{
if (!$this->user || !$this->server || $this->user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $this->server)) {
throw new HttpForbiddenException('You do not have permission to connect to this server\'s websocket.');
}
// @phpstan-ignore-next-line
$permissions = app(GetUserPermissionsService::class)->handle($this->server, $this->user);
// @phpstan-ignore-next-line
return app(NodeJWTService::class)
->setExpiresAt(now()->addMinutes(10)->toImmutable())
->setUser($this->user)
->setClaims([
'server_uuid' => $this->server->uuid,
'permissions' => $permissions,
])
->handle($this->server->node, $this->user->id . $this->server->uuid)->toString();
}
protected function getSocket(): string
{
$socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $this->server->node->getConnectionAddress());
$socket .= sprintf('/api/servers/%s/ws', $this->server->uuid);
return $socket;
}
protected function canSendCommand(): bool
{
return !$this->server->isInConflictState() && $this->server->retrieveStatus() === 'running';
}
public function up(): void
{
$this->historyIndex = min($this->historyIndex + 1, count($this->history) - 1);
@@ -42,7 +78,7 @@ class ServerConsole extends Widget
public function enter(): void
{
if (!empty($this->input)) {
if (!empty($this->input) && $this->canSendCommand()) {
$this->dispatch('sendServerCommand', command: $this->input);
$this->history = Arr::prepend($this->history, $this->input);

View File

@@ -12,7 +12,7 @@ class ServerCpuChart extends ChartWidget
{
protected static ?string $pollingInterval = '1s';
protected static ?string $maxHeight = '300px';
protected static ?string $maxHeight = '200px';
public ?Server $server = null;

View File

@@ -12,7 +12,7 @@ class ServerMemoryChart extends ChartWidget
{
protected static ?string $pollingInterval = '1s';
protected static ?string $maxHeight = '300px';
protected static ?string $maxHeight = '200px';
public ?Server $server = null;

View File

@@ -18,20 +18,29 @@ class ServerOverview extends StatsOverviewWidget
{
return [
Stat::make('Name', $this->server->name)
->description($this->server->description),
Stat::make('Status', Str::title($this->server->condition)),
Stat::make('Uptime', $this->uptime()),
->description($this->server->description)
->extraAttributes([
'class' => 'overflow-x-auto',
]),
Stat::make('Status', $this->status()),
Stat::make('Address', $this->server->allocation->address)
->extraAttributes([
'class' => 'overflow-x-auto',
]),
];
}
private function uptime(): string
private function status(): string
{
$status = Str::title($this->server->condition);
$uptime = collect(cache()->get("servers.{$this->server->id}.uptime"))->last() ?? 0;
if ($uptime === 0) {
return 'Offline';
return $status;
}
return now()->subMillis($uptime)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 2);
$uptime = now()->subMillis($uptime)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 2);
return "$status ($uptime)";
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Auth;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Livewire\Installer\PanelInstaller;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Str;

View File

@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Auth;
use App\Filament\Resources\UserResource\Pages\EditProfile;
use App\Filament\Pages\Auth\EditProfile;
use Filament\Notifications\Notification;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\RedirectResponse;

View File

@@ -1,14 +1,14 @@
<?php
namespace App\Filament\Pages\Installer;
namespace App\Livewire\Installer;
use App\Filament\Pages\Dashboard;
use App\Filament\Pages\Installer\Steps\CacheStep;
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\RequirementsStep;
use App\Filament\Pages\Installer\Steps\SessionStep;
use App\Filament\Admin\Pages\Dashboard;
use App\Livewire\Installer\Steps\CacheStep;
use App\Livewire\Installer\Steps\DatabaseStep;
use App\Livewire\Installer\Steps\EnvironmentStep;
use App\Livewire\Installer\Steps\QueueStep;
use App\Livewire\Installer\Steps\RequirementsStep;
use App\Livewire\Installer\Steps\SessionStep;
use App\Models\User;
use App\Services\Users\UserCreationService;
use App\Traits\CheckMigrationsTrait;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
namespace App\Livewire\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Livewire\Installer\PanelInstaller;
use Exception;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
namespace App\Livewire\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Livewire\Installer\PanelInstaller;
use Exception;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
namespace App\Livewire\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Livewire\Installer\PanelInstaller;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
namespace App\Livewire\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Livewire\Installer\PanelInstaller;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
namespace App\Livewire\Installer\Steps;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
namespace App\Livewire\Installer\Steps;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;

View File

@@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property \Carbon\Carbon|null $updated_at
* @property string $alias
* @property bool $has_alias
* @property string $address
* @property \App\Models\Server|null $server
* @property \App\Models\Node $node
*

View File

@@ -2,8 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
@@ -39,7 +39,7 @@ class DatabaseHost extends Model
* Fields that are mass assignable.
*/
protected $fillable = [
'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id',
'name', 'host', 'port', 'username', 'password', 'max_databases',
];
/**
@@ -51,7 +51,8 @@ class DatabaseHost extends Model
'port' => 'required|numeric|between:1,65535',
'username' => 'required|string|max:32',
'password' => 'nullable|string',
'node_id' => 'sometimes|nullable|integer|exists:nodes,id',
'node_ids' => 'nullable|array',
'node_ids.*' => 'required|integer,exists:nodes,id',
];
protected function casts(): array
@@ -59,7 +60,6 @@ class DatabaseHost extends Model
return [
'id' => 'integer',
'max_databases' => 'integer',
'node_id' => 'integer',
'password' => 'encrypted',
'created_at' => 'immutable_datetime',
'updated_at' => 'immutable_datetime',
@@ -71,12 +71,9 @@ class DatabaseHost extends Model
return 'id';
}
/**
* Gets the node associated with a database host.
*/
public function node(): BelongsTo
public function nodes(): BelongsToMany
{
return $this->belongsTo(Node::class);
return $this->belongsToMany(Node::class);
}
/**

View File

@@ -146,8 +146,8 @@ class File extends Model
return array_map(function ($file) {
return [
'name' => $file['name'],
'created_at' => Carbon::parse($file['created']),
'modified_at' => Carbon::parse($file['modified']),
'created_at' => Carbon::parse($file['created'])->timezone('UTC'),
'modified_at' => Carbon::parse($file['modified'])->timezone('UTC'),
'mode' => $file['mode'],
'mode_bits' => (int) $file['mode_bits'],
'size' => (int) $file['size'],

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Exceptions\Service\HasActiveServersException;
use App\Repositories\Daemon\DaemonConfigurationRepository;
use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Notifications\Notifiable;
@@ -243,9 +244,9 @@ class Node extends Model
return $this->hasMany(Allocation::class);
}
public function databaseHosts(): HasMany
public function databaseHosts(): BelongsToMany
{
return $this->hasMany(DatabaseHost::class);
return $this->belongsToMany(DatabaseHost::class);
}
/**
@@ -383,7 +384,10 @@ class Node extends Model
// pass
}
return $ips->all();
// Only IPV4
$ips = $ips->filter(fn (string $ip) => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false);
return $ips->unique()->all();
});
}
}

View File

@@ -5,11 +5,13 @@ namespace App\Models;
use App\Enums\ContainerStatus;
use App\Enums\ServerState;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Repositories\Daemon\DaemonServerRepository;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;
use Illuminate\Database\Eloquent\Relations\HasOne;
@@ -17,6 +19,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use App\Exceptions\Http\Server\ServerStateConflictException;
use App\Services\Subusers\SubuserDeletionService;
/**
* \App\Models\Server.
@@ -201,6 +204,17 @@ class Server extends Model
];
}
protected static function booted(): void
{
static::saved(function (self $server) {
$subuser = $server->subusers()->where('user_id', $server->owner_id)->first();
if ($subuser) {
// @phpstan-ignore-next-line
app(SubuserDeletionService::class)->handle($subuser, $server);
}
});
}
/**
* Returns the format for server allocations when communicating with the Daemon.
*/
@@ -431,6 +445,14 @@ class Server extends Model
return cache()->get("servers.$this->uuid.container.status") ?? 'missing';
}
public function resources(): array
{
return cache()->remember("resources:$this->uuid", now()->addSeconds(15), function () {
// @phpstan-ignore-next-line
return Arr::get(app(DaemonServerRepository::class)->setServer($this)->getDetails(), 'utilization', []);
});
}
public function condition(): Attribute
{
return Attribute::make(

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Policies;
class WebhookConfigurationPolicy
{
use DefaultPolicies;
protected string $modelName = 'webhook';
}

View File

@@ -3,7 +3,7 @@
namespace App\Providers\Filament;
use App\Filament\Pages\Auth\Login;
use App\Filament\Resources\UserResource\Pages\EditProfile;
use App\Filament\Pages\Auth\EditProfile;
use App\Http\Middleware\LanguageMiddleware;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
@@ -11,9 +11,7 @@ use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Navigation\MenuItem;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Enums\MaxWidth;
use Filament\Support\Facades\FilamentAsset;
use Filament\Widgets;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
@@ -53,16 +51,10 @@ class AdminPanelProvider extends PanelProvider
->icon('tabler-arrow-back')
->sort(24),
])
->maxContentWidth(MaxWidth::ScreenTwoExtraLarge)
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
->spa()
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([
Widgets\AccountWidget::class,
Widgets\FilamentInfoWidget::class,
])
->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources')
->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages')
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,

View File

@@ -3,7 +3,7 @@
namespace App\Providers\Filament;
use App\Filament\Pages\Auth\Login;
use App\Filament\Resources\UserResource\Pages\EditProfile;
use App\Filament\Pages\Auth\EditProfile;
use Filament\Facades\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
@@ -11,7 +11,6 @@ use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Navigation\MenuItem;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
@@ -34,7 +33,8 @@ class AppPanelProvider extends PanelProvider
->brandLogoHeight('2rem')
->favicon(config('app.favicon', '/pelican.ico'))
->topNavigation(config('panel.filament.top-navigation', true))
->maxContentWidth(MaxWidth::ScreenTwoExtraLarge)
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
->navigation(false)
->profile(EditProfile::class, false)
->login(Login::class)
->userMenuItems([

View File

@@ -4,7 +4,8 @@ namespace App\Providers\Filament;
use App\Filament\App\Resources\ServerResource\Pages\ListServers;
use App\Filament\Pages\Auth\Login;
use App\Filament\Resources\UserResource\Pages\EditProfile;
use App\Filament\Admin\Resources\ServerResource\Pages\EditServer;
use App\Filament\Pages\Auth\EditProfile;
use App\Http\Middleware\Activity\ServerSubject;
use App\Models\Server;
use Filament\Facades\Filament;
@@ -12,9 +13,9 @@ use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Navigation\MenuItem;
use Filament\Navigation\NavigationItem;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
@@ -38,7 +39,7 @@ class ServerPanelProvider extends PanelProvider
->brandLogoHeight('2rem')
->favicon(config('app.favicon', '/pelican.ico'))
->topNavigation(config('panel.filament.top-navigation', true))
->maxContentWidth(MaxWidth::ScreenTwoExtraLarge)
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
->login(Login::class)
->userMenuItems([
'profile' => MenuItem::make()->label('Profile')->url(fn () => EditProfile::getUrl(panel: 'app')),
@@ -54,6 +55,13 @@ class ServerPanelProvider extends PanelProvider
->sort(5)
->visible(fn (): bool => auth()->user()->canAccessPanel(Filament::getPanel('admin'))),
])
->navigationItems([
NavigationItem::make('Open in Admin')
->url(fn () => EditServer::getUrl(['record' => Filament::getTenant()], panel: 'admin', tenant: null), true)
->visible(fn () => auth()->user()->can('view server', Filament::getTenant()))
->icon('tabler-arrow-back')
->sort(99),
])
->discoverResources(in: app_path('Filament/Server/Resources'), for: 'App\\Filament\\Server\\Resources')
->discoverPages(in: app_path('Filament/Server/Pages'), for: 'App\\Filament\\Server\\Pages')
->discoverWidgets(in: app_path('Filament/Server/Widgets'), for: 'App\\Filament\\Server\\Widgets')

View File

@@ -25,15 +25,15 @@ class DeployServerDatabaseService
Assert::notEmpty($data['database'] ?? null);
Assert::notEmpty($data['remote'] ?? null);
$hosts = DatabaseHost::query()->get()->toBase();
$hosts = DatabaseHost::query()->get();
if ($hosts->isEmpty()) {
throw new NoSuitableDatabaseHostException();
} else {
$nodeHosts = $hosts->where('node_id', $server->node_id)->toBase();
}
if ($nodeHosts->isEmpty() && !config('panel.client_features.databases.allow_random')) {
throw new NoSuitableDatabaseHostException();
}
$nodeHosts = $server->node->databaseHosts()->get();
// TODO: @areyouscared remove allow random feature for database hosts
if ($nodeHosts->isEmpty() && !config('panel.client_features.databases.allow_random')) {
throw new NoSuitableDatabaseHostException();
}
return $this->managementService->create($server, [

View File

@@ -33,9 +33,10 @@ class HostCreationService
'port' => array_get($data, 'port'),
'username' => array_get($data, 'username'),
'max_databases' => array_get($data, 'max_databases'),
'node_id' => array_get($data, 'node_id'),
]);
$host->nodes()->sync(array_get($data, 'node_ids', []));
// Confirm access using the provided credentials before saving data.
$this->dynamic->set('dynamic', $host);
$this->databaseManager->connection('dynamic')->getPdo();

View File

@@ -5,7 +5,6 @@ namespace App\Transformers\Api\Application;
use App\Models\Node;
use App\Models\Database;
use App\Models\DatabaseHost;
use League\Fractal\Resource\Item;
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource;
@@ -13,7 +12,7 @@ class DatabaseHostTransformer extends BaseTransformer
{
protected array $availableIncludes = [
'databases',
'node',
'nodes',
];
/**
@@ -35,7 +34,6 @@ class DatabaseHostTransformer extends BaseTransformer
'host' => $model->host,
'port' => $model->port,
'username' => $model->username,
'node' => $model->node_id,
'created_at' => $model->created_at->toAtomString(),
'updated_at' => $model->updated_at->toAtomString(),
];
@@ -56,16 +54,16 @@ class DatabaseHostTransformer extends BaseTransformer
}
/**
* Include the node associated with this host.
* Include the nodes associated with this host.
*/
public function includeNode(DatabaseHost $model): Item|NullResource
public function includeNodes(DatabaseHost $model): Collection|NullResource
{
if (!$this->authorize(Node::RESOURCE_NAME)) {
return $this->null();
}
$model->loadMissing('node');
$model->loadMissing('nodes');
return $this->item($model->getRelation('node'), $this->makeTransformer(NodeTransformer::class), Node::RESOURCE_NAME);
return $this->collection($model->getRelation('nodes'), $this->makeTransformer(NodeTransformer::class), Node::RESOURCE_NAME);
}
}

View File

@@ -5,7 +5,7 @@ return [
'name' => env('APP_NAME', 'Pelican'),
'favicon' => env('APP_FAVICON', '/pelican.ico'),
'version' => '1.0.0-beta14',
'version' => 'canary',
'timezone' => 'UTC',

View File

@@ -18,7 +18,7 @@ return [
'inherit' => true,
'rules' => [
[
'background' => '0C1021',
'background' => '161F27',
'token' => '',
],
[
@@ -139,8 +139,8 @@ return [
],
'colors' => [
'editor.foreground' => '#F8F8F8',
'editor.background' => '#0C1021',
'editor.selectionBackground' => '#253B76',
'editor.background' => '#101519',
'editor.selectionBackground' => '#5a5f63',
'editor.lineHighlightBackground' => '#FFFFFF0F',
'editorCursor.foreground' => '#FFFFFFA6',
'editorWhitespace.foreground' => '#FFFFFF40',

View File

@@ -162,6 +162,7 @@ return [
'filament' => [
'top-navigation' => env('FILAMENT_TOP_NAVIGATION', false),
'display-width' => env('FILAMENT_WIDTH', 'screen-2xl'),
],
'use_binary_prefix' => env('PANEL_USE_BINARY_PREFIX', true),

View File

@@ -34,6 +34,9 @@ return new class extends Migration
if (Schema::getConnection()->getDriverName() !== 'sqlite') {
$table->dropIndex('permissions_server_id_foreign');
$table->dropIndex('permissions_user_id_foreign');
} else {
$table->dropForeign(['server_id']);
$table->dropForeign(['user_id']);
}
$table->dropColumn('server_id');

View File

@@ -13,7 +13,6 @@ return new class extends Migration
{
Schema::table('servers', function (Blueprint $table) {
$table->dropForeign(['pack_id']);
$table->dropColumn('pack_id');
});
}

View File

@@ -33,6 +33,7 @@ return new class extends Migration
} else {
$table->dropForeign(['nest_id']);
}
$table->dropColumn('nest_id');
});

View File

@@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('database_host_node', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('node_id');
$table->foreign('node_id')->references('id')->on('nodes');
$table->unsignedInteger('database_host_id');
$table->foreign('database_host_id')->references('id')->on('database_hosts');
$table->timestamps();
});
$databaseNodes = DB::table('database_hosts')->whereNotNull('node_id')->get();
$newJoinEntries = $databaseNodes->map(fn ($record) => [
'node_id' => $record->node_id,
'database_host_id' => $record->id,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('database_host_node')->insert($newJoinEntries->toArray());
Schema::table('database_hosts', function (Blueprint $table) {
$table->dropForeign(['node_id']);
$table->dropColumn('node_id');
});
}
public function down(): void
{
Schema::table('database_hosts', function (Blueprint $table) {
$table->unsignedInteger('node_id')->nullable();
$table->foreign('node_id')->references('id')->on('nodes');
});
foreach (DB::table('database_host_node')->get() as $record) {
DB::table('database_hosts')
->where('id', $record->database_host_id)
->update(['node_id' => $record->node_id]);
}
Schema::drop('database_host_node');
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('subusers')
->whereIn('user_id', function ($query) {
$query->select('id')
->from('servers')
->whereColumn('owner_id', 'subusers.server_id');
})
->delete();
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Not needed
}
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,46 @@
#terminal {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.xterm .xterm-rows > div {
padding-left: 10px;
padding-top: 2px;
padding-right: 10px;
}
input:read-only {
cursor: not-allowed;
}
::-webkit-scrollbar {
background: none;
width: 14px;
height: 14px;
}
::-webkit-scrollbar-thumb {
border: solid 0 rgb(0 0 0 / 0%);
border-right-width: 4px;
border-left-width: 4px;
-webkit-border-radius: 9px 4px;
-webkit-box-shadow: inset 0 0 0 1px hsl(211, 10%, 53%), inset 0 0 0 4px hsl(209deg 18% 30%);
}
::-webkit-scrollbar-track-piece {
margin: 4px 0;
}
::-webkit-scrollbar-thumb:horizontal {
border-right-width: 0;
border-left-width: 0;
border-top-width: 4px;
border-bottom-width: 4px;
-webkit-border-radius: 4px 9px;
}
::-webkit-scrollbar-corner {
background: transparent;
}

View File

@@ -51,10 +51,13 @@ const LoginContainer = ({ history }: RouteComponentProps) => {
history.replace('/auth/login/checkpoint', { token: response.confirmationToken });
})
.catch((error) => {
console.error(error);
setSubmitting(false);
addError({ message: 'Invalid login, please try again.' });
setToken('');
turnstile.reset();
if (turnstile) {
turnstile.reset();
}
setSubmitting(false);
clearAndAddHttpError({ error });

View File

@@ -1,3 +1,3 @@
<x-filament::widget>
@livewire(\App\Filament\Resources\NodeResource\Widgets\NodeCpuChart::class, ['record'=> $getRecord()])
@livewire(\App\Filament\Admin\Resources\NodeResource\Widgets\NodeCpuChart::class, ['record'=> $getRecord()])
</x-filament::widget>

Some files were not shown because too many files have changed in this diff Show More