mirror of
https://github.com/vitodeploy/vito.git
synced 2025-05-12 19:13:34 +00:00
2.x - firewall/metrics/services/cronjobs
This commit is contained in:
parent
2e9620409b
commit
906ddc38de
app
Actions
CronJob
FirewallRule
Monitoring
Service
SshKey
StorageProvider
Helpers
Models
Policies
Providers
SSH/Cron
Web
Pages
Servers
Create.php
CronJobs
Databases/Widgets
Firewall
Index.phpLogs/Widgets
Metrics
PHP
SSHKeys
Services
Sites
Widgets
Settings/Projects/Widgets
Traits
config
public
css/filament
js/filament
resources
css/filament/app
views/web
@ -6,15 +6,11 @@
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
use App\ValidationRules\CronRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CreateCronJob
|
||||
{
|
||||
public function create(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$cronJob = new CronJob([
|
||||
'server_id' => $server->id,
|
||||
'user' => $input['user'],
|
||||
@ -29,12 +25,9 @@ public function create(Server $server, array $input): void
|
||||
$cronJob->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
public static function rules(array $input): array
|
||||
{
|
||||
Validator::make($input, [
|
||||
$rules = [
|
||||
'command' => [
|
||||
'required',
|
||||
],
|
||||
@ -46,15 +39,15 @@ private function validate(array $input): void
|
||||
'required',
|
||||
new CronRule(acceptCustom: true),
|
||||
],
|
||||
])->validate();
|
||||
];
|
||||
|
||||
if ($input['frequency'] == 'custom') {
|
||||
Validator::make($input, [
|
||||
'custom' => [
|
||||
'required',
|
||||
new CronRule,
|
||||
],
|
||||
])->validate();
|
||||
if (isset($input['frequency']) && $input['frequency'] == 'custom') {
|
||||
$rules['custom'] = [
|
||||
'required',
|
||||
new CronRule,
|
||||
];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,13 @@
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\SSH\Services\Firewall\Firewall;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateRule
|
||||
{
|
||||
public function create(Server $server, array $input): FirewallRule
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
|
||||
$rule = new FirewallRule([
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
@ -23,15 +21,15 @@ public function create(Server $server, array $input): FirewallRule
|
||||
'mask' => $input['mask'] ?? null,
|
||||
]);
|
||||
|
||||
$server->firewall()
|
||||
->handler()
|
||||
->addRule(
|
||||
$rule->type,
|
||||
$rule->getRealProtocol(),
|
||||
$rule->port,
|
||||
$rule->source,
|
||||
$rule->mask
|
||||
);
|
||||
/** @var Firewall $firewallHandler */
|
||||
$firewallHandler = $server->firewall()->handler();
|
||||
$firewallHandler->addRule(
|
||||
$rule->type,
|
||||
$rule->getRealProtocol(),
|
||||
$rule->port,
|
||||
$rule->source,
|
||||
$rule->mask
|
||||
);
|
||||
|
||||
$rule->status = FirewallRuleStatus::READY;
|
||||
$rule->save();
|
||||
@ -39,19 +37,16 @@ public function create(Server $server, array $input): FirewallRule
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(Server $server, array $input): void
|
||||
public static function rules(): array
|
||||
{
|
||||
Validator::make($input, [
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
'in:allow,deny',
|
||||
],
|
||||
'protocol' => [
|
||||
'required',
|
||||
'in:'.implode(',', array_keys(config('core.firewall_protocols_port'))),
|
||||
Rule::in(array_keys(config('core.firewall_protocols_port'))),
|
||||
],
|
||||
'port' => [
|
||||
'required',
|
||||
@ -64,8 +59,9 @@ private function validate(Server $server, array $input): void
|
||||
'ip',
|
||||
],
|
||||
'mask' => [
|
||||
'required',
|
||||
'numeric',
|
||||
],
|
||||
])->validate();
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,13 @@
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Query\Expression;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class GetMetrics
|
||||
{
|
||||
public function filter(Server $server, array $input): array
|
||||
public function filter(Server $server, array $input): Collection
|
||||
{
|
||||
if (isset($input['from']) && isset($input['to']) && $input['from'] === $input['to']) {
|
||||
$input['from'] = Carbon::parse($input['from'])->format('Y-m-d').' 00:00:00';
|
||||
@ -24,8 +24,6 @@ public function filter(Server $server, array $input): array
|
||||
|
||||
$input = array_merge($defaultInput, $input);
|
||||
|
||||
$this->validate($input);
|
||||
|
||||
return $this->metrics(
|
||||
server: $server,
|
||||
fromDate: $this->getFromDate($input),
|
||||
@ -39,8 +37,8 @@ private function metrics(
|
||||
Carbon $fromDate,
|
||||
Carbon $toDate,
|
||||
?Expression $interval = null
|
||||
): array {
|
||||
$metrics = DB::table('metrics')
|
||||
): Collection {
|
||||
return DB::table('metrics')
|
||||
->where('server_id', $server->id)
|
||||
->whereBetween('created_at', [$fromDate->format('Y-m-d H:i:s'), $toDate->format('Y-m-d H:i:s')])
|
||||
->select(
|
||||
@ -64,10 +62,6 @@ private function metrics(
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
return [
|
||||
'metrics' => $metrics,
|
||||
];
|
||||
}
|
||||
|
||||
private function getFromDate(array $input): Carbon
|
||||
@ -110,14 +104,12 @@ private function getInterval(array $input): Expression
|
||||
return DB::raw("strftime('%Y-%m-%d %H:00:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
if ($periodInHours > 24) {
|
||||
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
|
||||
}
|
||||
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
public static function rules(array $input): array
|
||||
{
|
||||
Validator::make($input, [
|
||||
$rules = [
|
||||
'period' => [
|
||||
'required',
|
||||
Rule::in([
|
||||
@ -130,21 +122,13 @@ private function validate(array $input): void
|
||||
'custom',
|
||||
]),
|
||||
],
|
||||
])->validate();
|
||||
];
|
||||
|
||||
if ($input['period'] === 'custom') {
|
||||
Validator::make($input, [
|
||||
'from' => [
|
||||
'required',
|
||||
'date',
|
||||
'before:to',
|
||||
],
|
||||
'to' => [
|
||||
'required',
|
||||
'date',
|
||||
'after:from',
|
||||
],
|
||||
])->validate();
|
||||
if (isset($input['period']) && $input['period'] === 'custom') {
|
||||
$rules['from'] = ['required', 'date', 'before:to'];
|
||||
$rules['to'] = ['required', 'date', 'after:from'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,10 @@ class Install
|
||||
{
|
||||
public function install(Server $server, array $input): Service
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
|
||||
$service = new Service([
|
||||
'server_id' => $server->id,
|
||||
'name' => $input['name'],
|
||||
'type' => $input['type'],
|
||||
'type' => config('core.service_types')[$input['name']],
|
||||
'version' => $input['version'],
|
||||
'status' => ServiceStatus::INSTALLING,
|
||||
]);
|
||||
@ -40,18 +38,21 @@ public function install(Server $server, array $input): Service
|
||||
return $service;
|
||||
}
|
||||
|
||||
private function validate(Server $server, array $input): void
|
||||
public static function rules(array $input): array
|
||||
{
|
||||
Validator::make($input, [
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(config('core.service_types')),
|
||||
],
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
Rule::in(array_keys(config('core.service_types'))),
|
||||
],
|
||||
'version' => 'required',
|
||||
])->validate();
|
||||
'version' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
if (isset($input['name'])) {
|
||||
$rules['version'][] = Rule::in(config("core.service_versions.{$input['name']}"));
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
use App\Models\SshKey;
|
||||
use App\Models\User;
|
||||
use App\ValidationRules\SshKeyRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CreateSshKey
|
||||
@ -15,8 +14,6 @@ class CreateSshKey
|
||||
*/
|
||||
public function create(User $user, array $input): SshKey
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$key = new SshKey([
|
||||
'user_id' => $user->id,
|
||||
'name' => $input['name'],
|
||||
@ -30,14 +27,14 @@ public function create(User $user, array $input): SshKey
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
public static function rules(): array
|
||||
{
|
||||
Validator::make($input, [
|
||||
return [
|
||||
'name' => 'required',
|
||||
'public_key' => [
|
||||
'required',
|
||||
new SshKeyRule,
|
||||
],
|
||||
])->validate();
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -6,17 +6,14 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\SshKey;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class DeployKeyToServer
|
||||
{
|
||||
public function deploy(User $user, Server $server, array $input): void
|
||||
public function deploy(Server $server, array $input): void
|
||||
{
|
||||
$this->validate($user, $input);
|
||||
|
||||
/** @var SshKey $sshKey */
|
||||
$sshKey = SshKey::findOrFail($input['key_id']);
|
||||
$sshKey = SshKey::query()->findOrFail($input['key_id']);
|
||||
$server->sshKeys()->attach($sshKey, [
|
||||
'status' => SshKeyStatus::ADDING,
|
||||
]);
|
||||
@ -26,13 +23,14 @@ public function deploy(User $user, Server $server, array $input): void
|
||||
]);
|
||||
}
|
||||
|
||||
private function validate(User $user, array $input): void
|
||||
public static function rules(User $user, Server $server): array
|
||||
{
|
||||
Validator::make($input, [
|
||||
return [
|
||||
'key_id' => [
|
||||
'required',
|
||||
Rule::exists('ssh_keys', 'id')->where('user_id', $user->id),
|
||||
Rule::unique('server_ssh_keys', 'ssh_key_id')->where('server_id', $server->id),
|
||||
],
|
||||
])->validate();
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +55,6 @@ public static function rules(array $input): array
|
||||
$rules = array_merge($rules, $provider->validationRules());
|
||||
}
|
||||
|
||||
ds($rules);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
use App\Exceptions\SSHAuthenticationError;
|
||||
use App\Exceptions\SSHCommandError;
|
||||
use App\Exceptions\SSHConnectionError;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use Exception;
|
||||
@ -88,8 +89,7 @@ public function connect(bool $sftp = false): void
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHCommandError
|
||||
* @throws SSHConnectionError
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||
{
|
||||
@ -136,7 +136,6 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
return $output;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
throw $e;
|
||||
throw new SSHCommandError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -16,9 +17,15 @@
|
||||
* @property float $disk_total
|
||||
* @property float $disk_used
|
||||
* @property float $disk_free
|
||||
* @property-read float|int $memory_total_in_bytes
|
||||
* @property-read float|int $memory_used_in_bytes
|
||||
* @property-read float|int $memory_free_in_bytes
|
||||
* @property-read float|int $disk_total_in_bytes
|
||||
* @property-read float|int $disk_used_in_bytes
|
||||
* @property-read float|int $disk_free_in_bytes
|
||||
* @property Server $server
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class Metric extends Model
|
||||
{
|
||||
@ -50,4 +57,34 @@ public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function getMemoryTotalInBytesAttribute(): float|int
|
||||
{
|
||||
return $this->memory_total * 1024;
|
||||
}
|
||||
|
||||
public function getMemoryUsedInBytesAttribute(): float|int
|
||||
{
|
||||
return $this->memory_used * 1024;
|
||||
}
|
||||
|
||||
public function getMemoryFreeInBytesAttribute(): float|int
|
||||
{
|
||||
return $this->memory_free * 1024;
|
||||
}
|
||||
|
||||
public function getDiskTotalInBytesAttribute(): float|int
|
||||
{
|
||||
return $this->disk_total * (1024 * 1024);
|
||||
}
|
||||
|
||||
public function getDiskUsedInBytesAttribute(): float|int
|
||||
{
|
||||
return $this->disk_used * (1024 * 1024);
|
||||
}
|
||||
|
||||
public function getDiskFreeInBytesAttribute(): float|int
|
||||
{
|
||||
return $this->disk_free * (1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
* @property string $status
|
||||
* @property bool $is_default
|
||||
* @property Server $server
|
||||
* @property string $image_url
|
||||
*/
|
||||
class Service extends AbstractModel
|
||||
{
|
||||
@ -116,4 +117,9 @@ public function disable(): void
|
||||
{
|
||||
$this->unit && app(Manage::class)->disable($this);
|
||||
}
|
||||
|
||||
public function getImageUrlAttribute(): string
|
||||
{
|
||||
return url('/static/images/'.$this->name.'.svg');
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,8 @@
|
||||
use App\Enums\UserRole;
|
||||
use App\Traits\HasTimezoneTimestamps;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Models\Contracts\HasTenants;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
@ -42,7 +39,7 @@
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class User extends Authenticatable implements HasTenants
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasFactory;
|
||||
use HasTimezoneTimestamps;
|
||||
@ -119,16 +116,6 @@ public function projects(): BelongsToMany
|
||||
return $this->belongsToMany(Project::class, 'user_project')->withTimestamps();
|
||||
}
|
||||
|
||||
public function getTenants(Panel $panel): Collection
|
||||
{
|
||||
return $this->projects;
|
||||
}
|
||||
|
||||
public function canAccessTenant(Model $tenant): bool
|
||||
{
|
||||
return $this->projects()->whereKey($tenant)->exists();
|
||||
}
|
||||
|
||||
public function currentProject(): HasOne
|
||||
{
|
||||
return $this->HasOne(Project::class, 'id', 'current_project_id');
|
||||
|
41
app/Policies/CronJobPolicy.php
Normal file
41
app/Policies/CronJobPolicy.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CronJobPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) && $server->isReady();
|
||||
}
|
||||
|
||||
public function view(User $user, CronJob $cronjob): bool
|
||||
{
|
||||
return ($user->isAdmin() || $cronjob->server->project->users->contains($user)) &&
|
||||
$cronjob->server->isReady();
|
||||
}
|
||||
|
||||
public function create(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) && $server->isReady();
|
||||
}
|
||||
|
||||
public function update(User $user, CronJob $cronjob): bool
|
||||
{
|
||||
return ($user->isAdmin() || $cronjob->server->project->users->contains($user)) &&
|
||||
$cronjob->server->isReady();
|
||||
}
|
||||
|
||||
public function delete(User $user, CronJob $cronjob): bool
|
||||
{
|
||||
return ($user->isAdmin() || $cronjob->server->project->users->contains($user)) &&
|
||||
$cronjob->server->isReady();
|
||||
}
|
||||
}
|
41
app/Policies/FirewallRulePolicy.php
Normal file
41
app/Policies/FirewallRulePolicy.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class FirewallRulePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) && $server->isReady();
|
||||
}
|
||||
|
||||
public function view(User $user, FirewallRule $rule): bool
|
||||
{
|
||||
return ($user->isAdmin() || $rule->server->project->users->contains($user)) &&
|
||||
$rule->server->isReady();
|
||||
}
|
||||
|
||||
public function create(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) && $server->isReady();
|
||||
}
|
||||
|
||||
public function update(User $user, FirewallRule $rule): bool
|
||||
{
|
||||
return ($user->isAdmin() || $rule->server->project->users->contains($user)) &&
|
||||
$rule->server->isReady();
|
||||
}
|
||||
|
||||
public function delete(User $user, FirewallRule $rule): bool
|
||||
{
|
||||
return ($user->isAdmin() || $rule->server->project->users->contains($user)) &&
|
||||
$rule->server->isReady();
|
||||
}
|
||||
}
|
44
app/Policies/MetricPolicy.php
Normal file
44
app/Policies/MetricPolicy.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class MetricPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||
$server->service('monitoring');
|
||||
}
|
||||
|
||||
public function view(User $user, Metric $metric): bool
|
||||
{
|
||||
|
||||
return ($user->isAdmin() || $metric->server->project->users->contains($user)) &&
|
||||
$metric->server->service('monitoring');
|
||||
}
|
||||
|
||||
public function create(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||
$server->service('monitoring');
|
||||
}
|
||||
|
||||
public function update(User $user, Metric $metric): bool
|
||||
{
|
||||
return ($user->isAdmin() || $metric->server->project->users->contains($user)) &&
|
||||
$metric->server->service('monitoring');
|
||||
}
|
||||
|
||||
public function delete(User $user, Metric $metric): bool
|
||||
{
|
||||
return ($user->isAdmin() || $metric->server->project->users->contains($user)) &&
|
||||
$metric->server->service('monitoring');
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
@ -35,4 +36,52 @@ public function delete(User $user, Service $service): bool
|
||||
{
|
||||
return ($user->isAdmin() || $service->server->project->users->contains($user)) && $service->server->isReady();
|
||||
}
|
||||
|
||||
public function start(User $user, Service $service): bool
|
||||
{
|
||||
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
|
||||
$service->server->isReady() &&
|
||||
in_array($service->status, [
|
||||
ServiceStatus::STOPPED,
|
||||
ServiceStatus::FAILED,
|
||||
]);
|
||||
}
|
||||
|
||||
public function stop(User $user, Service $service): bool
|
||||
{
|
||||
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
|
||||
$service->server->isReady() &&
|
||||
in_array($service->status, [
|
||||
ServiceStatus::READY,
|
||||
ServiceStatus::FAILED,
|
||||
]);
|
||||
}
|
||||
|
||||
public function restart(User $user, Service $service): bool
|
||||
{
|
||||
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
|
||||
$service->server->isReady() &&
|
||||
in_array($service->status, [
|
||||
ServiceStatus::READY,
|
||||
ServiceStatus::FAILED,
|
||||
ServiceStatus::STOPPED,
|
||||
]);
|
||||
}
|
||||
|
||||
public function enable(User $user, Service $service): bool
|
||||
{
|
||||
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
|
||||
$service->server->isReady() &&
|
||||
$service->status == ServiceStatus::DISABLED;
|
||||
}
|
||||
|
||||
public function disable(User $user, Service $service): bool
|
||||
{
|
||||
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
|
||||
$service->server->isReady() &&
|
||||
in_array($service->status, [
|
||||
ServiceStatus::READY,
|
||||
ServiceStatus::STOPPED,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
66
app/Policies/SshKeyPolicy.php
Normal file
66
app/Policies/SshKeyPolicy.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\SshKey;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class SshKeyPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function view(User $user, SshKey $sshKey): bool
|
||||
{
|
||||
return $user->id === $sshKey->user_id;
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function update(User $user, SshKey $sshKey): bool
|
||||
{
|
||||
return $user->id === $sshKey->user_id;
|
||||
}
|
||||
|
||||
public function delete(User $user, SshKey $sshKey): bool
|
||||
{
|
||||
return $user->id === $sshKey->user_id;
|
||||
}
|
||||
|
||||
public function viewAnyServer(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) && $server->isReady();
|
||||
}
|
||||
|
||||
public function viewServer(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||
$server->isReady();
|
||||
}
|
||||
|
||||
public function createServer(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) && $server->isReady();
|
||||
}
|
||||
|
||||
public function updateServer(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||
$server->isReady();
|
||||
}
|
||||
|
||||
public function deleteServer(User $user, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $server->project->users->contains($user)) &&
|
||||
$server->isReady();
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Telescope\IncomingEntry;
|
||||
use Laravel\Telescope\Telescope;
|
||||
use Laravel\Telescope\TelescopeApplicationServiceProvider;
|
||||
|
||||
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Telescope::night();
|
||||
|
||||
$this->hideSensitiveRequestDetails();
|
||||
|
||||
$isLocal = $this->app->environment('local');
|
||||
|
||||
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
|
||||
return $isLocal ||
|
||||
$entry->isReportableException() ||
|
||||
$entry->isFailedRequest() ||
|
||||
$entry->isFailedJob() ||
|
||||
$entry->isScheduledTask() ||
|
||||
$entry->hasMonitoredTag();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent sensitive request details from being logged by Telescope.
|
||||
*/
|
||||
protected function hideSensitiveRequestDetails(): void
|
||||
{
|
||||
if ($this->app->environment('local')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telescope::hideRequestParameters(['_token']);
|
||||
|
||||
Telescope::hideRequestHeaders([
|
||||
'cookie',
|
||||
'x-csrf-token',
|
||||
'x-xsrf-token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Telescope gate.
|
||||
*
|
||||
* This gate determines who can access Telescope in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewTelescope', function ($user) {
|
||||
return in_array($user->email, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
@ -39,6 +39,10 @@ public function boot(): void
|
||||
PanelsRenderHook::SIDEBAR_NAV_START,
|
||||
fn () => Livewire::mount(SelectProject::class)
|
||||
);
|
||||
FilamentView::registerRenderHook(
|
||||
PanelsRenderHook::SIDEBAR_FOOTER,
|
||||
fn () => view('web.components.app-version')
|
||||
);
|
||||
FilamentColor::register([
|
||||
'slate' => Color::Slate,
|
||||
'gray' => Color::Gray,
|
||||
@ -96,6 +100,7 @@ public function panel(Panel $panel): Panel
|
||||
->login()
|
||||
->spa()
|
||||
->globalSearchKeyBindings(['command+k', 'ctrl+k'])
|
||||
->sidebarCollapsibleOnDesktop()
|
||||
->globalSearchFieldKeyBindingSuffix();
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,10 @@ public function update(string $user, string $cron): void
|
||||
if ! sudo -u $user crontab -l; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo 'cron updated!'
|
||||
EOD;
|
||||
|
||||
$this->server->ssh()->exec($command);
|
||||
$this->server->ssh()->exec($command, 'update-cron');
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class Create extends Page
|
||||
{
|
||||
protected static ?string $slug = 'servers/create';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Create Server';
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('create', Server::class) ?? false;
|
||||
}
|
||||
|
||||
protected function getExtraAttributes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\CreateServer::class],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/create-server.html')
|
||||
->openUrlInNewTab(),
|
||||
];
|
||||
}
|
||||
}
|
96
app/Web/Pages/Servers/CronJobs/Index.php
Normal file
96
app/Web/Pages/Servers/CronJobs/Index.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\CronJobs;
|
||||
|
||||
use App\Actions\CronJob\CreateCronJob;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/cronjobs';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Cron Jobs';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [CronJob::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\CronJobsList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/cronjobs.html')
|
||||
->openUrlInNewTab(),
|
||||
Action::make('create')
|
||||
->authorize(fn () => auth()->user()?->can('create', [CronJob::class, $this->server]))
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::ExtraLarge)
|
||||
->form([
|
||||
TextInput::make('command')
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['command'])
|
||||
->helperText(fn () => view('web.components.link', [
|
||||
'href' => 'https://vitodeploy.com/servers/cronjobs.html',
|
||||
'external' => true,
|
||||
'text' => 'How the command should look like?',
|
||||
])),
|
||||
Select::make('user')
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['user'])
|
||||
->options([
|
||||
'vito' => 'vito',
|
||||
'root' => 'root',
|
||||
]),
|
||||
Select::make('frequency')
|
||||
->options(config('core.cronjob_intervals'))
|
||||
->reactive()
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['frequency']),
|
||||
TextInput::make('custom')
|
||||
->label('Custom Frequency (Cron)')
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['custom'])
|
||||
->visible(fn (callable $get) => $get('frequency') === 'custom')
|
||||
->placeholder('0 * * * *'),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
try {
|
||||
app(CreateCronJob::class)->create($this->server, $data);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
67
app/Web/Pages/Servers/CronJobs/Widgets/CronJobsList.php
Normal file
67
app/Web/Pages/Servers/CronJobs/Widgets/CronJobsList.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\CronJobs\Widgets;
|
||||
|
||||
use App\Actions\CronJob\DeleteCronJob;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class CronJobsList extends Widget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return CronJob::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('command')
|
||||
->limit(40)
|
||||
->tooltip(fn (CronJob $cronJob) => $cronJob->command)
|
||||
->searchable()
|
||||
->copyable(),
|
||||
TextColumn::make('created_at')
|
||||
->formatStateUsing(fn (CronJob $cronJob) => $cronJob->created_at_by_timezone)
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->authorize(fn (CronJob $record) => auth()->user()->can('delete', $record))
|
||||
->action(function (CronJob $record) {
|
||||
try {
|
||||
app(DeleteCronJob::class)->delete($this->server, $record);
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ public function getTable(): Table
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-rectangle-stack')
|
||||
->modalHeading('Backup Files')
|
||||
->color('secondary')
|
||||
->color('gray')
|
||||
->tooltip('Show backup files')
|
||||
->authorize(fn (Backup $record) => auth()->user()->can('viewAny', [BackupFile::class, $record]))
|
||||
->modalContent(fn (Backup $record) => view('web.components.dynamic-widget', [
|
||||
|
@ -65,7 +65,7 @@ private function passwordAction(): Action
|
||||
return Action::make('password')
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-key')
|
||||
->color('secondary')
|
||||
->color('gray')
|
||||
->modalHeading('Database user\'s password')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->tooltip('Show the password')
|
||||
|
95
app/Web/Pages/Servers/Firewall/Index.php
Normal file
95
app/Web/Pages/Servers/Firewall/Index.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall;
|
||||
|
||||
use App\Actions\FirewallRule\CreateRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/firewall';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Firewall';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [FirewallRule::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\RulesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/firewall.html')
|
||||
->openUrlInNewTab(),
|
||||
Action::make('create')
|
||||
->authorize(fn () => auth()->user()?->can('create', [FirewallRule::class, $this->server]))
|
||||
->label('Create a Rule')
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->form([
|
||||
Select::make('type')
|
||||
->native(false)
|
||||
->options([
|
||||
'allow' => 'Allow',
|
||||
'deny' => 'Deny',
|
||||
])
|
||||
->rules(CreateRule::rules()['type']),
|
||||
Select::make('protocol')
|
||||
->native(false)
|
||||
->options([
|
||||
'tcp' => 'TCP',
|
||||
'udp' => 'UDP',
|
||||
])
|
||||
->rules(CreateRule::rules()['protocol']),
|
||||
TextInput::make('port')
|
||||
->rules(CreateRule::rules()['port']),
|
||||
TextInput::make('source')
|
||||
->rules(CreateRule::rules()['source']),
|
||||
TextInput::make('mask')
|
||||
->rules(CreateRule::rules()['mask']),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
try {
|
||||
app(CreateRule::class)->create($this->server, $data);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
72
app/Web/Pages/Servers/Firewall/Widgets/RulesList.php
Normal file
72
app/Web/Pages/Servers/Firewall/Widgets/RulesList.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall\Widgets;
|
||||
|
||||
use App\Actions\FirewallRule\DeleteRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class RulesList extends Widget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return FirewallRule::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('type')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase'])
|
||||
->color(fn (FirewallRule $record) => $record->type === 'allow' ? 'green' : 'red'),
|
||||
TextColumn::make('protocol')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase']),
|
||||
TextColumn::make('port')
|
||||
->sortable(),
|
||||
TextColumn::make('source')
|
||||
->sortable(),
|
||||
TextColumn::make('mask')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('delete', $record))
|
||||
->action(function (FirewallRule $record) {
|
||||
try {
|
||||
app(DeleteRule::class)->delete($this->server, $record);
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
@ -52,6 +52,12 @@ protected function getHeaderActions(): array
|
||||
]);
|
||||
|
||||
return [
|
||||
\Filament\Actions\Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/create-server.html')
|
||||
->openUrlInNewTab(),
|
||||
\Filament\Actions\Action::make('create')
|
||||
->label('Create a Server')
|
||||
->icon('heroicon-o-plus')
|
||||
@ -96,6 +102,8 @@ protected function getHeaderActions(): array
|
||||
)
|
||||
->placeholder('Select profile')
|
||||
->native(false)
|
||||
->live()
|
||||
->reactive()
|
||||
->selectablePlaceholder(false)
|
||||
->visible(fn ($get) => $get('provider') !== ServerProvider::CUSTOM),
|
||||
Grid::make()
|
||||
@ -110,7 +118,7 @@ protected function getHeaderActions(): array
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\ServerProvider::regions($get('serer_provider'));
|
||||
return \App\Models\ServerProvider::regions($get('server_provider'));
|
||||
})
|
||||
->loadingMessage('Loading regions...')
|
||||
->disabled(fn ($get) => ! $get('server_provider'))
|
||||
@ -219,6 +227,7 @@ protected function getHeaderActions(): array
|
||||
),
|
||||
]),
|
||||
])
|
||||
->modalSubmitActionLabel('Create')
|
||||
->action(function ($input) {
|
||||
$this->authorize('create', Server::class);
|
||||
|
||||
|
@ -86,7 +86,7 @@ public function getTable(): Table
|
||||
Action::make('download')
|
||||
->hiddenLabel()
|
||||
->tooltip('Download')
|
||||
->color('secondary')
|
||||
->color('gray')
|
||||
->icon('heroicon-o-archive-box-arrow-down')
|
||||
->authorize(fn ($record) => auth()->user()->can('view', $record))
|
||||
->action(fn (ServerLog $record) => $record->download()),
|
||||
|
39
app/Web/Pages/Servers/Metrics/Index.php
Normal file
39
app/Web/Pages/Servers/Metrics/Index.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/metrics';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Metrics';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [Metric::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\FilterForm::class, ['server' => $this->server]],
|
||||
[Widgets\MetricDetails::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
86
app/Web/Pages/Servers/Metrics/Widgets/FilterForm.php
Normal file
86
app/Web/Pages/Servers/Metrics/Widgets/FilterForm.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics\Widgets;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Models\Server;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\ViewField;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Widgets\Widget;
|
||||
|
||||
class FilterForm extends Widget implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static string $view = 'web.components.form';
|
||||
|
||||
public ?array $data = [
|
||||
'period' => '1h',
|
||||
'from' => null,
|
||||
'to' => null,
|
||||
];
|
||||
|
||||
public function updated($name, $value): void
|
||||
{
|
||||
if ($value !== 'custom') {
|
||||
$this->dispatch('updateFilters', filters: $this->data);
|
||||
}
|
||||
|
||||
if ($value === 'custom' && $this->data['from'] && $this->data['to']) {
|
||||
$this->dispatch('updateFilters', filters: $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
public Server $server;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Grid::make()
|
||||
->columns(3)
|
||||
->schema([
|
||||
Select::make('period')
|
||||
->live()
|
||||
->reactive()
|
||||
->options([
|
||||
'10m' => '10 Minutes',
|
||||
'30m' => '30 Minutes',
|
||||
'1h' => '1 Hour',
|
||||
'12h' => '12 Hours',
|
||||
'1d' => '1 Day',
|
||||
'7d' => '7 Days',
|
||||
'custom' => 'Custom',
|
||||
])
|
||||
->rules(fn (Get $get) => GetMetrics::rules($get())['period']),
|
||||
DatePicker::make('from')
|
||||
->reactive()
|
||||
->visible(fn (Get $get) => $get('period') === 'custom')
|
||||
->maxDate(fn (Get $get) => now())
|
||||
->rules(fn (Get $get) => GetMetrics::rules($get())['from']),
|
||||
DatePicker::make('to')
|
||||
->reactive()
|
||||
->visible(fn (Get $get) => $get('period') === 'custom')
|
||||
->minDate(fn (Get $get) => $get('from') ?: now())
|
||||
->maxDate(now())
|
||||
->rules(fn (Get $get) => GetMetrics::rules($get())['to']),
|
||||
]),
|
||||
ViewField::make('data')
|
||||
->reactive()
|
||||
->view('web.components.dynamic-widget', [
|
||||
'widget' => Metrics::class,
|
||||
'params' => [
|
||||
'server' => $this->server,
|
||||
'filters' => $this->data,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
}
|
83
app/Web/Pages/Servers/Metrics/Widgets/MetricDetails.php
Normal file
83
app/Web/Pages/Servers/Metrics/Widgets/MetricDetails.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics\Widgets;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Infolists\Components\Grid;
|
||||
use Filament\Infolists\Components\Section;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Infolists\Concerns\InteractsWithInfolists;
|
||||
use Filament\Infolists\Contracts\HasInfolists;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class MetricDetails extends Widget implements HasForms, HasInfolists
|
||||
{
|
||||
use InteractsWithForms;
|
||||
use InteractsWithInfolists;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static string $view = 'web.components.infolist';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return $infolist
|
||||
->record($this->server->metrics()->latest()->first())
|
||||
->schema([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->heading('Memory')
|
||||
->description('More details on memory')
|
||||
->columnSpan(1)
|
||||
->schema([
|
||||
TextEntry::make('memory_total')
|
||||
->label('Total Memory')
|
||||
->alignRight()
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->memory_total_in_bytes, 2))
|
||||
->inlineLabel(),
|
||||
TextEntry::make('memory_used')
|
||||
->label('Used Memory')
|
||||
->alignRight()
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->memory_used_in_bytes, 2))
|
||||
->inlineLabel(),
|
||||
TextEntry::make('memory_free')
|
||||
->label('Free Memory')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->memory_free_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
]),
|
||||
Section::make()
|
||||
->heading('Disk')
|
||||
->description('More details on disk')
|
||||
->columnSpan(1)
|
||||
->schema([
|
||||
TextEntry::make('disk_total')
|
||||
->label('Total Disk')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->disk_total_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
TextEntry::make('disk_used')
|
||||
->label('Used Disk')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->disk_used_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
TextEntry::make('disk_free')
|
||||
->label('Free Disk')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->disk_free_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
48
app/Web/Pages/Servers/Metrics/Widgets/Metrics.php
Normal file
48
app/Web/Pages/Servers/Metrics/Widgets/Metrics.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics\Widgets;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Illuminate\Support\Number;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class Metrics extends BaseWidget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public array $filters = [];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
#[On('updateFilters')]
|
||||
public function updateFilters(array $filters): void
|
||||
{
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
protected function getStats(): array
|
||||
{
|
||||
/** @var Metric $lastMetric */
|
||||
$lastMetric = $this->server
|
||||
->metrics()
|
||||
->latest()
|
||||
->first();
|
||||
$metrics = app(GetMetrics::class)->filter($this->server, $this->filters);
|
||||
|
||||
return [
|
||||
Stat::make('CPU Load', $lastMetric?->load ?? 0)
|
||||
->color('success')
|
||||
->chart($metrics->pluck('load')->toArray()),
|
||||
Stat::make('Memory Usage', Number::fileSize($lastMetric->memory_used_in_bytes, 2))
|
||||
->color('warning')
|
||||
->chart($metrics->pluck('memory_used')->toArray()),
|
||||
Stat::make('Disk Usage', Number::fileSize($lastMetric->disk_used_in_bytes, 2))
|
||||
->color('primary')
|
||||
->chart($metrics->pluck('disk_used')->toArray()),
|
||||
];
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
@ -57,7 +58,8 @@ protected function getHeaderActions(): array
|
||||
ActionGroup::make($phps)
|
||||
->authorize(fn () => auth()->user()?->can('create', [Service::class, $this->server]))
|
||||
->label('Install PHP')
|
||||
->icon('heroicon-o-plus')
|
||||
->icon('heroicon-o-chevron-up-down')
|
||||
->iconPosition(IconPosition::After)
|
||||
->dropdownPlacement('bottom-end')
|
||||
->color('primary')
|
||||
->button(),
|
||||
|
96
app/Web/Pages/Servers/SSHKeys/Index.php
Normal file
96
app/Web/Pages/Servers/SSHKeys/Index.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\SSHKeys;
|
||||
|
||||
use App\Actions\SshKey\CreateSshKey;
|
||||
use App\Actions\SshKey\DeployKeyToServer;
|
||||
use App\Models\Server;
|
||||
use App\Models\SshKey;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/ssh-keys';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'SSH Keys';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAnyServer', [SshKey::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\SshKeysList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('deploy')
|
||||
->label('Deploy a Key')
|
||||
->authorize(fn () => auth()->user()?->can('createServer', [SshKey::class, $this->server]))
|
||||
->icon('heroicon-o-rocket-launch')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->form([
|
||||
Select::make('type')
|
||||
->options([
|
||||
'existing' => 'An existing key',
|
||||
'new' => 'A new key',
|
||||
])
|
||||
->reactive()
|
||||
->default('existing'),
|
||||
Select::make('key_id')
|
||||
->label('Key')
|
||||
->options(auth()->user()->sshKeys()->pluck('name', 'id')->toArray())
|
||||
->visible(fn ($get) => $get('type') === 'existing')
|
||||
->rules(DeployKeyToServer::rules(auth()->user(), $this->server)['key_id']),
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->visible(fn ($get) => $get('type') === 'new')
|
||||
->rules(CreateSshKey::rules()['name']),
|
||||
Textarea::make('public_key')
|
||||
->label('Public Key')
|
||||
->visible(fn ($get) => $get('type') === 'new')
|
||||
->rules(CreateSshKey::rules()['public_key']),
|
||||
])
|
||||
->modalSubmitActionLabel('Deploy')
|
||||
->action(function (array $data) {
|
||||
$this->validate();
|
||||
|
||||
try {
|
||||
if (! isset($data['key_id'])) {
|
||||
$data['key_id'] = app(CreateSshKey::class)->create(auth()->user(), $data)->id;
|
||||
}
|
||||
|
||||
app(DeployKeyToServer::class)->deploy($this->server, $data);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
71
app/Web/Pages/Servers/SSHKeys/Widgets/SshKeysList.php
Normal file
71
app/Web/Pages/Servers/SSHKeys/Widgets/SshKeysList.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\SSHKeys\Widgets;
|
||||
|
||||
use App\Actions\SshKey\DeleteKeyFromServer;
|
||||
use App\Models\Server;
|
||||
use App\Models\SshKey;
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class SshKeysList extends TableWidget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return SshKey::query()->whereHas(
|
||||
'servers',
|
||||
fn (Builder $query) => $query->where('server_id', $this->server->id)
|
||||
);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('user.name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->authorize(fn (SshKey $record) => auth()->user()->can('deleteServer', [SshKey::class, $this->server]))
|
||||
->action(function (SshKey $record) {
|
||||
try {
|
||||
app(DeleteKeyFromServer::class)->delete($this->server, $record);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
93
app/Web/Pages/Servers/Services/Index.php
Normal file
93
app/Web/Pages/Servers/Services/Index.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Services;
|
||||
|
||||
use App\Actions\Service\Install;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/services';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Services';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [Service::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\ServicesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
$availableServices = [];
|
||||
foreach (config('core.service_handlers') as $key => $addOn) {
|
||||
if (! $this->server->services()->where('name', $key)->exists()) {
|
||||
$availableServices[$key] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
Action::make('install')
|
||||
->label('Install Service')
|
||||
->icon('heroicon-o-archive-box-arrow-down')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->authorize(fn () => auth()->user()?->can('create', [Service::class, $this->server]))
|
||||
->form([
|
||||
Select::make('name')
|
||||
->searchable()
|
||||
->options($availableServices)
|
||||
->reactive()
|
||||
->rules(fn ($get) => Install::rules($get())['name']),
|
||||
Select::make('version')
|
||||
->options(function (callable $get) {
|
||||
if (! $get('name')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect(config("core.service_versions.{$get('name')}"))
|
||||
->mapWithKeys(fn ($version) => [$version => $version]);
|
||||
})
|
||||
->rules(fn ($get) => Install::rules($get())['version'])
|
||||
->reactive(),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
$this->validate();
|
||||
|
||||
try {
|
||||
app(Install::class)->install($this->server, $data);
|
||||
|
||||
$this->redirect(self::getUrl(['server' => $this->server]));
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
117
app/Web/Pages/Servers/Services/Widgets/ServicesList.php
Normal file
117
app/Web/Pages/Servers/Services/Widgets/ServicesList.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Services\Widgets;
|
||||
|
||||
use App\Actions\Service\Manage;
|
||||
use App\Actions\Service\Uninstall;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Web\Pages\Servers\Services\Index;
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\ActionGroup;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ServicesList extends TableWidget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return Service::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = 'Installed Services';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
ImageColumn::make('image_url')
|
||||
->label('Service')
|
||||
->size(24),
|
||||
TextColumn::make('name')
|
||||
->sortable(),
|
||||
TextColumn::make('version')
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->color(fn (Service $service) => Service::$statusColors[$service->status])
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label('Installed At')
|
||||
->formatStateUsing(fn ($record) => $record->created_at_by_timezone),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
ActionGroup::make([
|
||||
$this->serviceAction('start'),
|
||||
$this->serviceAction('stop'),
|
||||
$this->serviceAction('restart'),
|
||||
$this->serviceAction('disable'),
|
||||
$this->serviceAction('enable'),
|
||||
$this->uninstallAction(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
private function serviceAction(string $type): Action
|
||||
{
|
||||
return Action::make($type)
|
||||
->authorize(fn (Service $service) => auth()->user()?->can($type, $service))
|
||||
->label(ucfirst($type).' Service')
|
||||
->action(function (Service $service) use ($type) {
|
||||
try {
|
||||
app(Manage::class)->$type($service);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
});
|
||||
}
|
||||
|
||||
private function uninstallAction(): Action
|
||||
{
|
||||
return Action::make('uninstall')
|
||||
->authorize(fn (Service $service) => auth()->user()?->can('delete', $service))
|
||||
->label('Uninstall Service')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(function (Service $service) {
|
||||
try {
|
||||
app(Uninstall::class)->uninstall($service);
|
||||
|
||||
$this->redirect(Index::getUrl(['server' => $this->server]));
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
});
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Pages\Servers\Sites\Widgets\SitesList;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Filament\Actions\CreateAction;
|
||||
|
||||
@ -29,7 +28,7 @@ public static function canAccess(): bool
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[SitesList::class, ['server' => $this->server]],
|
||||
[Widgets\SitesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,260 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Widgets;
|
||||
|
||||
use App\Actions\Server\CreateServer as CreateServerAction;
|
||||
use App\Enums\ServerProvider;
|
||||
use App\Enums\ServerType;
|
||||
use App\Enums\Webserver;
|
||||
use App\Models\Server;
|
||||
use App\Web\Fields\AlertField;
|
||||
use App\Web\Fields\ProviderField;
|
||||
use App\Web\Pages\Servers\View;
|
||||
use App\Web\Pages\Settings\ServerProviders\Actions\Create;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Widgets\Widget;
|
||||
use Throwable;
|
||||
|
||||
class CreateServer extends Widget implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static string $view = 'web.components.form';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
public ?string $provider = ServerProvider::HETZNER;
|
||||
|
||||
public ?string $server_provider = '';
|
||||
|
||||
public ?string $region = '';
|
||||
|
||||
public ?string $plan = '';
|
||||
|
||||
public ?string $public_key = '';
|
||||
|
||||
public ?string $name = '';
|
||||
|
||||
public ?string $ip = '';
|
||||
|
||||
public ?string $port = '';
|
||||
|
||||
public ?string $os = '';
|
||||
|
||||
public ?string $type = ServerType::REGULAR;
|
||||
|
||||
public ?string $webserver = Webserver::NGINX;
|
||||
|
||||
public ?string $database = '';
|
||||
|
||||
public ?string $php = '';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
$publicKey = __('servers.create.public_key_text', [
|
||||
'public_key' => get_public_key_content(),
|
||||
]);
|
||||
|
||||
return $form
|
||||
->schema([
|
||||
ProviderField::make('provider')
|
||||
->label('Select a provider')
|
||||
->default(ServerProvider::CUSTOM)
|
||||
->live()
|
||||
->reactive()
|
||||
->afterStateUpdated(function (callable $set) {
|
||||
$set('server_provider', null);
|
||||
$set('region', null);
|
||||
$set('plan', null);
|
||||
})
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['provider']),
|
||||
AlertField::make('alert')
|
||||
->warning()
|
||||
->message(__('servers.create.public_key_warning'))
|
||||
->visible(fn ($get) => $this->provider === ServerProvider::CUSTOM),
|
||||
Select::make('server_provider')
|
||||
->visible(fn ($get) => $this->provider !== ServerProvider::CUSTOM)
|
||||
->label('Server provider connection')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['server_provider'])
|
||||
->options(function ($get) {
|
||||
return \App\Models\ServerProvider::getByProjectId(auth()->user()->current_project_id)
|
||||
->where('provider', $this->provider)
|
||||
->pluck('profile', 'id');
|
||||
})
|
||||
->live()
|
||||
->suffixAction(
|
||||
Action::make('connect')
|
||||
->form(Create::form())
|
||||
->modalHeading('Connect to a new server provider')
|
||||
->modalSubmitActionLabel('Connect')
|
||||
->icon('heroicon-o-wifi')
|
||||
->tooltip('Connect to a new server provider')
|
||||
->modalWidth(MaxWidth::Medium)
|
||||
->authorize(fn () => auth()->user()->can('create', \App\Models\ServerProvider::class))
|
||||
// TODO: remove this after filament #14319 is fixed
|
||||
->url(\App\Web\Pages\Settings\ServerProviders\Index::getUrl())
|
||||
->action(fn (array $data) => Create::action($data))
|
||||
)
|
||||
->placeholder('Select profile')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->visible(fn ($get) => $this->provider !== ServerProvider::CUSTOM),
|
||||
Grid::make()
|
||||
->schema([
|
||||
Select::make('region')
|
||||
->label('Region')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['region'])
|
||||
->live()
|
||||
->reactive()
|
||||
->options(function () {
|
||||
if (! $this->server_provider) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\ServerProvider::regions($this->server_provider);
|
||||
})
|
||||
->loadingMessage('Loading regions...')
|
||||
->disabled(fn ($get) => ! $this->server_provider)
|
||||
->placeholder(fn ($get) => $this->server_provider ? 'Select region' : 'Select connection first')
|
||||
->searchable(),
|
||||
Select::make('plan')
|
||||
->label('Plan')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['plan'])
|
||||
->reactive()
|
||||
->options(function () {
|
||||
if (! $this->server_provider || ! $this->region) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\ServerProvider::plans($this->server_provider, $this->region);
|
||||
})
|
||||
->loadingMessage('Loading plans...')
|
||||
->disabled(fn ($get) => ! $this->region)
|
||||
->placeholder(fn ($get) => $this->region ? 'Select plan' : 'Select plan first')
|
||||
->searchable(),
|
||||
])
|
||||
->visible(fn ($get) => $this->provider !== ServerProvider::CUSTOM),
|
||||
TextInput::make('public_key')
|
||||
->label('Public Key')
|
||||
->default($publicKey)
|
||||
->suffixAction(
|
||||
Action::make('copy')
|
||||
->icon('heroicon-o-clipboard-document-list')
|
||||
->tooltip('Copy')
|
||||
->action(function ($livewire, $state) {
|
||||
$livewire->js(
|
||||
'window.navigator.clipboard.writeText("'.$state.'");'
|
||||
);
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Copied!')
|
||||
->send();
|
||||
})
|
||||
)
|
||||
->helperText('Run this command on your server as root user')
|
||||
->disabled()
|
||||
->visible(fn ($get) => $this->provider === ServerProvider::CUSTOM),
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['name']),
|
||||
Grid::make()
|
||||
->schema([
|
||||
TextInput::make('ip')
|
||||
->label('SSH IP Address')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['ip']),
|
||||
TextInput::make('port')
|
||||
->label('SSH Port')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['port']),
|
||||
])
|
||||
->visible(fn ($get) => $this->provider === ServerProvider::CUSTOM),
|
||||
Grid::make()
|
||||
->schema([
|
||||
Select::make('os')
|
||||
->label('OS')
|
||||
->native(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['os'])
|
||||
->options(
|
||||
collect(config('core.operating_systems'))
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
),
|
||||
Select::make('type')
|
||||
->label('Server Type')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['type'])
|
||||
->options(
|
||||
collect(config('core.server_types'))
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
)
|
||||
->default(ServerType::REGULAR),
|
||||
]),
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Select::make('webserver')
|
||||
->label('Webserver')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['webserver'] ?? [])
|
||||
->options(
|
||||
collect(config('core.webservers'))->mapWithKeys(fn ($value) => [$value => $value])
|
||||
),
|
||||
Select::make('database')
|
||||
->label('Database')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['database'] ?? [])
|
||||
->options(
|
||||
collect(config('core.databases_name'))
|
||||
->mapWithKeys(fn ($value, $key) => [
|
||||
$key => $value.' '.config('core.databases_version')[$key],
|
||||
])
|
||||
),
|
||||
Select::make('php')
|
||||
->label('PHP')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['php'] ?? [])
|
||||
->options(
|
||||
collect(config('core.php_versions'))
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
),
|
||||
]),
|
||||
Actions::make([
|
||||
Action::make('create')
|
||||
->label('Create Server')
|
||||
->button()
|
||||
->action(fn () => $this->submit()),
|
||||
]),
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
|
||||
public function submit(): void
|
||||
{
|
||||
$this->authorize('create', Server::class);
|
||||
|
||||
$this->validate();
|
||||
|
||||
try {
|
||||
$server = app(CreateServerAction::class)->create(auth()->user(), $this->all()['data']);
|
||||
|
||||
$this->redirect(View::getUrl(['server' => $server]));
|
||||
} catch (Throwable $e) {
|
||||
Notification::make()
|
||||
->title($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ public function infolist(Infolist $infolist): Infolist
|
||||
TextEntry::make('last_updated_check')
|
||||
->label('Last Updated Check')
|
||||
->inlineLabel()
|
||||
->state(fn (Server $record) => $record->getDateTimeByTimezone($record->last_update_check) ?? '-')
|
||||
->state(fn (Server $record) => $record->last_update_check?->ago())
|
||||
->suffixAction(
|
||||
Action::make('check-update')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
|
@ -18,7 +18,7 @@ protected function getStats(): array
|
||||
|
||||
if ($this->server->webserver()) {
|
||||
$stats[] = Stat::make('Sites', $this->server->sites()->count())
|
||||
->icon('heroicon-o-globe-alt');
|
||||
->icon('heroicon-o-cursor-arrow-ripple');
|
||||
}
|
||||
|
||||
if ($this->server->database()) {
|
||||
|
@ -3,23 +3,24 @@
|
||||
namespace App\Web\Pages\Settings\Projects\Widgets;
|
||||
|
||||
use App\Models\Project;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SelectProject extends Widget implements HasForms
|
||||
class SelectProject extends Widget
|
||||
{
|
||||
use InteractsWithForms;
|
||||
protected static string $view = 'web.widgets.select-project';
|
||||
|
||||
protected static string $view = 'web.components.form';
|
||||
public ?Project $currentProject;
|
||||
|
||||
public Collection $projects;
|
||||
|
||||
public int|string|null $project;
|
||||
|
||||
protected function getFormSchema(): array
|
||||
public function mount(): void
|
||||
{
|
||||
$options = Project::query()
|
||||
$this->currentProject = auth()->user()->currentProject;
|
||||
$this->projects = Project::query()
|
||||
->where(function (Builder $query) {
|
||||
if (auth()->user()->isAdmin()) {
|
||||
return;
|
||||
@ -27,35 +28,14 @@ protected function getFormSchema(): array
|
||||
$query->where('user_id', auth()->id())
|
||||
->orWhereHas('users', fn ($query) => $query->where('user_id', auth()->id()));
|
||||
})
|
||||
->get()
|
||||
->mapWithKeys(fn ($project) => [$project->id => $project->name])
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
Select::make('project')
|
||||
->name('project')
|
||||
->model($this->project)
|
||||
->label('Project')
|
||||
->searchable()
|
||||
->options($options)
|
||||
->searchPrompt('Select a project...')
|
||||
->extraAttributes(['class' => '-mx-2 pointer-choices'])
|
||||
->selectablePlaceholder(false)
|
||||
->live(),
|
||||
];
|
||||
->get();
|
||||
}
|
||||
|
||||
public function updatedProject($value): void
|
||||
public function updateProject(Project $project): void
|
||||
{
|
||||
$project = Project::query()->findOrFail($value);
|
||||
$this->authorize('view', $project);
|
||||
auth()->user()->update(['current_project_id' => $value]);
|
||||
auth()->user()->update(['current_project_id' => $project->id]);
|
||||
|
||||
$this->redirect('/app');
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->project = auth()->user()->current_project_id;
|
||||
$this->redirect('/');
|
||||
}
|
||||
}
|
||||
|
@ -3,25 +3,24 @@
|
||||
namespace App\Web\Traits;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Web\Pages\Servers\CronJobs\Index as CronJobsIndex;
|
||||
use App\Web\Pages\Servers\Databases\Index as DatabasesIndex;
|
||||
use App\Web\Pages\Servers\Firewall\Index as FirewallIndex;
|
||||
use App\Web\Pages\Servers\Logs\Index as LogsIndex;
|
||||
use App\Web\Pages\Servers\Metrics\Index as MetricsIndex;
|
||||
use App\Web\Pages\Servers\PHP\Index as PHPIndex;
|
||||
use App\Web\Pages\Servers\Services\Index as ServicesIndex;
|
||||
use App\Web\Pages\Servers\Settings as ServerSettings;
|
||||
use App\Web\Pages\Servers\Sites\Index as SitesIndex;
|
||||
use App\Web\Pages\Servers\SshKeys\Index as SshKeysIndex;
|
||||
use App\Web\Pages\Servers\View as ServerView;
|
||||
use App\Web\Pages\Servers\Widgets\ServerSummary;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
trait PageHasServer
|
||||
{
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return static::$title.' - '.$this->server->name;
|
||||
}
|
||||
|
||||
public function getSubNavigation(): array
|
||||
{
|
||||
$items = [];
|
||||
@ -35,7 +34,7 @@ public function getSubNavigation(): array
|
||||
|
||||
if (SitesIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(SitesIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-globe-alt')
|
||||
->icon('heroicon-o-cursor-arrow-ripple')
|
||||
->isActiveWhen(fn () => request()->routeIs(SitesIndex::getRouteName().'*'))
|
||||
->url(SitesIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
@ -54,6 +53,41 @@ public function getSubNavigation(): array
|
||||
->url(PHPIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (FirewallIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(FirewallIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-fire')
|
||||
->isActiveWhen(fn () => request()->routeIs(FirewallIndex::getRouteName().'*'))
|
||||
->url(FirewallIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (CronJobsIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(CronJobsIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-clock')
|
||||
->isActiveWhen(fn () => request()->routeIs(CronJobsIndex::getRouteName().'*'))
|
||||
->url(CronJobsIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (SshKeysIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(SshKeysIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-key')
|
||||
->isActiveWhen(fn () => request()->routeIs(SshKeysIndex::getRouteName().'*'))
|
||||
->url(SshKeysIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (ServicesIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(ServicesIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->isActiveWhen(fn () => request()->routeIs(ServicesIndex::getRouteName().'*'))
|
||||
->url(ServicesIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (MetricsIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(MetricsIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-chart-bar')
|
||||
->isActiveWhen(fn () => request()->routeIs(MetricsIndex::getRouteName().'*'))
|
||||
->url(MetricsIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (LogsIndex::canAccess()) {
|
||||
$items[] = NavigationItem::make(LogsIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-square-3-stack-3d')
|
||||
@ -63,7 +97,7 @@ public function getSubNavigation(): array
|
||||
|
||||
if (ServerSettings::canAccess()) {
|
||||
$items[] = NavigationItem::make(ServerSettings::getNavigationLabel())
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->icon('heroicon-o-wrench-screwdriver')
|
||||
->isActiveWhen(fn () => request()->routeIs(ServerSettings::getRouteName().'*'))
|
||||
->url(ServerSettings::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
154
composer.lock
generated
154
composer.lock
generated
@ -128,16 +128,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.322.6",
|
||||
"version": "3.322.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "ae7b0edab466c3440fe007c07cb62ae32a4dbfca"
|
||||
"reference": "fb5099160e49b676277ae787ff721628e5e4dd5a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ae7b0edab466c3440fe007c07cb62ae32a4dbfca",
|
||||
"reference": "ae7b0edab466c3440fe007c07cb62ae32a4dbfca",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fb5099160e49b676277ae787ff721628e5e4dd5a",
|
||||
"reference": "fb5099160e49b676277ae787ff721628e5e4dd5a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -220,22 +220,22 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.322.6"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.322.8"
|
||||
},
|
||||
"time": "2024-09-26T18:12:45+00:00"
|
||||
"time": "2024-09-30T19:09:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "v3.0.0",
|
||||
"version": "v3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "510de6eca6248d77d31b339d62437cc995e2fb41"
|
||||
"reference": "f9cc1f52b5a463062251d666761178dbdb6b544f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41",
|
||||
"reference": "510de6eca6248d77d31b339d62437cc995e2fb41",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f",
|
||||
"reference": "f9cc1f52b5a463062251d666761178dbdb6b544f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -274,9 +274,9 @@
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"support": {
|
||||
"issues": "https://github.com/Bacon/BaconQrCode/issues",
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0"
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1"
|
||||
},
|
||||
"time": "2024-04-18T11:16:25+00:00"
|
||||
"time": "2024-10-01T13:55:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "blade-ui-kit/blade-heroicons",
|
||||
@ -1240,16 +1240,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/actions",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/actions.git",
|
||||
"reference": "4cf93bf9ff04a76a9256ce6df88216583aeccb15"
|
||||
"reference": "38c6eb00c7e3265907b37482c2dfd411c6f910c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/4cf93bf9ff04a76a9256ce6df88216583aeccb15",
|
||||
"reference": "4cf93bf9ff04a76a9256ce6df88216583aeccb15",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/38c6eb00c7e3265907b37482c2dfd411c6f910c9",
|
||||
"reference": "38c6eb00c7e3265907b37482c2dfd411c6f910c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1289,20 +1289,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-09-17T08:30:20+00:00"
|
||||
"time": "2024-09-27T13:16:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/filament",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/panels.git",
|
||||
"reference": "8d28c9756a341349a5d7a0694a66be7cc2c986c3"
|
||||
"reference": "8d0f0e7101c14fe2f00490172452767f16b39f02"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/8d28c9756a341349a5d7a0694a66be7cc2c986c3",
|
||||
"reference": "8d28c9756a341349a5d7a0694a66be7cc2c986c3",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/8d0f0e7101c14fe2f00490172452767f16b39f02",
|
||||
"reference": "8d0f0e7101c14fe2f00490172452767f16b39f02",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1354,20 +1354,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-09-23T14:09:56+00:00"
|
||||
"time": "2024-09-27T13:16:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/forms",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/forms.git",
|
||||
"reference": "46a42dbc18f9273a3a59c54e94222fa62855c702"
|
||||
"reference": "ffa33043ea0ee67a4eed58535687f87311e4256b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/46a42dbc18f9273a3a59c54e94222fa62855c702",
|
||||
"reference": "46a42dbc18f9273a3a59c54e94222fa62855c702",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/ffa33043ea0ee67a4eed58535687f87311e4256b",
|
||||
"reference": "ffa33043ea0ee67a4eed58535687f87311e4256b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1410,20 +1410,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-09-17T08:30:15+00:00"
|
||||
"time": "2024-09-27T13:16:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/infolists",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/infolists.git",
|
||||
"reference": "dd6e2319aea92c5444c52792c750edfeb057f62a"
|
||||
"reference": "d4d3030644e3617aed252a5df3c385145ada0ec6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/dd6e2319aea92c5444c52792c750edfeb057f62a",
|
||||
"reference": "dd6e2319aea92c5444c52792c750edfeb057f62a",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/d4d3030644e3617aed252a5df3c385145ada0ec6",
|
||||
"reference": "d4d3030644e3617aed252a5df3c385145ada0ec6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1461,20 +1461,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-09-17T08:30:15+00:00"
|
||||
"time": "2024-09-27T13:16:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/notifications",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/notifications.git",
|
||||
"reference": "03ea56e0729c98c65831ab0215285a7cb1c4117f"
|
||||
"reference": "0272612e1d54e0520f8717b24c71b9b70f198c8f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/notifications/zipball/03ea56e0729c98c65831ab0215285a7cb1c4117f",
|
||||
"reference": "03ea56e0729c98c65831ab0215285a7cb1c4117f",
|
||||
"url": "https://api.github.com/repos/filamentphp/notifications/zipball/0272612e1d54e0520f8717b24c71b9b70f198c8f",
|
||||
"reference": "0272612e1d54e0520f8717b24c71b9b70f198c8f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1513,20 +1513,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-07-31T11:53:11+00:00"
|
||||
"time": "2024-09-27T13:16:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/support",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/support.git",
|
||||
"reference": "2183eb1149ef9ab742256155adf2afedda322e6d"
|
||||
"reference": "6dba51efd6f2a32db21bc8684cd663915ab0e4d7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/2183eb1149ef9ab742256155adf2afedda322e6d",
|
||||
"reference": "2183eb1149ef9ab742256155adf2afedda322e6d",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/6dba51efd6f2a32db21bc8684cd663915ab0e4d7",
|
||||
"reference": "6dba51efd6f2a32db21bc8684cd663915ab0e4d7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1572,20 +1572,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-09-23T14:10:13+00:00"
|
||||
"time": "2024-09-27T13:16:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/tables",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/tables.git",
|
||||
"reference": "75acf6f38a8ccfded57dc62bc3af0dd0bb04069d"
|
||||
"reference": "07226fcd080f0f547aac31cf5117bfab192ea770"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/75acf6f38a8ccfded57dc62bc3af0dd0bb04069d",
|
||||
"reference": "75acf6f38a8ccfded57dc62bc3af0dd0bb04069d",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/07226fcd080f0f547aac31cf5117bfab192ea770",
|
||||
"reference": "07226fcd080f0f547aac31cf5117bfab192ea770",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1624,11 +1624,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-09-17T08:30:46+00:00"
|
||||
"time": "2024-09-27T13:16:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/widgets",
|
||||
"version": "v3.2.114",
|
||||
"version": "v3.2.115",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/widgets.git",
|
||||
@ -2343,16 +2343,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v11.25.0",
|
||||
"version": "v11.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "b487a9089c0b1c71ac63bb6bc44fb4b00dc6da2e"
|
||||
"reference": "b8cb8998701d5b3cfe68539d3c3da1fc59ddd82b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/b487a9089c0b1c71ac63bb6bc44fb4b00dc6da2e",
|
||||
"reference": "b487a9089c0b1c71ac63bb6bc44fb4b00dc6da2e",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/b8cb8998701d5b3cfe68539d3c3da1fc59ddd82b",
|
||||
"reference": "b8cb8998701d5b3cfe68539d3c3da1fc59ddd82b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2371,7 +2371,7 @@
|
||||
"fruitcake/php-cors": "^1.3",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"guzzlehttp/uri-template": "^1.0",
|
||||
"laravel/prompts": "^0.1.18|^0.2.0",
|
||||
"laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
|
||||
"laravel/serializable-closure": "^1.3",
|
||||
"league/commonmark": "^2.2.1",
|
||||
"league/flysystem": "^3.8.0",
|
||||
@ -2548,7 +2548,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2024-09-26T11:21:58+00:00"
|
||||
"time": "2024-10-01T14:29:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
@ -3013,16 +3013,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "3.28.0",
|
||||
"version": "3.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c"
|
||||
"reference": "0adc0d9a51852e170e0028a60bd271726626d3f0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
|
||||
"reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0",
|
||||
"reference": "0adc0d9a51852e170e0028a60bd271726626d3f0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3090,22 +3090,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/flysystem/issues",
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/3.28.0"
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/3.29.0"
|
||||
},
|
||||
"time": "2024-05-22T10:09:12+00:00"
|
||||
"time": "2024-09-29T11:59:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-local",
|
||||
"version": "3.28.0",
|
||||
"version": "3.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem-local.git",
|
||||
"reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40"
|
||||
"reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
|
||||
"reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27",
|
||||
"reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3139,9 +3139,9 @@
|
||||
"local"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0"
|
||||
"source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0"
|
||||
},
|
||||
"time": "2024-05-06T20:05:52+00:00"
|
||||
"time": "2024-08-09T21:24:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/mime-type-detection",
|
||||
@ -3939,16 +3939,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.2.0",
|
||||
"version": "v5.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb"
|
||||
"reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
|
||||
"reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a",
|
||||
"reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3991,9 +3991,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0"
|
||||
},
|
||||
"time": "2024-09-15T16:40:33+00:00"
|
||||
"time": "2024-09-29T13:56:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/termwind",
|
||||
@ -8511,16 +8511,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sail.git",
|
||||
"reference": "d54af9d5745e3680d8a6463ffd9f314aa53eb2d1"
|
||||
"reference": "511e9c95b0f3ee778dc9e11e242bcd2af8e002cd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/d54af9d5745e3680d8a6463ffd9f314aa53eb2d1",
|
||||
"reference": "d54af9d5745e3680d8a6463ffd9f314aa53eb2d1",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/511e9c95b0f3ee778dc9e11e242bcd2af8e002cd",
|
||||
"reference": "511e9c95b0f3ee778dc9e11e242bcd2af8e002cd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8570,7 +8570,7 @@
|
||||
"issues": "https://github.com/laravel/sail/issues",
|
||||
"source": "https://github.com/laravel/sail"
|
||||
},
|
||||
"time": "2024-09-22T19:04:21+00:00"
|
||||
"time": "2024-09-27T14:58:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
|
@ -211,4 +211,5 @@
|
||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||
])->toArray(),
|
||||
|
||||
'version' => '2.0.0',
|
||||
];
|
||||
|
@ -172,6 +172,56 @@
|
||||
'vito-agent' => \App\SSH\Services\Monitoring\VitoAgent\VitoAgent::class,
|
||||
'remote-monitor' => \App\SSH\Services\Monitoring\RemoteMonitor\RemoteMonitor::class,
|
||||
],
|
||||
'service_versions' => [
|
||||
'nginx' => [
|
||||
'latest',
|
||||
],
|
||||
'mysql' => [
|
||||
'5.7',
|
||||
'8.0',
|
||||
],
|
||||
'mariadb' => [
|
||||
'10.3',
|
||||
'10.4',
|
||||
'10.6',
|
||||
'10.11',
|
||||
'11.4',
|
||||
],
|
||||
'postgresql' => [
|
||||
'12',
|
||||
'13',
|
||||
'14',
|
||||
'15',
|
||||
'16',
|
||||
],
|
||||
'redis' => [
|
||||
'latest',
|
||||
],
|
||||
'php' => [
|
||||
'5.6',
|
||||
'7.0',
|
||||
'7.1',
|
||||
'7.2',
|
||||
'7.3',
|
||||
'7.4',
|
||||
'8.0',
|
||||
'8.1',
|
||||
'8.2',
|
||||
'8.3',
|
||||
],
|
||||
'ufw' => [
|
||||
'latest',
|
||||
],
|
||||
'supervisor' => [
|
||||
'latest',
|
||||
],
|
||||
'vito-agent' => [
|
||||
'latest',
|
||||
],
|
||||
'remote-monitor' => [
|
||||
'latest',
|
||||
],
|
||||
],
|
||||
'service_units' => [
|
||||
'nginx' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
|
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@
|
||||
|
||||
filepond/dist/filepond.min.css:
|
||||
(*!
|
||||
* FilePond 4.31.3
|
||||
* FilePond 4.31.4
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*)
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -10,7 +10,20 @@ .choices__item--selectable {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
.fi-sidebar {
|
||||
@apply bg-gray-100/50 dark:bg-gray-900/50 !important;
|
||||
}
|
||||
|
||||
.fi-sidebar-item-active a {
|
||||
@apply bg-gray-100 dark:bg-gray-800/50 !important;
|
||||
}
|
||||
|
||||
.fi-btn-color-primary {
|
||||
background-image: linear-gradient(to bottom right, rgba(var(--primary-500), 1), rgba(var(--primary-900), 1));
|
||||
box-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
|
||||
}
|
||||
|
||||
.bg-primary-700-gradient {
|
||||
background-image: linear-gradient(to bottom right, rgba(var(--primary-300), 1), rgba(var(--primary-700), 1));
|
||||
box-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
|
||||
}
|
||||
|
5
resources/views/web/components/app-version.blade.php
Normal file
5
resources/views/web/components/app-version.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<div class="p-6 text-sm">
|
||||
<a href="https://github.com/vitodeploy/vito/releases/tag/{{ config("app.version") }}" target="_blank">
|
||||
V{{ config("app.version") }}
|
||||
</a>
|
||||
</div>
|
@ -1,3 +1,7 @@
|
||||
<div {{ $getExtraAttributeBag() }}>
|
||||
<div
|
||||
@if (isset($getExtraAttributeBag))
|
||||
{{ $getExtraAttributeBag() }}
|
||||
@endif
|
||||
>
|
||||
{!! $content !!}
|
||||
</div>
|
||||
|
@ -1,3 +1,6 @@
|
||||
<form>
|
||||
{{ $this->form }}
|
||||
</form>
|
||||
<div>
|
||||
<form wire:submit="submit">
|
||||
{{ $this->form }}
|
||||
</form>
|
||||
<x-filament-actions::modals />
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
<div>
|
||||
{{ $this->infolist }}
|
||||
<x-filament-actions::modals />
|
||||
</div>
|
||||
|
@ -1,11 +1,13 @@
|
||||
<div {{ $this->getExtraAttributesBag() }}>
|
||||
<x-filament-panels::page>
|
||||
@if (method_exists($this, "getSecondSubNavigation"))
|
||||
<x-filament-panels::page.sub-navigation.tabs :navigation="$this->getSecondSubNavigation()" />
|
||||
<x-filament-panels::page.sub-navigation.tabs class="!flex" :navigation="$this->getSecondSubNavigation()" />
|
||||
@endif
|
||||
|
||||
@foreach ($this->getWidgets() as $key => $widget)
|
||||
@livewire($widget[0], $widget[1] ?? [], key(class_basename($widget[0]) . "-" . $key))
|
||||
@endforeach
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</x-filament-panels::page>
|
||||
</div>
|
||||
|
53
resources/views/web/widgets/select-project.blade.php
Normal file
53
resources/views/web/widgets/select-project.blade.php
Normal file
@ -0,0 +1,53 @@
|
||||
<x-filament::dropdown placement="bottom-start" :size="true" :teleport="true" class="pointer-choices -mx-2">
|
||||
<x-slot name="trigger">
|
||||
<button
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-data="{ tooltip: false }"
|
||||
x-effect="
|
||||
tooltip = $store.sidebar.isOpen
|
||||
? false
|
||||
: {
|
||||
content: @js($currentProject->name),
|
||||
placement: document.dir === 'rtl' ? 'left' : 'right',
|
||||
theme: $store.theme,
|
||||
}
|
||||
"
|
||||
x-tooltip.html="tooltip"
|
||||
@endif
|
||||
type="button"
|
||||
class="fi-tenant-menu-trigger group flex w-full items-center justify-center gap-x-3 rounded-lg p-2 text-sm font-medium outline-none transition duration-75 hover:bg-gray-100 focus-visible:bg-gray-100 dark:hover:bg-white/5 dark:focus-visible:bg-white/5"
|
||||
>
|
||||
<div
|
||||
class="bg-primary-700-gradient text-md flex size-8 items-center justify-center rounded-lg capitalize text-white"
|
||||
>
|
||||
{{ $currentProject->name[0] }}
|
||||
</div>
|
||||
|
||||
<span
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-show="$store.sidebar.isOpen"
|
||||
@endif
|
||||
class="grid justify-items-start text-start"
|
||||
>
|
||||
<span class="text-gray-950 dark:text-white">
|
||||
{{ $currentProject->name }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<x-filament::icon
|
||||
icon="heroicon-m-chevron-down"
|
||||
icon-alias="panels::tenant-menu.toggle-button"
|
||||
:x-show="filament()->isSidebarCollapsibleOnDesktop() ? '$store.sidebar.isOpen' : null"
|
||||
class="ms-auto h-5 w-5 shrink-0 text-gray-400 transition duration-75 group-hover:text-gray-500 group-focus-visible:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-400 dark:group-focus-visible:text-gray-400"
|
||||
/>
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
@foreach ($projects as $project)
|
||||
<x-filament::dropdown.list.item wire:click="updateProject({{ $project }})" class="cursor-pointer" tag="a">
|
||||
{{ $project->name }}
|
||||
</x-filament::dropdown.list.item>
|
||||
@endforeach
|
||||
</x-filament::dropdown.list>
|
||||
</x-filament::dropdown>
|
Loading…
x
Reference in New Issue
Block a user