diff --git a/app/Http/Controllers/Admin/AdminAjaxController.php b/app/Http/Controllers/Admin/AdminAjaxController.php
index a0dbc1cc1..f02e0bbec 100644
--- a/app/Http/Controllers/Admin/AdminAjaxController.php
+++ b/app/Http/Controllers/Admin/AdminAjaxController.php
@@ -13,77 +13,96 @@ class AdminAjaxController extends BasePageController
/**
* @throws \Throwable
*/
- public function ajaxAction(Request $request): void
+ public function ajaxAction(Request $request)
{
if ($request->missing('action')) {
- exit();
+ return response()->json(['success' => false, 'message' => 'No action specified'], 400);
}
$settings = ['Settings' => $this->settings];
- switch ($request->input('action')) {
- case 'binary_blacklist_delete':
- $id = (int) $request->input('row_id');
- (new Binaries($settings))->deleteBlacklist($id);
- echo "Blacklist $id deleted.";
- break;
- case 'category_regex_delete':
- $id = (int) $request->input('row_id');
- (new Regexes(['Settings' => $this->settings, 'Table_Name' => 'category_regexes']))->deleteRegex($id);
- echo "Regex $id deleted.";
- break;
+ try {
+ switch ($request->input('action')) {
+ case 'binary_blacklist_delete':
+ $id = (int) $request->input('row_id');
+ (new Binaries($settings))->deleteBlacklist($id);
+ echo "Blacklist $id deleted.";
+ break;
- case 'collection_regex_delete':
- $id = (int) $request->input('row_id');
- (new Regexes(['Settings' => $this->settings, 'Table_Name' => 'collection_regexes']))->deleteRegex($id);
- echo "Regex $id deleted.";
- break;
+ case 'category_regex_delete':
+ $id = (int) $request->input('row_id');
+ (new Regexes(['Settings' => $this->settings, 'Table_Name' => 'category_regexes']))->deleteRegex($id);
+ echo "Regex $id deleted.";
+ break;
- case 'release_naming_regex_delete':
- $id = (int) $request->input('row_id');
- (new Regexes(['Settings' => $this->settings, 'Table_Name' => 'release_naming_regexes']))->deleteRegex($id);
- echo "Regex $id deleted.";
- break;
+ case 'collection_regex_delete':
+ $id = (int) $request->input('row_id');
+ (new Regexes(['Settings' => $this->settings, 'Table_Name' => 'collection_regexes']))->deleteRegex($id);
+ echo "Regex $id deleted.";
+ break;
- case 'group_edit_purge_all':
- UsenetGroup::purge();
- echo 'All groups purged.';
- break;
+ case 'release_naming_regex_delete':
+ $id = (int) $request->input('row_id');
+ (new Regexes(['Settings' => $this->settings, 'Table_Name' => 'release_naming_regexes']))->deleteRegex($id);
+ echo "Regex $id deleted.";
+ break;
- case 'group_edit_reset_all':
- UsenetGroup::resetall();
- echo 'All groups reset.';
- break;
+ case 'group_edit_purge_all':
+ case 'purge_all_groups':
+ UsenetGroup::purge();
+ return response()->json(['success' => true, 'message' => 'All groups purged successfully']);
- case 'group_edit_purge_single':
- $id = (int) $request->input('group_id');
- UsenetGroup::purge($id);
- echo "Group $id purged.";
- break;
+ case 'group_edit_reset_all':
+ case 'reset_all_groups':
+ UsenetGroup::resetall();
+ return response()->json(['success' => true, 'message' => 'All groups reset successfully']);
- case 'group_edit_reset_single':
- $id = (int) $request->input('group_id');
- UsenetGroup::reset($id);
- echo "Group $id reset.";
- break;
+ case 'group_edit_purge_single':
+ case 'purge_group':
+ $id = (int) $request->input('group_id');
+ UsenetGroup::purge($id);
+ return response()->json(['success' => true, 'message' => "Group $id purged successfully"]);
- case 'group_edit_delete_single':
- $id = (int) $request->input('group_id');
- UsenetGroup::deleteGroup($id);
- echo "Group $id deleted.";
- break;
+ case 'group_edit_reset_single':
+ case 'reset_group':
+ $id = (int) $request->input('group_id');
+ UsenetGroup::reset($id);
+ return response()->json(['success' => true, 'message' => "Group $id reset successfully"]);
- case 'toggle_group_active_status':
- print UsenetGroup::updateGroupStatus((int) $request->input('group_id'), 'active', ($request->has('group_status') ? (int) $request->input('group_status') : 0));
- break;
+ case 'group_edit_delete_single':
+ case 'delete_group':
+ $id = (int) $request->input('group_id');
+ UsenetGroup::deleteGroup($id);
+ return response()->json(['success' => true, 'message' => "Group $id deleted successfully"]);
- case 'toggle_group_backfill_status':
- print UsenetGroup::updateGroupStatus(
- (int) $request->input('group_id'),
- 'backfill',
- ($request->has('backfill_status') ? (int) $request->input('backfill_status') : 0)
- );
- break;
+ case 'toggle_group_active_status':
+ $groupId = (int) $request->input('group_id');
+ $status = $request->has('group_status') ? (int) $request->input('group_status') : 0;
+ $message = UsenetGroup::updateGroupStatus($groupId, 'active', $status);
+ return response()->json([
+ 'success' => true,
+ 'message' => $message,
+ 'newStatus' => $status
+ ]);
+
+ case 'toggle_group_backfill_status':
+ case 'toggle_group_backfill':
+ $groupId = (int) $request->input('group_id');
+ $status = $request->has('backfill_status')
+ ? (int) $request->input('backfill_status')
+ : ($request->has('backfill') ? (int) $request->input('backfill') : 0);
+ $message = UsenetGroup::updateGroupStatus($groupId, 'backfill', $status);
+ return response()->json([
+ 'success' => true,
+ 'message' => $message,
+ 'newStatus' => $status
+ ]);
+
+ default:
+ return response()->json(['success' => false, 'message' => 'Unknown action'], 400);
+ }
+ } catch (\Exception $e) {
+ return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
}
diff --git a/resources/js/csp-safe.js b/resources/js/csp-safe.js
index c42aba512..2ff84a6ad 100644
--- a/resources/js/csp-safe.js
+++ b/resources/js/csp-safe.js
@@ -3144,8 +3144,321 @@ function initializeSystemMetricsCharts() {
}
function initAdminGroups() {
- // Placeholder for admin groups functionality
- // Group management is handled in event delegation
+ // Get AJAX URL and CSRF token from page
+ const container = document.querySelector('[data-ajax-url]');
+ const ajaxUrl = container ? container.dataset.ajaxUrl : '/admin/ajax';
+ const csrfToken = container ? container.dataset.csrfToken : document.querySelector('meta[name="csrf-token"]')?.content;
+
+ // Toggle group active/inactive status
+ window.ajax_group_status = function(id, status) {
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'toggle_group_active_status',
+ group_id: id,
+ group_status: status
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ const groupCell = document.getElementById('group-' + id);
+ if (groupCell && data.newStatus !== undefined) {
+ const isActive = data.newStatus == 1;
+ groupCell.innerHTML = isActive
+ ? ``
+ : ``;
+ }
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Group status updated', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error updating group status', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error updating group status', 'error');
+ }
+ });
+ };
+
+ // Toggle backfill status
+ window.ajax_backfill_status = function(id, status) {
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'toggle_group_backfill',
+ group_id: id,
+ backfill: status
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ const backfillCell = document.getElementById('backfill-' + id);
+ if (backfillCell && data.newStatus !== undefined) {
+ const isEnabled = data.newStatus == 1;
+ backfillCell.innerHTML = isEnabled
+ ? ``
+ : ``;
+ }
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Backfill status updated', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error updating backfill status', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error updating backfill status', 'error');
+ }
+ });
+ };
+
+ // Reset group
+ window.ajax_group_reset = function(id) {
+ if (!confirm('Are you sure you want to reset this group? This will reset the article pointers back to the current state.')) {
+ return;
+ }
+
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'reset_group',
+ group_id: id
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Group reset successfully', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error resetting group', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error resetting group', 'error');
+ }
+ });
+ };
+
+ // Delete group
+ window.confirmGroupDelete = function(id) {
+ if (!confirm('Are you sure you want to delete this group? This action cannot be undone.')) {
+ return;
+ }
+
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'delete_group',
+ group_id: id
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ const row = document.getElementById('grouprow-' + id);
+ if (row) {
+ row.style.transition = 'opacity 0.3s';
+ row.style.opacity = '0';
+ setTimeout(() => row.remove(), 300);
+ }
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Group deleted successfully', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error deleting group', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error deleting group', 'error');
+ }
+ });
+ };
+
+ // Purge group
+ window.confirmGroupPurge = function(id) {
+ if (!confirm('Are you sure you want to purge this group? This will delete all releases and binaries for this group. This action cannot be undone!')) {
+ return;
+ }
+
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'purge_group',
+ group_id: id
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Group purged successfully', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error purging group', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error purging group', 'error');
+ }
+ });
+ };
+
+ // Reset all groups
+ window.ajax_group_reset_all = function() {
+ hideResetAllModal();
+
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'reset_all_groups'
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'All groups reset successfully', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error resetting all groups', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error resetting all groups', 'error');
+ }
+ });
+ };
+
+ // Purge all groups
+ window.ajax_group_purge_all = function() {
+ hidePurgeAllModal();
+
+ fetch(ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-TOKEN': csrfToken,
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ body: new URLSearchParams({
+ action: 'purge_all_groups'
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'All groups purged successfully', 'success');
+ }
+ } else {
+ if (typeof showToast === 'function') {
+ showToast(data.message || 'Error purging all groups', 'error');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ if (typeof showToast === 'function') {
+ showToast('Error purging all groups', 'error');
+ }
+ });
+ };
+
+ // Modal helpers
+ window.showResetAllModal = function() {
+ const modal = document.getElementById('resetAllModal');
+ if (modal) {
+ modal.classList.remove('hidden');
+ }
+ };
+
+ window.hideResetAllModal = function() {
+ const modal = document.getElementById('resetAllModal');
+ if (modal) {
+ modal.classList.add('hidden');
+ }
+ };
+
+ window.showPurgeAllModal = function() {
+ const modal = document.getElementById('purgeAllModal');
+ if (modal) {
+ modal.classList.remove('hidden');
+ }
+ };
+
+ window.hidePurgeAllModal = function() {
+ const modal = document.getElementById('purgeAllModal');
+ if (modal) {
+ modal.classList.add('hidden');
+ }
+ };
}
// TinyMCE Initialization for Admin Content Pages