You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
12 KiB
211 lines
12 KiB
@extends('layouts.app') |
|
|
|
@section('title', 'Databases — ' . config('app.name')) |
|
|
|
@section('content') |
|
<div class="max-w-[1600px] mx-auto px-4 py-8"> |
|
@include('partials.nav') |
|
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-8 mt-6"> |
|
<div> |
|
<h1 class="text-3xl font-bold text-white">Database List</h1> |
|
<p class="text-slate-400 mt-1 text-sm"> |
|
@if ($activeConnection) |
|
<span class="text-cyan-400">{{ $activeConnection->driverLabel() }}</span> |
|
· {{ $activeConnection->name }} |
|
@endif |
|
· Host: <span class="text-cyan-400">{{ $meta['server'] }}</span> |
|
@if ($meta['sql_started'] ?? null) |
|
· Started: {{ $meta['sql_started'] }} ({{ $meta['uptime_days'] }} days uptime) |
|
@endif |
|
</p> |
|
</div> |
|
<div class="flex flex-wrap gap-2"> |
|
@if (count($connections) > 1) |
|
<form method="GET" class="flex gap-2"> |
|
<input type="hidden" name="sort" value="{{ $sort }}"> |
|
<input type="hidden" name="dir" value="{{ $dir }}"> |
|
<input type="hidden" name="q" value="{{ $search }}"> |
|
<select name="connection" onchange="this.form.submit()" |
|
class="rounded-lg bg-slate-800 border border-slate-700 px-3 py-2 text-sm text-white"> |
|
@foreach ($connections as $conn) |
|
<option value="{{ $conn->id }}" @selected($activeConnection && $activeConnection->id === $conn->id)> |
|
{{ $conn->name }} ({{ $conn->driverLabel() }}) |
|
</option> |
|
@endforeach |
|
</select> |
|
</form> |
|
@endif |
|
<a href="{{ route('database-connections.index') }}" |
|
class="rounded-lg border border-slate-700 px-3 py-2 text-sm text-slate-300 hover:bg-slate-800"> |
|
Manage Connections |
|
</a> |
|
</div> |
|
</div> |
|
|
|
@if (session('success')) |
|
<div class="mb-6 rounded-lg bg-emerald-950/50 border border-emerald-800 text-emerald-200 px-4 py-3"> |
|
{{ session('success') }} |
|
</div> |
|
@endif |
|
|
|
@if (session('error')) |
|
<div class="mb-6 rounded-lg bg-red-950/50 border border-red-800 text-red-200 px-4 py-3"> |
|
{{ session('error') }} |
|
</div> |
|
@endif |
|
|
|
@if ($error) |
|
<div class="mb-6 rounded-lg bg-red-950/50 border border-red-800 text-red-200 px-4 py-3"> |
|
{{ $error }} |
|
</div> |
|
@endif |
|
|
|
<div class="flex flex-col xl:flex-row gap-6 items-start xl:items-stretch"> |
|
<div class="flex-1 min-w-0 bg-slate-900 border border-slate-800 rounded-2xl overflow-hidden w-full flex flex-col list-panel xl:sticky xl:top-4"> |
|
<div class="list-panel__toolbar p-4 border-b border-slate-800 flex flex-col md:flex-row gap-3 md:items-center md:justify-between bg-slate-900"> |
|
<form method="GET" class="flex flex-wrap gap-2 w-full md:w-auto"> |
|
@if ($activeConnection) |
|
<input type="hidden" name="connection" value="{{ $activeConnection->id }}"> |
|
@endif |
|
<input type="hidden" name="sort" value="{{ $sort }}"> |
|
<input type="hidden" name="dir" value="{{ $dir }}"> |
|
<input name="q" value="{{ $search }}" placeholder="Search database..." |
|
class="flex-1 md:w-72 rounded-lg bg-slate-800 border border-slate-700 px-4 py-2 text-sm text-white"> |
|
<button class="rounded-lg bg-cyan-600 hover:bg-cyan-500 px-4 py-2 text-sm font-medium">Search</button> |
|
</form> |
|
<div class="text-sm text-slate-400">{{ $meta['total'] }} databases</div> |
|
</div> |
|
|
|
<div class="list-panel__scroll"> |
|
<table class="min-w-full text-sm border-collapse list-table"> |
|
<thead class="text-slate-300"> |
|
<tr> |
|
@php |
|
$columns = [ |
|
'db_name' => 'Database', |
|
'created' => 'Created', |
|
'active_days' => 'Active Days', |
|
'state' => 'State', |
|
'last_index_activity' => 'Last Activity', |
|
'last_backup' => 'Last Backup', |
|
]; |
|
@endphp |
|
@foreach ($columns as $key => $label) |
|
@php |
|
$nextDir = ($sort === $key && $dir === 'asc') ? 'desc' : 'asc'; |
|
$arrow = $sort === $key ? ($dir === 'asc' ? '↑' : '↓') : ''; |
|
@endphp |
|
<th class="sticky top-0 z-10 px-4 py-3 text-left font-medium whitespace-nowrap bg-slate-800 border-b border-slate-700 shadow-[0_1px_0_0_rgb(30_41_59)]"> |
|
<a href="?sort={{ $key }}&dir={{ $nextDir }}&q={{ urlencode($search) }}@if($activeConnection)&connection={{ $activeConnection->id }}@endif" |
|
class="hover:text-cyan-400">{{ $label }} {{ $arrow }}</a> |
|
</th> |
|
@endforeach |
|
<th class="sticky top-0 z-10 px-4 py-3 text-left font-medium whitespace-nowrap bg-slate-800 border-b border-slate-700 shadow-[0_1px_0_0_rgb(30_41_59)]">Action</th> |
|
</tr> |
|
</thead> |
|
<tbody class="divide-y divide-slate-800"> |
|
@forelse ($databases as $db) |
|
@php |
|
$isOnline = in_array($db['state'] ?? '', ['ONLINE'], true); |
|
$protected = match ($meta['driver'] ?? 'sqlsrv') { |
|
'pgsql' => in_array(mb_strtolower($db['db_name'] ?? ''), ['postgres', 'template0', 'template1'], true), |
|
'mysql' => in_array(mb_strtolower($db['db_name'] ?? ''), ['mysql', 'information_schema', 'performance_schema', 'sys'], true), |
|
default => in_array(mb_strtolower($db['db_name'] ?? ''), ['master', 'model', 'msdb', 'tempdb'], true), |
|
}; |
|
@endphp |
|
<tr class="hover:bg-slate-800/40"> |
|
<td class="px-4 py-3 font-medium text-white whitespace-nowrap">{{ $db['db_name'] }}</td> |
|
<td class="px-4 py-3 text-slate-300 whitespace-nowrap">{{ $db['created'] ?? '—' }}</td> |
|
<td class="px-4 py-3 text-slate-300 whitespace-nowrap">{{ $db['active_days'] ?? '—' }} days</td> |
|
<td class="px-4 py-3"> |
|
<span class="inline-flex rounded-full px-2 py-1 text-xs font-medium |
|
{{ $isOnline ? 'bg-emerald-950 text-emerald-300' : 'bg-amber-950 text-amber-300' }}"> |
|
{{ $db['state'] ?? '—' }} |
|
</span> |
|
</td> |
|
<td class="px-4 py-3 text-slate-300 whitespace-nowrap">{{ $db['last_index_activity'] ?? '—' }}</td> |
|
<td class="px-4 py-3 text-slate-300 whitespace-nowrap">{{ $db['last_backup'] ?? '—' }}</td> |
|
<td class="px-4 py-3 whitespace-nowrap"> |
|
@if ($supportsOffline && $isOnline && ! $protected) |
|
<form method="POST" |
|
action="{{ route('databases.offline', $db['db_name']) }}" |
|
class="inline" |
|
onsubmit="return confirm('Restrict/offline database [{{ $db['db_name'] }}]? Active connections may be terminated.');"> |
|
@csrf |
|
<input type="hidden" name="confirm" value="1"> |
|
@if ($activeConnection) |
|
<input type="hidden" name="connection" value="{{ $activeConnection->id }}"> |
|
@endif |
|
<input type="hidden" name="sort" value="{{ $sort }}"> |
|
<input type="hidden" name="dir" value="{{ $dir }}"> |
|
<input type="hidden" name="q" value="{{ $search }}"> |
|
<button type="submit" |
|
class="rounded-lg bg-amber-700 hover:bg-amber-600 text-white text-xs font-medium px-3 py-1.5"> |
|
Make Offline |
|
</button> |
|
</form> |
|
@else |
|
<span class="text-xs text-slate-500">—</span> |
|
@endif |
|
</td> |
|
</tr> |
|
@empty |
|
<tr> |
|
<td colspan="7" class="px-4 py-8 text-center text-slate-400">No databases found.</td> |
|
</tr> |
|
@endforelse |
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
|
|
<aside class="w-full xl:w-80 shrink-0 bg-slate-900 border border-slate-800 rounded-2xl p-5 xl:sticky xl:top-4 notes-panel"> |
|
<h2 class="text-lg font-semibold text-white mb-4">Catatan</h2> |
|
<dl class="space-y-4 text-sm"> |
|
<div> |
|
<dt class="font-medium text-cyan-400 mb-1">Active Days</dt> |
|
<dd class="text-slate-400 leading-relaxed"> |
|
Jumlah hari sejak database dibuat (<em>create date</em>) sampai hari ini. |
|
Semakin besar angkanya, semakin lama database tersebut ada di server. |
|
</dd> |
|
</div> |
|
<div> |
|
<dt class="font-medium text-emerald-400 mb-1">State — ONLINE</dt> |
|
<dd class="text-slate-400 leading-relaxed"> |
|
Database aktif dan dapat diakses. Aplikasi bisa connect, query, insert, update, dan backup dapat dijalankan. |
|
</dd> |
|
</div> |
|
<div> |
|
<dt class="font-medium text-amber-400 mb-1">State — OFFLINE</dt> |
|
<dd class="text-slate-400 leading-relaxed"> |
|
Database tidak dapat digunakan atau diakses. Semua koneksi ditolak sampai di-set ONLINE lagi oleh admin. |
|
</dd> |
|
</div> |
|
<div> |
|
<dt class="font-medium text-amber-300 mb-1">Make Offline</dt> |
|
<dd class="text-slate-400 leading-relaxed"> |
|
Menonaktifkan database dengan perintah |
|
<code class="text-slate-300">ALTER DATABASE ... SET OFFLINE</code>. |
|
Koneksi aktif akan diputus. Database sistem (<code class="text-slate-300">master</code>, <code class="text-slate-300">model</code>, <code class="text-slate-300">msdb</code>, <code class="text-slate-300">tempdb</code>) dilindungi dan tidak bisa di-offline dari sini. |
|
</dd> |
|
</div> |
|
<div> |
|
<dt class="font-medium text-slate-200 mb-1">Last Activity</dt> |
|
<dd class="text-slate-400 leading-relaxed"> |
|
Waktu terakhir ada aktivitas baca/tulis (index usage) pada database. |
|
Data dihitung sejak SQL Server terakhir restart. Kosong (<code class="text-slate-300">—</code>) berarti tidak ada aktivitas sejak restart. |
|
</dd> |
|
</div> |
|
<div> |
|
<dt class="font-medium text-slate-200 mb-1">Last Backup</dt> |
|
<dd class="text-slate-400 leading-relaxed"> |
|
Tanggal backup full terakhir yang tercatat di <code class="text-slate-300">msdb</code>. |
|
Kosong (<code class="text-slate-300">—</code>) berarti belum ada record backup full untuk database tersebut. |
|
</dd> |
|
</div> |
|
</dl> |
|
</aside> |
|
</div> |
|
</div> |
|
@endsection
|
|
|