Initial commit: Animexe Laravel platform
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,528 @@
|
||||
@extends('admin.layouts.app')
|
||||
@section('title', 'Anime İçe Aktar')
|
||||
@section('page-title', 'Anime İçe Aktar')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.job-card {
|
||||
background:var(--bg2); border:1px solid var(--border);
|
||||
border-radius:var(--radius); padding:16px 20px; margin-bottom:12px;
|
||||
transition:border-color .15s;
|
||||
}
|
||||
.job-card:hover { border-color:var(--border2); }
|
||||
.test-output {
|
||||
display:none;
|
||||
background:var(--bg);
|
||||
border:1px solid var(--border);
|
||||
border-radius:var(--radius-sm);
|
||||
padding:14px;
|
||||
font-size:12px;
|
||||
font-family:'JetBrains Mono',monospace;
|
||||
white-space:pre-wrap;
|
||||
max-height:200px;
|
||||
overflow:auto;
|
||||
margin-top:12px;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h5 style="font-size:18px;font-weight:700;color:var(--text);margin:0">Anime İçe Aktar</h5>
|
||||
<div style="font-size:13px;color:var(--text2);margin-top:2px">CDN bağlantısından otomatik anime indirme</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
{{-- ── Sol: Form ── --}}
|
||||
<div class="col-lg-5">
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-cloud-download" style="color:var(--accent-lt)"></i>
|
||||
<h6>Yeni Import Job Oluştur</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.import.store') }}">
|
||||
@csrf
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Anime URL <span style="color:var(--danger)">*</span></label>
|
||||
<input type="url" name="source_url" class="form-control"
|
||||
placeholder="https://anizium.co/watch/..." value="{{ old('source_url') }}" required>
|
||||
<div class="form-text">Anizium watch veya anime sayfası URL'si</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Anime Adı <span style="color:var(--text2);font-size:11px">(opsiyonel)</span></label>
|
||||
<input type="text" name="anime_title" class="form-control"
|
||||
placeholder="Demon Slayer" value="{{ old('anime_title') }}">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">CDN ID <span style="color:var(--text2);font-size:11px">(opsiyonel — otomatik bulunur)</span></label>
|
||||
<input type="text" name="cdn_id" class="form-control"
|
||||
style="font-family:monospace" placeholder="85937" value="{{ old('cdn_id') }}">
|
||||
<div class="form-text">
|
||||
URL'den al: <code>f.aniziumserver.sbs/<strong>85937</strong>/1/1/...</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
Sezon / Bölüm Aralıkları
|
||||
<span style="color:var(--text2);font-size:11px">(opsiyonel — otomatik taranır)</span>
|
||||
</label>
|
||||
<div id="rangeRows">
|
||||
@php $oldRanges = old('season_ranges',[['season'=>1,'from'=>1,'to'=>'']]) @endphp
|
||||
@foreach($oldRanges as $i => $range)
|
||||
<div class="range-row d-flex gap-2 mb-2 align-items-center">
|
||||
<div style="flex:0 0 70px">
|
||||
<input type="number" name="season_ranges[{{ $i }}][season]"
|
||||
class="form-control form-control-sm text-center"
|
||||
placeholder="S" min="1" value="{{ $range['season']??1 }}" required>
|
||||
</div>
|
||||
<input type="number" name="season_ranges[{{ $i }}][from]"
|
||||
class="form-control form-control-sm text-center"
|
||||
placeholder="Baş" min="1" value="{{ $range['from']??'' }}" style="flex:1">
|
||||
<span style="color:var(--text3);font-size:12px">—</span>
|
||||
<input type="number" name="season_ranges[{{ $i }}][to]"
|
||||
class="form-control form-control-sm text-center"
|
||||
placeholder="Bit" min="1" value="{{ $range['to']??'' }}" style="flex:1">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeRange(this)">
|
||||
<i class="bi bi-trash3"></i>
|
||||
</button>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-secondary mt-1" onclick="addRange()">
|
||||
<i class="bi bi-plus-circle"></i> Sezon Ekle
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-download"></i> Import Job Oluştur
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Test --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-plug" style="color:var(--info)"></i>
|
||||
<h6>Bağlantı Testi</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex gap-2 mb-2">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="testApi()">
|
||||
<i class="bi bi-plug"></i> API Test
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="testCdn()">
|
||||
<i class="bi bi-play-circle"></i> CDN Test
|
||||
</button>
|
||||
</div>
|
||||
<div class="test-output" id="testResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Araçlar: Çift Kaynak + Altyazı Fix ── --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-tools" style="color:var(--warning)"></i>
|
||||
<h6>Çift Kaynak Araçları</h6>
|
||||
</div>
|
||||
<div class="card-body" style="font-size:13px">
|
||||
|
||||
{{-- İstatistikler --}}
|
||||
<div id="srcStats" style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:14px">
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:8px 10px;text-align:center">
|
||||
<div style="font-size:11px;color:var(--text2)">İki kaynağı olan bölüm</div>
|
||||
<div style="font-size:18px;font-weight:700;color:var(--success)" id="st-both">—</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:8px 10px;text-align:center">
|
||||
<div style="font-size:11px;color:var(--text2)">Altyazı hatası</div>
|
||||
<div style="font-size:18px;font-weight:700;color:var(--danger)" id="st-sub">{{ $stats['subtitle_mismatch'] }}</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:8px 10px;text-align:center">
|
||||
<div style="font-size:11px;color:var(--text2)">Sadece Anizium</div>
|
||||
<div style="font-size:18px;font-weight:700;color:var(--info)" id="st-aniz">—</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:8px 10px;text-align:center">
|
||||
<div style="font-size:11px;color:var(--text2)">Sadece AnimeCix</div>
|
||||
<div style="font-size:18px;font-weight:700;color:var(--accent)" id="st-acix">—</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-secondary mb-3 w-100" onclick="loadStats()">
|
||||
<i class="bi bi-arrow-clockwise"></i> İstatistikleri Yenile
|
||||
</button>
|
||||
|
||||
<hr style="border-color:var(--border);margin:10px 0">
|
||||
|
||||
{{-- Altyazı Fix --}}
|
||||
<div class="mb-3">
|
||||
<div style="font-weight:600;color:var(--text);margin-bottom:6px">
|
||||
<i class="bi bi-closed-caption" style="color:var(--danger)"></i>
|
||||
Altyazı Uyuşmazlık Düzelt
|
||||
</div>
|
||||
<div style="color:var(--text2);font-size:12px;margin-bottom:8px">
|
||||
Anizium'dan yanlış bölümün altyazısının kaydedildiği satırları siler.
|
||||
Bot sonraki çalışmada doğru altyazıları yeniden indirir.
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-warning flex-fill" onclick="fixSubs(true)">
|
||||
<i class="bi bi-eye"></i> Önce Gör
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger flex-fill" onclick="fixSubs(false)">
|
||||
<i class="bi bi-trash3"></i> Hataları Sil
|
||||
</button>
|
||||
</div>
|
||||
<div id="fixSubResult" style="display:none;margin-top:8px;font-size:12px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;font-family:monospace;white-space:pre-wrap;max-height:150px;overflow:auto"></div>
|
||||
</div>
|
||||
|
||||
<hr style="border-color:var(--border);margin:10px 0">
|
||||
|
||||
{{-- Re-queue --}}
|
||||
<div class="mb-2">
|
||||
<div style="font-weight:600;color:var(--text);margin-bottom:6px">
|
||||
<i class="bi bi-arrow-repeat" style="color:var(--info)"></i>
|
||||
Yeniden Kuyruğa Al
|
||||
</div>
|
||||
<div style="color:var(--text2);font-size:12px;margin-bottom:8px">
|
||||
Done job'ları tekrar pending yaparak botların her iki kaynaktan
|
||||
<code>video_sources</code> eklemesini sağlar.
|
||||
</div>
|
||||
<div class="d-flex gap-2 mb-2">
|
||||
<div style="flex:1">
|
||||
<label style="font-size:11px;color:var(--text2)">Adet</label>
|
||||
<input type="number" id="requeueLimit" class="form-control form-control-sm" value="50" min="1" max="500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-info flex-fill" onclick="requeueAnizium()">
|
||||
<i class="bi bi-cloud-arrow-down"></i> Anizium'u Yenile
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary flex-fill" onclick="requeueAnimecix()">
|
||||
<i class="bi bi-film"></i> AnimeCix'i Yenile
|
||||
</button>
|
||||
</div>
|
||||
<div id="requeueResult" style="display:none;margin-top:8px;font-size:12px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:6px"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Yardım --}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-question-circle" style="color:var(--text2)"></i>
|
||||
<h6>Nasıl Kullanılır?</h6>
|
||||
</div>
|
||||
<div class="card-body" style="font-size:13px">
|
||||
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Çift Kaynak Akışı</div>
|
||||
<ol style="color:var(--text2);padding-left:18px;line-height:2;margin-bottom:10px">
|
||||
<li>Local: <code>python reimport_all.py</code> çalıştır</li>
|
||||
<li>Local: <code>python anizium_scraper/bot2_upload.py --daemon</code></li>
|
||||
<li>Local: <code>python animecix_scraper/daemon.py</code></li>
|
||||
<li>Her bölüm otomatik <strong>"4K"</strong> + <strong>çevirmen</strong> seçeneğiyle gelir</li>
|
||||
</ol>
|
||||
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">CDN ID Nasıl Bulunur?</div>
|
||||
<ol style="color:var(--text2);padding-left:18px;line-height:2;margin-bottom:0">
|
||||
<li>Anizium'da herhangi bir bölümü oynat</li>
|
||||
<li>F12 → Network → <code>master.m3u8</code> ara</li>
|
||||
<li>URL'den sayıyı al: <code style="color:var(--warning)">85937</code></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- ── Sağ: Job Listesi ── --}}
|
||||
<div class="col-lg-7">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-list-task" style="color:var(--info)"></i>
|
||||
<h6>Import Geçmişi</h6>
|
||||
<span style="font-size:12px;color:var(--text2)">{{ $jobs->total() }} job</span>
|
||||
<button class="btn btn-sm btn-outline-danger ms-auto" onclick="openBulkDelete()" title="Toplu Sil">
|
||||
<i class="bi bi-trash3"></i> Toplu Sil
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Toplu silme paneli --}}
|
||||
<div id="bulkDeletePanel" style="display:none;padding:12px 16px;background:rgba(220,53,69,.06);border-bottom:1px solid rgba(220,53,69,.2)">
|
||||
<div style="font-size:12px;font-weight:700;color:var(--danger);margin-bottom:10px">
|
||||
<i class="bi bi-exclamation-triangle"></i> Hangi joblar silinsin?
|
||||
</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer">
|
||||
<input type="checkbox" id="del-failed" checked> <span style="color:var(--danger)">Hatalı</span> <span id="cnt-failed" class="badge bg-danger ms-1">—</span>
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer">
|
||||
<input type="checkbox" id="del-pending"> <span style="color:var(--text2)">Bekleyen</span> <span id="cnt-pending" class="badge bg-secondary ms-1">—</span>
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer">
|
||||
<input type="checkbox" id="del-stuck"> <span style="color:var(--warning)">Takılı Kalan</span> <span id="cnt-stuck" class="badge bg-warning text-dark ms-1">—</span>
|
||||
<span style="font-size:11px;color:var(--text2)">(2+ saat işlemde)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button class="btn btn-sm btn-danger" onclick="runBulkDelete()">
|
||||
<i class="bi bi-trash3"></i> Seçilenleri Sil
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="document.getElementById('bulkDeletePanel').style.display='none'">
|
||||
İptal
|
||||
</button>
|
||||
<span id="bulkDeleteMsg" style="font-size:12px;margin-left:4px"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@forelse($jobs as $job)
|
||||
<div class="job-card">
|
||||
<div class="d-flex align-items-start justify-content-between gap-3 mb-2">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:13.5px;font-weight:700;color:var(--text)" class="text-truncate">
|
||||
{{ $job->anime_title ?: 'Anime CDN #'.$job->cdn_id }}
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--text2);margin-top:2px">
|
||||
CDN: <code>{{ $job->cdn_id }}</code>
|
||||
@if($job->season_ranges)
|
||||
·
|
||||
@foreach($job->season_ranges as $r)
|
||||
S{{ $r['season'] }} E{{ $r['from'] }}-{{ $r['to'] }}{{ !$loop->last ? ', ' : '' }}
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 align-items-center flex-shrink-0">
|
||||
<span class="badge bg-{{ $job->status_color }}">{{ $job->status_label }}</span>
|
||||
<a href="{{ route('admin.import.show',$job) }}" class="btn btn-sm btn-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<form method="POST" action="{{ route('admin.import.destroy',$job) }}"
|
||||
onsubmit="return confirm('Job silinsin mi?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash3"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($job->total_episodes > 0)
|
||||
<div class="mb-2">
|
||||
<div class="d-flex justify-content-between mb-1" style="font-size:12px;color:var(--text2)">
|
||||
<span>{{ $job->done_episodes }}/{{ $job->total_episodes }} bölüm</span>
|
||||
<span>%{{ $job->progress_percent }}</span>
|
||||
</div>
|
||||
<div class="progress" style="height:5px">
|
||||
<div class="progress-bar" style="background:{{ $job->status_color==='success'?'var(--success)':($job->status_color==='danger'?'var(--danger)':'var(--accent)') }};width:{{ $job->progress_percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($job->current_step)
|
||||
<div style="font-size:12px;color:var(--text2)">{{ $job->current_step }}</div>
|
||||
@endif
|
||||
|
||||
<div style="font-size:11.5px;color:var(--text3);margin-top:4px">{{ $job->created_at->format('d.m.Y H:i') }}</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-cloud-download"></i>
|
||||
<h6>Henüz import job yok</h6>
|
||||
<p>Sol panelden yeni bir import başlatın.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
<div class="mt-3">
|
||||
{{ $jobs->links('admin.partials.pagination') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function showTestResult(text, ok=true) {
|
||||
const el = document.getElementById('testResult');
|
||||
el.style.display='block';
|
||||
el.style.color = ok ? 'var(--success)' : 'var(--danger)';
|
||||
el.textContent = text;
|
||||
}
|
||||
async function testApi() {
|
||||
showTestResult('Test ediliyor...', true);
|
||||
try {
|
||||
const r = await fetch('{{ url("/api/import/test") }}', {
|
||||
headers:{'X-Import-Key':'{{ config("app.import_api_key") }}','Accept':'application/json'}
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.ok) showTestResult(`✓ API bağlantısı başarılı\n DB: ${d.db}\n Toplam job: ${d.total}\n Bekleyen: ${d.pending}`, true);
|
||||
else showTestResult('✗ API hatası: '+JSON.stringify(d), false);
|
||||
} catch(e) { showTestResult('✗ Bağlantı hatası: '+e.message, false); }
|
||||
}
|
||||
async function testCdn() {
|
||||
const cdnId = document.querySelector('[name="cdn_id"]').value.trim()||'85937';
|
||||
const url = `https://f.aniziumserver.sbs/${cdnId}/1/1/1080p-original/master.m3u8`;
|
||||
showTestResult(`CDN test ediliyor...\nURL: ${url}`, true);
|
||||
try {
|
||||
await fetch(url, {method:'HEAD',mode:'no-cors'});
|
||||
showTestResult(`✓ CDN erişilebilir (CDN ID: ${cdnId})\n${url}`, true);
|
||||
} catch(e) { showTestResult(`✗ CDN erişilemedi: ${e.message}`, false); }
|
||||
}
|
||||
|
||||
let rangeIndex = {{ count(old('season_ranges',[[]])) }};
|
||||
function addRange() {
|
||||
const row = document.createElement('div');
|
||||
row.className='range-row d-flex gap-2 mb-2 align-items-center';
|
||||
row.innerHTML=`
|
||||
<div style="flex:0 0 70px"><input type="number" name="season_ranges[${rangeIndex}][season]" class="form-control form-control-sm text-center" placeholder="S" min="1" value="1" required></div>
|
||||
<input type="number" name="season_ranges[${rangeIndex}][from]" class="form-control form-control-sm text-center" placeholder="Baş" min="1" style="flex:1">
|
||||
<span style="color:var(--text3);font-size:12px">—</span>
|
||||
<input type="number" name="season_ranges[${rangeIndex}][to]" class="form-control form-control-sm text-center" placeholder="Bit" min="1" style="flex:1">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeRange(this)"><i class="bi bi-trash3"></i></button>`;
|
||||
document.getElementById('rangeRows').appendChild(row);
|
||||
rangeIndex++;
|
||||
}
|
||||
function removeRange(btn) {
|
||||
if (document.querySelectorAll('.range-row').length<=1) return;
|
||||
btn.closest('.range-row').remove();
|
||||
}
|
||||
|
||||
// ── Araçlar ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const CSRF = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const r = await fetch('{{ route("admin.import.sourceStats") }}', {
|
||||
headers:{'Accept':'application/json','X-Requested-With':'XMLHttpRequest'}
|
||||
});
|
||||
const d = await r.json();
|
||||
document.getElementById('st-both').textContent = d.episodes_with_both ?? '—';
|
||||
document.getElementById('st-aniz').textContent = d.episodes_only_anizium ?? '—';
|
||||
document.getElementById('st-acix').textContent = d.episodes_only_animecix ?? '—';
|
||||
document.getElementById('st-sub').textContent = d.subtitle_mismatch ?? '—';
|
||||
} catch(e) { console.warn('stats error', e); }
|
||||
}
|
||||
loadStats(); // sayfa açılışında bir kez çek
|
||||
|
||||
async function fixSubs(dryRun) {
|
||||
const el = document.getElementById('fixSubResult');
|
||||
el.style.display = 'block';
|
||||
el.textContent = 'İşleniyor...';
|
||||
try {
|
||||
const r = await fetch('{{ route("admin.import.fixSubtitles") }}', {
|
||||
method:'POST',
|
||||
headers:{'Accept':'application/json','Content-Type':'application/json','X-CSRF-TOKEN':CSRF},
|
||||
body: JSON.stringify({dry_run: dryRun}),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (dryRun) {
|
||||
el.textContent = `Kontrol edildi: ${d.checked}\nUyuşmazlık: ${d.mismatch}\n\n`
|
||||
+ (d.details.map(x => `S${x.season}E${x.episode} ${x.lang}: name=${x.name} → beklenen=${x.expected}`).join('\n') || 'Hata bulunamadı.');
|
||||
} else {
|
||||
el.textContent = `Kontrol: ${d.checked} • Uyuşmazlık: ${d.mismatch} • Silindi: ${d.deleted}`;
|
||||
loadStats();
|
||||
}
|
||||
} catch(e) { el.textContent = 'Hata: ' + e.message; }
|
||||
}
|
||||
|
||||
async function requeueAnizium() {
|
||||
const limit = document.getElementById('requeueLimit').value || 50;
|
||||
const el = document.getElementById('requeueResult');
|
||||
el.style.display = 'block';
|
||||
el.textContent = 'İşleniyor...';
|
||||
try {
|
||||
const r = await fetch('{{ route("admin.import.requeueAnizium") }}', {
|
||||
method:'POST',
|
||||
headers:{'Accept':'application/json','Content-Type':'application/json','X-CSRF-TOKEN':CSRF},
|
||||
body: JSON.stringify({limit: parseInt(limit)}),
|
||||
});
|
||||
const d = await r.json();
|
||||
el.innerHTML = `<span style="color:var(--success)">✓ Anizium: ${d.requeued} job yeniden kuyruğa alındı (${d.checked} kontrol edildi)</span>`;
|
||||
} catch(e) { el.textContent = 'Hata: ' + e.message; }
|
||||
}
|
||||
|
||||
async function requeueAnimecix() {
|
||||
const limit = document.getElementById('requeueLimit').value || 50;
|
||||
const el = document.getElementById('requeueResult');
|
||||
el.style.display = 'block';
|
||||
el.textContent = 'İşleniyor...';
|
||||
try {
|
||||
const r = await fetch('{{ route("admin.import.requeueAnimecix") }}', {
|
||||
method:'POST',
|
||||
headers:{'Accept':'application/json','Content-Type':'application/json','X-CSRF-TOKEN':CSRF},
|
||||
body: JSON.stringify({limit: parseInt(limit)}),
|
||||
});
|
||||
const d = await r.json();
|
||||
el.innerHTML = `<span style="color:var(--success)">✓ AnimeCix: ${d.requeued} job yeniden kuyruğa alındı (${d.checked} kontrol edildi)</span>`;
|
||||
} catch(e) { el.textContent = 'Hata: ' + e.message; }
|
||||
}
|
||||
|
||||
// ── Toplu Silme ──────────────────────────────────────────────────────────────
|
||||
|
||||
const BULK_COUNTS_URL = '{{ route("admin.import.bulkCounts") }}';
|
||||
const BULK_DELETE_URL = '{{ route("admin.import.destroyByStatus") }}';
|
||||
|
||||
async function openBulkDelete() {
|
||||
const panel = document.getElementById('bulkDeletePanel');
|
||||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||||
if (panel.style.display === 'block') loadBulkCounts();
|
||||
}
|
||||
|
||||
async function loadBulkCounts() {
|
||||
try {
|
||||
const r = await fetch(BULK_COUNTS_URL, {headers:{'Accept':'application/json'}});
|
||||
const d = await r.json();
|
||||
document.getElementById('cnt-failed').textContent = d.failed ?? 0;
|
||||
document.getElementById('cnt-pending').textContent = d.pending ?? 0;
|
||||
document.getElementById('cnt-stuck').textContent = d.stuck ?? 0;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function runBulkDelete() {
|
||||
const statuses = [];
|
||||
if (document.getElementById('del-failed').checked) statuses.push('failed');
|
||||
if (document.getElementById('del-pending').checked) statuses.push('pending');
|
||||
if (document.getElementById('del-stuck').checked) statuses.push('fetching','downloading','uploading');
|
||||
|
||||
if (!statuses.length) { alert('En az bir kategori seç.'); return; }
|
||||
|
||||
const names = [];
|
||||
if (document.getElementById('del-failed').checked) names.push('hatalı');
|
||||
if (document.getElementById('del-pending').checked) names.push('bekleyen');
|
||||
if (document.getElementById('del-stuck').checked) names.push('takılı kalan');
|
||||
|
||||
if (!confirm(`${names.join(', ')} joblar silinsin mi? Bu işlem geri alınamaz.`)) return;
|
||||
|
||||
const msgEl = document.getElementById('bulkDeleteMsg');
|
||||
msgEl.style.color = 'var(--text2)';
|
||||
msgEl.textContent = 'Siliniyor...';
|
||||
|
||||
try {
|
||||
const r = await fetch(BULK_DELETE_URL, {
|
||||
method: 'DELETE',
|
||||
headers: {'Accept':'application/json','Content-Type':'application/json','X-CSRF-TOKEN':CSRF},
|
||||
body: JSON.stringify({ statuses, stuck_hours: 2 }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.ok) {
|
||||
msgEl.style.color = 'var(--success)';
|
||||
msgEl.textContent = `✓ ${d.deleted} job silindi.`;
|
||||
setTimeout(() => location.reload(), 1200);
|
||||
} else {
|
||||
msgEl.style.color = 'var(--danger)';
|
||||
msgEl.textContent = d.message || 'Hata oluştu.';
|
||||
}
|
||||
} catch(e) {
|
||||
msgEl.style.color = 'var(--danger)';
|
||||
msgEl.textContent = 'Sunucu hatası: ' + e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user