919 lines
32 KiB
PHP
919 lines
32 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Mail\VmSuspendedMail;
|
|
use App\Models\ModuleIpAddress;
|
|
use App\Models\ModuleVmware;
|
|
use App\Models\VirtualMachines;
|
|
use App\Repositories\Module\Datacenter\DatacenterRepositoryInterface;
|
|
use App\Repositories\Module\Datastore\DatastoreRepositoryInterface;
|
|
use App\Repositories\Module\Folder\FolderRepositoryInterface;
|
|
use App\Repositories\Module\Host\HostRepositoryInterface;
|
|
use App\Repositories\Module\IpAddress\IpAddressRepositoryInterface;
|
|
use App\Repositories\Module\Network\NetworkRepositoryInterface;
|
|
use App\Repositories\vCenter\vCenterRepositoryInterface;
|
|
use Illuminate\Http\Request;
|
|
use App\Support\TlsHttpClientFactory;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Yajra\DataTables\Facades\DataTables;
|
|
|
|
class VCenterManagerController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly vCenterRepositoryInterface $vCenterRepo,
|
|
private readonly DatacenterRepositoryInterface $datacenterRepo,
|
|
private readonly HostRepositoryInterface $hostRepo,
|
|
private readonly DatastoreRepositoryInterface $datastoreRepo,
|
|
private readonly NetworkRepositoryInterface $networkRepo,
|
|
private readonly FolderRepositoryInterface $folderRepo,
|
|
private readonly IpAddressRepositoryInterface $ipRepo,
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* Main view — server-render vCenters + their datacenters for initial tree.
|
|
*/
|
|
public function index()
|
|
{
|
|
$vcenters = $this->vCenterRepo->filter(
|
|
[],
|
|
['datacenters'],
|
|
['datacenters'],
|
|
['id', 'host', 'status']
|
|
);
|
|
|
|
return view('administrator.pages.vcenter.manager', compact('vcenters'));
|
|
}
|
|
|
|
/**
|
|
* AJAX — Lazy-load children of a tree node.
|
|
*
|
|
* GET vcenter-manager/tree/children?type=datacenter&id=1
|
|
* Returns JSON array of { id, type, name, icon, has_children, meta }
|
|
*/
|
|
public function treeChildren(Request $request)
|
|
{
|
|
$type = $request->input('type');
|
|
$id = (int) $request->input('id');
|
|
|
|
return match ($type) {
|
|
'vcenter' => $this->vcenterNodes($id),
|
|
'datacenter' => $this->hostNodes($id),
|
|
'host' => $this->hostChildNodes($id),
|
|
default => response()->json([]),
|
|
};
|
|
}
|
|
|
|
private function vcenterNodes(int $vcenterId)
|
|
{
|
|
$datacenters = $this->datacenterRepo->filter(
|
|
['vcenter_id' => $vcenterId],
|
|
[],
|
|
[],
|
|
['id', 'name']
|
|
);
|
|
|
|
return response()->json($datacenters->map(fn ($dc) => [
|
|
'id' => $dc->id,
|
|
'type' => 'datacenter',
|
|
'name' => $dc->name,
|
|
'icon' => 'fas fa-building',
|
|
'has_children' => true,
|
|
'meta' => [],
|
|
]));
|
|
}
|
|
|
|
private function hostNodes(int $datacenterId)
|
|
{
|
|
$hosts = $this->hostRepo->filter(
|
|
['module_datacenter_id' => $datacenterId],
|
|
[],
|
|
['datastore', 'network'],
|
|
['id', 'name', 'template']
|
|
);
|
|
|
|
return response()->json($hosts->map(fn ($h) => [
|
|
'id' => $h->id,
|
|
'type' => 'host',
|
|
'name' => $h->name,
|
|
'icon' => 'fas fa-server',
|
|
'has_children' => true,
|
|
'meta' => [
|
|
'datastores' => $h->datastore_count ?? 0,
|
|
'networks' => $h->network_count ?? 0,
|
|
'template' => (bool) $h->template,
|
|
],
|
|
]));
|
|
}
|
|
|
|
private function hostChildNodes(int $hostId)
|
|
{
|
|
$datastores = $this->datastoreRepo->filter(
|
|
['module_host_id' => $hostId],
|
|
[],
|
|
[],
|
|
['id', 'name', 'type', 'space', 'status']
|
|
);
|
|
|
|
$networks = $this->networkRepo->filter(
|
|
['module_host_id' => $hostId],
|
|
[],
|
|
['ipAddresses'],
|
|
['id', 'name', 'type']
|
|
);
|
|
|
|
$children = [];
|
|
|
|
foreach ($datastores as $ds) {
|
|
$children[] = [
|
|
'id' => $ds->id,
|
|
'type' => 'datastore',
|
|
'name' => $ds->name,
|
|
'icon' => 'fas fa-hdd',
|
|
'has_children' => false,
|
|
'meta' => ['space' => $ds->space, 'type' => $ds->type, 'status' => $ds->status],
|
|
];
|
|
}
|
|
|
|
foreach ($networks as $net) {
|
|
$children[] = [
|
|
'id' => $net->id,
|
|
'type' => 'network',
|
|
'name' => $net->name,
|
|
'icon' => 'fas fa-network-wired',
|
|
'has_children' => false,
|
|
'meta' => ['type' => $net->type, 'ips' => $net->ip_addresses_count ?? 0],
|
|
];
|
|
}
|
|
|
|
return response()->json($children);
|
|
}
|
|
|
|
/**
|
|
* AJAX — Return rendered detail panel HTML for a tree node.
|
|
*
|
|
* GET vcenter-manager/tree/detail?type=host&id=3
|
|
* Returns JSON { html, type, id }
|
|
*/
|
|
public function nodeDetail(Request $request)
|
|
{
|
|
$type = $request->input('type');
|
|
$id = (int) $request->input('id');
|
|
|
|
$data = match ($type) {
|
|
'vcenter' => $this->vCenterDetail($id),
|
|
'datacenter' => $this->datacenterDetail($id),
|
|
'host' => $this->hostDetail($id),
|
|
'datastore' => $this->datastoreDetail($id),
|
|
'network' => $this->networkDetail($id),
|
|
default => null,
|
|
};
|
|
|
|
if (! $data) {
|
|
return response()->json(['error' => 'Not found'], 404);
|
|
}
|
|
|
|
$html = view("administrator.pages.vcenter.partials.detail-{$type}", $data)->render();
|
|
|
|
return response()->json(compact('html', 'type', 'id'));
|
|
}
|
|
|
|
private function vCenterDetail(int $id): array
|
|
{
|
|
$item = $this->vCenterRepo->find($id);
|
|
$datacenterCount = $this->datacenterRepo->filter(['vcenter_id' => $id])->count();
|
|
$vmCount = ModuleVmware::where('vcenter_id', $id)->count();
|
|
|
|
return compact('item', 'datacenterCount', 'vmCount');
|
|
}
|
|
|
|
private function datacenterDetail(int $id): array
|
|
{
|
|
$item = $this->datacenterRepo->find($id);
|
|
$hosts = $this->hostRepo->filter(
|
|
['module_datacenter_id' => $id],
|
|
[],
|
|
['datastore', 'network'],
|
|
['id', 'name', 'template']
|
|
);
|
|
$folders = $this->folderRepo->filter(
|
|
['module_datacenter_id' => $id],
|
|
[],
|
|
[],
|
|
['id', 'name', 'type', 'folder']
|
|
);
|
|
|
|
return compact('item', 'hosts', 'folders');
|
|
}
|
|
|
|
private function hostDetail(int $id): array
|
|
{
|
|
$item = $this->hostRepo->find($id);
|
|
$datastores = $this->datastoreRepo->filter(['module_host_id' => $id]);
|
|
$networks = $this->networkRepo->filter(
|
|
['module_host_id' => $id],
|
|
[],
|
|
['ipAddresses'],
|
|
['id', 'name', 'type']
|
|
);
|
|
$ipTotal = ModuleIpAddress::where('module_host_id', $id)->count();
|
|
$ipFree = ModuleIpAddress::where('module_host_id', $id)->where('status', 0)->count();
|
|
|
|
return compact('item', 'datastores', 'networks', 'ipTotal', 'ipFree');
|
|
}
|
|
|
|
private function datastoreDetail(int $id): array
|
|
{
|
|
$item = $this->datastoreRepo->find($id);
|
|
return compact('item');
|
|
}
|
|
|
|
private function networkDetail(int $id): array
|
|
{
|
|
$item = $this->networkRepo->find($id);
|
|
$ips = $this->ipRepo->filter(
|
|
['module_network_id' => $id],
|
|
[],
|
|
[],
|
|
['id', 'ip', 'gateway', 'netmask', 'dns', 'status']
|
|
);
|
|
|
|
return compact('item', 'ips');
|
|
}
|
|
|
|
/**
|
|
* AJAX — DataTables JSON for Virtual Machines scoped by tree node.
|
|
*
|
|
* GET vcenter-manager/vms/datatable?scope_type=host&scope_id=2
|
|
*/
|
|
public function vmDatatable(Request $request)
|
|
{
|
|
$scopeType = $request->input('scope_type', 'all');
|
|
$scopeId = (int) $request->input('scope_id', 0);
|
|
|
|
$query = ModuleVmware::with(['customer', 'ipAddress', 'vCenter', 'os']);
|
|
|
|
if ($scopeType === 'vcenter' && $scopeId) {
|
|
$query->where('vcenter_id', $scopeId);
|
|
} elseif ($scopeType === 'datacenter' && $scopeId) {
|
|
$hostIds = $this->hostRepo
|
|
->filter(['module_datacenter_id' => $scopeId], [], [], ['id'])
|
|
->pluck('id');
|
|
$query->whereHas('ipAddress', fn ($q) => $q->whereIn('module_host_id', $hostIds));
|
|
} elseif ($scopeType === 'host' && $scopeId) {
|
|
$query->whereHas('ipAddress', fn ($q) => $q->where('module_host_id', $scopeId));
|
|
}
|
|
|
|
return DataTables::of($query)
|
|
->addColumn('customer_name', fn ($vm) => $vm->customer?->fullname ?? 'N/A')
|
|
->addColumn('ip_address', fn ($vm) => $vm->ipAddress?->ip ?? 'N/A')
|
|
->addColumn('vcenter_host', fn ($vm) => $vm->vCenter?->host ?? 'N/A')
|
|
->addColumn('os_name', fn ($vm) => $vm->os?->version ?? 'N/A')
|
|
->addColumn('status_badge', fn ($vm) => $vm->getStatus())
|
|
->rawColumns(['status_badge'])
|
|
->make(true);
|
|
}
|
|
|
|
public function wizardVcenters()
|
|
{
|
|
$vcenters = $this->vCenterRepo->filter(['status' => 1], [], [], ['id', 'host', 'user']);
|
|
return response()->json($vcenters);
|
|
}
|
|
|
|
public function wizardStoreVcenter(Request $request)
|
|
{
|
|
$data = $request->validate([
|
|
'host' => 'required|string',
|
|
'user' => 'required|string',
|
|
'password' => 'required|string',
|
|
'status' => 'nullable|integer',
|
|
]);
|
|
|
|
try {
|
|
$vcenter = \App\Models\Vcenter::create([
|
|
'host' => $data['host'],
|
|
'user' => $data['user'],
|
|
'password' => $data['password'],
|
|
'status' => $data['status'] ?? 1,
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'vcenter' => ['id' => $vcenter->id, 'host' => $vcenter->host, 'user' => $vcenter->user]
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json(['error' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
public function wizardDatacenters(Request $request)
|
|
{
|
|
$vcenterId = $request->input('vcenter_id');
|
|
$vcenter = $this->vCenterRepo->find($vcenterId);
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => 'vCenter not found'], 404);
|
|
}
|
|
|
|
$vcenterService = new \App\Http\Services\vCenter();
|
|
$loginResult = $vcenterService->loginServer($vcenter);
|
|
|
|
if (!$loginResult) {
|
|
return response()->json(['error' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$datacenters = $vcenterService->getAllDatacenter();
|
|
|
|
if ($datacenters === false || $datacenters === 'Login_Again') {
|
|
return response()->json(['error' => 'Cannot get datacenters'], 400);
|
|
}
|
|
|
|
return response()->json(array_map(fn($dc) => [
|
|
'datacenter_id' => $dc['datacenter'] ?? $dc['datacenter_id'] ?? '',
|
|
'name' => $dc['name'] ?? $dc['datacenter'] ?? '',
|
|
], $datacenters));
|
|
}
|
|
|
|
public function wizardHosts(Request $request)
|
|
{
|
|
$vcenterId = $request->input('vcenter_id');
|
|
$datacenterId = $request->input('datacenter_id');
|
|
|
|
$vcenter = $this->vCenterRepo->find($vcenterId);
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => 'vCenter not found'], 404);
|
|
}
|
|
|
|
$vcenterService = new \App\Http\Services\vCenter();
|
|
$vcenterService->loginServer($vcenter);
|
|
|
|
$hosts = $vcenterService->getAllHost($datacenterId);
|
|
|
|
if ($hosts === false || $hosts === 'Login_Again') {
|
|
return response()->json(['error' => 'Cannot get hosts'], 400);
|
|
}
|
|
|
|
return response()->json(array_map(fn($host) => [
|
|
'host_id' => $host['host'] ?? $host['host_id'] ?? '',
|
|
'name' => $host['name'] ?? $host['host'] ?? '',
|
|
], $hosts));
|
|
}
|
|
|
|
public function wizardDatastoreNetwork(Request $request)
|
|
{
|
|
$vcenterId = $request->input('vcenter_id');
|
|
$hostId = $request->input('host_id');
|
|
|
|
$vcenter = $this->vCenterRepo->find($vcenterId);
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => 'vCenter not found'], 404);
|
|
}
|
|
|
|
$vcenterService = new \App\Http\Services\vCenter();
|
|
$vcenterService->loginServer($vcenter);
|
|
|
|
$datastores = $vcenterService->getAllDataStore($hostId);
|
|
$networks = $vcenterService->getNetwork($hostId);
|
|
|
|
return response()->json([
|
|
'datastores' => $datastores ? array_map(fn($ds) => [
|
|
'datastore' => $ds['datastore'] ?? $ds['name'] ?? '',
|
|
'name' => $ds['name'] ?? $ds['datastore'] ?? '',
|
|
'type' => $ds['type'] ?? 'UNKNOWN',
|
|
], $datastores) : [],
|
|
'networks' => $networks ? array_map(fn($net) => [
|
|
'network_id' => $net['network'] ?? $net['name'] ?? '',
|
|
'name' => $net['name'] ?? $net['network'] ?? '',
|
|
'type' => $net['type'] ?? 'UNKNOWN',
|
|
], $networks) : [],
|
|
]);
|
|
}
|
|
|
|
public function wizardSave(Request $request)
|
|
{
|
|
$data = $request->validate([
|
|
'vcenter_id' => 'required|integer',
|
|
'datacenter' => 'required|array',
|
|
'host' => 'required|array',
|
|
'datastores' => 'nullable|array',
|
|
'networks' => 'nullable|array',
|
|
]);
|
|
|
|
try {
|
|
$datacenter = \App\Models\ModuleDatacenter::create([
|
|
'name' => $data['datacenter']['name'],
|
|
'datacenter_id' => $data['datacenter']['datacenter_id'],
|
|
'vcenter_id' => $data['vcenter_id'],
|
|
]);
|
|
|
|
$host = \App\Models\ModuleHost::create([
|
|
'name' => $data['host']['name'],
|
|
'host_id' => $data['host']['host_id'],
|
|
'module_datacenter_id' => $datacenter->id,
|
|
]);
|
|
|
|
foreach ($data['datastores'] ?? [] as $ds) {
|
|
\App\Models\ModuleDatastore::create([
|
|
'name' => $ds['name'],
|
|
'datastore' => $ds['datastore'],
|
|
'type' => $ds['type'],
|
|
'module_host_id' => $host->id,
|
|
'status' => 1,
|
|
]);
|
|
}
|
|
|
|
foreach ($data['networks'] ?? [] as $net) {
|
|
\App\Models\ModuleNetwork::create([
|
|
'name' => $net['name'],
|
|
'network' => $net['network_id'],
|
|
'type' => $net['type'],
|
|
'module_host_id' => $host->id,
|
|
]);
|
|
}
|
|
|
|
return response()->json(['success' => true, 'message' => 'Thêm mới infrastructure thành công!']);
|
|
} catch (\Exception $e) {
|
|
return response()->json(['error' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
public function powerState(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
return response()->json(['error' => false, 'data' => ['power_state' => $vm->power_state]]);
|
|
}
|
|
|
|
public function powerOn(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
if ($vm->power_state === 'POWERED_ON') {
|
|
return response()->json(['error' => true, 'message' => 'VM is already powered on'], 400);
|
|
}
|
|
|
|
$vcenter = null;
|
|
|
|
if ($vm->datacenter?->vCenter) {
|
|
$vcenter = $vm->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter && $vm->host?->datacenter?->vCenter) {
|
|
$vcenter = $vm->host->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders([
|
|
'vmware-api-session-id' => $vCenterService->token
|
|
])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/start");
|
|
|
|
if ($response->status() === 200) {
|
|
$vm->update(['power_state' => 'POWERED_ON']);
|
|
return response()->json(['error' => false, 'message' => 'Power on command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') {
|
|
$vm->update(['power_state' => 'POWERED_ON']);
|
|
return response()->json(['error' => false, 'message' => 'VM is already powered on']);
|
|
}
|
|
|
|
return response()->json(['error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to power on VM'], 400);
|
|
}
|
|
|
|
public function powerOff(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
if ($vm->power_state === 'POWERED_OFF') {
|
|
return response()->json(['error' => true, 'message' => 'VM is already powered off'], 400);
|
|
}
|
|
|
|
$vcenter = null;
|
|
|
|
if ($vm->datacenter?->vCenter) {
|
|
$vcenter = $vm->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter && $vm->host?->datacenter?->vCenter) {
|
|
$vcenter = $vm->host->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders([
|
|
'vmware-api-session-id' => $vCenterService->token
|
|
])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/stop");
|
|
|
|
if ($response->status() === 200) {
|
|
$vm->update(['power_state' => 'POWERED_OFF']);
|
|
return response()->json(['error' => false, 'message' => 'Power off command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') {
|
|
$vm->update(['power_state' => 'POWERED_OFF']);
|
|
return response()->json(['error' => false, 'message' => 'VM is already powered off']);
|
|
}
|
|
|
|
return response()->json(['error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to power off VM'], 400);
|
|
}
|
|
|
|
public function restart(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders(['vmware-api-session-id' => $vCenterService->token])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/reset");
|
|
|
|
if ($response->status() === 200) {
|
|
return response()->json(['error' => false, 'message' => 'Restart command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to restart VM',
|
|
], 400);
|
|
}
|
|
|
|
public function snapshot(Request $request)
|
|
{
|
|
$request->validate([
|
|
'vm_id' => 'required|integer',
|
|
'name' => 'required|string|max:255',
|
|
'description' => 'nullable|string|max:1000',
|
|
]);
|
|
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders(['vmware-api-session-id' => $vCenterService->token])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/snapshot", [
|
|
'spec' => [
|
|
'name' => $request->input('name'),
|
|
'description' => $request->input('description', ''),
|
|
'memory' => false,
|
|
'quiesce' => false,
|
|
],
|
|
]);
|
|
|
|
if ($response->status() === 200) {
|
|
return response()->json(['error' => false, 'message' => 'Snapshot created successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to create snapshot',
|
|
], 400);
|
|
}
|
|
|
|
public function stats(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
if ($vm->power_state !== 'POWERED_ON') {
|
|
return response()->json(['error' => true, 'message' => 'VM is not powered on'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$result = $vCenterService->GetVMStats($vmwareId);
|
|
|
|
if ($result['error'] ?? true) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $result['message'] ?? 'Failed to fetch VM stats',
|
|
], 400);
|
|
}
|
|
|
|
$stats = $result['data'];
|
|
$cpuPercent = $stats['cpu']['used_percent'] ?? 0;
|
|
$memPercent = $stats['memory']['usage_percent'] ?? 0;
|
|
|
|
// ponytail: overall_status is a CPU/RAM usage threshold heuristic, not real vCenter
|
|
// health/alarms — that needs the SOAP EventManager/AlarmManager, not wired up in this
|
|
// codebase (REST Automation API only). Upgrade path: add a SOAP client and read
|
|
// triggeredAlarmState off the VM's ManagedEntity if per-alarm detail is ever needed.
|
|
$overallStatus = match (true) {
|
|
$cpuPercent >= 90 || $memPercent >= 90 => 'red',
|
|
$cpuPercent >= 75 || $memPercent >= 75 => 'yellow',
|
|
default => 'green',
|
|
};
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'data' => [
|
|
'overall_status' => $overallStatus,
|
|
'cpu_usage_percent' => round($cpuPercent, 1),
|
|
'mem_usage_percent' => round($memPercent, 1),
|
|
'mem_usage_mb' => $stats['memory']['used_MiB'] ?? 0,
|
|
'mem_total_mb' => $stats['memory']['size_MiB'] ?? 0,
|
|
// ponytail: no uptime source wired up (would need SOAP PerfManager
|
|
// sys.uptime.latest); frontend renders "—" when null.
|
|
'uptime_seconds' => null,
|
|
'alarms' => [],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function getPoweredOffVms()
|
|
{
|
|
$vms = VirtualMachines::whereNotNull('vm_id')
|
|
->where('power_state', 'POWERED_OFF')
|
|
->get()
|
|
->map(function ($vm) {
|
|
return [
|
|
'id' => $vm->id,
|
|
'name' => $vm->name,
|
|
'vm_id' => $vm->vm_id,
|
|
'power_state' => $vm->power_state,
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'data' => $vms
|
|
]);
|
|
}
|
|
|
|
public function suspend(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
// vm_id from frontend = services.vm_id = module_vmware.id (FK)
|
|
$vmware = ModuleVmware::with('vCenter')->find($vmId);
|
|
|
|
if (!$vmware) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vmware->vmware_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
$vm = VirtualMachines::where('vm_id', $vmwareId)->first();
|
|
|
|
if ($vm?->power_state === 'SUSPENDED') {
|
|
return response()->json(['error' => true, 'message' => 'VM is already suspended'], 400);
|
|
}
|
|
|
|
$vcenter = $vmware->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders(['vmware-api-session-id' => $vCenterService->token])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/suspend");
|
|
|
|
if ($response->status() === 200) {
|
|
$vm?->update(['power_state' => 'SUSPENDED']);
|
|
$this->sendSuspendNotification($vmware, $vm);
|
|
return response()->json(['error' => false, 'message' => 'Suspend command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') {
|
|
$vm?->update(['power_state' => 'SUSPENDED']);
|
|
$this->sendSuspendNotification($vmware, $vm);
|
|
return response()->json(['error' => false, 'message' => 'VM is already suspended']);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to suspend VM',
|
|
], 400);
|
|
}
|
|
|
|
public function updateConfig(Request $request)
|
|
{
|
|
$request->validate([
|
|
'vm_name' => 'required|string',
|
|
'cpu' => 'required|integer|min:1',
|
|
'ram' => 'required|integer|min:1',
|
|
]);
|
|
|
|
$vmware = ModuleVmware::where('name', $request->input('vm_name'))->first();
|
|
|
|
if (!$vmware) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
\App\Jobs\VirtualMachine\Provision\UpdateVmConfigJob::dispatch(
|
|
$vmware->id,
|
|
(string) $vmware->vmware_id,
|
|
'',
|
|
(int) $request->input('cpu'),
|
|
(int) $request->input('ram'),
|
|
);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã gửi yêu cầu cập nhật cấu hình VM']);
|
|
}
|
|
|
|
public function deleteVM(Request $request)
|
|
{
|
|
$request->validate([
|
|
'vm_id' => 'required|integer',
|
|
'confirm_name' => 'required|string',
|
|
]);
|
|
|
|
$vmId = (int) $request->input('vm_id');
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
if ($request->input('confirm_name') !== $vm->name) {
|
|
return response()->json(['error' => true, 'message' => "Tên xác nhận không khớp. Vui lòng nhập đúng: {$vm->name}"], 422);
|
|
}
|
|
|
|
if ($vm->power_state !== 'POWERED_OFF') {
|
|
return response()->json(['error' => true, 'message' => 'Vui lòng tắt máy ảo trước khi xoá'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$powerShellService = app(\App\Http\Services\vCenter\PowerShellService::class);
|
|
|
|
$q = fn(string $v) => str_replace("'", "''", $v);
|
|
$vmName = $vm->name;
|
|
$host = $vcenter->host;
|
|
$user = $vcenter->user;
|
|
$password = \App\Http\Facade\SignRSA::DecryptFunc($vcenter->password);
|
|
|
|
$command = "& 'C:\\CLISave\\DeleteVM\\deleteVM.ps1' -VmName '" . $q($vmName) . "' -VCenterHost '" . $q($host) . "' -VCenterUser '" . $q($user) . "' -VCenterPassword '" . $q($password) . "'";
|
|
$cmdResult = $powerShellService->runPsCommand($command);
|
|
|
|
if (strpos($cmdResult, 'Login_Fail') !== false) {
|
|
return response()->json(['error' => true, 'message' => 'SSH Login to PowerShell server failed'], 400);
|
|
}
|
|
|
|
if (strpos($cmdResult, 'SUCCESS') === false) {
|
|
return response()->json(['error' => true, 'message' => 'Xoá VM thất bại: ' . $cmdResult], 400);
|
|
}
|
|
|
|
$vm->delete();
|
|
|
|
return response()->json(['error' => false, 'message' => 'VM đã được xoá thành công']);
|
|
}
|
|
|
|
private function sendSuspendNotification(ModuleVmware $vmware, ?VirtualMachines $vm = null): void
|
|
{
|
|
$vmware->loadMissing('customer');
|
|
|
|
if (!$vmware->customer || !$vmware->customer->email) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Mail::to($vmware->customer->email)
|
|
->queue(new VmSuspendedMail($vmware->customer, $vm?->name ?? $vmware->name, $vmware->vmware_id));
|
|
} catch (\Throwable) {
|
|
// Email failure must not block the suspend response
|
|
}
|
|
}
|
|
}
|