update
This commit is contained in:
@@ -2,28 +2,115 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\Dashboard;
|
||||
|
||||
use App\Enums\Invoice\InvoiceStatus;
|
||||
use App\Enums\Order\OrderStatus;
|
||||
use App\Enums\TransactionStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Order;
|
||||
use App\Models\Service;
|
||||
use App\Models\Transaction;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
// Các type được tính là "nạp tiền vào ví"
|
||||
private const DEPOSIT_TYPES = ['manual', 'vnpay', 'deposit'];
|
||||
|
||||
/** Map Order status int → string FE */
|
||||
private function orderStatusLabel(int $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
OrderStatus::PENDING->value => 'pending',
|
||||
OrderStatus::ACTIVE->value => 'active',
|
||||
OrderStatus::CANCELLED->value => 'cancelled',
|
||||
OrderStatus::PROCESSING->value => 'processing',
|
||||
default => (string) $status,
|
||||
};
|
||||
}
|
||||
|
||||
/** Map Invoice status int → FE payment status (paid/pending/failed) */
|
||||
private function paymentStatusFromInvoice(?int $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
InvoiceStatus::PAID->value => 'paid',
|
||||
InvoiceStatus::UNPAID->value => 'pending',
|
||||
InvoiceStatus::CANCELLED->value => 'failed',
|
||||
default => 'pending',
|
||||
};
|
||||
}
|
||||
|
||||
/** % delta giữa current vs previous (an toàn chia 0) */
|
||||
private function deltaPct(float $current, float $previous): float
|
||||
{
|
||||
if ($previous == 0.0) {
|
||||
return $current > 0 ? 100.0 : 0.0;
|
||||
}
|
||||
return round((($current - $previous) / $previous) * 100, 2);
|
||||
}
|
||||
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
$total_customers = Customer::count();
|
||||
$now = now();
|
||||
$startOfMonth = $now->copy()->startOfMonth();
|
||||
$startOfPrevMonth = $now->copy()->subMonthNoOverflow()->startOfMonth();
|
||||
$endOfPrevMonth = $now->copy()->subMonthNoOverflow()->endOfMonth();
|
||||
|
||||
$total_deposits = (float) Transaction::where('type', 'manual')
|
||||
->where('status', TransactionStatus::COMPLETED->value)
|
||||
// Customers
|
||||
$totalCustomers = Customer::count();
|
||||
$newThisMonth = Customer::where('created_at', '>=', $startOfMonth)->count();
|
||||
$newPrevMonth = Customer::whereBetween('created_at', [$startOfPrevMonth, $endOfPrevMonth])->count();
|
||||
|
||||
// Deposits (transactions completed, type ∈ DEPOSIT_TYPES)
|
||||
$depositBase = Transaction::whereIn('type', self::DEPOSIT_TYPES)
|
||||
->where('status', TransactionStatus::COMPLETED->value);
|
||||
|
||||
$totalDeposits = (float) (clone $depositBase)->sum('amount');
|
||||
$depositsThisMonth = (float) (clone $depositBase)
|
||||
->where('created_at', '>=', $startOfMonth)
|
||||
->sum('amount');
|
||||
$depositsPrevMonth = (float) (clone $depositBase)
|
||||
->whereBetween('created_at', [$startOfPrevMonth, $endOfPrevMonth])
|
||||
->sum('amount');
|
||||
|
||||
// Số dư ví khách hàng (cột customers.total)
|
||||
$totalBalance = (float) Customer::sum('total');
|
||||
|
||||
// Services
|
||||
$totalActiveServices = Service::where('status', 'active')->count();
|
||||
$expiring7d = Service::where('status', 'active')
|
||||
->whereNotNull('expires_at')
|
||||
->whereBetween('expires_at', [$now, $now->copy()->addDays(7)])
|
||||
->count();
|
||||
|
||||
// Orders
|
||||
$totalOrdersThisMonth = Order::where('created_at', '>=', $startOfMonth)->count();
|
||||
$totalOrdersPrevMonth = Order::whereBetween('created_at', [$startOfPrevMonth, $endOfPrevMonth])->count();
|
||||
$pendingOrders = Order::where('status', OrderStatus::PENDING->value)->count();
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'total_customers' => $total_customers,
|
||||
'total_deposits' => $total_deposits,
|
||||
'data' => [
|
||||
'total_customers' => $totalCustomers,
|
||||
'new_customers_this_month' => $newThisMonth,
|
||||
'new_customers_delta_pct' => $this->deltaPct($newThisMonth, $newPrevMonth),
|
||||
|
||||
'total_deposits' => $totalDeposits,
|
||||
'deposits_this_month' => $depositsThisMonth,
|
||||
'deposits_delta_pct' => $this->deltaPct($depositsThisMonth, $depositsPrevMonth),
|
||||
|
||||
'total_balance' => $totalBalance,
|
||||
|
||||
'total_active_services' => $totalActiveServices,
|
||||
'expiring_services_7d' => $expiring7d,
|
||||
|
||||
'total_orders_this_month' => $totalOrdersThisMonth,
|
||||
'pending_orders' => $pendingOrders,
|
||||
'orders_delta_pct' => $this->deltaPct($totalOrdersThisMonth, $totalOrdersPrevMonth),
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -32,34 +119,322 @@ class DashboardController extends Controller
|
||||
{
|
||||
$year = $request->integer('year', now()->year);
|
||||
|
||||
$deposits = Transaction::where('type', 'manual')
|
||||
$deposits = Transaction::whereIn('type', self::DEPOSIT_TYPES)
|
||||
->where('status', TransactionStatus::COMPLETED->value)
|
||||
->whereYear('created_at', $year)
|
||||
->selectRaw('MONTH(created_at) as month, SUM(amount) as total_deposits')
|
||||
->groupBy('month')
|
||||
->pluck('total_deposits', 'month');
|
||||
->selectRaw('MONTH(created_at) as m, SUM(amount) as total')
|
||||
->groupBy('m')
|
||||
->pluck('total', 'm');
|
||||
|
||||
$newCustomers = Customer::whereYear('created_at', $year)
|
||||
->selectRaw('MONTH(created_at) as month, COUNT(*) as new_customers')
|
||||
->groupBy('month')
|
||||
->pluck('new_customers', 'month');
|
||||
->selectRaw('MONTH(created_at) as m, COUNT(*) as c')
|
||||
->groupBy('m')
|
||||
->pluck('c', 'm');
|
||||
|
||||
$months = ['T1', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7', 'T8', 'T9', 'T10', 'T11', 'T12'];
|
||||
$orders = Order::whereYear('created_at', $year)
|
||||
->selectRaw('MONTH(created_at) as m, COUNT(*) as c')
|
||||
->groupBy('m')
|
||||
->pluck('c', 'm');
|
||||
|
||||
// Doanh thu dịch vụ = tổng invoice đã PAID trong tháng
|
||||
$serviceRevenue = Invoice::where('status', InvoiceStatus::PAID->value)
|
||||
->whereYear('paid_at', $year)
|
||||
->selectRaw('MONTH(paid_at) as m, SUM(total) as total')
|
||||
->groupBy('m')
|
||||
->pluck('total', 'm');
|
||||
|
||||
$data = collect(range(1, 12))->map(fn ($m) => [
|
||||
'month' => 'T' . $m,
|
||||
'deposits' => (float) ($deposits[$m] ?? 0),
|
||||
'new_customers' => (int) ($newCustomers[$m] ?? 0),
|
||||
'orders' => (int) ($orders[$m] ?? 0),
|
||||
'service_revenue' => (float) ($serviceRevenue[$m] ?? 0),
|
||||
])->values();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $data]);
|
||||
}
|
||||
|
||||
public function dailyStats(Request $request): JsonResponse
|
||||
{
|
||||
$month = $request->integer('month', now()->month);
|
||||
$year = $request->integer('year', now()->year);
|
||||
|
||||
$start = Carbon::create($year, $month, 1)->startOfMonth();
|
||||
$end = $start->copy()->endOfMonth();
|
||||
$daysInMonth = $start->daysInMonth;
|
||||
|
||||
$orders = Order::whereBetween('created_at', [$start, $end])
|
||||
->selectRaw('DAY(created_at) as d, COUNT(*) as c')
|
||||
->groupBy('d')
|
||||
->pluck('c', 'd');
|
||||
|
||||
$depositsAgg = Transaction::whereIn('type', self::DEPOSIT_TYPES)
|
||||
->where('status', TransactionStatus::COMPLETED->value)
|
||||
->whereBetween('created_at', [$start, $end])
|
||||
->selectRaw('DAY(created_at) as d, COUNT(*) as c, SUM(amount) as total')
|
||||
->groupBy('d')
|
||||
->get()
|
||||
->keyBy('d');
|
||||
|
||||
$data = collect(range(1, $daysInMonth))->map(fn ($d) => [
|
||||
'day' => str_pad((string) $d, 2, '0', STR_PAD_LEFT),
|
||||
'orders' => (int) ($orders[$d] ?? 0),
|
||||
'deposits_count' => (int) ($depositsAgg[$d]->c ?? 0),
|
||||
'deposits_amount' => (float) ($depositsAgg[$d]->total ?? 0),
|
||||
])->values();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $data]);
|
||||
}
|
||||
|
||||
public function recentOrders(Request $request): JsonResponse
|
||||
{
|
||||
$limit = (int) $request->integer('limit', 8);
|
||||
$limit = max(1, min(50, $limit));
|
||||
|
||||
$orders = Order::with([
|
||||
'customer:id,fullname,email',
|
||||
'orderItems:id,order_id,product_id',
|
||||
'orderItems.product:id,name',
|
||||
'invoice:id,order_id,status',
|
||||
])
|
||||
->orderByDesc('created_at')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
$items = $orders->map(function (Order $o) {
|
||||
$firstItem = $o->orderItems->first();
|
||||
$serviceName = $firstItem?->product?->name ?? '—';
|
||||
$invoiceStatus = $o->invoice?->status ?? null;
|
||||
|
||||
$data = collect(range(1, 12))->map(function ($m) use ($months, $deposits, $newCustomers) {
|
||||
return [
|
||||
'month' => $months[$m - 1],
|
||||
'deposits' => (float) ($deposits[$m] ?? 0),
|
||||
'new_customers' => (int) ($newCustomers[$m] ?? 0),
|
||||
'id' => $o->id,
|
||||
'code' => $o->order_num,
|
||||
'customer_id' => $o->customer_id,
|
||||
'customer_name' => $o->customer->fullname ?? '—',
|
||||
'customer_email' => $o->customer->email ?? '',
|
||||
'service_name' => $serviceName,
|
||||
'amount' => (float) $o->total,
|
||||
'payment_status' => $this->paymentStatusFromInvoice($invoiceStatus),
|
||||
'status' => $this->orderStatusLabel((int) $o->status),
|
||||
'created_at' => $o->created_at?->toISOString(),
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $data,
|
||||
'data' => ['items' => $items, 'total' => Order::count()],
|
||||
]);
|
||||
}
|
||||
|
||||
public function recentDeposits(Request $request): JsonResponse
|
||||
{
|
||||
$limit = (int) $request->integer('limit', 6);
|
||||
$limit = max(1, min(50, $limit));
|
||||
|
||||
$txs = Transaction::with('customer:id,fullname')
|
||||
->whereIn('type', self::DEPOSIT_TYPES)
|
||||
->orderByDesc('created_at')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
$items = $txs->map(function (Transaction $t) {
|
||||
$status = match ((int) $t->status) {
|
||||
TransactionStatus::COMPLETED->value => 'success',
|
||||
TransactionStatus::PENDING->value => 'pending',
|
||||
TransactionStatus::FAILED->value => 'failed',
|
||||
default => 'pending',
|
||||
};
|
||||
|
||||
return [
|
||||
'id' => $t->id,
|
||||
'code' => $t->public_code ?? $t->trans ?? ('TX-' . $t->id),
|
||||
'customer_id' => $t->customer_id,
|
||||
'customer_name' => $t->customer->fullname ?? '—',
|
||||
'amount' => (float) $t->amount,
|
||||
'method' => (string) ($t->type ?? 'manual'),
|
||||
'status' => $status,
|
||||
'created_at' => $t->created_at?->toISOString(),
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'items' => $items,
|
||||
'total' => Transaction::whereIn('type', self::DEPOSIT_TYPES)->count(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function topServices(Request $request): JsonResponse
|
||||
{
|
||||
$limit = (int) $request->integer('limit', 5);
|
||||
$limit = max(1, min(20, $limit));
|
||||
|
||||
$startOfMonth = now()->startOfMonth();
|
||||
$startOfPrevMonth = now()->copy()->subMonthNoOverflow()->startOfMonth();
|
||||
$endOfPrevMonth = now()->copy()->subMonthNoOverflow()->endOfMonth();
|
||||
|
||||
// Top theo số service đang active của từng product
|
||||
$rows = Service::query()
|
||||
->select(
|
||||
'services.product_id',
|
||||
'products.name as product_name',
|
||||
'products.slug as product_slug',
|
||||
DB::raw('COUNT(*) as active_count')
|
||||
)
|
||||
->join('products', 'products.id', '=', 'services.product_id')
|
||||
->where('services.status', 'active')
|
||||
->whereNotNull('services.product_id')
|
||||
->groupBy('services.product_id', 'products.name', 'products.slug')
|
||||
->orderByDesc('active_count')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
$productIds = $rows->pluck('product_id')->all();
|
||||
|
||||
// Đã bán & doanh thu kỳ hiện tại / kỳ trước (theo paid invoice item)
|
||||
$soldCurrent = DB::table('order_items')
|
||||
->join('orders', 'orders.id', '=', 'order_items.order_id')
|
||||
->whereIn('order_items.product_id', $productIds)
|
||||
->where('orders.created_at', '>=', $startOfMonth)
|
||||
->selectRaw('order_items.product_id, COUNT(*) as sold, SUM(order_items.price * order_items.quantity) as revenue')
|
||||
->groupBy('order_items.product_id')
|
||||
->get()
|
||||
->keyBy('product_id');
|
||||
|
||||
$soldPrev = DB::table('order_items')
|
||||
->join('orders', 'orders.id', '=', 'order_items.order_id')
|
||||
->whereIn('order_items.product_id', $productIds)
|
||||
->whereBetween('orders.created_at', [$startOfPrevMonth, $endOfPrevMonth])
|
||||
->selectRaw('order_items.product_id, SUM(order_items.price * order_items.quantity) as revenue')
|
||||
->groupBy('order_items.product_id')
|
||||
->get()
|
||||
->keyBy('product_id');
|
||||
|
||||
$data = $rows->map(function ($r) use ($soldCurrent, $soldPrev) {
|
||||
$cur = (float) ($soldCurrent[$r->product_id]->revenue ?? 0);
|
||||
$prev = (float) ($soldPrev[$r->product_id]->revenue ?? 0);
|
||||
|
||||
return [
|
||||
'id' => (int) $r->product_id,
|
||||
'name' => $r->product_name,
|
||||
'code' => $r->product_slug,
|
||||
'active_count' => (int) $r->active_count,
|
||||
'sold_count' => (int) ($soldCurrent[$r->product_id]->sold ?? 0),
|
||||
'revenue' => $cur,
|
||||
'trend_pct' => $this->deltaPct($cur, $prev),
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $data]);
|
||||
}
|
||||
|
||||
public function expiringServices(Request $request): JsonResponse
|
||||
{
|
||||
$withinDays = (int) $request->integer('within_days', 7);
|
||||
$withinDays = max(1, min(60, $withinDays));
|
||||
|
||||
$now = now();
|
||||
$until = $now->copy()->addDays($withinDays);
|
||||
|
||||
$services = Service::with(['customer:id,fullname', 'product:id,name'])
|
||||
->where('status', 'active')
|
||||
->whereNotNull('expires_at')
|
||||
->whereBetween('expires_at', [$now, $until])
|
||||
->orderBy('expires_at')
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
$data = $services->map(function (Service $s) use ($now) {
|
||||
$expires = $s->expires_at instanceof Carbon
|
||||
? $s->expires_at
|
||||
: Carbon::parse((string) $s->expires_at);
|
||||
|
||||
return [
|
||||
'id' => $s->id,
|
||||
'service_name' => $s->product->name ?? ($s->server_name ?? 'Dịch vụ'),
|
||||
'customer_id' => $s->customer_id,
|
||||
'customer_name' => $s->customer->fullname ?? '—',
|
||||
'expired_at' => $expires->toISOString(),
|
||||
'days_left' => max(0, (int) $now->diffInDays($expires, false)),
|
||||
'amount' => 0.0,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $data]);
|
||||
}
|
||||
|
||||
public function activities(Request $request): JsonResponse
|
||||
{
|
||||
$limit = (int) $request->integer('limit', 8);
|
||||
$limit = max(1, min(30, $limit));
|
||||
|
||||
// Tổng hợp 3 nguồn rồi sort theo thời gian
|
||||
$orders = Order::with('customer:id,fullname')
|
||||
->orderByDesc('created_at')
|
||||
->limit($limit)
|
||||
->get()
|
||||
->map(function (Order $o) {
|
||||
$isCancelled = (int) $o->status === OrderStatus::CANCELLED->value;
|
||||
$isActive = (int) $o->status === OrderStatus::ACTIVE->value;
|
||||
$type = $isCancelled ? 'order_cancelled' : ($isActive ? 'order_completed' : 'order_created');
|
||||
|
||||
return [
|
||||
'id' => 'order-' . $o->id,
|
||||
'type' => $type,
|
||||
'title' => 'Đơn ' . $o->order_num . ' ' . match ($type) {
|
||||
'order_cancelled' => 'đã huỷ',
|
||||
'order_completed' => 'đã kích hoạt',
|
||||
default => 'được tạo',
|
||||
},
|
||||
'description' => ($o->customer->fullname ?? '—') . ' · ' . number_format((float) $o->total, 0, ',', '.') . 'đ',
|
||||
'created_at' => $o->created_at?->toISOString(),
|
||||
'_ts' => $o->created_at?->timestamp ?? 0,
|
||||
];
|
||||
});
|
||||
|
||||
$deposits = Transaction::with('customer:id,fullname')
|
||||
->whereIn('type', self::DEPOSIT_TYPES)
|
||||
->orderByDesc('created_at')
|
||||
->limit($limit)
|
||||
->get()
|
||||
->map(function (Transaction $t) {
|
||||
$ok = (int) $t->status === TransactionStatus::COMPLETED->value;
|
||||
return [
|
||||
'id' => 'tx-' . $t->id,
|
||||
'type' => $ok ? 'deposit_success' : 'deposit_failed',
|
||||
'title' => $ok ? 'Nạp tiền thành công' : 'Giao dịch nạp thất bại',
|
||||
'description' => ($t->customer->fullname ?? '—') . ' · ' . number_format((float) $t->amount, 0, ',', '.') . 'đ',
|
||||
'created_at' => $t->created_at?->toISOString(),
|
||||
'_ts' => $t->created_at?->timestamp ?? 0,
|
||||
];
|
||||
});
|
||||
|
||||
$customers = Customer::orderByDesc('created_at')
|
||||
->limit($limit)
|
||||
->get(['id', 'fullname', 'email', 'created_at'])
|
||||
->map(fn (Customer $c) => [
|
||||
'id' => 'customer-' . $c->id,
|
||||
'type' => 'customer_registered',
|
||||
'title' => 'Khách hàng mới đăng ký',
|
||||
'description' => ($c->fullname ?: $c->email) ?? '—',
|
||||
'created_at' => $c->created_at?->toISOString(),
|
||||
'_ts' => $c->created_at?->timestamp ?? 0,
|
||||
]);
|
||||
|
||||
$data = $orders->concat($deposits)->concat($customers)
|
||||
->sortByDesc('_ts')
|
||||
->take($limit)
|
||||
->map(function ($a) {
|
||||
unset($a['_ts']);
|
||||
return $a;
|
||||
})
|
||||
->values();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $data]);
|
||||
}
|
||||
|
||||
public function latestCustomers(): JsonResponse
|
||||
{
|
||||
$customers = Customer::orderByDesc('created_at')
|
||||
@@ -68,13 +443,13 @@ class DashboardController extends Controller
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'items' => $customers->map(fn(Customer $c) => [
|
||||
'id' => $c->id,
|
||||
'uuid' => $c->uuid,
|
||||
'fullname' => $c->fullname,
|
||||
'email' => $c->email,
|
||||
'total' => (float) $c->total,
|
||||
'data' => [
|
||||
'items' => $customers->map(fn (Customer $c) => [
|
||||
'id' => $c->id,
|
||||
'uuid' => $c->uuid,
|
||||
'fullname' => $c->fullname,
|
||||
'email' => $c->email,
|
||||
'total' => (float) $c->total,
|
||||
'created_at' => $c->created_at?->toISOString(),
|
||||
])->values(),
|
||||
'total' => Customer::count(),
|
||||
|
||||
@@ -20,9 +20,10 @@ class DomainController extends Controller
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 500);
|
||||
$filter = $request->get('filter');
|
||||
$status = $request->get('status');
|
||||
$perPage = min((int) $request->get('per_page', 15), 500);
|
||||
$filter = $request->get('filter');
|
||||
$status = $request->get('status');
|
||||
$customerName = trim((string) $request->get('customer_name', ''));
|
||||
|
||||
$query = $this->domainRepository->model
|
||||
->with(['server', 'customer'])
|
||||
@@ -36,6 +37,12 @@ class DomainController extends Controller
|
||||
$query->where('is_created', (int) $status);
|
||||
}
|
||||
|
||||
if ($customerName !== '') {
|
||||
$query->whereHas('customer', function ($q) use ($customerName) {
|
||||
$q->where('fullname', 'like', "%{$customerName}%");
|
||||
});
|
||||
}
|
||||
|
||||
$paginator = $query->orderByDesc('id')->paginate($perPage);
|
||||
|
||||
$items = $paginator->getCollection()->map(fn($domain) => $this->formatDomain($domain));
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\Provider;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\VirtualMachine\VmActiveProviderService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Admin endpoints to toggle which row in the
|
||||
* vcenter / datacenter / host / datastore / network chain is active.
|
||||
*
|
||||
* Only ONE row per parent scope can be active at a time — enforced by
|
||||
* VmActiveProviderService::activate() inside a transaction. Deactivating
|
||||
* a row cascade-deactivates its descendants.
|
||||
*/
|
||||
class ActiveInfraController extends Controller
|
||||
{
|
||||
public function __construct(private readonly VmActiveProviderService $service)
|
||||
{
|
||||
}
|
||||
|
||||
/** GET /administrator/provider/active-chain — returns the current active chain (or error). */
|
||||
public function chain(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$chain = $this->service->resolveChain();
|
||||
} catch (Throwable $e) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $e->getMessage(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'vcenter' => $chain['vcenter']->only(['id', 'host', 'user', 'status']),
|
||||
'datacenter' => $chain['datacenter']->only(['id', 'name', 'datacenter_id', 'status']),
|
||||
'host' => $chain['host']->only(['id', 'name', 'host_id', 'status']),
|
||||
'datastore' => $chain['datastore']->only(['id', 'name', 'datastore', 'status']),
|
||||
'network' => $chain['network']->only(['id', 'name', 'network', 'status']),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** POST /administrator/provider/{type}/{id}/activate */
|
||||
public function activate(string $type, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$row = $this->service->activate($type, $id);
|
||||
} catch (Throwable $e) {
|
||||
return response()->json(['error' => true, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $row,
|
||||
]);
|
||||
}
|
||||
|
||||
/** POST /administrator/provider/{type}/{id}/deactivate */
|
||||
public function deactivate(string $type, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$row = $this->service->deactivate($type, $id);
|
||||
} catch (Throwable $e) {
|
||||
return response()->json(['error' => true, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $row,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\Provider;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\vCenter;
|
||||
use App\Services\VirtualMachine\VmProviderSettingsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* Reads / writes the provider config blob in setting key=`provider`.
|
||||
*
|
||||
* active_vcenter_id is read-only here — it mirrors module_vcenter.status=1.
|
||||
* To switch the active vCenter, call ActiveInfraController::activate().
|
||||
*/
|
||||
class ProviderSettingController extends Controller
|
||||
{
|
||||
public function __construct(private readonly VmProviderSettingsService $service)
|
||||
{
|
||||
}
|
||||
|
||||
/** GET /administrator/provider/settings */
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$data = $this->service->all();
|
||||
|
||||
// Mirror current truth: active vCenter id from module_vcenter
|
||||
$data['active_vcenter_id'] = vCenter::where('status', 1)->value('id');
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/** PUT /administrator/provider/settings */
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'default_folder' => 'sometimes|string|max:100',
|
||||
'default_resource_pool' => 'sometimes|string|max:100',
|
||||
'default_datastore' => 'sometimes|string|max:100',
|
||||
'name' => 'sometimes|nullable|string|max:120',
|
||||
'region' => 'sometimes|nullable|string|max:60',
|
||||
'tags' => 'sometimes|array',
|
||||
'tags.*' => 'string|max:40',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $validator->errors()->first(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$patch = $validator->validated();
|
||||
|
||||
// active_vcenter_id is read-only — strip if client sent it
|
||||
unset($patch['active_vcenter_id']);
|
||||
|
||||
$merged = $this->service->merge($patch);
|
||||
$merged['active_vcenter_id'] = vCenter::where('status', 1)->value('id');
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $merged,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user