mirror of
https://github.com/PhyreApps/PhyrePanel.git
synced 2024-11-25 00:50:32 +00:00
Compare commits
19 commits
030d73cf77
...
8897c4818c
Author | SHA1 | Date | |
---|---|---|---|
|
8897c4818c | ||
|
bb0945a63f | ||
|
54238b30b3 | ||
|
a77bc147a4 | ||
|
97ee0da261 | ||
|
5b20b10b90 | ||
|
1030589021 | ||
|
71a3ac48e1 | ||
|
0b11ce9324 | ||
|
bca5e2b9b1 | ||
|
3d91014e34 | ||
|
7d1b8e7d28 | ||
|
4a502ffb15 | ||
|
828d650d6d | ||
|
969cd8c376 | ||
|
079f18f728 | ||
|
538246e795 | ||
|
18a7c686c4 | ||
|
9c40235dba |
32 changed files with 12911 additions and 96 deletions
|
@ -16,7 +16,7 @@ The admin panel can be opened on port: yourserver.com:8443
|
|||
## Build Status
|
||||
|
||||
### 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)
|
||||
|
||||
All development is done on the `dev` branch. The `master` branch is used for stable releases.
|
||||
|
|
|
@ -9,6 +9,9 @@ fi
|
|||
# Go to web directory
|
||||
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
|
||||
MYSQL_PHYRE_ROOT_USERNAME="phyre"
|
||||
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"
|
||||
|
|
|
@ -90,6 +90,9 @@ fi
|
|||
# Go to web directory
|
||||
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
|
||||
MYSQL_PHYRE_ROOT_USERNAME="phyre"
|
||||
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"
|
||||
|
|
|
@ -9,6 +9,9 @@ fi
|
|||
# Go to web directory
|
||||
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
|
||||
MYSQL_PHYRE_ROOT_USERNAME="phyre"
|
||||
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"
|
||||
|
|
|
@ -92,6 +92,9 @@ fi
|
|||
# Go to web directory
|
||||
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
|
||||
MYSQL_PHYRE_ROOT_USERNAME="phyre"
|
||||
MYSQL_PHYRE_ROOT_PASSWORD="$(tr -dc a-za-z0-9 </dev/urandom | head -c 32; echo)"
|
||||
|
|
|
@ -43,7 +43,6 @@ class DomainIsCreatedListener
|
|||
if (! in_array('letsencrypt', $findHostingPlan->additional_services)) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
$generalSettings = Settings::general();
|
||||
|
||||
|
|
|
@ -2,15 +2,18 @@
|
|||
|
||||
namespace App\Filament\Pages\Settings;
|
||||
|
||||
use App\Helpers;
|
||||
use App\MasterDomain;
|
||||
use Closure;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Monarobase\CountryList\CountryList;
|
||||
use Outerweb\FilamentSettings\Filament\Pages\Settings as BaseSettings;
|
||||
use Symfony\Component\Console\Input\Input;
|
||||
|
||||
|
@ -41,16 +44,15 @@ class Settings extends BaseSettings
|
|||
TextInput::make('general.brand_logo_url'),
|
||||
ColorPicker::make('general.brand_primary_color'),
|
||||
|
||||
|
||||
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_country'),
|
||||
Select::make('general.master_country')
|
||||
->searchable()
|
||||
->options(function () {
|
||||
$countryList = new CountryList();
|
||||
return $countryList->getList();
|
||||
}),
|
||||
TextInput::make('general.master_locality'),
|
||||
TextInput::make('general.organization_name'),
|
||||
]),
|
||||
|
|
|
@ -7,6 +7,7 @@ use App\Filament\Enums\BackupStatus;
|
|||
use App\Filament\Enums\BackupType;
|
||||
use App\Filament\Resources\BackupResource\Pages;
|
||||
use app\Filament\Resources\BackupResource\Widgets\BackupStats;
|
||||
use App\Helpers;
|
||||
use App\Models\Backup;
|
||||
use App\Models\HostingSubscription;
|
||||
use Filament\Forms\Components\Select;
|
||||
|
@ -83,17 +84,17 @@ class BackupResource extends Resource
|
|||
return $backup->created_at ? $backup->created_at : 'N/A';
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('completed_at')
|
||||
->label('Completed time')
|
||||
->state(function (Backup $backup) {
|
||||
$diff = \Carbon\Carbon::parse($backup->completed_at)
|
||||
->diffForHumans($backup->created_at);
|
||||
return $backup->completed_at ? $diff : 'N/A';
|
||||
}),
|
||||
// Tables\Columns\TextColumn::make('completed_at')
|
||||
// ->label('Completed time')
|
||||
// ->state(function (Backup $backup) {
|
||||
// $diff = \Carbon\Carbon::parse($backup->completed_at)
|
||||
// ->diffForHumans($backup->created_at);
|
||||
// return $backup->completed_at ? $diff : 'N/A';
|
||||
// }),
|
||||
|
||||
Tables\Columns\TextColumn::make('size')
|
||||
->state(function (Backup $backup) {
|
||||
return $backup->size ? $backup->size : 'N/A';
|
||||
return ($backup->size > 0) ? Helpers::getHumanReadableSize($backup->size) : 'N/A';
|
||||
}),
|
||||
])
|
||||
->filters([
|
||||
|
@ -112,6 +113,26 @@ class BackupResource extends Resource
|
|||
|
||||
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(),
|
||||
])
|
||||
->defaultSort('id', 'desc')
|
||||
|
@ -138,9 +159,10 @@ class BackupResource extends Resource
|
|||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListBackups::route('/'),
|
||||
'create' => Pages\CreateBackup::route('/create'),
|
||||
'view' => Pages\ViewBackup::route('/{record}'),
|
||||
// 'index' => Pages\ListBackups::route('/'),
|
||||
// 'create' => Pages\CreateBackup::route('/create'),
|
||||
// 'view' => Pages\ViewBackup::route('/{record}'),
|
||||
'index' => Pages\ManageBackups::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
|||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Flowframe\Trend\Trend;
|
||||
use Flowframe\Trend\TrendValue;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class BackupStats extends BaseWidget
|
||||
{
|
||||
|
@ -25,6 +26,8 @@ class BackupStats extends BaseWidget
|
|||
|
||||
protected function getStats(): array
|
||||
{
|
||||
|
||||
$stats = Cache::remember('backup-stats', 300, function () {
|
||||
$findBackups = Backup::select(['id'])->where('status', 'processing')->get();
|
||||
if ($findBackups->count() > 0) {
|
||||
foreach ($findBackups as $backup) {
|
||||
|
@ -36,10 +39,15 @@ class BackupStats extends BaseWidget
|
|||
if (is_dir($backupPath)) {
|
||||
$usedSpace = $this->getDirectorySize($backupPath);
|
||||
}
|
||||
return [
|
||||
'totalBackups' => Backup::count(),
|
||||
'usedSpace' => $usedSpace,
|
||||
];
|
||||
});
|
||||
|
||||
return [
|
||||
Stat::make('Total backups', Backup::count()),
|
||||
Stat::make('Total used space', $usedSpace),
|
||||
Stat::make('Total backups', $stats['totalBackups']),
|
||||
Stat::make('Total used space', $stats['usedSpace']),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\HostingSubscriptionResource\Pages;
|
||||
use app\Filament\Resources\HostingSubscriptionResource\Pages\ManageHostingSubscriptionFtpAccounts;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Domain;
|
||||
use App\Models\HostingSubscription;
|
||||
|
@ -188,6 +189,7 @@ class HostingSubscriptionResource extends Resource
|
|||
Pages\EditHostingSubscription::class,
|
||||
Pages\ManageHostingSubscriptionDatabases::class,
|
||||
Pages\ManageHostingSubscriptionBackups::class,
|
||||
ManageHostingSubscriptionFtpAccounts::class
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -207,6 +209,7 @@ class HostingSubscriptionResource extends Resource
|
|||
'edit' => Pages\EditHostingSubscription::route('/{record}/edit'),
|
||||
'databases' => Pages\ManageHostingSubscriptionDatabases::route('/{record}/databases'),
|
||||
'backups' => Pages\ManageHostingSubscriptionBackups::route('/{record}/backups'),
|
||||
'ftp-accounts' => Pages\ManageHostingSubscriptionFtpAccounts::route('/{record}/ftp-accounts'),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -7,11 +7,13 @@ use App\Filament\Enums\BackupStatus;
|
|||
use App\Filament\Enums\HostingSubscriptionBackupType;
|
||||
use App\Filament\Resources\Blog\PostResource;
|
||||
use App\Filament\Resources\HostingSubscriptionResource;
|
||||
use App\Helpers;
|
||||
use App\Models\Backup;
|
||||
use App\Models\DatabaseUser;
|
||||
use App\Models\HostingSubscriptionBackup;
|
||||
use App\Models\RemoteDatabaseServer;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Infolists\Components\IconEntry;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
@ -21,6 +23,7 @@ use Filament\Tables;
|
|||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use JaOcero\RadioDeck\Forms\Components\RadioDeck;
|
||||
|
||||
class ManageHostingSubscriptionBackups extends ManageRelatedRecords
|
||||
|
@ -47,7 +50,7 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
|
|||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return 'Manage Backups';
|
||||
return 'Backups';
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
|
@ -88,18 +91,8 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
|
|||
|
||||
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
|
||||
->recordTitleAttribute('id')
|
||||
->recordTitleAttribute('file_name')
|
||||
->columns([
|
||||
|
||||
Tables\Columns\TextColumn::make('backup_type')
|
||||
|
@ -117,7 +110,7 @@ class ManageHostingSubscriptionBackups extends ManageRelatedRecords
|
|||
|
||||
Tables\Columns\TextColumn::make('size')
|
||||
->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);
|
||||
}),
|
||||
|
||||
|
||||
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\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
|
|
|
@ -41,7 +41,7 @@ class ManageHostingSubscriptionDatabases extends ManageRelatedRecords
|
|||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return 'Manage Databases';
|
||||
return 'Databases';
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
|
|
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -37,4 +37,23 @@ class Helpers
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
37
web/app/Livewire/BackupLog.php
Normal file
37
web/app/Livewire/BackupLog.php
Normal 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');
|
||||
}
|
||||
}
|
37
web/app/Livewire/HostingSubscriptionBackupLog.php
Normal file
37
web/app/Livewire/HostingSubscriptionBackupLog.php
Normal 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');
|
||||
}
|
||||
}
|
|
@ -300,6 +300,9 @@ class Installer extends Page
|
|||
$this->install_log = nl2br($this->install_log);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,9 +56,9 @@ class Backup extends Model
|
|||
// ShellApi::safeDelete($model->path, [
|
||||
// Storage::path('backups')
|
||||
// ]);
|
||||
if (Storage::disk('backups')->exists($model->filepath)) {
|
||||
Storage::disk('backups')->delete($model->filepath);
|
||||
}
|
||||
// if (Storage::disk('backups')->exists($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);
|
||||
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->backup_log = file_get_contents($this->path.'/backup.log');
|
||||
$this->save();
|
||||
// $this->size = 0;
|
||||
//$this->backup_log = $backupLog;
|
||||
// $this->save();
|
||||
|
||||
return [
|
||||
'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
|
||||
$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 .= 'echo "Backup complete"' . PHP_EOL;
|
||||
|
|
|
@ -109,6 +109,11 @@ class HostingSubscription extends Model
|
|||
return $this->hasMany(Domain::class);
|
||||
}
|
||||
|
||||
public function ftpAccounts()
|
||||
{
|
||||
return $this->hasMany(HostingSubscriptionFtpAccount::class);
|
||||
}
|
||||
|
||||
private function _createLinuxWebUser($model): array
|
||||
{
|
||||
$findCustomer = Customer::where('id', $model->customer_id)->first();
|
||||
|
|
|
@ -122,9 +122,11 @@ class HostingSubscriptionBackup extends Model
|
|||
}
|
||||
|
||||
$checkProcess = shell_exec('ps -p ' . $this->process_id . ' | grep ' . $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();
|
||||
|
||||
return [
|
||||
|
@ -133,10 +135,11 @@ class HostingSubscriptionBackup extends Model
|
|||
];
|
||||
} else {
|
||||
$this->status = 'failed';
|
||||
$this->backup_log = "Backup failed. Process not found";
|
||||
$this->save();
|
||||
return [
|
||||
'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 [
|
||||
'status' => 'processing',
|
||||
'message' => 'Backup is already processing'
|
||||
'status' => 'failed',
|
||||
'message' => 'Backup already started'
|
||||
];
|
||||
}
|
||||
|
||||
$findMainDomain = Domain::where('hosting_subscription_id', $findHostingSubscription->id)
|
||||
->where('is_main', 1)
|
||||
->first();
|
||||
|
|
19
web/app/Models/HostingSubscriptionFtpAccount.php
Normal file
19
web/app/Models/HostingSubscriptionFtpAccount.php
Normal 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',
|
||||
];
|
||||
}
|
|
@ -11,7 +11,9 @@ use App\Listeners\ModelDomainCreatedListener;
|
|||
use App\Listeners\ModelDomainDeletingListener;
|
||||
use App\Listeners\ModelHostingSubscriptionCreatingListener;
|
||||
use App\Listeners\ModelHostingSubscriptionDeletingListener;
|
||||
use App\Livewire\BackupLog;
|
||||
use App\Livewire\Components\QuickServiceRestartMenu;
|
||||
use App\Livewire\HostingSubscriptionBackupLog;
|
||||
use App\Models\Domain;
|
||||
use App\Models\HostingSubscription;
|
||||
use App\Policies\CustomerPolicy;
|
||||
|
@ -63,6 +65,8 @@ class AppServiceProvider extends ServiceProvider
|
|||
});
|
||||
|
||||
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']);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"laravel/tinker": "^2.8",
|
||||
"leandrocfe/filament-apex-charts": "^3.1",
|
||||
"mkocansey/bladewind": "^2.4",
|
||||
"monarobase/country-list": "^3.5",
|
||||
"nwidart/laravel-modules": "^10.0",
|
||||
"outerweb/filament-settings": "^1.2",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
|
|
12387
web/composer.lock
generated
Normal file
12387
web/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
@ -0,0 +1,5 @@
|
|||
<div>
|
||||
|
||||
@livewire($component, $componentProps)
|
||||
|
||||
</div>
|
|
@ -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>
|
|
@ -4,6 +4,7 @@ namespace tests\Unit;
|
|||
|
||||
use App\Filament\Enums\BackupStatus;
|
||||
use App\Helpers;
|
||||
use App\Jobs\ProcessHostingSubscriptionBackup;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Database;
|
||||
|
@ -18,6 +19,7 @@ use Illuminate\Support\Facades\DB;
|
|||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\Feature\Api\ActionTestCase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
class HostingSubscriptionBackupTest extends ActionTestCase
|
||||
{
|
||||
|
@ -26,9 +28,17 @@ class HostingSubscriptionBackupTest extends ActionTestCase
|
|||
ini_set('memory_limit', '-1');
|
||||
ini_set('max_execution_time', 0);
|
||||
|
||||
Queue::fake();
|
||||
|
||||
$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'])
|
||||
->first();
|
||||
|
@ -41,8 +51,6 @@ class HostingSubscriptionBackupTest extends ActionTestCase
|
|||
$backupFinished = false;
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
|
||||
Artisan::call('phyre:run-hosting-subscriptions-backup-checks');
|
||||
|
||||
$findLastBackup = HostingSubscriptionBackup::where('id', $findLastBackup->id)->first();
|
||||
if ($findLastBackup->status == BackupStatus::Completed) {
|
||||
$backupFinished = true;
|
||||
|
@ -68,14 +76,15 @@ class HostingSubscriptionBackupTest extends ActionTestCase
|
|||
$backup->hosting_subscription_id = $chs['hostingSubscriptionId'];
|
||||
$backup->save();
|
||||
|
||||
$phsb = new ProcessHostingSubscriptionBackup($backup->id);
|
||||
$phsb->handle();
|
||||
|
||||
$backupId = $backup->id;
|
||||
|
||||
$findBackup = false;
|
||||
$backupCompleted = false;
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
|
||||
Artisan::call('phyre:run-hosting-subscriptions-backup-checks');
|
||||
|
||||
$findBackup = HostingSubscriptionBackup::where('id', $backupId)->first();
|
||||
if ($findBackup) {
|
||||
if ($findBackup->status == BackupStatus::Completed) {
|
||||
|
|
Loading…
Reference in a new issue