WIP: storage-fix-local-nas work before playlist controls feature

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
ghassanandClaude Sonnet 4.6 committed 2026-05-16 11:15:20 +03:00
1 parent 6b3ab5b65e
commit c160242dbc
46 files changed
+5365 -815

No files matched your search

+85 -12
View File
@@ -2,6 +2,11 @@
@section('title', 'Videos')
@php
$adminNeedsOtp = auth()->user()->two_factor_enabled &&
(! session('admin_2fa_verified_at') || now()->timestamp - session('admin_2fa_verified_at') >= 1800);
@endphp
@section('content')
{{-- ── Page header ──────────────────────────────────────────────────── --}}
@@ -100,18 +105,19 @@
<th>Type</th>
<th>Views</th>
<th>Likes</th>
<th>Shares</th>
<th>Uploaded</th>
<th style="width:110px; text-align:right;">Actions</th>
</tr>
</thead>
<tbody>
@forelse($videos as $video)
<tr>
<tr style="cursor:pointer;" onclick="window.open('{{ route('videos.show', $video) }}', '_blank')">
{{-- Thumbnail + title --}}
<td>
<div style="display:flex; align-items:center; gap:12px;">
@if($video->thumbnail)
<img src="{{ asset('storage/thumbnails/' . $video->thumbnail) }}"
<img src="{{ route('media.thumbnail', $video->thumbnail) }}"
alt="" style="width:72px;height:44px;object-fit:cover;border-radius:6px;flex-shrink:0;border:1px solid var(--border);">
@else
<div style="width:72px;height:44px;border-radius:6px;background:var(--bg-card2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;flex-shrink:0;">
@@ -132,7 +138,7 @@
</td>
{{-- Owner --}}
<td>
<td onclick="event.stopPropagation()">
<a href="{{ route('channel', $video->user->channel) }}" target="_blank"
style="font-size:13px;color:var(--text);text-decoration:none;display:flex;align-items:center;gap:4px;">
{{ $video->user->name }}
@@ -193,11 +199,16 @@
{{ number_format(\DB::table('video_likes')->where('video_id',$video->id)->count()) }}
</td>
{{-- Shares --}}
<td style="font-size:13px;color:var(--text-2);">
{{ number_format(\DB::table('video_shares')->where('video_id',$video->id)->count()) }}
</td>
{{-- Date --}}
<td class="text-muted-sm">{{ $video->created_at->format('M d, Y') }}</td>
{{-- Actions --}}
<td>
<td onclick="event.stopPropagation()">
<div class="adm-row-actions" style="justify-content:flex-end;">
<a href="{{ route('videos.show', $video) }}" target="_blank"
class="adm-btn adm-btn-sm" title="Watch">
@@ -218,7 +229,7 @@
</tr>
@empty
<tr>
<td colspan="9">
<td colspan="10">
<div class="empty-state">
<i class="bi bi-play-circle"></i>
<p>No videos found</p>
@@ -256,15 +267,25 @@
<i class="bi bi-info-circle-fill" style="flex-shrink:0;margin-top:1px;"></i>
All views, likes, comments, and HLS files for this video will also be deleted.
</div>
@if($adminNeedsOtp)
<div style="margin-top:14px;">
<label style="font-size:13px;color:var(--text-2);display:flex;align-items:center;gap:6px;margin-bottom:6px;">
<i class="bi bi-shield-lock-fill" style="color:#a78bfa;"></i>
Enter your <strong style="color:var(--text);">2FA code</strong> to confirm
</label>
<input type="text" id="dlgVideoOtp" inputmode="numeric" pattern="[0-9]*"
maxlength="6" autocomplete="one-time-code"
placeholder="000000"
style="width:100%;background:var(--bg-card2);border:1px solid var(--border-light);border-radius:8px;padding:10px 14px;color:#fff;font-size:22px;letter-spacing:0.3em;text-align:center;">
</div>
@endif
<div id="dlgVideoError" style="display:none;margin-top:10px;padding:8px 12px;background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.3);border-radius:6px;color:#f87171;font-size:13px;"></div>
</div>
<div class="adm-dialog-footer">
<button type="button" class="adm-btn" onclick="closeDeleteDialog()">Cancel</button>
<form id="deleteForm" method="POST">
@csrf @method('DELETE')
<button type="submit" class="adm-btn adm-btn-danger" style="height:36px;">
<i class="bi bi-trash"></i> Delete Video
</button>
</form>
<button type="button" class="adm-btn adm-btn-danger" id="dlgVideoConfirmBtn" onclick="confirmDeleteVideo()">
<i class="bi bi-trash"></i> Delete Video
</button>
</div>
</div>
</div>
@@ -273,13 +294,65 @@
@section('scripts')
<script>
const _adminNeedsOtp = {{ $adminNeedsOtp ? 'true' : 'false' }};
let _deleteVideoId = null;
function openDeleteDialog(videoId, videoTitle) {
_deleteVideoId = videoId;
document.getElementById('dlgVideoTitle').textContent = videoTitle;
document.getElementById('deleteForm').action = '/admin/videos/' + videoId;
document.getElementById('dlgVideoError').style.display = 'none';
if (_adminNeedsOtp) document.getElementById('dlgVideoOtp').value = '';
document.getElementById('deleteDialog').classList.add('open');
if (_adminNeedsOtp) setTimeout(() => document.getElementById('dlgVideoOtp').focus(), 100);
}
function closeDeleteDialog() {
document.getElementById('deleteDialog').classList.remove('open');
_deleteVideoId = null;
}
function confirmDeleteVideo() {
if (!_deleteVideoId) return;
const btn = document.getElementById('dlgVideoConfirmBtn');
const errEl = document.getElementById('dlgVideoError');
const otpCode = _adminNeedsOtp ? document.getElementById('dlgVideoOtp').value.replace(/\s/g,'') : '';
if (_adminNeedsOtp && otpCode.length !== 6) {
errEl.textContent = 'Please enter your 6-digit 2FA code.';
errEl.style.display = 'block';
document.getElementById('dlgVideoOtp').focus();
return;
}
btn.disabled = true;
btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Deleting…';
errEl.style.display = 'none';
const body = new URLSearchParams({ '_token': '{{ csrf_token() }}', '_method': 'DELETE' });
if (_adminNeedsOtp) body.append('otp_code', otpCode);
fetch('/admin/videos/' + _deleteVideoId, {
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
closeDeleteDialog();
window.location.reload();
} else {
errEl.textContent = data.message || 'Delete failed.';
errEl.style.display = 'block';
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-trash"></i> Delete Video';
if (_adminNeedsOtp) { document.getElementById('dlgVideoOtp').value = ''; document.getElementById('dlgVideoOtp').focus(); }
}
})
.catch(() => {
errEl.textContent = 'An error occurred. Please try again.';
errEl.style.display = 'block';
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-trash"></i> Delete Video';
});
}
document.getElementById('deleteDialog').addEventListener('click', function(e) {
if (e.target === this) closeDeleteDialog();