diff --git a/CHANGELOG.md b/CHANGELOG.md index b2595d3e8e..daee9aa86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - Extension point to add links to the repository cards from plug ins ([#1041](https://github.com/scm-manager/scm-manager/pull/1041)) +- Libc based restart strategy for posix operating systems ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) +- Simple restart strategy with System.exit ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) +- Notification if restart is not supported on the underlying platform ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) ### Changed - Update resteasy to version 4.5.2.Final +- Update shiro to version 1.5.2 - Use browser built-in EventSource for apiClient subscriptions - Changeover to MIT license ([#1066](https://github.com/scm-manager/scm-manager/pull/1066)) ### Removed - EventSource Polyfill +- ClassLoader based restart logic ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) ### Fixed - Build on windows ([#1048](https://github.com/scm-manager/scm-manager/issues/1048), [#1049](https://github.com/scm-manager/scm-manager/issues/1049), [#1056](https://github.com/scm-manager/scm-manager/pull/1056)) diff --git a/pom.xml b/pom.xml index c3f6cb139f..2054116a65 100644 --- a/pom.xml +++ b/pom.xml @@ -363,6 +363,13 @@ test + + junit + junit + 4.13 + test + + org.hamcrest hamcrest-core @@ -919,7 +926,7 @@ 1.2.0 - 1.5.1 + 1.5.2 5.6.1.202002131546-r-scm1 diff --git a/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java b/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java index 0315103d22..2485e78de4 100644 --- a/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java +++ b/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java @@ -29,63 +29,37 @@ package sonia.scm.lifecycle; import sonia.scm.event.Event; /** - * This event can be used to force a restart of the webapp context. The restart - * event is useful during plugin development, because we don't have to restart - * the whole server, to see our changes. The restart event could also be used - * to install or upgrade plugins. - * - * But the restart event should be used carefully, because the whole context - * will be restarted and that process could take some time. - * + * This event indicates a forced restart of scm-manager. * @author Sebastian Sdorra * @since 2.0.0 */ @Event -public class RestartEvent -{ +public class RestartEvent { - /** - * Constructs ... - * - * - * @param cause - * @param reason - */ - public RestartEvent(Class cause, String reason) - { + private final Class cause; + private final String reason; + + RestartEvent(Class cause, String reason) { this.cause = cause; this.reason = reason; } - //~--- get methods ---------------------------------------------------------- - /** * The class which has fired the restart event. * - * * @return class which has fired the restart event */ - public Class getCause() - { + public Class getCause() { return cause; } /** * Returns the reason for the restart. * - * * @return reason for restart */ - public String getReason() - { + public String getReason() { return reason; } - //~--- fields --------------------------------------------------------------- - - /** cause of restart */ - private final Class cause; - - /** reason for restart */ - private final String reason; } diff --git a/scm-core/src/main/java/sonia/scm/lifecycle/RestartNotSupportedException.java b/scm-core/src/main/java/sonia/scm/lifecycle/RestartNotSupportedException.java new file mode 100644 index 0000000000..7b560f7222 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/lifecycle/RestartNotSupportedException.java @@ -0,0 +1,37 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package sonia.scm.lifecycle; + +/** + * Exception is thrown if a restart is not supported or a restart strategy is misconfigured. + */ +public class RestartNotSupportedException extends RuntimeException { + RestartNotSupportedException(String message) { + super(message); + } + + RestartNotSupportedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/scm-core/src/main/java/sonia/scm/lifecycle/Restarter.java b/scm-core/src/main/java/sonia/scm/lifecycle/Restarter.java new file mode 100644 index 0000000000..12f9279c80 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/lifecycle/Restarter.java @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package sonia.scm.lifecycle; + +/** + * {@link Restarter} is able to restart scm-manager. + * + * @since 2.0.0 + */ +public interface Restarter { + + /** + * Return {@code true} if restarting scm-manager is supported. + * + * @return {@code true} if restart is supported + */ + boolean isSupported(); + + /** + * Issues a restart. The method will fire a {@link RestartEvent} to notify the system about the upcoming restart. + * If restarting is not supported by the underlying platform a {@link RestartNotSupportedException} is thrown. + * + * @param cause cause of the restart. This should be the class which calls this method. + * @param reason reason for the required restart. + * @throws RestartNotSupportedException if restarting is not supported by the underlying platform. + */ + void restart(Class cause, String reason); +} diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 05d773a6f8..a24e048f55 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -38,7 +38,7 @@ scm-test 2.0.0-SNAPSHOT scm-test - + @@ -53,6 +53,12 @@ 2.0.0-SNAPSHOT + + junit + junit + compile + + com.github.sdorra shiro-unit @@ -88,17 +94,17 @@ - + - + - + maven.tmatesoft.com tmatesoft release repository https://maven.tmatesoft.com/content/repositories/releases - + diff --git a/scm-test/src/main/java/sonia/scm/lifecycle/RestartEventFactory.java b/scm-test/src/main/java/sonia/scm/lifecycle/RestartEventFactory.java new file mode 100644 index 0000000000..d58897dd40 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/lifecycle/RestartEventFactory.java @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package sonia.scm.lifecycle; + +/** + * Creates restart events for testing. + * This is required, because the constructor of {@link RestartEvent} is package private. + */ +public final class RestartEventFactory { + + private RestartEventFactory(){} + + public static RestartEvent create(Class cause, String reason) { + return new RestartEvent(cause, reason); + } + +} diff --git a/scm-ui/ui-components/src/MarkdownView.tsx b/scm-ui/ui-components/src/MarkdownView.tsx index 3e20830585..d1ef3008c2 100644 --- a/scm-ui/ui-components/src/MarkdownView.tsx +++ b/scm-ui/ui-components/src/MarkdownView.tsx @@ -122,7 +122,7 @@ class MarkdownView extends React.Component {
(this.contentRef = el)}>

Here is markdown with code, but without Language. @@ -33022,7 +33022,7 @@ exports[`Storyshots MarkdownView Default 1`] = ` >

Xml Code Block in Markdown diff --git a/scm-ui/ui-components/src/layout/CustomQueryFlexWrappedColumns.tsx b/scm-ui/ui-components/src/layout/CustomQueryFlexWrappedColumns.tsx new file mode 100644 index 0000000000..7c9b42d743 --- /dev/null +++ b/scm-ui/ui-components/src/layout/CustomQueryFlexWrappedColumns.tsx @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { ReactNode } from "react"; +import styled from "styled-components"; + +type Props = { + children?: ReactNode; +}; + +const FlexWrapped = styled.div` + /* Do not wrap before using the media query, + otherwise long content will always break the navigation. */ + @media (max-width: 785px) { + flex-wrap: wrap; + } +`; + +export default class CustomQueryFlexWrappedColumns extends React.Component { + render() { + return {this.props.children}; + } +} diff --git a/scm-ui/ui-components/src/layout/PrimaryContentColumn.tsx b/scm-ui/ui-components/src/layout/PrimaryContentColumn.tsx new file mode 100644 index 0000000000..76427dc4e0 --- /dev/null +++ b/scm-ui/ui-components/src/layout/PrimaryContentColumn.tsx @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { ReactNode } from "react"; +import styled from "styled-components"; + +type Props = { + children?: ReactNode; + collapsed: boolean; +}; + +const PrimaryColumn = styled.div<{ collapsed: boolean }>` + /* This is the counterpart to the specific column in SecondaryNavigationColumn. */ + flex: none; + width: ${(props: { collapsed: boolean }) => (props.collapsed ? "89.7%" : "75%")}; + /* Render this column to full size if column construct breaks (page size too small). */ + @media (max-width: 785px) { + width: 100%; + } +`; + +export default class PrimaryContentColumn extends React.Component { + static defaultProps = { + collapsed: false + }; + + render() { + const { children, collapsed } = this.props; + + return ( + + {children} + + ); + } +} diff --git a/scm-ui/ui-components/src/layout/SecondaryNavigationColumn.tsx b/scm-ui/ui-components/src/layout/SecondaryNavigationColumn.tsx new file mode 100644 index 0000000000..aae722bbe3 --- /dev/null +++ b/scm-ui/ui-components/src/layout/SecondaryNavigationColumn.tsx @@ -0,0 +1,59 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { ReactNode } from "react"; +import styled from "styled-components"; + +type Props = { + children?: ReactNode; + collapsed: boolean; +}; + +const SecondaryColumn = styled.div<{ collapsed: boolean }>` + /* In Bulma there is unfortunately no intermediate step between .is-1 and .is-2, hence the size. + Navigation size should be as constant as possible. */ + flex: none; + width: ${props => (props.collapsed ? "5.5rem" : "20.5rem")}; + max-width: ${(props: { collapsed: boolean }) => (props.collapsed ? "11.3%" : "25%")}; + /* Render this column to full size if column construct breaks (page size too small). */ + @media (max-width: 785px) { + width: 100%; + max-width: 100%; + } +`; + +export default class SecondaryNavigationColumn extends React.Component { + static defaultProps = { + collapsed: false + }; + + render() { + const { children, collapsed } = this.props; + + return ( + + {children} + + ); + } +} diff --git a/scm-ui/ui-components/src/layout/index.ts b/scm-ui/ui-components/src/layout/index.ts index abba371ea3..2c9f7fba72 100644 --- a/scm-ui/ui-components/src/layout/index.ts +++ b/scm-ui/ui-components/src/layout/index.ts @@ -31,3 +31,7 @@ export { default as Page } from "./Page"; export { default as PageActions } from "./PageActions"; export { default as Subtitle } from "./Subtitle"; export { default as Title } from "./Title"; +export { default as CustomQueryFlexWrappedColumns } from "./CustomQueryFlexWrappedColumns"; +export { default as PrimaryContentColumn } from "./PrimaryContentColumn"; +export { default as SecondaryNavigationColumn } from "./SecondaryNavigationColumn"; + diff --git a/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx b/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx index b8b8531365..fd8e1dd3eb 100644 --- a/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx +++ b/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx @@ -38,11 +38,10 @@ type CollapsedProps = { collapsed: boolean; }; -const SectionContainer = styled.aside` +const SectionContainer = styled.aside` position: sticky; position: -webkit-sticky; /* Safari */ top: 2rem; - width: ${props => (props.collapsed ? "5.5rem" : "20.5rem")}; `; const Icon = styled.i` @@ -80,7 +79,7 @@ const SecondaryNavigation: FC = ({ label, children, collapsed, onCollapse const arrowIcon = isCollapsed ? : ; return ( - +
{ value={{ menuCollapsed, setMenuCollapsed: (collapsed: boolean) => this.setState({ menuCollapsed: collapsed }) }} > -
-
+ + @@ -143,8 +146,8 @@ class Admin extends React.Component { } /> -
-
+ + this.onCollapseAdminMenu(!menuCollapsed)} @@ -189,8 +192,8 @@ class Admin extends React.Component { -
-
+ +
); diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx index c0b35a4027..fbec769aaa 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx @@ -27,6 +27,7 @@ import { PendingPlugins } from "@scm-manager/ui-types"; import { WithTranslation, withTranslation } from "react-i18next"; import waitForRestart from "./waitForRestart"; import SuccessNotification from "./SuccessNotification"; +import PendingPluginsQueue from "./PendingPluginsQueue"; type Props = WithTranslation & { onClose: () => void; @@ -85,70 +86,14 @@ class ExecutePendingModal extends React.Component { }); }; - renderInstallQueue = () => { - const { pendingPlugins, t } = this.props; - return ( - <> - {pendingPlugins._embedded && pendingPlugins._embedded.new.length > 0 && ( - <> - {t("plugins.modal.installQueue")} -
    - {pendingPlugins._embedded.new.map(plugin => ( -
  • {plugin.name}
  • - ))} -
- - )} - - ); - }; - - renderUpdateQueue = () => { - const { pendingPlugins, t } = this.props; - return ( - <> - {pendingPlugins._embedded && pendingPlugins._embedded.update.length > 0 && ( - <> - {t("plugins.modal.updateQueue")} -
    - {pendingPlugins._embedded.update.map(plugin => ( -
  • {plugin.name}
  • - ))} -
- - )} - - ); - }; - - renderUninstallQueue = () => { - const { pendingPlugins, t } = this.props; - return ( - <> - {pendingPlugins._embedded && pendingPlugins._embedded.uninstall.length > 0 && ( - <> - {t("plugins.modal.uninstallQueue")} -
    - {pendingPlugins._embedded.uninstall.map(plugin => ( -
  • {plugin.name}
  • - ))} -
- - )} - - ); - }; - renderBody = () => { - const { t } = this.props; + const { pendingPlugins, t } = this.props; return ( <>

{t("plugins.modal.executePending")}

- {this.renderInstallQueue()} - {this.renderUpdateQueue()} - {this.renderUninstallQueue()} +
{this.renderNotifications()}
diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx new file mode 100644 index 0000000000..f8fd298674 --- /dev/null +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx @@ -0,0 +1,66 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC } from "react"; +import { PendingPlugins } from "@scm-manager/ui-types"; +import { useTranslation } from "react-i18next"; + +type Props = { + pendingPlugins: PendingPlugins; +}; + +type SectionProps = Props & { + type: string; + label: string; +}; + +const Section: FC = ({ pendingPlugins, type, label }) => { + const plugins = pendingPlugins?._embedded[type]; + if (!plugins || plugins.length === 0) { + return null; + } + return ( + <> + {label} +
    + {plugins.map(plugin => ( +
  • {plugin.name}
  • + ))} +
+ + ); +}; + +const PendingPluginsQueue: FC = ({ pendingPlugins }) => { + const [t] = useTranslation("admin"); + return ( + <> +
+
+
+ + ); +}; + +export default PendingPluginsQueue; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx index 7c1d12a6b2..1074588e59 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx @@ -214,8 +214,25 @@ class PluginModal extends React.Component { }); }; - render() { + createRestartSectionContent = () => { const { restart } = this.state; + const { plugin, pluginAction, t } = this.props; + + if (plugin._links[pluginAction + "WithRestart"]) { + return ( + + ); + } else { + return {t("plugins.modal.manualRestartRequired")}; + } + }; + + render() { const { plugin, pluginAction, onClose, t } = this.props; const body = ( @@ -262,12 +279,7 @@ class PluginModal extends React.Component {
- + {this.createRestartSectionContent()}
{this.renderNotifications()} diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/ShowPendingModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/ShowPendingModal.tsx new file mode 100644 index 0000000000..d28b51f927 --- /dev/null +++ b/scm-ui/ui-webapp/src/admin/plugins/components/ShowPendingModal.tsx @@ -0,0 +1,70 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC } from "react"; +import { Button, Modal, Notification } from "@scm-manager/ui-components"; +import { PendingPlugins } from "@scm-manager/ui-types"; +import { useTranslation } from "react-i18next"; +import PendingPluginsQueue from "./PendingPluginsQueue"; + + +type ModalBodyProps = { + pendingPlugins: PendingPlugins; +}; + +const ModalBody: FC = ({ pendingPlugins }) => { + const [t] = useTranslation("admin"); + return ( + <> +
+
+

{t("plugins.modal.showPending")}

+ +
+
+
+ {t("plugins.modal.restartNotification")} +
+ + ); +}; + +type Props = { + onClose: () => void; + pendingPlugins: PendingPlugins; +}; + +const ShowPendingModal: FC = ({ pendingPlugins, onClose }) => { + const [t] = useTranslation("admin"); + return ( + } + footer={