mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 00:45:44 +01:00
implement update function for plugins on frontend / adjust the plugin pending modal to show pending installations and updates
This commit is contained in:
@@ -4,6 +4,7 @@ import type {Collection, Links} from "./hal";
|
|||||||
export type Plugin = {
|
export type Plugin = {
|
||||||
name: string,
|
name: string,
|
||||||
version: string,
|
version: string,
|
||||||
|
newVersion: string,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
description?: string,
|
description?: string,
|
||||||
author: string,
|
author: string,
|
||||||
@@ -24,3 +25,11 @@ export type PluginGroup = {
|
|||||||
name: string,
|
name: string,
|
||||||
plugins: Plugin[]
|
plugins: Plugin[]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PendingPlugins = {
|
||||||
|
_links: Links,
|
||||||
|
_embedded: {
|
||||||
|
new: [],
|
||||||
|
update: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export type { SubRepository, File } from "./Sources";
|
|||||||
|
|
||||||
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
||||||
|
|
||||||
export type { Plugin, PluginCollection, PluginGroup } from "./Plugin";
|
export type { Plugin, PluginCollection, PluginGroup, PendingPlugins } from "./Plugin";
|
||||||
|
|
||||||
export type { RepositoryRole } from "./RepositoryRole";
|
export type { RepositoryRole } from "./RepositoryRole";
|
||||||
|
|
||||||
|
|||||||
@@ -29,22 +29,32 @@
|
|||||||
"installedNavLink": "Installiert",
|
"installedNavLink": "Installiert",
|
||||||
"availableNavLink": "Verfügbar"
|
"availableNavLink": "Verfügbar"
|
||||||
},
|
},
|
||||||
"installPending": "Austehende Plugins installieren",
|
"executePending": "Austehende Plugin-Änderungen ausführen",
|
||||||
"noPlugins": "Keine Plugins gefunden.",
|
"noPlugins": "Keine Plugins gefunden.",
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "{{name}} Plugin installieren",
|
"title": {
|
||||||
|
"install": "{{name}} Plugin installieren",
|
||||||
|
"update": "{{name}} Plugin aktualisieren"
|
||||||
|
},
|
||||||
"restart": "Neustarten um Plugin zu aktivieren",
|
"restart": "Neustarten um Plugin zu aktivieren",
|
||||||
"install": "Installieren",
|
"install": "Installieren",
|
||||||
|
"update": "Aktualisieren",
|
||||||
|
"installQueue": "Werden installiert:",
|
||||||
|
"updateQueue": "Werden aktualisiert:",
|
||||||
"installAndRestart": "Installieren und Neustarten",
|
"installAndRestart": "Installieren und Neustarten",
|
||||||
|
"updateAndRestart": "Aktualisieren und Neustarten",
|
||||||
|
"executeAndRestart": "Ausführen und Neustarten",
|
||||||
"abort": "Abbrechen",
|
"abort": "Abbrechen",
|
||||||
"author": "Autor",
|
"author": "Autor",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
"currentVersion": "Installierte Version",
|
||||||
|
"newVersion": "Neue Version",
|
||||||
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert, wenn sie noch nicht vorhanden sind!",
|
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert, wenn sie noch nicht vorhanden sind!",
|
||||||
"dependencies": "Abhängigkeiten",
|
"dependencies": "Abhängigkeiten",
|
||||||
"successNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",
|
"successNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",
|
||||||
"reload": "jetzt neu laden",
|
"reload": "jetzt neu laden",
|
||||||
"restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.",
|
"restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.",
|
||||||
"installPending": "Die folgenden Plugins werden installiert. 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."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repositoryRole": {
|
"repositoryRole": {
|
||||||
|
|||||||
@@ -29,22 +29,32 @@
|
|||||||
"installedNavLink": "Installed",
|
"installedNavLink": "Installed",
|
||||||
"availableNavLink": "Available"
|
"availableNavLink": "Available"
|
||||||
},
|
},
|
||||||
"installPending": "Install pending plugins",
|
"executePending": "Execute pending plugin changes",
|
||||||
"noPlugins": "No plugins found.",
|
"noPlugins": "No plugins found.",
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Install {{name}} Plugin",
|
"title": {
|
||||||
|
"install": "Install {{name}} Plugin",
|
||||||
|
"update": "Update {{name}} Plugin"
|
||||||
|
},
|
||||||
"restart": "Restart to activate",
|
"restart": "Restart to activate",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
|
"update": "Update",
|
||||||
|
"installQueue": "Will be installed:",
|
||||||
|
"updateQueue": "Will be updated:",
|
||||||
"installAndRestart": "Install and Restart",
|
"installAndRestart": "Install and Restart",
|
||||||
|
"updateAndRestart": "Update and Restart",
|
||||||
|
"executeAndRestart": "Execute and Restart",
|
||||||
"abort": "Abort",
|
"abort": "Abort",
|
||||||
"author": "Author",
|
"author": "Author",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
"currentVersion": "Installed version",
|
||||||
|
"newVersion": "New version",
|
||||||
"dependencyNotification": "With this plugin, the following dependencies will be installed if they are not available yet!",
|
"dependencyNotification": "With this plugin, the following dependencies will be installed if they are not available yet!",
|
||||||
"dependencies": "Dependencies",
|
"dependencies": "Dependencies",
|
||||||
"successNotification": "Successful installed plugin. You have to reload the page, to see ui changes:",
|
"successNotification": "Successful installed plugin. You have to reload the page, to see ui changes:",
|
||||||
"reload": "reload now",
|
"reload": "reload now",
|
||||||
"restartNotification": "You should only restart the scm-manager context if no one else is currently working with it.",
|
"restartNotification": "You should only restart the scm-manager context if no one else is currently working with it.",
|
||||||
"installPending": "The following plugins will be installed and after installation 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."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repositoryRole": {
|
"repositoryRole": {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button } from "@scm-manager/ui-components";
|
import { Button } from "@scm-manager/ui-components";
|
||||||
import type { PluginCollection } from "@scm-manager/ui-types";
|
import type { PendingPlugins } from "@scm-manager/ui-types";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import InstallPendingModal from "./InstallPendingModal";
|
import ExecutePendingModal from "./ExecutePendingModal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collection: PluginCollection,
|
pendingPlugins: PendingPlugins,
|
||||||
|
|
||||||
// context props
|
// context props
|
||||||
t: string => string
|
t: string => string
|
||||||
@@ -16,7 +16,7 @@ type State = {
|
|||||||
showModal: boolean
|
showModal: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class InstallPendingAction extends React.Component<Props, State> {
|
class ExecutePendingAction extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -38,11 +38,11 @@ class InstallPendingAction extends React.Component<Props, State> {
|
|||||||
|
|
||||||
renderModal = () => {
|
renderModal = () => {
|
||||||
const { showModal } = this.state;
|
const { showModal } = this.state;
|
||||||
const { collection } = this.props;
|
const { pendingPlugins } = this.props;
|
||||||
if (showModal) {
|
if (showModal) {
|
||||||
return (
|
return (
|
||||||
<InstallPendingModal
|
<ExecutePendingModal
|
||||||
collection={collection}
|
pendingPlugins={pendingPlugins}
|
||||||
onClose={this.closeModal}
|
onClose={this.closeModal}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -57,7 +57,7 @@ class InstallPendingAction extends React.Component<Props, State> {
|
|||||||
{this.renderModal()}
|
{this.renderModal()}
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
label={t("plugins.installPending")}
|
label={t("plugins.executePending")}
|
||||||
action={this.openModal}
|
action={this.openModal}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -65,4 +65,4 @@ class InstallPendingAction extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("admin")(InstallPendingAction);
|
export default translate("admin")(ExecutePendingAction);
|
||||||
@@ -8,14 +8,14 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Notification
|
Notification
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import type { PluginCollection } from "@scm-manager/ui-types";
|
import type { PendingPlugins } from "@scm-manager/ui-types";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import waitForRestart from "./waitForRestart";
|
import waitForRestart from "./waitForRestart";
|
||||||
import InstallSuccessNotification from "./InstallSuccessNotification";
|
import InstallSuccessNotification from "./SuccessNotification";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
collection: PluginCollection,
|
pendingPlugins: PendingPlugins,
|
||||||
|
|
||||||
// context props
|
// context props
|
||||||
t: string => string
|
t: string => string
|
||||||
@@ -27,7 +27,7 @@ type State = {
|
|||||||
error?: Error
|
error?: Error
|
||||||
};
|
};
|
||||||
|
|
||||||
class InstallPendingModal extends React.Component<Props, State> {
|
class ExecutePendingModal extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -53,13 +53,13 @@ class InstallPendingModal extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
installAndRestart = () => {
|
installAndRestart = () => {
|
||||||
const { collection } = this.props;
|
const { pendingPlugins } = this.props;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
apiClient
|
apiClient
|
||||||
.post(collection._links.installPending.href)
|
.post(pendingPlugins._links.execute.href)
|
||||||
.then(waitForRestart)
|
.then(waitForRestart)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -77,22 +77,57 @@ class InstallPendingModal extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderInstallQueue = () => {
|
||||||
|
const { pendingPlugins, t } = this.props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{pendingPlugins._embedded &&
|
||||||
|
pendingPlugins._embedded.new.length > 0 && (
|
||||||
|
<>
|
||||||
|
<strong>{t("plugins.modal.installQueue")}</strong>
|
||||||
|
<ul>
|
||||||
|
{pendingPlugins._embedded.new
|
||||||
|
.filter(plugin => plugin.pending)
|
||||||
|
.map(plugin => (
|
||||||
|
<li key={plugin.name}>{plugin.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderUpdateQueue = () => {
|
||||||
|
const { pendingPlugins, t } = this.props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{pendingPlugins._embedded &&
|
||||||
|
pendingPlugins._embedded.update.length > 0 && (
|
||||||
|
<>
|
||||||
|
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||||
|
<ul>
|
||||||
|
{pendingPlugins._embedded.update
|
||||||
|
.filter(plugin => plugin.pending)
|
||||||
|
.map(plugin => (
|
||||||
|
<li key={plugin.name}>{plugin.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
renderBody = () => {
|
renderBody = () => {
|
||||||
const { collection, t } = this.props;
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="media">
|
<div className="media">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<p>{t("plugins.modal.installPending")}</p>
|
<p>{t("plugins.modal.executePending")}</p>
|
||||||
<ul>
|
{this.renderInstallQueue()}
|
||||||
{collection._embedded.plugins
|
{this.renderUpdateQueue()}
|
||||||
.filter(plugin => plugin.pending)
|
|
||||||
.map(plugin => (
|
|
||||||
<li key={plugin.name} className="has-text-weight-bold">
|
|
||||||
{plugin.name}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="media">{this.renderNotifications()}</div>
|
<div className="media">{this.renderNotifications()}</div>
|
||||||
@@ -107,7 +142,7 @@ class InstallPendingModal extends React.Component<Props, State> {
|
|||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
label={t("plugins.modal.installAndRestart")}
|
label={t("plugins.modal.executeAndRestart")}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
action={this.installAndRestart}
|
action={this.installAndRestart}
|
||||||
disabled={error || success}
|
disabled={error || success}
|
||||||
@@ -121,7 +156,7 @@ class InstallPendingModal extends React.Component<Props, State> {
|
|||||||
const { onClose, t } = this.props;
|
const { onClose, t } = this.props;
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t("plugins.modal.installAndRestart")}
|
title={t("plugins.modal.executeAndRestart")}
|
||||||
closeFunction={onClose}
|
closeFunction={onClose}
|
||||||
body={this.renderBody()}
|
body={this.renderBody()}
|
||||||
footer={this.renderFooter()}
|
footer={this.renderFooter()}
|
||||||
@@ -131,4 +166,4 @@ class InstallPendingModal extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("admin")(InstallPendingModal);
|
export default translate("admin")(ExecutePendingModal);
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import waitForRestart from "./waitForRestart";
|
import waitForRestart from "./waitForRestart";
|
||||||
import InstallSuccessNotification from "./InstallSuccessNotification";
|
import SuccessNotification from "./SuccessNotification";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
plugin: Plugin,
|
plugin: Plugin,
|
||||||
@@ -45,7 +45,7 @@ const styles = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class PluginModal extends React.Component<Props, State> {
|
class InstallPluginModal extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -162,7 +162,7 @@ class PluginModal extends React.Component<Props, State> {
|
|||||||
} else if (success) {
|
} else if (success) {
|
||||||
return (
|
return (
|
||||||
<div className="media">
|
<div className="media">
|
||||||
<InstallSuccessNotification />
|
<SuccessNotification />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (restart) {
|
} else if (restart) {
|
||||||
@@ -252,7 +252,7 @@ class PluginModal extends React.Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t("plugins.modal.title", {
|
title={t("plugins.modal.title.install", {
|
||||||
name: plugin.displayName ? plugin.displayName : plugin.name
|
name: plugin.displayName ? plugin.displayName : plugin.name
|
||||||
})}
|
})}
|
||||||
closeFunction={() => onClose()}
|
closeFunction={() => onClose()}
|
||||||
@@ -267,4 +267,4 @@ class PluginModal extends React.Component<Props, State> {
|
|||||||
export default compose(
|
export default compose(
|
||||||
injectSheet(styles),
|
injectSheet(styles),
|
||||||
translate("admin")
|
translate("admin")
|
||||||
)(PluginModal);
|
)(InstallPluginModal);
|
||||||
@@ -4,8 +4,9 @@ import injectSheet from "react-jss";
|
|||||||
import type { Plugin } from "@scm-manager/ui-types";
|
import type { Plugin } from "@scm-manager/ui-types";
|
||||||
import { CardColumn } from "@scm-manager/ui-components";
|
import { CardColumn } from "@scm-manager/ui-components";
|
||||||
import PluginAvatar from "./PluginAvatar";
|
import PluginAvatar from "./PluginAvatar";
|
||||||
import PluginModal from "./PluginModal";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import InstallPluginModal from "./InstallPluginModal";
|
||||||
|
import UpdatePluginModal from "./UpdatePluginModal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
plugin: Plugin,
|
plugin: Plugin,
|
||||||
@@ -22,9 +23,15 @@ type State = {
|
|||||||
const styles = {
|
const styles = {
|
||||||
link: {
|
link: {
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
pointerEvents: "all"
|
pointerEvents: "all",
|
||||||
|
padding: "0.5rem",
|
||||||
|
border: "solid 1px var(--dark-25)",
|
||||||
|
borderRadius: "4px",
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "var(--dark-50)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
spinner: {
|
topRight: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 0
|
top: 0
|
||||||
@@ -59,17 +66,52 @@ class PluginEntry extends React.Component<Props, State> {
|
|||||||
return plugin._links && plugin._links.install && plugin._links.install.href;
|
return plugin._links && plugin._links.install && plugin._links.install.href;
|
||||||
};
|
};
|
||||||
|
|
||||||
createFooterLeft = () => {
|
isUpdatable = () => {
|
||||||
|
const { plugin } = this.props;
|
||||||
|
return plugin._links && plugin._links.update && plugin._links.update.href;
|
||||||
|
};
|
||||||
|
|
||||||
|
createActionbar = () => {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
if (this.isInstallable()) {
|
if (this.isInstallable()) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={classNames(classes.link, "level-item")}
|
className={classNames(classes.link, classes.topRight, "level-item")}
|
||||||
onClick={this.toggleModal}
|
onClick={this.toggleModal}
|
||||||
>
|
>
|
||||||
<i className="fas fa-download has-text-info" />
|
<i className="fas fa-download has-text-info" />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
} else if (this.isUpdatable()) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={classNames(classes.link, classes.topRight, "level-item")}
|
||||||
|
onClick={this.toggleModal}
|
||||||
|
>
|
||||||
|
<i className="fas fa-sync-alt has-text-info" />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderModal = () => {
|
||||||
|
const { plugin, refresh } = this.props;
|
||||||
|
if (this.isInstallable()) {
|
||||||
|
return (
|
||||||
|
<InstallPluginModal
|
||||||
|
plugin={plugin}
|
||||||
|
refresh={refresh}
|
||||||
|
onClose={this.toggleModal}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (this.isUpdatable()) {
|
||||||
|
return (
|
||||||
|
<UpdatePluginModal
|
||||||
|
plugin={plugin}
|
||||||
|
refresh={refresh}
|
||||||
|
onClose={this.toggleModal}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,7 +119,7 @@ class PluginEntry extends React.Component<Props, State> {
|
|||||||
const { plugin, classes } = this.props;
|
const { plugin, classes } = this.props;
|
||||||
if (plugin.pending) {
|
if (plugin.pending) {
|
||||||
return (
|
return (
|
||||||
<span className={classes.spinner}>
|
<span className={classes.topRight}>
|
||||||
<i className="fas fa-spinner fa-spin has-text-info" />
|
<i className="fas fa-spinner fa-spin has-text-info" />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -86,19 +128,13 @@ class PluginEntry extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { plugin, refresh } = this.props;
|
const { plugin } = this.props;
|
||||||
const { showModal } = this.state;
|
const { showModal } = this.state;
|
||||||
const avatar = this.createAvatar(plugin);
|
const avatar = this.createAvatar(plugin);
|
||||||
const footerLeft = this.createFooterLeft();
|
const actionbar = this.createActionbar();
|
||||||
const footerRight = this.createFooterRight(plugin);
|
const footerRight = this.createFooterRight(plugin);
|
||||||
|
|
||||||
const modal = showModal ? (
|
const modal = showModal ? this.renderModal() : null;
|
||||||
<PluginModal
|
|
||||||
plugin={plugin}
|
|
||||||
refresh={refresh}
|
|
||||||
onClose={this.toggleModal}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -107,8 +143,9 @@ class PluginEntry extends React.Component<Props, State> {
|
|||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
title={plugin.displayName ? plugin.displayName : plugin.name}
|
title={plugin.displayName ? plugin.displayName : plugin.name}
|
||||||
description={plugin.description}
|
description={plugin.description}
|
||||||
contentRight={this.createPendingSpinner()}
|
contentRight={
|
||||||
footerLeft={footerLeft}
|
plugin.pending ? this.createPendingSpinner() : actionbar
|
||||||
|
}
|
||||||
footerRight={footerRight}
|
footerRight={footerRight}
|
||||||
/>
|
/>
|
||||||
{modal}
|
{modal}
|
||||||
|
|||||||
288
scm-ui/src/admin/plugins/components/UpdatePluginModal.js
Normal file
288
scm-ui/src/admin/plugins/components/UpdatePluginModal.js
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { compose } from "redux";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import type { Plugin } from "@scm-manager/ui-types";
|
||||||
|
import {
|
||||||
|
apiClient,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Checkbox,
|
||||||
|
ErrorNotification,
|
||||||
|
Modal,
|
||||||
|
Notification
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import waitForRestart from "./waitForRestart";
|
||||||
|
import SuccessNotification from "./SuccessNotification";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
plugin: Plugin,
|
||||||
|
refresh: () => void,
|
||||||
|
onClose: () => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
classes: any,
|
||||||
|
t: (key: string, params?: Object) => string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
success: boolean,
|
||||||
|
restart: boolean,
|
||||||
|
loading: boolean,
|
||||||
|
error?: Error
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
userLabelAlignment: {
|
||||||
|
textAlign: "left",
|
||||||
|
marginRight: 0,
|
||||||
|
minWidth: "9em"
|
||||||
|
},
|
||||||
|
userFieldFlex: {
|
||||||
|
flexGrow: 4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class UpdatePluginModal extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: false,
|
||||||
|
restart: false,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateSuccess = () => {
|
||||||
|
const { restart } = this.state;
|
||||||
|
const { refresh, onClose } = this.props;
|
||||||
|
|
||||||
|
const newState = {
|
||||||
|
loading: false,
|
||||||
|
error: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
if (restart) {
|
||||||
|
waitForRestart()
|
||||||
|
.then(() => {
|
||||||
|
this.setState({
|
||||||
|
...newState,
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
success: false,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState(newState, () => {
|
||||||
|
refresh();
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
update = (e: Event) => {
|
||||||
|
const { restart } = this.state;
|
||||||
|
const { plugin } = this.props;
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
apiClient
|
||||||
|
.post(plugin._links.update.href + "?restart=" + restart.toString())
|
||||||
|
.then(this.onUpdateSuccess)
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
error: error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
footer = () => {
|
||||||
|
const { onClose, t } = this.props;
|
||||||
|
const { loading, error, restart, success } = this.state;
|
||||||
|
|
||||||
|
let color = "primary";
|
||||||
|
let label = "plugins.modal.update";
|
||||||
|
if (restart) {
|
||||||
|
color = "warning";
|
||||||
|
label = "plugins.modal.updateAndRestart";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
label={t(label)}
|
||||||
|
color={color}
|
||||||
|
action={this.update}
|
||||||
|
loading={loading}
|
||||||
|
disabled={!!error || success}
|
||||||
|
/>
|
||||||
|
<Button label={t("plugins.modal.abort")} action={onClose} />
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDependencies() {
|
||||||
|
const { plugin, classes, t } = this.props;
|
||||||
|
|
||||||
|
let dependencies = null;
|
||||||
|
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
||||||
|
dependencies = (
|
||||||
|
<div className="media">
|
||||||
|
<Notification type="warning">
|
||||||
|
<strong>{t("plugins.modal.dependencyNotification")}</strong>
|
||||||
|
<ul className={classes.listSpacing}>
|
||||||
|
{plugin.dependencies.map((dependency, index) => {
|
||||||
|
return <li key={index}>{dependency}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</Notification>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNotifications = () => {
|
||||||
|
const { t } = this.props;
|
||||||
|
const { restart, error, success } = this.state;
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="media">
|
||||||
|
<ErrorNotification error={error} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (success) {
|
||||||
|
return (
|
||||||
|
<div className="media">
|
||||||
|
<SuccessNotification />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (restart) {
|
||||||
|
return (
|
||||||
|
<div className="media">
|
||||||
|
<Notification type="warning">
|
||||||
|
{t("plugins.modal.restartNotification")}
|
||||||
|
</Notification>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRestartChange = (value: boolean) => {
|
||||||
|
this.setState({
|
||||||
|
restart: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { restart } = this.state;
|
||||||
|
const { plugin, onClose, classes, t } = this.props;
|
||||||
|
|
||||||
|
const body = (
|
||||||
|
<>
|
||||||
|
<div className="media">
|
||||||
|
<div className="media-content">
|
||||||
|
<p>{plugin.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="media">
|
||||||
|
<div className="media-content">
|
||||||
|
<div className="field is-horizontal">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.userLabelAlignment,
|
||||||
|
"field-label is-inline-flex"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t("plugins.modal.author")}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.userFieldFlex,
|
||||||
|
"field-body is-inline-flex"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{plugin.author}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="field is-horizontal">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.userLabelAlignment,
|
||||||
|
"field-label is-inline-flex"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t("plugins.modal.currentVersion")}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.userFieldFlex,
|
||||||
|
"field-body is-inline-flex"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{plugin.version}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="field is-horizontal">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.userLabelAlignment,
|
||||||
|
"field-label is-inline-flex"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t("plugins.modal.newVersion")}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.userFieldFlex,
|
||||||
|
"field-body is-inline-flex"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{plugin.newVersion}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.renderDependencies()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="media">
|
||||||
|
<div className="media-content">
|
||||||
|
<Checkbox
|
||||||
|
checked={restart}
|
||||||
|
label={t("plugins.modal.restart")}
|
||||||
|
onChange={this.handleRestartChange}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.renderNotifications()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("plugins.modal.title.update", {
|
||||||
|
name: plugin.displayName ? plugin.displayName : plugin.name
|
||||||
|
})}
|
||||||
|
closeFunction={() => onClose()}
|
||||||
|
body={body}
|
||||||
|
footer={this.footer()}
|
||||||
|
active={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
injectSheet(styles),
|
||||||
|
translate("admin")
|
||||||
|
)(UpdatePluginModal);
|
||||||
@@ -3,28 +3,31 @@ import * as React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { compose } from "redux";
|
import { compose } from "redux";
|
||||||
import type { PluginCollection } from "@scm-manager/ui-types";
|
import type { PendingPlugins, PluginCollection } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
|
ErrorNotification,
|
||||||
Loading,
|
Loading,
|
||||||
Title,
|
|
||||||
Subtitle,
|
|
||||||
Notification,
|
Notification,
|
||||||
ErrorNotification
|
Subtitle,
|
||||||
|
Title
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import {
|
import {
|
||||||
|
fetchPendingPlugins,
|
||||||
fetchPluginsByLink,
|
fetchPluginsByLink,
|
||||||
getFetchPluginsFailure,
|
getFetchPluginsFailure,
|
||||||
|
getPendingPlugins,
|
||||||
getPluginCollection,
|
getPluginCollection,
|
||||||
isFetchPluginsPending
|
isFetchPluginsPending
|
||||||
} from "../modules/plugins";
|
} from "../modules/plugins";
|
||||||
import PluginsList from "../components/PluginList";
|
import PluginsList from "../components/PluginList";
|
||||||
import {
|
import {
|
||||||
getAvailablePluginsLink,
|
getAvailablePluginsLink,
|
||||||
getInstalledPluginsLink
|
getInstalledPluginsLink,
|
||||||
|
getPendingPluginsLink
|
||||||
} from "../../../modules/indexResource";
|
} from "../../../modules/indexResource";
|
||||||
import PluginTopActions from "../components/PluginTopActions";
|
import PluginTopActions from "../components/PluginTopActions";
|
||||||
import PluginBottomActions from "../components/PluginBottomActions";
|
import PluginBottomActions from "../components/PluginBottomActions";
|
||||||
import InstallPendingAction from "../components/InstallPendingAction";
|
import ExecutePendingAction from "../components/ExecutePendingAction";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
@@ -34,12 +37,15 @@ type Props = {
|
|||||||
installed: boolean,
|
installed: boolean,
|
||||||
availablePluginsLink: string,
|
availablePluginsLink: string,
|
||||||
installedPluginsLink: string,
|
installedPluginsLink: string,
|
||||||
|
pendingPluginsLink: string,
|
||||||
|
pendingPlugins: PendingPlugins,
|
||||||
|
|
||||||
// context objects
|
// context objects
|
||||||
t: string => string,
|
t: string => string,
|
||||||
|
|
||||||
// dispatched functions
|
// dispatched functions
|
||||||
fetchPluginsByLink: (link: string) => void
|
fetchPluginsByLink: (link: string) => void,
|
||||||
|
fetchPendingPlugins: (link: string) => void
|
||||||
};
|
};
|
||||||
|
|
||||||
class PluginsOverview extends React.Component<Props> {
|
class PluginsOverview extends React.Component<Props> {
|
||||||
@@ -48,15 +54,16 @@ class PluginsOverview extends React.Component<Props> {
|
|||||||
installed,
|
installed,
|
||||||
fetchPluginsByLink,
|
fetchPluginsByLink,
|
||||||
availablePluginsLink,
|
availablePluginsLink,
|
||||||
installedPluginsLink
|
installedPluginsLink,
|
||||||
|
pendingPluginsLink,
|
||||||
|
fetchPendingPlugins
|
||||||
} = this.props;
|
} = this.props;
|
||||||
fetchPluginsByLink(installed ? installedPluginsLink : availablePluginsLink);
|
fetchPluginsByLink(installed ? installedPluginsLink : availablePluginsLink);
|
||||||
|
fetchPendingPlugins(pendingPluginsLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const {
|
const { installed } = this.props;
|
||||||
installed,
|
|
||||||
} = this.props;
|
|
||||||
if (prevProps.installed !== installed) {
|
if (prevProps.installed !== installed) {
|
||||||
this.fetchPlugins();
|
this.fetchPlugins();
|
||||||
}
|
}
|
||||||
@@ -67,11 +74,12 @@ class PluginsOverview extends React.Component<Props> {
|
|||||||
installed,
|
installed,
|
||||||
fetchPluginsByLink,
|
fetchPluginsByLink,
|
||||||
availablePluginsLink,
|
availablePluginsLink,
|
||||||
installedPluginsLink
|
installedPluginsLink,
|
||||||
|
pendingPluginsLink,
|
||||||
|
fetchPendingPlugins
|
||||||
} = this.props;
|
} = this.props;
|
||||||
fetchPluginsByLink(
|
fetchPluginsByLink(installed ? installedPluginsLink : availablePluginsLink);
|
||||||
installed ? installedPluginsLink : availablePluginsLink
|
fetchPendingPlugins(pendingPluginsLink);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHeader = (actions: React.Node) => {
|
renderHeader = (actions: React.Node) => {
|
||||||
@@ -101,9 +109,13 @@ class PluginsOverview extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createActions = () => {
|
createActions = () => {
|
||||||
const { collection } = this.props;
|
const { pendingPlugins } = this.props;
|
||||||
if (collection._links.installPending) {
|
if (
|
||||||
return <InstallPendingAction collection={collection} />;
|
pendingPlugins &&
|
||||||
|
pendingPlugins._links &&
|
||||||
|
pendingPlugins._links.execute
|
||||||
|
) {
|
||||||
|
return <ExecutePendingAction pendingPlugins={pendingPlugins} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -134,7 +146,12 @@ class PluginsOverview extends React.Component<Props> {
|
|||||||
const { collection, t } = this.props;
|
const { collection, t } = this.props;
|
||||||
|
|
||||||
if (collection._embedded && collection._embedded.plugins.length > 0) {
|
if (collection._embedded && collection._embedded.plugins.length > 0) {
|
||||||
return <PluginsList plugins={collection._embedded.plugins} refresh={this.fetchPlugins} />;
|
return (
|
||||||
|
<PluginsList
|
||||||
|
plugins={collection._embedded.plugins}
|
||||||
|
refresh={this.fetchPlugins}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return <Notification type="info">{t("plugins.noPlugins")}</Notification>;
|
return <Notification type="info">{t("plugins.noPlugins")}</Notification>;
|
||||||
}
|
}
|
||||||
@@ -146,13 +163,17 @@ const mapStateToProps = state => {
|
|||||||
const error = getFetchPluginsFailure(state);
|
const error = getFetchPluginsFailure(state);
|
||||||
const availablePluginsLink = getAvailablePluginsLink(state);
|
const availablePluginsLink = getAvailablePluginsLink(state);
|
||||||
const installedPluginsLink = getInstalledPluginsLink(state);
|
const installedPluginsLink = getInstalledPluginsLink(state);
|
||||||
|
const pendingPluginsLink = getPendingPluginsLink(state);
|
||||||
|
const pendingPlugins = getPendingPlugins(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
collection,
|
collection,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
availablePluginsLink,
|
availablePluginsLink,
|
||||||
installedPluginsLink
|
installedPluginsLink,
|
||||||
|
pendingPluginsLink,
|
||||||
|
pendingPlugins
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,6 +181,9 @@ const mapDispatchToProps = dispatch => {
|
|||||||
return {
|
return {
|
||||||
fetchPluginsByLink: (link: string) => {
|
fetchPluginsByLink: (link: string) => {
|
||||||
dispatch(fetchPluginsByLink(link));
|
dispatch(fetchPluginsByLink(link));
|
||||||
|
},
|
||||||
|
fetchPendingPlugins: (link: string) => {
|
||||||
|
dispatch(fetchPendingPlugins(link));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,17 @@ export const FETCH_PLUGIN_PENDING = `${FETCH_PLUGIN}_${types.PENDING_SUFFIX}`;
|
|||||||
export const FETCH_PLUGIN_SUCCESS = `${FETCH_PLUGIN}_${types.SUCCESS_SUFFIX}`;
|
export const FETCH_PLUGIN_SUCCESS = `${FETCH_PLUGIN}_${types.SUCCESS_SUFFIX}`;
|
||||||
export const FETCH_PLUGIN_FAILURE = `${FETCH_PLUGIN}_${types.FAILURE_SUFFIX}`;
|
export const FETCH_PLUGIN_FAILURE = `${FETCH_PLUGIN}_${types.FAILURE_SUFFIX}`;
|
||||||
|
|
||||||
|
export const FETCH_PENDING_PLUGINS = "scm/plugins/FETCH_PENDING_PLUGINS";
|
||||||
|
export const FETCH_PENDING_PLUGINS_PENDING = `${FETCH_PENDING_PLUGINS}_${
|
||||||
|
types.PENDING_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const FETCH_PENDING_PLUGINS_SUCCESS = `${FETCH_PENDING_PLUGINS}_${
|
||||||
|
types.SUCCESS_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const FETCH_PENDING_PLUGINS_FAILURE = `${FETCH_PENDING_PLUGINS}_${
|
||||||
|
types.FAILURE_SUFFIX
|
||||||
|
}`;
|
||||||
|
|
||||||
// fetch plugins
|
// fetch plugins
|
||||||
export function fetchPluginsByLink(link: string) {
|
export function fetchPluginsByLink(link: string) {
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
@@ -105,8 +116,44 @@ export function fetchPluginFailure(name: string, error: Error): Action {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch pending plugins
|
||||||
|
export function fetchPendingPlugins(link: string) {
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(fetchPendingPluginsPending());
|
||||||
|
return apiClient
|
||||||
|
.get(link)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(PendingPlugins => {
|
||||||
|
dispatch(fetchPendingPluginsSuccess(PendingPlugins));
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dispatch(fetchPendingPluginsFailure(err));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPendingPluginsPending(): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_PENDING_PLUGINS_PENDING
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPendingPluginsSuccess(PendingPlugins: {}): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_PENDING_PLUGINS_SUCCESS,
|
||||||
|
payload: PendingPlugins
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPendingPluginsFailure(err: Error): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_PENDING_PLUGINS_FAILURE,
|
||||||
|
payload: err
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// reducer
|
// reducer
|
||||||
function normalizeByName(pluginCollection: PluginCollection) {
|
function normalizeByName(state: Object, pluginCollection: PluginCollection) {
|
||||||
const names = [];
|
const names = [];
|
||||||
const byNames = {};
|
const byNames = {};
|
||||||
for (const plugin of pluginCollection._embedded.plugins) {
|
for (const plugin of pluginCollection._embedded.plugins) {
|
||||||
@@ -114,6 +161,7 @@ function normalizeByName(pluginCollection: PluginCollection) {
|
|||||||
byNames[plugin.name] = plugin;
|
byNames[plugin.name] = plugin;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
...state,
|
||||||
list: {
|
list: {
|
||||||
...pluginCollection,
|
...pluginCollection,
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -144,9 +192,11 @@ export default function reducer(
|
|||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case FETCH_PLUGINS_SUCCESS:
|
case FETCH_PLUGINS_SUCCESS:
|
||||||
return normalizeByName(action.payload);
|
return normalizeByName(state, action.payload);
|
||||||
case FETCH_PLUGIN_SUCCESS:
|
case FETCH_PLUGIN_SUCCESS:
|
||||||
return reducerByNames(state, action.payload);
|
return reducerByNames(state, action.payload);
|
||||||
|
case FETCH_PENDING_PLUGINS_SUCCESS:
|
||||||
|
return { ...state, pending: action.payload };
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -189,3 +239,17 @@ export function isFetchPluginPending(state: Object, name: string) {
|
|||||||
export function getFetchPluginFailure(state: Object, name: string) {
|
export function getFetchPluginFailure(state: Object, name: string) {
|
||||||
return getFailure(state, FETCH_PLUGIN, name);
|
return getFailure(state, FETCH_PLUGIN, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPendingPlugins(state: Object) {
|
||||||
|
if (state.plugins && state.plugins.pending) {
|
||||||
|
return state.plugins.pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchPendingPluginsPending(state: Object) {
|
||||||
|
return isPending(state, FETCH_PENDING_PLUGINS);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFetchPendingPluginsFailure(state: Object) {
|
||||||
|
return getFailure(state, FETCH_PENDING_PLUGINS);
|
||||||
|
}
|
||||||
|
|||||||
@@ -124,6 +124,10 @@ export function getInstalledPluginsLink(state: Object) {
|
|||||||
return getLink(state, "installedPlugins");
|
return getLink(state, "installedPlugins");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPendingPluginsLink(state: Object) {
|
||||||
|
return getLink(state, "pendingPlugins");
|
||||||
|
}
|
||||||
|
|
||||||
export function getMeLink(state: Object) {
|
export function getMeLink(state: Object) {
|
||||||
return getLink(state, "me");
|
return getLink(state, "me");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,13 +64,16 @@ public class PendingPluginResource {
|
|||||||
|
|
||||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.pendingPluginCollection().self());
|
Links.Builder linksBuilder = linkingTo().self(resourceLinks.pendingPluginCollection().self());
|
||||||
|
|
||||||
if (!pending.isEmpty()) {
|
List<PluginDto> newPluginDtos = newPlugins.map(mapper::mapAvailable).collect(toList());
|
||||||
linksBuilder.single(link("install", resourceLinks.pendingPluginCollection().installPending()));
|
List<PluginDto> updatePluginDtos = updatePlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList());
|
||||||
|
|
||||||
|
if (newPluginDtos.size() > 0 || updatePluginDtos.size() > 0) {
|
||||||
|
linksBuilder.single(link("execute", resourceLinks.pendingPluginCollection().installPending()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Embedded.Builder embedded = Embedded.embeddedBuilder();
|
Embedded.Builder embedded = Embedded.embeddedBuilder();
|
||||||
embedded.with("new", newPlugins.map(mapper::mapAvailable).collect(toList()));
|
embedded.with("new", newPluginDtos);
|
||||||
embedded.with("update", updatePlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()));
|
embedded.with("update", updatePluginDtos);
|
||||||
|
|
||||||
return Response.ok(new HalRepresentation(linksBuilder.build(), embedded.build())).build();
|
return Response.ok(new HalRepresentation(linksBuilder.build(), embedded.build())).build();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user