GeoNetAgent, LDAPWeb, server-audit, server-connection
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

@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