From bb6207e29eb77732e0f63be5461f1a0607f15592 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Mon, 6 Mar 2023 17:33:03 +0100
Subject: [PATCH] [TASK] Build submodules from scheduler module parts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change splits up
* Edit/List/Add view of Scheduler
* Check Setup
* Available Tasks

in each a separate controller + a custom third-level
module.

This way, it is then possible to separate the
main scheduler module into custom actions (via subroutes)
making the code a tiny bit more manageable.

The previous "system_txschedulerM1" module name is now
aliased to "scheduler_manage" (third-level), as the main
scheduler module is now solely called "scheduler". The old
name is kept for backwards-compatibility reasons.

A note for historical fans: "M1" stands for "module 1" within
the "tx_scheduler" extension.

Resolves: #100104
Releases: main
Change-Id: I2ba7c76899853864de42a11f2dc45c40ce699c91
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/78038
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Jochen <rothjochen@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Jochen <rothjochen@gmail.com>
Tested-by: Benni Mack <benni@typo3.org>
---
 .../Sources/TypeScript/scheduler/scheduler.ts |   6 +-
 .../Application/Frontend/SitemapXmlCest.php   |  33 +---
 .../Application/Scheduler/TasksCest.php       |  26 +--
 .../AvailableSchedulerTasksController.php     |  99 +++++++++++
 .../Controller/SchedulerModuleController.php  | 166 ++----------------
 .../SchedulerSetupCheckController.php         | 142 +++++++++++++++
 .../SystemInformation/ToolbarItemProvider.php |   8 +-
 .../Configuration/Backend/Modules.php         |  39 +++-
 .../scheduler/Configuration/Services.yaml     |   3 -
 .../Resources/Private/Partials/TaskList.html  |  10 +-
 .../Private/Templates/InfoScreen.html         |   4 +-
 .../Resources/Public/JavaScript/scheduler.js  |   2 +-
 12 files changed, 324 insertions(+), 214 deletions(-)
 create mode 100644 typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php
 create mode 100644 typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php

diff --git a/Build/Sources/TypeScript/scheduler/scheduler.ts b/Build/Sources/TypeScript/scheduler/scheduler.ts
index 5dc3f32b4e49..15851b8b90c1 100644
--- a/Build/Sources/TypeScript/scheduler/scheduler.ts
+++ b/Build/Sources/TypeScript/scheduler/scheduler.ts
@@ -55,15 +55,15 @@ class Scheduler {
   private static storeCollapseState(table: string, isCollapsed: boolean): void {
     let storedModuleData = {};
 
-    if (PersistentStorage.isset('moduleData.system_txschedulerM1')) {
-      storedModuleData = PersistentStorage.get('moduleData.system_txschedulerM1');
+    if (PersistentStorage.isset('moduleData.scheduler_manage')) {
+      storedModuleData = PersistentStorage.get('moduleData.scheduler_manage');
     }
 
     const collapseConfig: any = {};
     collapseConfig[table] = isCollapsed ? 1 : 0;
 
     $.extend(storedModuleData, collapseConfig);
-    PersistentStorage.set('moduleData.system_txschedulerM1', storedModuleData);
+    PersistentStorage.set('moduleData.scheduler_manage', storedModuleData);
   }
 
   constructor() {
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php
index bdb856e0e6b7..77e80e86cfe1 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php
@@ -35,18 +35,11 @@ final class SitemapXmlCest
         $I->waitForElement('#typo3-pagetree-tree .nodes .node', 5);
         $pageTree->openPath(['styleguide frontend demo']);
         $I->switchToContentFrame();
-        $I->click('.t3js-module-docheader-bar a[title="View webpage"]');
-        $I->executeInSelenium(function (RemoteWebDriver $webdriver) {
-            $handles = $webdriver->getWindowHandles();
-            $lastWindow = end($handles);
-            $webdriver->switchTo()->window($lastWindow);
-        });
-
-        // Get current url
-        $url = $this->getCurrentURL($I);
-
+        $I->waitForElementNotVisible('#nprogress');
+        $dataDispatchArgs = $I->grabAttributeFrom('.module-docheader-bar-column-left a:first-child', 'data-dispatch-args');
+        $url = json_decode($dataDispatchArgs, false, 512, JSON_THROW_ON_ERROR);
         // Add Sitemap parameter to URL
-        $I->amOnUrl($url . '?type=1533906435');
+        $I->amOnPage(str_replace('/typo3temp/var/tests/acceptance', '', $url[0]) . '?type=1533906435');
     }
 
     private function sitemapDataProvider(): array
@@ -97,24 +90,6 @@ final class SitemapXmlCest
         $I->assertIsNumeric($priority);
     }
 
-    private function getCurrentURL(ApplicationTester $I, int $attempt = 1): string
-    {
-        $url = $I->executeInSelenium(function (RemoteWebDriver $webdriver) {
-            return $webdriver->getCurrentURL();
-        });
-
-        if ($attempt > 4) {
-            return $url ?? '';
-        }
-
-        if (!$url || str_contains($url, 'about:blank')) {
-            $I->wait(0.5);
-            $url = $this->getCurrentURL($I, $attempt + 1);
-        }
-
-        return $url;
-    }
-
     /**
      * Find text by given slug part
      */
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php
index bbf4b4afca7c..cc3a9ef6b30c 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php
@@ -28,9 +28,9 @@ final class TasksCest
     public function _before(ApplicationTester $I): void
     {
         $I->useExistingSession('admin');
-        $I->scrollTo('[data-modulemenu-identifier="system_txschedulerM1"]');
-        $I->see('Scheduler', '[data-modulemenu-identifier="system_txschedulerM1"]');
-        $I->click('[data-modulemenu-identifier="system_txschedulerM1"]');
+        $I->scrollTo('[data-modulemenu-identifier="scheduler"]');
+        $I->see('Scheduler', '[data-modulemenu-identifier="scheduler"]');
+        $I->click('[data-modulemenu-identifier="scheduler"]');
         $I->switchToContentFrame();
     }
 
@@ -61,7 +61,7 @@ final class TasksCest
         // run the task
         $I->click('button[name="execute"]');
         $I->waitForText('Task "System Status Update (reports)" with uid "1" has been executed.');
-        $I->seeElement('[data-module-name="system_txschedulerM1"] .disabled');
+        $I->seeElement('[data-module-name="scheduler_manage"] .disabled');
         $I->see('disabled');
     }
 
@@ -86,7 +86,7 @@ final class TasksCest
     {
         $I->wantTo('See a enable button for a task');
         $I->click('//button[contains(@title, "Enable")]', '#tx_scheduler_form');
-        $I->dontSeeElement('[data-module-name="system_txschedulerM1"] .disabled');
+        $I->dontSeeElement('[data-module-name="scheduler_manage"] .disabled');
         $I->dontSee('disabled');
         $I->wantTo('See a disable button for a task');
         // Give tooltips some time to fully init
@@ -94,8 +94,8 @@ final class TasksCest
         $I->moveMouseOver('//button[contains(@title, "Disable")]');
         $I->wait(1);
         $I->click('//button[contains(@title, "Disable")]');
-        $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]');
-        $I->seeElement('[data-module-name="system_txschedulerM1"] .disabled');
+        $I->waitForElementVisible('[data-module-name="scheduler_manage"]');
+        $I->seeElement('[data-module-name="scheduler_manage"] .disabled');
         $I->see('disabled');
     }
 
@@ -125,18 +125,18 @@ final class TasksCest
 
     public function canSwitchToSetupCheck(ApplicationTester $I): void
     {
-        $I->selectOption('select[name=SchedulerJumpMenu]', 'Scheduler setup check');
-        $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]');
+        $I->selectOption('select[name=moduleMenu]', 'Scheduler setup check');
+        $I->waitForElementVisible('[data-module-name="scheduler_setupcheck"]');
         $I->see('Scheduler setup check');
         $I->see('This screen checks if the requisites for running the Scheduler as a cron job are fulfilled');
     }
 
     public function canSwitchToInformation(ApplicationTester $I): void
     {
-        $I->selectOption('select[name=SchedulerJumpMenu]', 'Available scheduler tasks');
-        $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]');
+        $I->selectOption('select[name=moduleMenu]', 'Available scheduler tasks');
+        $I->waitForElementVisible('[data-module-name="scheduler_availabletasks"]');
         $I->see('Available scheduler tasks');
-        $I->canSeeNumberOfElements('[data-module-name="system_txschedulerM1"] table tbody tr', [1, 10000]);
+        $I->canSeeNumberOfElements('[data-module-name="scheduler_availabletasks"] table tbody tr', [1, 10000]);
     }
 
     public function canCreateNewTaskGroupFromEditForm(ApplicationTester $I): void
@@ -164,7 +164,7 @@ final class TasksCest
         $I->click('button[value="save"]');
         $I->waitForElementNotVisible('#t3js-ui-block');
         $I->click('a[title="Close"]');
-        $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]');
+        $I->waitForElementVisible('[data-module-name="scheduler_manage"]');
 
         $I->canSee('new task group', '.panel-heading');
     }
diff --git a/typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php b/typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php
new file mode 100644
index 000000000000..6a5f01bd4f9e
--- /dev/null
+++ b/typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php
@@ -0,0 +1,99 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Scheduler\Controller;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Attribute\Controller as BackendController;
+use TYPO3\CMS\Backend\Template\ModuleTemplate;
+use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
+use TYPO3\CMS\Core\Localization\LanguageService;
+
+/**
+ * Render information about available task classes.
+ * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
+ */
+#[BackendController]
+class AvailableSchedulerTasksController
+{
+    public function __construct(
+        protected readonly ModuleTemplateFactory $moduleTemplateFactory,
+    ) {
+    }
+
+    public function handle(ServerRequestInterface $request): ResponseInterface
+    {
+        $languageService = $this->getLanguageService();
+        $view = $this->moduleTemplateFactory->create($request);
+        $view->assign('dateFormat', [
+            'day' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?? 'd-m-y',
+            'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i',
+        ]);
+
+        $view->assign('registeredClasses', $this->getRegisteredClasses());
+        $view->setTitle(
+            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
+            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info')
+        );
+        $view->makeDocHeaderModuleMenu();
+        $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info'));
+        return $view->renderResponse('InfoScreen');
+    }
+
+    protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name): void
+    {
+        $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
+        $shortcutButton = $buttonBar->makeShortcutButton()
+            ->setRouteIdentifier('scheduler_availabletasks')
+            ->setDisplayName($name);
+        $buttonBar->addButton($shortcutButton);
+    }
+
+    /**
+     * This method fetches a list of all classes that have been registered with the Scheduler
+     * For each item the following information is provided, as an associative array:
+     *
+     * ['extension'] => Key of the extension which provides the class
+     * ['filename'] => Path to the file containing the class
+     * ['title'] => String (possibly localized) containing a human-readable name for the class
+     * ['provider'] => Name of class that implements the interface for additional fields, if necessary
+     *
+     * The name of the class itself is used as the key of the list array
+     */
+    protected function getRegisteredClasses(): array
+    {
+        $languageService = $this->getLanguageService();
+        $list = [];
+        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] ?? [] as $class => $registrationInformation) {
+            $title = isset($registrationInformation['title']) ? $languageService->sL($registrationInformation['title']) : '';
+            $description = isset($registrationInformation['description']) ? $languageService->sL($registrationInformation['description']) : '';
+            $list[$class] = [
+                'extension' => $registrationInformation['extension'],
+                'title' => $title,
+                'description' => $description,
+                'provider' => $registrationInformation['additionalFields'] ?? '',
+            ];
+        }
+        return $list;
+    }
+
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php
index 9fdb694740e8..30fd674de027 100644
--- a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php
+++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Scheduler\Controller;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Attribute\Controller as BackendController;
 use TYPO3\CMS\Backend\Module\ModuleData;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
@@ -26,19 +27,16 @@ use TYPO3\CMS\Backend\Template\ModuleTemplate;
 use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Context\Context;
-use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\SysLog\Action\Database as SystemLogDatabaseAction;
 use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
 use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
 use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
 use TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface;
 use TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand;
 use TYPO3\CMS\Scheduler\Exception\InvalidDateException;
@@ -54,6 +52,7 @@ use TYPO3\CMS\Scheduler\Task\TaskSerializer;
  *
  * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
  */
+#[BackendController]
 class SchedulerModuleController
 {
     protected Action $currentAction;
@@ -71,10 +70,9 @@ class SchedulerModuleController
     /**
      * Entry dispatcher method.
      *
-     * There are three arguments involved regarding main module routing:
-     * * 'submodule': Third level module selection - "scheduler" (list, add, edit), "info", "check"
-     * * 'action': Sub module "scheduler" only: add, edit, delete, toggleHidden, ...
-     * * 'CMD': Sub module "scheduler" only. "save", "close", "new" when adding / editing a task.
+     * There are two arguments involved regarding main module routing:
+     * * 'action': add, edit, delete, toggleHidden, ...
+     * * 'CMD': "save", "close", "new" when adding / editing a task.
      *          A better naming would be "nextAction", but the split button ModuleTemplate and
      *          DocumentSaveActions.ts can not cope with a renaming here and need "CMD".
      */
@@ -89,21 +87,8 @@ class SchedulerModuleController
             'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i',
         ]);
 
-        // See if action from main module drop down is given, else fetch from user data and update if needed.
         $backendUser = $this->getBackendUser();
         $moduleData = $request->getAttribute('moduleData');
-        if ($moduleData->clean('subModule', ['scheduler', 'info', 'check'])) {
-            $backendUser->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray());
-        }
-        $requestedSubModule = (string)$moduleData->get('subModule');
-
-        // 'info' and 'check' submodules have no other action and can be rendered directly.
-        if ($requestedSubModule === 'info') {
-            return $this->renderInfoView($view);
-        }
-        if ($requestedSubModule === 'check') {
-            return $this->renderCheckView($view);
-        }
 
         // Simple actions from list view.
         if (!empty($parsedBody['action']['toggleHidden'])) {
@@ -193,87 +178,6 @@ class SchedulerModuleController
         return $this->currentAction;
     }
 
-    /**
-     * Render 'Setup Check' view.
-     */
-    protected function renderCheckView(ModuleTemplate $view): ResponseInterface
-    {
-        $languageService = $this->getLanguageService();
-
-        // Display information about last automated run, as stored in the system registry.
-        $registry = GeneralUtility::makeInstance(Registry::class);
-        $lastRun = $registry->get('tx_scheduler', 'lastRun');
-        $lastRunMessageLabel = 'msg.noLastRun';
-        $lastRunMessageLabelArguments = [];
-        $lastRunSeverity = InfoboxViewHelper::STATE_WARNING;
-        if (is_array($lastRun)) {
-            if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) {
-                $lastRunMessageLabel = 'msg.incompleteLastRun';
-                $lastRunSeverity = InfoboxViewHelper::STATE_WARNING;
-            } else {
-                $lastRunMessageLabelArguments = [
-                    $lastRun['type'] === 'manual'
-                        ? $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.manually')
-                        : $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.automatically'),
-                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']),
-                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']),
-                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']),
-                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']),
-                ];
-                $lastRunMessageLabel = 'msg.lastRun';
-                $lastRunSeverity = InfoboxViewHelper::STATE_INFO;
-            }
-        }
-
-        // Information about cli script.
-        $script = $this->determineExecutablePath();
-        $isExecutableMessageLabel = 'msg.cliScriptNotExecutable';
-        $isExecutableSeverity = InfoboxViewHelper::STATE_ERROR;
-        $composerMode = !$script && Environment::isComposerMode();
-        if (!$composerMode) {
-            // Check if CLI script is executable or not. Skip this check if running Windows since executable detection
-            // is not reliable on this platform, the script will always appear as *not* executable.
-            $isExecutable = Environment::isWindows() ? true : ($script && is_executable($script));
-            if ($isExecutable) {
-                $isExecutableMessageLabel = 'msg.cliScriptExecutable';
-                $isExecutableSeverity = InfoboxViewHelper::STATE_OK;
-            }
-        }
-
-        $view->assignMultiple([
-            'composerMode' => $composerMode,
-            'script' => $script,
-            'lastRunMessageLabel' => $lastRunMessageLabel,
-            'lastRunMessageLabelArguments' => $lastRunMessageLabelArguments,
-            'lastRunSeverity' => $lastRunSeverity,
-            'isExecutableMessageLabel' => $isExecutableMessageLabel,
-            'isExecutableSeverity' => $isExecutableSeverity,
-        ]);
-        $view->setTitle(
-            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
-            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check')
-        );
-        $this->addDocHeaderModuleDropDown($view, 'check');
-        $this->addDocHeaderShortcutButton($view, 'check', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check'));
-        return $view->renderResponse('CheckScreen');
-    }
-
-    /**
-     * Render information about available task classes.
-     */
-    protected function renderInfoView(ModuleTemplate $view): ResponseInterface
-    {
-        $languageService = $this->getLanguageService();
-        $view->assign('registeredClasses', $this->getRegisteredClasses());
-        $view->setTitle(
-            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
-            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info')
-        );
-        $this->addDocHeaderModuleDropDown($view, 'info');
-        $this->addDocHeaderShortcutButton($view, 'info', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info'));
-        return $view->renderResponse('InfoScreen');
-    }
-
     /**
      * Set a task to deleted.
      */
@@ -445,13 +349,13 @@ class SchedulerModuleController
             // Adding a group in edit view switches to formEngine. returnUrl is needed to go back to edit view on group record close.
             'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
         ]);
-        $this->addDocHeaderModuleDropDown($view, 'scheduler');
+        $view->makeDocHeaderModuleMenu();
         $this->addDocHeaderCloseAndSaveButtons($view);
         $view->setTitle(
             $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
             $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add')
         );
-        $this->addDocHeaderShortcutButton($view, 'scheduler', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'), 'add');
+        $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'), 'add');
         return $view->renderResponse('AddTaskForm');
     }
 
@@ -544,7 +448,7 @@ class SchedulerModuleController
             // Adding a group in edit view switches to formEngine. returnUrl is needed to go back to edit view on group record close.
             'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
         ]);
-        $this->addDocHeaderModuleDropDown($view, 'scheduler');
+        $view->makeDocHeaderModuleMenu();
         $this->addDocHeaderCloseAndSaveButtons($view);
         $view->setTitle(
             $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
@@ -554,7 +458,6 @@ class SchedulerModuleController
         $this->addDocHeaderDeleteButton($view, $taskUid);
         $this->addDocHeaderShortcutButton(
             $view,
-            'scheduler',
             sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.edit'), $taskName),
             'edit',
             $taskUid
@@ -740,12 +643,12 @@ class SchedulerModuleController
             $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
             $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler')
         );
-        $this->addDocHeaderModuleDropDown($view, 'scheduler');
+        $view->makeDocHeaderModuleMenu();
         $this->addDocHeaderReloadButton($view);
         if (!empty($registeredClasses)) {
             $this->addDocHeaderAddButton($view);
         }
-        $this->addDocHeaderShortcutButton($view, 'scheduler', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler'));
+        $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler'));
         return $view->renderResponse('ListTasks');
     }
 
@@ -983,23 +886,6 @@ class SchedulerModuleController
         return $currentAdditionalFields;
     }
 
-    protected function addDocHeaderModuleDropDown(ModuleTemplate $moduleTemplate, string $activeEntry): void
-    {
-        $languageService = $this->getLanguageService();
-        $menu = $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
-        $menu->setIdentifier('SchedulerJumpMenu');
-        foreach (['scheduler', 'check', 'info'] as $entry) {
-            $item = $menu->makeMenuItem()
-                ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1', ['subModule' => $entry]))
-                ->setTitle($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.' . $entry));
-            if ($entry === $activeEntry) {
-                $item->setActive(true);
-            }
-            $menu->addMenuItem($item);
-        }
-        $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
-    }
-
     protected function addDocHeaderReloadButton(ModuleTemplate $moduleTemplate): void
     {
         $languageService = $this->getLanguageService();
@@ -1007,7 +893,7 @@ class SchedulerModuleController
         $reloadButton = $buttonBar->makeLinkButton()
             ->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
             ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL))
-            ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1'));
+            ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage'));
         $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
     }
 
@@ -1019,7 +905,7 @@ class SchedulerModuleController
             ->setTitle($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'))
             ->setShowLabelText(true)
             ->setIcon($this->iconFactory->getIcon('actions-plus', Icon::SIZE_SMALL))
-            ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1', ['action' => 'add']));
+            ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage', ['action' => 'add']));
         $buttonBar->addButton($addButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
     }
 
@@ -1031,7 +917,7 @@ class SchedulerModuleController
             ->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:close'))
             ->setIcon($this->iconFactory->getIcon('actions-close', Icon::SIZE_SMALL))
             ->setShowLabelText(true)
-            ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1'));
+            ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage'));
         $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
         $saveAndCloseButton = $buttonBar->makeInputButton()
             ->setName('CMD')
@@ -1070,7 +956,7 @@ class SchedulerModuleController
         $languageService = $this->getLanguageService();
         $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
         $deleteButton = $buttonBar->makeLinkButton()
-            ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1', ['action' => ['delete' => $taskUid]]))
+            ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage', ['action' => ['delete' => $taskUid]]))
             ->setClasses('t3js-modal-trigger')
             ->setDataAttributes([
                 'severity' => 'warning',
@@ -1084,10 +970,10 @@ class SchedulerModuleController
         $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 6);
     }
 
-    protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $moduleMenuIdentifier, string $name, string $action = '', int $taskUid = 0): void
+    protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name, string $action = '', int $taskUid = 0): void
     {
         $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
-        $shortcutArguments = ['subModule' => $moduleMenuIdentifier];
+        $shortcutArguments = [];
         if ($action) {
             $shortcutArguments['action'] = $action;
         }
@@ -1095,30 +981,12 @@ class SchedulerModuleController
             $shortcutArguments['uid'] = $taskUid;
         }
         $shortcutButton = $buttonBar->makeShortcutButton()
-            ->setRouteIdentifier('system_txschedulerM1')
+            ->setRouteIdentifier('scheduler_manage')
             ->setDisplayName($name)
             ->setArguments($shortcutArguments);
         $buttonBar->addButton($shortcutButton);
     }
 
-    private function determineExecutablePath(): ?string
-    {
-        if (!Environment::isComposerMode()) {
-            return GeneralUtility::getFileAbsFileName('EXT:core/bin/typo3');
-        }
-        $composerJsonFile = getenv('TYPO3_PATH_COMPOSER_ROOT') . '/composer.json';
-        if (!file_exists($composerJsonFile) || !($jsonContent = file_get_contents($composerJsonFile))) {
-            return null;
-        }
-        $jsonConfig = @json_decode($jsonContent, true);
-        if (empty($jsonConfig) || !is_array($jsonConfig)) {
-            return null;
-        }
-        $vendorDir = trim($jsonConfig['config']['vendor-dir'] ?? 'vendor', '/');
-        $binDir = trim($jsonConfig['config']['bin-dir'] ?? $vendorDir . '/bin', '/');
-        return sprintf('%s/%s/typo3', getenv('TYPO3_PATH_COMPOSER_ROOT'), $binDir);
-    }
-
     protected function getHumanReadableTaskName(AbstractTask $task): string
     {
         $class = get_class($task);
diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php
new file mode 100644
index 000000000000..146d5fa18e97
--- /dev/null
+++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php
@@ -0,0 +1,142 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Scheduler\Controller;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Backend\Attribute\Controller as BackendController;
+use TYPO3\CMS\Backend\Template\ModuleTemplate;
+use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Registry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
+
+/**
+ * Render 'Setup Check' view.
+ * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
+ */
+#[BackendController]
+class SchedulerSetupCheckController implements RequestHandlerInterface
+{
+    public function __construct(
+        protected readonly Registry $registry,
+        protected readonly ModuleTemplateFactory $moduleTemplateFactory,
+    ) {
+    }
+
+    public function handle(ServerRequestInterface $request): ResponseInterface
+    {
+        $languageService = $this->getLanguageService();
+        $view = $this->moduleTemplateFactory->create($request);
+        $view->assign('dateFormat', [
+            'day' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?? 'd-m-y',
+            'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i',
+        ]);
+
+        // Display information about last automated run, as stored in the system registry.
+        $lastRun = $this->registry->get('tx_scheduler', 'lastRun');
+        $lastRunMessageLabel = 'msg.noLastRun';
+        $lastRunMessageLabelArguments = [];
+        $lastRunSeverity = InfoboxViewHelper::STATE_WARNING;
+        if (is_array($lastRun)) {
+            if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) {
+                $lastRunMessageLabel = 'msg.incompleteLastRun';
+                $lastRunSeverity = InfoboxViewHelper::STATE_WARNING;
+            } else {
+                $lastRunMessageLabelArguments = [
+                    $lastRun['type'] === 'manual'
+                        ? $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.manually')
+                        : $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.automatically'),
+                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']),
+                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']),
+                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']),
+                    date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']),
+                ];
+                $lastRunMessageLabel = 'msg.lastRun';
+                $lastRunSeverity = InfoboxViewHelper::STATE_INFO;
+            }
+        }
+
+        // Information about cli script.
+        $script = $this->determineExecutablePath();
+        $isExecutableMessageLabel = 'msg.cliScriptNotExecutable';
+        $isExecutableSeverity = InfoboxViewHelper::STATE_ERROR;
+        $composerMode = !$script && Environment::isComposerMode();
+        if (!$composerMode) {
+            // Check if CLI script is executable or not. Skip this check if running Windows since executable detection
+            // is not reliable on this platform, the script will always appear as *not* executable.
+            $isExecutable = Environment::isWindows() ? true : ($script && is_executable($script));
+            if ($isExecutable) {
+                $isExecutableMessageLabel = 'msg.cliScriptExecutable';
+                $isExecutableSeverity = InfoboxViewHelper::STATE_OK;
+            }
+        }
+
+        $view->assignMultiple([
+            'composerMode' => $composerMode,
+            'script' => $script,
+            'lastRunMessageLabel' => $lastRunMessageLabel,
+            'lastRunMessageLabelArguments' => $lastRunMessageLabelArguments,
+            'lastRunSeverity' => $lastRunSeverity,
+            'isExecutableMessageLabel' => $isExecutableMessageLabel,
+            'isExecutableSeverity' => $isExecutableSeverity,
+        ]);
+        $view->setTitle(
+            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
+            $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check')
+        );
+        $view->makeDocHeaderModuleMenu();
+        $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check'));
+        return $view->renderResponse('CheckScreen');
+    }
+
+    protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name): void
+    {
+        $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
+        $shortcutButton = $buttonBar->makeShortcutButton()
+            ->setRouteIdentifier('scheduler_availabletasks')
+            ->setDisplayName($name);
+        $buttonBar->addButton($shortcutButton);
+    }
+
+    private function determineExecutablePath(): ?string
+    {
+        if (!Environment::isComposerMode()) {
+            return GeneralUtility::getFileAbsFileName('EXT:core/bin/typo3');
+        }
+        $composerJsonFile = getenv('TYPO3_PATH_COMPOSER_ROOT') . '/composer.json';
+        if (!file_exists($composerJsonFile) || !($jsonContent = file_get_contents($composerJsonFile))) {
+            return null;
+        }
+        $jsonConfig = @json_decode($jsonContent, true);
+        if (empty($jsonConfig) || !is_array($jsonConfig)) {
+            return null;
+        }
+        $vendorDir = trim($jsonConfig['config']['vendor-dir'] ?? 'vendor', '/');
+        $binDir = trim($jsonConfig['config']['bin-dir'] ?? $vendorDir . '/bin', '/');
+        return sprintf('%s/%s/typo3', getenv('TYPO3_PATH_COMPOSER_ROOT'), $binDir);
+    }
+
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php b/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php
index c1a9761ffe85..8913cfa3a4c7 100644
--- a/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php
+++ b/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php
@@ -54,21 +54,19 @@ final class ToolbarItemProvider
             return;
         }
         $languageService = $this->getLanguageService();
-        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
 
         if (!$this->schedulerWasExecuted()) {
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
             // Display system message if the Scheduler has never yet run
-            $moduleIdentifier = 'system_txschedulerM1';
-            $moduleParams = ['subModule' => 'check'];
+            $moduleIdentifier = 'scheduler_setupcheck';
             $systemInformationToolbarItem->addSystemMessage(
                 sprintf(
                     $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:systemmessage.noLastRun'),
-                    (string)$uriBuilder->buildUriFromRoute($moduleIdentifier, $moduleParams)
+                    (string)$uriBuilder->buildUriFromRoute($moduleIdentifier)
                 ),
                 InformationStatus::STATUS_WARNING,
                 1,
                 $moduleIdentifier,
-                http_build_query($moduleParams)
             );
         } else {
             // Display information about the last Scheduler execution
diff --git a/typo3/sysext/scheduler/Configuration/Backend/Modules.php b/typo3/sysext/scheduler/Configuration/Backend/Modules.php
index 016abc5e90f1..0c504fd0d214 100644
--- a/typo3/sysext/scheduler/Configuration/Backend/Modules.php
+++ b/typo3/sysext/scheduler/Configuration/Backend/Modules.php
@@ -6,10 +6,10 @@ use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
  * Definitions for modules provided by EXT:scheduler
  */
 return [
-    'system_txschedulerM1' => [
+    'scheduler' => [
         'parent' => 'system',
         'access' => 'admin',
-        'path' => '/module/system/scheduler',
+        'path' => '/module/scheduler',
         'iconIdentifier' => 'module-scheduler',
         'labels' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf',
         'routes' => [
@@ -17,8 +17,39 @@ return [
                 'target' => SchedulerModuleController::class . '::handleRequest',
             ],
         ],
-        'moduleData' => [
-            'subModule' => 'scheduler',
+    ],
+    'scheduler_manage' => [
+        'parent' => 'scheduler',
+        'access' => 'inherit',
+        'path' => '/module/scheduler/manage',
+        'labels' => ['title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler'],
+        'routes' => [
+            '_default' => [
+                'target' => SchedulerModuleController::class . '::handleRequest',
+            ],
+        ],
+        'aliases' => ['system_txschedulerM1'],
+    ],
+    'scheduler_availabletasks' => [
+        'parent' => 'scheduler',
+        'access' => 'inherit',
+        'path' => '/module/scheduler/available-tasks',
+        'labels' => ['title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info'],
+        'routes' => [
+            '_default' => [
+                'target' => \TYPO3\CMS\Scheduler\Controller\AvailableSchedulerTasksController::class . '::handle',
+            ],
+        ],
+    ],
+    'scheduler_setupcheck' => [
+        'parent' => 'scheduler',
+        'access' => 'inherit',
+        'path' => '/module/scheduler/check-setup',
+        'labels' => ['title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check'],
+        'routes' => [
+            '_default' => [
+                'target' => \TYPO3\CMS\Scheduler\Controller\SchedulerSetupCheckController::class . '::handle',
+            ],
         ],
     ],
 ];
diff --git a/typo3/sysext/scheduler/Configuration/Services.yaml b/typo3/sysext/scheduler/Configuration/Services.yaml
index 4742b815a72d..d039d85a37f2 100644
--- a/typo3/sysext/scheduler/Configuration/Services.yaml
+++ b/typo3/sysext/scheduler/Configuration/Services.yaml
@@ -7,9 +7,6 @@ services:
   TYPO3\CMS\Scheduler\:
     resource: '../Classes/*'
 
-  TYPO3\CMS\Scheduler\Controller\SchedulerModuleController:
-    tags: [ 'backend.controller' ]
-
   TYPO3\CMS\Scheduler\Command\SchedulerCommand:
     tags:
       - name: 'console.command'
diff --git a/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html b/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html
index ca48a26c7997..f85213d7fbd3 100644
--- a/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html
+++ b/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html
@@ -142,7 +142,7 @@
                                     <td class="right">{task.uid}</td>
                                     <td class="nowrap-disabled">
                                         <div class="name">
-                                            <f:be.link route="system_txschedulerM1" parameters="{action: 'edit', 'uid': task.uid}">
+                                            <f:be.link route="scheduler_manage" parameters="{action: 'edit', 'uid': task.uid}">
                                                 <strong>{task.classTitle} ({task.classExtension})</strong>
                                                 <f:if condition="{task.progress}">
                                                     <div class="progress">
@@ -267,7 +267,7 @@
                                             <f:then>
                                                 <div class="btn-group" role="group">
                                                     <f:comment>@todo: Next one should be a button type="submit".</f:comment>
-                                                    <f:be.link route="system_txschedulerM1"
+                                                    <f:be.link route="scheduler_manage"
                                                         parameters="{action: {'stop': task.uid}}"
                                                         class="btn btn-default t3js-modal-trigger"
                                                         data="{
@@ -283,7 +283,7 @@
                                             </f:then>
                                             <f:else>
                                                 <div class="btn-group" role="group">
-                                                    <f:be.link route="system_txschedulerM1"
+                                                    <f:be.link route="scheduler_manage"
                                                         parameters="{action: 'edit', 'uid': task.uid}"
                                                         class="btn btn-default"
                                                         title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:edit')}"
@@ -315,7 +315,7 @@
                                                         </f:else>
                                                     </f:if>
                                                     <f:comment>@todo: Next one should be a button type="submit", too. Similar to the "toggle" button above.</f:comment>
-                                                    <f:be.link route="system_txschedulerM1"
+                                                    <f:be.link route="scheduler_manage"
                                                         parameters="{action: {'delete': task.uid}}"
                                                         class="btn btn-default t3js-modal-trigger"
                                                         data="{
@@ -406,7 +406,7 @@
                                     <div class="btn-group" role="group">
                                         <f:comment>@todo: Next one should be a button type="submit"</f:comment>
                                         <f:be.link
-                                            route="system_txschedulerM1"
+                                            route="scheduler_manage"
                                             parameters="{action: {'delete': errorClass.uid}}"
                                             class="btn btn-default t3js-modal-trigger"
                                             data="{
diff --git a/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html b/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html
index 4d54ff74fbd0..1d58f4cbf079 100644
--- a/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html
+++ b/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html
@@ -29,7 +29,7 @@
                             <tr>
                                 <td>
                                     <a
-                                        href="{be:moduleLink(route: 'system_txschedulerM1', query: 'subModule=scheduler&action=add&tx_scheduler[class]={class}')}"
+                                        href="{be:moduleLink(route: 'scheduler_manage', query: 'action=add&tx_scheduler[class]={class}')}"
                                         title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:new')}"
                                     >
                                         {classInfo.title}
@@ -41,7 +41,7 @@
                                     <div class="btn-group" role="group">
                                     <span class="btn btn-default">
                                         <a
-                                            href="{be:moduleLink(route: 'system_txschedulerM1', query: 'subModule=scheduler&action=add&tx_scheduler[class]={class}')}"
+                                            href="{be:moduleLink(route: 'scheduler_manage', query: 'action=add&tx_scheduler[class]={class}')}"
                                             title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:new')}"
                                         >
                                             <core:icon identifier="actions-plus" />
diff --git a/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js b/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js
index f2493dd6e824..9449b08da2ee 100644
--- a/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js
+++ b/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import $ from"jquery";import Tablesort from"tablesort";import DocumentSaveActions from"@typo3/backend/document-save-actions.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Modal from"@typo3/backend/modal.js";import Icons from"@typo3/backend/icons.js";import{MessageUtility}from"@typo3/backend/utility/message-utility.js";import PersistentStorage from"@typo3/backend/storage/persistent.js";import DateTimePicker from"@typo3/backend/date-time-picker.js";import{MultiRecordSelectionSelectors}from"@typo3/backend/multi-record-selection.js";class Scheduler{static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach((e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"}))}static resolveDefaultNumberOfDays(){const e=document.getElementById("task_tableGarbageCollection_numberOfDays");return null===e||void 0===e.dataset.defaultNumberOfDays?null:JSON.parse(e.dataset.defaultNumberOfDays)}static storeCollapseState(e,t){let a={};PersistentStorage.isset("moduleData.system_txschedulerM1")&&(a=PersistentStorage.get("moduleData.system_txschedulerM1"));const l={};l[e]=t?1:0,$.extend(a,l),PersistentStorage.set("moduleData.system_txschedulerM1",a)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),DocumentSaveActions.getInstance().addPreSubmitCallback((()=>{let e=$("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),$(".extraFields").appendTo($("#extraFieldsHidden")),$(".extra_fields_"+e).appendTo($("#extraFieldsSection"))}))}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),$(".extraFields").hide(),$(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType($(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=$("#task_tableGarbageCollection_numberOfDays"),a=$("#task_tableGarbageCollection_table");if(e.prop("checked"))a.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=a.val();const l=Scheduler.resolveDefaultNumberOfDays();null!==l&&(e=l[t])}a.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=$("#task_tableGarbageCollection_numberOfDays");const a=Scheduler.resolveDefaultNumberOfDays();null!==a&&a[e.val()]>0?(t.prop("disabled",!1),t.val(a[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),$("#task_end_col").toggle(2===e),$("#task_frequency_row").toggle(2===e)}initializeEvents(){$("#task_class").on("change",(e=>{this.actOnChangedTaskClass($(e.currentTarget))})),$("#task_type").on("change",this.actOnChangedTaskType.bind(this)),$("#task_tableGarbageCollection_allTables").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables($(e.currentTarget))})),$("#task_tableGarbageCollection_table").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionTable($(e.currentTarget))})),$("[data-update-task-frequency]").on("change",(e=>{const t=$(e.currentTarget);$("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")}));const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),document.querySelectorAll("#tx_scheduler_form .t3js-datetimepicker").forEach((e=>DateTimePicker.initialize(e))),$(document).on("click",".t3js-element-browser",(e=>{e.preventDefault();const t=e.currentTarget;Modal.advanced({type:Modal.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:Modal.sizes.large})})),new RegularEvent("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=$("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=$("#task_class");t.length&&(this.actOnChangedTaskClass(t),Scheduler.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;if("typo3:elementBrowser:elementAdded"===e.data.actionName){if(void 0===e.data.fieldName)throw"fieldName not defined in message";if(void 0===e.data.value)throw"value not defined in message";document.querySelector('input[name="'+e.data.fieldName+'"]').value=e.data.value.split("_").pop()}}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,a=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==a&&Icons.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",Icons.sizes.small).then((e=>{a.innerHTML=e})),Scheduler.storeCollapseState($(e.target).data("table"),t)}executeTasks(e){const t=document.querySelector("#tx_scheduler_form");if(null===t)return;const a=[];if(e.detail.checkboxes.forEach((e=>{const t=e.closest(MultiRecordSelectionSelectors.elementSelector);null!==t&&t.dataset.taskId&&a.push(t.dataset.taskId)})),a.length){if("multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","scheduleCron"),e.setAttribute("value",a.join(",")),t.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","execute"),e.setAttribute("value",a.join(",")),t.append(e)}t.submit()}}}export default new Scheduler;
\ No newline at end of file
+import $ from"jquery";import Tablesort from"tablesort";import DocumentSaveActions from"@typo3/backend/document-save-actions.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Modal from"@typo3/backend/modal.js";import Icons from"@typo3/backend/icons.js";import{MessageUtility}from"@typo3/backend/utility/message-utility.js";import PersistentStorage from"@typo3/backend/storage/persistent.js";import DateTimePicker from"@typo3/backend/date-time-picker.js";import{MultiRecordSelectionSelectors}from"@typo3/backend/multi-record-selection.js";class Scheduler{static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach((e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"}))}static resolveDefaultNumberOfDays(){const e=document.getElementById("task_tableGarbageCollection_numberOfDays");return null===e||void 0===e.dataset.defaultNumberOfDays?null:JSON.parse(e.dataset.defaultNumberOfDays)}static storeCollapseState(e,t){let a={};PersistentStorage.isset("moduleData.scheduler_manage")&&(a=PersistentStorage.get("moduleData.scheduler_manage"));const l={};l[e]=t?1:0,$.extend(a,l),PersistentStorage.set("moduleData.scheduler_manage",a)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),DocumentSaveActions.getInstance().addPreSubmitCallback((()=>{let e=$("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),$(".extraFields").appendTo($("#extraFieldsHidden")),$(".extra_fields_"+e).appendTo($("#extraFieldsSection"))}))}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),$(".extraFields").hide(),$(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType($(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=$("#task_tableGarbageCollection_numberOfDays"),a=$("#task_tableGarbageCollection_table");if(e.prop("checked"))a.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=a.val();const l=Scheduler.resolveDefaultNumberOfDays();null!==l&&(e=l[t])}a.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=$("#task_tableGarbageCollection_numberOfDays");const a=Scheduler.resolveDefaultNumberOfDays();null!==a&&a[e.val()]>0?(t.prop("disabled",!1),t.val(a[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),$("#task_end_col").toggle(2===e),$("#task_frequency_row").toggle(2===e)}initializeEvents(){$("#task_class").on("change",(e=>{this.actOnChangedTaskClass($(e.currentTarget))})),$("#task_type").on("change",this.actOnChangedTaskType.bind(this)),$("#task_tableGarbageCollection_allTables").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables($(e.currentTarget))})),$("#task_tableGarbageCollection_table").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionTable($(e.currentTarget))})),$("[data-update-task-frequency]").on("change",(e=>{const t=$(e.currentTarget);$("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")}));const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),document.querySelectorAll("#tx_scheduler_form .t3js-datetimepicker").forEach((e=>DateTimePicker.initialize(e))),$(document).on("click",".t3js-element-browser",(e=>{e.preventDefault();const t=e.currentTarget;Modal.advanced({type:Modal.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:Modal.sizes.large})})),new RegularEvent("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=$("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=$("#task_class");t.length&&(this.actOnChangedTaskClass(t),Scheduler.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;if("typo3:elementBrowser:elementAdded"===e.data.actionName){if(void 0===e.data.fieldName)throw"fieldName not defined in message";if(void 0===e.data.value)throw"value not defined in message";document.querySelector('input[name="'+e.data.fieldName+'"]').value=e.data.value.split("_").pop()}}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,a=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==a&&Icons.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",Icons.sizes.small).then((e=>{a.innerHTML=e})),Scheduler.storeCollapseState($(e.target).data("table"),t)}executeTasks(e){const t=document.querySelector("#tx_scheduler_form");if(null===t)return;const a=[];if(e.detail.checkboxes.forEach((e=>{const t=e.closest(MultiRecordSelectionSelectors.elementSelector);null!==t&&t.dataset.taskId&&a.push(t.dataset.taskId)})),a.length){if("multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","scheduleCron"),e.setAttribute("value",a.join(",")),t.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","execute"),e.setAttribute("value",a.join(",")),t.append(e)}t.submit()}}}export default new Scheduler;
\ No newline at end of file
-- 
GitLab