update sync vm

This commit is contained in:
pbao9
2026-02-05 21:57:57 +07:00
parent 3a01d07a74
commit eef04846a8
11 changed files with 436 additions and 76 deletions
@@ -1,38 +1,148 @@
<?php
<?php
namespace App\Http\Controllers\admin\vm;
use App\Http\Controllers\Controller;
use App\Jobs\VirtualMachine\Sync\SyncVirtualMachine;
use App\Repositories\Module\VirtualMachine\VirtualMachineRepositoryInterface;
use App\Repositories\vCenter\vCenterRepositoryInterface;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Yajra\DataTables\Facades\DataTables;
class VirtualMachineController extends Controller
{
public function index()
public function __construct(
public VirtualMachineRepositoryInterface $virtualMachineRepository,
public vCenterRepositoryInterface $vCenterRepository
) {
parent::__construct();
}
public function index(Request $request)
{
return view('administrator.pages.package.vm.index');
$vms = $this->virtualMachineRepository->filter(
[
'page' => $request->get('page') ?? 1
]
);
$title = 'List all Virtual Machine';
return view('administrator.pages.package.vm.index', compact('vms', 'title'));
}
public function create()
{
return view('administrator.pages.package.vm.add');
$title = 'Add Virtual Machine';
return view('administrator.pages.package.vm.add', compact('title'));
}
public function store(Request $request)
{
return view('administrator.pages.package.vm.index');
$request->validate([
'vm_id' => 'required|unique:virtual_machines,vm_id',
'name' => 'required',
]);
DB::beginTransaction();
try {
$data = $request->all();
if (empty($data['uuid'])) {
$data['uuid'] = Str::uuid();
}
$this->virtualMachineRepository->create($data);
DB::commit();
} catch (Exception $error) {
DB::rollBack();
return back()->withErrors($error->getMessage());
}
return redirect()->route('package.vm.index')->with('success', 'Add new VM successfully!');
}
public function edit($id)
{
return view('administrator.pages.package.vm.edit');
$vm = $this->virtualMachineRepository->find($id);
if (!$vm) {
return back()->withErrors('Invalid request.');
}
$title = 'Edit Virtual Machine';
return view('administrator.pages.package.vm.edit', compact('title', 'vm'));
}
public function update(Request $request, $id)
{
return view('administrator.pages.package.vm.index');
$vm = $this->virtualMachineRepository->find($id);
if (!$vm) {
return back()->withErrors('Invalid request.');
}
$request->validate([
'name' => 'required',
'vm_id' => 'required|unique:virtual_machines,vm_id,' . $id,
]);
DB::beginTransaction();
try {
$this->virtualMachineRepository->update($id, $request->all());
DB::commit();
} catch (Exception $error) {
DB::rollBack();
return back()->withErrors($error->getMessage());
}
return redirect()->route('package.vm.index')->with('success', 'Update VM successfully!');
}
public function destroy($id)
{
return view('administrator.pages.package.vm.index');
$vm = $this->virtualMachineRepository->find($id);
if (!$vm) {
return back()->withErrors('Invalid request.');
}
DB::beginTransaction();
try {
$this->virtualMachineRepository->delete($id);
DB::commit();
} catch (Exception $error) {
DB::rollBack();
return back()->withErrors($error->getMessage());
}
return redirect()->route('package.vm.index')->with('success', 'Deleted VM successfully!');
}
public function sync(SyncVirtualMachine $syncVM)
{
dispatch($syncVM)->delay(now()->addSeconds(5));
return back()->with('success', 'The process is being established, please wait');
}
public function datatable(Request $request)
{
$query = $this->virtualMachineRepository->filter(
$request->all(),
['datacenter', 'host'],
[],
['*'],
true
);
return DataTables::eloquent($query)
->addColumn(
'action',
function ($d) {
return '<a href="' . route('package.vm.edit', $d->id) . '" class="btn btn-warning btn-sm"><i class="fas fa-search"></i></a>
<form method="post" class="d-inline" action="' . route('package.vm.destroy', $d->id) . '">
' . csrf_field() . '
' . method_field('delete') . '
<button type="submit" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</form>';
}
)
->editColumn('updated_at', function ($d) {
return $d->updated_at ? $d->updated_at->format('H:i:s d/m/Y') : '';
})
->rawColumns(['action'])
->make(true);
}
}
+23
View File
@@ -77,6 +77,29 @@ class vCenter
return "Login_Again";
}
public function getAllVirtualMachine($datacenter = '')
{
$check = $this->checkApiExits();
if ($check) {
$params = [];
if (!empty($datacenter)) {
$params['filter.datacenters'] = $datacenter;
}
$response = Http::withoutVerifying()
->withHeaders([
'vmware-api-session-id' => $this->token
])
->get("https://$this->apiUri/rest/vcenter/vm", $params)->json();
if (!isset($response['type'])) {
return $response['value'];
}
return false;
}
return "Login_Again";
}
public function checkApiExits(): bool
{
@@ -0,0 +1,146 @@
<?php
namespace App\Jobs\VirtualMachine\Sync;
use App\Http\Services\Telegram;
use App\Http\Services\vCenter as ServiceVCenter;
use App\Models\ModuleDatacenter;
use App\Models\VirtualMachines;
use App\Models\vCenter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Throwable;
class SyncVirtualMachine implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $vCenter;
protected $telegram;
/**
* Create a new job instance.
*/
public function __construct()
{
// Keep constructor empty or minimal to ensure lightweight serialization
}
/**
* Execute the job.
*/
public function handle()
{
$this->vCenter = new ServiceVCenter;
$this->telegram = new Telegram();
try {
$data = vCenter::select('id', 'host', 'user', 'password', 'token')->get();
$vCenterCount = 0;
$vmTotalCount = 0;
foreach ($data as $item) {
Log::info("Processing vCenter: {$item->host}");
$this->vCenter->apiUri = $item->host;
// Always login to get a fresh token
$token = $this->vCenter->loginServer($item);
if ($token) {
$item->token = $token;
$item->save();
$this->vCenter->token = $token;
} else {
$this->vCenter->token = null;
Log::error("Login failed for {$item->host}");
}
if (!$this->vCenter->token) {
continue;
}
$datacenters = ModuleDatacenter::where('vcenter_id', $item->id)->get();
if ($datacenters->count() > 0) {
foreach ($datacenters as $dc) {
$vms = $this->vCenter->getAllVirtualMachine($dc->datacenter_id);
$vmCount = $this->processVMs($vms, $item, $dc->id); // Extract method for reuse
$vmTotalCount += $vmCount;
}
} else {
// Fallback: Sync all VMs without datacenter context
Log::info("No datacenters found, fetching all VMs for {$item->host}");
$vms = $this->vCenter->getAllVirtualMachine();
$vmCount = $this->processVMs($vms, $item, null);
$vmTotalCount += $vmCount;
}
$vCenterCount++; // Increment vCenter synced count
}
// Notify success via Telegram
$this->telegram->SendJobRunInfo('SyncVirtualMachine', 'Success', [
'message' => 'Sync VMs completed successfully',
'synced_vcenter_count' => $vCenterCount,
'synced_vm_count' => $vmTotalCount,
]);
} catch (Throwable $e) {
Log::error("SyncVirtualMachine Job Failed: " . $e->getMessage());
// Gửi log lỗi qua Telegram
if (isset($this->telegram)) {
$this->telegram->SendJobRunInfo('SyncVirtualMachine', 'Failed', [
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
]);
}
}
}
private function processVMs($vms, $vCenterItem, $datacenterId)
{
// If token expired, retry login (handled inside loop in previous logic, but simpler here if we just handle the array check)
if ($vms === 'Login_Again') {
Log::info("Token expired, retrying login for {$vCenterItem->host}");
$token = $this->vCenter->loginServer($vCenterItem);
if ($token) {
$vCenterItem->token = $token;
$vCenterItem->save();
$this->vCenter->token = $token;
// Retry fetch
$vms = $this->vCenter->getAllVirtualMachine($datacenterId ? ModuleDatacenter::find($datacenterId)->datacenter_id : '');
}
}
if (!is_array($vms)) {
Log::warning("Failed to fetch VMs or empty response. Response: " . json_encode($vms));
return 0;
}
Log::info("Fetched " . count($vms) . " VMs");
$count = 0;
foreach ($vms as $vmData) {
$vmObj = VirtualMachines::firstOrNew(['vm_id' => $vmData['vm']]);
if (!$vmObj->exists) {
$vmObj->uuid = Str::uuid();
}
$vmObj->name = $vmData['name'];
$vmObj->power_state = $vmData['power_state'];
$vmObj->cpu = $vmData['cpu_count'] ?? 0;
$vmObj->ram_mb = $vmData['memory_size_MiB'] ?? 0;
$vmObj->datacenter_id = $datacenterId; // Assign datacenter if known
$vmObj->last_synced_at = now();
$vmObj->save();
$count++;
}
return $count;
}
}
+25
View File
@@ -8,4 +8,29 @@ use Illuminate\Database\Eloquent\Model;
class VirtualMachines extends Model
{
use HasFactory;
protected $fillable = [
'uuid',
'vm_id',
'name',
'power_state',
'folder',
'is_template',
'cpu',
'ram_mb',
'is_system',
'last_synced_at',
'datacenter_id',
'host_id'
];
public function datacenter()
{
return $this->belongsTo(ModuleDatacenter::class, 'datacenter_id');
}
public function host()
{
return $this->belongsTo(ModuleHost::class, 'host_id');
}
}
+8
View File
@@ -94,6 +94,8 @@ use App\Repositories\vCenter\vCenterCachedRepository;
use App\Repositories\vCenter\vCenterRepositoryInterface;
use App\Repositories\VPS\VPSCachedRepository;
use App\Repositories\VPS\VPSRepositoryInterface;
use App\Repositories\Module\VirtualMachine\VirtualMachineRepository;
use App\Repositories\Module\VirtualMachine\VirtualMachineRepositoryInterface;
class RepositoryProvider extends ServiceProvider
{
@@ -335,6 +337,12 @@ class RepositoryProvider extends ServiceProvider
IPPoolRepositoryInterface::class,
IPPoolCachedRepository::class
);
//Module VirtualMachine
$this->app->bind(
VirtualMachineRepositoryInterface::class,
VirtualMachineRepository::class
);
}
/**
@@ -0,0 +1,62 @@
<?php
namespace App\Repositories\Module\VirtualMachine;
use App\Models\VirtualMachines;
use App\Repositories\BaseRepository;
class VirtualMachineRepository extends BaseRepository implements VirtualMachineRepositoryInterface
{
public function getModel()
{
return VirtualMachines::class;
}
public function filter(
array $data = [],
array $relationships = [],
array $withCount = [],
array $select = ['*'],
bool $returnQuery = false
) {
$query = $this->model
->select($select)
->with($relationships);
if (!empty($data['datacenter_id'])) {
$query->where('datacenter_id', $data['datacenter_id']);
}
if (!empty($data['host_id'])) {
$query->where('host_id', $data['host_id']);
}
if (!empty($data['name'])) {
$query->where('name', 'like', '%' . $data['name'] . '%');
}
if (!empty($withCount)) {
$query->withCount($withCount);
}
if ($returnQuery) {
return $query;
}
if (!empty($data['paginate']) || !empty($data['page'])) {
return $query->paginate(
$data['per_page'] ?? 15
);
}
return $query->get();
}
public function findBy(string $key = '', string|int|null $value = '')
{
if (empty($key) || empty($value)) {
return false;
}
return $this->model->where($key, $value)->first();
}
}
@@ -0,0 +1,15 @@
<?php
namespace App\Repositories\Module\VirtualMachine;
use App\Repositories\BaseRepositoryInterface;
interface VirtualMachineRepositoryInterface extends BaseRepositoryInterface {
public function filter(
array $data = [],
array $relationships = [],
array $withCount = [],
array $select = ['*'],
bool $returnQuery = false
);
}
@@ -26,7 +26,6 @@ return new class extends Migration {
$table->boolean('is_system')->default(false); // vCLS, system VM
$table->timestamp('last_synced_at')->nullable();
$table->foreignId('datacenter_id')->constrained('module_datacenter')->nullable();
$table->foreignId('host_id')->constrained('module_host')->nullable();
$table->timestamps();
});
}
@@ -0,0 +1,5 @@
<x-button-crm type="button" variant="primary" size="sm" route="package.vm.create" icon="plus-circle">Add
VM</x-button-crm>
@include("administrator.pages.block.Sync.All")
<x-button-crm type="button" variant="secondary" size="sm" route="package.vm.sync" icon="refresh-cw">Sync
VM</x-button-crm>
@@ -1,79 +1,39 @@
@php use App\Http\Helpers\Helper; @endphp
@extends('administrator.layout.master')
@push('plugin-styles')
@foreach (config('assets.datatable.css') as $datatable)
<link href="{{ asset($datatable) }}" rel="stylesheet" />
@endforeach
@endpush
@section('breadcrumb')
@php
$breadcrumbItems = [
['label' => 'Modules'],
['label' => 'Package vCenter'],
['label' => 'Virtual Machine', 'active' => true],
];
@endphp
<x-admin-breadcrumb :items="$breadcrumbItems" />
@endsection
@section('content')
@if(auth()->guard('admins')->user()->can("manager package module"))
<div class="card card-primary card-outline">
<div class="card-header">
@include("administrator.pages.block.Sync.Host")
@include("administrator.pages.block.Sync.VM")
</div>
<div class="card-body">
<table class="table table-invoice table-bordered">
<thead>
<tr>
<th class="text-center">
<label>
<input type="checkbox" name="check" class="check-all">
</label>
</th>
<th class="text-center">Host Name</th>
<th class="text-center">Datacenter Name</th>
<th class="text-center">vCenter Host</th>
<th class="text-center">Last Updated</th>
<th style="width: 40px"></th>
<th style="width: 40px"></th>
</tr>
</thead>
<tbody>
@if(count($hosts) > 0)
@foreach($hosts->all() as $item)
<tr>
<td class="text-center">
<label for=""></label><input type="checkbox" name="id[]" value="{{ $item->id }}"
id="">
</td>
<td class="text-center">
{{ $item->name }}
</td>
<td class="text-center">
@isset($item->datacenter->name)
{{$item->datacenter->name}}
@endisset
</td>
<td class="text-center">
@isset($item->datacenter->vCenter->host)
{{$item->datacenter->vCenter->host}}
@endisset
</td>
<td class="text-center">
@time($item->updated_at)
</td>
<td class="text-center">
<a class="btn btn-warning btn-sm"
href="{{ route('package.host.edit', ['id' => $item->id]) }}">
<i class="fas fa-search"></i>
</a>
</td>
<td>
@if(auth()->guard('admins')->user()->can("delete module vcenter"))
<form method="post" class="d-inline"
action="{{ route('package.host.destroy', ['id' => $item->id]) }}">
@method('delete')
@csrf
<button type="submit" data-toggle="tooltip" data-placement="top" title=""
class="btn btn-sm btn-danger" data-original-title="Xoá">
<i class="fas fa-trash"></i>
</button>
</form>
@endif
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
<x-datatable id="vmTable" ajax="{{ route('package.vm.datatable') }}" :columns="[
['key' => 'vm_id', 'label' => 'VM ID'],
['key' => 'name', 'label' => 'Name'],
['key' => 'power_state', 'label' => 'State'],
['key' => 'cpu', 'label' => 'CPU'],
['key' => 'ram_mb', 'label' => 'RAM (MB)'],
['key' => 'datacenter.name', 'label' => 'Datacenter'],
['key' => 'host.name', 'label' => 'Host'],
['key' => 'action', 'label' => 'Action', 'orderable' => false, 'searchable' => false],
]" />
</div>
<div class="card-footer">
{{ $hosts->withQueryString()->links() }}
</div>
</div>
@else
@@ -83,3 +43,8 @@
</div>
@endif
@endsection
@push('plugin-scripts')
@foreach (config('assets.datatable.js') as $datatable)
<script src="{{ asset($datatable) }}"></script>
@endforeach
@endpush
+2
View File
@@ -331,6 +331,8 @@ Route::prefix('administrator')->group(function () {
'controller' => VirtualMachineController::class
], function () {
Route::get('list-all-vm', 'index')->name('index');
Route::get('datatable', 'datatable')->name('datatable');
Route::get('sync', 'sync')->name('sync');
Route::get('create', 'create')->name('create');
Route::post('create', 'store')->name('store');
Route::get('edit/{id}', 'edit')->name('edit');