diff options
| author | Thomas Grothe <grothe.tr@gmail.com> | 2026-04-11 17:49:42 -0400 |
|---|---|---|
| committer | Thomas Grothe <grothe.tr@gmail.com> | 2026-04-11 17:49:42 -0400 |
| commit | d4f97aa956be051dd5b9a184557106dc7de112ac (patch) | |
| tree | e4c73c93408b5fbc4cc1b92cabcaceaaebbb1833 /resources/views/admin.blade.php | |
| parent | bcac54576d7309ac0471a7be5664c5a4e8d7349e (diff) | |
| parent | 054c19bf65beb43d0dd6137f9bf16cf8ca9f6190 (diff) | |
Merge remote-tracking branch 'origin/main'
Diffstat (limited to 'resources/views/admin.blade.php')
| -rw-r--r-- | resources/views/admin.blade.php | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/resources/views/admin.blade.php b/resources/views/admin.blade.php new file mode 100644 index 0000000..bf8db6f --- /dev/null +++ b/resources/views/admin.blade.php @@ -0,0 +1,268 @@ +@extends('template') + +@section('head') +@endsection + +@section('content') +<h1>Admin</h1> + +@if(session('success')) + <div class="flash-success">{{ session('success') }}</div> +@endif + +<div class="admin-layout"> + <nav class="admin-sidebar"> + <a href="#visitors" class="admin-nav active" data-section="visitors">Visitors</a> + <a href="#files" class="admin-nav" data-section="files">Files</a> + <a href="#links" class="admin-nav" data-section="links">Links</a> + </nav> + + <div class="admin-content"> + {{-- ==================== VISITORS ==================== --}} + <div id="section-visitors" class="admin-section active"> + <h2>Visitor Logs</h2> + + <div class="summary-row"> + <div class="summary-stat"> + <div class="num">{{ $visitorSummary['unique_ips'] }}</div> + <div class="lbl">Unique IPs</div> + </div> + <div class="summary-stat"> + <div class="num">{{ number_format($visitorSummary['total_requests']) }}</div> + <div class="lbl">Total Requests</div> + </div> + <div class="summary-stat"> + <div class="num">{{ number_format($visitorSummary['total_200']) }}</div> + <div class="lbl">200</div> + </div> + <div class="summary-stat"> + <div class="num">{{ number_format($visitorSummary['total_404']) }}</div> + <div class="lbl">404</div> + </div> + <div class="summary-stat"> + <div class="num">{{ number_format($visitorSummary['total_405']) }}</div> + <div class="lbl">405</div> + </div> + <div class="summary-stat"> + <div class="num">{{ number_format($visitorSummary['total_500']) }}</div> + <div class="lbl">500</div> + </div> + </div> + + <table class="admin-table"> + <thead> + <tr> + <th>IP Address</th> + <th>Requests</th> + <th>200</th> + <th>404</th> + <th>405</th> + <th>500</th> + <th>First Seen</th> + <th>Last Seen</th> + </tr> + </thead> + <tbody> + @forelse($visitors as $v) + <tr> + <td>{{ $v->ip_address }}</td> + <td>{{ number_format($v->total_requests) }}</td> + <td>{{ $v->status_200_count }}</td> + <td>{{ $v->status_404_count }}</td> + <td>{{ $v->status_405_count }}</td> + <td>{{ $v->status_500_count }}</td> + <td>{{ $v->first_seen_at?->format('Y-m-d H:i') }}</td> + <td>{{ $v->last_seen_at?->diffForHumans() }}</td> + </tr> + @empty + <tr><td colspan="8" style="text-align:center;color:#999;">No visitor data yet</td></tr> + @endforelse + </tbody> + </table> + </div> + + {{-- ==================== FILES ==================== --}} + <div id="section-files" class="admin-section"> + <h2>Files</h2> + + <div class="admin-form"> + <h3>Upload Files</h3> + <form action="/f" method="POST" enctype="multipart/form-data"> + @csrf + <input type="hidden" name="response_format" value="html"> + <input type="hidden" name="_redirect" value="/admin#files"> + <div class="form-row"> + <div class="form-group"> + <label for="f">Select files</label> + <input type="file" name="f[]" id="f" multiple> + </div> + <div class="form-group"> + <label for="upload-tags">Tags (comma-separated)</label> + <input type="text" name="tags" id="upload-tags" placeholder="e.g. photo, doc"> + </div> + <button type="submit" class="btn btn-primary">Upload</button> + </div> + </form> + </div> + + <table class="admin-table"> + <thead> + <tr> + <th>Filename</th> + <th>Path</th> + <th>Size</th> + <th>Uploaded</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + @forelse($files as $file) + <tr> + <td>{{ $file->filename }}</td> + <td>{{ $file->path }}</td> + <td>{{ $file->size ? round($file->size / 1024, 1) . ' KB' : '—' }}</td> + <td>{{ $file->created_at?->format('Y-m-d') }}</td> + <td> + <form action="/f/{{ $file->id }}" method="POST" style="display:inline" + onsubmit="return confirm('Delete this file?')"> + @csrf + @method('DELETE') + <input type="hidden" name="_redirect" value="/admin#files"> + <button type="submit" class="btn btn-danger btn-sm">Delete</button> + </form> + </td> + </tr> + @empty + <tr><td colspan="5" style="text-align:center;color:#999;">No files</td></tr> + @endforelse + </tbody> + </table> + </div> + + {{-- ==================== LINKS ==================== --}} + <div id="section-links" class="admin-section"> + <h2>Links</h2> + + <div class="admin-form"> + <h3>Add Link</h3> + <form action="/l" method="POST"> + @csrf + <input type="hidden" name="_redirect" value="/admin#links"> + <div class="form-row"> + <div class="form-group"> + <label for="link-label">Label</label> + <input type="text" name="label" id="link-label" required> + </div> + <div class="form-group"> + <label for="link-url">URL</label> + <input type="text" name="url" id="link-url" required> + </div> + <div class="form-group"> + <label for="link-desc">Description</label> + <input type="text" name="description" id="link-desc"> + </div> + <button type="submit" class="btn btn-primary">Add</button> + </div> + </form> + </div> + + <table class="admin-table"> + <thead> + <tr> + <th>Label</th> + <th>URL</th> + <th>Description</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + @forelse($links as $link) + <tr id="link-row-{{ $link->id }}"> + <td class="link-display">{{ $link->label }}</td> + <td class="link-display"><a href="{{ $link->url }}" target="_blank">{{ Str::limit($link->url, 50) }}</a></td> + <td class="link-display">{{ $link->description }}</td> + <td> + <button class="btn btn-primary btn-sm" onclick="editLink({{ $link->id }}, this)">Edit</button> + <form action="/l/{{ $link->id }}" method="POST" style="display:inline" + onsubmit="return confirm('Delete this link?')"> + @csrf + @method('DELETE') + <input type="hidden" name="_redirect" value="/admin#links"> + <button type="submit" class="btn btn-danger btn-sm">Delete</button> + </form> + </td> + </tr> + @empty + <tr><td colspan="4" style="text-align:center;color:#999;">No links</td></tr> + @endforelse + </tbody> + </table> + </div> + </div> +</div> +@endsection + +@section('scripts') +<script> +// Tab navigation +document.querySelectorAll('.admin-nav').forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const section = this.dataset.section; + + document.querySelectorAll('.admin-nav').forEach(n => n.classList.remove('active')); + document.querySelectorAll('.admin-section').forEach(s => s.classList.remove('active')); + + this.classList.add('active'); + document.getElementById('section-' + section).classList.add('active'); + + history.replaceState(null, '', '#' + section); + }); +}); + +// Restore tab from hash +window.addEventListener('DOMContentLoaded', function() { + const hash = window.location.hash.replace('#', ''); + if (hash) { + const navLink = document.querySelector(`.admin-nav[data-section="${hash}"]`); + if (navLink) navLink.click(); + } +}); + +// Inline link editing +function editLink(id, btn) { + const row = document.getElementById('link-row-' + id); + const cells = row.querySelectorAll('.link-display'); + const label = cells[0].textContent.trim(); + const url = cells[1].textContent.trim(); + const desc = cells[2].textContent.trim(); + + cells[0].innerHTML = `<input name="label" value="${label}">`; + cells[1].innerHTML = `<input name="url" value="${url}">`; + cells[2].innerHTML = `<input name="description" value="${desc}">`; + + btn.textContent = 'Save'; + btn.onclick = function() { saveLink(id, row, btn); }; +} + +function saveLink(id, row, btn) { + const data = { + label: row.querySelector('input[name="label"]').value, + url: row.querySelector('input[name="url"]').value, + description: row.querySelector('input[name="description"]').value, + _token: '{{ csrf_token() }}', + _method: 'PUT', + _redirect: '/admin#links' + }; + + axios.post('/l/' + id, data) + .then(function() { + window.location.href = '/admin#links'; + window.location.reload(); + }) + .catch(function(err) { + alert('Error saving link: ' + (err.response?.data?.message || err.message)); + }); +} +</script> +@endsection |
