update change password
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\frontend;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Vm\SnapShotRequest;
|
||||
use App\Jobs\VirtualMachine\Action\ResetVmPasswordJob;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceItem;
|
||||
use App\Repositories\Invoice\InvoiceRepositoryInterface;
|
||||
@@ -552,109 +553,50 @@ class ServiceController extends Controller
|
||||
/**
|
||||
* Reset VM password (Windows/Linux) via vCenter helpers
|
||||
*/
|
||||
public function resetVMPassword(Request $request, vCenter $serviceVCenter)
|
||||
public function resetVMPassword(Request $request)
|
||||
{
|
||||
try {
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.'),
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validated = FacadesValidator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
'new_password' => 'required|string|min:8|max:128',
|
||||
]);
|
||||
|
||||
if ($validated->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validated->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$productId = $this->sanitizeInput($request->product_id);
|
||||
$newPassword = (string) $request->new_password;
|
||||
|
||||
// Load product and ensure ownership
|
||||
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter')
|
||||
->where('vmware_id', $productId)
|
||||
->where('customer_id', $user->id)
|
||||
->first();
|
||||
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập'),
|
||||
], 404);
|
||||
}
|
||||
|
||||
$vcenter = $product->ipAddress->host->datacenter->vCenter ?? null;
|
||||
if (!$vcenter) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Cấu hình vCenter không hợp lệ'),
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Login to vCenter
|
||||
if (!$serviceVCenter->loginServer($vcenter)) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không thể kết nối đến vCenter'),
|
||||
], 500);
|
||||
}
|
||||
|
||||
// Determine OS family
|
||||
$osFamily = strtoupper($product->os->osGroup->name ?? '');
|
||||
$isWindows = str_contains($osFamily, 'WINDOWS');
|
||||
|
||||
// Prepare data
|
||||
$ip = $product->ipAddress->ip ?? null;
|
||||
if (!$ip) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy IP của máy ảo'),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'ip' => $ip,
|
||||
'user' => $isWindows ? 'administrator' : 'root',
|
||||
'password' => $product->password ?? 'TGS@123', // fallback if field exists; adjust per model
|
||||
'new_password' => $newPassword,
|
||||
];
|
||||
|
||||
$ok = $isWindows
|
||||
? $serviceVCenter->change_password_windows($payload)
|
||||
: $serviceVCenter->change_password_linux($payload);
|
||||
|
||||
if (!$ok) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không thể đặt lại mật khẩu. Vui lòng thử lại sau.'),
|
||||
], 500);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Đặt lại mật khẩu thành công'),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Lỗi reset VM password', [
|
||||
'product_id' => $request->product_id ?? null,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Có lỗi xảy ra khi đặt lại mật khẩu'),
|
||||
], 500);
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json(['error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')], 401);
|
||||
}
|
||||
|
||||
$validated = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
'new_password' => 'required|string|min:8|max:128',
|
||||
'old_password' => 'required|string|min:8|max:128',
|
||||
]);
|
||||
if ($validated->fails()) {
|
||||
return response()->json(['error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors()], 422);
|
||||
}
|
||||
|
||||
$productId = $this->sanitizeInput($request->product_id);
|
||||
$newPassword = (string) $request->new_password;
|
||||
$oldPassword = (string) $request->old_password;
|
||||
|
||||
// load & ownership check nhanh (không cần toàn bộ nặng nề vì Job sẽ retry)
|
||||
$product = ModuleVmware::where('vmware_id', $productId)
|
||||
->where('customer_id', $user->id)
|
||||
->first();
|
||||
if (!$product) {
|
||||
return response()->json(['error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')], 404);
|
||||
}
|
||||
|
||||
// Dispatch job với delay 5s
|
||||
ResetVmPasswordJob::dispatch(
|
||||
productId: $productId,
|
||||
newPassword: $newPassword,
|
||||
oldPassword: $oldPassword,
|
||||
userId: $user->id,
|
||||
email: $user->email
|
||||
)->delay(now()->addSeconds(5));
|
||||
|
||||
// trả accepted để UI biết đang xử lý nền
|
||||
return redirect()->back()->with('success', 'Đã gửi yêu cầu tạo đặt lại mật khẩu. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Change Remote Desktop port (Windows only)
|
||||
*/
|
||||
@@ -827,8 +769,8 @@ class ServiceController extends Controller
|
||||
|
||||
// Fetch product price based on invoice item link
|
||||
$invoiceItem = InvoiceItem::whereHas('invoice', function ($q) use ($user) {
|
||||
$q->where('customer_id', $user->id);
|
||||
})
|
||||
$q->where('customer_id', $user->id);
|
||||
})
|
||||
->where('product_type', 4) // VPS
|
||||
->whereHas('invoice', fn($q) => $q->where('status', '!=', 0))
|
||||
->first();
|
||||
@@ -1131,7 +1073,7 @@ class ServiceController extends Controller
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), [
|
||||
$validator = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
@@ -1158,7 +1100,7 @@ class ServiceController extends Controller
|
||||
$result = $vCenterService->ShutdownGuestVirtualMachine($product, $product->vmware_id);
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
// Clear VM info cache after stopping VM
|
||||
\Cache::forget('vm_info_' . $product->vmware_id);
|
||||
Cache::forget('the_gioi_so_database_the_gioi_so_cache:vm_info_' . $product->vmware_id);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Yêu cầu tắt máy ảo đã được gửi thành công.')
|
||||
@@ -1287,9 +1229,9 @@ class ServiceController extends Controller
|
||||
|
||||
// Debug: Log invoice lookup
|
||||
Log::info('Looking for invoice', ['invoice_id' => $request->invoice_id]);
|
||||
|
||||
|
||||
$invoice = Invoice::find($request->invoice_id);
|
||||
|
||||
|
||||
// Debug: Log invoice result
|
||||
Log::info('Invoice lookup result', [
|
||||
'invoice_id' => $request->invoice_id,
|
||||
|
||||
+157
-18
@@ -15,7 +15,6 @@ use phpseclib3\Net\SSH2;
|
||||
|
||||
class vCenter
|
||||
{
|
||||
protected $process = null;
|
||||
public ?string $token = null;
|
||||
public string $apiUri = '10.10.10.6';
|
||||
private string $username = 'administrator@vsphere.local';
|
||||
@@ -23,7 +22,8 @@ class vCenter
|
||||
private int $diskCapacity = 1073741824;
|
||||
private int $memory = 1024;
|
||||
|
||||
protected string $os;
|
||||
private ?string $os = null;
|
||||
private SSH2|WinRMClient|null $process = null;
|
||||
|
||||
public function loginServer(\App\Models\Vcenter $vcenterModel): bool|string
|
||||
{
|
||||
@@ -325,6 +325,142 @@ class vCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Login SSH
|
||||
public function loginSSH(): bool
|
||||
{
|
||||
$conn = $this->LoginAuth();
|
||||
|
||||
if ($conn) {
|
||||
Log::info("✅ Connected to vCenter via SSH successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::error("❌ Failed to connect to vCenter via SSH");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function runPsCommand(string $psCommand): string
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth)
|
||||
return "Login_Fail";
|
||||
|
||||
// gọi trực tiếp -Command, thu cả STDERR
|
||||
$action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass '
|
||||
. '-Command "' . str_replace('"', '\"', $psCommand) . '"';
|
||||
|
||||
try {
|
||||
$stream = ssh2_exec($auth, $action);
|
||||
if (!$stream)
|
||||
throw new \RuntimeException("ssh2_exec failed");
|
||||
$err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
|
||||
stream_set_blocking($stream, true);
|
||||
stream_set_blocking($err, true);
|
||||
$out = stream_get_contents($stream) ?: '';
|
||||
$errOut = stream_get_contents($err) ?: '';
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Throwable $e) {
|
||||
return "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function CommandRSP(string $cmd)
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth)
|
||||
return "Login_Fail";
|
||||
|
||||
// Escape để dùng trong chuỗi single-quoted của PowerShell
|
||||
$esc = fn(string $v) => str_replace("'", "''", $v);
|
||||
|
||||
$server = $esc($this->apiUri ?? '');
|
||||
$user = $esc($this->username ?? '');
|
||||
$pass = $esc($this->password ?? '');
|
||||
|
||||
// Ghép script PowerShell hoàn chỉnh, chạy trong 1 scriptblock
|
||||
// Lưu ý: dùng placeholder rồi strtr để tránh lẫn dấu ngoặc
|
||||
$psTemplate = <<<'PS'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$InformationPreference = 'SilentlyContinue'
|
||||
$WarningPreference = 'SilentlyContinue'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Import-Module VMware.PowerCLI -ErrorAction Stop
|
||||
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
|
||||
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
|
||||
|
||||
# Thực thi lệnh công việc (đã được xây dựng sẵn, ví dụ: & 'C:\path\script.ps1' -Param 'Value' ... )
|
||||
& {
|
||||
<CMD>
|
||||
} | Out-String
|
||||
PS;
|
||||
|
||||
// Chèn dữ liệu
|
||||
$psScript = strtr($psTemplate, [
|
||||
'<SERVER>' => $server,
|
||||
'<USER>' => $user,
|
||||
'<PASS>' => $pass,
|
||||
'<CMD>' => $cmd, // ở đây $cmd KHÔNG cần ngoặc kép bao ngoài
|
||||
]);
|
||||
|
||||
// Encode UTF-16LE → Base64 cho -EncodedCommand
|
||||
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
|
||||
|
||||
// Dùng -EncodedCommand để tránh mọi lỗi escape/quote
|
||||
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
|
||||
|
||||
try {
|
||||
$stream = ssh2_exec($auth, $action);
|
||||
if (!$stream)
|
||||
return "ssh2_exec failed";
|
||||
$err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
|
||||
stream_set_blocking($stream, true);
|
||||
stream_set_blocking($err, true);
|
||||
$out = stream_get_contents($stream) ?: '';
|
||||
$errOut = stream_get_contents($err) ?: '';
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Throwable $e) {
|
||||
return "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string
|
||||
{
|
||||
// File ps1 chạy được ngoài PowerShell: bạn đã test ok
|
||||
$psFile = "C:\\CLISave\\AssignIP\\SetGuestPwd.ps1";
|
||||
|
||||
// Escape chuỗi cho PowerShell single-quoted
|
||||
$q = fn(string $v) => str_replace("'", "''", $v);
|
||||
|
||||
// Xây args kiểu: -Key 'Value'
|
||||
$args = " -VMName '" . $q($vmName) . "'";
|
||||
$args .= " -GuestUser '" . $q($guestUser) . "'";
|
||||
$args .= " -GuestPassword '" . $q($guestPassword) . "'";
|
||||
$args .= " -TargetUser '" . $q($targetUser) . "'";
|
||||
$args .= " -NewPassword '" . $q($newPassword) . "'";
|
||||
|
||||
// Gọi file trong cùng phiên PS đã Connect-VIServer (nhờ Command())
|
||||
// Thêm Out-String để ép JSON nằm trên STDOUT gọn
|
||||
$cmd = "& '$psFile'$args | Out-String";
|
||||
|
||||
$raw = $this->CommandRSP($cmd); // <-- dùng lại Command() của bạn
|
||||
if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
|
||||
return "Login_Fail";
|
||||
}
|
||||
|
||||
// Thử parse JSON (có thể lẫn banner/log)
|
||||
$trim = trim($raw);
|
||||
$data = json_decode($trim, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
if (preg_match('/(\{.*\}|\[.*\])/s', $trim, $m)) {
|
||||
$data = json_decode($m[1], true);
|
||||
}
|
||||
}
|
||||
|
||||
return $data ?: $raw; // nếu chưa parse được, trả raw để debug
|
||||
}
|
||||
|
||||
private function getAllNetwork()
|
||||
{
|
||||
$check = $this->checkApiExits();
|
||||
@@ -457,7 +593,7 @@ class vCenter
|
||||
|
||||
try {
|
||||
Log::info('Bắt đầu clone VM với timeout 300 giây', ['template' => $template, 'target_name' => $body['spec']['name']]);
|
||||
|
||||
|
||||
$response = Http::withoutVerifying()
|
||||
->timeout(300) // Tăng timeout lên 5 phút cho quá trình clone
|
||||
->connectTimeout(30) // Giữ connect timeout ngắn để phát hiện lỗi kết nối sớm
|
||||
@@ -1854,8 +1990,11 @@ class vCenter
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function change_password_linux(array $data = ['ip' => '103.42.58.37', 'user' => 'root', 'password' => 'TGS@123', 'new_password' => '']): bool
|
||||
/*
|
||||
* ip = 103.42.58.37
|
||||
* pwd = TGS@123
|
||||
*/
|
||||
public function change_password_linux(array $data = ['ip' => '10.10.10.173', 'user' => 'admin', 'password' => 'TGS@123', 'new_password' => '']): bool
|
||||
{
|
||||
try {
|
||||
// Kiểm tra mật khẩu mới
|
||||
@@ -2139,14 +2278,14 @@ class vCenter
|
||||
$dns = '8.8.8.8';
|
||||
$vmware_name = '';
|
||||
$vCenter = null;
|
||||
|
||||
|
||||
// Check if it's ModuleVmware (new structure)
|
||||
if ($product && isset($product->ipAddress) && $product->ipAddress) {
|
||||
Log::info('✅ Using ModuleVmware structure for assignIPWindows', [
|
||||
'module_vmware_id' => $product->id,
|
||||
'vmware_id' => $vmware_id
|
||||
]);
|
||||
|
||||
|
||||
// Extract from ModuleVmware->ipAddress relationship
|
||||
$ipAddress = $product->ipAddress->ip ?? null;
|
||||
$gateway = $product->ipAddress->gateway ?? null;
|
||||
@@ -2154,13 +2293,13 @@ class vCenter
|
||||
$dns = $product->ipAddress->dns ?? '8.8.8.8';
|
||||
$vmware_name = $product->name ?? $vmware_id;
|
||||
$vCenter = $product->ipAddress->host->datacenter->vCenter ?? null;
|
||||
|
||||
|
||||
} elseif ($product && isset($product->IpAddress) && $product->IpAddress) {
|
||||
Log::info('✅ Using old Product structure for assignIPWindows', [
|
||||
'product_id' => $product->id,
|
||||
'vmware_id' => $vmware_id
|
||||
]);
|
||||
|
||||
|
||||
// Extract from old Product->IpAddress relationship
|
||||
$ipAddress = $product->IpAddress->ip ?? null;
|
||||
$gateway = $product->IpAddress->gateway ?? null;
|
||||
@@ -2169,7 +2308,7 @@ class vCenter
|
||||
$vmware_name = $product->name ?? $vmware_id;
|
||||
$vCenter = $product->IpAddress->Host->Datacenter->vCenter ?? null;
|
||||
}
|
||||
|
||||
|
||||
// Validate required data
|
||||
if (!$ipAddress || !$gateway || !$vCenter) {
|
||||
Log::error('❌ assignIPWindows: Missing required data', [
|
||||
@@ -2186,10 +2325,10 @@ class vCenter
|
||||
}
|
||||
|
||||
$this->SetConfig($vCenter);
|
||||
|
||||
|
||||
// Convert netmask to prefix length (e.g., 255.255.255.0 = 24)
|
||||
$maskBits = $this->convertNetmaskToPrefixLength($netmask);
|
||||
|
||||
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
Log::channel('Custom')->info("Assign IP to Windows VM - $vmware_name");
|
||||
Log::channel('Custom')->info("IP: $ipAddress, Gateway: $gateway, DNS: $dns, MaskBits: $maskBits");
|
||||
@@ -2197,14 +2336,14 @@ class vCenter
|
||||
|
||||
// Create PowerShell command with dynamic values
|
||||
$Command = "\$vmName = '$vmware_name'; \$IP = '$ipAddress'; \$MaskBits = $maskBits; \$Gateway = '$gateway'; \$DNS = '$dns'; cd /; C:\CLISave\AssignIP\assignIPWindows.ps1;";
|
||||
|
||||
|
||||
Log::channel('Custom')->info("Executing Command: $Command");
|
||||
|
||||
|
||||
$output = $this->Command($Command);
|
||||
|
||||
|
||||
Log::channel('Custom')->info("Script output: " . $output);
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
|
||||
|
||||
// Check if execution was successful
|
||||
if (strpos($output, '🎉') !== false || strpos($output, 'successfully') !== false) {
|
||||
return [
|
||||
@@ -2219,7 +2358,7 @@ class vCenter
|
||||
'output' => $output
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('Custom')->error("Exception in assignIPWindows: " . $e->getMessage());
|
||||
return [
|
||||
@@ -2261,7 +2400,7 @@ class vCenter
|
||||
'255.128.0.0' => 9,
|
||||
'255.0.0.0' => 8
|
||||
];
|
||||
|
||||
|
||||
return $netmaskMap[$netmask] ?? 24; // Default to /24 if not found
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\VirtualMachine\Action;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Http\Services\vCenter;
|
||||
use App\Mail\VmPasswordResetSuccess;
|
||||
use App\Models\ModuleVmware;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ResetVmPasswordJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int $tries = 3;
|
||||
public int $timeout = 180;
|
||||
|
||||
public function __construct(
|
||||
public string $productId,
|
||||
public string $oldPassword,
|
||||
public string $newPassword,
|
||||
public int $userId,
|
||||
public string $email
|
||||
) {}
|
||||
|
||||
public function handle(vCenter $serviceVCenter): void
|
||||
{
|
||||
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter','os.osGroup')
|
||||
->where('vmware_id', $this->productId)
|
||||
->where('customer_id', $this->userId)
|
||||
->firstOrFail();
|
||||
|
||||
$osFamily = (string)($product->os->osGroup->name ?? '');
|
||||
$isWindows = stripos($osFamily, 'windows') !== false;
|
||||
if (!$isWindows) {
|
||||
throw new \RuntimeException('Only Windows is supported by this reset job.');
|
||||
}
|
||||
|
||||
$vmName = $product->ipAddress->vm_name
|
||||
?? $product->name
|
||||
?? $product->hostname
|
||||
?? null;
|
||||
|
||||
if (!$vmName && preg_match('/^vm-\d+$/i', (string)$product->vmware_id)) {
|
||||
$rawName = trim($serviceVCenter->runPsCommand("(Get-VM -Id '".addslashes($product->vmware_id)."').Name"));
|
||||
if (($p = strpos($rawName, '[STDERR]')) !== false) {
|
||||
$rawName = trim(substr($rawName, 0, $p));
|
||||
}
|
||||
$vmName = trim(preg_replace('/\s+/', ' ', $rawName));
|
||||
}
|
||||
if (!$vmName) {
|
||||
throw new \RuntimeException('Cannot resolve VM Name on vCenter.');
|
||||
}
|
||||
|
||||
$guestUser = 'Administrator';
|
||||
$guestPassword = $this->oldPassword;
|
||||
|
||||
$result = $serviceVCenter->resetGuestPasswordViaPs1(
|
||||
vmName: $vmName,
|
||||
guestUser: $guestUser,
|
||||
guestPassword: $guestPassword,
|
||||
newPassword: $this->newPassword,
|
||||
);
|
||||
|
||||
$product->update(['password' => encrypt($this->newPassword)]);
|
||||
|
||||
if ($result === 'Login_Fail' || $result === false || $result === null) {
|
||||
throw new \RuntimeException('SSH runner login failed.');
|
||||
}
|
||||
|
||||
if (is_string($result)) {
|
||||
throw new \RuntimeException('Script returned non-JSON output: '.$result);
|
||||
}
|
||||
|
||||
$row = $result;
|
||||
if (isset($result[0])) {
|
||||
$row = $result[0];
|
||||
foreach ($result as $it) {
|
||||
if (is_array($it) && (($it['vm'] ?? null) === $vmName)) { $row = $it; break; }
|
||||
}
|
||||
}
|
||||
|
||||
$status = strtolower($row['status'] ?? '');
|
||||
if ($status !== 'ok') {
|
||||
throw new \RuntimeException($row['message'] ?? 'Reset failed by script');
|
||||
}
|
||||
|
||||
// Gửi mail thông báo
|
||||
Mail::to($this->email)->send(
|
||||
new VmPasswordResetSuccess(vmName: $vmName, newPassword: $this->newPassword)
|
||||
);
|
||||
|
||||
Log::info('ResetVmPasswordJob: success', ['vm'=>$vmName]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,16 +104,17 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'ram' => 0,
|
||||
'disk' => 0
|
||||
];
|
||||
|
||||
|
||||
// Lấy addon từ order item
|
||||
if ($this->item->addons) {
|
||||
foreach ($this->item->addons as $orderAddon) {
|
||||
$addon = $orderAddon->addonItems;
|
||||
if (!$addon) continue;
|
||||
|
||||
if (!$addon)
|
||||
continue;
|
||||
|
||||
$addonName = strtolower(trim($addon->name));
|
||||
$quantity = (int) $orderAddon->qty;
|
||||
|
||||
|
||||
switch ($addonName) {
|
||||
case 'cpu':
|
||||
$addonData['cpu'] += $quantity;
|
||||
@@ -187,10 +188,10 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
return false;
|
||||
} else {
|
||||
Log::info('Tạo máy ảo thành công: ' . json_encode($result));
|
||||
|
||||
|
||||
// Lấy vm_id từ kết quả
|
||||
$vmId = is_array($result) ? ($result['data'] ?? 'unknown') : $result;
|
||||
|
||||
|
||||
if ($vmId !== 'unknown' && $vmId !== 'UNKNOWN') {
|
||||
try {
|
||||
// Lưu vm_id vào bảng invoice
|
||||
@@ -212,11 +213,11 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
try {
|
||||
$customer = $this->item->order->customer;
|
||||
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
|
||||
|
||||
|
||||
// 1. Lấy IP available từ IPPool
|
||||
$networkId = $product->ipAddress?->module_network_id ?? 1;
|
||||
$availableIP = $this->getAvailableIPFromPool($networkId);
|
||||
|
||||
|
||||
// 2. Tạo ModuleIpAddress với IP từ pool
|
||||
$moduleIpAddress = ModuleIpAddress::create([
|
||||
'vm_name' => $serverName,
|
||||
@@ -228,7 +229,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'dns' => $product->ipAddress?->dns ?? '8.8.8.8',
|
||||
'status' => 1 // 1 = Assigned
|
||||
]);
|
||||
|
||||
|
||||
// 3. Tạo ModuleVmware với IP address vừa tạo
|
||||
$moduleVmware = ModuleVmware::create([
|
||||
'name' => $serverName,
|
||||
@@ -239,7 +240,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'next_payment' => $this->item->order->next_payment ?? 0,
|
||||
'next_due_date' => $this->item->order->next_due_date ?? null,
|
||||
'next_invoice' => $this->item->order->next_invoice ?? 0,
|
||||
'user' => 'root',
|
||||
'user' => 'Administrator',
|
||||
'password' => '',
|
||||
'hostname' => $serverName,
|
||||
'vmware_id' => $vmId,
|
||||
@@ -247,7 +248,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'customer_id' => $customer->id,
|
||||
'status' => 1
|
||||
]);
|
||||
|
||||
|
||||
Log::info('✅ Lưu dữ liệu VM và IP address thành công', [
|
||||
'vmware_id' => $vmId,
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
@@ -256,18 +257,18 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'name' => $serverName,
|
||||
'order_item_id' => $this->item->id
|
||||
]);
|
||||
|
||||
|
||||
// Dispatch AssignIPJob để chạy sau 5 phút
|
||||
AssignIPJob::dispatch($vmId, $moduleVmware->id, $moduleIpAddress->ip)
|
||||
->delay(now()->addMinutes(5));
|
||||
|
||||
|
||||
Log::info('✅ Đã dispatch AssignIPJob để chạy sau 5 phút', [
|
||||
'vm_id' => $vmId,
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
'assigned_ip' => $moduleIpAddress->ip,
|
||||
'delay_minutes' => 5
|
||||
]);
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address: ' . $e->getMessage(), [
|
||||
'vmware_id' => $vmId,
|
||||
@@ -284,7 +285,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Gửi thông báo thành công
|
||||
$telegram->SendJobRunInfo(
|
||||
'CreateVirtualMachineJob',
|
||||
@@ -333,22 +334,22 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
$ipPoolList = IpPool::where('module_network_id', $networkId)
|
||||
->orderBy('ip_address')
|
||||
->get();
|
||||
|
||||
|
||||
Log::info('✅ Lấy IP available từ pool', [
|
||||
'ip_pool_list' => $ipPoolList,
|
||||
'network_id' => $networkId
|
||||
]);
|
||||
|
||||
|
||||
if ($ipPoolList->isEmpty()) {
|
||||
// Nếu không có IP trong pool, trả về IP default
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
|
||||
// Lấy tất cả IP đã được assign trong ModuleIpAddress
|
||||
$assignedIPs = ModuleIpAddress::where('module_network_id', $networkId)
|
||||
->pluck('ip')
|
||||
->toArray();
|
||||
|
||||
|
||||
// Tìm IP đầu tiên chưa được assign
|
||||
foreach ($ipPoolList as $ipPool) {
|
||||
if (!in_array($ipPool->ip_address, $assignedIPs)) {
|
||||
@@ -359,14 +360,14 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
return $ipPool->ip_address;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Nếu tất cả IP đã được assign, log warning và trả về IP default
|
||||
Log::warning('⚠️ Không có IP available trong pool', [
|
||||
'network_id' => $networkId,
|
||||
'total_ips_in_pool' => $ipPoolList->count(),
|
||||
'assigned_ips' => count($assignedIPs)
|
||||
]);
|
||||
|
||||
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
@@ -381,21 +382,21 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
if (!$check) {
|
||||
return 'Login_Again';
|
||||
}
|
||||
|
||||
|
||||
// Gọi API start VM trực tiếp
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $vCenter->token
|
||||
])
|
||||
->post("https://{$vCenter->apiUri}/rest/vcenter/vm/{$vmId}/power/start");
|
||||
|
||||
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response->body()
|
||||
|
||||
@@ -29,10 +29,10 @@ class SyncAllPackage implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
SyncDatacenter::dispatchSync();
|
||||
SyncHost::dispatchSync();
|
||||
SyncDatastore::dispatchSync();
|
||||
SyncNetwork::dispatchSync();
|
||||
SyncFolder::dispatchSync();
|
||||
SyncDatacenter::dispatch();
|
||||
SyncHost::dispatch();
|
||||
SyncDatastore::dispatch();
|
||||
SyncNetwork::dispatch();
|
||||
SyncFolder::dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class SyncNetwork implements ShouldQueue
|
||||
'name' => $_subItem['name'],
|
||||
'network' => $_subItem['network'],
|
||||
'type' => $_subItem['type'],
|
||||
'ModuleHost_id' => $hostItem->id
|
||||
'module_host_id' => $hostItem->id
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VmPasswordResetSuccess extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public string $vmName,
|
||||
public string $newPassword
|
||||
) {}
|
||||
|
||||
public function build()
|
||||
{
|
||||
return $this->subject('Mật khẩu VPS của bạn đã được đặt lại')
|
||||
->view('emails.vm_password_reset_success');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Horizon\Horizon;
|
||||
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
||||
|
||||
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Horizon::routeSmsNotificationsTo('15556667777');
|
||||
// Horizon::routeMailNotificationsTo('example@example.com');
|
||||
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Horizon gate.
|
||||
*
|
||||
* This gate determines who can access Horizon in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewHorizon', function ($user = null) {
|
||||
return in_array(optional($user)->email, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Cache;
|
||||
|
||||
|
||||
use App\Services\Caches\CacheServiceInterface;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class CacheService implements CacheServiceInterface
|
||||
{
|
||||
protected int $ttl;
|
||||
|
||||
public function __construct(int $ttl = 60)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
|
||||
public function putCache(string $prefix, string $key, mixed $value, ?int $ttl = null): void
|
||||
{
|
||||
$cacheKey = $this->makeKey($prefix, $key);
|
||||
Cache::put($cacheKey, $value, $ttl ?? $this->ttl);
|
||||
}
|
||||
|
||||
public function getCache(string $prefix, string $key, mixed $default = null): mixed
|
||||
{
|
||||
$cacheKey = $this->makeKey($prefix, $key);
|
||||
return Cache::get($cacheKey, $default);
|
||||
}
|
||||
|
||||
public function clearCache(string $prefix, string $key): void
|
||||
{
|
||||
$cacheKey = $this->makeKey($prefix, $key);
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
|
||||
public function clearCacheByPrefix(string $prefix): void
|
||||
{
|
||||
|
||||
$cachePrefix = config('cache.prefix');
|
||||
|
||||
$pattern = $cachePrefix . $prefix . '_*';
|
||||
$keys = Redis::connection('cache')->keys($pattern);
|
||||
|
||||
|
||||
foreach ($keys as $k) {
|
||||
Redis::connection('cache')->del($k);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function makeKey(string $prefix, string $key = ''): string
|
||||
{
|
||||
return trim($prefix . '_' . $key, '_');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Caches;
|
||||
|
||||
interface CacheServiceInterface
|
||||
{
|
||||
public function putCache(string $prefix, string $key, mixed $value, ?int $ttl = null): void;
|
||||
public function getCache(string $prefix, string $key, mixed $default = null): mixed;
|
||||
public function clearCache(string $prefix, string $key): void;
|
||||
public function clearCacheByPrefix(string $prefix): void;
|
||||
}
|
||||
@@ -181,6 +181,7 @@ return [
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\RepositoryProvider::class
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the subdomain where Horizon will be accessible from. If this
|
||||
| setting is null, Horizon will reside under the same domain as the
|
||||
| application. Otherwise, this value will serve as the subdomain.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('HORIZON_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the URI path where Horizon will be accessible from. Feel free
|
||||
| to change this path to anything you like. Note that the URI will not
|
||||
| affect the paths of its internal API that aren't exposed to users.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('HORIZON_PATH', 'horizon'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Redis Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the name of the Redis connection where Horizon will store the
|
||||
| meta information required for it to function. It includes the list
|
||||
| of supervisors, failed jobs, job metrics, and other information.
|
||||
|
|
||||
*/
|
||||
|
||||
'use' => 'default',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Redis Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This prefix will be used when storing all Horizon data in Redis. You
|
||||
| may modify the prefix when you are running multiple installations
|
||||
| of Horizon on the same server so that they don't have problems.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env(
|
||||
'HORIZON_PREFIX',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Route Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These middleware will get attached onto each Horizon route, giving you
|
||||
| the chance to add your own middleware to this list or change any of
|
||||
| the existing middleware. Or, you can simply stick with this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Wait Time Thresholds
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to configure when the LongWaitDetected event
|
||||
| will be fired. Every connection / queue combination may have its
|
||||
| own, unique threshold (in seconds) before this event is fired.
|
||||
|
|
||||
*/
|
||||
|
||||
'waits' => [
|
||||
'redis:default' => 60,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Trimming Times
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you can configure for how long (in minutes) you desire Horizon to
|
||||
| persist the recent and failed jobs. Typically, recent jobs are kept
|
||||
| for one hour while all failed jobs are stored for an entire week.
|
||||
|
|
||||
*/
|
||||
|
||||
'trim' => [
|
||||
'recent' => 60,
|
||||
'pending' => 60,
|
||||
'completed' => 60,
|
||||
'recent_failed' => 10080,
|
||||
'failed' => 10080,
|
||||
'monitored' => 10080,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Silenced Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Silencing a job will instruct Horizon to not place the job in the list
|
||||
| of completed jobs within the Horizon dashboard. This setting may be
|
||||
| used to fully remove any noisy jobs from the completed jobs list.
|
||||
|
|
||||
*/
|
||||
|
||||
'silenced' => [
|
||||
// App\Jobs\ExampleJob::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Metrics
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you can configure how many snapshots should be kept to display in
|
||||
| the metrics graph. This will get used in combination with Horizon's
|
||||
| `horizon:snapshot` schedule to define how long to retain metrics.
|
||||
|
|
||||
*/
|
||||
|
||||
'metrics' => [
|
||||
'trim_snapshots' => [
|
||||
'job' => 24,
|
||||
'queue' => 24,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fast Termination
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When this option is enabled, Horizon's "terminate" command will not
|
||||
| wait on all of the workers to terminate unless the --wait option
|
||||
| is provided. Fast termination can shorten deployment delay by
|
||||
| allowing a new instance of Horizon to start while the last
|
||||
| instance will continue to terminate each of its workers.
|
||||
|
|
||||
*/
|
||||
|
||||
'fast_termination' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Memory Limit (MB)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value describes the maximum amount of memory the Horizon master
|
||||
| supervisor may consume before it is terminated and restarted. For
|
||||
| configuring these limits on your workers, see the next section.
|
||||
|
|
||||
*/
|
||||
|
||||
'memory_limit' => 64,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Worker Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the queue worker settings used by your application
|
||||
| in all environments. These supervisors and settings handle all your
|
||||
| queued jobs and will be provisioned by Horizon during deployment.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'supervisor-1' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['default'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 128,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
],
|
||||
],
|
||||
|
||||
'environments' => [
|
||||
'production' => [
|
||||
'supervisor-1' => [
|
||||
'maxProcesses' => 10,
|
||||
'balanceMaxShift' => 1,
|
||||
'balanceCooldown' => 3,
|
||||
],
|
||||
],
|
||||
|
||||
'local' => [
|
||||
'supervisor-1' => [
|
||||
'maxProcesses' => 3,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -1,28 +0,0 @@
|
||||
<?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::table('invoice', function (Blueprint $table) {
|
||||
$table->unsignedInteger('rebuild_count')->nullable()->default(0)->after('vm_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('invoice', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -29,5 +29,6 @@ class DatabaseSeeder extends Seeder
|
||||
$this->call(SnapshotLimitedSeeder::class);
|
||||
$this->call(TemplateSeeder::class);
|
||||
$this->call(Setting::class);
|
||||
$this->call(MenuSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,5 +174,3 @@
|
||||
<button type="submit" class="btn btn-outline-primary">Lưu dữ liệu</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<p>Xin chào,</p>
|
||||
<p>Mật khẩu VPS <strong>{{ $vmName }}</strong> đã được đặt lại thành công.</p>
|
||||
<p>Mật khẩu mới: <code>{{ $newPassword }}</code></p>
|
||||
<p>Vui lòng đăng nhập và đổi lại mật khẩu theo ý bạn.</p>
|
||||
<p>Trân trọng,</p>
|
||||
@@ -41,7 +41,7 @@
|
||||
<span class="text-gray-600 font-medium">Password</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-mono text-sm bg-gray-100 px-2 py-1 rounded"
|
||||
x-text="passwordVisible ? 'TGS@123' : '••••••••'">
|
||||
x-text="passwordVisible ? '' : '••••••••'">
|
||||
</span>
|
||||
<button type="button" @click="passwordVisible = !passwordVisible"
|
||||
class="text-blue-600 hover:text-blue-800" title="{{ __('Toggle password visibility') }}">
|
||||
|
||||
@@ -9,24 +9,31 @@
|
||||
</flux:text>
|
||||
</div>
|
||||
|
||||
<form x-data="{ submitting: false }" x-on:submit.prevent="if (submitting) return; submitting = true; $el.submit()" method="POST" action="{{ route('service.reset.vm.password') }}">
|
||||
<form x-data="{ submitting: false }" x-on:submit.prevent="if (submitting) return; submitting = true; $el.submit()"
|
||||
method="POST" action="{{ route('service.reset.vm.password') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="product_id" value="{{ $invoice->vm_id }}">
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Mật khẩu cũ') }}</flux:label>
|
||||
<flux:input name="old_password" type="password" placeholder="{{ __('Nhập mật khẩu cũ') }}" required
|
||||
minlength="8" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Mật khẩu mới') }}</flux:label>
|
||||
<flux:input name="new_password" type="password" placeholder="{{ __('Nhập mật khẩu mới') }}" required minlength="8" />
|
||||
<flux:description>{{ $isWindows ? __('Tài khoản: administrator') : __('Tài khoản: root') }}</flux:description>
|
||||
<flux:input name="new_password" type="password" placeholder="{{ __('Nhập mật khẩu mới') }}" required
|
||||
minlength="8" />
|
||||
<flux:description>{{ $isWindows ? __('Tài khoản: administrator') : __('Tài khoản: root') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
|
||||
<div class="flex gap-2 justify-end mt-6">
|
||||
<flux:modal.close>
|
||||
<flux:button variant="ghost">{{ __('Hủy') }}</flux:button>
|
||||
</flux:modal.close>
|
||||
<flux:button type="submit" variant="primary" color="orange" x-bind:disabled="submitting">{{ __('Đồng ý') }}</flux:button>
|
||||
<flux:button type="submit" variant="primary" color="orange" x-bind:disabled="submitting">
|
||||
{{ __('Đồng ý') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</flux:modal>
|
||||
|
||||
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
@include('frontend.service.partials.control-buttons.snapshot-button')
|
||||
@include('frontend.service.partials.control-buttons.reset-password-button')
|
||||
@include('frontend.service.partials.control-buttons.change-ip-button')
|
||||
@endif
|
||||
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Notes Section --}}
|
||||
@@ -62,7 +61,7 @@
|
||||
'isWindows' => $isWindows,
|
||||
])
|
||||
@include('frontend.service.partials.change-ip-modal', ['invoice' => $invoice])
|
||||
@include('frontend.service.partials.change-remote-port-modal', ['invoice' => $invoice])
|
||||
@include('frontend.service.partials.change-remote-port-modal', ['invoice' => $invoice])
|
||||
@include('frontend.service.partials.renewal-modal', ['invoice' => $invoice])
|
||||
@endif
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user