feat: Implement VMware console access with a new API endpoint and UI, and update the admin header to display user details.

This commit is contained in:
pbao9
2026-02-14 15:36:25 +07:00
parent 78b87c10fe
commit 449502e290
7 changed files with 433 additions and 452 deletions
@@ -193,9 +193,6 @@ class ProductController extends Controller
);
return DataTables::eloquent($query)
->addColumn('checkbox', function ($d) {
return '<input type="checkbox" class="form-check-input dt-checkbox" value="' . $d->id . '">';
})
->addColumn('category', function ($d) {
return view('administrator.pages.product.datatable.category', ['row' => $d])->render();
})
@@ -208,13 +205,10 @@ class ProductController extends Controller
->addColumn('status', function ($d) {
return view('administrator.pages.product.datatable.status', ['row' => $d])->render();
})
->addColumn(
'action',
function ($d) {
return view('administrator.pages.product.datatable.action', ['row' => $d])->render();
}
)
->rawColumns(['checkbox', 'category', 'price', 'specs', 'status', 'action'])
->addColumn('action', function ($d) {
return view('administrator.pages.product.datatable.action', ['row' => $d])->render();
})
->rawColumns(['category', 'price', 'specs', 'status', 'action'])
->make(true);
}
@@ -186,27 +186,31 @@
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="profileDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="w-30px h-30px ms-1 rounded-circle" src="{{ url('https://placehold.co/30x30') }}" alt="profile">
<img class="w-30px h-30px ms-1 rounded-circle"
src="https://ui-avatars.com/api/?name={{ auth()->user()->lastname }}+{{ auth()->user()->firstname }}&background=random"
alt="profile">
</a>
<div class="dropdown-menu p-0" aria-labelledby="profileDropdown">
<div class="d-flex flex-column align-items-center border-bottom px-5 py-3">
<div class="mb-3">
<img class="w-80px h-80px rounded-circle" src="{{ url('https://placehold.co/80x80') }}" alt="">
<img class="w-80px h-80px rounded-circle"
src="https://ui-avatars.com/api/?name={{ auth()->user()->lastname }}+{{ auth()->user()->firstname }}&background=random"
alt="">
</div>
<div class="text-center">
<p class="fs-16px fw-bolder">Amiah Burton</p>
<p class="fs-12px text-secondary">amiahburton@gmail.com</p>
<p class="fs-16px fw-bolder">{{ auth()->user()->lastname }} {{ auth()->user()->firstname }}</p>
<p class="fs-12px text-secondary">{{ auth()->user()->email }}</p>
</div>
</div>
<ul class="list-unstyled p-1">
<li>
<a href="{{ url('/general/profile') }}" class="dropdown-item py-2 text-body ms-0">
<a href="{{ route('account') }}" class="dropdown-item py-2 text-body ms-0">
<i class="me-2 icon-md" data-lucide="user"></i>
<span>Profile</span>
</a>
</li>
<li>
<a href="javascript:;" class="dropdown-item py-2 text-body ms-0">
<a href="{{ route('account') }}" class="dropdown-item py-2 text-body ms-0">
<i class="me-2 icon-md" data-lucide="edit"></i>
<span>Edit Profile</span>
</a>
@@ -218,7 +222,7 @@
</a>
</li>
<li>
<a href="javascript:;" class="dropdown-item py-2 text-body ms-0">
<a href="{{ route('logout') }}" class="dropdown-item py-2 text-body ms-0">
<i class="me-2 icon-md" data-lucide="log-out"></i>
<span>Log Out</span>
</a>
@@ -1,11 +1,12 @@
<div class="dt-action-dropdown dropdown">
<button class="btn btn-sm btn-light border d-flex align-items-center gap-1" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<button class="btn btn-sm btn-light d-flex align-items-center gap-1" type="button" data-bs-toggle="dropdown"
aria-expanded="false">
<i data-lucide="more-vertical" width="14" height="14"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{{ route('products.edit', ['id' => $row->id]) }}">
<i data-lucide="pencil" width="14" height="14"></i>
<i data-lucide="pencil" width="14" height="14" class="me-2"></i>
Edit
</a>
</li>
@@ -13,22 +14,42 @@
<form action="{{ route('products.duplicate', ['id' => $row->id]) }}" method="POST" class="d-inline">
@csrf
<button type="submit" class="dropdown-item">
<i data-lucide="copy" width="14" height="14"></i>
<i data-lucide="copy" width="14" height="14" class="me-2"></i>
Duplicate
</button>
</form>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<form action="{{ route('products.destroy', ['id' => $row->id]) }}" method="POST" class="d-inline"
onsubmit="return confirm('Are you sure you want to delete this product?')">
@csrf
@method('DELETE')
<button type="submit" class="dropdown-item text-danger">
<i data-lucide="trash-2" width="14" height="14"></i>
<hr class="dropdown-divider">
<li>
<button type="button" class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i data-lucide="trash-2" width="14" height="14" class="me-2 text-danger"></i>
Delete
</button>
</form>
</li>
</ul>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form action="{{ route('products.destroy', ['id' => $row->id]) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<input type="hidden" name="id" value="{{ $row->id }}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Xoá sản phẩm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Bạn có chắc chắn muốn xoá sản phẩm này?
<strong>{{ $row->name }}</strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Huỷ</button>
<button type="submit" class="btn btn-danger" id="confirmDeleteBtn">Xoá</button>
</div>
</div>
</form>
</div>
</div>
@@ -16,231 +16,227 @@ $breadcrumbItems = [
<x-admin-breadcrumb :items="$breadcrumbItems" />
@endsection
@section('content')
<form action="{{ route('products.update', $product->id) }}" method="post">
@csrf
@method('PUT')
<div class="row align-items-start">
{{-- Left Column: Basic Info & Pricing --}}
<div class="col-md-8">
<div class="card card-primary card-outline">
<div class="card-header">
<h5 class="card-title m-0">Edit Product</h5>
</div>
<div class="card-body">
<form action="{{ route('products.update', $product->id) }}" method="post">
@csrf
@method('PUT')
<div class="row align-items-start">
<div class="col-md-8">
<div class="card card-primary card-outline">
<div class="card-header bg-white">
<h5 class="card-title m-0">Edit Product</h5>
</div>
<div class="card-body">
<div class="mb-4">
<h5 class="text-primary border-bottom mb-3 pb-2">Basic Information</h5>
{{-- Basic Information --}}
<div class="mb-4">
<h5 class="text-primary border-bottom mb-3 pb-2">Basic Information</h5>
<div class="row mb-3">
<div class="col-md-12">
<div class="form-group">
<label for="name">Product Name <span class="text-danger">*</span></label>
<input type="text" id="name" name="name" value="{{ old('name', $product->name) }}"
class="form-control" required placeholder="Enter product name" />
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<div class="form-group">
<label for="name">Product Name <span class="text-danger">*</span></label>
<input type="text" id="name" name="name"
value="{{ old('name', $product->name) }}"
class="form-control" required placeholder="Enter product name" />
<div class="form-group mb-3">
<label for="short_description">Short Description</label>
<textarea name="short_description" id="short_description" rows="2" class="form-control"
placeholder="Brief summary">{{ old('short_description', $product->short_description) }}</textarea>
</div>
<div class="form-group mb-3">
<label for="description">Full Description</label>
<textarea name="description" id="description" rows="4" class="form-control"
placeholder="Detailed description">{{ old('description', $product->description) }}</textarea>
</div>
</div>
</div>
<div class="form-group mb-3">
<label for="short_description">Short Description</label>
<textarea name="short_description" id="short_description" rows="2" class="form-control"
placeholder="Brief summary">{{ old('short_description', $product->short_description) }}</textarea>
</div>
<div class="form-group mb-3">
<label for="description">Full Description</label>
<textarea name="description" id="description" rows="4" class="form-control"
placeholder="Detailed description">{{ old('description', $product->description) }}</textarea>
</div>
</div>
{{-- Pricing & Billing --}}
<div class="mb-4">
<h5 class="text-primary border-bottom mb-3 pb-2">Pricing & Billing</h5>
{{-- Global Settings --}}
<div class="row mb-3">
<div class="col-md-4">
<div class="form-group">
<label for="currency">Currency</label>
<select name="currency" id="currency" class="form-control select2">
<option value="USD"
{{ old('currency', $product->currency ?? 'USD') == 'USD' ? 'selected' : '' }}>
USD</option>
<option value="VND"
{{ old('currency', $product->currency ?? 'VND') == 'VND' ? 'selected' : '' }}>
VND</option>
</select>
{{-- Pricing & Billing --}}
<div class="mb-4">
<h5 class="text-primary border-bottom mb-3 pb-2">Pricing & Billing</h5>
{{-- Global Settings --}}
<div class="row mb-3">
<div class="col-md-4">
<div class="form-group">
<label for="currency">Currency</label>
<select name="currency" id="currency" class="form-control select2">
<option value="USD"
{{ old('currency', $product->currency ?? 'USD') == 'USD' ? 'selected' : '' }}>
USD</option>
<option value="VND"
{{ old('currency', $product->currency ?? 'VND') == 'VND' ? 'selected' : '' }}>
VND</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="setup_fee">Setup Fee</label>
<input type="number" step="0.01" id="setup_fee" name="setup_fee"
value="{{ old('setup_fee', $product->setup_fee ?? 0) }}" class="form-control">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="trial_days">Trial Days</label>
<input type="number" id="trial_days" name="trial_days"
value="{{ old('trial_days', $product->trial_days ?? 0) }}" class="form-control">
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="setup_fee">Setup Fee</label>
<input type="number" step="0.01" id="setup_fee" name="setup_fee"
value="{{ old('setup_fee', $product->setup_fee ?? 0) }}" class="form-control">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="trial_days">Trial Days</label>
<input type="number" id="trial_days" name="trial_days"
value="{{ old('trial_days', $product->trial_days ?? 0) }}" class="form-control">
</div>
</div>
</div>
{{-- Billing Cycles Table --}}
<label class="fw-bold mb-2">Billing Cycles</label>
<table class="table">
<thead>
<tr>
<th scope="col" style="width: 50px;">#</th>
<th scope="col">Billing Cycle</th>
<th scope="col">Price</th>
<th scope="col">Sale Price</th>
</tr>
</thead>
<tbody>
@foreach (\App\Enums\Product\ProductBillingCycle::cases() as $cycle)
@php
{{-- Billing Cycles Table --}}
<label class="fw-bold mb-2">Billing Cycles</label>
<table class="table">
<thead>
<tr>
<th scope="col" style="width: 50px;">#</th>
<th scope="col">Billing Cycle</th>
<th scope="col">Price</th>
<th scope="col">Sale Price</th>
</tr>
</thead>
<tbody>
@foreach (\App\Enums\Product\ProductBillingCycle::cases() as $cycle)
@php
$pricing = $product->pricings
->where('billing_cycle', $cycle->value)
->first();
@endphp
<tr>
<th scope="row" class="text-center align-middle">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
id="cycle_{{ $cycle->value }}"
name="pricing[{{ $cycle->value }}][enabled]" value="1"
{{ old("pricing.{$cycle->value}.enabled", $pricing ? 1 : 0) ? 'checked' : '' }}>
<label class="form-check-label"
for="cycle_{{ $cycle->value }}"></label>
</div>
</th>
<td class="align-middle">
{{ $cycle->label() }}
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" step="0.01" class="form-control"
name="pricing[{{ $cycle->value }}][price]" placeholder="0.00"
value="{{ old("pricing.{$cycle->value}.price", $pricing->price ?? '') }}">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" step="0.01" class="form-control"
name="pricing[{{ $cycle->value }}][price_sale]"
placeholder="0.00"
value="{{ old("pricing.{$cycle->value}.price_sale", $pricing->price_sale ?? '') }}">
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endphp
<tr>
<th scope="row" class="text-center align-middle">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
id="cycle_{{ $cycle->value }}"
name="pricing[{{ $cycle->value }}][enabled]" value="1"
{{ old("pricing.{$cycle->value}.enabled", $pricing ? 1 : 0) ? 'checked' : '' }}>
<label class="form-check-label"
for="cycle_{{ $cycle->value }}"></label>
</div>
</th>
<td class="align-middle">
{{ $cycle->label() }}
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" step="0.01" class="form-control"
name="pricing[{{ $cycle->value }}][price]" placeholder="0.00"
value="{{ old("pricing.{$cycle->value}.price", $pricing->price ?? '') }}">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" step="0.01" class="form-control"
name="pricing[{{ $cycle->value }}][price_sale]"
placeholder="0.00"
value="{{ old("pricing.{$cycle->value}.price_sale", $pricing->price_sale ?? '') }}">
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{-- Hardware Specs (VPS/Dedicated) --}}
<div class="mb-4">
<h5 class="text-info border-bottom mb-3 pb-2">Hardware Specifications</h5>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="cpu">CPU</label>
<input type="text" name="cpu" id="cpu" class="form-control"
value="{{ old('cpu', $product->cpu) }}" placeholder="e.g. 2 Core" />
{{-- Hardware Specs (VPS/Dedicated) --}}
<div class="mb-4">
<h5 class="text-info border-bottom mb-3 pb-2">Hardware Specifications</h5>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="cpu">CPU</label>
<input type="text" name="cpu" id="cpu" class="form-control"
value="{{ old('cpu', $product->cpu) }}" placeholder="e.g. 2 Core" />
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="ram">RAM</label>
<input type="text" name="ram" id="ram" class="form-control"
value="{{ old('ram', $product->ram) }}" placeholder="e.g. 4 GB" />
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="disk">Disk/Storage</label>
<input type="text" name="disk" id="disk" class="form-control"
value="{{ old('disk', $product->disk) }}" placeholder="e.g. 50 GB NVMe" />
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="ram">RAM</label>
<input type="text" name="ram" id="ram" class="form-control"
value="{{ old('ram', $product->ram) }}" placeholder="e.g. 4 GB" />
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="disk">Disk/Storage</label>
<input type="text" name="disk" id="disk" class="form-control"
value="{{ old('disk', $product->disk) }}" placeholder="e.g. 50 GB NVMe" />
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="form-group">
<label for="bandwidth">Bandwidth</label>
<input type="text" name="bandwidth" id="bandwidth" class="form-control"
value="{{ old('bandwidth', $product->bandwidth) }}" placeholder="e.g. Unlimited" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="gpu">GPU</label>
<input type="text" name="gpu" id="gpu" class="form-control"
value="{{ old('gpu', $product->gpu) }}" placeholder="Optional">
<div class="row mt-3">
<div class="col-md-6">
<div class="form-group">
<label for="bandwidth">Bandwidth</label>
<input type="text" name="bandwidth" id="bandwidth" class="form-control"
value="{{ old('bandwidth', $product->bandwidth) }}" placeholder="e.g. Unlimited" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="gpu">GPU</label>
<input type="text" name="gpu" id="gpu" class="form-control"
value="{{ old('gpu', $product->gpu) }}" placeholder="Optional">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- Right Column: Classification & Settings --}}
<div class="col-md-4 sticky-top top-10 z-0">
<div class="card card-secondary card-outline mb-3">
<div class="card-header">
<h5 class="card-title m-0">Classification</h5>
</div>
<div class="card-body">
{{-- Product Category --}}
<div class="form-group mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label for="product_categories_id">Group</label>
<a href="{{ route('products.group.index') }}"
class="text-primary text-decoration-underline">
<i data-lucide="plus" class="w-12px h-12px"></i>
Thêm Group
</a>
{{-- Right Column: Classification & Settings --}}
<div class="col-md-4 sticky-top top-10 z-0">
<div class="card card-secondary card-outline mb-3">
<div class="card-header bg-white">
<h5 class="card-title m-0">Classification</h5>
</div>
@include('administrator.pages.product.categories.partials.category-tree', [
'categories' => $group,
'selected' => old('product_categories_id', $product->product_categories_id),
'name' => 'product_categories_id',
])
@error('product_categories_id')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
<div class="card-body">
{{-- Product Category --}}
<div class="form-group mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label for="product_categories_id">Group</label>
<a href="{{ route('products.group.index') }}"
class="text-primary text-decoration-underline">
<i data-lucide="plus" class="w-12px h-12px"></i>
Thêm Group
</a>
</div>
@include('administrator.pages.product.categories.partials.category-tree', [
'categories' => $group,
'selected' => old('product_categories_id', $product->product_categories_id),
'name' => 'product_categories_id',
])
@error('product_categories_id')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
{{-- Operating System (for VPS) --}}
<div class="form-group mb-3" id="os-section">
<label for="os_ids" class="mb-2">Operating System</label>
@php
{{-- Operating System (for VPS) --}}
<div class="form-group mb-3" id="os-section">
<label for="os_ids" class="mb-2">Operating System</label>
@php
$selectedOsIds = old('os_ids', $product->osVersions->pluck('id')->toArray());
@endphp
<select class="form-control select2" name="os_ids[]" id="os_ids" multiple
data-placeholder="-- Select OS versions --">
@foreach ($osVersions as $osVersion)
<option value="{{ $osVersion->id }}"
{{ in_array($osVersion->id, $selectedOsIds) ? 'selected' : '' }}>
{{ $osVersion->osGroup->name ?? 'Unknown' }} - {{ $osVersion->version }}
</option>
@endforeach
</select>
<small class="text-muted">Select multiple OS versions supported by this product</small>
</div>
@endphp
<select class="form-control select2" name="os_ids[]" id="os_ids" multiple
data-placeholder="-- Select OS versions --">
@foreach ($osVersions as $osVersion)
<option value="{{ $osVersion->id }}"
{{ in_array($osVersion->id, $selectedOsIds) ? 'selected' : '' }}>
{{ $osVersion->osGroup->name ?? 'Unknown' }} - {{ $osVersion->version }}
</option>
@endforeach
</select>
<small class="text-muted">Select multiple OS versions supported by this product</small>
</div>
{{-- Control Panels (for Hosting) --}}
<div class="form-group mb-3" id="control-panel-section" style="display: none;">
<label for="control_panel_ids" class="mb-2">Control Panels</label>
@php
{{-- Control Panels (for Hosting) --}}
<div class="form-group mb-3" id="control-panel-section" style="display: none;">
<label for="control_panel_ids" class="mb-2">Control Panels</label>
@php
$selectedPanelIds = old('control_panel_ids', $product->controlPanels->pluck('id')->toArray());
$panelPriceModifiers = old(
'control_panel_price_modifiers',
@@ -250,117 +246,117 @@ $breadcrumbItems = [
'control_panel_default',
$product->controlPanels->where('pivot.is_default', true)->first()?->id
);
@endphp
<select class="form-control select2" name="control_panel_ids[]" id="control_panel_ids" multiple data-placeholder="-- Select Control Panels --">
@foreach ($controlPanels as $panel)
<option value="{{ $panel->id }}" {{ in_array($panel->id, $selectedPanelIds) ? 'selected' : '' }}>
{{ $panel->name }}
</option>
@endforeach
</select>
<small class="text-muted">Select control panels available for this hosting product</small>
@endphp
<select class="form-control select2" name="control_panel_ids[]" id="control_panel_ids" multiple data-placeholder="-- Select Control Panels --">
@foreach ($controlPanels as $panel)
<option value="{{ $panel->id }}" {{ in_array($panel->id, $selectedPanelIds) ? 'selected' : '' }}>
{{ $panel->name }}
</option>
@endforeach
</select>
<small class="text-muted">Select control panels available for this hosting product</small>
{{-- Price modifiers for each panel --}}
<div id="panel-price-modifiers" class="mt-3">
@foreach ($controlPanels as $panel)
<div class="panel-price-row mb-2" id="panel-price-{{ $panel->id }}"
style="display: {{ in_array($panel->id, $selectedPanelIds) ? 'block' : 'none' }};">
<label class="form-label">{{ $panel->name }} - Phụ phí (VNĐ)</label>
<div class="input-group">
<input type="number"
class="form-control"
name="control_panel_price_modifiers[{{ $panel->id }}]"
placeholder="0"
min="0"
step="1000"
value="{{ $panelPriceModifiers[$panel->id] ?? 0 }}">
<div class="input-group-append">
<div class="input-group-text">
<input type="radio"
name="control_panel_default"
value="{{ $panel->id }}"
{{ $defaultPanel == $panel->id ? 'checked' : '' }}>
<span class="ms-1">Mặc định</span>
{{-- Price modifiers for each panel --}}
<div id="panel-price-modifiers" class="mt-3">
@foreach ($controlPanels as $panel)
<div class="panel-price-row mb-2" id="panel-price-{{ $panel->id }}"
style="display: {{ in_array($panel->id, $selectedPanelIds) ? 'block' : 'none' }};">
<label class="form-label">{{ $panel->name }} - Phụ phí (VNĐ)</label>
<div class="input-group">
<input type="number"
class="form-control"
name="control_panel_price_modifiers[{{ $panel->id }}]"
placeholder="0"
min="0"
step="1000"
value="{{ $panelPriceModifiers[$panel->id] ?? 0 }}">
<div class="input-group-append">
<div class="input-group-text">
<input type="radio"
name="control_panel_default"
value="{{ $panel->id }}"
{{ $defaultPanel == $panel->id ? 'checked' : '' }}>
<span class="ms-1">Mặc định</span>
</div>
</div>
</div>
</div>
</div>
@endforeach
</div>
@endforeach
</div>
{{-- Product Type --}}
<div class="form-group mb-3">
<label for="product_type" class="mb-2">Product Type</label>
<select class="form-control select2" name="product_type" id="product_type">
<option value="">-- Select Type --</option>
@foreach (\App\Enums\Product\ProductTypeEnums::cases() as $type)
<option value="{{ $type->value }}"
{{ old('product_type', $product->product_type) == $type->value ? 'selected' : '' }}>
{{ $type->label() }}
</option>
@endforeach
</select>
</div>
{{-- Status --}}
<div class="form-group mb-3">
<label for="is_active" class="mb-2">Status</label>
<select class="form-control select2" name="is_active" id="is_active">
@foreach (\App\Enums\Status::cases() as $status)
<option value="{{ $status->value }}"
{{ old('is_active', $product->is_active ? 1 : 0) == $status->value ? 'selected' : '' }}>
{{ $status->label() }}
</option>
@endforeach
</select>
</div>
<div class="form-group mb-3">
<label for="sort_order" class="mb-2">Sort Order</label>
<input type="number" name="sort_order" id="sort_order" class="form-control"
value="{{ old('sort_order', $product->sort_order ?? 0) }}">
</div>
</div>
</div>
{{-- Product Type --}}
<div class="form-group mb-3">
<label for="product_type" class="mb-2">Product Type</label>
<select class="form-control select2" name="product_type" id="product_type">
<option value="">-- Select Type --</option>
@foreach (\App\Enums\Product\ProductTypeEnums::cases() as $type)
<option value="{{ $type->value }}"
{{ old('product_type', $product->product_type) == $type->value ? 'selected' : '' }}>
{{ $type->label() }}
</option>
@endforeach
</select>
</div>
<div class="card card-secondary card-outline">
<div class="card-header bg-white">
<h5 class="card-title m-0">Inventory & Limits</h5>
</div>
<div class="card-body">
<div class="form-group">
<label for="stock">Stock Quantity</label>
<input type="number" name="stock" id="stock" class="form-control"
placeholder="Unlimited if empty" value="{{ old('stock', $product->stock) }}">
</div>
{{-- Status --}}
<div class="form-group mb-3">
<label for="is_active" class="mb-2">Status</label>
<select class="form-control select2" name="is_active" id="is_active">
@foreach (\App\Enums\Status::cases() as $status)
<option value="{{ $status->value }}"
{{ old('is_active', $product->is_active ? 1 : 0) == $status->value ? 'selected' : '' }}>
{{ $status->label() }}
</option>
@endforeach
</select>
</div>
<div class="form-group mt-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="is_unlimited"
name="is_unlimited" value="1"
{{ old('is_unlimited', $product->is_unlimited) ? 'checked' : '' }}>
<label class="form-check-label" for="is_unlimited">Unlimited Stock</label>
</div>
</div>
<div class="form-group mb-3">
<label for="sort_order" class="mb-2">Sort Order</label>
<input type="number" name="sort_order" id="sort_order" class="form-control"
value="{{ old('sort_order', $product->sort_order ?? 0) }}">
<div class="form-group mt-3">
<label for="published_at">Publish Date</label>
<input type="date" name="published_at" id="published_at" class="form-control"
value="{{ old('published_at', $product->published_at?->format('Y-m-d')) }}">
</div>
</div>
</div>
</div>
</div>
<div class="card card-secondary card-outline">
<div class="card-header">
<h5 class="card-title m-0">Inventory & Limits</h5>
</div>
<div class="card-body">
<div class="form-group">
<label for="stock">Stock Quantity</label>
<input type="number" name="stock" id="stock" class="form-control"
placeholder="Unlimited if empty" value="{{ old('stock', $product->stock) }}">
</div>
<div class="form-group mt-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="is_unlimited"
name="is_unlimited" value="1"
{{ old('is_unlimited', $product->is_unlimited) ? 'checked' : '' }}>
<label class="form-check-label" for="is_unlimited">Unlimited Stock</label>
</div>
</div>
<div class="form-group mt-3">
<label for="published_at">Publish Date</label>
<input type="date" name="published_at" id="published_at" class="form-control"
value="{{ old('published_at', $product->published_at?->format('Y-m-d')) }}">
</div>
</div>
</div>
</div>
</div>
<x-fixed-action-bar>
<x-button-crm type="button" variant="secondary" size="sm" icon="arrow-left"
route="products.index">Cancel</x-button-crm>
<x-button-crm type="submit" variant="primary" size="sm" icon="save">Update
Product</x-button-crm>
</x-fixed-action-bar>
</form>
<x-fixed-action-bar>
<x-button-crm type="button" variant="secondary" size="sm" icon="arrow-left"
route="products.index">Cancel</x-button-crm>
<x-button-crm type="submit" variant="primary" size="sm" icon="save">Update
Product</x-button-crm>
</x-fixed-action-bar>
</form>
@endsection
@push('plugin-scripts')
@foreach (config('assets.datatable.js') as $datatable)
@@ -7,47 +7,46 @@
@section('breadcrumb')
@php
$breadcrumbItems = [
['label' => 'Product'],
['label' => 'Product / Services'],
['label' => 'Product List', 'active' => true],
];
$breadcrumbItems = [
['label' => 'Product'],
['label' => 'Product / Services'],
['label' => 'Product List', 'active' => true],
];
@endphp
<x-admin-breadcrumb :items="$breadcrumbItems" />
@endsection
@section('tab')
@php
$tabs = [
[
'label' => 'Product',
'route' => 'products.index',
'icon' => 'server',
],
[
'label' => 'Product Group',
'route' => 'products.group.index',
'icon' => 'server',
],
];
$tabs = [
[
'label' => 'Product',
'route' => 'products.index',
'icon' => 'server',
],
[
'label' => 'Product Group',
'route' => 'products.group.index',
'icon' => 'server',
],
];
@endphp
<x-admin-tab :items="$tabs" />
@endsection
@section('content')
@if (auth()->guard('admins')->user()->can('manager product'))
<div class="card card-primary card-outline rounded-4 shadow-none">
<div class="card-header d-flex justify-content-end gap-2 rounded-top-4">
<div class="card-header bg-white d-flex justify-content-end gap-2 rounded-top-4">
@include('administrator.pages.block.product.btn')
</div>
<div class="card-body p-0">
@php
$productColumns = [
['key' => 'checkbox', 'label' => '<input type="checkbox" class="form-check-input" id="dt-master-checkbox">', 'orderable' => false, 'searchable' => false, 'className' => 'text-center'],
['key' => 'name', 'label' => 'Product Name'],
['key' => 'category', 'label' => 'Category / Type', 'searchable' => false],
['key' => 'price', 'label' => 'Price', 'orderable' => false, 'searchable' => false],
['key' => 'specs', 'label' => 'Specifications', 'orderable' => false, 'searchable' => false],
['key' => 'status', 'label' => 'Status', 'searchable' => false],
['key' => 'action', 'label' => '', 'orderable' => false, 'searchable' => false, 'className' => 'text-center'],
['key' => 'category', 'label' => 'Category / Type'],
['key' => 'price', 'label' => 'Price'],
['key' => 'specs', 'label' => 'Specifications'],
['key' => 'status', 'label' => 'Status'],
['key' => 'action', 'label' => 'Action'],
];
@endphp
<x-datatable id="productTable" ajax="{{ route('products.datatable') }}" :columns="$productColumns" />
@@ -64,4 +63,4 @@ $tabs = [
@foreach (config('assets.datatable.js') as $datatable)
<script src="{{ asset($datatable) }}"></script>
@endforeach
@endpush
@endpush
@@ -346,73 +346,6 @@ $dtColumns = collect($columns)
height: 100%;
}
.data-table-wrapper .table thead th {
border-color: #f1f5f9 !important;
font-family: 'Inter', sans-serif;
vertical-align: middle;
position: sticky;
top: 0;
z-index: 2;
background-color: #f8f9fa;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.table thead th:last-child,
.table tbody td:last-child {
position: sticky;
right: 0;
background: #fff;
z-index: 1;
border-left: 1px solid #f1f5f9;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.02);
width: 70px;
min-width: 70px;
max-width: 70px;
}
.table thead th:last-child {
background: #f8f9fa;
z-index: 3;
}
.table thead th:first-child,
.table tbody td:first-child {
position: sticky;
left: 0;
background: #fff;
z-index: 1;
border-right: 1px solid #f1f5f9;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.02);
}
.table thead th:first-child {
background: #f8f9fa;
z-index: 3;
}
.table tbody tr {
position: relative;
z-index: 0;
}
.table tbody tr:hover {
z-index: 5;
}
.table tbody tr:hover td {
background-color: #f8fafc !important;
cursor: pointer;
}
.data-table-wrapper .table td:not(:last-child) {
border-right: 1px solid #f1f5f9;
}
.data-table-wrapper .table thead tr.filter-row th {
top: 45px;
z-index: 2;
}
.data-table-wrapper .input-group-text {
color: #94a3b8;
}
@@ -584,35 +517,5 @@ $dtColumns = collect($columns)
gap: 0.5rem;
margin: 0;
}
.dt-action-dropdown .dropdown-menu {
min-width: 140px;
padding: 0.25rem 0;
font-size: 0.8125rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid #e2e8f0;
z-index: 1050;
position: absolute;
}
.dt-action-dropdown .dropdown-item {
padding: 0.4rem 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.15s ease;
}
.dt-action-dropdown .dropdown-item:hover {
background-color: #f8fafc;
}
.dt-action-dropdown .dropdown-item.text-danger:hover {
background-color: #fef2f2;
}
.dt-action-dropdown .dropdown-divider {
margin: 0.25rem 0;
}
</style>
@endpush
+64
View File
@@ -57,6 +57,70 @@ Route::prefix('v1')
return response()->json(['data' => $data]);
})->name('api.GetAllImage');
Route::get('get-ticket-console', function (Request $request, ServiceVCenter $serviceVCenter) {
$product_id = $request->get('product');
if (!$product_id) {
return response()->json(['error' => true, 'message' => "Please choose product"]);
}
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter')->find($product_id);
if (!$product) {
return response()->json(['error' => true, 'message' => "Can't find product"]);
}
$vcenter = null;
if ($product->ipAddress && $product->ipAddress->host && $product->ipAddress->host->datacenter && $product->ipAddress->host->datacenter->vCenter) {
$vcenter = $product->ipAddress->host->datacenter->vCenter;
}
if (!$vcenter) {
return response()->json(['error' => true, 'message' => "Can't find vCenter information"]);
}
// Đăng nhập vCenter trước khi lấy ticket
if (!$serviceVCenter->loginServer($vcenter)) {
return response()->json([
'error' => true,
'message' => 'Không thể đăng nhập vào vCenter'
]);
}
$ConsoleTicket = $serviceVCenter->GetConsoleTickets($product->vmware_id);
// Nếu login hết hạn, thử login lại 1 lần nữa
if ($ConsoleTicket === 'Login_Again') {
if ($serviceVCenter->loginServer($vcenter)) {
$ConsoleTicket = $serviceVCenter->GetConsoleTickets($product->vmware_id);
} else {
return response()->json([
'error' => true,
'message' => 'Không thể đăng nhập lại vào vCenter'
]);
}
}
// Nếu vẫn lỗi, trả về lỗi rõ ràng
if (is_string($ConsoleTicket)) {
return response()->json([
'error' => true,
'message' => "Failed to get console ticket: $ConsoleTicket"
]);
}
if ($ConsoleTicket['error']) {
return response()->json([
'error' => true,
'message' => $ConsoleTicket['data']
]);
}
return response()->json([
'error' => false,
'data' => $ConsoleTicket['data'],
'host' => $vcenter->host,
'vmware-api-session-id' => $serviceVCenter->token
])->cookie('vmware-api-session-id', $serviceVCenter->token, 60, '/', null, false, false);
})->name('api.GetConsoleTicket');
});
});
Route::any('{any}', function () {