diff --git a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java index db8d96ca15..5a85055a0c 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java +++ b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java @@ -5,7 +5,7 @@ import com.google.common.base.Preconditions; public class AvailablePlugin implements Plugin { private final AvailablePluginDescriptor pluginDescriptor; - private final boolean pending; + private boolean pending; public AvailablePlugin(AvailablePluginDescriptor pluginDescriptor) { this(pluginDescriptor, false); @@ -25,6 +25,10 @@ public class AvailablePlugin implements Plugin { return pending; } + public void cancelInstallation() { + this.pending = false; + } + public AvailablePlugin install() { Preconditions.checkState(!pending, "installation is already pending"); return new AvailablePlugin(pluginDescriptor, true); diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index 3d0ea94536..4c8fcd7306 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -73,6 +73,13 @@ public interface PluginManager { */ List getAvailable(); + /** + * Returns all updatable plugins. + * + * @return a list of updatable plugins. + */ + List getUpdatable(); + /** * Installs the plugin with the given name from the list of available plugins. * @@ -93,4 +100,14 @@ public interface PluginManager { * Install all pending plugins and restart the scm context. */ void executePendingAndRestart(); + + /** + * Cancel all pending plugins. + */ + void cancelPending(); + + /** + * Update all installed plugins. + */ + void updateAll(); } diff --git a/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js b/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js index b73688ebbf..66b3f21052 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js +++ b/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js @@ -14,7 +14,7 @@ class ButtonGroup extends React.Component { const childWrapper = []; React.Children.forEach(children, child => { if (child) { - childWrapper.push(

{child}

); + childWrapper.push(
{child}
); } }); diff --git a/scm-ui-components/packages/ui-types/src/Plugin.js b/scm-ui-components/packages/ui-types/src/Plugin.js index c7612a8bf8..f5a621e1ff 100644 --- a/scm-ui-components/packages/ui-types/src/Plugin.js +++ b/scm-ui-components/packages/ui-types/src/Plugin.js @@ -17,6 +17,7 @@ export type Plugin = { }; export type PluginCollection = Collection & { + _links: Links, _embedded: { plugins: Plugin[] | string[] } diff --git a/scm-ui/public/locales/de/admin.json b/scm-ui/public/locales/de/admin.json index d4f3567059..af34c5b759 100644 --- a/scm-ui/public/locales/de/admin.json +++ b/scm-ui/public/locales/de/admin.json @@ -29,7 +29,11 @@ "installedNavLink": "Installiert", "availableNavLink": "Verfügbar" }, - "executePending": "Ausstehende Plugin-Änderungen ausführen", + "executePending": "Änderungen ausführen", + "outdatedPlugins": "{{count}} veraltetes Plugin", + "outdatedPlugins_plural": "{{count}} veraltete Plugins", + "updateAll": "Alle Plugins aktualisieren", + "cancelPending": "Änderungen abbrechen", "noPlugins": "Keine Plugins gefunden.", "modal": { "title": { @@ -48,6 +52,7 @@ "updateAndRestart": "Aktualisieren und Neustarten", "uninstallAndRestart": "Deinstallieren and Neustarten", "executeAndRestart": "Ausführen und Neustarten", + "updateAll": "Alle Plugins aktualisieren", "abort": "Abbrechen", "author": "Autor", "version": "Version", @@ -58,7 +63,9 @@ "successNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:", "reload": "jetzt neu laden", "restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.", - "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird der SCM-Manager Kontext neu gestartet." + "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird der SCM-Manager Kontext neu gestartet.", + "cancelPending": "Die folgenden Plugin-Änderungen werden abgebrochen und zurückgesetzt.", + "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam." } }, "repositoryRole": { diff --git a/scm-ui/public/locales/en/admin.json b/scm-ui/public/locales/en/admin.json index c23a88a920..c315d1d142 100644 --- a/scm-ui/public/locales/en/admin.json +++ b/scm-ui/public/locales/en/admin.json @@ -29,7 +29,11 @@ "installedNavLink": "Installed", "availableNavLink": "Available" }, - "executePending": "Execute pending plugin changes", + "executePending": "Execute changes", + "outdatedPlugins": "{{count}} outdated plugin", + "outdatedPlugins_plural": "{{count}} outdated plugins", + "updateAll": "Update all plugins", + "cancelPending": "Cancel changes", "noPlugins": "No plugins found.", "modal": { "title": { @@ -48,6 +52,7 @@ "updateAndRestart": "Update and Restart", "uninstallAndRestart": "Uninstall and Restart", "executeAndRestart": "Execute and Restart", + "updateAll": "Update all plugins", "abort": "Abort", "author": "Author", "version": "Version", @@ -58,7 +63,9 @@ "successNotification": "Successful installed plugin. You have to reload the page, to see ui changes:", "reload": "reload now", "restartNotification": "You should only restart the scm-manager context if no one else is currently working with it.", - "executePending": "The following plugin changes will be executed and after that the scm-manager context will be restarted." + "executePending": "The following plugin changes will be executed and after that the scm-manager context will be restarted.", + "cancelPending": "The following plugin changes will be canceled.", + "updateAllInfo": "The following plugin changes will be executed. You need to restart the scm-manager to make these changes effective." } }, "repositoryRole": { diff --git a/scm-ui/public/locales/es/admin.json b/scm-ui/public/locales/es/admin.json index db9fade43c..155313ed99 100644 --- a/scm-ui/public/locales/es/admin.json +++ b/scm-ui/public/locales/es/admin.json @@ -30,6 +30,8 @@ "availableNavLink": "Disponibles" }, "executePending": "Ejecutar los complementos pendientes", + "updateAll": "Actualizar todos los complementos", + "cancelPending": "Cancelar los complementos pendientes", "noPlugins": "No se han encontrado complementos.", "modal": { "title": { diff --git a/scm-ui/src/admin/plugins/components/CancelPendingActionModal.js b/scm-ui/src/admin/plugins/components/CancelPendingActionModal.js new file mode 100644 index 0000000000..6ca6400099 --- /dev/null +++ b/scm-ui/src/admin/plugins/components/CancelPendingActionModal.js @@ -0,0 +1,42 @@ +// @flow + +import React from "react"; +import PluginActionModal from "./PluginActionModal"; +import type { PendingPlugins } from "@scm-manager/ui-types"; +import { apiClient } from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; + +type Props = { + onClose: () => void, + refresh: () => void, + pendingPlugins: PendingPlugins, + + // context props + t: string => string +}; + +class CancelPendingActionModal extends React.Component { + render() { + const { onClose, pendingPlugins, t } = this.props; + + return ( + + ); + } + + cancelPending = () => { + const { pendingPlugins, refresh, onClose } = this.props; + return apiClient + .post(pendingPlugins._links.cancel.href) + .then(refresh) + .then(onClose); + }; +} + +export default translate("admin")(CancelPendingActionModal); diff --git a/scm-ui/src/admin/plugins/components/ExecutePendingAction.js b/scm-ui/src/admin/plugins/components/ExecutePendingAction.js deleted file mode 100644 index 6c8d407205..0000000000 --- a/scm-ui/src/admin/plugins/components/ExecutePendingAction.js +++ /dev/null @@ -1,68 +0,0 @@ -// @flow -import React from "react"; -import { Button } from "@scm-manager/ui-components"; -import type { PendingPlugins } from "@scm-manager/ui-types"; -import { translate } from "react-i18next"; -import ExecutePendingModal from "./ExecutePendingModal"; - -type Props = { - pendingPlugins: PendingPlugins, - - // context props - t: string => string -}; - -type State = { - showModal: boolean -}; - -class ExecutePendingAction extends React.Component { - constructor(props: Props) { - super(props); - this.state = { - showModal: false - }; - } - - openModal = () => { - this.setState({ - showModal: true - }); - }; - - closeModal = () => { - this.setState({ - showModal: false - }); - }; - - renderModal = () => { - const { showModal } = this.state; - const { pendingPlugins } = this.props; - if (showModal) { - return ( - - ); - } - return null; - }; - - render() { - const { t } = this.props; - return ( - <> - {this.renderModal()} -