Compare commits
9 Commits
v0.9-rc54
...
c1599f5dd1
| Author | SHA1 | Date | |
|---|---|---|---|
| c1599f5dd1 | |||
| 6064de6c81 | |||
| f7902105de | |||
| b049d338d8 | |||
| 8573d96719 | |||
| 800e07d2ba | |||
| c6f5b6cab8 | |||
|
|
8acc55a799 | ||
|
|
a5742a3156 |
8
AGENT.md
8
AGENT.md
@@ -620,7 +620,15 @@ All administrative actions are logged to the `audit_logs` table.
|
||||
- **USE Tailwind classes** - Only when absolutely necessary for minor adjustments
|
||||
- **MUST be responsive** - All pages must work on mobile, tablet, and desktop
|
||||
|
||||
### Warning Banners
|
||||
|
||||
- Use Filament `Section::make()` for warning banners (no raw HTML).
|
||||
- Always set `->icon('heroicon-o-exclamation-triangle')` and `->iconColor('warning')`.
|
||||
- Keep banners non-collapsible: `->collapsed(false)->collapsible(false)`.
|
||||
- Put the full message in `->description()` and keep the heading short.
|
||||
|
||||
### Allowed Components
|
||||
|
||||
Use these Filament native components exclusively:
|
||||
|
||||
| Category | Components |
|
||||
|
||||
@@ -16,7 +16,9 @@ use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\EmbeddedTable;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Text;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
@@ -50,6 +52,13 @@ class Dashboard extends Page implements HasActions, HasForms
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (! DnsSetting::get('onboarding_completed', false)) {
|
||||
$this->defaultAction = 'onboarding';
|
||||
}
|
||||
}
|
||||
|
||||
protected function getForms(): array
|
||||
{
|
||||
return [
|
||||
@@ -78,21 +87,63 @@ class Dashboard extends Page implements HasActions, HasForms
|
||||
->color('gray')
|
||||
->action(fn () => $this->redirect(request()->url())),
|
||||
|
||||
Action::make('onboarding')
|
||||
Action::make('onboarding')->modalCancelActionLabel('Maybe later')
|
||||
->label(__('Setup Wizard'))
|
||||
->icon('heroicon-o-sparkles')
|
||||
->visible(fn () => ! DnsSetting::get('onboarding_completed', false))
|
||||
|
||||
->modalHeading(__('Welcome to Jabali!'))
|
||||
->modalDescription(__('Let\'s get your server control panel set up.'))
|
||||
->modalWidth('md')
|
||||
->modalWidth('2xl')
|
||||
->form([
|
||||
Section::make(__('Next Steps'))
|
||||
->description(__('Here is a quick setup path to get your first site online.'))
|
||||
->icon('heroicon-o-check-circle')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact()
|
||||
->schema([
|
||||
Grid::make(['default' => 1, 'md' => 2])
|
||||
->schema([
|
||||
Section::make(__('1. Configure Server Settings'))
|
||||
->description(__('Set hostname, DNS, email, storage, and PHP defaults.'))
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('2. Create a Hosting Package'))
|
||||
->description(__('Define limits and features for your plans.'))
|
||||
->icon('heroicon-o-cube')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('3. Create a User'))
|
||||
->description(__('Assign the hosting package and set credentials.'))
|
||||
->icon('heroicon-o-user-plus')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('4. Add a Domain'))
|
||||
->description(__('Issue SSL and deploy your site files.'))
|
||||
->icon('heroicon-o-globe-alt')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
]),
|
||||
Text::make(__('Optional: review Services and Server Status to confirm everything is healthy.'))
|
||||
->color('gray'),
|
||||
]),
|
||||
TextInput::make('admin_email')
|
||||
->label(__('Your Email Address'))
|
||||
->helperText(__('Enter your email to receive important server notifications.'))
|
||||
->email()
|
||||
->placeholder(__('admin@example.com')),
|
||||
])
|
||||
->modalSubmitActionLabel(__('Get Started'))
|
||||
->modalSubmitActionLabel(__("Don't show again"))
|
||||
->action(function (array $data): void {
|
||||
if (! empty($data['admin_email'])) {
|
||||
DnsSetting::set('admin_email_recipients', $data['admin_email']);
|
||||
|
||||
@@ -575,7 +575,9 @@ class Security extends Page implements HasActions, HasForms, HasTable
|
||||
->visible(fn () => $this->fail2banRunning)
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('Disable Fail2ban'))
|
||||
->modalDescription(__('Fail2ban will be stopped and disabled. You can re-enable it later from this tab.'))
|
||||
->modalIcon('heroicon-o-exclamation-triangle')
|
||||
->modalIconColor('warning')
|
||||
->modalDescription(__('Warning: Fail2ban will be stopped and disabled. You can re-enable it later from this tab.'))
|
||||
->action('disableFail2ban'),
|
||||
])
|
||||
->schema([
|
||||
@@ -665,9 +667,12 @@ class Security extends Page implements HasActions, HasForms, HasTable
|
||||
->color(fn () => $this->clamavRunning ? 'warning' : 'success')
|
||||
->size('sm')
|
||||
->action(fn () => $this->clamavRunning ? $this->disableClamav() : $this->enableClamav())
|
||||
->requiresConfirmation(fn () => ! $this->clamavRunning)
|
||||
->requiresConfirmation()
|
||||
->modalHeading(fn () => $this->clamavRunning ? __('Disable ClamAV') : __('Enable ClamAV'))
|
||||
->modalIcon('heroicon-o-exclamation-triangle')
|
||||
->modalIconColor('warning')
|
||||
->modalDescription(fn () => $this->clamavRunning
|
||||
? __('ClamAV will be stopped and disabled. You can re-enable it later.')
|
||||
? __('Warning: This will stop and disable ClamAV. You can re-enable it later.')
|
||||
: __('Starting ClamAV daemon uses ~500MB RAM. Continue?')),
|
||||
FormAction::make('updateSignatures')
|
||||
->label(__('Update Signatures'))
|
||||
|
||||
@@ -692,17 +692,17 @@ class ServerSettings extends Page implements HasActions, HasForms
|
||||
protected function databaseTabContent(): array
|
||||
{
|
||||
return [
|
||||
Section::make(__('Warning: Changing database settings can impact performance or cause outages'))
|
||||
->description(__('Apply changes only if you understand their effects, and prefer doing so during maintenance windows.'))
|
||||
->icon('heroicon-o-exclamation-triangle')
|
||||
->iconColor('warning')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('Database Tuning'))
|
||||
->description(__('Adjust MariaDB/MySQL global variables.'))
|
||||
->icon('heroicon-o-circle-stack')
|
||||
->schema([
|
||||
Placeholder::make('database_tuning_warning')
|
||||
->content(new HtmlString(
|
||||
'<div class="rounded-lg bg-warning-500/10 p-4 text-sm text-warning-700 dark:text-warning-400">'.
|
||||
'<strong>'.__('Warning:').'</strong> '.
|
||||
__('Changing database settings can impact performance or cause outages. Apply changes only if you understand their effects, and prefer doing so during maintenance windows.').
|
||||
'</div>'
|
||||
)),
|
||||
EmbeddedTable::make(DatabaseTuningTable::class),
|
||||
]),
|
||||
];
|
||||
|
||||
@@ -208,7 +208,9 @@ class Services extends Page implements HasActions, HasForms, HasTable
|
||||
->visible(fn (array $record): bool => $record['is_active'])
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('Stop Service'))
|
||||
->modalDescription(fn (array $record): string => __('Are you sure you want to stop :service? This may affect running websites and services.', ['service' => $record['name']]))
|
||||
->modalIcon('heroicon-o-exclamation-triangle')
|
||||
->modalIconColor('warning')
|
||||
->modalDescription(fn (array $record): string => __('Warning: This will stop :service and may affect running websites and services. Are you sure you want to continue?', ['service' => $record['name']]))
|
||||
->modalSubmitActionLabel(__('Stop Service'))
|
||||
->action(fn (array $record) => $this->executeServiceAction($record['service'], 'stop')),
|
||||
Action::make('restart')
|
||||
@@ -236,7 +238,9 @@ class Services extends Page implements HasActions, HasForms, HasTable
|
||||
->visible(fn (array $record): bool => $record['is_enabled'])
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('Disable Service'))
|
||||
->modalDescription(fn (array $record): string => __("Are you sure you want to disable :service? It won't start automatically on boot.", ['service' => $record['name']]))
|
||||
->modalIcon('heroicon-o-exclamation-triangle')
|
||||
->modalIconColor('warning')
|
||||
->modalDescription(fn (array $record): string => __('Warning: This will disable :service and it will not start automatically on boot. Are you sure you want to continue?', ['service' => $record['name']]))
|
||||
->modalSubmitActionLabel(__('Disable Service'))
|
||||
->action(fn (array $record) => $this->executeServiceAction($record['service'], 'disable')),
|
||||
])
|
||||
|
||||
30
app/Filament/Admin/Pages/Support.php
Normal file
30
app/Filament/Admin/Pages/Support.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
class Support extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle';
|
||||
|
||||
protected static ?int $navigationSort = 23;
|
||||
|
||||
protected static ?string $slug = 'support';
|
||||
|
||||
protected string $view = 'filament.admin.pages.support';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Support');
|
||||
}
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return __('Support');
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,13 @@ use App\Filament\Jabali\Widgets\DomainsWidget;
|
||||
use App\Filament\Jabali\Widgets\MailboxesWidget;
|
||||
use App\Filament\Jabali\Widgets\RecentBackupsWidget;
|
||||
use App\Filament\Jabali\Widgets\StatsOverview;
|
||||
use App\Models\DnsSetting;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Pages\Dashboard as BaseDashboard;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Text;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Dashboard extends BaseDashboard
|
||||
@@ -41,6 +46,75 @@ class Dashboard extends BaseDashboard
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (! DnsSetting::get('user_onboarding_completed_'.(string) Auth::id(), false)) {
|
||||
$this->defaultAction = 'onboarding';
|
||||
}
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('onboarding')->modalCancelActionLabel('Maybe later')
|
||||
->label(__('Setup Wizard'))
|
||||
->icon('heroicon-o-sparkles')
|
||||
|
||||
->modalHeading(__('Welcome to Jabali!'))
|
||||
->modalDescription(__('Here is a quick path to launch your first site.'))
|
||||
->modalWidth('2xl')
|
||||
->form([
|
||||
Section::make(__('Next Steps'))
|
||||
->description(__('Follow these steps to get online quickly.'))
|
||||
->icon('heroicon-o-check-circle')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact()
|
||||
->schema([
|
||||
Grid::make(['default' => 1, 'md' => 2])
|
||||
->schema([
|
||||
Section::make(__('1. Add a Domain'))
|
||||
->description(__('Point your DNS to this server or update nameservers.'))
|
||||
->icon('heroicon-o-globe-alt')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('2. Issue SSL'))
|
||||
->description(__('Enable HTTPS for your site with SSL certificates.'))
|
||||
->icon('heroicon-o-lock-closed')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('3. Upload or Install'))
|
||||
->description(__('Upload files or install WordPress to deploy your site.'))
|
||||
->icon('heroicon-o-arrow-up-tray')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
Section::make(__('4. Create Email & Databases'))
|
||||
->description(__('Set up mailboxes and databases for your app.'))
|
||||
->icon('heroicon-o-envelope')
|
||||
->iconColor('info')
|
||||
->collapsed(false)
|
||||
->collapsible(false)
|
||||
->compact(),
|
||||
]),
|
||||
Text::make(__('Optional: configure backups, cron jobs, and SSH keys for day-to-day operations.'))
|
||||
->color('gray'),
|
||||
]),
|
||||
])
|
||||
->modalSubmitActionLabel(__("Don't show again"))
|
||||
->action(function (): void {
|
||||
DnsSetting::set('user_onboarding_completed_'.(string) Auth::id(), '1');
|
||||
DnsSetting::clearCache();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function getSubheading(): ?string
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
@@ -32,6 +32,7 @@ use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\View;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@@ -1017,21 +1018,26 @@ class Email extends Page implements HasActions, HasForms, HasTable
|
||||
->label(__('Domain'))
|
||||
->options(fn () => Domain::where('user_id', Auth::id())->pluck('domain', 'id')->toArray())
|
||||
->required()
|
||||
->searchable(),
|
||||
->searchable()
|
||||
->live()
|
||||
->live(),
|
||||
TextInput::make('local_part')
|
||||
->label(__('Email Address'))
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->regex('/^[a-zA-Z0-9._%+-]+$/')
|
||||
->maxLength(64)
|
||||
->helperText(__('The part before the @ symbol')),
|
||||
TextInput::make('name')
|
||||
->label(__('Display Name'))
|
||||
->visible(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->label(__('Password'))
|
||||
->password()
|
||||
->revealable()
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->minLength(8)
|
||||
->rules([
|
||||
'regex:/[a-z]/', // lowercase
|
||||
@@ -1063,6 +1069,7 @@ class Email extends Page implements HasActions, HasForms, HasTable
|
||||
TextInput::make('quota_mb')
|
||||
->label(__('Quota (MB)'))
|
||||
->numeric()
|
||||
->visible(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->default(1024)
|
||||
->minValue(100)
|
||||
->maxValue(10240)
|
||||
@@ -1236,16 +1243,19 @@ class Email extends Page implements HasActions, HasForms, HasTable
|
||||
->label(__('Domain'))
|
||||
->options(fn () => Domain::where('user_id', Auth::id())->pluck('domain', 'id')->toArray())
|
||||
->required()
|
||||
->searchable(),
|
||||
->searchable()
|
||||
->live(),
|
||||
TextInput::make('local_part')
|
||||
->label(__('Email Address'))
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->regex('/^[a-zA-Z0-9._%+-]+$/')
|
||||
->maxLength(64)
|
||||
->helperText(__('The part before the @ symbol')),
|
||||
TextInput::make('destinations')
|
||||
->label(__('Forward To'))
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain_id')))
|
||||
->helperText(__('Comma-separated email addresses to forward to')),
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
|
||||
@@ -60,7 +60,7 @@ class Files extends Page implements HasActions, HasForms, HasTable
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return 'File Manager';
|
||||
return __('File Manager');
|
||||
}
|
||||
|
||||
public function getAgent(): AgentClient
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Jabali\Pages;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use App\Filament\Jabali\Widgets\ActivityLogTable;
|
||||
use App\Services\Agent\AgentClient;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
@@ -16,6 +16,7 @@ use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\EmbeddedTable;
|
||||
use Filament\Schemas\Components\View;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
@@ -119,7 +120,7 @@ class Logs extends Page implements HasActions, HasForms
|
||||
'activity' => Tab::make(__('Activity Log'))
|
||||
->icon('heroicon-o-clipboard-document-list')
|
||||
->schema([
|
||||
View::make('filament.jabali.pages.logs-tab-activity'),
|
||||
EmbeddedTable::make(ActivityLogTable::class),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -227,15 +228,6 @@ class Logs extends Page implements HasActions, HasForms
|
||||
->send();
|
||||
}
|
||||
|
||||
public function getActivityLogs()
|
||||
{
|
||||
return AuditLog::query()
|
||||
->where('user_id', Auth::id())
|
||||
->latest()
|
||||
->limit(50)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function generateStats(): void
|
||||
{
|
||||
if (! $this->selectedDomain) {
|
||||
|
||||
30
app/Filament/Jabali/Pages/Support.php
Normal file
30
app/Filament/Jabali/Pages/Support.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Jabali\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
class Support extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle';
|
||||
|
||||
protected static ?int $navigationSort = 23;
|
||||
|
||||
protected static ?string $slug = 'support';
|
||||
|
||||
protected string $view = 'filament.jabali.pages.support';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Support');
|
||||
}
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return __('Support');
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ViewColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
@@ -448,24 +449,29 @@ class WordPress extends Page implements HasActions, HasForms, HasTable
|
||||
->options($domainOptions)
|
||||
->required()
|
||||
->searchable()
|
||||
->live()
|
||||
->placeholder(__('Select a domain...'))
|
||||
->helperText(__('The domain where WordPress will be installed')),
|
||||
Toggle::make('use_www')
|
||||
->label(__('Use www prefix'))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->helperText(__('Install on www.domain.com instead of domain.com'))
|
||||
->default(false),
|
||||
TextInput::make('path')
|
||||
->label(__('Directory (optional)'))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->placeholder(__('Leave empty to install in root'))
|
||||
->helperText(__('e.g., "blog" to install at domain.com/blog')),
|
||||
TextInput::make('site_title')
|
||||
->label(__('Site Title'))
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->default(__('My WordPress Site'))
|
||||
->helperText(__('The name of your WordPress site')),
|
||||
TextInput::make('admin_user')
|
||||
->label(__('Admin Username'))
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->default('admin')
|
||||
->alphaNum()
|
||||
->helperText(__('Username for the WordPress admin account')),
|
||||
@@ -473,7 +479,8 @@ class WordPress extends Page implements HasActions, HasForms, HasTable
|
||||
->label(__('Admin Password'))
|
||||
->password()
|
||||
->revealable()
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->default(fn () => $this->generateSecurePassword())
|
||||
->minLength(8)
|
||||
->rules([
|
||||
@@ -504,7 +511,8 @@ class WordPress extends Page implements HasActions, HasForms, HasTable
|
||||
->helperText(__('Minimum 8 characters with uppercase, lowercase, and numbers')),
|
||||
TextInput::make('admin_email')
|
||||
->label(__('Admin Email'))
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->email()
|
||||
->default(Auth::user()->email ?? '')
|
||||
->helperText(__('Email address for the WordPress admin account')),
|
||||
@@ -538,14 +546,17 @@ class WordPress extends Page implements HasActions, HasForms, HasTable
|
||||
])
|
||||
->default('en_US')
|
||||
->searchable()
|
||||
->required()
|
||||
->required(fn (Get $get): bool => filled($get('domain')))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->helperText(__('Default language for WordPress admin and content')),
|
||||
Toggle::make('enable_cache')
|
||||
->label(__('Enable Jabali Cache'))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->helperText(__('Install Redis object caching for better performance'))
|
||||
->default(true),
|
||||
Toggle::make('enable_auto_update')
|
||||
->label(__('Enable Auto-Updates'))
|
||||
->visible(fn (Get $get): bool => filled($get('domain')))
|
||||
->helperText(__('Automatically update WordPress, plugins, and themes'))
|
||||
->default(false),
|
||||
])
|
||||
|
||||
79
app/Filament/Jabali/Widgets/ActivityLogTable.php
Normal file
79
app/Filament/Jabali/Widgets/ActivityLogTable.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Jabali\Widgets;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class ActivityLogTable extends Component implements HasTable, HasSchemas, HasActions
|
||||
{
|
||||
use InteractsWithTable;
|
||||
use InteractsWithSchemas;
|
||||
use InteractsWithActions;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
AuditLog::query()
|
||||
->where('user_id', Auth::id())
|
||||
->latest()
|
||||
)
|
||||
->columns([
|
||||
TextColumn::make('created_at')
|
||||
->label(__('Time'))
|
||||
->dateTime('M d, H:i')
|
||||
->color('gray'),
|
||||
TextColumn::make('category')
|
||||
->label(__('Category'))
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'domain' => 'info',
|
||||
'email' => 'primary',
|
||||
'database' => 'warning',
|
||||
'auth' => 'gray',
|
||||
'firewall' => 'danger',
|
||||
'service' => 'success',
|
||||
default => 'gray',
|
||||
}),
|
||||
TextColumn::make('action')
|
||||
->label(__('Action'))
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'create', 'created' => 'success',
|
||||
'update', 'updated' => 'warning',
|
||||
'delete', 'deleted' => 'danger',
|
||||
'login' => 'info',
|
||||
default => 'gray',
|
||||
}),
|
||||
TextColumn::make('description')
|
||||
->label(__('Description'))
|
||||
->limit(60)
|
||||
->wrap(),
|
||||
TextColumn::make('ip_address')
|
||||
->label(__('IP'))
|
||||
->color('gray'),
|
||||
])
|
||||
->defaultPaginationPageOption(25)
|
||||
->striped()
|
||||
->emptyStateHeading(__('No activity recorded yet'))
|
||||
->emptyStateDescription(__('Recent actions performed in your account will appear here.'))
|
||||
->emptyStateIcon('heroicon-o-clipboard-document-list');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return $this->getTable()->render();
|
||||
}
|
||||
}
|
||||
@@ -185,3 +185,5 @@ class BackupSchedule extends Model
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"filament/blueprint": "^2.0",
|
||||
"filament/blueprint": "^2.1",
|
||||
"laravel/boost": "*",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
|
||||
8
composer.lock
generated
8
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "194d87cc129a30c6e832109fb820097a",
|
||||
"content-hash": "7083b0b087c4b503b50d3aa23cfbbfac",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -8817,14 +8817,14 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/blueprint",
|
||||
"version": "v2.0.1",
|
||||
"version": "v2.1.0",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://packages.filamentphp.com/composer/10/127/download"
|
||||
"url": "https://packages.filamentphp.com/composer/10/473/download"
|
||||
},
|
||||
"require": {
|
||||
"filament/support": "^5.0",
|
||||
"laravel/boost": "^1.8"
|
||||
"laravel/boost": "^1.8|^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"license": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Jabali Documentation Index
|
||||
|
||||
Last updated: 2026-02-06
|
||||
Last updated: 2026-02-09
|
||||
|
||||
## Top-Level Docs
|
||||
- /var/www/jabali/README.md - Product overview, features, install, upgrade, and architecture summary.
|
||||
@@ -11,7 +11,7 @@ Last updated: 2026-02-06
|
||||
- /var/www/jabali/TODO.md - Active checklist items.
|
||||
|
||||
## Docs Folder
|
||||
- /var/www/jabali/docs/installation.md - Debian package install path and Filament notifications patch.
|
||||
- /var/www/jabali/docs/installation.md - Debian package install path, Filament notifications patch, and deploy script usage.
|
||||
- /var/www/jabali/docs/architecture/control-panel-blueprint.md - High-level blueprint for a hosting panel.
|
||||
- /var/www/jabali/docs/archive-notes.md - Archived files and restore notes.
|
||||
- /var/www/jabali/docs/screenshots/README.md - Screenshot generation instructions.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Documentation Summary (Jabali Panel)
|
||||
|
||||
Last updated: 2026-02-06
|
||||
Last updated: 2026-02-09
|
||||
|
||||
## Product Overview
|
||||
Jabali Panel is a modern web hosting control panel for WordPress and general PHP hosting. It provides an admin panel for server-wide operations and a user panel for per-tenant management. The core goals are safe automation, clean multi-tenant isolation, and operational clarity.
|
||||
@@ -41,6 +41,7 @@ DNSSEC can be enabled per domain and generates KSK/ZSK keys, DS records, and sig
|
||||
- Target OS: Fresh Debian 12/13 install with no pre-existing web/mail stack.
|
||||
- Installer: install.sh, builds assets as www-data and ensures permissions.
|
||||
- Upgrade: php artisan jabali:upgrade manages dependencies, caches, and permissions for public/build and node_modules.
|
||||
- Deploy helper: scripts/deploy.sh syncs code to a server, runs composer/npm, migrations, and caches, and can push to Gitea/GitHub with automatic VERSION bump.
|
||||
|
||||
## Packaging
|
||||
Debian packaging is supported via scripts:
|
||||
|
||||
@@ -110,6 +110,51 @@ What is included:
|
||||
If you update or rebuild assets, keep the guard in place and hard‑refresh the
|
||||
browser (Ctrl+Shift+R) after deployment.
|
||||
|
||||
## Deploy script
|
||||
|
||||
The repository ships with a deploy helper at `scripts/deploy.sh`. It syncs the
|
||||
project to a remote server over SSH, then runs composer/npm, migrations, and
|
||||
cache rebuilds as the web user.
|
||||
|
||||
Defaults (override via flags or env vars):
|
||||
- Host: `192.168.100.50`
|
||||
- User: `root`
|
||||
- Path: `/var/www/jabali`
|
||||
- Web user: `www-data`
|
||||
|
||||
Common usage:
|
||||
```
|
||||
# Basic deploy to the default host
|
||||
scripts/deploy.sh
|
||||
|
||||
# Target a different host/path/user
|
||||
scripts/deploy.sh --host 192.168.100.50 --user root --path /var/www/jabali --www-user www-data
|
||||
|
||||
# Dry-run rsync only
|
||||
scripts/deploy.sh --dry-run
|
||||
|
||||
# Skip npm build and cache steps
|
||||
scripts/deploy.sh --skip-npm --skip-cache
|
||||
```
|
||||
|
||||
Push to Git remotes (optional):
|
||||
```
|
||||
# Push to Gitea and/or GitHub before deploying
|
||||
scripts/deploy.sh --push-gitea --push-github
|
||||
|
||||
# Push to explicit URLs
|
||||
scripts/deploy.sh --push-gitea --gitea-url http://192.168.100.100:3001/shukivaknin/jabali-panel.git \
|
||||
--push-github --github-url git@github.com:shukiv/jabali-panel.git
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--push-gitea` / `--push-github` require a clean worktree.
|
||||
- When pushing, the script bumps `VERSION`, updates the `install.sh` fallback,
|
||||
and commits the version bump before pushing.
|
||||
- Rsync excludes `.env`, `storage/`, `vendor/`, `node_modules/`, `public/build/`,
|
||||
`bootstrap/cache/`, and SQLite DB files. Handle those separately if needed.
|
||||
- `--delete` passes `--delete` to rsync (dangerous).
|
||||
|
||||
## Testing after changes
|
||||
|
||||
After every change, run a test to make sure there are no errors.
|
||||
|
||||
@@ -16,7 +16,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||
JABALI_VERSION="$(sed -n 's/^VERSION=//p' "$SCRIPT_DIR/VERSION")"
|
||||
fi
|
||||
JABALI_VERSION="${JABALI_VERSION:-0.9-rc54}"
|
||||
JABALI_VERSION="${JABALI_VERSION:-0.9-rc59}"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
|
||||
16
lang/ar.json
16
lang/ar.json
@@ -219,6 +219,20 @@
|
||||
"Disk Usage": "استخدام القرص",
|
||||
"Display Name": "الاسم المعروض",
|
||||
"Document Root": "المجلد الجذر",
|
||||
"Documentation": "التوثيق",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "اعثر على إجابات في التوثيق أو تحدث مع روبوت الدعم المدرَّب لدينا. اطّلع على أدلة الإعداد وخطوات استكشاف الأخطاء وأفضل الممارسات.",
|
||||
"Chat with our AI support bot.": "تحدث مع روبوت الدعم بالذكاء الاصطناعي.",
|
||||
"GitHub Issues": "مشكلات GitHub",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "أبلغ عن الأخطاء أو اطلب الميزات. أرفق الخطوات والسجلات ولقطات الشاشة لنتمكن من إعادة الإنتاج بسرعة.",
|
||||
"Open GitHub Issues": "فتح مشكلات GitHub",
|
||||
"Paid Support": "دعم مدفوع",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "احصل على مساعدة احترافية للترحيل وتحسين الأداء والإصلاحات ذات الأولوية. تتضمن الخطط الإعداد والدعم المخصص.",
|
||||
"View Support Plans": "عرض خطط الدعم",
|
||||
"Response Time": "وقت الاستجابة",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "نستجيب عادة خلال 4-8 ساعات. للحوادث الحرجة، استخدم دعم الطوارئ للحصول على استجابة أسرع.",
|
||||
"Emergency Support": "دعم الطوارئ",
|
||||
"Open Documentation": "فتح التوثيق",
|
||||
"Support Chat": "دردشة الدعم",
|
||||
"Domain": "نطاق",
|
||||
"Domain Name": "اسم النطاق",
|
||||
"Domain Verification Code (optional)": "رمز التحقق من النطاق (اختياري)",
|
||||
@@ -879,4 +893,4 @@
|
||||
"results": "نتائج",
|
||||
"selected": "محدد",
|
||||
"to": "إلى"
|
||||
}
|
||||
}
|
||||
|
||||
13
lang/en.json
13
lang/en.json
@@ -782,6 +782,19 @@
|
||||
"Display Name": "Display Name",
|
||||
"Document Root": "Document Root",
|
||||
"Documentation": "Documentation",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.",
|
||||
"Chat with our AI support bot.": "Chat with our AI support bot.",
|
||||
"GitHub Issues": "GitHub Issues",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.",
|
||||
"Open GitHub Issues": "Open GitHub Issues",
|
||||
"Paid Support": "Paid Support",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.",
|
||||
"View Support Plans": "View Support Plans",
|
||||
"Response Time": "Response Time",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.",
|
||||
"Emergency Support": "Emergency Support",
|
||||
"Open Documentation": "Open Documentation",
|
||||
"Support Chat": "Support Chat",
|
||||
"Domain": "Domain",
|
||||
"Domain Aliases": "Domain Aliases",
|
||||
"Domain Certificates": "Domain Certificates",
|
||||
|
||||
16
lang/es.json
16
lang/es.json
@@ -308,6 +308,20 @@
|
||||
"Disk Usage": "Uso de disco",
|
||||
"Display Name": "Nombre para mostrar",
|
||||
"Document Root": "Raíz del documento",
|
||||
"Documentation": "Documentación",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "Encuentra respuestas en nuestra documentación o habla con nuestro bot de soporte entrenado. Explora guías de configuración, pasos de solución de problemas y buenas prácticas.",
|
||||
"Chat with our AI support bot.": "Chatea con nuestro bot de soporte con IA.",
|
||||
"GitHub Issues": "Issues de GitHub",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "Reporta errores o solicita funciones. Incluye pasos, registros y capturas de pantalla para que podamos reproducirlo rápido.",
|
||||
"Open GitHub Issues": "Abrir issues de GitHub",
|
||||
"Paid Support": "Soporte de pago",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "Obtén asistencia profesional para migraciones, ajustes de rendimiento y correcciones prioritarias. Los planes incluyen incorporación y soporte dedicado.",
|
||||
"View Support Plans": "Ver planes de soporte",
|
||||
"Response Time": "Tiempo de respuesta",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "Normalmente respondemos en 4-8 horas. Para incidentes críticos, usa el Soporte de Emergencia para una respuesta más rápida.",
|
||||
"Emergency Support": "Soporte de emergencia",
|
||||
"Open Documentation": "Abrir documentación",
|
||||
"Support Chat": "Chat de soporte",
|
||||
"Domain": "Dominio",
|
||||
"Domain Configuration": "Configuración de dominio",
|
||||
"Domain Count": "Cantidad de dominios",
|
||||
@@ -1170,4 +1184,4 @@
|
||||
"results": "resultados",
|
||||
"selected": "seleccionado(s)",
|
||||
"to": "a"
|
||||
}
|
||||
}
|
||||
|
||||
16
lang/fr.json
16
lang/fr.json
@@ -220,6 +220,20 @@
|
||||
"Disk Usage": "Utilisation du disque",
|
||||
"Display Name": "Nom d'affichage",
|
||||
"Document Root": "Racine du document",
|
||||
"Documentation": "Documentation",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "Trouvez des réponses dans notre documentation ou discutez avec notre bot d'assistance entraîné. Explorez les guides de configuration, les étapes de dépannage et les bonnes pratiques.",
|
||||
"Chat with our AI support bot.": "Discutez avec notre bot de support IA.",
|
||||
"GitHub Issues": "Issues GitHub",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "Signalez des bugs ou demandez des fonctionnalités. Incluez les étapes, les journaux et des captures d'écran pour une reproduction rapide.",
|
||||
"Open GitHub Issues": "Ouvrir les issues GitHub",
|
||||
"Paid Support": "Support payant",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "Obtenez une assistance professionnelle pour les migrations, l'optimisation des performances et les correctifs prioritaires. Les plans incluent l'onboarding et un support dédié.",
|
||||
"View Support Plans": "Voir les plans de support",
|
||||
"Response Time": "Délai de réponse",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "Nous répondons généralement sous 4-8 heures. Pour les incidents critiques, utilisez le support d'urgence pour une réponse plus rapide.",
|
||||
"Emergency Support": "Support d'urgence",
|
||||
"Open Documentation": "Ouvrir la documentation",
|
||||
"Support Chat": "Chat de support",
|
||||
"Domain": "Domaine",
|
||||
"Domain Name": "Nom de domaine",
|
||||
"Domain Verification Code (optional)": "Code de verification du domaine (optionnel)",
|
||||
@@ -886,4 +900,4 @@
|
||||
"results": "resultats",
|
||||
"selected": "selectionne(s)",
|
||||
"to": "a"
|
||||
}
|
||||
}
|
||||
|
||||
16
lang/he.json
16
lang/he.json
@@ -219,6 +219,20 @@
|
||||
"Disk Usage": "שימוש בדיסק",
|
||||
"Display Name": "שם תצוגה",
|
||||
"Document Root": "תיקיית שורש",
|
||||
"Documentation": "תיעוד",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "מצאו תשובות בתיעוד שלנו או דברו עם בוט התמיכה המאומן שלנו. עיינו במדריכי התקנה, שלבי פתרון תקלות ושיטות עבודה מומלצות.",
|
||||
"Chat with our AI support bot.": "שוחחו עם בוט התמיכה שלנו.",
|
||||
"GitHub Issues": "Issues של GitHub",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "דווחו על באגים או בקשו פיצ'רים. צרפו שלבים, לוגים וצילומי מסך כדי שנוכל לשחזר במהירות.",
|
||||
"Open GitHub Issues": "פתח Issues ב-GitHub",
|
||||
"Paid Support": "תמיכה בתשלום",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "קבלו סיוע מקצועי במיגרציות, שיפור ביצועים ותיקונים בעדיפות גבוהה. התוכניות כוללות קליטה ותמיכה ייעודית.",
|
||||
"View Support Plans": "צפו בתוכניות התמיכה",
|
||||
"Response Time": "זמן תגובה",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "אנחנו בדרך כלל מגיבים תוך 4-8 שעות. לאירועים קריטיים, השתמשו בתמיכת חירום לקבלת מענה מהיר יותר.",
|
||||
"Emergency Support": "תמיכת חירום",
|
||||
"Open Documentation": "פתח תיעוד",
|
||||
"Support Chat": "צ'אט תמיכה",
|
||||
"Domain": "דומיין",
|
||||
"Domain Name": "שם הדומיין",
|
||||
"Domain Verification Code (optional)": "קוד אימות דומיין (אופציונלי)",
|
||||
@@ -879,4 +893,4 @@
|
||||
"results": "תוצאות",
|
||||
"selected": "נבחרו",
|
||||
"to": "עד"
|
||||
}
|
||||
}
|
||||
|
||||
16
lang/pt.json
16
lang/pt.json
@@ -219,6 +219,20 @@
|
||||
"Disk Usage": "Uso de Disco",
|
||||
"Display Name": "Nome de Exibição",
|
||||
"Document Root": "Raiz do Documento",
|
||||
"Documentation": "Documentação",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "Encontre respostas na nossa documentação ou fale com o nosso bot de suporte treinado. Explore guias de configuração, etapas de solução de problemas e boas práticas.",
|
||||
"Chat with our AI support bot.": "Converse com nosso bot de suporte com IA.",
|
||||
"GitHub Issues": "Issues do GitHub",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "Reporte bugs ou solicite recursos. Inclua passos, logs e capturas de tela para podermos reproduzir rapidamente.",
|
||||
"Open GitHub Issues": "Abrir issues do GitHub",
|
||||
"Paid Support": "Suporte pago",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "Obtenha assistência profissional para migrações, ajustes de desempenho e correções prioritárias. Os planos incluem onboarding e suporte dedicado.",
|
||||
"View Support Plans": "Ver planos de suporte",
|
||||
"Response Time": "Tempo de resposta",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "Normalmente respondemos em 4-8 horas. Para incidentes críticos, use o Suporte de Emergência para uma resposta mais rápida.",
|
||||
"Emergency Support": "Suporte de emergência",
|
||||
"Open Documentation": "Abrir documentação",
|
||||
"Support Chat": "Chat de suporte",
|
||||
"Domain": "Domínio",
|
||||
"Domain Name": "Nome do Domínio",
|
||||
"Domain Verification Code (optional)": "Código de Verificação do Domínio (opcional)",
|
||||
@@ -879,4 +893,4 @@
|
||||
"results": "resultados",
|
||||
"selected": "selecionado(s)",
|
||||
"to": "até"
|
||||
}
|
||||
}
|
||||
|
||||
16
lang/ru.json
16
lang/ru.json
@@ -220,6 +220,20 @@
|
||||
"Disk Usage": "Использование диска",
|
||||
"Display Name": "Отображаемое имя",
|
||||
"Document Root": "Корневая директория",
|
||||
"Documentation": "Документация",
|
||||
"Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.": "Найдите ответы в нашей документации или поговорите с нашим обученным ботом поддержки. Изучите руководства по настройке, шаги по устранению неполадок и лучшие практики.",
|
||||
"Chat with our AI support bot.": "Чат с нашим AI-ботом поддержки.",
|
||||
"GitHub Issues": "GitHub Issues",
|
||||
"Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.": "Сообщайте о багах или запрашивайте функции. Укажите шаги, логи и скриншоты, чтобы мы могли быстро воспроизвести.",
|
||||
"Open GitHub Issues": "Открыть GitHub Issues",
|
||||
"Paid Support": "Платная поддержка",
|
||||
"Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.": "Профессиональная помощь по миграциям, оптимизации производительности и приоритетным исправлениям. Планы включают онбординг и выделенную поддержку.",
|
||||
"View Support Plans": "Посмотреть планы поддержки",
|
||||
"Response Time": "Время ответа",
|
||||
"We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.": "Обычно отвечаем в течение 4–8 часов. Для критических инцидентов используйте экстренную поддержку для более быстрого ответа.",
|
||||
"Emergency Support": "Экстренная поддержка",
|
||||
"Open Documentation": "Открыть документацию",
|
||||
"Support Chat": "Чат поддержки",
|
||||
"Domain": "Домен",
|
||||
"Domain Name": "Имя домена",
|
||||
"Domain Verification Code (optional)": "Код подтверждения домена (необязательно)",
|
||||
@@ -886,4 +900,4 @@
|
||||
"results": "результатов",
|
||||
"selected": "выбрано",
|
||||
"to": "до"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
[x-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
78
resources/views/filament/admin/pages/support.blade.php
Normal file
78
resources/views/filament/admin/pages/support.blade.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<x-filament-panels::page>
|
||||
<div class="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
|
||||
<x-filament::section
|
||||
icon="heroicon-o-book-open"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ __('Documentation') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://jabali-panel.com/docs/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
>
|
||||
{{ __('Open Documentation') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="heroicon-o-bug-ant"
|
||||
icon-color="warning"
|
||||
>
|
||||
<x-slot name="heading">{{ __('GitHub Issues') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://github.com/shukiv/jabali-panel/issues"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
color="gray"
|
||||
>
|
||||
{{ __('Open GitHub Issues') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="heroicon-o-lifebuoy"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ __('Paid Support') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://jabali-panel.com/support/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
>
|
||||
{{ __('View Support Plans') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="heroicon-o-clock"
|
||||
icon-color="gray"
|
||||
compact
|
||||
>
|
||||
<x-slot name="heading">{{ __('Emergency Support') }}</x-slot>
|
||||
<x-slot name="description">{{ __('We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://jabali-panel.com/emergency/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
color="warning"
|
||||
>
|
||||
{{ __('Emergency Support') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
</div>
|
||||
</x-filament-panels::page>
|
||||
@@ -1,35 +0,0 @@
|
||||
<x-filament::section class="mt-4" icon="heroicon-o-clipboard-document-list">
|
||||
<x-slot name="heading">{{ __('Activity Log') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Recent actions performed in your account.') }}</x-slot>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left fi-section-header-heading">{{ __('Time') }}</th>
|
||||
<th class="px-4 py-3 text-left fi-section-header-heading">{{ __('Category') }}</th>
|
||||
<th class="px-4 py-3 text-left fi-section-header-heading">{{ __('Action') }}</th>
|
||||
<th class="px-4 py-3 text-left fi-section-header-heading">{{ __('Description') }}</th>
|
||||
<th class="px-4 py-3 text-left fi-section-header-heading">{{ __('IP') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($this->getActivityLogs() as $log)
|
||||
<tr>
|
||||
<td class="px-4 py-3 fi-section-header-description">{{ $log->created_at?->format('Y-m-d H:i') }}</td>
|
||||
<td class="px-4 py-3 fi-section-header-description">{{ $log->category }}</td>
|
||||
<td class="px-4 py-3 fi-section-header-description">{{ $log->action }}</td>
|
||||
<td class="px-4 py-3 fi-section-header-description">{{ $log->description }}</td>
|
||||
<td class="px-4 py-3 fi-section-header-description">{{ $log->ip_address }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-4 py-6 text-center fi-section-header-description">
|
||||
{{ __('No activity recorded yet.') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
78
resources/views/filament/jabali/pages/support.blade.php
Normal file
78
resources/views/filament/jabali/pages/support.blade.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<x-filament-panels::page>
|
||||
<div class="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
|
||||
<x-filament::section
|
||||
icon="heroicon-o-book-open"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ __('Documentation') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Find answers in our docs or talk with our trainned support bot. Explore setup guides, troubleshooting steps, and best practices.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://jabali-panel.com/docs/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
>
|
||||
{{ __('Open Documentation') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="heroicon-o-bug-ant"
|
||||
icon-color="warning"
|
||||
>
|
||||
<x-slot name="heading">{{ __('GitHub Issues') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Report bugs or request features. Include steps, logs, and screenshots so we can reproduce quickly.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://github.com/shukiv/jabali-panel/issues"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
color="gray"
|
||||
>
|
||||
{{ __('Open GitHub Issues') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="heroicon-o-lifebuoy"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ __('Paid Support') }}</x-slot>
|
||||
<x-slot name="description">{{ __('Get professional assistance for migrations, performance tuning, and priority fixes. Plans include onboarding and dedicated support.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://jabali-panel.com/support/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
>
|
||||
{{ __('View Support Plans') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="heroicon-o-clock"
|
||||
icon-color="gray"
|
||||
compact
|
||||
>
|
||||
<x-slot name="heading">{{ __('Emergency Support') }}</x-slot>
|
||||
<x-slot name="description">{{ __('We typically respond within 4-8 hours. For critical incidents, use Emergency Support for faster response.') }}</x-slot>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="https://jabali-panel.com/emergency/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
icon="heroicon-o-arrow-top-right-on-square"
|
||||
color="warning"
|
||||
>
|
||||
{{ __('Emergency Support') }}
|
||||
</x-filament::button>
|
||||
</x-filament::section>
|
||||
</div>
|
||||
</x-filament-panels::page>
|
||||
323
scripts/deploy.sh
Executable file
323
scripts/deploy.sh
Executable file
@@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
DEPLOY_HOST="${DEPLOY_HOST:-192.168.100.50}"
|
||||
DEPLOY_USER="${DEPLOY_USER:-root}"
|
||||
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/jabali}"
|
||||
WWW_USER="${WWW_USER:-www-data}"
|
||||
NPM_CACHE_DIR="${NPM_CACHE_DIR:-}"
|
||||
GITEA_REMOTE="${GITEA_REMOTE:-gitea}"
|
||||
GITEA_URL="${GITEA_URL:-}"
|
||||
GITHUB_REMOTE="${GITHUB_REMOTE:-origin}"
|
||||
GITHUB_URL="${GITHUB_URL:-}"
|
||||
PUSH_BRANCH="${PUSH_BRANCH:-}"
|
||||
|
||||
SKIP_SYNC=0
|
||||
SKIP_COMPOSER=0
|
||||
SKIP_NPM=0
|
||||
SKIP_MIGRATE=0
|
||||
SKIP_CACHE=0
|
||||
DELETE_REMOTE=0
|
||||
DRY_RUN=0
|
||||
PUSH_GITEA=0
|
||||
PUSH_GITHUB=0
|
||||
SET_VERSION=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/deploy.sh [options]
|
||||
|
||||
Options:
|
||||
--host HOST Remote host (default: 192.168.100.50)
|
||||
--user USER SSH user (default: root)
|
||||
--path PATH Remote path (default: /var/www/jabali)
|
||||
--www-user USER Remote runtime user (default: www-data)
|
||||
--skip-sync Skip rsync sync step
|
||||
--skip-composer Skip composer install
|
||||
--skip-npm Skip npm install/build
|
||||
--skip-migrate Skip php artisan migrate
|
||||
--skip-cache Skip cache clear/rebuild
|
||||
--delete Pass --delete to rsync (dangerous)
|
||||
--dry-run Dry-run rsync only
|
||||
--push-gitea Push current branch to Gitea before deploy
|
||||
--gitea-remote NAME Gitea git remote name (default: gitea)
|
||||
--gitea-url URL Push to this URL instead of a named remote
|
||||
--push-github Push current branch to GitHub before deploy
|
||||
--github-remote NAME GitHub git remote name (default: origin)
|
||||
--github-url URL Push to this URL instead of a named remote
|
||||
--version VALUE Set VERSION to a specific value before push
|
||||
-h, --help Show this help
|
||||
|
||||
Environment overrides:
|
||||
DEPLOY_HOST, DEPLOY_USER, DEPLOY_PATH, WWW_USER, NPM_CACHE_DIR, GITEA_REMOTE, GITEA_URL, GITHUB_REMOTE, GITHUB_URL, PUSH_BRANCH
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--host)
|
||||
DEPLOY_HOST="$2"
|
||||
shift 2
|
||||
;;
|
||||
--user)
|
||||
DEPLOY_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--path)
|
||||
DEPLOY_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--www-user)
|
||||
WWW_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-sync)
|
||||
SKIP_SYNC=1
|
||||
shift
|
||||
;;
|
||||
--skip-composer)
|
||||
SKIP_COMPOSER=1
|
||||
shift
|
||||
;;
|
||||
--skip-npm)
|
||||
SKIP_NPM=1
|
||||
shift
|
||||
;;
|
||||
--skip-migrate)
|
||||
SKIP_MIGRATE=1
|
||||
shift
|
||||
;;
|
||||
--skip-cache)
|
||||
SKIP_CACHE=1
|
||||
shift
|
||||
;;
|
||||
--delete)
|
||||
DELETE_REMOTE=1
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
--push-gitea)
|
||||
PUSH_GITEA=1
|
||||
shift
|
||||
;;
|
||||
--gitea-remote)
|
||||
GITEA_REMOTE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--gitea-url)
|
||||
GITEA_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--push-github)
|
||||
PUSH_GITHUB=1
|
||||
shift
|
||||
;;
|
||||
--github-remote)
|
||||
GITHUB_REMOTE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--github-url)
|
||||
GITHUB_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
SET_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
REMOTE="${DEPLOY_USER}@${DEPLOY_HOST}"
|
||||
|
||||
ensure_clean_worktree() {
|
||||
if ! git -C "$ROOT_DIR" diff --quiet || ! git -C "$ROOT_DIR" diff --cached --quiet; then
|
||||
echo "Working tree is dirty. Commit or stash changes before pushing."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
get_current_version() {
|
||||
sed -n 's/^VERSION=//p' "$ROOT_DIR/VERSION"
|
||||
}
|
||||
|
||||
bump_version() {
|
||||
local current new base num
|
||||
current="$(get_current_version)"
|
||||
|
||||
if [[ -n "$SET_VERSION" ]]; then
|
||||
new="$SET_VERSION"
|
||||
else
|
||||
if [[ "$current" =~ ^(.+-rc)([0-9]+)?$ ]]; then
|
||||
base="${BASH_REMATCH[1]}"
|
||||
num="${BASH_REMATCH[2]}"
|
||||
if [[ -z "$num" ]]; then
|
||||
num=1
|
||||
else
|
||||
num=$((num + 1))
|
||||
fi
|
||||
new="${base}${num}"
|
||||
elif [[ "$current" =~ ^(.+?)([0-9]+)$ ]]; then
|
||||
new="${BASH_REMATCH[1]}$((BASH_REMATCH[2] + 1))"
|
||||
else
|
||||
echo "Cannot auto-bump VERSION from '$current'. Use --version to set it explicitly."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$new" == "$current" ]]; then
|
||||
echo "VERSION is already '$current'. Use --version to set a new value."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'VERSION=%s\n' "$new" > "$ROOT_DIR/VERSION"
|
||||
perl -0pi -e "s/JABALI_VERSION=\\\"\\$\\{JABALI_VERSION:-[^\\\"]+\\}\\\"/JABALI_VERSION=\\\"\\$\\{JABALI_VERSION:-$new\\}\\\"/g" "$ROOT_DIR/install.sh"
|
||||
|
||||
git -C "$ROOT_DIR" add VERSION install.sh
|
||||
if ! git -C "$ROOT_DIR" diff --cached --quiet; then
|
||||
git -C "$ROOT_DIR" commit -m "Bump VERSION to $new"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_push() {
|
||||
ensure_clean_worktree
|
||||
bump_version
|
||||
}
|
||||
|
||||
push_remote() {
|
||||
local label="$1"
|
||||
local remote_name="$2"
|
||||
local remote_url="$3"
|
||||
local target
|
||||
|
||||
if [[ -n "$remote_url" ]]; then
|
||||
target="$remote_url"
|
||||
else
|
||||
if ! git -C "$ROOT_DIR" remote get-url "$remote_name" >/dev/null 2>&1; then
|
||||
echo "$label remote '$remote_name' not found. Use --${label,,}-url or --${label,,}-remote."
|
||||
exit 1
|
||||
fi
|
||||
target="$remote_name"
|
||||
fi
|
||||
|
||||
if [[ -z "$PUSH_BRANCH" ]]; then
|
||||
PUSH_BRANCH="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD)"
|
||||
fi
|
||||
|
||||
git -C "$ROOT_DIR" push "$target" "$PUSH_BRANCH"
|
||||
}
|
||||
|
||||
rsync_project() {
|
||||
local -a rsync_opts
|
||||
rsync_opts=(-az --info=progress2)
|
||||
if [[ "$DELETE_REMOTE" -eq 1 ]]; then
|
||||
rsync_opts+=(--delete)
|
||||
fi
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
rsync_opts+=(--dry-run)
|
||||
fi
|
||||
|
||||
rsync "${rsync_opts[@]}" \
|
||||
--exclude ".git/" \
|
||||
--exclude "node_modules/" \
|
||||
--exclude "vendor/" \
|
||||
--exclude "storage/" \
|
||||
--exclude "bootstrap/cache/" \
|
||||
--exclude "public/build/" \
|
||||
--exclude ".env" \
|
||||
--exclude ".env.*" \
|
||||
--exclude "database/*.sqlite" \
|
||||
--exclude "database/*.sqlite-wal" \
|
||||
--exclude "database/*.sqlite-shm" \
|
||||
"$ROOT_DIR/" \
|
||||
"${REMOTE}:${DEPLOY_PATH}/"
|
||||
}
|
||||
|
||||
remote_run() {
|
||||
ssh -o StrictHostKeyChecking=no "$REMOTE" "bash -lc '$1'"
|
||||
}
|
||||
|
||||
remote_run_www() {
|
||||
ssh -o StrictHostKeyChecking=no "$REMOTE" "bash -lc 'cd \"$DEPLOY_PATH\" && sudo -u \"$WWW_USER\" -H bash -lc \"$1\"'"
|
||||
}
|
||||
|
||||
ensure_remote_permissions() {
|
||||
local parent_dir
|
||||
parent_dir="$(dirname "$DEPLOY_PATH")"
|
||||
|
||||
if [[ -z "$NPM_CACHE_DIR" ]]; then
|
||||
NPM_CACHE_DIR="${parent_dir}/.npm"
|
||||
fi
|
||||
|
||||
remote_run "mkdir -p \"$DEPLOY_PATH/storage\" \"$DEPLOY_PATH/bootstrap/cache\" \"$DEPLOY_PATH/public/build\" \"$DEPLOY_PATH/node_modules\" \"$DEPLOY_PATH/database\" \"$NPM_CACHE_DIR\""
|
||||
remote_run "chown -R \"$WWW_USER\":\"$WWW_USER\" \"$DEPLOY_PATH/storage\" \"$DEPLOY_PATH/bootstrap/cache\" \"$DEPLOY_PATH/public\" \"$DEPLOY_PATH/public/build\" \"$DEPLOY_PATH/node_modules\" \"$DEPLOY_PATH/database\" \"$NPM_CACHE_DIR\""
|
||||
remote_run "if [[ -f \"$DEPLOY_PATH/auth.json\" ]]; then chown \"$WWW_USER\":\"$WWW_USER\" \"$DEPLOY_PATH/auth.json\" && chmod 600 \"$DEPLOY_PATH/auth.json\"; fi"
|
||||
}
|
||||
|
||||
echo "Deploying to ${REMOTE}:${DEPLOY_PATH}"
|
||||
|
||||
if [[ "$PUSH_GITEA" -eq 1 ]]; then
|
||||
prepare_push
|
||||
echo "Pushing to Gitea..."
|
||||
push_remote "Gitea" "$GITEA_REMOTE" "$GITEA_URL"
|
||||
fi
|
||||
|
||||
if [[ "$PUSH_GITHUB" -eq 1 ]]; then
|
||||
if [[ "$PUSH_GITEA" -ne 1 ]]; then
|
||||
prepare_push
|
||||
fi
|
||||
echo "Pushing to GitHub..."
|
||||
push_remote "GitHub" "$GITHUB_REMOTE" "$GITHUB_URL"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_SYNC" -eq 0 ]]; then
|
||||
echo "Syncing project files..."
|
||||
rsync_project
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
echo "Dry run complete. No remote commands executed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Ensuring remote permissions..."
|
||||
ensure_remote_permissions
|
||||
|
||||
if [[ "$SKIP_COMPOSER" -eq 0 ]]; then
|
||||
echo "Installing composer dependencies..."
|
||||
remote_run_www "composer install --no-interaction --prefer-dist --optimize-autoloader"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_NPM" -eq 0 ]]; then
|
||||
echo "Building frontend assets..."
|
||||
remote_run_www "npm ci"
|
||||
remote_run_www "npm run build"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_MIGRATE" -eq 0 ]]; then
|
||||
echo "Running migrations..."
|
||||
remote_run_www "php artisan migrate --force"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_CACHE" -eq 0 ]]; then
|
||||
echo "Refreshing caches..."
|
||||
remote_run_www "php artisan optimize:clear"
|
||||
remote_run_www "php artisan config:cache"
|
||||
remote_run_www "php artisan route:cache"
|
||||
remote_run_www "php artisan view:cache"
|
||||
fi
|
||||
|
||||
echo "Deploy complete."
|
||||
43
tests/Feature/Filament/SupportPagesTest.php
Normal file
43
tests/Feature/Filament/SupportPagesTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Filament;
|
||||
|
||||
use App\Filament\Admin\Pages\Support as AdminSupport;
|
||||
use App\Filament\Jabali\Pages\Support as UserSupport;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SupportPagesTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_admin_support_page_renders_support_links(): void
|
||||
{
|
||||
$admin = User::factory()->admin()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Livewire::test(AdminSupport::class)
|
||||
->assertStatus(200)
|
||||
->assertSee('Open Documentation')
|
||||
->assertSee('GitHub Issues')
|
||||
->assertSee('Paid Support');
|
||||
}
|
||||
|
||||
public function test_user_support_page_renders_support_links(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire::test(UserSupport::class)
|
||||
->assertStatus(200)
|
||||
->assertSee('Open Documentation')
|
||||
->assertSee('GitHub Issues')
|
||||
->assertSee('Paid Support');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user