Compare commits

...

19 commits

Author SHA1 Message Date
Bozhidar
8897c4818c Update Installer.php 2024-05-09 17:56:50 +03:00
Bozhidar
bb0945a63f Update DomainIsCreatedListener.php 2024-05-09 17:47:45 +03:00
Bozhidar
54238b30b3 update 2024-05-09 17:46:02 +03:00
Bozhidar
a77bc147a4 update 2024-05-09 17:21:30 +03:00
Bozhidar
97ee0da261 fix backup issues 2024-05-09 16:58:36 +03:00
Bozhidar
5b20b10b90 Update HostingSubscriptionBackup.php 2024-05-09 16:46:15 +03:00
Bozhidar
1030589021 update 2024-05-09 16:41:00 +03:00
Bozhidar
71a3ac48e1 Update README.md 2024-05-09 16:24:07 +03:00
Bozhidar
0b11ce9324 Update Backup.php 2024-05-09 15:59:41 +03:00
Bozhidar
bca5e2b9b1 Update Backup.php 2024-05-09 15:55:23 +03:00
Bozhidar
3d91014e34 Update Backup.php 2024-05-09 15:47:42 +03:00
Bozhidar
7d1b8e7d28 Update BackupResource.php 2024-05-09 15:44:15 +03:00
Bozhidar
4a502ffb15 update 2024-05-09 15:44:00 +03:00
Bozhidar
828d650d6d Update BackupResource.php 2024-05-09 15:20:03 +03:00
Bozhidar
969cd8c376 update 2024-05-09 15:16:11 +03:00
Bozhidar
079f18f728 update 2024-05-09 15:14:31 +03:00
Bozhidar
538246e795 add hosting backup view log 2024-05-09 14:50:58 +03:00
Bozhidar
18a7c686c4 Update HostingSubscriptionBackupTest.php 2024-05-09 13:29:21 +03:00
Bozhidar
9c40235dba update 2024-05-09 11:42:27 +03:00
32 changed files with 12911 additions and 96 deletions

View file

@ -16,7 +16,7 @@ The admin panel can be opened on port: yourserver.com:8443
## Build Status ## Build Status
### Master branch ### Master branch
[![Build Status](http://95.217.182.28/api/badges/PhyreApps/PhyrePanel/status.svg)](http://95.217.182.28/PhyreApps/PhyrePanel) [![Build Status](https://drone.phyrecloud.com/api/badges/PhyreApps/PhyrePanel/status.svg)](https://drone.phyrecloud.com/PhyreApps/PhyrePanel)
[![codecov](https://codecov.io/gh/PhyreApps/PhyrePanel/graph/badge.svg?token=BO0RTPLS4W)](https://codecov.io/gh/PhyreApps/PhyrePanel) [![codecov](https://codecov.io/gh/PhyreApps/PhyrePanel/graph/badge.svg?token=BO0RTPLS4W)](https://codecov.io/gh/PhyreApps/PhyrePanel)
All development is done on the `dev` branch. The `master` branch is used for stable releases. All development is done on the `dev` branch. The `master` branch is used for stable releases.

View file

@ -9,6 +9,9 @@ fi
# Go to web directory # Go to web directory
cd /usr/local/phyre/web cd /usr/local/phyre/web
# Add supervisor conf
cp /usr/local/phyre/web/app/Supervisor/configs/phyre-worker.conf /etc/supervisor/conf.d/phyre.conf
# Create MySQL user # Create MySQL user
MYSQL_PHYRE_ROOT_USERNAME="phyre" MYSQL_PHYRE_ROOT_USERNAME="phyre"
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)" MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"

View file

@ -90,6 +90,9 @@ fi
# Go to web directory # Go to web directory
cd /usr/local/phyre/web cd /usr/local/phyre/web
# Add supervisor conf
cp /usr/local/phyre/web/app/Supervisor/configs/phyre-worker.conf /etc/supervisor/conf.d/phyre.conf
# Create MySQL user # Create MySQL user
MYSQL_PHYRE_ROOT_USERNAME="phyre" MYSQL_PHYRE_ROOT_USERNAME="phyre"
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)" MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"

View file

@ -9,6 +9,9 @@ fi
# Go to web directory # Go to web directory
cd /usr/local/phyre/web cd /usr/local/phyre/web
# Add supervisor conf
cp /usr/local/phyre/web/app/Supervisor/configs/phyre-worker.conf /etc/supervisor/conf.d/phyre.conf
# Create MySQL user # Create MySQL user
MYSQL_PHYRE_ROOT_USERNAME="phyre" MYSQL_PHYRE_ROOT_USERNAME="phyre"
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)" MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"

View file

@ -92,6 +92,9 @@ fi
# Go to web directory # Go to web directory
cd /usr/local/phyre/web cd /usr/local/phyre/web
# Add supervisor conf
cp /usr/local/phyre/web/app/Supervisor/configs/phyre-worker.conf /etc/supervisor/conf.d/phyre.conf
# Create MySQL user # Create MySQL user
MYSQL_PHYRE_ROOT_USERNAME="phyre" MYSQL_PHYRE_ROOT_USERNAME="phyre"
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)" MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"

View file

@ -43,7 +43,6 @@ class DomainIsCreatedListener
if (! in_array('letsencrypt', $findHostingPlan->additional_services)) { if (! in_array('letsencrypt', $findHostingPlan->additional_services)) {
return; return;
} }
return;
$generalSettings = Settings::general(); $generalSettings = Settings::general();

View file

@ -2,15 +2,18 @@
namespace App\Filament\Pages\Settings; namespace App\Filament\Pages\Settings;
use App\Helpers;
use App\MasterDomain; use App\MasterDomain;
use Closure; use Closure;
use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\ColorPicker; use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs; use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Get; use Filament\Forms\Get;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Monarobase\CountryList\CountryList;
use Outerweb\FilamentSettings\Filament\Pages\Settings as BaseSettings; use Outerweb\FilamentSettings\Filament\Pages\Settings as BaseSettings;
use Symfony\Component\Console\Input\Input; use Symfony\Component\Console\Input\Input;
@ -41,16 +44,15 @@ class Settings extends BaseSettings
TextInput::make('general.brand_logo_url'), TextInput::make('general.brand_logo_url'),
ColorPicker::make('general.brand_primary_color'), ColorPicker::make('general.brand_primary_color'),
TextInput::make('general.master_domain')->live(), TextInput::make('general.master_domain')->live(),
// Checkbox::make('general.master_domain_wildcard_enabled')
// ->label('Wildcard on Master Domain')
// ->helperText(function (Get $get) {
// return 'Enable wildcard for master domain. Eg: *.'.$get('general.master_domain');
// }),
TextInput::make('general.master_email'), TextInput::make('general.master_email'),
TextInput::make('general.master_country'), Select::make('general.master_country')
->searchable()
->options(function () {
$countryList = new CountryList();
return $countryList->getList();
}),
TextInput::make('general.master_locality'), TextInput::make('general.master_locality'),
TextInput::make('general.organization_name'), TextInput::make('general.organization_name'),
]), ]),

View file

@ -7,6 +7,7 @@ use App\Filament\Enums\BackupStatus;
use App\Filament\Enums\BackupType; use App\Filament\Enums\BackupType;
use App\Filament\Resources\BackupResource\Pages; use App\Filament\Resources\BackupResource\Pages;
use app\Filament\Resources\BackupResource\Widgets\BackupStats; use app\Filament\Resources\BackupResource\Widgets\BackupStats;
use App\Helpers;
use App\Models\Backup; use App\Models\Backup;
use App\Models\HostingSubscription; use App\Models\HostingSubscription;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
@ -83,17 +84,17 @@ class BackupResource extends Resource
return $backup->created_at ? $backup->created_at : 'N/A'; return $backup->created_at ? $backup->created_at : 'N/A';
}), }),
Tables\Columns\TextColumn::make('completed_at') // Tables\Columns\TextColumn::make('completed_at')
->label('Completed time') // ->label('Completed time')
->state(function (Backup $backup) { // ->state(function (Backup $backup) {
$diff = \Carbon\Carbon::parse($backup->completed_at) // $diff = \Carbon\Carbon::parse($backup->completed_at)
->diffForHumans($backup->created_at); // ->diffForHumans($backup->created_at);
return $backup->completed_at ? $diff : 'N/A'; // return $backup->completed_at ? $diff : 'N/A';
}), // }),
Tables\Columns\TextColumn::make('size') Tables\Columns\TextColumn::make('size')
->state(function (Backup $backup) { ->state(function (Backup $backup) {
return $backup->size ? $backup->size : 'N/A'; return ($backup->size > 0) ? Helpers::getHumanReadableSize($backup->size) : 'N/A';
}), }),
]) ])
->filters([ ->filters([
@ -112,6 +113,26 @@ class BackupResource extends Resource
return redirect($tempUrl); return redirect($tempUrl);
}), }),
Tables\Actions\Action::make('viewLog')
->label('View Log')
->icon('heroicon-o-document')
->hidden(function (Backup $backup) {
$hide = true;
if ($backup->status === BackupStatus::Processing || $backup->status === BackupStatus::Failed) {
$hide = false;
}
return $hide;
})
->modalContent(function (Backup $backup) {
return view('filament.modals.view-livewire-component', [
'component' => 'backup-log',
'componentProps' => [
'backupId' => $backup->id,
],
]);
}),
Tables\Actions\ViewAction::make(), Tables\Actions\ViewAction::make(),
]) ])
->defaultSort('id', 'desc') ->defaultSort('id', 'desc')
@ -138,9 +159,10 @@ class BackupResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListBackups::route('/'), // 'index' => Pages\ListBackups::route('/'),
'create' => Pages\CreateBackup::route('/create'), // 'create' => Pages\CreateBackup::route('/create'),
'view' => Pages\ViewBackup::route('/{record}'), // 'view' => Pages\ViewBackup::route('/{record}'),
'index' => Pages\ManageBackups::route('/'),
]; ];
} }
} }

View file

@ -0,0 +1,41 @@
<?php
namespace App\Filament\Resources\BackupResource\Pages;
use App\Filament\Resources\BackupResource;
use Filament\Actions;
use Filament\Pages\Concerns\ExposesTableToWidgets;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ManageRecords;
class ManageBackups extends ManageRecords
{
use ExposesTableToWidgets;
protected static string $resource = BackupResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make()
->size('sm')
->slideOver(),
];
}
protected function getHeaderWidgets(): array
{
return BackupResource::getWidgets();
}
public function getTabs(): array
{
return [
null => Tab::make('All'),
'completed' => Tab::make()->query(fn ($query) => $query->where('status', 'completed')),
'processing' => Tab::make()->query(fn ($query) => $query->where('status', 'processing')),
'failed' => Tab::make()->query(fn ($query) => $query->where('status', 'failed')),
];
}
}

View file

@ -11,6 +11,7 @@ use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat; use Filament\Widgets\StatsOverviewWidget\Stat;
use Flowframe\Trend\Trend; use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue; use Flowframe\Trend\TrendValue;
use Illuminate\Support\Facades\Cache;
class BackupStats extends BaseWidget class BackupStats extends BaseWidget
{ {
@ -25,6 +26,8 @@ class BackupStats extends BaseWidget
protected function getStats(): array protected function getStats(): array
{ {
$stats = Cache::remember('backup-stats', 300, function () {
$findBackups = Backup::select(['id'])->where('status', 'processing')->get(); $findBackups = Backup::select(['id'])->where('status', 'processing')->get();
if ($findBackups->count() > 0) { if ($findBackups->count() > 0) {
foreach ($findBackups as $backup) { foreach ($findBackups as $backup) {
@ -36,10 +39,15 @@ class BackupStats extends BaseWidget
if (is_dir($backupPath)) { if (is_dir($backupPath)) {
$usedSpace = $this->getDirectorySize($backupPath); $usedSpace = $this->getDirectorySize($backupPath);
} }
return [
'totalBackups' => Backup::count(),
'usedSpace' => $usedSpace,
];
});
return [ return [
Stat::make('Total backups', Backup::count()), Stat::make('Total backups', $stats['totalBackups']),
Stat::make('Total used space', $usedSpace), Stat::make('Total used space', $stats['usedSpace']),
]; ];
} }

View file

@ -3,6 +3,7 @@
namespace App\Filament\Resources; namespace App\Filament\Resources;
use App\Filament\Resources\HostingSubscriptionResource\Pages; use App\Filament\Resources\HostingSubscriptionResource\Pages;
use app\Filament\Resources\HostingSubscriptionResource\Pages\ManageHostingSubscriptionFtpAccounts;
use App\Models\Customer; use App\Models\Customer;
use App\Models\Domain; use App\Models\Domain;
use App\Models\HostingSubscription; use App\Models\HostingSubscription;
@ -188,6 +189,7 @@ class HostingSubscriptionResource extends Resource
Pages\EditHostingSubscription::class, Pages\EditHostingSubscription::class,
Pages\ManageHostingSubscriptionDatabases::class, Pages\ManageHostingSubscriptionDatabases::class,
Pages\ManageHostingSubscriptionBackups::class, Pages\ManageHostingSubscriptionBackups::class,
ManageHostingSubscriptionFtpAccounts::class
]); ]);
} }
@ -207,6 +209,7 @@ class HostingSubscriptionResource extends Resource
'edit' => Pages\EditHostingSubscription::route('/{record}/edit'), 'edit' => Pages\EditHostingSubscription::route('/{record}/edit'),
'databases' => Pages\ManageHostingSubscriptionDatabases::route('/{record}/databases'), 'databases' => Pages\ManageHostingSubscriptionDatabases::route('/{record}/databases'),
'backups' => Pages\ManageHostingSubscriptionBackups::route('/{record}/backups'), 'backups' => Pages\ManageHostingSubscriptionBackups::route('/{record}/backups'),
'ftp-accounts' => Pages\ManageHostingSubscriptionFtpAccounts::route('/{record}/ftp-accounts'),
]; ];
} }

View file

@ -7,11 +7,13 @@ use App\Filament\Enums\BackupStatus;
use App\Filament\Enums\HostingSubscriptionBackupType; use App\Filament\Enums\HostingSubscriptionBackupType;
use App\Filament\Resources\Blog\PostResource; use App\Filament\Resources\Blog\PostResource;
use App\Filament\Resources\HostingSubscriptionResource; use App\Filament\Resources\HostingSubscriptionResource;
use App\Helpers;
use App\Models\Backup; use App\Models\Backup;
use App\Models\DatabaseUser; use App\Models\DatabaseUser;
use App\Models\HostingSubscriptionBackup; use App\Models\HostingSubscriptionBackup;
use App\Models\RemoteDatabaseServer; use App\Models\RemoteDatabaseServer;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Infolists\Components\IconEntry; use Filament\Infolists\Components\IconEntry;
use Filament\Infolists\Components\TextEntry; use Filament\Infolists\Components\TextEntry;
@ -21,6 +23,7 @@ use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Contracts\Support\Htmlable; use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File;
use JaOcero\RadioDeck\Forms\Components\RadioDeck; use JaOcero\RadioDeck\Forms\Components\RadioDeck;
class ManageHostingSubscriptionBackups extends ManageRelatedRecords class ManageHostingSubscriptionBackups extends ManageRelatedRecords
@ -47,7 +50,7 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
public static function getNavigationLabel(): string public static function getNavigationLabel(): string
{ {
return 'Manage Backups'; return 'Backups';
} }
public function form(Form $form): Form public function form(Form $form): Form
@ -88,18 +91,8 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
public function table(Table $table): Table public function table(Table $table): Table
{ {
$findHostingSubscription = HostingSubscriptionBackup::where('hosting_subscription_id', $this->record->id)
->where('status', 'processing')
->get();
if ($findHostingSubscription->count() > 0) {
foreach ($findHostingSubscription as $backup) {
$backup->checkBackup();
}
}
return $table return $table
->recordTitleAttribute('id') ->recordTitleAttribute('file_name')
->columns([ ->columns([
Tables\Columns\TextColumn::make('backup_type') Tables\Columns\TextColumn::make('backup_type')
@ -117,7 +110,7 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
Tables\Columns\TextColumn::make('size') Tables\Columns\TextColumn::make('size')
->state(function (HostingSubscriptionBackup $backup) { ->state(function (HostingSubscriptionBackup $backup) {
return $backup->size ? $backup->size : 'N/A'; return ($backup->size > 0) ? Helpers::getHumanReadableSize($backup->size) : 'N/A';
}), }),
]) ])
@ -141,6 +134,27 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
return redirect($tempUrl); return redirect($tempUrl);
}), }),
Tables\Actions\Action::make('viewLog')
->label('View Log')
->icon('heroicon-o-document')
->hidden(function (HostingSubscriptionBackup $backup) {
$hide = true;
if ($backup->status === BackupStatus::Processing || $backup->status === BackupStatus::Failed) {
$hide = false;
}
return $hide;
})
->modalContent(function (HostingSubscriptionBackup $backup) {
return view('filament.modals.view-livewire-component', [
'component' => 'hosting-subscription-backup-log',
'componentProps' => [
'hostingSubscriptionBackupId' => $backup->id,
],
]);
}),
Tables\Actions\ViewAction::make(), Tables\Actions\ViewAction::make(),
// Tables\Actions\EditAction::make(), // Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(), Tables\Actions\DeleteAction::make(),

View file

@ -41,7 +41,7 @@ class ManageHostingSubscriptionDatabases extends ManageRelatedRecords
public static function getNavigationLabel(): string public static function getNavigationLabel(): string
{ {
return 'Manage Databases'; return 'Databases';
} }
public function form(Form $form): Form public function form(Form $form): Form

View file

@ -0,0 +1,134 @@
<?php
namespace app\Filament\Resources\HostingSubscriptionResource\Pages;
use App\BackupStorage;
use App\Filament\Enums\BackupStatus;
use App\Filament\Enums\HostingSubscriptionBackupType;
use App\Filament\Resources\Blog\PostResource;
use App\Filament\Resources\HostingSubscriptionResource;
use App\Models\Backup;
use App\Models\DatabaseUser;
use App\Models\HostingSubscriptionBackup;
use App\Models\RemoteDatabaseServer;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Infolists\Components\IconEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Infolist;
use Filament\Resources\Pages\ManageRelatedRecords;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Carbon;
use JaOcero\RadioDeck\Forms\Components\RadioDeck;
class ManageHostingSubscriptionFtpAccounts extends ManageRelatedRecords
{
protected static string $resource = HostingSubscriptionResource::class;
protected static string $relationship = 'ftpAccounts';
protected static ?string $navigationIcon = 'heroicon-o-inbox-stack';
public function getTitle(): string|Htmlable
{
$recordTitle = $this->getRecordTitle();
$recordTitle = $recordTitle instanceof Htmlable ? $recordTitle->toHtml() : $recordTitle;
return "Manage {$recordTitle} FTP Accounts";
}
public function getBreadcrumb(): string
{
return 'FTP Accounts';
}
public static function getNavigationLabel(): string
{
return 'FTP Accounts';
}
public function form(Form $form): Form
{
$systemUsername = $this->record->system_username;
return $form
->schema([
Forms\Components\TextInput::make('username')
->label('Username')
->required(),
Forms\Components\TextInput::make('password')
->label('Password')
->required(),
Forms\Components\TextInput::make('confirm_password')
->label('Confirm Password')
->same('password')
->required(),
Forms\Components\TextInput::make('path')
->label('Home Directory')
->prefix('/home/' . $systemUsername . '/')
->required(),
Forms\Components\TextInput::make('quota')
->label('Quota'),
Forms\Components\Toggle::make('unlimited_quota')
->label('Unlimited Quota')
->default(false),
])
->columns(1);
}
public function infolist(Infolist $infolist): Infolist
{
return $infolist
->columns(1)
->schema([
//TextEntry::make('id')->label('id'),
TextEntry::make('username')->label('Username'),
TextEntry::make('path')->label('Home Directory'),
TextEntry::make('quota')->label('Quota'),
IconEntry::make('unlimited_quota')->label('Unlimited Quota'),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('username')
->columns([
Tables\Columns\TextColumn::make('username')
->label('Username'),
Tables\Columns\TextColumn::make('path')
->label('Home Directory')
->default('/'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
//
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->groupedBulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
}

View file

@ -37,4 +37,23 @@ class Helpers
return 0; return 0;
} }
public static function getHumanReadableSize($size, $unit = null, $decemals = 2) {
$size = intval($size);
$byteUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if (!is_null($unit) && !in_array($unit, $byteUnits)) {
$unit = null;
}
$extent = 1;
foreach ($byteUnits as $rank) {
if ((is_null($unit) && ($size < $extent <<= 10)) || ($rank == $unit)) {
break;
}
}
return number_format($size / ($extent >> 10), $decemals) . $rank;
}
} }

View file

@ -0,0 +1,37 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class BackupLog extends Component
{
public $backupId;
public $backupLog;
public function pullBackupLog()
{
$findBackup = \App\Models\Backup::where('id', $this->backupId)->first();
if ($findBackup) {
$backupLog = $findBackup->path . '/backup.log';
if (file_exists($backupLog)) {
$backupLogContent = file_get_contents($backupLog);
// Get last 1000 lines of the log
$backupLogContent = substr($backupLogContent, -5000, 5000);
$backupLogContent = str_replace("\n", "<br>", $backupLogContent);
$this->backupLog = $backupLogContent;
}
}
}
public function mount($backupId)
{
$this->backupId = $backupId;
}
public function render()
{
return view('livewire.hosting-subscription-backup-log');
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class HostingSubscriptionBackupLog extends Component
{
public $hostingSubscriptionBackupId;
public $backupLog;
public function pullBackupLog()
{
$findHsb = \App\Models\HostingSubscriptionBackup::where('id', $this->hostingSubscriptionBackupId)->first();
if ($findHsb) {
$backupLog = $findHsb->path . '/backup.log';
if (file_exists($backupLog)) {
$backupLogContent = file_get_contents($backupLog);
// Get last 1000 lines of the log
$backupLogContent = substr($backupLogContent, -5000, 5000);
$backupLogContent = str_replace("\n", "<br>", $backupLogContent);
$this->backupLog = $backupLogContent;
}
}
}
public function mount($hostingSubscriptionBackupId)
{
$this->hostingSubscriptionBackupId = $hostingSubscriptionBackupId;
}
public function render()
{
return view('livewire.hosting-subscription-backup-log');
}
}

View file

@ -300,6 +300,9 @@ class Installer extends Page
$this->install_log = nl2br($this->install_log); $this->install_log = nl2br($this->install_log);
if (strpos($this->install_log, 'DONE!') !== false) { if (strpos($this->install_log, 'DONE!') !== false) {
file_put_contents(storage_path('installed'), 'installed-'.date('Y-m-d H:i:s'));
return redirect('/admin/login'); return redirect('/admin/login');
} }
@ -308,11 +311,4 @@ class Installer extends Page
} }
} }
public function install()
{
file_put_contents(storage_path('installed'), 'installed-'.date('Y-m-d H:i:s'));
return redirect('/admin/login');
}
} }

View file

@ -56,9 +56,9 @@ class Backup extends Model
// ShellApi::safeDelete($model->path, [ // ShellApi::safeDelete($model->path, [
// Storage::path('backups') // Storage::path('backups')
// ]); // ]);
if (Storage::disk('backups')->exists($model->filepath)) { // if (Storage::disk('backups')->exists($model->filepath)) {
Storage::disk('backups')->delete($model->filepath); // Storage::disk('backups')->delete($model->filepath);
} // }
} }
}); });
} }
@ -161,10 +161,12 @@ class Backup extends Model
$checkProcess = shell_exec('ps -p ' . $this->process_id . ' | grep ' . $this->process_id); $checkProcess = shell_exec('ps -p ' . $this->process_id . ' | grep ' . $this->process_id);
if (Str::contains($checkProcess, $this->process_id)) { if (Str::contains($checkProcess, $this->process_id)) {
// $backupLog = file_get_contents($this->path.'/backup.log');
// $backupLog = substr($backupLog, -3000, 3000);
$this->size = Helpers::checkPathSize($this->path); // $this->size = 0;
$this->backup_log = file_get_contents($this->path.'/backup.log'); //$this->backup_log = $backupLog;
$this->save(); // $this->save();
return [ return [
'status' => 'processing', 'status' => 'processing',
@ -281,7 +283,7 @@ class Backup extends Model
} }
// With find, we can search for all files,directories (including hidden) in the current directory and zip them // With find, we can search for all files,directories (including hidden) in the current directory and zip them
$shellFileContent .= 'cd '.$backupTempPath .' && find . -exec zip -r '.$backupFilePath.' {} \;'. PHP_EOL; $shellFileContent .= 'cd '.$backupTempPath .' && find . -exec zip --symlinks -r '.$backupFilePath.' {} \;'. PHP_EOL;
$shellFileContent .= 'rm -rf '.$backupTempPath.PHP_EOL; $shellFileContent .= 'rm -rf '.$backupTempPath.PHP_EOL;
$shellFileContent .= 'echo "Backup complete"' . PHP_EOL; $shellFileContent .= 'echo "Backup complete"' . PHP_EOL;

View file

@ -109,6 +109,11 @@ class HostingSubscription extends Model
return $this->hasMany(Domain::class); return $this->hasMany(Domain::class);
} }
public function ftpAccounts()
{
return $this->hasMany(HostingSubscriptionFtpAccount::class);
}
private function _createLinuxWebUser($model): array private function _createLinuxWebUser($model): array
{ {
$findCustomer = Customer::where('id', $model->customer_id)->first(); $findCustomer = Customer::where('id', $model->customer_id)->first();

View file

@ -122,9 +122,11 @@ class HostingSubscriptionBackup extends Model
} }
$checkProcess = shell_exec('ps -p ' . $this->process_id . ' | grep ' . $this->process_id); $checkProcess = shell_exec('ps -p ' . $this->process_id . ' | grep ' . $this->process_id);
if (Str::contains($checkProcess, $this->process_id)) { if (Str::contains($checkProcess, $this->process_id)) {
$this->size = Helpers::checkPathSize($this->temp_path); $this->size = 0;
$this->backup_log = "Backup is started with process id: $this->process_id";
$this->save(); $this->save();
return [ return [
@ -133,10 +135,11 @@ class HostingSubscriptionBackup extends Model
]; ];
} else { } else {
$this->status = 'failed'; $this->status = 'failed';
$this->backup_log = "Backup failed. Process not found";
$this->save(); $this->save();
return [ return [
'status' => 'failed', 'status' => 'failed',
'message' => 'Backup failed' 'message' => 'Backup failed. Process not found'
]; ];
} }
} }
@ -154,12 +157,13 @@ class HostingSubscriptionBackup extends Model
]; ];
} }
if ($this->status == BackupStatus::Processing) { if ($this->status !== BackupStatus::Pending) {
return [ return [
'status' => 'processing', 'status' => 'failed',
'message' => 'Backup is already processing' 'message' => 'Backup already started'
]; ];
} }
$findMainDomain = Domain::where('hosting_subscription_id', $findHostingSubscription->id) $findMainDomain = Domain::where('hosting_subscription_id', $findHostingSubscription->id)
->where('is_main', 1) ->where('is_main', 1)
->first(); ->first();

View file

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class HostingSubscriptionFtpAccount extends Model
{
use HasFactory;
protected $fillable = [
'username',
'password',
'path',
'quota',
'unlimited_quota',
];
}

View file

@ -11,7 +11,9 @@ use App\Listeners\ModelDomainCreatedListener;
use App\Listeners\ModelDomainDeletingListener; use App\Listeners\ModelDomainDeletingListener;
use App\Listeners\ModelHostingSubscriptionCreatingListener; use App\Listeners\ModelHostingSubscriptionCreatingListener;
use App\Listeners\ModelHostingSubscriptionDeletingListener; use App\Listeners\ModelHostingSubscriptionDeletingListener;
use App\Livewire\BackupLog;
use App\Livewire\Components\QuickServiceRestartMenu; use App\Livewire\Components\QuickServiceRestartMenu;
use App\Livewire\HostingSubscriptionBackupLog;
use App\Models\Domain; use App\Models\Domain;
use App\Models\HostingSubscription; use App\Models\HostingSubscription;
use App\Policies\CustomerPolicy; use App\Policies\CustomerPolicy;
@ -63,6 +65,8 @@ class AppServiceProvider extends ServiceProvider
}); });
Livewire::component('quick-service-restart-menu', QuickServiceRestartMenu::class); Livewire::component('quick-service-restart-menu', QuickServiceRestartMenu::class);
Livewire::component('hosting-subscription-backup-log', HostingSubscriptionBackupLog::class);
Livewire::component('backup-log', BackupLog::class);
Gate::define('delete-customer', [CustomerPolicy::class, 'delete']); Gate::define('delete-customer', [CustomerPolicy::class, 'delete']);

View file

@ -21,6 +21,7 @@
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"leandrocfe/filament-apex-charts": "^3.1", "leandrocfe/filament-apex-charts": "^3.1",
"mkocansey/bladewind": "^2.4", "mkocansey/bladewind": "^2.4",
"monarobase/country-list": "^3.5",
"nwidart/laravel-modules": "^10.0", "nwidart/laravel-modules": "^10.0",
"outerweb/filament-settings": "^1.2", "outerweb/filament-settings": "^1.2",
"phpseclib/phpseclib": "^3.0", "phpseclib/phpseclib": "^3.0",

12387
web/composer.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('hosting_subscription_ftp_accounts', function (Blueprint $table) {
$table->id();
$table->bigInteger('hosting_subscription_id')->nullable();
$table->string('username')->nullable();
$table->string('password')->nullable();
$table->string('path')->nullable();
$table->string('quota')->nullable();
$table->tinyInteger('unlimited_quota')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('hosting_subscription_ftp_accounts');
}
};

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

View file

@ -0,0 +1,5 @@
<div>
@livewire($component, $componentProps)
</div>

View file

@ -0,0 +1,20 @@
<div>
<div id="js-backup-log" wire:poll="pullBackupLog" class="text-left text-sm font-medium text-gray-950 dark:text-yellow-500 h-[20rem] overflow-y-scroll">
@if($this->backupLog == '')
<div class="text-center text-gray-500 dark:text-gray-400">
{{ __('No backup log available.') }}
</div>
@else
{!! $this->backupLog !!}
@endif
</div>
<script>
window.setInterval(function() {
var elem = document.getElementById('js-backup-log');
elem.scrollTop = elem.scrollHeight;
}, 3000);
</script>
</div>

View file

@ -4,6 +4,7 @@ namespace tests\Unit;
use App\Filament\Enums\BackupStatus; use App\Filament\Enums\BackupStatus;
use App\Helpers; use App\Helpers;
use App\Jobs\ProcessHostingSubscriptionBackup;
use App\Models\Backup; use App\Models\Backup;
use App\Models\Customer; use App\Models\Customer;
use App\Models\Database; use App\Models\Database;
@ -18,6 +19,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Tests\Feature\Api\ActionTestCase; use Tests\Feature\Api\ActionTestCase;
use Illuminate\Support\Facades\Queue;
class HostingSubscriptionBackupTest extends ActionTestCase class HostingSubscriptionBackupTest extends ActionTestCase
{ {
@ -26,9 +28,17 @@ class HostingSubscriptionBackupTest extends ActionTestCase
ini_set('memory_limit', '-1'); ini_set('memory_limit', '-1');
ini_set('max_execution_time', 0); ini_set('max_execution_time', 0);
Queue::fake();
$chs = $this->_createHostingSubscription(); $chs = $this->_createHostingSubscription();
Artisan::call('phyre:create-daily-full-hosting-subscriptions-backup'); $newBackup = new HostingSubscriptionBackup();
$newBackup->backup_type = 'full';
$newBackup->hosting_subscription_id = $chs['hostingSubscriptionId'];
$newBackup->save();
$phsb = new ProcessHostingSubscriptionBackup($newBackup->id);
$phsb->handle();
$findLastBackup = HostingSubscriptionBackup::where('hosting_subscription_id', $chs['hostingSubscriptionId']) $findLastBackup = HostingSubscriptionBackup::where('hosting_subscription_id', $chs['hostingSubscriptionId'])
->first(); ->first();
@ -41,8 +51,6 @@ class HostingSubscriptionBackupTest extends ActionTestCase
$backupFinished = false; $backupFinished = false;
for ($i = 0; $i < 100; $i++) { for ($i = 0; $i < 100; $i++) {
Artisan::call('phyre:run-hosting-subscriptions-backup-checks');
$findLastBackup = HostingSubscriptionBackup::where('id', $findLastBackup->id)->first(); $findLastBackup = HostingSubscriptionBackup::where('id', $findLastBackup->id)->first();
if ($findLastBackup->status == BackupStatus::Completed) { if ($findLastBackup->status == BackupStatus::Completed) {
$backupFinished = true; $backupFinished = true;
@ -68,14 +76,15 @@ class HostingSubscriptionBackupTest extends ActionTestCase
$backup->hosting_subscription_id = $chs['hostingSubscriptionId']; $backup->hosting_subscription_id = $chs['hostingSubscriptionId'];
$backup->save(); $backup->save();
$phsb = new ProcessHostingSubscriptionBackup($backup->id);
$phsb->handle();
$backupId = $backup->id; $backupId = $backup->id;
$findBackup = false; $findBackup = false;
$backupCompleted = false; $backupCompleted = false;
for ($i = 0; $i < 100; $i++) { for ($i = 0; $i < 100; $i++) {
Artisan::call('phyre:run-hosting-subscriptions-backup-checks');
$findBackup = HostingSubscriptionBackup::where('id', $backupId)->first(); $findBackup = HostingSubscriptionBackup::where('id', $backupId)->first();
if ($findBackup) { if ($findBackup) {
if ($findBackup->status == BackupStatus::Completed) { if ($findBackup->status == BackupStatus::Completed) {