Skip to content

Commit

Permalink
[BUGFIX] Disable trigger buttons in Install Tool when actions are exe…
Browse files Browse the repository at this point in the history
…cuted

If an action in the Install Tool is executed that is related to an
inline module or an interactable module (a.k.a "modal"), its trigger
button(s) get now properly disabled and enabled to avoid executing the
same actions consecutively while any request is still pending.

Resolves: #91076
Releases: master
Change-Id: I9a61063819f21a33ac8ede644fa8f998212b342b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64207
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Jonas Eberle <flightvision@googlemail.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Susanne Moog <look@susi.dev>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Susanne Moog <look@susi.dev>
  • Loading branch information
andreaskienast authored and susannemoog committed May 21, 2020
1 parent 534e7cf commit 2f2cfff
Show file tree
Hide file tree
Showing 45 changed files with 235 additions and 210 deletions.
Expand Up @@ -10,4 +10,11 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports"],(function(e,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0})}));

export abstract class AbstractInlineModule {
protected setButtonState(button: JQuery, interactable: boolean): void {
button.toggleClass('disabled', !interactable).prop('disabled', !interactable);
}

abstract initialize($trigger: JQuery): void;
}
Expand Up @@ -33,5 +33,15 @@ export abstract class AbstractInteractableModule {
return this.currentModal.find(selector);
}

protected setModalButtonsState(interactable: boolean): void {
this.getModalFooter().find('button').each((_: number, elem: Element): void => {
this.setModalButtonState($(elem), interactable)
});
}

protected setModalButtonState(button: JQuery, interactable: boolean): void {
button.toggleClass('disabled', !interactable).prop('disabled', !interactable);
}

public abstract initialize(currentModal: JQuery): void;
}
Expand Up @@ -45,12 +45,13 @@ class EnvironmentCheck extends AbstractInteractableModule {
}

private runTests(): void {
this.setModalButtonsState(false);

const modalContent = this.getModalBody();
const $errorBadge = $(this.selectorGridderBadge);
$errorBadge.text('').hide();
const message = ProgressBar.render(Severity.loading, 'Loading...', '');
modalContent.find(this.selectorOutputContainer).empty().append(message);
this.findInModal(this.selectorExecuteTrigger).addClass('disabled').prop('disabled', true);

(new AjaxRequest(Router.getUrl('environmentCheckGetStatus')))
.get({cache: 'no-cache'})
Expand Down
Expand Up @@ -49,7 +49,6 @@ class FolderStructure extends AbstractInteractableModule {

currentModal.on('click', this.selectorErrorFixTrigger, (e: JQueryEventObject): void => {
e.preventDefault();
$(e.currentTarget).addClass('disabled').prop('disabled', true);
this.fix();
});
}
Expand Down Expand Up @@ -111,6 +110,8 @@ class FolderStructure extends AbstractInteractableModule {
}

private fix(): void {
this.setModalButtonsState(false);

const modalContent: JQuery = this.getModalBody();
const $outputContainer: JQuery = this.findInModal(this.selectorOutputContainer);
const message: any = ProgressBar.render(Severity.loading, 'Loading...', '');
Expand Down
Expand Up @@ -68,7 +68,7 @@ class ImageProcessing extends AbstractInteractableModule {
private runTests(): void {
const modalContent = this.getModalBody();
const $triggerButton = this.findInModal(this.selectorExecuteTrigger);
$triggerButton.addClass('disabled').prop('disabled', true);
this.setModalButtonsState(false);

const $twinImageTemplate = this.findInModal(this.selectorTwinImageTemplate);
const promises: Array<Promise<any>> = [];
Expand Down
Expand Up @@ -59,41 +59,43 @@ class MailTest extends AbstractInteractableModule {
},
(error: ResponseError): void => {
Router.handleAjaxError(error, modalContent);
}
},
);
}

private send(): void {
this.setModalButtonsState(false);

const executeToken: string = this.getModuleContent().data('mail-test-token');
const $outputContainer: JQuery = this.findInModal(this.selectorOutputContainer);
const message: any = ProgressBar.render(Severity.loading, 'Loading...', '');
$outputContainer.empty().html(message);
(new AjaxRequest(Router.getUrl()))
.post({
install: {
action: 'mailTest',
token: executeToken,
email: this.findInModal('.t3js-mailTest-email').val(),
},
})
.then(
async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
$outputContainer.empty();
if (Array.isArray(data.status)) {
data.status.forEach((element: any): void => {
const aMessage: any = InfoBox.render(element.severity, element.title, element.message);
$outputContainer.html(aMessage);
});
} else {
Notification.error('Something went wrong');
}
},
(): void => {
// 500 can happen here if the mail configuration is broken
(new AjaxRequest(Router.getUrl())).post({
install: {
action: 'mailTest',
token: executeToken,
email: this.findInModal('.t3js-mailTest-email').val(),
},
}).then(
async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
$outputContainer.empty();
if (Array.isArray(data.status)) {
data.status.forEach((element: any): void => {
const aMessage: any = InfoBox.render(element.severity, element.title, element.message);
$outputContainer.html(aMessage);
});
} else {
Notification.error('Something went wrong');
}
);
},
(): void => {
// 500 can happen here if the mail configuration is broken
Notification.error('Something went wrong');
},
).finally((): void => {
this.setModalButtonsState(true);
});
}
}

Expand Down
Expand Up @@ -15,14 +15,14 @@ import Notification = require('TYPO3/CMS/Backend/Notification');
import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest');
import Router = require('../../Router');
import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse';
import {InlineModuleInterface} from './../InlineModuleInterface';
import {AbstractInlineModule} from '../AbstractInlineModule';

/**
* Module: TYPO3/CMS/Install/Module/Cache
*/
class Cache implements InlineModuleInterface {
class Cache extends AbstractInlineModule {
public initialize($trigger: JQuery): void {
$trigger.addClass('disabled').prop('disabled', true);
this.setButtonState($trigger, false);

(new AjaxRequest(Router.getUrl('cacheClearAll', 'maintenance')))
.get({cache: 'no-cache'})
Expand All @@ -45,10 +45,10 @@ class Cache implements InlineModuleInterface {
Notification.error(
'Clearing caches went wrong on the server side. Check the system for broken extensions or missing database tables and try again',
);
}
},
)
.finally((): void => {
$trigger.removeClass('disabled').prop('disabled', false);
this.setButtonState($trigger, true);
});
}
}
Expand Down
Expand Up @@ -51,6 +51,8 @@ class ClearTables extends AbstractInteractableModule {
}

private getStats(): void {
this.setModalButtonsState(false);

const modalContent: JQuery = this.getModalBody();
(new AjaxRequest(Router.getUrl('clearTablesStats')))
.get({cache: 'no-cache'})
Expand Down
Expand Up @@ -50,6 +50,8 @@ class ClearTypo3tempFiles extends AbstractInteractableModule {
}

private getStats(): void {
this.setModalButtonsState(false);

const modalContent = this.getModalBody();
(new AjaxRequest(Router.getUrl('clearTypo3tempFilesStats')))
.get({cache: 'no-cache'})
Expand Down
Expand Up @@ -56,49 +56,53 @@ class CreateAdmin extends AbstractInteractableModule {
},
(error: ResponseError): void => {
Router.handleAjaxError(error, modalContent);
}
},
);
}

private create(): void {
this.setModalButtonsState(false);

const modalContent = this.getModalBody();
const executeToken = this.getModuleContent().data('create-admin-token');
(new AjaxRequest(Router.getUrl()))
.post({
install: {
action: 'createAdmin',
token: executeToken,
userName: this.findInModal('.t3js-createAdmin-user').val(),
userPassword: this.findInModal('.t3js-createAdmin-password').val(),
userPasswordCheck: this.findInModal('.t3js-createAdmin-password-check').val(),
userEmail: this.findInModal('.t3js-createAdmin-email').val(),
userSystemMaintainer: (this.findInModal('.t3js-createAdmin-system-maintainer').is(':checked')) ? 1 : 0,
},
})
.then(
async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true && Array.isArray(data.status)) {
data.status.forEach((element: any): void => {
if (element.severity === 2) {
Notification.error(element.message);
} else {
Notification.success(element.title);
}
});
const payload = {
install: {
action: 'createAdmin',
token: executeToken,
userName: this.findInModal('.t3js-createAdmin-user').val(),
userPassword: this.findInModal('.t3js-createAdmin-password').val(),
userPasswordCheck: this.findInModal('.t3js-createAdmin-password-check').val(),
userEmail: this.findInModal('.t3js-createAdmin-email').val(),
userSystemMaintainer: (this.findInModal('.t3js-createAdmin-system-maintainer').is(':checked')) ? 1 : 0,
},
};
this.getModuleContent().find(':input').prop('disabled', true);

(new AjaxRequest(Router.getUrl())).post(payload).then(async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true && Array.isArray(data.status)) {
data.status.forEach((element: any): void => {
if (element.severity === 2) {
Notification.error(element.message);
} else {
Notification.error('Something went wrong');
Notification.success(element.title);
}
},
(error: ResponseError): void => {
Router.handleAjaxError(error, modalContent);
}
);
this.findInModal('.t3js-createAdmin-user').val('');
this.findInModal('.t3js-createAdmin-password').val('');
this.findInModal('.t3js-createAdmin-password-check').val('');
this.findInModal('.t3js-createAdmin-email').val('');
this.findInModal('.t3js-createAdmin-system-maintainer').prop('checked', false);
});
} else {
Notification.error('Something went wrong');
}
}, (error: ResponseError): void => {
Router.handleAjaxError(error, modalContent);
}).finally((): void => {
this.setModalButtonsState(true);

this.getModuleContent().find(':input').prop('disabled', false);
this.findInModal('.t3js-createAdmin-user').val('');
this.findInModal('.t3js-createAdmin-password').val('');
this.findInModal('.t3js-createAdmin-password-check').val('');
this.findInModal('.t3js-createAdmin-email').val('');
this.findInModal('.t3js-createAdmin-system-maintainer').prop('checked', false);
});
}
}

Expand Down
Expand Up @@ -75,20 +75,18 @@ class DatabaseAnalyzer extends AbstractInteractableModule {
}

private analyze(): void {
this.setModalButtonsState(false);

const modalContent = this.getModalBody();
const modalFooter = this.getModalFooter();
const outputContainer = modalContent.find(this.selectorOutputContainer);
const executeTrigger = modalFooter.find(this.selectorExecuteTrigger);
const analyzeTrigger = modalFooter.find(this.selectorAnalyzeTrigger);

outputContainer.empty().append(ProgressBar.render(Severity.loading, 'Analyzing current database schema...', ''));

analyzeTrigger.prop('disabled', true);
executeTrigger.prop('disabled', true);

outputContainer.on('change', 'input[type="checkbox"]', (): void => {
const hasCheckedCheckboxes = outputContainer.find(':checked').length > 0;
executeTrigger.prop('disabled', !hasCheckedCheckboxes);
this.setModalButtonState(executeTrigger, hasCheckedCheckboxes);
});

(new AjaxRequest(Router.getUrl('databaseAnalyzerAnalyze')))
Expand Down Expand Up @@ -138,9 +136,8 @@ class DatabaseAnalyzer extends AbstractInteractableModule {
outputContainer.append(aBlock.html());
});

const isInitiallyDisabled = outputContainer.find(':checked').length === 0;
analyzeTrigger.prop('disabled', false);
executeTrigger.prop('disabled', isInitiallyDisabled);
this.setModalButtonState(analyzeTrigger, true);
this.setModalButtonState(executeTrigger, outputContainer.find(':checked').length > 0);
}
if (data.suggestions.length === 0 && data.status.length === 0) {
outputContainer.append(InfoBox.render(Severity.ok, 'Database schema is up to date. Good job!', ''));
Expand All @@ -156,18 +153,17 @@ class DatabaseAnalyzer extends AbstractInteractableModule {
}

private execute(): void {
this.setModalButtonsState(false);

const modalContent = this.getModalBody();
const modalFooter = this.getModalFooter();
const executeToken = this.getModuleContent().data('database-analyzer-execute-token');
const outputContainer = modalContent.find(this.selectorOutputContainer);
const selectedHashes: Array<any> = [];

const selectedHashes: Array<any> = [];
outputContainer.find('.t3js-databaseAnalyzer-suggestion-line input:checked').each((index: number, element: any): void => {
selectedHashes.push($(element).data('hash'));
});
outputContainer.empty().append(ProgressBar.render(Severity.loading, 'Executing database updates...', ''));
modalFooter.find(this.selectorExecuteTrigger).prop('disabled', true);
modalFooter.find(this.selectorAnalyzeTrigger).prop('disabled', true);

(new AjaxRequest(Router.getUrl()))
.post({
Expand Down
Expand Up @@ -15,14 +15,14 @@ import Notification = require('TYPO3/CMS/Backend/Notification');
import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest');
import Router = require('../../Router');
import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse';
import {InlineModuleInterface} from './../InlineModuleInterface';
import {AbstractInlineModule} from '../AbstractInlineModule';

/**
* Module: TYPO3/CMS/Install/Module/DumpAutoload
*/
class DumpAutoload implements InlineModuleInterface {
class DumpAutoload extends AbstractInlineModule {
public initialize($trigger: JQuery): void {
$trigger.addClass('disabled').prop('disabled', true);
this.setButtonState($trigger, false);

(new AjaxRequest(Router.getUrl('dumpAutoload')))
.get({cache: 'no-cache'})
Expand All @@ -46,7 +46,7 @@ class DumpAutoload implements InlineModuleInterface {
}
)
.finally((): void => {
$trigger.removeClass('disabled').prop('disabled', false);
this.setButtonState($trigger, true);
});
}
}
Expand Down
Expand Up @@ -13,16 +13,16 @@

import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest');
import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse';
import {InlineModuleInterface} from './../InlineModuleInterface';
import {AbstractInlineModule} from '../AbstractInlineModule';
import Notification = require('TYPO3/CMS/Backend/Notification');
import Router = require('../../Router');

/**
* Module: TYPO3/CMS/Install/Module/ResetBackendUserUc
*/
class ResetBackendUserUc implements InlineModuleInterface {
class ResetBackendUserUc extends AbstractInlineModule {
public initialize($trigger: JQuery): void {
$trigger.addClass('disabled').prop('disabled', true);
this.setButtonState($trigger, false);

(new AjaxRequest(Router.getUrl('resetBackendUserUc')))
.get({cache: 'no-cache'})
Expand All @@ -46,7 +46,7 @@ class ResetBackendUserUc implements InlineModuleInterface {
}
)
.finally((): void => {
$trigger.removeClass('disabled').prop('disabled', false);
this.setButtonState($trigger, true);
});
}
}
Expand Down

0 comments on commit 2f2cfff

Please sign in to comment.