mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Add release reporting feature
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\BasePageController;
|
||||
use App\Models\Release;
|
||||
use App\Models\ReleaseReport;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AdminReleaseReportController extends BasePageController
|
||||
{
|
||||
/**
|
||||
* Display a listing of release reports.
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$this->setAdminPrefs();
|
||||
|
||||
$status = $request->input('status', 'pending');
|
||||
$reportsList = ReleaseReport::getReportsRange($status, 50);
|
||||
$statusCounts = ReleaseReport::getCountByStatus();
|
||||
|
||||
$meta_title = $title = 'Release Reports';
|
||||
|
||||
return view('admin.release-reports.index', compact(
|
||||
'reportsList',
|
||||
'statusCounts',
|
||||
'status',
|
||||
'title',
|
||||
'meta_title'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report status.
|
||||
*/
|
||||
public function updateStatus(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'status' => 'required|in:pending,reviewed,resolved,dismissed',
|
||||
]);
|
||||
|
||||
$report = ReleaseReport::findOrFail($id);
|
||||
$report->update([
|
||||
'status' => $request->input('status'),
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Report status updated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the reported release and resolve the report.
|
||||
*/
|
||||
public function deleteRelease(int $id): RedirectResponse
|
||||
{
|
||||
$report = ReleaseReport::with('release')->findOrFail($id);
|
||||
|
||||
if ($report->release) {
|
||||
$releaseName = $report->release->searchname;
|
||||
$releaseId = $report->releases_id;
|
||||
|
||||
// Delete the release
|
||||
Release::where('id', $releaseId)->delete();
|
||||
|
||||
// Update all reports for this release to resolved
|
||||
ReleaseReport::where('releases_id', $releaseId)
|
||||
->update([
|
||||
'status' => 'resolved',
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', "Release '{$releaseName}' deleted and all related reports resolved.");
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Release not found or already deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss a report.
|
||||
*/
|
||||
public function dismiss(int $id): RedirectResponse
|
||||
{
|
||||
$report = ReleaseReport::findOrFail($id);
|
||||
$report->update([
|
||||
'status' => 'dismissed',
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Report dismissed successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update report statuses.
|
||||
*/
|
||||
public function bulkAction(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'action' => 'required|in:dismiss,resolve,reviewed,delete',
|
||||
'report_ids' => 'required|array',
|
||||
'report_ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$action = $request->input('action');
|
||||
$reportIds = $request->input('report_ids');
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($reportIds as $reportId) {
|
||||
$report = ReleaseReport::with('release')->find($reportId);
|
||||
if (! $report) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($action === 'delete' && $report->release) {
|
||||
$releaseId = $report->releases_id;
|
||||
Release::where('id', $releaseId)->delete();
|
||||
ReleaseReport::where('releases_id', $releaseId)
|
||||
->update([
|
||||
'status' => 'resolved',
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
} elseif ($action === 'dismiss') {
|
||||
$report->update([
|
||||
'status' => 'dismissed',
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
} elseif ($action === 'resolve') {
|
||||
$report->update([
|
||||
'status' => 'resolved',
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
} elseif ($action === 'reviewed') {
|
||||
$report->update([
|
||||
'status' => 'reviewed',
|
||||
'reviewed_by' => Auth::id(),
|
||||
'reviewed_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
$actionLabel = match ($action) {
|
||||
'delete' => 'deleted',
|
||||
'dismiss' => 'dismissed',
|
||||
'resolve' => 'resolved',
|
||||
'reviewed' => 'marked as reviewed',
|
||||
default => 'processed',
|
||||
};
|
||||
|
||||
return redirect()->back()->with('success', "{$count} report(s) {$actionLabel} successfully.");
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Models\ReleaseComment;
|
||||
use App\Models\ReleaseFile;
|
||||
use App\Models\ReleaseNfo;
|
||||
use App\Models\ReleaseRegex;
|
||||
use App\Models\ReleaseReport;
|
||||
use App\Models\Settings;
|
||||
use App\Models\UserDownload;
|
||||
use App\Models\Video;
|
||||
@@ -74,6 +75,11 @@ class DetailsController extends BasePageController
|
||||
$comments = ReleaseComment::getComments($data['id']);
|
||||
$similars = $this->releaseSearchService->searchSimilar($data['id'], $data['searchname'], $this->userdata->categoryexclusions);
|
||||
$failed = DnzbFailure::getFailedCount($data['id']);
|
||||
$reportData = ReleaseReport::where('releases_id', $data['id'])
|
||||
->whereIn('status', ['pending', 'reviewed'])
|
||||
->get();
|
||||
$reportCount = $reportData->count();
|
||||
$reportReasons = ReleaseReport::reasonKeysToLabels($reportData->pluck('reason')->unique()->implode(', '));
|
||||
$downloadedBy = UserDownload::query()->with('user')->where('releases_id', $data['id'])->get(['users_id']);
|
||||
|
||||
$showInfo = '';
|
||||
@@ -183,6 +189,8 @@ class DetailsController extends BasePageController
|
||||
'similars' => $similars !== false ? $similars : [],
|
||||
'privateprofiles' => config('nntmux_settings.private_profiles'),
|
||||
'failed' => $failed,
|
||||
'reportCount' => $reportCount,
|
||||
'reportReasons' => $reportReasons,
|
||||
'regex' => $releaseRegex,
|
||||
'downloadedby' => $downloadedBy,
|
||||
'meta_title' => 'View NZB',
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Release;
|
||||
use App\Models\ReleaseReport;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ReleaseReportController extends BasePageController
|
||||
{
|
||||
/**
|
||||
* Submit a report for a release.
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'release_id' => 'required|integer',
|
||||
'reason' => 'required|string|max:255',
|
||||
'description' => 'nullable|string|max:1000',
|
||||
]);
|
||||
|
||||
$releaseId = $request->input('release_id');
|
||||
$userId = Auth::id();
|
||||
|
||||
// Check if release exists
|
||||
$release = Release::find($releaseId);
|
||||
if (! $release) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Release not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Check if user already reported this release
|
||||
if (ReleaseReport::hasUserReported($releaseId, $userId)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'You have already reported this release.',
|
||||
], 409);
|
||||
}
|
||||
|
||||
// Create the report
|
||||
ReleaseReport::create([
|
||||
'releases_id' => $releaseId,
|
||||
'users_id' => $userId,
|
||||
'reason' => $request->input('reason'),
|
||||
'description' => $request->input('description'),
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Report submitted successfully. Thank you for helping improve our content.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report reasons for the form.
|
||||
*/
|
||||
public function getReasons(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'reasons' => ReleaseReport::REASONS,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has already reported a release.
|
||||
*/
|
||||
public function checkReported(Request $request): JsonResponse
|
||||
{
|
||||
$releaseId = $request->input('release_id');
|
||||
$userId = Auth::id();
|
||||
|
||||
$hasReported = ReleaseReport::hasUserReported($releaseId, $userId);
|
||||
|
||||
return response()->json([
|
||||
'has_reported' => $hasReported,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,11 @@ class Release extends Model
|
||||
return $this->hasMany(ReleaseComment::class, 'releases_id');
|
||||
}
|
||||
|
||||
public function reports(): HasMany
|
||||
{
|
||||
return $this->hasMany(ReleaseReport::class, 'releases_id');
|
||||
}
|
||||
|
||||
public function releaseGroup(): HasMany
|
||||
{
|
||||
return $this->hasMany(ReleasesGroups::class, 'releases_id');
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ReleaseReport extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected $table = 'release_reports';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected $fillable = [
|
||||
'releases_id',
|
||||
'users_id',
|
||||
'reason',
|
||||
'description',
|
||||
'status',
|
||||
'reviewed_by',
|
||||
'reviewed_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*/
|
||||
protected $casts = [
|
||||
'reviewed_at' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Predefined report reasons
|
||||
*/
|
||||
public const REASONS = [
|
||||
'duplicate' => 'Duplicate Release',
|
||||
'fake' => 'Fake/Malicious Content',
|
||||
'password' => 'Password Protected',
|
||||
'incomplete' => 'Incomplete/Corrupted',
|
||||
'wrong_category' => 'Wrong Category',
|
||||
'spam' => 'Spam/Advertisement',
|
||||
'other' => 'Other',
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert a comma-separated list of reason keys to their labels.
|
||||
*/
|
||||
public static function reasonKeysToLabels(?string $reasonKeys): string
|
||||
{
|
||||
if (empty($reasonKeys)) {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
$keys = array_map('trim', explode(',', $reasonKeys));
|
||||
$labels = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$labels[] = self::REASONS[$key] ?? ucfirst($key);
|
||||
}
|
||||
|
||||
return implode(', ', array_unique($labels));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the release that was reported.
|
||||
*/
|
||||
public function release(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Release::class, 'releases_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who reported the release.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'users_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin who reviewed the report.
|
||||
*/
|
||||
public function reviewer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reviewed_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a paginated list of reports with optional filters.
|
||||
*/
|
||||
public static function getReportsRange(
|
||||
?string $status = null,
|
||||
int $perPage = 50
|
||||
): LengthAwarePaginator {
|
||||
$query = self::query()
|
||||
->with(['release', 'user', 'reviewer'])
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
if ($status !== null && $status !== 'all') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report count by status.
|
||||
*/
|
||||
public static function getCountByStatus(): array
|
||||
{
|
||||
$counts = self::query()
|
||||
->selectRaw('status, COUNT(*) as count')
|
||||
->groupBy('status')
|
||||
->pluck('count', 'status')
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
'pending' => $counts['pending'] ?? 0,
|
||||
'reviewed' => $counts['reviewed'] ?? 0,
|
||||
'resolved' => $counts['resolved'] ?? 0,
|
||||
'dismissed' => $counts['dismissed'] ?? 0,
|
||||
'total' => array_sum($counts),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has already reported a specific release.
|
||||
*/
|
||||
public static function hasUserReported(int $releaseId, int $userId): bool
|
||||
{
|
||||
return self::query()
|
||||
->where('releases_id', $releaseId)
|
||||
->where('users_id', $userId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable reason label.
|
||||
*/
|
||||
public function getReasonLabelAttribute(): string
|
||||
{
|
||||
return self::REASONS[$this->reason] ?? $this->reason;
|
||||
}
|
||||
}
|
||||
@@ -77,11 +77,14 @@ class MovieBrowseService
|
||||
."GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_comments, "
|
||||
."GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_grabs, "
|
||||
."GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_failed, "
|
||||
."GROUP_CONCAT(rr.report_count ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_reports, "
|
||||
."GROUP_CONCAT(rr.report_reasons ORDER BY r.postdate DESC SEPARATOR '|' ) AS grp_release_report_reasons, "
|
||||
."GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_catname, "
|
||||
.'m.*, g.name AS group_name, rn.releases_id AS nfoid FROM releases r '
|
||||
.'LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id '
|
||||
.'LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id '
|
||||
.'LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id '
|
||||
.'LEFT OUTER JOIN (SELECT releases_id, COUNT(*) AS report_count, GROUP_CONCAT(DISTINCT reason SEPARATOR \', \') AS report_reasons FROM release_reports WHERE status IN (\'pending\', \'reviewed\') GROUP BY releases_id) rr ON rr.releases_id = r.id '
|
||||
.'LEFT OUTER JOIN categories c ON c.id = r.categories_id '
|
||||
.'LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id '
|
||||
.'INNER JOIN movieinfo m ON m.imdbid = r.imdbid '
|
||||
|
||||
@@ -63,6 +63,8 @@ class ReleaseBrowseService
|
||||
CONCAT(cp.title, ' > ', c.title) AS category_name,
|
||||
CONCAT(cp.id, ',', c.id) AS category_ids,
|
||||
df.failed AS failed_count,
|
||||
rr.report_count AS report_count,
|
||||
rr.report_reasons AS report_reasons,
|
||||
rn.releases_id AS nfoid,
|
||||
re.releases_id AS reid,
|
||||
v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
|
||||
@@ -85,6 +87,7 @@ class ReleaseBrowseService
|
||||
LEFT OUTER JOIN video_data re ON re.releases_id = r.id
|
||||
LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
|
||||
LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
|
||||
LEFT OUTER JOIN (SELECT releases_id, COUNT(*) AS report_count, GROUP_CONCAT(DISTINCT reason SEPARATOR ', ') AS report_reasons FROM release_reports WHERE status IN ('pending', 'reviewed') GROUP BY releases_id) rr ON rr.releases_id = r.id
|
||||
GROUP BY r.id
|
||||
ORDER BY %8\$s %9\$s",
|
||||
$this->showPasswords(),
|
||||
|
||||
@@ -1121,6 +1121,8 @@ class ReleaseSearchService
|
||||
cp.title AS parent_category, c.title AS sub_category,
|
||||
CONCAT(cp.title, ' > ', c.title) AS category_name,
|
||||
df.failed AS failed_count,
|
||||
rr.report_count AS report_count,
|
||||
rr.report_reasons AS report_reasons,
|
||||
g.name AS group_name,
|
||||
rn.releases_id AS nfoid,
|
||||
re.releases_id AS reid,
|
||||
@@ -1136,6 +1138,7 @@ class ReleaseSearchService
|
||||
LEFT JOIN categories c ON c.id = r.categories_id
|
||||
LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
|
||||
LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
|
||||
LEFT OUTER JOIN (SELECT releases_id, COUNT(*) AS report_count, GROUP_CONCAT(DISTINCT reason SEPARATOR ', ') AS report_reasons FROM release_reports WHERE status IN ('pending', 'reviewed') GROUP BY releases_id) rr ON rr.releases_id = r.id
|
||||
%s",
|
||||
$whereSql
|
||||
);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('release_reports', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('releases_id');
|
||||
$table->unsignedInteger('users_id');
|
||||
$table->string('reason', 255);
|
||||
$table->text('description')->nullable();
|
||||
$table->enum('status', ['pending', 'reviewed', 'resolved', 'dismissed'])->default('pending');
|
||||
$table->unsignedInteger('reviewed_by')->nullable();
|
||||
$table->timestamp('reviewed_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('releases_id')->references('id')->on('releases')->onDelete('cascade');
|
||||
$table->foreign('users_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$table->foreign('reviewed_by')->references('id')->on('users')->onDelete('set null');
|
||||
|
||||
$table->index(['releases_id', 'status']);
|
||||
$table->index(['status', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('release_reports');
|
||||
}
|
||||
};
|
||||
@@ -5727,3 +5727,293 @@ function initSortDropdowns() {
|
||||
// Initialize sort dropdowns on DOMContentLoaded
|
||||
document.addEventListener('DOMContentLoaded', initSortDropdowns);
|
||||
|
||||
// =============================================================================
|
||||
// RELEASE REPORT FUNCTIONALITY
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Initialize release report buttons and modals
|
||||
* CSP-safe implementation for reporting bad releases
|
||||
*/
|
||||
function initReleaseReportButtons() {
|
||||
// Find all report button containers
|
||||
var containers = document.querySelectorAll('.report-button-container');
|
||||
|
||||
containers.forEach(function(container) {
|
||||
var trigger = container.querySelector('.report-trigger');
|
||||
var modal = container.querySelector('.report-modal');
|
||||
|
||||
if (!trigger || !modal) return;
|
||||
|
||||
// Skip if already initialized
|
||||
if (trigger.hasAttribute('data-report-initialized')) return;
|
||||
trigger.setAttribute('data-report-initialized', 'true');
|
||||
|
||||
var releaseId = trigger.getAttribute('data-report-release-id');
|
||||
|
||||
var form = modal.querySelector('.report-form');
|
||||
var closeButtons = modal.querySelectorAll('.report-modal-close');
|
||||
var backdrop = modal.querySelector('.report-modal-backdrop');
|
||||
var reasonSelect = modal.querySelector('.report-reason');
|
||||
var descriptionTextarea = modal.querySelector('.report-description');
|
||||
var charCount = modal.querySelector('.report-char-count');
|
||||
var submitButton = modal.querySelector('.report-submit');
|
||||
var errorDiv = modal.querySelector('.report-error');
|
||||
var successDiv = modal.querySelector('.report-success');
|
||||
var reportLabel = trigger.querySelector('.report-label');
|
||||
var flagIcon = trigger.querySelector('i');
|
||||
|
||||
var hasReported = false;
|
||||
var isSubmitting = false;
|
||||
|
||||
// Open modal
|
||||
trigger.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (hasReported) return;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
// Reset form state
|
||||
if (errorDiv) errorDiv.classList.add('hidden');
|
||||
if (successDiv) successDiv.classList.add('hidden');
|
||||
if (reasonSelect) reasonSelect.value = '';
|
||||
if (descriptionTextarea) descriptionTextarea.value = '';
|
||||
if (charCount) charCount.textContent = '0/1000 characters';
|
||||
if (submitButton) submitButton.disabled = true;
|
||||
});
|
||||
|
||||
// Close modal function
|
||||
function closeModal() {
|
||||
modal.classList.add('hidden');
|
||||
if (hasReported) {
|
||||
trigger.disabled = true;
|
||||
trigger.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
if (flagIcon) flagIcon.classList.add('text-red-500');
|
||||
if (reportLabel) reportLabel.textContent = 'Reported';
|
||||
}
|
||||
}
|
||||
|
||||
// Close button handlers
|
||||
closeButtons.forEach(function(closeBtn) {
|
||||
closeBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
closeModal();
|
||||
});
|
||||
});
|
||||
|
||||
// Backdrop click to close
|
||||
if (backdrop) {
|
||||
backdrop.addEventListener('click', function() {
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Character count for description
|
||||
if (descriptionTextarea && charCount) {
|
||||
descriptionTextarea.addEventListener('input', function() {
|
||||
charCount.textContent = this.value.length + '/1000 characters';
|
||||
});
|
||||
}
|
||||
|
||||
// Enable/disable submit based on reason selection
|
||||
if (reasonSelect && submitButton) {
|
||||
reasonSelect.addEventListener('change', function() {
|
||||
submitButton.disabled = !this.value;
|
||||
});
|
||||
}
|
||||
|
||||
// Submit button click handler
|
||||
if (submitButton) {
|
||||
submitButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isSubmitting || !reasonSelect || !reasonSelect.value) return;
|
||||
|
||||
isSubmitting = true;
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Submitting...';
|
||||
|
||||
if (errorDiv) errorDiv.classList.add('hidden');
|
||||
if (successDiv) successDiv.classList.add('hidden');
|
||||
|
||||
var csrfToken = document.querySelector('meta[name="csrf-token"]');
|
||||
var csrfValue = csrfToken ? csrfToken.getAttribute('content') : '';
|
||||
|
||||
var requestBody = {
|
||||
release_id: releaseId,
|
||||
reason: reasonSelect.value,
|
||||
description: descriptionTextarea ? descriptionTextarea.value : ''
|
||||
};
|
||||
|
||||
fetch('/release-report', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfValue,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json().then(function(data) {
|
||||
return { ok: response.ok, data: data };
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
if (result.ok && result.data.success) {
|
||||
if (successDiv) {
|
||||
successDiv.querySelector('p').textContent = result.data.message;
|
||||
successDiv.classList.remove('hidden');
|
||||
}
|
||||
hasReported = true;
|
||||
setTimeout(closeModal, 2000);
|
||||
} else {
|
||||
if (errorDiv) {
|
||||
errorDiv.querySelector('p').textContent = result.data.message || 'An error occurred. Please try again.';
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
if (errorDiv) {
|
||||
errorDiv.querySelector('p').textContent = 'Network error. Please try again.';
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
})
|
||||
.finally(function() {
|
||||
isSubmitting = false;
|
||||
if (submitButton) {
|
||||
submitButton.disabled = !reasonSelect.value;
|
||||
submitButton.innerHTML = 'Submit Report';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize report buttons on DOMContentLoaded
|
||||
document.addEventListener('DOMContentLoaded', initReleaseReportButtons);
|
||||
|
||||
// Re-initialize on dynamic content load (for AJAX-loaded content)
|
||||
document.addEventListener('contentLoaded', initReleaseReportButtons);
|
||||
|
||||
// =============================================================================
|
||||
// ADMIN RELEASE REPORTS PAGE FUNCTIONALITY
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Initialize admin release reports page functionality
|
||||
* Handles description modal, select all, and bulk actions
|
||||
*/
|
||||
function initAdminReleaseReports() {
|
||||
// Report Description Modal
|
||||
var descModal = document.getElementById('reportDescriptionModal');
|
||||
var descButtons = document.querySelectorAll('.report-description-btn');
|
||||
var descContent = document.getElementById('reportDescContent');
|
||||
var descReason = document.getElementById('reportDescReason');
|
||||
var descReporter = document.getElementById('reportDescReporter');
|
||||
var descCloseButtons = document.querySelectorAll('.report-desc-modal-close');
|
||||
var descBackdrop = document.querySelector('.report-desc-modal-backdrop');
|
||||
|
||||
// Open description modal
|
||||
descButtons.forEach(function(btn) {
|
||||
if (btn.hasAttribute('data-desc-initialized')) return;
|
||||
btn.setAttribute('data-desc-initialized', 'true');
|
||||
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var description = this.getAttribute('data-description');
|
||||
var reason = this.getAttribute('data-reason');
|
||||
var reporter = this.getAttribute('data-reporter');
|
||||
|
||||
if (descContent) descContent.textContent = description || 'No additional details provided.';
|
||||
if (descReason) descReason.textContent = reason || 'Unknown';
|
||||
if (descReporter) descReporter.textContent = reporter || 'Unknown';
|
||||
|
||||
if (descModal) descModal.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Close description modal
|
||||
function closeDescModal() {
|
||||
if (descModal) descModal.classList.add('hidden');
|
||||
}
|
||||
|
||||
descCloseButtons.forEach(function(btn) {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
closeDescModal();
|
||||
});
|
||||
});
|
||||
|
||||
if (descBackdrop) {
|
||||
descBackdrop.addEventListener('click', closeDescModal);
|
||||
}
|
||||
|
||||
// Select All functionality
|
||||
var selectAll = document.getElementById('select-all');
|
||||
var checkboxes = document.querySelectorAll('.report-checkbox');
|
||||
|
||||
if (selectAll && !selectAll.hasAttribute('data-selectall-initialized')) {
|
||||
selectAll.setAttribute('data-selectall-initialized', 'true');
|
||||
|
||||
selectAll.addEventListener('change', function() {
|
||||
checkboxes.forEach(function(checkbox) {
|
||||
checkbox.checked = selectAll.checked;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update select all checkbox when individual checkboxes change
|
||||
checkboxes.forEach(function(checkbox) {
|
||||
if (checkbox.hasAttribute('data-checkbox-initialized')) return;
|
||||
checkbox.setAttribute('data-checkbox-initialized', 'true');
|
||||
|
||||
checkbox.addEventListener('change', function() {
|
||||
var allChecked = Array.from(checkboxes).every(function(cb) { return cb.checked; });
|
||||
var someChecked = Array.from(checkboxes).some(function(cb) { return cb.checked; });
|
||||
if (selectAll) {
|
||||
selectAll.checked = allChecked;
|
||||
selectAll.indeterminate = someChecked && !allChecked;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk action form validation
|
||||
var bulkForm = document.getElementById('bulk-action-form');
|
||||
if (bulkForm && !bulkForm.hasAttribute('data-bulk-initialized')) {
|
||||
bulkForm.setAttribute('data-bulk-initialized', 'true');
|
||||
|
||||
bulkForm.addEventListener('submit', function(e) {
|
||||
var actionSelect = this.querySelector('select[name="action"]');
|
||||
var action = actionSelect ? actionSelect.value : '';
|
||||
var checkedCount = document.querySelectorAll('.report-checkbox:checked').length;
|
||||
|
||||
if (!action) {
|
||||
e.preventDefault();
|
||||
alert('Please select an action.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkedCount === 0) {
|
||||
e.preventDefault();
|
||||
alert('Please select at least one report.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'delete') {
|
||||
if (!confirm('Are you sure you want to DELETE ' + checkedCount + ' release(s)? This action cannot be undone.')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize admin release reports on DOMContentLoaded
|
||||
document.addEventListener('DOMContentLoaded', initAdminReleaseReports);
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto px-4 py-6">
|
||||
<!-- Flash Messages -->
|
||||
@if(session('success'))
|
||||
<div class="mb-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4 flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-check-circle text-green-600 dark:text-green-400 mr-3 text-xl"></i>
|
||||
<p class="text-sm text-green-800 dark:text-green-200 font-medium">{{ session('success') }}</p>
|
||||
</div>
|
||||
<button onclick="this.parentElement.parentElement.remove()" class="text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-200">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="mb-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-exclamation-circle text-red-600 dark:text-red-400 mr-3 text-xl"></i>
|
||||
<p class="text-sm text-red-800 dark:text-red-200 font-medium">{{ session('error') }}</p>
|
||||
</div>
|
||||
<button onclick="this.parentElement.parentElement.remove()" class="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm">
|
||||
<!-- Header -->
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-200">
|
||||
<i class="fas fa-flag mr-2 text-red-500"></i>{{ $title }}
|
||||
</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Manage release reports submitted by users</p>
|
||||
</div>
|
||||
|
||||
<!-- Status Stats -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="{{ route('admin.release-reports', ['status' => 'pending']) }}"
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'pending' ? 'bg-yellow-500 text-white' : 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 hover:bg-yellow-200 dark:hover:bg-yellow-800' }}">
|
||||
<i class="fas fa-clock mr-1"></i> Pending ({{ $statusCounts['pending'] }})
|
||||
</a>
|
||||
<a href="{{ route('admin.release-reports', ['status' => 'reviewed']) }}"
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'reviewed' ? 'bg-blue-500 text-white' : 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 hover:bg-blue-200 dark:hover:bg-blue-800' }}">
|
||||
<i class="fas fa-eye mr-1"></i> Reviewed ({{ $statusCounts['reviewed'] }})
|
||||
</a>
|
||||
<a href="{{ route('admin.release-reports', ['status' => 'resolved']) }}"
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'resolved' ? 'bg-green-500 text-white' : 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 hover:bg-green-200 dark:hover:bg-green-800' }}">
|
||||
<i class="fas fa-check mr-1"></i> Resolved ({{ $statusCounts['resolved'] }})
|
||||
</a>
|
||||
<a href="{{ route('admin.release-reports', ['status' => 'dismissed']) }}"
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'dismissed' ? 'bg-gray-500 text-white' : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600' }}">
|
||||
<i class="fas fa-ban mr-1"></i> Dismissed ({{ $statusCounts['dismissed'] }})
|
||||
</a>
|
||||
<a href="{{ route('admin.release-reports', ['status' => 'all']) }}"
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'all' ? 'bg-purple-500 text-white' : 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 hover:bg-purple-200 dark:hover:bg-purple-800' }}">
|
||||
<i class="fas fa-list mr-1"></i> All ({{ $statusCounts['total'] }})
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reports Table -->
|
||||
@if($reportsList->count() > 0)
|
||||
<form id="bulk-action-form" method="POST" action="{{ route('admin.release-reports.bulk') }}">
|
||||
@csrf
|
||||
<div class="px-6 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 flex items-center gap-4">
|
||||
<input type="checkbox" id="select-all" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700">
|
||||
<select name="action" class="text-sm border-gray-300 dark:border-gray-600 rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-200">
|
||||
<option value="">Bulk Actions</option>
|
||||
<option value="reviewed">Mark as Reviewed</option>
|
||||
<option value="resolve">Mark as Resolved</option>
|
||||
<option value="dismiss">Dismiss Selected</option>
|
||||
<option value="delete">Delete Releases & Resolve</option>
|
||||
</select>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-10"></th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Release</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Reporter</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Reason</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Date</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach($reportsList as $report)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
<input type="checkbox" name="report_ids[]" value="{{ $report->id }}" class="report-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700">
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-200 font-mono">
|
||||
#{{ $report->id }}
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="max-w-md">
|
||||
@if($report->release)
|
||||
<a href="{{ url('/details/' . $report->release->guid) }}"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 font-medium text-sm break-all">
|
||||
{{ Str::limit($report->release->searchname, 80) }}
|
||||
</a>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
ID: {{ $report->releases_id }} | Size: {{ number_format($report->release->size / 1073741824, 2) }} GB
|
||||
</div>
|
||||
@else
|
||||
<span class="text-red-500 dark:text-red-400 text-sm">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i> Release Deleted
|
||||
</span>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Original ID: {{ $report->releases_id }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-900 dark:text-gray-200">
|
||||
@if($report->user)
|
||||
{{ $report->user->username }}
|
||||
@else
|
||||
<span class="text-gray-500">Deleted User</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200">
|
||||
{{ $report->reason_label }}
|
||||
</span>
|
||||
@if($report->description)
|
||||
<button type="button"
|
||||
class="report-description-btn ml-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
data-description="{{ htmlspecialchars($report->description, ENT_QUOTES) }}"
|
||||
data-reason="{{ $report->reason_label }}"
|
||||
data-reporter="{{ $report->user ? $report->user->username : 'Unknown' }}"
|
||||
title="View description">
|
||||
<i class="fas fa-comment-dots"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
@php
|
||||
$statusColors = [
|
||||
'pending' => 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200',
|
||||
'reviewed' => 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
|
||||
'resolved' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
|
||||
'dismissed' => 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200',
|
||||
];
|
||||
$statusIcons = [
|
||||
'pending' => 'fa-clock',
|
||||
'reviewed' => 'fa-eye',
|
||||
'resolved' => 'fa-check',
|
||||
'dismissed' => 'fa-ban',
|
||||
];
|
||||
@endphp
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $statusColors[$report->status] ?? 'bg-gray-100 text-gray-800' }}">
|
||||
<i class="fas {{ $statusIcons[$report->status] ?? 'fa-question' }} mr-1"></i>
|
||||
{{ ucfirst($report->status) }}
|
||||
</span>
|
||||
@if($report->reviewer && $report->reviewed_at)
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
by {{ $report->reviewer->username }} at {{ $report->reviewed_at->format('M d, Y H:i') }}
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $report->created_at->format('M d, Y') }}
|
||||
<div class="text-xs">{{ $report->created_at->format('H:i') }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
@if($report->release && in_array($report->status, ['pending', 'reviewed']))
|
||||
<!-- Delete Release Button -->
|
||||
<form method="POST" action="{{ route('admin.release-reports.delete-release', $report->id) }}" class="inline" onsubmit="return confirm('Are you sure you want to DELETE this release? This action cannot be undone.');">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 bg-red-600 text-white rounded-lg hover:bg-red-700 transition text-sm inline-flex items-center"
|
||||
title="Delete Release">
|
||||
<i class="fas fa-trash mr-1"></i> Delete
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($report->status === 'pending')
|
||||
<!-- Mark as Reviewed Button -->
|
||||
<form method="POST" action="{{ route('admin.release-reports.update-status', $report->id) }}" class="inline">
|
||||
@csrf
|
||||
<input type="hidden" name="status" value="reviewed">
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm inline-flex items-center"
|
||||
title="Mark as Reviewed">
|
||||
<i class="fas fa-eye mr-1"></i> Reviewed
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Dismiss Button -->
|
||||
<form method="POST" action="{{ route('admin.release-reports.dismiss', $report->id) }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition text-sm inline-flex items-center"
|
||||
title="Dismiss Report">
|
||||
<i class="fas fa-ban mr-1"></i> Dismiss
|
||||
</button>
|
||||
</form>
|
||||
@elseif($report->status === 'reviewed')
|
||||
<!-- Mark as Resolved Button -->
|
||||
<form method="POST" action="{{ route('admin.release-reports.update-status', $report->id) }}" class="inline">
|
||||
@csrf
|
||||
<input type="hidden" name="status" value="resolved">
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition text-sm inline-flex items-center"
|
||||
title="Mark as Resolved">
|
||||
<i class="fas fa-check mr-1"></i> Resolve
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Dismiss Button -->
|
||||
<form method="POST" action="{{ route('admin.release-reports.dismiss', $report->id) }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition text-sm inline-flex items-center"
|
||||
title="Dismiss Report">
|
||||
<i class="fas fa-ban mr-1"></i> Dismiss
|
||||
</button>
|
||||
</form>
|
||||
@elseif(!$report->release)
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">Release already deleted</span>
|
||||
@else
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ ucfirst($report->status) }}</span>
|
||||
@endif
|
||||
|
||||
@if($report->release)
|
||||
<a href="{{ url('/details/' . $report->release->guid) }}"
|
||||
target="_blank"
|
||||
class="px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm inline-flex items-center"
|
||||
title="View Release">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ $reportsList->appends(['status' => $status])->links() }}
|
||||
</div>
|
||||
@else
|
||||
<div class="px-6 py-12 text-center">
|
||||
<i class="fas fa-flag text-gray-300 dark:text-gray-600 text-6xl mb-4"></i>
|
||||
<h3 class="text-xl font-medium text-gray-700 dark:text-gray-300 mb-2">No Reports Found</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
@if($status !== 'all')
|
||||
No {{ $status }} reports at this time.
|
||||
<a href="{{ route('admin.release-reports', ['status' => 'all']) }}" class="text-blue-600 dark:text-blue-400 hover:underline">View all reports</a>
|
||||
@else
|
||||
No release reports have been submitted yet.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Description Modal -->
|
||||
<div id="reportDescriptionModal" class="fixed inset-0 z-50 overflow-y-auto hidden">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<!-- Backdrop -->
|
||||
<div class="report-desc-modal-backdrop fixed inset-0 transition-opacity bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75"></div>
|
||||
|
||||
<!-- Modal Content -->
|
||||
<div class="relative inline-block w-full max-w-lg p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 shadow-xl rounded-lg">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<i class="fas fa-comment-dots text-blue-500 mr-2"></i>Report Details
|
||||
</h3>
|
||||
<button type="button" class="report-desc-modal-close text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Reason</label>
|
||||
<p id="reportDescReason" class="text-gray-900 dark:text-gray-100 font-medium"></p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Reported By</label>
|
||||
<p id="reportDescReporter" class="text-gray-900 dark:text-gray-100"></p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Additional Details</label>
|
||||
<div id="reportDescContent" class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-words max-h-64 overflow-y-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button type="button" class="report-desc-modal-close px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -141,6 +141,12 @@
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<a href="{{ url('/details/' . $result->guid) }}" class="text-blue-600 dark:text-blue-400 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 dark:hover:text-blue-300 font-medium break-words break-all">{{ $result->searchname }}</a>
|
||||
@if(!empty($result->report_count) && $result->report_count > 0)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
|
||||
title="Reported: {{ \App\Models\ReleaseReport::reasonKeysToLabels($result->report_reasons ?? '') }}">
|
||||
<i class="fas fa-flag mr-1"></i> Reported ({{ $result->report_count }})
|
||||
</span>
|
||||
@endif
|
||||
@if(!empty($result->failed_count) && $result->failed_count > 0)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
|
||||
title="{{ $result->failed_count }} user(s) reported download failure">
|
||||
@@ -254,6 +260,7 @@
|
||||
<i class="fa fa-film"></i>
|
||||
</a>
|
||||
@endif
|
||||
<x-report-button :release-id="$result->id" variant="icon" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
@props([
|
||||
'releaseId' => null,
|
||||
'releaseGuid' => null,
|
||||
'size' => 'sm', // sm, md, lg
|
||||
'variant' => 'icon', // icon, button, text
|
||||
])
|
||||
|
||||
@auth
|
||||
<div class="report-button-container inline-block">
|
||||
<!-- Report Button -->
|
||||
@if($variant === 'icon')
|
||||
<button type="button"
|
||||
data-report-release-id="{{ $releaseId }}"
|
||||
class="report-trigger text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 transition p-1"
|
||||
title="Report this release">
|
||||
<i class="fas fa-flag"></i>
|
||||
</button>
|
||||
@elseif($variant === 'button')
|
||||
<button type="button"
|
||||
data-report-release-id="{{ $releaseId }}"
|
||||
class="report-trigger px-3 py-1.5 text-{{ $size }} bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-red-100 dark:hover:bg-red-900 hover:text-red-700 dark:hover:text-red-300 transition inline-flex items-center">
|
||||
<i class="fas fa-flag mr-1.5"></i>
|
||||
<span class="report-label">Report</span>
|
||||
</button>
|
||||
@else
|
||||
<button type="button"
|
||||
data-report-release-id="{{ $releaseId }}"
|
||||
class="report-trigger text-gray-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 text-{{ $size }} transition">
|
||||
<i class="fas fa-flag mr-1"></i>
|
||||
<span class="report-label">Report Issue</span>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- Report Modal -->
|
||||
<div class="report-modal fixed inset-0 z-50 overflow-y-auto hidden" data-release-id="{{ $releaseId }}">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<!-- Backdrop -->
|
||||
<div class="report-modal-backdrop fixed inset-0 transition-opacity bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75"></div>
|
||||
|
||||
<!-- Modal Content -->
|
||||
<div class="relative inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 shadow-xl rounded-lg">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<i class="fas fa-flag text-red-500 mr-2"></i>Report Release
|
||||
</h3>
|
||||
<button type="button" class="report-modal-close text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form class="report-form" data-ajax-form="true">
|
||||
<!-- Reason Select -->
|
||||
<div class="mb-4">
|
||||
<label for="report-reason-{{ $releaseId }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Reason for Report <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="reason"
|
||||
id="report-reason-{{ $releaseId }}"
|
||||
required
|
||||
class="report-reason w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
|
||||
<option value="">Select a reason...</option>
|
||||
<option value="duplicate">Duplicate Release</option>
|
||||
<option value="fake">Fake/Malicious Content</option>
|
||||
<option value="password">Password Protected</option>
|
||||
<option value="incomplete">Incomplete/Corrupted</option>
|
||||
<option value="wrong_category">Wrong Category</option>
|
||||
<option value="spam">Spam/Advertisement</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Description Textarea -->
|
||||
<div class="mb-4">
|
||||
<label for="report-description-{{ $releaseId }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Additional Details (optional)
|
||||
</label>
|
||||
<textarea name="description"
|
||||
id="report-description-{{ $releaseId }}"
|
||||
rows="3"
|
||||
maxlength="1000"
|
||||
placeholder="Provide any additional details that might help us review this report..."
|
||||
class="report-description w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 resize-none"></textarea>
|
||||
<p class="report-char-count text-xs text-gray-500 dark:text-gray-400 mt-1">0/1000 characters</p>
|
||||
</div>
|
||||
|
||||
<!-- Error/Success Messages -->
|
||||
<div class="report-error hidden mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
||||
<p class="text-sm text-red-600 dark:text-red-400"></p>
|
||||
</div>
|
||||
|
||||
<div class="report-success hidden mb-4 p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
|
||||
<p class="text-sm text-green-600 dark:text-green-400"></p>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button type="button"
|
||||
class="report-modal-close px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button"
|
||||
disabled
|
||||
class="report-submit px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700 transition disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center">
|
||||
Submit Report
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
@@ -30,12 +30,21 @@
|
||||
<div class="flex-1">
|
||||
<div class="mb-3">
|
||||
<h2 class="text-xl font-bold text-gray-800 dark:text-gray-200 mb-2 break-words break-all">{{ $release->searchname }}</h2>
|
||||
@if(!empty($failed) && $failed > 0)
|
||||
<div class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-red-100 text-red-800 border border-red-200">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
<span>{{ $failed }} user{{ $failed > 1 ? 's' : '' }} reported download failure</span>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if(!empty($reportCount) && $reportCount > 0)
|
||||
<div class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 border border-orange-200 dark:border-orange-800"
|
||||
title="Reported: {{ $reportReasons ?? 'Unknown' }}">
|
||||
<i class="fas fa-flag mr-2"></i>
|
||||
<span>{{ $reportCount }} report{{ $reportCount > 1 ? 's' : '' }}: {{ $reportReasons ?? 'Unknown' }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if(!empty($failed) && $failed > 0)
|
||||
<div class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-red-100 text-red-800 border border-red-200">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
<span>{{ $failed }} user{{ $failed > 1 ? 's' : '' }} reported download failure</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="{{ url('/getnzb/' . $release->guid) }}" class="download-nzb px-4 py-2 bg-green-600 dark:bg-green-700 text-white rounded-lg hover:bg-green-700 dark:hover:bg-green-800 transition inline-flex items-center">
|
||||
@@ -49,6 +58,7 @@
|
||||
<i class="fas fa-file-alt mr-2"></i> View NFO
|
||||
</button>
|
||||
@endif
|
||||
<x-report-button :release-id="$release->id" variant="button" />
|
||||
@auth
|
||||
@if(auth()->user()->hasRole('Admin') || auth()->user()->hasRole('Moderator'))
|
||||
<a href="{{ route('admin.release-edit', ['id' => $release->guid]) }}" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 transition inline-flex items-center" title="Edit Release">
|
||||
|
||||
@@ -148,6 +148,24 @@
|
||||
<h3 class="text-xl font-bold text-gray-900">{{ $result->title }}</h3>
|
||||
@endif
|
||||
|
||||
@if(!empty($result->grp_release_reports))
|
||||
@php
|
||||
$reportCounts = array_filter(explode(',', $result->grp_release_reports));
|
||||
$totalReports = array_sum($reportCounts);
|
||||
$reportReasonsRaw = !empty($result->grp_release_report_reasons)
|
||||
? implode(',', array_unique(array_filter(preg_split('/[|,]/', $result->grp_release_report_reasons))))
|
||||
: '';
|
||||
$reportReasons = \App\Models\ReleaseReport::reasonKeysToLabels($reportReasonsRaw);
|
||||
@endphp
|
||||
@if($totalReports > 0)
|
||||
<div class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 border border-orange-200 dark:border-orange-800 mt-1"
|
||||
title="Reported: {{ $reportReasons }}">
|
||||
<i class="fas fa-flag mr-1"></i>
|
||||
<span>{{ $totalReports }} report{{ $totalReports > 1 ? 's' : '' }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if(!empty($result->grp_release_failed))
|
||||
@php
|
||||
$failedCounts = array_filter(explode(',', $result->grp_release_failed));
|
||||
@@ -227,6 +245,7 @@
|
||||
$releaseNames = isset($result->grp_release_name) ? explode('#', $result->grp_release_name) : [];
|
||||
$releaseSizes = isset($result->grp_release_size) ? explode(',', $result->grp_release_size) : [];
|
||||
$releaseGuids = isset($result->grp_release_guid) ? explode(',', $result->grp_release_guid) : [];
|
||||
$releaseIds = isset($result->grp_release_id) ? explode(',', $result->grp_release_id) : [];
|
||||
$releasePostDates = isset($result->grp_release_postdate) ? explode(',', $result->grp_release_postdate) : [];
|
||||
$releaseAddDates = isset($result->grp_release_adddate) ? explode(',', $result->grp_release_adddate) : [];
|
||||
$releaseHasPreview = isset($result->grp_haspreview) ? explode(',', $result->grp_haspreview) : [];
|
||||
@@ -240,6 +259,7 @@
|
||||
$releaseNames = array_slice($releaseNames, 0, $maxReleases);
|
||||
$releaseSizes = array_slice($releaseSizes, 0, $maxReleases);
|
||||
$releaseGuids = array_slice($releaseGuids, 0, $maxReleases);
|
||||
$releaseIds = array_slice($releaseIds, 0, $maxReleases);
|
||||
$releasePostDates = array_slice($releasePostDates, 0, $maxReleases);
|
||||
$releaseAddDates = array_slice($releaseAddDates, 0, $maxReleases);
|
||||
$releaseHasPreview = array_slice($releaseHasPreview, 0, $maxReleases);
|
||||
@@ -327,6 +347,9 @@
|
||||
<a href="{{ url('/details/' . $releaseGuids[$index]) }}" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-800 transition">
|
||||
<i class="fas fa-info-circle mr-1"></i> Details
|
||||
</a>
|
||||
@if(isset($releaseIds[$index]))
|
||||
<x-report-button :release-id="(int)$releaseIds[$index]" variant="icon" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
</button>
|
||||
<div id="releases-menu" class="hidden mt-2 ml-6 space-y-1">
|
||||
<a href="{{ url('/admin/release-list') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">Release List</a>
|
||||
<a href="{{ url('/admin/release-reports') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">
|
||||
<i class="fas fa-flag text-red-500 mr-1"></i> Release Reports
|
||||
</a>
|
||||
<a href="{{ url('/admin/failrel-list') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">Failed Releases</a>
|
||||
<a href="{{ url('/admin/category-list') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">Categories</a>
|
||||
</div>
|
||||
|
||||
@@ -210,6 +210,12 @@
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<a href="{{ url('/details/' . $result->guid) }}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium break-words break-all">{{ $result->searchname }}</a>
|
||||
@if(!empty($result->report_count) && $result->report_count > 0)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
|
||||
title="Reported: {{ \App\Models\ReleaseReport::reasonKeysToLabels($result->report_reasons ?? '') }}">
|
||||
<i class="fas fa-flag mr-1"></i> Reported ({{ $result->report_count }})
|
||||
</span>
|
||||
@endif
|
||||
@if(!empty($result->failed_count) && $result->failed_count > 0)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
|
||||
title="{{ $result->failed_count }} user(s) reported download failure">
|
||||
@@ -322,6 +328,7 @@
|
||||
<i class="fa fa-film"></i>
|
||||
</a>
|
||||
@endif
|
||||
<x-report-button :release-id="$result->id" variant="icon" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -341,6 +348,12 @@
|
||||
<a href="{{ url('/details/' . $result->guid) }}" class="text-lg font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 break-words break-all">
|
||||
{{ $result->searchname }}
|
||||
</a>
|
||||
@if(!empty($result->report_count) && $result->report_count > 0)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
|
||||
title="Reported: {{ \App\Models\ReleaseReport::reasonKeysToLabels($result->report_reasons ?? '') }}">
|
||||
<i class="fas fa-flag mr-1"></i> Reported ({{ $result->report_count }})
|
||||
</span>
|
||||
@endif
|
||||
@if(!empty($result->failed_count) && $result->failed_count > 0)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
|
||||
title="{{ $result->failed_count }} user(s) reported download failure">
|
||||
@@ -429,6 +442,7 @@
|
||||
<i class="fa fa-film"></i>
|
||||
</a>
|
||||
@endif
|
||||
<x-report-button :release-id="$result->id" variant="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -154,6 +154,11 @@ Route::middleware('isVerified')->group(function () {
|
||||
Route::post('profile/update-theme', [ProfileController::class, 'updateTheme'])->name('profile.update-theme');
|
||||
Route::match(['GET', 'POST'], 'search', [SearchController::class, 'search'])->name('search');
|
||||
|
||||
// Release Report routes
|
||||
Route::post('release-report', [\App\Http\Controllers\ReleaseReportController::class, 'store'])->name('release-report.store');
|
||||
Route::get('release-report/reasons', [\App\Http\Controllers\ReleaseReportController::class, 'getReasons'])->name('release-report.reasons');
|
||||
Route::get('release-report/check', [\App\Http\Controllers\ReleaseReportController::class, 'checkReported'])->name('release-report.check');
|
||||
|
||||
Route::match(['GET', 'POST'], 'mymovies', [MyMoviesController::class, 'show'])->name('mymovies');
|
||||
Route::match(['GET', 'POST'], 'myshows', [MyShowsController::class, 'show'])->name('myshows');
|
||||
Route::match(['GET', 'POST'], 'myshows/browse', [MyShowsController::class, 'browse'])->name('myshows.browse');
|
||||
@@ -236,6 +241,14 @@ Route::middleware('role:Admin', '2fa')->prefix('admin')->group(function () {
|
||||
Route::match(['GET', 'POST'], 'tmux-edit', [AdminTmuxController::class, 'edit'])->name('admin.tmux-edit');
|
||||
Route::get('release-list', [AdminReleasesController::class, 'index'])->name('admin.release-list');
|
||||
Route::post('release-delete/{id}', [AdminReleasesController::class, 'destroy'])->name('admin.release-delete');
|
||||
|
||||
// Release Reports Management
|
||||
Route::get('release-reports', [\App\Http\Controllers\Admin\AdminReleaseReportController::class, 'index'])->name('admin.release-reports');
|
||||
Route::post('release-reports/{id}/status', [\App\Http\Controllers\Admin\AdminReleaseReportController::class, 'updateStatus'])->name('admin.release-reports.update-status');
|
||||
Route::post('release-reports/{id}/delete-release', [\App\Http\Controllers\Admin\AdminReleaseReportController::class, 'deleteRelease'])->name('admin.release-reports.delete-release');
|
||||
Route::post('release-reports/{id}/dismiss', [\App\Http\Controllers\Admin\AdminReleaseReportController::class, 'dismiss'])->name('admin.release-reports.dismiss');
|
||||
Route::post('release-reports/bulk', [\App\Http\Controllers\Admin\AdminReleaseReportController::class, 'bulkAction'])->name('admin.release-reports.bulk');
|
||||
|
||||
Route::get('show-list', [AdminShowsController::class, 'index'])->name('admin.show-list');
|
||||
Route::match(['GET', 'POST'], 'show-edit', [AdminShowsController::class, 'edit'])->name('admin.show-edit');
|
||||
Route::get('show-remove/{id}', [AdminShowsController::class, 'destroy'])->name('admin.show-remove');
|
||||
|
||||
Reference in New Issue
Block a user