319 lines
15 KiB
PHP
319 lines
15 KiB
PHP
@extends('administrator.layout.console')
|
|
|
|
@push('style')
|
|
<link rel="stylesheet" href="{{ asset('lib/wmks/css/wmks-all.css') }}">
|
|
<link rel="stylesheet" href="{{ asset('css/service/console.css') }}">
|
|
@endpush
|
|
|
|
@section('breadcrumb')
|
|
@php
|
|
$breadcrumbItems = [
|
|
['label' => 'Modules'],
|
|
['label' => 'Package vCenter'],
|
|
['label' => 'Virtual Machine', 'route' => 'package.vm.index'],
|
|
['label' => $vm->name ?? $vm->vm_id ?? 'Console', 'active' => true],
|
|
];
|
|
@endphp
|
|
<x-admin-breadcrumb :items="$breadcrumbItems" />
|
|
@endsection
|
|
|
|
@section('content')
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<!-- VM Info Card -->
|
|
<div class="card card-outline card-secondary mb-3">
|
|
<div class="card-header d-flex bg-white align-items-center justify-content-between">
|
|
<h5 class="card-title mb-0">{{ $vm->name ?? 'VM Console' }} ({{ $vm->vm_id }})</h5>
|
|
<div class="card-tools">
|
|
<x-button-crm type="button" variant="outline-info" size="sm" route="package.vm.index" icon="list">Tất cả
|
|
VM</x-button-crm>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-2">
|
|
<div class="row">
|
|
<div class="col-md-2">
|
|
<small class="text-muted">Power State</small>
|
|
<p class="mb-0">{{ $vm->power_state ?? 'N/A' }}</p>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<small class="text-muted">CPU</small>
|
|
@if($vmStats && isset($vmStats['cpu']))
|
|
<p class="mb-0">
|
|
{{ $vmStats['cpu']['cores'] ?? $vm->cpu ?? 'N/A' }} vCore
|
|
@if(isset($vmStats['cpu']['used_percent']))
|
|
<span class="badge bg-{{ $vmStats['cpu']['used_percent'] > 80 ? 'danger' : 'success' }} ml-1">
|
|
{{ $vmStats['cpu']['used_percent'] }}%
|
|
</span>
|
|
@endif
|
|
</p>
|
|
@else
|
|
<p class="mb-0">{{ $vm->cpu ?? 'N/A' }} vCore</p>
|
|
@endif
|
|
</div>
|
|
<div class="col-md-2">
|
|
<small class="text-muted">RAM</small>
|
|
@if($vmStats && isset($vmStats['memory']))
|
|
<p class="mb-0">
|
|
{{ $vmStats['memory']['size_GB'] ?? round(($vm->ram_mb ?? 0) / 1024, 2) }} GB
|
|
@if(isset($vmStats['memory']['used_GB']))
|
|
<span class="badge bg-{{ $vmStats['memory']['usage_percent'] > 80 ? 'danger' : 'success' }} ml-1">
|
|
{{ $vmStats['memory']['used_GB'] }} GB ({{ $vmStats['memory']['usage_percent'] }}%)
|
|
</span>
|
|
@endif
|
|
</p>
|
|
@else
|
|
<p class="mb-0">{{ $vm->ram_mb ? round($vm->ram_mb / 1024, 2) : 'N/A' }} GB</p>
|
|
@endif
|
|
</div>
|
|
<div class="col-md-2">
|
|
<small class="text-muted">Disk</small>
|
|
@if($vmStats && isset($vmStats['disk']) && count($vmStats['disk']) > 0)
|
|
<p class="mb-0">
|
|
@foreach($vmStats['disk'] as $disk)
|
|
{{ $disk['capacity_GB'] }} GB
|
|
@endforeach
|
|
</p>
|
|
@else
|
|
<p class="mb-0">N/A</p>
|
|
@endif
|
|
</div>
|
|
<div class="col-md-2">
|
|
<small class="text-muted">Datacenter</small>
|
|
<p class="mb-0">{{ $vm->datacenter->name ?? 'N/A' }}</p>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<small class="text-muted">vCenter</small>
|
|
<p class="mb-0">
|
|
@if($vm->datacenter && $vm->datacenter->vCenter)
|
|
{{ $vm->datacenter->vCenter->host }}
|
|
@else
|
|
N/A
|
|
@endif
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Control Panel -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-white">
|
|
<h5 class="card-title fw-bold mb-0">Console Controls</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label for="sendStringText">Send Text:</label>
|
|
<input id="sendStringText" class="form-control" type="text"
|
|
placeholder="Enter text...">
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label> </label>
|
|
<div class="btn-group d-flex gap-2" role="group">
|
|
<x-button-c-r-m type="button" variant="primary" size="sm" icon="maximize" onclick="enterFullScreen()" class="flex-fill">
|
|
Fullscreen
|
|
</x-button-c-r-m>
|
|
<x-button-c-r-m type="button" variant="info" size="sm" icon="keyboard" onclick="sendCAD()" class="flex-fill">
|
|
Ctrl+Alt+Del
|
|
</x-button-c-r-m>
|
|
<x-button-c-r-m type="button" variant="success" size="sm" icon="send" onclick="sendInputString()" class="flex-fill">
|
|
Send
|
|
</x-button-c-r-m>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Console Container -->
|
|
<div class="card">
|
|
<div class="card-body p-0 position-relative">
|
|
<!-- Loading State -->
|
|
<div id="loadingState"
|
|
class="loading-container d-flex align-items-center justify-content-center fade-transition"
|
|
style="height: 70vh;">
|
|
<div class="text-center" style="position: relative; z-index: 1; color: #3b82f6;">
|
|
<div class="lds-roller mb-4">
|
|
<div></div>
|
|
<div></div>
|
|
<div></div>
|
|
<div></div>
|
|
<div></div>
|
|
<div></div>
|
|
<div></div>
|
|
<div></div>
|
|
</div>
|
|
<h5 class="loading-title mb-2">Connecting to vCenter</h5>
|
|
<p class="loading-text mb-1" id="loadingText">Initializing console</p>
|
|
<small class="loading-status" id="connectionStatus">Setting up session...</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Console Display -->
|
|
<div id="wmksContainer"
|
|
class="console-container d-flex justify-content-center align-items-center bg-dark fade-transition"
|
|
style="height: 70vh; opacity: 0; position: absolute; top: 0; left: 0; right: 0;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- jQuery 3 and jQuery UI are loaded in administrator.layout.console <head> --}}
|
|
<script src="{{ asset('lib/wmks/wmks.min.js') }}"></script>
|
|
|
|
<script type="text/javascript">
|
|
let loadingSteps = [
|
|
'{{ __('Khởi tạo kết nối...') }}',
|
|
'{{ __('Đang xác thực...') }}',
|
|
'{{ __('Thiết lập phiên làm việc...') }}',
|
|
'{{ __('Đang kết nối Console...') }}',
|
|
'{{ __('Sẵn sàng!') }}'
|
|
];
|
|
let currentStep = 0;
|
|
|
|
$(document).ready(function() {
|
|
showLoadingState();
|
|
animateLoadingText();
|
|
});
|
|
|
|
function showLoadingState() {
|
|
$('#loadingState').css('opacity', '1').show();
|
|
$('#wmksContainer').css('opacity', '0').hide();
|
|
}
|
|
|
|
function hideLoadingAndShowConsole() {
|
|
$('#loadingText').text('{{ __('Kết nối thành công!') }}');
|
|
$('#connectionStatus').text('{{ __('Console đã sẵn sàng') }}');
|
|
|
|
setTimeout(() => {
|
|
$('#loadingState').animate({ opacity: 0 }, 400, function() {
|
|
$(this).hide();
|
|
$('#wmksContainer').show().animate({ opacity: 1 }, 400);
|
|
});
|
|
}, 800);
|
|
}
|
|
|
|
function showErrorState(errorMessage) {
|
|
$('#loadingState').animate({ opacity: 0 }, 300, function() {
|
|
$(this).hide();
|
|
$('#wmksContainer').html(`
|
|
<div class="d-flex align-items-center justify-content-center w-100 h-100">
|
|
<div class="text-center text-black">
|
|
<div class="mb-3">
|
|
<i class="fas fa-exclamation-triangle text-danger" style="font-size: 3rem;"></i>
|
|
</div>
|
|
<h5>{{ __('Kết nối thất bại') }}</h5>
|
|
<p class="text-muted">${errorMessage || '{{ __('Máy chủ không sẵn sàng hoặc có lỗi kết nối') }}'}</p>
|
|
<button onclick="location.reload()" class="btn btn-primary">
|
|
<i class="fas fa-redo"></i> {{ __('Thử lại') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).show().animate({ opacity: 1 }, 400);
|
|
});
|
|
}
|
|
|
|
function animateLoadingText() {
|
|
setInterval(() => {
|
|
if (currentStep < loadingSteps.length - 1) {
|
|
currentStep++;
|
|
$('#loadingText').fadeOut(200, function() {
|
|
$(this).text(loadingSteps[currentStep]).fadeIn(200);
|
|
});
|
|
}
|
|
}, 1500);
|
|
}
|
|
|
|
function updateConnectionStatus(status) {
|
|
$('#connectionStatus').fadeOut(200, function() {
|
|
$(this).text(status).fadeIn(200);
|
|
});
|
|
}
|
|
|
|
let WMKSContainer = $('#wmksContainer');
|
|
var wmks = WMKS.createWMKS("wmksContainer", {})
|
|
.register(WMKS.CONST.Events.CONNECTION_STATE_CHANGE, function(event, data) {
|
|
console.log("WMKS state:", data.state, data);
|
|
if (data.state === WMKS.CONST.ConnectionState.CONNECTED) {
|
|
hideLoadingAndShowConsole();
|
|
} else if (data.state === WMKS.CONST.ConnectionState.DISCONNECTED) {
|
|
showErrorState('{{ __('Mất kết nối WebSocket') }} (code: ' + data.code + ', reason: ' + data.reason + ')');
|
|
}
|
|
})
|
|
.register(WMKS.CONST.Events.ERROR, function(event, data) {
|
|
console.error("WMKS error:", data);
|
|
showErrorState('{{ __('Lỗi WMKS') }}: ' + (data.errorType || JSON.stringify(data)));
|
|
});
|
|
|
|
const getTicket = new Promise((resolve, reject) => {
|
|
updateConnectionStatus('{{ __('Đang lấy ticket xác thực...') }}');
|
|
|
|
$.ajax({
|
|
url: '{{ route('admin.getConsoleTicket') }}',
|
|
type: 'get',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Accept': 'application/json'
|
|
},
|
|
data: {
|
|
vm_id: '{{ $vm->vm_id }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.error === false) {
|
|
updateConnectionStatus('{{ __('Đang thiết lập kết nối Console...') }}');
|
|
resolve(response);
|
|
} else {
|
|
reject(response.message);
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
reject(`{{ __('Lỗi kết nối:') }} ${error}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
getTicket
|
|
.then(result => {
|
|
if (result !== false) {
|
|
updateConnectionStatus('{{ __('Đang kết nối đến Console...') }}');
|
|
wmks.connect(result.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.log(`Có lỗi xảy ra: ${error}.`);
|
|
showErrorState(error);
|
|
});
|
|
|
|
function enterFullScreen() {
|
|
if (wmks && wmks.canFullScreen()) {
|
|
wmks.enterFullScreen();
|
|
}
|
|
}
|
|
|
|
function sendCAD() {
|
|
if (wmks) {
|
|
wmks.sendCAD();
|
|
}
|
|
}
|
|
|
|
function sendInputString() {
|
|
const inputText = $('#sendStringText').val().trim();
|
|
if (inputText && wmks) {
|
|
wmks.sendInputString(inputText);
|
|
$('#sendStringText').val('');
|
|
} else if (!inputText) {
|
|
alert('{{ __('Vui lòng nhập văn bản cần gửi') }}');
|
|
}
|
|
}
|
|
|
|
$('#sendStringText').on('keypress', function(e) {
|
|
if (e.which == 13) {
|
|
sendInputString();
|
|
}
|
|
});
|
|
</script>
|
|
@endsection
|