mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 00:15:44 +01:00
merge 2.0.0-m3
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Checkbox, InputField, Subtitle } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
baseUrl: string,
|
||||
forceBaseUrl: boolean,
|
||||
t: string => string,
|
||||
onChange: (boolean, any, string) => void,
|
||||
hasUpdatePermission: boolean
|
||||
type Props = WithTranslation & {
|
||||
baseUrl: string;
|
||||
forceBaseUrl: boolean;
|
||||
onChange: (p1: boolean, p2: any, p3: string) => void;
|
||||
hasUpdatePermission: boolean;
|
||||
};
|
||||
|
||||
class BaseUrlSettings extends React.Component<Props> {
|
||||
@@ -50,4 +48,4 @@ class BaseUrlSettings extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("config")(BaseUrlSettings);
|
||||
export default withTranslation("config")(BaseUrlSettings);
|
||||
@@ -1,32 +1,29 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { NamespaceStrategies, Config } from "@scm-manager/ui-types";
|
||||
import { SubmitButton, Notification } from "@scm-manager/ui-components";
|
||||
import type { NamespaceStrategies, Config } from "@scm-manager/ui-types";
|
||||
import ProxySettings from "./ProxySettings";
|
||||
import GeneralSettings from "./GeneralSettings";
|
||||
import BaseUrlSettings from "./BaseUrlSettings";
|
||||
import LoginAttempt from "./LoginAttempt";
|
||||
|
||||
type Props = {
|
||||
submitForm: Config => void,
|
||||
config?: Config,
|
||||
loading?: boolean,
|
||||
configReadPermission: boolean,
|
||||
configUpdatePermission: boolean,
|
||||
namespaceStrategies?: NamespaceStrategies,
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
submitForm: (p: Config) => void;
|
||||
config?: Config;
|
||||
loading?: boolean;
|
||||
configReadPermission: boolean;
|
||||
configUpdatePermission: boolean;
|
||||
namespaceStrategies?: NamespaceStrategies;
|
||||
};
|
||||
|
||||
type State = {
|
||||
config: Config,
|
||||
showNotification: boolean,
|
||||
config: Config;
|
||||
showNotification: boolean;
|
||||
error: {
|
||||
loginAttemptLimitTimeout: boolean,
|
||||
loginAttemptLimit: boolean
|
||||
},
|
||||
changed: boolean
|
||||
loginAttemptLimitTimeout: boolean;
|
||||
loginAttemptLimit: boolean;
|
||||
};
|
||||
changed: boolean;
|
||||
};
|
||||
|
||||
class ConfigForm extends React.Component<Props, State> {
|
||||
@@ -68,10 +65,18 @@ class ConfigForm extends React.Component<Props, State> {
|
||||
componentDidMount() {
|
||||
const { config, configUpdatePermission } = this.props;
|
||||
if (config) {
|
||||
this.setState({ ...this.state, config: { ...config } });
|
||||
this.setState({
|
||||
...this.state,
|
||||
config: {
|
||||
...config
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!configUpdatePermission) {
|
||||
this.setState({ ...this.state, showNotification: true });
|
||||
this.setState({
|
||||
...this.state,
|
||||
showNotification: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,24 +89,13 @@ class ConfigForm extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
t,
|
||||
namespaceStrategies,
|
||||
configReadPermission,
|
||||
configUpdatePermission
|
||||
} = this.props;
|
||||
const { loading, t, namespaceStrategies, configReadPermission, configUpdatePermission } = this.props;
|
||||
const config = this.state.config;
|
||||
|
||||
let noPermissionNotification = null;
|
||||
|
||||
if (!configReadPermission) {
|
||||
return (
|
||||
<Notification
|
||||
type={"danger"}
|
||||
children={t("config.form.no-read-permission-notification")}
|
||||
/>
|
||||
);
|
||||
return <Notification type={"danger"} children={t("config.form.no-read-permission-notification")} />;
|
||||
}
|
||||
|
||||
if (this.state.showNotification) {
|
||||
@@ -128,27 +122,21 @@ class ConfigForm extends React.Component<Props, State> {
|
||||
pluginUrl={config.pluginUrl}
|
||||
enabledXsrfProtection={config.enabledXsrfProtection}
|
||||
namespaceStrategy={config.namespaceStrategy}
|
||||
onChange={(isValid, changedValue, name) =>
|
||||
this.onChange(isValid, changedValue, name)
|
||||
}
|
||||
onChange={(isValid, changedValue, name) => this.onChange(isValid, changedValue, name)}
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<LoginAttempt
|
||||
loginAttemptLimit={config.loginAttemptLimit}
|
||||
loginAttemptLimitTimeout={config.loginAttemptLimitTimeout}
|
||||
onChange={(isValid, changedValue, name) =>
|
||||
this.onChange(isValid, changedValue, name)
|
||||
}
|
||||
onChange={(isValid, changedValue, name) => this.onChange(isValid, changedValue, name)}
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<BaseUrlSettings
|
||||
baseUrl={config.baseUrl}
|
||||
forceBaseUrl={config.forceBaseUrl}
|
||||
onChange={(isValid, changedValue, name) =>
|
||||
this.onChange(isValid, changedValue, name)
|
||||
}
|
||||
onChange={(isValid, changedValue, name) => this.onChange(isValid, changedValue, name)}
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
@@ -159,18 +147,14 @@ class ConfigForm extends React.Component<Props, State> {
|
||||
proxyUser={config.proxyUser ? config.proxyUser : ""}
|
||||
enableProxy={config.enableProxy}
|
||||
proxyExcludes={config.proxyExcludes}
|
||||
onChange={(isValid, changedValue, name) =>
|
||||
this.onChange(isValid, changedValue, name)
|
||||
}
|
||||
onChange={(isValid, changedValue, name) => this.onChange(isValid, changedValue, name)}
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<SubmitButton
|
||||
loading={loading}
|
||||
label={t("config.form.submit")}
|
||||
disabled={
|
||||
!configUpdatePermission || this.hasError() || !this.state.changed
|
||||
}
|
||||
disabled={!configUpdatePermission || this.hasError() || !this.state.changed}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
@@ -192,10 +176,7 @@ class ConfigForm extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
hasError = () => {
|
||||
return (
|
||||
this.state.error.loginAttemptLimit ||
|
||||
this.state.error.loginAttemptLimitTimeout
|
||||
);
|
||||
return this.state.error.loginAttemptLimit || this.state.error.loginAttemptLimitTimeout;
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
@@ -206,4 +187,4 @@ class ConfigForm extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("config")(ConfigForm);
|
||||
export default withTranslation("config")(ConfigForm);
|
||||
@@ -1,25 +1,22 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import {Checkbox, InputField} from "@scm-manager/ui-components";
|
||||
import type {NamespaceStrategies} from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Checkbox, InputField } from "@scm-manager/ui-components";
|
||||
import { NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import NamespaceStrategySelect from "./NamespaceStrategySelect";
|
||||
|
||||
type Props = {
|
||||
realmDescription: string,
|
||||
loginInfoUrl: string,
|
||||
disableGroupingGrid: boolean,
|
||||
dateFormat: string,
|
||||
anonymousAccessEnabled: boolean,
|
||||
skipFailedAuthenticators: boolean,
|
||||
pluginUrl: string,
|
||||
enabledXsrfProtection: boolean,
|
||||
namespaceStrategy: string,
|
||||
namespaceStrategies?: NamespaceStrategies,
|
||||
onChange: (boolean, any, string) => void,
|
||||
hasUpdatePermission: boolean,
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
realmDescription: string;
|
||||
loginInfoUrl: string;
|
||||
disableGroupingGrid: boolean;
|
||||
dateFormat: string;
|
||||
anonymousAccessEnabled: boolean;
|
||||
skipFailedAuthenticators: boolean;
|
||||
pluginUrl: string;
|
||||
enabledXsrfProtection: boolean;
|
||||
namespaceStrategy: string;
|
||||
namespaceStrategies?: NamespaceStrategies;
|
||||
onChange: (p1: boolean, p2: any, p3: string) => void;
|
||||
hasUpdatePermission: boolean;
|
||||
};
|
||||
|
||||
class GeneralSettings extends React.Component<Props> {
|
||||
@@ -123,4 +120,4 @@ class GeneralSettings extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("config")(GeneralSettings);
|
||||
export default withTranslation("config")(GeneralSettings);
|
||||
@@ -1,23 +1,17 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
InputField,
|
||||
Subtitle,
|
||||
validation as validator
|
||||
} from "@scm-manager/ui-components";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { InputField, Subtitle, validation as validator } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
loginAttemptLimit: number,
|
||||
loginAttemptLimitTimeout: number,
|
||||
t: string => string,
|
||||
onChange: (boolean, any, string) => void,
|
||||
hasUpdatePermission: boolean
|
||||
type Props = WithTranslation & {
|
||||
loginAttemptLimit: number;
|
||||
loginAttemptLimitTimeout: number;
|
||||
onChange: (p1: boolean, p2: any, p3: string) => void;
|
||||
hasUpdatePermission: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loginAttemptLimitError: boolean,
|
||||
loginAttemptLimitTimeoutError: boolean
|
||||
loginAttemptLimitError: boolean;
|
||||
loginAttemptLimitTimeoutError: boolean;
|
||||
};
|
||||
|
||||
class LoginAttempt extends React.Component<Props, State> {
|
||||
@@ -30,12 +24,7 @@ class LoginAttempt extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
loginAttemptLimit,
|
||||
loginAttemptLimitTimeout,
|
||||
hasUpdatePermission
|
||||
} = this.props;
|
||||
const { t, loginAttemptLimit, loginAttemptLimitTimeout, hasUpdatePermission } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -74,11 +63,7 @@ class LoginAttempt extends React.Component<Props, State> {
|
||||
...this.state,
|
||||
loginAttemptLimitError: !validator.isNumberValid(value)
|
||||
});
|
||||
this.props.onChange(
|
||||
validator.isNumberValid(value),
|
||||
value,
|
||||
"loginAttemptLimit"
|
||||
);
|
||||
this.props.onChange(validator.isNumberValid(value), value, "loginAttemptLimit");
|
||||
};
|
||||
|
||||
handleLoginAttemptLimitTimeoutChange = (value: string) => {
|
||||
@@ -86,12 +71,8 @@ class LoginAttempt extends React.Component<Props, State> {
|
||||
...this.state,
|
||||
loginAttemptLimitTimeoutError: !validator.isNumberValid(value)
|
||||
});
|
||||
this.props.onChange(
|
||||
validator.isNumberValid(value),
|
||||
value,
|
||||
"loginAttemptLimitTimeout"
|
||||
);
|
||||
this.props.onChange(validator.isNumberValid(value), value, "loginAttemptLimitTimeout");
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("config")(LoginAttempt);
|
||||
export default withTranslation("config")(LoginAttempt);
|
||||
@@ -1,18 +1,15 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate, type TFunction } from "react-i18next";
|
||||
import { withTranslation, WithTranslation } from "react-i18next";
|
||||
import { NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import { Select } from "@scm-manager/ui-components";
|
||||
import type { NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
namespaceStrategies: NamespaceStrategies,
|
||||
label: string,
|
||||
value?: string,
|
||||
disabled?: boolean,
|
||||
helpText?: string,
|
||||
onChange: (value: string, name?: string) => void,
|
||||
// context props
|
||||
t: TFunction
|
||||
type Props = WithTranslation & {
|
||||
namespaceStrategies: NamespaceStrategies;
|
||||
label: string;
|
||||
value?: string;
|
||||
disabled?: boolean;
|
||||
helpText?: string;
|
||||
onChange: (value: string, name?: string) => void;
|
||||
};
|
||||
|
||||
class NamespaceStrategySelect extends React.Component<Props> {
|
||||
@@ -38,11 +35,7 @@ class NamespaceStrategySelect extends React.Component<Props> {
|
||||
|
||||
findSelected = () => {
|
||||
const { namespaceStrategies, value } = this.props;
|
||||
if (
|
||||
!namespaceStrategies ||
|
||||
!namespaceStrategies.available ||
|
||||
namespaceStrategies.available.indexOf(value) < 0
|
||||
) {
|
||||
if (!namespaceStrategies || !namespaceStrategies.available || namespaceStrategies.available.indexOf(value) < 0) {
|
||||
return namespaceStrategies.current;
|
||||
}
|
||||
return value;
|
||||
@@ -64,4 +57,4 @@ class NamespaceStrategySelect extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("plugins")(NamespaceStrategySelect);
|
||||
export default withTranslation("plugins")(NamespaceStrategySelect);
|
||||
@@ -1,146 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
Checkbox,
|
||||
InputField,
|
||||
Subtitle,
|
||||
AddEntryToTableField
|
||||
} from "@scm-manager/ui-components";
|
||||
import ProxyExcludesTable from "../table/ProxyExcludesTable";
|
||||
|
||||
type Props = {
|
||||
proxyPassword: string,
|
||||
proxyPort: number,
|
||||
proxyServer: string,
|
||||
proxyUser: string,
|
||||
enableProxy: boolean,
|
||||
proxyExcludes: string[],
|
||||
t: string => string,
|
||||
onChange: (boolean, any, string) => void,
|
||||
hasUpdatePermission: boolean
|
||||
};
|
||||
|
||||
class ProxySettings extends React.Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
proxyPassword,
|
||||
proxyPort,
|
||||
proxyServer,
|
||||
proxyUser,
|
||||
enableProxy,
|
||||
proxyExcludes,
|
||||
hasUpdatePermission
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Subtitle subtitle={t("proxy-settings.name")} />
|
||||
<div className="columns">
|
||||
<div className="column is-full">
|
||||
<Checkbox
|
||||
checked={enableProxy}
|
||||
label={t("proxy-settings.enable-proxy")}
|
||||
onChange={this.handleEnableProxyChange}
|
||||
disabled={!hasUpdatePermission}
|
||||
helpText={t("help.enableProxyHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-password")}
|
||||
onChange={this.handleProxyPasswordChange}
|
||||
value={proxyPassword}
|
||||
type="password"
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyPasswordHelpText")}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-port")}
|
||||
value={proxyPort}
|
||||
onChange={this.handleProxyPortChange}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyPortHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-server")}
|
||||
value={proxyServer}
|
||||
onChange={this.handleProxyServerChange}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyServerHelpText")}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-user")}
|
||||
value={proxyUser}
|
||||
onChange={this.handleProxyUserChange}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyUserHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-full">
|
||||
<ProxyExcludesTable
|
||||
proxyExcludes={proxyExcludes}
|
||||
onChange={(isValid, changedValue, name) =>
|
||||
this.props.onChange(isValid, changedValue, name)
|
||||
}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
/>
|
||||
<AddEntryToTableField
|
||||
addEntry={this.addProxyExclude}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
buttonLabel={t("proxy-settings.add-proxy-exclude-button")}
|
||||
fieldLabel={t("proxy-settings.add-proxy-exclude-textfield")}
|
||||
errorMessage={t("proxy-settings.add-proxy-exclude-error")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleProxyPasswordChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyPassword");
|
||||
};
|
||||
handleProxyPortChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyPort");
|
||||
};
|
||||
handleProxyServerChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyServer");
|
||||
};
|
||||
handleProxyUserChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyUser");
|
||||
};
|
||||
handleEnableProxyChange = (value: string) => {
|
||||
this.props.onChange(true, value, "enableProxy");
|
||||
};
|
||||
|
||||
addProxyExclude = (proxyExcludeName: string) => {
|
||||
if (this.isProxyExcludeMember(proxyExcludeName)) {
|
||||
return;
|
||||
}
|
||||
this.props.onChange(
|
||||
true,
|
||||
[...this.props.proxyExcludes, proxyExcludeName],
|
||||
"proxyExcludes"
|
||||
);
|
||||
};
|
||||
|
||||
isProxyExcludeMember = (proxyExcludeName: string) => {
|
||||
return this.props.proxyExcludes.includes(proxyExcludeName);
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("config")(ProxySettings);
|
||||
133
scm-ui/ui-webapp/src/admin/components/form/ProxySettings.tsx
Normal file
133
scm-ui/ui-webapp/src/admin/components/form/ProxySettings.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Checkbox, InputField, Subtitle, AddEntryToTableField } from "@scm-manager/ui-components";
|
||||
import ProxyExcludesTable from "../table/ProxyExcludesTable";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
proxyPassword: string;
|
||||
proxyPort: number;
|
||||
proxyServer: string;
|
||||
proxyUser: string;
|
||||
enableProxy: boolean;
|
||||
proxyExcludes: string[];
|
||||
onChange: (p1: boolean, p2: any, p3: string) => void;
|
||||
hasUpdatePermission: boolean;
|
||||
};
|
||||
|
||||
class ProxySettings extends React.Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
proxyPassword,
|
||||
proxyPort,
|
||||
proxyServer,
|
||||
proxyUser,
|
||||
enableProxy,
|
||||
proxyExcludes,
|
||||
hasUpdatePermission
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Subtitle subtitle={t("proxy-settings.name")} />
|
||||
<div className="columns">
|
||||
<div className="column is-full">
|
||||
<Checkbox
|
||||
checked={enableProxy}
|
||||
label={t("proxy-settings.enable-proxy")}
|
||||
onChange={this.handleEnableProxyChange}
|
||||
disabled={!hasUpdatePermission}
|
||||
helpText={t("help.enableProxyHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-password")}
|
||||
onChange={this.handleProxyPasswordChange}
|
||||
value={proxyPassword}
|
||||
type="password"
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyPasswordHelpText")}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-port")}
|
||||
value={proxyPort}
|
||||
onChange={this.handleProxyPortChange}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyPortHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-server")}
|
||||
value={proxyServer}
|
||||
onChange={this.handleProxyServerChange}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyServerHelpText")}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
label={t("proxy-settings.proxy-user")}
|
||||
value={proxyUser}
|
||||
onChange={this.handleProxyUserChange}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
helpText={t("help.proxyUserHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-full">
|
||||
<ProxyExcludesTable
|
||||
proxyExcludes={proxyExcludes}
|
||||
onChange={(isValid, changedValue, name) => this.props.onChange(isValid, changedValue, name)}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
/>
|
||||
<AddEntryToTableField
|
||||
addEntry={this.addProxyExclude}
|
||||
disabled={!enableProxy || !hasUpdatePermission}
|
||||
buttonLabel={t("proxy-settings.add-proxy-exclude-button")}
|
||||
fieldLabel={t("proxy-settings.add-proxy-exclude-textfield")}
|
||||
errorMessage={t("proxy-settings.add-proxy-exclude-error")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleProxyPasswordChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyPassword");
|
||||
};
|
||||
handleProxyPortChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyPort");
|
||||
};
|
||||
handleProxyServerChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyServer");
|
||||
};
|
||||
handleProxyUserChange = (value: string) => {
|
||||
this.props.onChange(true, value, "proxyUser");
|
||||
};
|
||||
handleEnableProxyChange = (value: string) => {
|
||||
this.props.onChange(true, value, "enableProxy");
|
||||
};
|
||||
|
||||
addProxyExclude = (proxyExcludeName: string) => {
|
||||
if (this.isProxyExcludeMember(proxyExcludeName)) {
|
||||
return;
|
||||
}
|
||||
this.props.onChange(true, [...this.props.proxyExcludes, proxyExcludeName], "proxyExcludes");
|
||||
};
|
||||
|
||||
isProxyExcludeMember = (proxyExcludeName: string) => {
|
||||
return this.props.proxyExcludes.includes(proxyExcludeName);
|
||||
};
|
||||
}
|
||||
|
||||
export default withTranslation("config")(ProxySettings);
|
||||
@@ -1,14 +1,13 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { RemoveEntryOfTableButton, LabelWithHelpIcon } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
items: string[],
|
||||
label: string,
|
||||
removeLabel: string,
|
||||
onRemove: (string[], string) => void,
|
||||
disabled: boolean,
|
||||
helpText: string
|
||||
items: string[];
|
||||
label: string;
|
||||
removeLabel: string;
|
||||
onRemove: (p1: string[], p2: string) => void;
|
||||
disabled: boolean;
|
||||
helpText: string;
|
||||
};
|
||||
|
||||
class ArrayConfigTable extends React.Component<Props> {
|
||||
@@ -16,7 +15,7 @@ class ArrayConfigTable extends React.Component<Props> {
|
||||
const { label, disabled, removeLabel, items, helpText } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<LabelWithHelpIcon label={label} helpText={helpText}/>
|
||||
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||
<table className="table is-hoverable is-fullwidth">
|
||||
<tbody>
|
||||
{items.map(item => {
|
||||
@@ -1,13 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import ArrayConfigTable from "./ArrayConfigTable";
|
||||
|
||||
type Props = {
|
||||
proxyExcludes: string[],
|
||||
t: string => string,
|
||||
onChange: (boolean, any, string) => void,
|
||||
disabled: boolean
|
||||
type Props = WithTranslation & {
|
||||
proxyExcludes: string[];
|
||||
onChange: (p1: boolean, p2: any, p3: string) => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
type State = {};
|
||||
@@ -32,4 +30,4 @@ class ProxyExcludesTable extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("config")(ProxyExcludesTable);
|
||||
export default withTranslation("config")(ProxyExcludesTable);
|
||||
@@ -1,236 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Redirect, Route, Switch } from "react-router-dom";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { History } from "history";
|
||||
import { connect } from "react-redux";
|
||||
import { compose } from "redux";
|
||||
import type { Links } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Page,
|
||||
Navigation,
|
||||
NavLink,
|
||||
Section,
|
||||
SubNavigation
|
||||
} from "@scm-manager/ui-components";
|
||||
import {
|
||||
getLinks,
|
||||
getAvailablePluginsLink,
|
||||
getInstalledPluginsLink
|
||||
} from "../../modules/indexResource";
|
||||
import AdminDetails from "./AdminDetails";
|
||||
import PluginsOverview from "../plugins/containers/PluginsOverview";
|
||||
import GlobalConfig from "./GlobalConfig";
|
||||
import RepositoryRoles from "../roles/containers/RepositoryRoles";
|
||||
import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
|
||||
import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
|
||||
|
||||
type Props = {
|
||||
links: Links,
|
||||
availablePluginsLink: string,
|
||||
installedPluginsLink: string,
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
class Admin extends React.Component<Props> {
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
if (url.includes("role")) {
|
||||
return url.substring(0, url.length - 2);
|
||||
}
|
||||
return url.substring(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
matchedUrl = () => {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
|
||||
matchesRoles = (route: any) => {
|
||||
const url = this.matchedUrl();
|
||||
const regex = new RegExp(`${url}/role/`);
|
||||
return route.location.pathname.match(regex);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { links, availablePluginsLink, installedPluginsLink, t } = this.props;
|
||||
|
||||
const url = this.matchedUrl();
|
||||
const extensionProps = {
|
||||
links,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Switch>
|
||||
<Redirect exact from={url} to={`${url}/info`} />
|
||||
<Route path={`${url}/info`} exact component={AdminDetails} />
|
||||
<Route
|
||||
path={`${url}/settings/general`}
|
||||
exact
|
||||
component={GlobalConfig}
|
||||
/>
|
||||
<Redirect
|
||||
exact
|
||||
from={`${url}/plugins`}
|
||||
to={`${url}/plugins/installed/`}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/installed`}
|
||||
exact
|
||||
render={() => (
|
||||
<PluginsOverview
|
||||
baseUrl={`${url}/plugins/installed`}
|
||||
installed={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/installed/:page`}
|
||||
exact
|
||||
render={() => (
|
||||
<PluginsOverview
|
||||
baseUrl={`${url}/plugins/installed`}
|
||||
installed={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/available`}
|
||||
exact
|
||||
render={() => (
|
||||
<PluginsOverview
|
||||
baseUrl={`${url}/plugins/available`}
|
||||
installed={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/available/:page`}
|
||||
exact
|
||||
render={() => (
|
||||
<PluginsOverview
|
||||
baseUrl={`${url}/plugins/available`}
|
||||
installed={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/role/:role`}
|
||||
render={() => (
|
||||
<SingleRepositoryRole
|
||||
baseUrl={`${url}/roles`}
|
||||
history={this.props.history}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/roles`}
|
||||
exact
|
||||
render={() => <RepositoryRoles baseUrl={`${url}/roles`} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/roles/create`}
|
||||
render={() => (
|
||||
<CreateRepositoryRole history={this.props.history} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/roles/:page`}
|
||||
exact
|
||||
render={() => <RepositoryRoles baseUrl={`${url}/roles`} />}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="admin.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
<div className="column is-one-quarter">
|
||||
<Navigation>
|
||||
<Section label={t("admin.menu.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}/info`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("admin.menu.informationNavLink")}
|
||||
/>
|
||||
{(availablePluginsLink || installedPluginsLink) && (
|
||||
<SubNavigation
|
||||
to={`${url}/plugins/`}
|
||||
icon="fas fa-puzzle-piece"
|
||||
label={t("plugins.menu.pluginsNavLink")}
|
||||
>
|
||||
{installedPluginsLink && (
|
||||
<NavLink
|
||||
to={`${url}/plugins/installed/`}
|
||||
label={t("plugins.menu.installedNavLink")}
|
||||
/>
|
||||
)}
|
||||
{availablePluginsLink && (
|
||||
<NavLink
|
||||
to={`${url}/plugins/available/`}
|
||||
label={t("plugins.menu.availableNavLink")}
|
||||
/>
|
||||
)}
|
||||
</SubNavigation>
|
||||
)}
|
||||
<NavLink
|
||||
to={`${url}/roles/`}
|
||||
icon="fas fa-user-shield"
|
||||
label={t("repositoryRole.navLink")}
|
||||
activeWhenMatch={this.matchesRoles}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="admin.navigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
<SubNavigation
|
||||
to={`${url}/settings/general`}
|
||||
label={t("admin.menu.settingsNavLink")}
|
||||
>
|
||||
<NavLink
|
||||
to={`${url}/settings/general`}
|
||||
label={t("admin.menu.generalNavLink")}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="admin.setting"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => {
|
||||
const links = getLinks(state);
|
||||
const availablePluginsLink = getAvailablePluginsLink(state);
|
||||
const installedPluginsLink = getInstalledPluginsLink(state);
|
||||
return {
|
||||
links,
|
||||
availablePluginsLink,
|
||||
installedPluginsLink
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps),
|
||||
translate("admin")
|
||||
)(Admin);
|
||||
153
scm-ui/ui-webapp/src/admin/containers/Admin.tsx
Normal file
153
scm-ui/ui-webapp/src/admin/containers/Admin.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { compose } from "redux";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Redirect, Route, Switch } from "react-router-dom";
|
||||
import { History } from "history";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { Links } from "@scm-manager/ui-types";
|
||||
import { Page, Navigation, NavLink, Section, SubNavigation } from "@scm-manager/ui-components";
|
||||
import { getLinks, getAvailablePluginsLink, getInstalledPluginsLink } from "../../modules/indexResource";
|
||||
import AdminDetails from "./AdminDetails";
|
||||
import PluginsOverview from "../plugins/containers/PluginsOverview";
|
||||
import GlobalConfig from "./GlobalConfig";
|
||||
import RepositoryRoles from "../roles/containers/RepositoryRoles";
|
||||
import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
|
||||
import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
links: Links;
|
||||
availablePluginsLink: string;
|
||||
installedPluginsLink: string;
|
||||
|
||||
// context objects
|
||||
match: any;
|
||||
history: History;
|
||||
};
|
||||
|
||||
class Admin extends React.Component<Props> {
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
if (url.includes("role")) {
|
||||
return url.substring(0, url.length - 2);
|
||||
}
|
||||
return url.substring(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
matchedUrl = () => {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
|
||||
matchesRoles = (route: any) => {
|
||||
const url = this.matchedUrl();
|
||||
const regex = new RegExp(`${url}/role/`);
|
||||
return route.location.pathname.match(regex);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { links, availablePluginsLink, installedPluginsLink, t } = this.props;
|
||||
|
||||
const url = this.matchedUrl();
|
||||
const extensionProps = {
|
||||
links,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Switch>
|
||||
<Redirect exact from={url} to={`${url}/info`} />
|
||||
<Route path={`${url}/info`} exact component={AdminDetails} />
|
||||
<Route path={`${url}/settings/general`} exact component={GlobalConfig} />
|
||||
<Redirect exact from={`${url}/plugins`} to={`${url}/plugins/installed/`} />
|
||||
<Route
|
||||
path={`${url}/plugins/installed`}
|
||||
exact
|
||||
render={() => <PluginsOverview baseUrl={`${url}/plugins/installed`} installed={true} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/installed/:page`}
|
||||
exact
|
||||
render={() => <PluginsOverview baseUrl={`${url}/plugins/installed`} installed={true} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/available`}
|
||||
exact
|
||||
render={() => <PluginsOverview baseUrl={`${url}/plugins/available`} installed={false} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/plugins/available/:page`}
|
||||
exact
|
||||
render={() => <PluginsOverview baseUrl={`${url}/plugins/available`} installed={false} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/role/:role`}
|
||||
render={() => <SingleRepositoryRole baseUrl={`${url}/roles`} history={this.props.history} />}
|
||||
/>
|
||||
<Route path={`${url}/roles`} exact render={() => <RepositoryRoles baseUrl={`${url}/roles`} />} />
|
||||
<Route
|
||||
path={`${url}/roles/create`}
|
||||
render={() => <CreateRepositoryRole history={this.props.history} />}
|
||||
/>
|
||||
<Route path={`${url}/roles/:page`} exact render={() => <RepositoryRoles baseUrl={`${url}/roles`} />} />
|
||||
<ExtensionPoint name="admin.route" props={extensionProps} renderAll={true} />
|
||||
</Switch>
|
||||
</div>
|
||||
<div className="column is-one-quarter">
|
||||
<Navigation>
|
||||
<Section label={t("admin.menu.navigationLabel")}>
|
||||
<NavLink to={`${url}/info`} icon="fas fa-info-circle" label={t("admin.menu.informationNavLink")} />
|
||||
{(availablePluginsLink || installedPluginsLink) && (
|
||||
<SubNavigation
|
||||
to={`${url}/plugins/`}
|
||||
icon="fas fa-puzzle-piece"
|
||||
label={t("plugins.menu.pluginsNavLink")}
|
||||
>
|
||||
{installedPluginsLink && (
|
||||
<NavLink to={`${url}/plugins/installed/`} label={t("plugins.menu.installedNavLink")} />
|
||||
)}
|
||||
{availablePluginsLink && (
|
||||
<NavLink to={`${url}/plugins/available/`} label={t("plugins.menu.availableNavLink")} />
|
||||
)}
|
||||
</SubNavigation>
|
||||
)}
|
||||
<NavLink
|
||||
to={`${url}/roles/`}
|
||||
icon="fas fa-user-shield"
|
||||
label={t("repositoryRole.navLink")}
|
||||
activeWhenMatch={this.matchesRoles}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
<ExtensionPoint name="admin.navigation" props={extensionProps} renderAll={true} />
|
||||
<SubNavigation to={`${url}/settings/general`} label={t("admin.menu.settingsNavLink")}>
|
||||
<NavLink to={`${url}/settings/general`} label={t("admin.menu.generalNavLink")} />
|
||||
<ExtensionPoint name="admin.setting" props={extensionProps} renderAll={true} />
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => {
|
||||
const links = getLinks(state);
|
||||
const availablePluginsLink = getAvailablePluginsLink(state);
|
||||
const installedPluginsLink = getInstalledPluginsLink(state);
|
||||
return {
|
||||
links,
|
||||
availablePluginsLink,
|
||||
installedPluginsLink
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps),
|
||||
withTranslation("admin")
|
||||
)(Admin);
|
||||
@@ -1,23 +1,18 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { Image, Loading, Subtitle, Title } from "@scm-manager/ui-components";
|
||||
import { getAppVersion } from "../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
version: string,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
version: string;
|
||||
};
|
||||
|
||||
const BoxShadowBox = styled.div`
|
||||
box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1),
|
||||
0 0 0 2px rgba(40, 177, 232, 0.2);
|
||||
box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1), 0 0 0 2px rgba(40, 177, 232, 0.2);
|
||||
`;
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
@@ -39,22 +34,13 @@ class AdminDetails extends React.Component<Props> {
|
||||
<BoxShadowBox className="box">
|
||||
<article className="media">
|
||||
<ImageWrapper className="media-left">
|
||||
<Image
|
||||
src="/images/iconCommunitySupport.png"
|
||||
alt={t("admin.info.communityIconAlt")}
|
||||
/>
|
||||
<Image src="/images/iconCommunitySupport.png" alt={t("admin.info.communityIconAlt")} />
|
||||
</ImageWrapper>
|
||||
<div className="media-content">
|
||||
<div className="content">
|
||||
<h3 className="has-text-weight-medium">
|
||||
{t("admin.info.communityTitle")}
|
||||
</h3>
|
||||
<h3 className="has-text-weight-medium">{t("admin.info.communityTitle")}</h3>
|
||||
<p>{t("admin.info.communityInfo")}</p>
|
||||
<a
|
||||
className="button is-info is-pulled-right"
|
||||
target="_blank"
|
||||
href="https://scm-manager.org/support/"
|
||||
>
|
||||
<a className="button is-info is-pulled-right" target="_blank" href="https://scm-manager.org/support/">
|
||||
{t("admin.info.communityButton")}
|
||||
</a>
|
||||
</div>
|
||||
@@ -64,16 +50,11 @@ class AdminDetails extends React.Component<Props> {
|
||||
<BoxShadowBox className="box">
|
||||
<article className="media">
|
||||
<ImageWrapper className="media-left">
|
||||
<Image
|
||||
src="/images/iconEnterpriseSupport.png"
|
||||
alt={t("admin.info.enterpriseIconAlt")}
|
||||
/>
|
||||
<Image src="/images/iconEnterpriseSupport.png" alt={t("admin.info.enterpriseIconAlt")} />
|
||||
</ImageWrapper>
|
||||
<div className="media-content">
|
||||
<div className="content">
|
||||
<h3 className="has-text-weight-medium">
|
||||
{t("admin.info.enterpriseTitle")}
|
||||
</h3>
|
||||
<h3 className="has-text-weight-medium">{t("admin.info.enterpriseTitle")}</h3>
|
||||
<p>
|
||||
{t("admin.info.enterpriseInfo")}
|
||||
<br />
|
||||
@@ -102,4 +83,4 @@ const mapStateToProps = (state: any) => {
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(translate("admin")(AdminDetails));
|
||||
export default connect(mapStateToProps)(withTranslation("admin")(AdminDetails));
|
||||
@@ -1,7 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Config, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import { Title, Loading, ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { getConfigLink } from "../../modules/indexResource";
|
||||
import {
|
||||
fetchConfig,
|
||||
getFetchConfigFailure,
|
||||
@@ -13,10 +15,7 @@ import {
|
||||
getModifyConfigFailure,
|
||||
modifyConfigReset
|
||||
} from "../modules/config";
|
||||
import { connect } from "react-redux";
|
||||
import type { Config, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import ConfigForm from "../components/form/ConfigForm";
|
||||
import { getConfigLink } from "../../modules/indexResource";
|
||||
import {
|
||||
fetchNamespaceStrategiesIfNeeded,
|
||||
getFetchNamespaceStrategiesFailure,
|
||||
@@ -24,27 +23,24 @@ import {
|
||||
isFetchNamespaceStrategiesPending
|
||||
} from "../modules/namespaceStrategies";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
config: Config,
|
||||
configUpdatePermission: boolean,
|
||||
configLink: string,
|
||||
namespaceStrategies?: NamespaceStrategies,
|
||||
type Props = WithTranslation & {
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
config: Config;
|
||||
configUpdatePermission: boolean;
|
||||
configLink: string;
|
||||
namespaceStrategies?: NamespaceStrategies;
|
||||
|
||||
// dispatch functions
|
||||
modifyConfig: (config: Config, callback?: () => void) => void,
|
||||
fetchConfig: (link: string) => void,
|
||||
configReset: void => void,
|
||||
fetchNamespaceStrategiesIfNeeded: void => void,
|
||||
|
||||
// context objects
|
||||
t: string => string
|
||||
modifyConfig: (config: Config, callback?: () => void) => void;
|
||||
fetchConfig: (link: string) => void;
|
||||
configReset: (p: void) => void;
|
||||
fetchNamespaceStrategiesIfNeeded: (p: void) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
configReadPermission: boolean,
|
||||
configChanged: boolean
|
||||
configReadPermission: boolean;
|
||||
configChanged: boolean;
|
||||
};
|
||||
|
||||
class GlobalConfig extends React.Component<Props, State> {
|
||||
@@ -63,13 +59,17 @@ class GlobalConfig extends React.Component<Props, State> {
|
||||
if (this.props.configLink) {
|
||||
this.props.fetchConfig(this.props.configLink);
|
||||
} else {
|
||||
this.setState({configReadPermission: false});
|
||||
this.setState({
|
||||
configReadPermission: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modifyConfig = (config: Config) => {
|
||||
this.props.modifyConfig(config);
|
||||
this.setState({ configChanged: true });
|
||||
this.setState({
|
||||
configChanged: true
|
||||
});
|
||||
};
|
||||
|
||||
renderConfigChangedNotification = () => {
|
||||
@@ -78,7 +78,11 @@ class GlobalConfig extends React.Component<Props, State> {
|
||||
<div className="notification is-primary">
|
||||
<button
|
||||
className="delete"
|
||||
onClick={() => this.setState({ configChanged: false })}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
configChanged: false
|
||||
})
|
||||
}
|
||||
/>
|
||||
{this.props.t("config.form.submit-success-notification")}
|
||||
</div>
|
||||
@@ -151,12 +155,10 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const loading = isFetchConfigPending(state)
|
||||
|| isModifyConfigPending(state)
|
||||
|| isFetchNamespaceStrategiesPending(state);
|
||||
const error = getFetchConfigFailure(state)
|
||||
|| getModifyConfigFailure(state)
|
||||
|| getFetchNamespaceStrategiesFailure(state);
|
||||
const loading =
|
||||
isFetchConfigPending(state) || isModifyConfigPending(state) || isFetchNamespaceStrategiesPending(state);
|
||||
const error =
|
||||
getFetchConfigFailure(state) || getModifyConfigFailure(state) || getFetchNamespaceStrategiesFailure(state);
|
||||
|
||||
const config = getConfig(state);
|
||||
const configUpdatePermission = getConfigUpdatePermission(state);
|
||||
@@ -176,4 +178,4 @@ const mapStateToProps = state => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("config")(GlobalConfig));
|
||||
)(withTranslation("config")(GlobalConfig));
|
||||
@@ -1,4 +1,3 @@
|
||||
//@flow
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
@@ -51,8 +50,12 @@ const config = {
|
||||
enabledXsrfProtection: true,
|
||||
namespaceStrategy: "UsernameNamespaceStrategy",
|
||||
_links: {
|
||||
self: { href: "http://localhost:8081/api/v2/config" },
|
||||
update: { href: "http://localhost:8081/api/v2/config" }
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/config"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/api/v2/config"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,8 +82,12 @@ const configWithNullValues = {
|
||||
enabledXsrfProtection: true,
|
||||
namespaceStrategy: "UsernameNamespaceStrategy",
|
||||
_links: {
|
||||
self: { href: "http://localhost:8081/api/v2/config" },
|
||||
update: { href: "http://localhost:8081/api/v2/config" }
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/config"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/api/v2/config"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,7 +97,9 @@ const responseBody = {
|
||||
};
|
||||
|
||||
const response = {
|
||||
headers: { "content-type": "application/json" },
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
responseBody
|
||||
};
|
||||
|
||||
@@ -105,7 +114,9 @@ describe("config fetch()", () => {
|
||||
fetchMock.getOnce(URL, response);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_CONFIG_PENDING },
|
||||
{
|
||||
type: FETCH_CONFIG_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_CONFIG_SUCCESS,
|
||||
payload: response
|
||||
@@ -222,8 +233,7 @@ describe("config reducer", () => {
|
||||
|
||||
it("should return empty arrays for null values", () => {
|
||||
// $FlowFixMe
|
||||
const config = reducer({}, fetchConfigSuccess(configWithNullValues))
|
||||
.entries;
|
||||
const config = reducer({}, fetchConfigSuccess(configWithNullValues)).entries;
|
||||
expect(config.adminUsers).toEqual([]);
|
||||
expect(config.adminGroups).toEqual([]);
|
||||
expect(config.proxyExcludes).toEqual([]);
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import * as types from "../../modules/types";
|
||||
import type { Action } from "@scm-manager/ui-types";
|
||||
import { Action } from "@scm-manager/ui-types";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
import { Dispatch } from "redux";
|
||||
import type { Config } from "@scm-manager/ui-types";
|
||||
import { Config } from "@scm-manager/ui-types";
|
||||
|
||||
export const FETCH_CONFIG = "scm/config/FETCH_CONFIG";
|
||||
export const FETCH_CONFIG_PENDING = `${FETCH_CONFIG}_${types.PENDING_SUFFIX}`;
|
||||
@@ -142,29 +141,29 @@ export default reducer;
|
||||
|
||||
// selectors
|
||||
|
||||
export function isFetchConfigPending(state: Object) {
|
||||
export function isFetchConfigPending(state: object) {
|
||||
return isPending(state, FETCH_CONFIG);
|
||||
}
|
||||
|
||||
export function getFetchConfigFailure(state: Object) {
|
||||
export function getFetchConfigFailure(state: object) {
|
||||
return getFailure(state, FETCH_CONFIG);
|
||||
}
|
||||
|
||||
export function isModifyConfigPending(state: Object) {
|
||||
export function isModifyConfigPending(state: object) {
|
||||
return isPending(state, MODIFY_CONFIG);
|
||||
}
|
||||
|
||||
export function getModifyConfigFailure(state: Object) {
|
||||
export function getModifyConfigFailure(state: object) {
|
||||
return getFailure(state, MODIFY_CONFIG);
|
||||
}
|
||||
|
||||
export function getConfig(state: Object) {
|
||||
export function getConfig(state: object) {
|
||||
if (state.config && state.config.entries) {
|
||||
return state.config.entries;
|
||||
}
|
||||
}
|
||||
|
||||
export function getConfigUpdatePermission(state: Object) {
|
||||
export function getConfigUpdatePermission(state: object) {
|
||||
if (state.config && state.config.configUpdatePermission) {
|
||||
return state.config.configUpdatePermission;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import fetchMock from "fetch-mock";
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
@@ -98,7 +97,9 @@ describe("namespace strategies fetch", () => {
|
||||
fetchMock.getOnce(URL, strategies);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_NAMESPACESTRATEGIES_TYPES_PENDING },
|
||||
{
|
||||
type: FETCH_NAMESPACESTRATEGIES_TYPES_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS,
|
||||
payload: strategies
|
||||
@@ -1,31 +1,20 @@
|
||||
// @flow
|
||||
import * as types from "../../modules/types";
|
||||
import type { Action, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import { Action, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
import { MODIFY_CONFIG_SUCCESS } from "./config";
|
||||
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES =
|
||||
"scm/config/FETCH_NAMESPACESTRATEGIES_TYPES";
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES_PENDING = `${FETCH_NAMESPACESTRATEGIES_TYPES}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS = `${FETCH_NAMESPACESTRATEGIES_TYPES}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES_FAILURE = `${FETCH_NAMESPACESTRATEGIES_TYPES}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES = "scm/config/FETCH_NAMESPACESTRATEGIES_TYPES";
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES_PENDING = `${FETCH_NAMESPACESTRATEGIES_TYPES}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS = `${FETCH_NAMESPACESTRATEGIES_TYPES}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_NAMESPACESTRATEGIES_TYPES_FAILURE = `${FETCH_NAMESPACESTRATEGIES_TYPES}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export function fetchNamespaceStrategiesIfNeeded() {
|
||||
return function(dispatch: any, getState: () => Object) {
|
||||
return function(dispatch: any, getState: () => object) {
|
||||
const state = getState();
|
||||
if (shouldFetchNamespaceStrategies(state)) {
|
||||
return fetchNamespaceStrategies(
|
||||
dispatch,
|
||||
state.indexResources.links.namespaceStrategies.href
|
||||
);
|
||||
return fetchNamespaceStrategies(dispatch, state.indexResources.links.namespaceStrategies.href);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -43,11 +32,8 @@ function fetchNamespaceStrategies(dispatch: any, url: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldFetchNamespaceStrategies(state: Object) {
|
||||
if (
|
||||
isFetchNamespaceStrategiesPending(state) ||
|
||||
getFetchNamespaceStrategiesFailure(state)
|
||||
) {
|
||||
export function shouldFetchNamespaceStrategies(state: object) {
|
||||
if (isFetchNamespaceStrategiesPending(state) || getFetchNamespaceStrategiesFailure(state)) {
|
||||
return false;
|
||||
}
|
||||
return !state.namespaceStrategies || !state.namespaceStrategies.current;
|
||||
@@ -59,9 +45,7 @@ export function fetchNamespaceStrategiesPending(): Action {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchNamespaceStrategiesSuccess(
|
||||
namespaceStrategies: NamespaceStrategies
|
||||
): Action {
|
||||
export function fetchNamespaceStrategiesSuccess(namespaceStrategies: NamespaceStrategies): Action {
|
||||
return {
|
||||
type: FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS,
|
||||
payload: namespaceStrategies
|
||||
@@ -78,13 +62,12 @@ export function fetchNamespaceStrategiesFailure(error: Error): Action {
|
||||
// reducers
|
||||
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
if (
|
||||
action.type === FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS &&
|
||||
action.payload
|
||||
) {
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: "UNKNOWN"
|
||||
}
|
||||
): object {
|
||||
if (action.type === FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS && action.payload) {
|
||||
return action.payload;
|
||||
} else if (action.type === MODIFY_CONFIG_SUCCESS && action.payload) {
|
||||
const config = action.payload;
|
||||
@@ -98,17 +81,17 @@ export default function reducer(
|
||||
|
||||
// selectors
|
||||
|
||||
export function getNamespaceStrategies(state: Object) {
|
||||
export function getNamespaceStrategies(state: object) {
|
||||
if (state.namespaceStrategies) {
|
||||
return state.namespaceStrategies;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function isFetchNamespaceStrategiesPending(state: Object) {
|
||||
export function isFetchNamespaceStrategiesPending(state: object) {
|
||||
return isPending(state, FETCH_NAMESPACESTRATEGIES_TYPES);
|
||||
}
|
||||
|
||||
export function getFetchNamespaceStrategiesFailure(state: Object) {
|
||||
export function getFetchNamespaceStrategiesFailure(state: object) {
|
||||
return getFailure(state, FETCH_NAMESPACESTRATEGIES_TYPES);
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import PluginActionModal from "./PluginActionModal";
|
||||
import type { PendingPlugins } from "@scm-manager/ui-types";
|
||||
import { PendingPlugins } from "@scm-manager/ui-types";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
refresh: () => void,
|
||||
pendingPlugins: PendingPlugins,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
onClose: () => void;
|
||||
refresh: () => void;
|
||||
pendingPlugins: PendingPlugins;
|
||||
};
|
||||
|
||||
class CancelPendingActionModal extends React.Component<Props> {
|
||||
@@ -39,4 +34,4 @@ class CancelPendingActionModal extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("admin")(CancelPendingActionModal);
|
||||
export default withTranslation("admin")(CancelPendingActionModal);
|
||||
@@ -1,19 +1,15 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PendingPlugins } from "@scm-manager/ui-types";
|
||||
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 Props = WithTranslation & {
|
||||
pendingPlugins: PendingPlugins;
|
||||
};
|
||||
|
||||
type State = {
|
||||
showModal: boolean
|
||||
showModal: boolean;
|
||||
};
|
||||
|
||||
class ExecutePendingAction extends React.Component<Props, State> {
|
||||
@@ -40,12 +36,7 @@ class ExecutePendingAction extends React.Component<Props, State> {
|
||||
const { showModal } = this.state;
|
||||
const { pendingPlugins } = this.props;
|
||||
if (showModal) {
|
||||
return (
|
||||
<ExecutePendingModal
|
||||
pendingPlugins={pendingPlugins}
|
||||
onClose={this.closeModal}
|
||||
/>
|
||||
);
|
||||
return <ExecutePendingModal pendingPlugins={pendingPlugins} onClose={this.closeModal} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -55,14 +46,10 @@ class ExecutePendingAction extends React.Component<Props, State> {
|
||||
return (
|
||||
<>
|
||||
{this.renderModal()}
|
||||
<Button
|
||||
color="primary"
|
||||
label={t("plugins.executePending")}
|
||||
action={this.openModal}
|
||||
/>
|
||||
<Button color="primary" label={t("plugins.executePending")} action={this.openModal} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(ExecutePendingAction);
|
||||
export default withTranslation("admin")(ExecutePendingAction);
|
||||
@@ -1,18 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import PluginActionModal from "./PluginActionModal";
|
||||
import type { PendingPlugins } from "@scm-manager/ui-types";
|
||||
import waitForRestart from "./waitForRestart";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PendingPlugins } from "@scm-manager/ui-types";
|
||||
import { apiClient, Notification } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import waitForRestart from "./waitForRestart";
|
||||
import PluginActionModal from "./PluginActionModal";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
pendingPlugins: PendingPlugins,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
onClose: () => void;
|
||||
pendingPlugins: PendingPlugins;
|
||||
};
|
||||
|
||||
class ExecutePendingActionModal extends React.Component<Props> {
|
||||
@@ -27,19 +22,15 @@ class ExecutePendingActionModal extends React.Component<Props> {
|
||||
pendingPlugins={pendingPlugins}
|
||||
execute={this.executeAndRestart}
|
||||
>
|
||||
<Notification type="warning">
|
||||
{t("plugins.modal.restartNotification")}
|
||||
</Notification>
|
||||
<Notification type="warning">{t("plugins.modal.restartNotification")}</Notification>
|
||||
</PluginActionModal>
|
||||
);
|
||||
}
|
||||
|
||||
executeAndRestart = () => {
|
||||
const { pendingPlugins } = this.props;
|
||||
return apiClient
|
||||
.post(pendingPlugins._links.execute.href)
|
||||
.then(waitForRestart);
|
||||
return apiClient.post(pendingPlugins._links.execute.href).then(waitForRestart);
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("admin")(ExecutePendingActionModal);
|
||||
export default withTranslation("admin")(ExecutePendingActionModal);
|
||||
@@ -1,30 +1,19 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {
|
||||
apiClient,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ErrorNotification,
|
||||
Modal,
|
||||
Notification
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { PendingPlugins } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import { apiClient, Button, ButtonGroup, ErrorNotification, Modal, Notification } from "@scm-manager/ui-components";
|
||||
import { PendingPlugins } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import waitForRestart from "./waitForRestart";
|
||||
import SuccessNotification from "./SuccessNotification";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
pendingPlugins: PendingPlugins,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
onClose: () => void;
|
||||
pendingPlugins: PendingPlugins;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loading: boolean,
|
||||
success: boolean,
|
||||
error?: Error
|
||||
loading: boolean;
|
||||
success: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class ExecutePendingModal extends React.Component<Props, State> {
|
||||
@@ -44,11 +33,7 @@ class ExecutePendingModal extends React.Component<Props, State> {
|
||||
} else if (success) {
|
||||
return <SuccessNotification />;
|
||||
} else {
|
||||
return (
|
||||
<Notification type="warning">
|
||||
{t("plugins.modal.restartNotification")}
|
||||
</Notification>
|
||||
);
|
||||
return <Notification type="warning">{t("plugins.modal.restartNotification")}</Notification>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,17 +66,16 @@ class ExecutePendingModal extends React.Component<Props, State> {
|
||||
const { pendingPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{pendingPlugins._embedded &&
|
||||
pendingPlugins._embedded.new.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.installQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.new.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{pendingPlugins._embedded && pendingPlugins._embedded.new.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.installQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.new.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -100,17 +84,16 @@ class ExecutePendingModal extends React.Component<Props, State> {
|
||||
const { pendingPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{pendingPlugins._embedded &&
|
||||
pendingPlugins._embedded.update.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.update.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{pendingPlugins._embedded && pendingPlugins._embedded.update.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.update.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -119,17 +102,16 @@ class ExecutePendingModal extends React.Component<Props, State> {
|
||||
const { pendingPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{pendingPlugins._embedded &&
|
||||
pendingPlugins._embedded.uninstall.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.uninstallQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.uninstall.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{pendingPlugins._embedded && pendingPlugins._embedded.uninstall.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.uninstallQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.uninstall.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -182,4 +164,4 @@ class ExecutePendingModal extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(ExecutePendingModal);
|
||||
export default withTranslation("admin")(ExecutePendingModal);
|
||||
@@ -1,35 +1,26 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ErrorNotification,
|
||||
Modal
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { PendingPlugins, PluginCollection } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PendingPlugins, PluginCollection } from "@scm-manager/ui-types";
|
||||
import { Button, ButtonGroup, ErrorNotification, Modal } from "@scm-manager/ui-components";
|
||||
import SuccessNotification from "./SuccessNotification";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
actionType: string,
|
||||
pendingPlugins?: PendingPlugins,
|
||||
installedPlugins?: PluginCollection,
|
||||
refresh: () => void,
|
||||
execute: () => Promise<any>,
|
||||
description: string,
|
||||
label: string,
|
||||
type Props = WithTranslation & {
|
||||
onClose: () => void;
|
||||
actionType: string;
|
||||
pendingPlugins?: PendingPlugins;
|
||||
installedPlugins?: PluginCollection;
|
||||
refresh: () => void;
|
||||
execute: () => Promise<any>;
|
||||
description: string;
|
||||
label: string;
|
||||
|
||||
children?: React.Node,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
children?: React.Node;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loading: boolean,
|
||||
success: boolean,
|
||||
error?: Error
|
||||
loading: boolean;
|
||||
success: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class PluginActionModal extends React.Component<Props, State> {
|
||||
@@ -90,20 +81,18 @@ class PluginActionModal extends React.Component<Props, State> {
|
||||
const { installedPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{installedPlugins &&
|
||||
installedPlugins._embedded &&
|
||||
installedPlugins._embedded.plugins && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||
<ul>
|
||||
{installedPlugins._embedded.plugins
|
||||
.filter(plugin => plugin._links && plugin._links.update)
|
||||
.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{installedPlugins && installedPlugins._embedded && installedPlugins._embedded.plugins && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||
<ul>
|
||||
{installedPlugins._embedded.plugins
|
||||
.filter(plugin => plugin._links && plugin._links.update)
|
||||
.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -112,18 +101,16 @@ class PluginActionModal extends React.Component<Props, State> {
|
||||
const { pendingPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{pendingPlugins &&
|
||||
pendingPlugins._embedded &&
|
||||
pendingPlugins._embedded.new.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.installQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.new.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{pendingPlugins && pendingPlugins._embedded && pendingPlugins._embedded.new.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.installQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.new.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -132,18 +119,16 @@ class PluginActionModal extends React.Component<Props, State> {
|
||||
const { pendingPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{pendingPlugins &&
|
||||
pendingPlugins._embedded &&
|
||||
pendingPlugins._embedded.update.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.update.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{pendingPlugins && pendingPlugins._embedded && pendingPlugins._embedded.update.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.updateQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.update.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -152,18 +137,16 @@ class PluginActionModal extends React.Component<Props, State> {
|
||||
const { pendingPlugins, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{pendingPlugins &&
|
||||
pendingPlugins._embedded &&
|
||||
pendingPlugins._embedded.uninstall.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.uninstallQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.uninstall.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{pendingPlugins && pendingPlugins._embedded && pendingPlugins._embedded.uninstall.length > 0 && (
|
||||
<>
|
||||
<strong>{t("plugins.modal.uninstallQueue")}</strong>
|
||||
<ul>
|
||||
{pendingPlugins._embedded.uninstall.map(plugin => (
|
||||
<li key={plugin.name}>{plugin.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -213,4 +196,4 @@ class PluginActionModal extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(PluginActionModal);
|
||||
export default withTranslation("admin")(PluginActionModal);
|
||||
@@ -1,11 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||
import type {Plugin} from "@scm-manager/ui-types";
|
||||
import {Image} from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { Plugin } from "@scm-manager/ui-types";
|
||||
import { Image } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin
|
||||
plugin: Plugin;
|
||||
};
|
||||
|
||||
export default class PluginAvatar extends React.Component<Props> {
|
||||
@@ -13,7 +12,12 @@ export default class PluginAvatar extends React.Component<Props> {
|
||||
const { plugin } = this.props;
|
||||
return (
|
||||
<p className="image is-64x64">
|
||||
<ExtensionPoint name="plugins.plugin-avatar" props={{ plugin }}>
|
||||
<ExtensionPoint
|
||||
name="plugins.plugin-avatar"
|
||||
props={{
|
||||
plugin
|
||||
}}
|
||||
>
|
||||
<Image src={plugin.avatarUrl ? plugin.avatarUrl : "/images/blib.jpg"} alt="Logo" />
|
||||
</ExtensionPoint>
|
||||
</p>
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
children?: React.Node
|
||||
children?: React.Node;
|
||||
};
|
||||
|
||||
const ActionWrapper = styled.div`
|
||||
@@ -1,9 +1,7 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import type { Plugin } from "@scm-manager/ui-types";
|
||||
import { Plugin } from "@scm-manager/ui-types";
|
||||
import { CardColumn, Icon } from "@scm-manager/ui-components";
|
||||
import PluginAvatar from "./PluginAvatar";
|
||||
import PluginModal from "./PluginModal";
|
||||
@@ -14,18 +12,15 @@ export const PluginAction = {
|
||||
UNINSTALL: "uninstall"
|
||||
};
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin,
|
||||
refresh: () => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
plugin: Plugin;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
showInstallModal: boolean,
|
||||
showUpdateModal: boolean,
|
||||
showUninstallModal: boolean
|
||||
showInstallModal: boolean;
|
||||
showUpdateModal: boolean;
|
||||
showUninstallModal: boolean;
|
||||
};
|
||||
|
||||
const ActionbarWrapper = styled.div`
|
||||
@@ -64,7 +59,9 @@ class PluginEntry extends React.Component<Props, State> {
|
||||
|
||||
toggleModal = (showModal: string) => {
|
||||
const oldValue = this.state[showModal];
|
||||
this.setState({ [showModal]: !oldValue });
|
||||
this.setState({
|
||||
[showModal]: !oldValue
|
||||
});
|
||||
};
|
||||
|
||||
createFooterRight = (plugin: Plugin) => {
|
||||
@@ -83,9 +80,7 @@ class PluginEntry extends React.Component<Props, State> {
|
||||
|
||||
isUninstallable = () => {
|
||||
const { plugin } = this.props;
|
||||
return (
|
||||
plugin._links && plugin._links.uninstall && plugin._links.uninstall.href
|
||||
);
|
||||
return plugin._links && plugin._links.uninstall && plugin._links.uninstall.href;
|
||||
};
|
||||
|
||||
createActionbar = () => {
|
||||
@@ -93,39 +88,18 @@ class PluginEntry extends React.Component<Props, State> {
|
||||
return (
|
||||
<ActionbarWrapper className="is-flex">
|
||||
{this.isInstallable() && (
|
||||
<IconWrapper
|
||||
className="level-item"
|
||||
onClick={() => this.toggleModal("showInstallModal")}
|
||||
>
|
||||
<Icon
|
||||
title={t("plugins.modal.install")}
|
||||
name="download"
|
||||
color="info"
|
||||
/>
|
||||
<IconWrapper className="level-item" onClick={() => this.toggleModal("showInstallModal")}>
|
||||
<Icon title={t("plugins.modal.install")} name="download" color="info" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
{this.isUninstallable() && (
|
||||
<IconWrapper
|
||||
className="level-item"
|
||||
onClick={() => this.toggleModal("showUninstallModal")}
|
||||
>
|
||||
<Icon
|
||||
title={t("plugins.modal.uninstall")}
|
||||
name="trash"
|
||||
color="info"
|
||||
/>
|
||||
<IconWrapper className="level-item" onClick={() => this.toggleModal("showUninstallModal")}>
|
||||
<Icon title={t("plugins.modal.uninstall")} name="trash" color="info" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
{this.isUpdatable() && (
|
||||
<IconWrapper
|
||||
className="level-item"
|
||||
onClick={() => this.toggleModal("showUpdateModal")}
|
||||
>
|
||||
<Icon
|
||||
title={t("plugins.modal.update")}
|
||||
name="sync-alt"
|
||||
color="info"
|
||||
/>
|
||||
<IconWrapper className="level-item" onClick={() => this.toggleModal("showUpdateModal")}>
|
||||
<Icon title={t("plugins.modal.update")} name="sync-alt" color="info" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
</ActionbarWrapper>
|
||||
@@ -168,13 +142,7 @@ class PluginEntry extends React.Component<Props, State> {
|
||||
|
||||
createPendingSpinner = () => {
|
||||
const { plugin } = this.props;
|
||||
return (
|
||||
<Icon
|
||||
className="fa-spin fa-lg"
|
||||
name="spinner"
|
||||
color={plugin.markedForUninstall ? "danger" : "info"}
|
||||
/>
|
||||
);
|
||||
return <Icon className="fa-spin fa-lg" name="spinner" color={plugin.markedForUninstall ? "danger" : "info"} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -188,19 +156,11 @@ class PluginEntry extends React.Component<Props, State> {
|
||||
return (
|
||||
<>
|
||||
<CardColumn
|
||||
action={
|
||||
this.isInstallable()
|
||||
? () => this.toggleModal("showInstallModal")
|
||||
: null
|
||||
}
|
||||
action={this.isInstallable() ? () => this.toggleModal("showInstallModal") : null}
|
||||
avatar={avatar}
|
||||
title={plugin.displayName ? plugin.displayName : plugin.name}
|
||||
description={plugin.description}
|
||||
contentRight={
|
||||
plugin.pending || plugin.markedForUninstall
|
||||
? this.createPendingSpinner()
|
||||
: actionbar
|
||||
}
|
||||
contentRight={plugin.pending || plugin.markedForUninstall ? this.createPendingSpinner() : actionbar}
|
||||
footerRight={footerRight}
|
||||
/>
|
||||
{modal}
|
||||
@@ -209,4 +169,4 @@ class PluginEntry extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(PluginEntry);
|
||||
export default withTranslation("admin")(PluginEntry);
|
||||
@@ -1,12 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { CardColumnGroup } from "@scm-manager/ui-components";
|
||||
import type { PluginGroup } from "@scm-manager/ui-types";
|
||||
import { PluginGroup } from "@scm-manager/ui-types";
|
||||
import PluginEntry from "./PluginEntry";
|
||||
|
||||
type Props = {
|
||||
group: PluginGroup,
|
||||
refresh: () => void
|
||||
group: PluginGroup;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
class PluginGroupEntry extends React.Component<Props> {
|
||||
@@ -1,12 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Plugin } from "@scm-manager/ui-types";
|
||||
import { Plugin } from "@scm-manager/ui-types";
|
||||
import PluginGroupEntry from "../components/PluginGroupEntry";
|
||||
import groupByCategory from "./groupByCategory";
|
||||
|
||||
type Props = {
|
||||
plugins: Plugin[],
|
||||
refresh: () => void
|
||||
plugins: Plugin[];
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
class PluginList extends React.Component<Props> {
|
||||
@@ -1,9 +1,8 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import type { Plugin } from "@scm-manager/ui-types";
|
||||
import { Plugin } from "@scm-manager/ui-types";
|
||||
import {
|
||||
apiClient,
|
||||
Button,
|
||||
@@ -17,27 +16,23 @@ import waitForRestart from "./waitForRestart";
|
||||
import SuccessNotification from "./SuccessNotification";
|
||||
import { PluginAction } from "./PluginEntry";
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin,
|
||||
pluginAction: string,
|
||||
refresh: () => void,
|
||||
onClose: () => void,
|
||||
|
||||
// context props
|
||||
t: (key: string, params?: Object) => string
|
||||
type Props = WithTranslation & {
|
||||
plugin: Plugin;
|
||||
pluginAction: string;
|
||||
refresh: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
success: boolean,
|
||||
restart: boolean,
|
||||
loading: boolean,
|
||||
error?: Error
|
||||
success: boolean;
|
||||
restart: boolean;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
const ListParent = styled.div`
|
||||
margin-right: 0;
|
||||
min-width: ${props =>
|
||||
props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em"};
|
||||
min-width: ${props => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")};
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
@@ -183,9 +178,7 @@ class PluginModal extends React.Component<Props, State> {
|
||||
} else if (restart) {
|
||||
return (
|
||||
<div className="media">
|
||||
<Notification type="warning">
|
||||
{t("plugins.modal.restartNotification")}
|
||||
</Notification>
|
||||
<Notification type="warning">{t("plugins.modal.restartNotification")}</Notification>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -212,58 +205,33 @@ class PluginModal extends React.Component<Props, State> {
|
||||
<div className="media">
|
||||
<div className="media-content">
|
||||
<div className="field is-horizontal">
|
||||
<ListParent
|
||||
className={classNames("field-label", "is-inline-flex")}
|
||||
pluginAction={pluginAction}
|
||||
>
|
||||
<ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}>
|
||||
{t("plugins.modal.author")}:
|
||||
</ListParent>
|
||||
<ListChild className={classNames("field-body", "is-inline-flex")}>
|
||||
{plugin.author}
|
||||
</ListChild>
|
||||
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild>
|
||||
</div>
|
||||
{pluginAction === PluginAction.INSTALL && (
|
||||
<div className="field is-horizontal">
|
||||
<ListParent
|
||||
className={classNames("field-label", "is-inline-flex")}
|
||||
pluginAction={pluginAction}
|
||||
>
|
||||
<ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}>
|
||||
{t("plugins.modal.version")}:
|
||||
</ListParent>
|
||||
<ListChild
|
||||
className={classNames("field-body", "is-inline-flex")}
|
||||
>
|
||||
{plugin.version}
|
||||
</ListChild>
|
||||
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild>
|
||||
</div>
|
||||
)}
|
||||
{(pluginAction === PluginAction.UPDATE ||
|
||||
pluginAction === PluginAction.UNINSTALL) && (
|
||||
{(pluginAction === PluginAction.UPDATE || pluginAction === PluginAction.UNINSTALL) && (
|
||||
<div className="field is-horizontal">
|
||||
<ListParent
|
||||
className={classNames("field-label", "is-inline-flex")}
|
||||
>
|
||||
<ListParent className={classNames("field-label", "is-inline-flex")}>
|
||||
{t("plugins.modal.currentVersion")}:
|
||||
</ListParent>
|
||||
<ListChild
|
||||
className={classNames("field-body", "is-inline-flex")}
|
||||
>
|
||||
{plugin.version}
|
||||
</ListChild>
|
||||
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild>
|
||||
</div>
|
||||
)}
|
||||
{pluginAction === PluginAction.UPDATE && (
|
||||
<div className="field is-horizontal">
|
||||
<ListParent
|
||||
className={classNames("field-label", "is-inline-flex")}
|
||||
>
|
||||
<ListParent className={classNames("field-label", "is-inline-flex")}>
|
||||
{t("plugins.modal.newVersion")}:
|
||||
</ListParent>
|
||||
<ListChild
|
||||
className={classNames("field-body", "is-inline-flex")}
|
||||
>
|
||||
{plugin.newVersion}
|
||||
</ListChild>
|
||||
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.newVersion}</ListChild>
|
||||
</div>
|
||||
)}
|
||||
{this.renderDependencies()}
|
||||
@@ -297,4 +265,4 @@ class PluginModal extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(PluginModal);
|
||||
export default withTranslation("admin")(PluginModal);
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
children?: React.Node
|
||||
children?: React.Node;
|
||||
};
|
||||
|
||||
const ChildWrapper = styled.div`
|
||||
@@ -16,14 +15,7 @@ export default class PluginTopActions extends React.Component<Props> {
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<ChildWrapper
|
||||
className={classNames(
|
||||
"column",
|
||||
"is-flex",
|
||||
"is-one-fifths",
|
||||
"is-mobile-action-spacing"
|
||||
)}
|
||||
>
|
||||
<ChildWrapper className={classNames("column", "is-flex", "is-one-fifths", "is-mobile-action-spacing")}>
|
||||
{children}
|
||||
</ChildWrapper>
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Notification } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class InstallSuccessNotification extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Notification type="success">
|
||||
{t("plugins.modal.successNotification")}{" "}
|
||||
<a onClick={e => window.location.reload(true)}>
|
||||
{t("plugins.modal.reload")}
|
||||
</a>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(InstallSuccessNotification);
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Notification } from "@scm-manager/ui-components";
|
||||
|
||||
class InstallSuccessNotification extends React.Component<WithTranslation> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Notification type="success">
|
||||
{t("plugins.modal.successNotification")}{" "}
|
||||
<a onClick={e => window.location.reload(true)}>{t("plugins.modal.reload")}</a>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("admin")(InstallSuccessNotification);
|
||||
@@ -1,18 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import PluginActionModal from "./PluginActionModal";
|
||||
import type { PluginCollection } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PluginCollection } from "@scm-manager/ui-types";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import PluginActionModal from "./PluginActionModal";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
refresh: () => void,
|
||||
installedPlugins: PluginCollection,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
onClose: () => void;
|
||||
refresh: () => void;
|
||||
installedPlugins: PluginCollection;
|
||||
};
|
||||
|
||||
class UpdateAllActionModal extends React.Component<Props> {
|
||||
@@ -39,4 +34,4 @@ class UpdateAllActionModal extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("admin")(UpdateAllActionModal);
|
||||
export default withTranslation("admin")(UpdateAllActionModal);
|
||||
@@ -1,11 +1,8 @@
|
||||
// @flow
|
||||
import type { Plugin, PluginGroup } from "@scm-manager/ui-types";
|
||||
import { Plugin, PluginGroup } from "@scm-manager/ui-types";
|
||||
|
||||
export default function groupByCategory(
|
||||
plugins: Plugin[]
|
||||
): PluginGroup[] {
|
||||
let groups = {};
|
||||
for (let plugin of plugins) {
|
||||
export default function groupByCategory(plugins: Plugin[]): PluginGroup[] {
|
||||
const groups = {};
|
||||
for (const plugin of plugins) {
|
||||
const groupName = plugin.category;
|
||||
|
||||
let group = groups[groupName];
|
||||
@@ -19,8 +16,8 @@ export default function groupByCategory(
|
||||
group.plugins.push(plugin);
|
||||
}
|
||||
|
||||
let groupArray = [];
|
||||
for (let groupName in groups) {
|
||||
const groupArray = [];
|
||||
for (const groupName in groups) {
|
||||
const group = groups[groupName];
|
||||
group.plugins.sort(sortByName);
|
||||
groupArray.push(groups[groupName]);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
const waitForRestart = () => {
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { compose } from "redux";
|
||||
import type { PendingPlugins, PluginCollection } from "@scm-manager/ui-types";
|
||||
import { PendingPlugins, PluginCollection } from "@scm-manager/ui-types";
|
||||
import {
|
||||
ButtonGroup,
|
||||
ErrorNotification,
|
||||
@@ -33,34 +32,31 @@ import ExecutePendingActionModal from "../components/ExecutePendingActionModal";
|
||||
import CancelPendingActionModal from "../components/CancelPendingActionModal";
|
||||
import UpdateAllActionModal from "../components/UpdateAllActionModal";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
collection: PluginCollection,
|
||||
baseUrl: string,
|
||||
installed: boolean,
|
||||
availablePluginsLink: string,
|
||||
installedPluginsLink: string,
|
||||
pendingPluginsLink: string,
|
||||
pendingPlugins: PendingPlugins,
|
||||
|
||||
// context objects
|
||||
t: (key: string, params?: Object) => string,
|
||||
type Props = WithTranslation & {
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
collection: PluginCollection;
|
||||
baseUrl: string;
|
||||
installed: boolean;
|
||||
availablePluginsLink: string;
|
||||
installedPluginsLink: string;
|
||||
pendingPluginsLink: string;
|
||||
pendingPlugins: PendingPlugins;
|
||||
|
||||
// dispatched functions
|
||||
fetchPluginsByLink: (link: string) => void,
|
||||
fetchPendingPlugins: (link: string) => void
|
||||
fetchPluginsByLink: (link: string) => void;
|
||||
fetchPendingPlugins: (link: string) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
showPendingModal: boolean,
|
||||
showUpdateAllModal: boolean,
|
||||
showCancelModal: boolean
|
||||
showPendingModal: boolean;
|
||||
showUpdateAllModal: boolean;
|
||||
showCancelModal: boolean;
|
||||
};
|
||||
|
||||
class PluginsOverview extends React.Component<Props, State> {
|
||||
constructor(props: Props, context: *) {
|
||||
super(props, context);
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showPendingModal: false,
|
||||
showUpdateAllModal: false,
|
||||
@@ -100,13 +96,7 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<Title title={t("plugins.title")} />
|
||||
<Subtitle
|
||||
subtitle={
|
||||
installed
|
||||
? t("plugins.installedSubtitle")
|
||||
: t("plugins.availableSubtitle")
|
||||
}
|
||||
/>
|
||||
<Subtitle subtitle={installed ? t("plugins.installedSubtitle") : t("plugins.availableSubtitle")} />
|
||||
</div>
|
||||
<PluginTopActions>{actions}</PluginTopActions>
|
||||
</div>
|
||||
@@ -124,11 +114,7 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
const { pendingPlugins, collection, t } = this.props;
|
||||
const buttons = [];
|
||||
|
||||
if (
|
||||
pendingPlugins &&
|
||||
pendingPlugins._links &&
|
||||
pendingPlugins._links.execute
|
||||
) {
|
||||
if (pendingPlugins && pendingPlugins._links && pendingPlugins._links.execute) {
|
||||
buttons.push(
|
||||
<Button
|
||||
color="primary"
|
||||
@@ -136,16 +122,16 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
key={"executePending"}
|
||||
icon={"arrow-circle-right"}
|
||||
label={t("plugins.executePending")}
|
||||
action={() => this.setState({ showPendingModal: true })}
|
||||
action={() =>
|
||||
this.setState({
|
||||
showPendingModal: true
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
pendingPlugins &&
|
||||
pendingPlugins._links &&
|
||||
pendingPlugins._links.cancel
|
||||
) {
|
||||
if (pendingPlugins && pendingPlugins._links && pendingPlugins._links.cancel) {
|
||||
buttons.push(
|
||||
<Button
|
||||
color="primary"
|
||||
@@ -153,7 +139,11 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
key={"cancelPending"}
|
||||
icon={"times"}
|
||||
label={t("plugins.cancelPending")}
|
||||
action={() => this.setState({ showCancelModal: true })}
|
||||
action={() =>
|
||||
this.setState({
|
||||
showCancelModal: true
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -166,7 +156,11 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
key={"updateAll"}
|
||||
icon={"sync-alt"}
|
||||
label={this.computeUpdateAllSize()}
|
||||
action={() => this.setState({ showUpdateAllModal: true })}
|
||||
action={() =>
|
||||
this.setState({
|
||||
showUpdateAllModal: true
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -179,9 +173,7 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
|
||||
computeUpdateAllSize = () => {
|
||||
const { collection, t } = this.props;
|
||||
const outdatedPlugins = collection._embedded.plugins.filter(
|
||||
p => p._links.update
|
||||
).length;
|
||||
const outdatedPlugins = collection._embedded.plugins.filter(p => p._links.update).length;
|
||||
return t("plugins.outdatedPlugins", {
|
||||
count: outdatedPlugins
|
||||
});
|
||||
@@ -212,16 +204,16 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
|
||||
renderModals = () => {
|
||||
const { collection, pendingPlugins } = this.props;
|
||||
const {
|
||||
showPendingModal,
|
||||
showCancelModal,
|
||||
showUpdateAllModal
|
||||
} = this.state;
|
||||
const { showPendingModal, showCancelModal, showUpdateAllModal } = this.state;
|
||||
|
||||
if (showPendingModal) {
|
||||
return (
|
||||
<ExecutePendingActionModal
|
||||
onClose={() => this.setState({ showPendingModal: false })}
|
||||
onClose={() =>
|
||||
this.setState({
|
||||
showPendingModal: false
|
||||
})
|
||||
}
|
||||
pendingPlugins={pendingPlugins}
|
||||
/>
|
||||
);
|
||||
@@ -229,7 +221,11 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
if (showCancelModal) {
|
||||
return (
|
||||
<CancelPendingActionModal
|
||||
onClose={() => this.setState({ showCancelModal: false })}
|
||||
onClose={() =>
|
||||
this.setState({
|
||||
showCancelModal: false
|
||||
})
|
||||
}
|
||||
refresh={this.fetchPlugins}
|
||||
pendingPlugins={pendingPlugins}
|
||||
/>
|
||||
@@ -238,7 +234,11 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
if (showUpdateAllModal) {
|
||||
return (
|
||||
<UpdateAllActionModal
|
||||
onClose={() => this.setState({ showUpdateAllModal: false })}
|
||||
onClose={() =>
|
||||
this.setState({
|
||||
showUpdateAllModal: false
|
||||
})
|
||||
}
|
||||
refresh={this.fetchPlugins}
|
||||
installedPlugins={collection}
|
||||
/>
|
||||
@@ -250,12 +250,7 @@ class PluginsOverview extends React.Component<Props, State> {
|
||||
const { collection, t } = this.props;
|
||||
|
||||
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>;
|
||||
}
|
||||
@@ -293,7 +288,7 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
|
||||
export default compose(
|
||||
translate("admin"),
|
||||
withTranslation("admin"),
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
@@ -23,7 +22,7 @@ import reducer, {
|
||||
isFetchPluginPending,
|
||||
getFetchPluginFailure
|
||||
} from "./plugins";
|
||||
import type { Plugin, PluginCollection } from "@scm-manager/ui-types";
|
||||
import { Plugin, PluginCollection } from "@scm-manager/ui-types";
|
||||
|
||||
const groupManagerPlugin: Plugin = {
|
||||
name: "scm-groupmanager-plugin",
|
||||
@@ -103,7 +102,9 @@ describe("plugins fetch", () => {
|
||||
fetchMock.getOnce(PLUGINS_URL, pluginCollection);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_PLUGINS_PENDING },
|
||||
{
|
||||
type: FETCH_PLUGINS_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_PLUGINS_SUCCESS,
|
||||
payload: pluginCollection
|
||||
@@ -131,10 +132,7 @@ describe("plugins fetch", () => {
|
||||
});
|
||||
|
||||
it("should successfully fetch scm-groupmanager-plugin by name", () => {
|
||||
fetchMock.getOnce(
|
||||
PLUGINS_URL + "/scm-groupmanager-plugin",
|
||||
groupManagerPlugin
|
||||
);
|
||||
fetchMock.getOnce(PLUGINS_URL + "/scm-groupmanager-plugin", groupManagerPlugin);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
@@ -152,11 +150,9 @@ describe("plugins fetch", () => {
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchPluginByName(URL, "scm-groupmanager-plugin"))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
return store.dispatch(fetchPluginByName(URL, "scm-groupmanager-plugin")).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_PLUGIN_FAILURE, if the request for scm-groupmanager-plugin by name fails", () => {
|
||||
@@ -165,23 +161,18 @@ describe("plugins fetch", () => {
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchPluginByName(URL, "scm-groupmanager-plugin"))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_PLUGIN_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_PLUGIN_FAILURE);
|
||||
expect(actions[1].payload.name).toBe("scm-groupmanager-plugin");
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
expect(actions[1].itemId).toBe("scm-groupmanager-plugin");
|
||||
});
|
||||
return store.dispatch(fetchPluginByName(URL, "scm-groupmanager-plugin")).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_PLUGIN_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_PLUGIN_FAILURE);
|
||||
expect(actions[1].payload.name).toBe("scm-groupmanager-plugin");
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
expect(actions[1].itemId).toBe("scm-groupmanager-plugin");
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch scm-groupmanager-plugin", () => {
|
||||
fetchMock.getOnce(
|
||||
"http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin",
|
||||
groupManagerPlugin
|
||||
);
|
||||
fetchMock.getOnce("http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin", groupManagerPlugin);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
@@ -205,12 +196,9 @@ describe("plugins fetch", () => {
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_PLUGIN_FAILURE, it the request for scm-groupmanager-plugin fails", () => {
|
||||
fetchMock.getOnce(
|
||||
"http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin",
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
fetchMock.getOnce("http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchPluginByLink(groupManagerPlugin)).then(() => {
|
||||
@@ -230,13 +218,21 @@ describe("plugins reducer", () => {
|
||||
});
|
||||
|
||||
it("should return the same state, if the action is undefined", () => {
|
||||
const state = { x: true };
|
||||
const state = {
|
||||
x: true
|
||||
};
|
||||
expect(reducer(state)).toBe(state);
|
||||
});
|
||||
|
||||
it("should return the same state, if the action is unknown to the reducer", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state, { type: "EL_SPECIALE" })).toBe(state);
|
||||
const state = {
|
||||
x: true
|
||||
};
|
||||
expect(
|
||||
reducer(state, {
|
||||
type: "EL_SPECIALE"
|
||||
})
|
||||
).toBe(state);
|
||||
});
|
||||
|
||||
it("should store the plugins by it's type and name on FETCH_PLUGINS_SUCCESS", () => {
|
||||
@@ -246,18 +242,14 @@ describe("plugins reducer", () => {
|
||||
"scm-script-plugin",
|
||||
"scm-branchwp-plugin"
|
||||
]);
|
||||
expect(newState.byNames["scm-groupmanager-plugin"]).toBe(
|
||||
groupManagerPlugin
|
||||
);
|
||||
expect(newState.byNames["scm-groupmanager-plugin"]).toBe(groupManagerPlugin);
|
||||
expect(newState.byNames["scm-script-plugin"]).toBe(scriptPlugin);
|
||||
expect(newState.byNames["scm-branchwp-plugin"]).toBe(branchwpPlugin);
|
||||
});
|
||||
|
||||
it("should store the plugin at byNames", () => {
|
||||
const newState = reducer({}, fetchPluginSuccess(groupManagerPlugin));
|
||||
expect(newState.byNames["scm-groupmanager-plugin"]).toBe(
|
||||
groupManagerPlugin
|
||||
);
|
||||
expect(newState.byNames["scm-groupmanager-plugin"]).toBe(groupManagerPlugin);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -325,9 +317,7 @@ describe("plugins selectors", () => {
|
||||
[FETCH_PLUGIN + "/scm-groupmanager-plugin"]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchPluginPending(state, "scm-groupmanager-plugin")).toEqual(
|
||||
true
|
||||
);
|
||||
expect(isFetchPluginPending(state, "scm-groupmanager-plugin")).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when fetch plugin is not pending", () => {
|
||||
@@ -340,14 +330,10 @@ describe("plugins selectors", () => {
|
||||
[FETCH_PLUGIN + "/scm-groupmanager-plugin"]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchPluginFailure(state, "scm-groupmanager-plugin")).toEqual(
|
||||
error
|
||||
);
|
||||
expect(getFetchPluginFailure(state, "scm-groupmanager-plugin")).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch plugin did not fail", () => {
|
||||
expect(getFetchPluginFailure({}, "scm-groupmanager-plugin")).toBe(
|
||||
undefined
|
||||
);
|
||||
expect(getFetchPluginFailure({}, "scm-groupmanager-plugin")).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
import * as types from "../../../modules/types";
|
||||
import { isPending } from "../../../modules/pending";
|
||||
import { getFailure } from "../../../modules/failure";
|
||||
import type { Action, Plugin, PluginCollection } from "@scm-manager/ui-types";
|
||||
import { Action, Plugin, PluginCollection } from "@scm-manager/ui-types";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
export const FETCH_PLUGINS = "scm/plugins/FETCH_PLUGINS";
|
||||
@@ -16,15 +15,9 @@ export const FETCH_PLUGIN_SUCCESS = `${FETCH_PLUGIN}_${types.SUCCESS_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
|
||||
}`;
|
||||
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
|
||||
export function fetchPluginsByLink(link: string) {
|
||||
@@ -153,7 +146,7 @@ export function fetchPendingPluginsFailure(err: Error): Action {
|
||||
}
|
||||
|
||||
// reducer
|
||||
function normalizeByName(state: Object, pluginCollection: PluginCollection) {
|
||||
function normalizeByName(state: object, pluginCollection: PluginCollection) {
|
||||
const names = [];
|
||||
const byNames = {};
|
||||
for (const plugin of pluginCollection._embedded.plugins) {
|
||||
@@ -172,7 +165,7 @@ function normalizeByName(state: Object, pluginCollection: PluginCollection) {
|
||||
};
|
||||
}
|
||||
|
||||
const reducerByNames = (state: Object, plugin: Plugin) => {
|
||||
const reducerByNames = (state: object, plugin: Plugin) => {
|
||||
return {
|
||||
...state,
|
||||
byNames: {
|
||||
@@ -183,9 +176,11 @@ const reducerByNames = (state: Object, plugin: Plugin) => {
|
||||
};
|
||||
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: "UNKNOWN"
|
||||
}
|
||||
): object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
@@ -196,17 +191,20 @@ export default function reducer(
|
||||
case FETCH_PLUGIN_SUCCESS:
|
||||
return reducerByNames(state, action.payload);
|
||||
case FETCH_PENDING_PLUGINS_SUCCESS:
|
||||
return { ...state, pending: action.payload };
|
||||
return {
|
||||
...state,
|
||||
pending: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// selectors
|
||||
export function getPluginCollection(state: Object) {
|
||||
export function getPluginCollection(state: object) {
|
||||
if (state.plugins && state.plugins.list && state.plugins.byNames) {
|
||||
const plugins = [];
|
||||
for (let pluginName of state.plugins.list._embedded.plugins) {
|
||||
for (const pluginName of state.plugins.list._embedded.plugins) {
|
||||
plugins.push(state.plugins.byNames[pluginName]);
|
||||
}
|
||||
return {
|
||||
@@ -218,38 +216,38 @@ export function getPluginCollection(state: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchPluginsPending(state: Object) {
|
||||
export function isFetchPluginsPending(state: object) {
|
||||
return isPending(state, FETCH_PLUGINS);
|
||||
}
|
||||
|
||||
export function getFetchPluginsFailure(state: Object) {
|
||||
export function getFetchPluginsFailure(state: object) {
|
||||
return getFailure(state, FETCH_PLUGINS);
|
||||
}
|
||||
|
||||
export function getPlugin(state: Object, name: string) {
|
||||
export function getPlugin(state: object, name: string) {
|
||||
if (state.plugins && state.plugins.byNames) {
|
||||
return state.plugins.byNames[name];
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchPluginPending(state: Object, name: string) {
|
||||
export function isFetchPluginPending(state: object, name: string) {
|
||||
return isPending(state, FETCH_PLUGIN, name);
|
||||
}
|
||||
|
||||
export function getFetchPluginFailure(state: Object, name: string) {
|
||||
export function getFetchPluginFailure(state: object, name: string) {
|
||||
return getFailure(state, FETCH_PLUGIN, name);
|
||||
}
|
||||
|
||||
export function getPendingPlugins(state: Object) {
|
||||
export function getPendingPlugins(state: object) {
|
||||
if (state.plugins && state.plugins.pending) {
|
||||
return state.plugins.pending;
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchPendingPluginsPending(state: Object) {
|
||||
export function isFetchPendingPluginsPending(state: object) {
|
||||
return isPending(state, FETCH_PENDING_PLUGINS);
|
||||
}
|
||||
|
||||
export function getFetchPendingPluginsFailure(state: Object) {
|
||||
export function getFetchPendingPluginsFailure(state: object) {
|
||||
return getFailure(state, FETCH_PENDING_PLUGINS);
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
role: RepositoryRole,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
role: RepositoryRole;
|
||||
};
|
||||
|
||||
class AvailableVerbs extends React.Component<Props> {
|
||||
@@ -21,9 +17,7 @@ class AvailableVerbs extends React.Component<Props> {
|
||||
<td className="is-paddingless">
|
||||
<ul>
|
||||
{role.verbs.map(verb => {
|
||||
return (
|
||||
<li>{t("verbs.repository." + verb + ".displayName")}</li>
|
||||
);
|
||||
return <li>{t("verbs.repository." + verb + ".displayName")}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
@@ -34,4 +28,4 @@ class AvailableVerbs extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("plugins")(AvailableVerbs);
|
||||
export default withTranslation("plugins")(AvailableVerbs);
|
||||
@@ -1,30 +1,20 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { Button } from "@scm-manager/ui-components";
|
||||
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
|
||||
|
||||
type Props = {
|
||||
role: RepositoryRole,
|
||||
url: string,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
role: RepositoryRole;
|
||||
url: string;
|
||||
};
|
||||
|
||||
class PermissionRoleDetails extends React.Component<Props> {
|
||||
renderEditButton() {
|
||||
const { t, url } = this.props;
|
||||
if (!!this.props.role._links.update) {
|
||||
return (
|
||||
<Button
|
||||
label={t("repositoryRole.editButton")}
|
||||
link={`${url}/edit`}
|
||||
color="primary"
|
||||
/>
|
||||
);
|
||||
return <Button label={t("repositoryRole.editButton")} link={`${url}/edit`} color="primary" />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -40,11 +30,13 @@ class PermissionRoleDetails extends React.Component<Props> {
|
||||
<ExtensionPoint
|
||||
name="repositoryRole.role-details.information"
|
||||
renderAll={true}
|
||||
props={{ role }}
|
||||
props={{
|
||||
role
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(PermissionRoleDetails);
|
||||
export default withTranslation("admin")(PermissionRoleDetails);
|
||||
@@ -1,38 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import AvailableVerbs from "./AvailableVerbs";
|
||||
|
||||
type Props = {
|
||||
role: RepositoryRole,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class PermissionRoleDetailsTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { role, t } = this.props;
|
||||
return (
|
||||
<table className="table content">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.name")}</th>
|
||||
<td>{role.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.type")}</th>
|
||||
<td>{role.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.verbs")}</th>
|
||||
<AvailableVerbs role={role} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(PermissionRoleDetailsTable);
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import AvailableVerbs from "./AvailableVerbs";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
role: RepositoryRole;
|
||||
};
|
||||
|
||||
class PermissionRoleDetailsTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { role, t } = this.props;
|
||||
return (
|
||||
<table className="table content">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.name")}</th>
|
||||
<td>{role.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.type")}</th>
|
||||
<td>{role.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.verbs")}</th>
|
||||
<AvailableVerbs role={role} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("admin")(PermissionRoleDetailsTable);
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import SystemRoleTag from "./SystemRoleTag";
|
||||
|
||||
type Props = {
|
||||
baseUrl: string,
|
||||
role: RepositoryRole
|
||||
baseUrl: string;
|
||||
role: RepositoryRole;
|
||||
};
|
||||
|
||||
class PermissionRoleRow extends React.Component<Props> {
|
||||
@@ -1,37 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import PermissionRoleRow from "./PermissionRoleRow";
|
||||
|
||||
type Props = {
|
||||
baseUrl: string,
|
||||
roles: RepositoryRole[],
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class PermissionRoleTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { baseUrl, roles, t } = this.props;
|
||||
return (
|
||||
<table className="card-table table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.name")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{roles.map((role, index) => {
|
||||
return (
|
||||
<PermissionRoleRow key={index} baseUrl={baseUrl} role={role} />
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(PermissionRoleTable);
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import PermissionRoleRow from "./PermissionRoleRow";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
baseUrl: string;
|
||||
roles: RepositoryRole[];
|
||||
};
|
||||
|
||||
class PermissionRoleTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { baseUrl, roles, t } = this.props;
|
||||
return (
|
||||
<table className="card-table table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("repositoryRole.name")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{roles.map((role, index) => {
|
||||
return <PermissionRoleRow key={index} baseUrl={baseUrl} role={role} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("admin")(PermissionRoleTable);
|
||||
@@ -1,14 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { Tag } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
system?: boolean,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
system?: boolean;
|
||||
};
|
||||
|
||||
const LeftMarginTag = styled(Tag)`
|
||||
@@ -27,4 +23,4 @@ class SystemRoleTag extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(SystemRoleTag);
|
||||
export default withTranslation("admin")(SystemRoleTag);
|
||||
@@ -1,32 +1,20 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import RepositoryRoleForm from "./RepositoryRoleForm";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {ErrorNotification, Subtitle, Title} from "@scm-manager/ui-components";
|
||||
import {
|
||||
createRole,
|
||||
getCreateRoleFailure,
|
||||
getFetchVerbsFailure,
|
||||
isFetchVerbsPending
|
||||
} from "../modules/roles";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import {
|
||||
getRepositoryRolesLink,
|
||||
getRepositoryVerbsLink
|
||||
} from "../../../modules/indexResource";
|
||||
import type {History} from "history";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Subtitle, Title } from "@scm-manager/ui-components";
|
||||
import { createRole, getCreateRoleFailure, getFetchVerbsFailure, isFetchVerbsPending } from "../modules/roles";
|
||||
import { getRepositoryRolesLink, getRepositoryVerbsLink } from "../../../modules/indexResource";
|
||||
import RepositoryRoleForm from "./RepositoryRoleForm";
|
||||
|
||||
type Props = {
|
||||
repositoryRolesLink: string,
|
||||
error?: Error,
|
||||
history: History,
|
||||
type Props = WithTranslation & {
|
||||
repositoryRolesLink: string;
|
||||
error?: Error;
|
||||
history: History;
|
||||
|
||||
//dispatch function
|
||||
addRole: (link: string, role: RepositoryRole, callback?: () => void) => void,
|
||||
|
||||
// context objects
|
||||
t: string => string
|
||||
// dispatch function
|
||||
addRole: (link: string, role: RepositoryRole, callback?: () => void) => void;
|
||||
};
|
||||
|
||||
class CreateRepositoryRole extends React.Component<Props> {
|
||||
@@ -36,9 +24,7 @@ class CreateRepositoryRole extends React.Component<Props> {
|
||||
};
|
||||
|
||||
createRepositoryRole = (role: RepositoryRole) => {
|
||||
this.props.addRole(this.props.repositoryRolesLink, role, () =>
|
||||
this.repositoryRoleCreated(role)
|
||||
);
|
||||
this.props.addRole(this.props.repositoryRolesLink, role, () => this.repositoryRoleCreated(role));
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -52,15 +38,13 @@ class CreateRepositoryRole extends React.Component<Props> {
|
||||
<>
|
||||
<Title title={t("repositoryRole.title")} />
|
||||
<Subtitle subtitle={t("repositoryRole.createSubtitle")} />
|
||||
<RepositoryRoleForm
|
||||
submitForm={role => this.createRepositoryRole(role)}
|
||||
/>
|
||||
<RepositoryRoleForm submitForm={role => this.createRepositoryRole(role)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state) => {
|
||||
const loading = isFetchVerbsPending(state);
|
||||
const error = getFetchVerbsFailure(state) || getCreateRoleFailure(state);
|
||||
const verbsLink = getRepositoryVerbsLink(state);
|
||||
@@ -85,4 +69,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("admin")(CreateRepositoryRole));
|
||||
)(withTranslation("admin")(CreateRepositoryRole));
|
||||
@@ -1,32 +1,21 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
DeleteButton,
|
||||
confirmAlert,
|
||||
ErrorNotification
|
||||
} from "@scm-manager/ui-components";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
import {
|
||||
deleteRole,
|
||||
getDeleteRoleFailure,
|
||||
isDeleteRolePending
|
||||
} from "../modules/roles";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { deleteRole, getDeleteRoleFailure, isDeleteRolePending } from "../modules/roles";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
role: RepositoryRole,
|
||||
confirmDialog?: boolean,
|
||||
deleteRole: (role: RepositoryRole, callback?: () => void) => void,
|
||||
type Props = WithTranslation & {
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
role: RepositoryRole;
|
||||
confirmDialog?: boolean;
|
||||
deleteRole: (role: RepositoryRole, callback?: () => void) => void;
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
t: string => string
|
||||
history: History;
|
||||
};
|
||||
|
||||
class DeleteRepositoryRole extends React.Component<Props> {
|
||||
@@ -78,11 +67,7 @@ class DeleteRepositoryRole extends React.Component<Props> {
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<ErrorNotification error={error} />
|
||||
<DeleteButton
|
||||
label={t("repositoryRole.delete.button")}
|
||||
action={action}
|
||||
loading={loading}
|
||||
/>
|
||||
<DeleteButton label={t("repositoryRole.delete.button")} action={action} loading={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -110,4 +95,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(translate("admin")(DeleteRepositoryRole)));
|
||||
)(withRouter(withTranslation("admin")(DeleteRepositoryRole)));
|
||||
@@ -1,30 +1,25 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import RepositoryRoleForm from "./RepositoryRoleForm";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
getModifyRoleFailure,
|
||||
isModifyRolePending,
|
||||
modifyRole
|
||||
} from "../modules/roles";
|
||||
import { ErrorNotification, Subtitle } from "@scm-manager/ui-components";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import type { History } from "history";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { getModifyRoleFailure, isModifyRolePending, modifyRole } from "../modules/roles";
|
||||
import { ErrorNotification, Subtitle, Loading } from "@scm-manager/ui-components";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { History } from "history";
|
||||
import DeleteRepositoryRole from "./DeleteRepositoryRole";
|
||||
|
||||
type Props = {
|
||||
disabled: boolean,
|
||||
role: RepositoryRole,
|
||||
repositoryRolesLink: string,
|
||||
error?: Error,
|
||||
type Props = WithTranslation & {
|
||||
disabled: boolean;
|
||||
role: RepositoryRole;
|
||||
repositoryRolesLink: string;
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
history: History,
|
||||
history: History;
|
||||
|
||||
//dispatch function
|
||||
updateRole: (role: RepositoryRole, callback?: () => void) => void
|
||||
updateRole: (role: RepositoryRole, callback?: () => void) => void;
|
||||
};
|
||||
|
||||
class EditRepositoryRole extends React.Component<Props> {
|
||||
@@ -37,21 +32,20 @@ class EditRepositoryRole extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { error, t } = this.props;
|
||||
const { loading, error, t } = this.props;
|
||||
|
||||
if (error) {
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("repositoryRole.editSubtitle")} />
|
||||
<RepositoryRoleForm
|
||||
role={this.props.role}
|
||||
submitForm={role => this.updateRepositoryRole(role)}
|
||||
/>
|
||||
<hr/>
|
||||
<DeleteRepositoryRole role={this.props.role}/>
|
||||
<RepositoryRoleForm role={this.props.role} submitForm={role => this.updateRepositoryRole(role)} />
|
||||
<hr />
|
||||
<DeleteRepositoryRole role={this.props.role} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -78,4 +72,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("admin")(EditRepositoryRole));
|
||||
)(withTranslation("admin")(EditRepositoryRole));
|
||||
@@ -1,37 +1,25 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { InputField, SubmitButton } from "@scm-manager/ui-components";
|
||||
import { getRepositoryRolesLink, getRepositoryVerbsLink } from "../../../modules/indexResource";
|
||||
import { fetchAvailableVerbs, getFetchVerbsFailure, getVerbsFromState, isFetchVerbsPending } from "../modules/roles";
|
||||
import PermissionCheckbox from "../../../repos/permissions/components/PermissionCheckbox";
|
||||
import {
|
||||
fetchAvailableVerbs,
|
||||
getFetchVerbsFailure,
|
||||
getVerbsFromState,
|
||||
isFetchVerbsPending
|
||||
} from "../modules/roles";
|
||||
import {
|
||||
getRepositoryRolesLink,
|
||||
getRepositoryVerbsLink
|
||||
} from "../../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
role?: RepositoryRole,
|
||||
loading?: boolean,
|
||||
availableVerbs: string[],
|
||||
verbsLink: string,
|
||||
submitForm: RepositoryRole => void,
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
type Props = WithTranslation & {
|
||||
role?: RepositoryRole;
|
||||
loading?: boolean;
|
||||
availableVerbs: string[];
|
||||
verbsLink: string;
|
||||
submitForm: (p: RepositoryRole) => void;
|
||||
|
||||
// dispatch functions
|
||||
fetchAvailableVerbs: (link: string) => void
|
||||
fetchAvailableVerbs: (link: string) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
role: RepositoryRole
|
||||
role: RepositoryRole;
|
||||
};
|
||||
|
||||
class RepositoryRoleForm extends React.Component<Props, State> {
|
||||
@@ -52,7 +40,9 @@ class RepositoryRoleForm extends React.Component<Props, State> {
|
||||
const { fetchAvailableVerbs, verbsLink } = this.props;
|
||||
fetchAvailableVerbs(verbsLink);
|
||||
if (this.props.role) {
|
||||
this.setState({ role: this.props.role });
|
||||
this.setState({
|
||||
role: this.props.role
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +52,7 @@ class RepositoryRoleForm extends React.Component<Props, State> {
|
||||
|
||||
isValid = () => {
|
||||
const { role } = this.state;
|
||||
return !(
|
||||
this.isFalsy(role) ||
|
||||
this.isFalsy(role.name) ||
|
||||
this.isFalsy(role.verbs.length > 0)
|
||||
);
|
||||
return !(this.isFalsy(role) || this.isFalsy(role.name) || this.isFalsy(role.verbs.length > 0));
|
||||
};
|
||||
|
||||
handleNameChange = (name: string) => {
|
||||
@@ -81,9 +67,7 @@ class RepositoryRoleForm extends React.Component<Props, State> {
|
||||
handleVerbChange = (value: boolean, name: string) => {
|
||||
const { role } = this.state;
|
||||
|
||||
const newVerbs = value
|
||||
? [...role.verbs, name]
|
||||
: role.verbs.filter(v => v !== name);
|
||||
const newVerbs = value ? [...role.verbs, name] : role.verbs.filter(v => v !== name);
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
@@ -126,23 +110,17 @@ class RepositoryRoleForm extends React.Component<Props, State> {
|
||||
disabled={!!this.props.role}
|
||||
/>
|
||||
<div className="field">
|
||||
<label className="label">
|
||||
{t("repositoryRole.form.permissions")}
|
||||
</label>
|
||||
<label className="label">{t("repositoryRole.form.permissions")}</label>
|
||||
{verbSelectBoxes}
|
||||
</div>
|
||||
<hr />
|
||||
<SubmitButton
|
||||
loading={loading}
|
||||
label={t("repositoryRole.form.submit")}
|
||||
disabled={!this.isValid()}
|
||||
/>
|
||||
<SubmitButton loading={loading} label={t("repositoryRole.form.submit")} disabled={!this.isValid()} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state) => {
|
||||
const loading = isFetchVerbsPending(state);
|
||||
const error = getFetchVerbsFailure(state);
|
||||
const verbsLink = getRepositoryVerbsLink(state);
|
||||
@@ -169,4 +147,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("admin")(RepositoryRoleForm));
|
||||
)(withTranslation("admin")(RepositoryRoleForm));
|
||||
@@ -1,19 +1,10 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { translate } from "react-i18next";
|
||||
import type { History } from "history";
|
||||
import type { RepositoryRole, PagedCollection } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Title,
|
||||
Subtitle,
|
||||
Loading,
|
||||
Notification,
|
||||
LinkPaginator,
|
||||
urls,
|
||||
CreateButton
|
||||
} from "@scm-manager/ui-components";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { RepositoryRole, PagedCollection } from "@scm-manager/ui-types";
|
||||
import { Title, Subtitle, Loading, Notification, LinkPaginator, urls, CreateButton } from "@scm-manager/ui-components";
|
||||
import {
|
||||
fetchRolesByPage,
|
||||
getRolesFromState,
|
||||
@@ -25,23 +16,22 @@ import {
|
||||
import PermissionRoleTable from "../components/PermissionRoleTable";
|
||||
import { getRepositoryRolesLink } from "../../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
baseUrl: string,
|
||||
roles: RepositoryRole[],
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
canAddRoles: boolean,
|
||||
list: PagedCollection,
|
||||
page: number,
|
||||
rolesLink: string,
|
||||
type Props = WithTranslation & {
|
||||
baseUrl: string;
|
||||
roles: RepositoryRole[];
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
canAddRoles: boolean;
|
||||
list: PagedCollection;
|
||||
page: number;
|
||||
rolesLink: string;
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
history: History,
|
||||
location: any,
|
||||
history: History;
|
||||
location: any;
|
||||
|
||||
// dispatch functions
|
||||
fetchRolesByPage: (link: string, page: number) => void
|
||||
fetchRolesByPage: (link: string, page: number) => void;
|
||||
};
|
||||
|
||||
class RepositoryRoles extends React.Component<Props> {
|
||||
@@ -51,21 +41,11 @@ class RepositoryRoles extends React.Component<Props> {
|
||||
}
|
||||
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const {
|
||||
loading,
|
||||
list,
|
||||
page,
|
||||
rolesLink,
|
||||
location,
|
||||
fetchRolesByPage
|
||||
} = this.props;
|
||||
const { loading, list, page, rolesLink, location, fetchRolesByPage } = this.props;
|
||||
if (list && page && !loading) {
|
||||
const statePage: number = list.page + 1;
|
||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||
fetchRolesByPage(
|
||||
rolesLink,
|
||||
page
|
||||
);
|
||||
fetchRolesByPage(rolesLink, page);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -97,22 +77,13 @@ class RepositoryRoles extends React.Component<Props> {
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Notification type="info">
|
||||
{t("repositoryRole.overview.noPermissionRoles")}
|
||||
</Notification>
|
||||
);
|
||||
return <Notification type="info">{t("repositoryRole.overview.noPermissionRoles")}</Notification>;
|
||||
}
|
||||
|
||||
renderCreateButton() {
|
||||
const { canAddRoles, baseUrl, t } = this.props;
|
||||
if (canAddRoles) {
|
||||
return (
|
||||
<CreateButton
|
||||
label={t("repositoryRole.overview.createButton")}
|
||||
link={`${baseUrl}/create`}
|
||||
/>
|
||||
);
|
||||
return <CreateButton label={t("repositoryRole.overview.createButton")} link={`${baseUrl}/create`} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -151,5 +122,5 @@ export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("admin")(RepositoryRoles))
|
||||
)(withTranslation("admin")(RepositoryRoles))
|
||||
);
|
||||
@@ -1,46 +1,35 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Loading, ErrorPage, Title } from "@scm-manager/ui-components";
|
||||
import { Route } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
import { translate } from "react-i18next";
|
||||
import type { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { getRepositoryRolesLink } from "../../../modules/indexResource";
|
||||
import { Route, withRouter } from "react-router-dom";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import {
|
||||
fetchRoleByName,
|
||||
getFetchRoleFailure,
|
||||
getRoleByName,
|
||||
isFetchRolePending
|
||||
} from "../modules/roles";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { Loading, ErrorPage, Title } from "@scm-manager/ui-components";
|
||||
import { getRepositoryRolesLink } from "../../../modules/indexResource";
|
||||
import { fetchRoleByName, getFetchRoleFailure, getRoleByName, isFetchRolePending } from "../modules/roles";
|
||||
import PermissionRoleDetail from "../components/PermissionRoleDetails";
|
||||
import EditRepositoryRole from "./EditRepositoryRole";
|
||||
|
||||
type Props = {
|
||||
roleName: string,
|
||||
role: RepositoryRole,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
repositoryRolesLink: string,
|
||||
disabled: boolean,
|
||||
type Props = WithTranslation & {
|
||||
roleName: string;
|
||||
role: RepositoryRole;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
repositoryRolesLink: string;
|
||||
disabled: boolean;
|
||||
|
||||
// dispatcher function
|
||||
fetchRoleByName: (string, string) => void,
|
||||
fetchRoleByName: (p1: string, p2: string) => void;
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
match: any;
|
||||
history: History;
|
||||
};
|
||||
|
||||
class SingleRepositoryRole extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.fetchRoleByName(
|
||||
this.props.repositoryRolesLink,
|
||||
this.props.roleName
|
||||
);
|
||||
this.props.fetchRoleByName(this.props.repositoryRolesLink, this.props.roleName);
|
||||
}
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
@@ -59,11 +48,7 @@ class SingleRepositoryRole extends React.Component<Props> {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("repositoryRole.errorTitle")}
|
||||
subtitle={t("repositoryRole.errorSubtitle")}
|
||||
error={error}
|
||||
/>
|
||||
<ErrorPage title={t("repositoryRole.errorTitle")} subtitle={t("repositoryRole.errorSubtitle")} error={error} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,22 +66,13 @@ class SingleRepositoryRole extends React.Component<Props> {
|
||||
return (
|
||||
<>
|
||||
<Title title={t("repositoryRole.title")} />
|
||||
<Route
|
||||
path={`${url}/info`}
|
||||
component={() => <PermissionRoleDetail role={role} url={url} />}
|
||||
/>
|
||||
<Route path={`${url}/info`} component={() => <PermissionRoleDetail role={role} url={url} />} />
|
||||
<Route
|
||||
path={`${url}/edit`}
|
||||
exact
|
||||
component={() => (
|
||||
<EditRepositoryRole role={role} history={this.props.history} />
|
||||
)}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="roles.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
component={() => <EditRepositoryRole role={role} history={this.props.history} />}
|
||||
/>
|
||||
<ExtensionPoint name="roles.route" props={extensionProps} renderAll={true} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -129,5 +105,5 @@ export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("admin")(SingleRepositoryRole))
|
||||
)(withTranslation("admin")(SingleRepositoryRole))
|
||||
);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
@@ -67,15 +66,7 @@ const role1 = {
|
||||
};
|
||||
const role2 = {
|
||||
name: "WRITE",
|
||||
verbs: [
|
||||
"read",
|
||||
"pull",
|
||||
"push",
|
||||
"createPullRequest",
|
||||
"readPullRequest",
|
||||
"commentPullRequest",
|
||||
"mergePullRequest"
|
||||
],
|
||||
verbs: ["read", "pull", "push", "createPullRequest", "readPullRequest", "commentPullRequest", "mergePullRequest"],
|
||||
system: true,
|
||||
_links: {
|
||||
self: {
|
||||
@@ -89,16 +80,13 @@ const responseBody = {
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositoryRoles/?page=0&pageSize=10"
|
||||
href: "http://localhost:8081/scm/api/v2/repositoryRoles/?page=0&pageSize=10"
|
||||
},
|
||||
first: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositoryRoles/?page=0&pageSize=10"
|
||||
href: "http://localhost:8081/scm/api/v2/repositoryRoles/?page=0&pageSize=10"
|
||||
},
|
||||
last: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositoryRoles/?page=0&pageSize=10"
|
||||
href: "http://localhost:8081/scm/api/v2/repositoryRoles/?page=0&pageSize=10"
|
||||
},
|
||||
create: {
|
||||
href: "http://localhost:8081/scm/api/v2/repositoryRoles/"
|
||||
@@ -110,14 +98,15 @@ const responseBody = {
|
||||
};
|
||||
|
||||
const response = {
|
||||
headers: { "content-type": "application/json" },
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
responseBody
|
||||
};
|
||||
|
||||
const URL = "repositoryRoles";
|
||||
const ROLES_URL = "/api/v2/repositoryRoles";
|
||||
const ROLE1_URL =
|
||||
"http://localhost:8081/scm/api/v2/repositoryRoles/specialrole";
|
||||
const ROLE1_URL = "http://localhost:8081/scm/api/v2/repositoryRoles/specialrole";
|
||||
|
||||
const error = new Error("FEHLER!");
|
||||
|
||||
@@ -132,7 +121,9 @@ describe("repository roles fetch", () => {
|
||||
fetchMock.getOnce(ROLES_URL, response);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_ROLES_PENDING },
|
||||
{
|
||||
type: FETCH_ROLES_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_ROLES_SUCCESS,
|
||||
payload: response
|
||||
@@ -446,7 +437,13 @@ describe("repository roles reducer", () => {
|
||||
describe("repository roles selector", () => {
|
||||
it("should return an empty object", () => {
|
||||
expect(selectListAsCollection({})).toEqual({});
|
||||
expect(selectListAsCollection({ roles: { a: "a" } })).toEqual({});
|
||||
expect(
|
||||
selectListAsCollection({
|
||||
roles: {
|
||||
a: "a"
|
||||
}
|
||||
})
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("should return a state slice collection", () => {
|
||||
@@ -467,12 +464,24 @@ describe("repository roles selector", () => {
|
||||
|
||||
it("should return false", () => {
|
||||
expect(isPermittedToCreateRoles({})).toBe(false);
|
||||
expect(isPermittedToCreateRoles({ roles: { list: { entry: {} } } })).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
isPermittedToCreateRoles({
|
||||
roles: { list: { entry: { roleCreatePermission: false } } }
|
||||
roles: {
|
||||
list: {
|
||||
entry: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
isPermittedToCreateRoles({
|
||||
roles: {
|
||||
list: {
|
||||
entry: {
|
||||
roleCreatePermission: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
@@ -497,12 +506,23 @@ describe("repository roles selector", () => {
|
||||
entries: ["a", "b"]
|
||||
},
|
||||
byNames: {
|
||||
a: { name: "a" },
|
||||
b: { name: "b" }
|
||||
a: {
|
||||
name: "a"
|
||||
},
|
||||
b: {
|
||||
name: "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(getRolesFromState(state)).toEqual([{ name: "a" }, { name: "b" }]);
|
||||
expect(getRolesFromState(state)).toEqual([
|
||||
{
|
||||
name: "a"
|
||||
},
|
||||
{
|
||||
name: "b"
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return true, when fetch repositoryRoles is pending", () => {
|
||||
@@ -1,14 +1,9 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { isPending } from "../../../modules/pending";
|
||||
import { getFailure } from "../../../modules/failure";
|
||||
import * as types from "../../../modules/types";
|
||||
import { combineReducers, Dispatch } from "redux";
|
||||
import type {
|
||||
Action,
|
||||
PagedCollection,
|
||||
RepositoryRole
|
||||
} from "@scm-manager/ui-types";
|
||||
import { Action, PagedCollection, RepositoryRole } from "@scm-manager/ui-types";
|
||||
|
||||
export const FETCH_ROLES = "scm/roles/FETCH_ROLES";
|
||||
export const FETCH_ROLES_PENDING = `${FETCH_ROLES}_${types.PENDING_SUFFIX}`;
|
||||
@@ -173,11 +168,7 @@ export function createRoleReset() {
|
||||
};
|
||||
}
|
||||
|
||||
export function createRole(
|
||||
link: string,
|
||||
role: RepositoryRole,
|
||||
callback?: () => void
|
||||
) {
|
||||
export function createRole(link: string, role: RepositoryRole, callback?: () => void) {
|
||||
return function(dispatch: Dispatch) {
|
||||
dispatch(createRolePending(role));
|
||||
return apiClient
|
||||
@@ -234,7 +225,10 @@ function verbReducer(state: any = {}, action: any = {}) {
|
||||
switch (action.type) {
|
||||
case FETCH_VERBS_SUCCESS:
|
||||
const verbs = action.payload.verbs;
|
||||
return { ...state, verbs };
|
||||
return {
|
||||
...state,
|
||||
verbs
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -340,34 +334,30 @@ export function deleteRole(role: RepositoryRole, callback?: () => void) {
|
||||
};
|
||||
}
|
||||
|
||||
function extractRolesByNames(
|
||||
roles: RepositoryRole[],
|
||||
roleNames: string[],
|
||||
oldRolesByNames: Object
|
||||
) {
|
||||
function extractRolesByNames(roles: RepositoryRole[], roleNames: string[], oldRolesByNames: object) {
|
||||
const rolesByNames = {};
|
||||
|
||||
for (let role of roles) {
|
||||
for (const role of roles) {
|
||||
rolesByNames[role.name] = role;
|
||||
}
|
||||
|
||||
for (let roleName in oldRolesByNames) {
|
||||
for (const roleName in oldRolesByNames) {
|
||||
rolesByNames[roleName] = oldRolesByNames[roleName];
|
||||
}
|
||||
return rolesByNames;
|
||||
}
|
||||
|
||||
function deleteRoleInRolesByNames(roles: {}, roleName: string) {
|
||||
let newRoles = {};
|
||||
for (let rolename in roles) {
|
||||
const newRoles = {};
|
||||
for (const rolename in roles) {
|
||||
if (rolename !== roleName) newRoles[rolename] = roles[rolename];
|
||||
}
|
||||
return newRoles;
|
||||
}
|
||||
|
||||
function deleteRoleInEntries(roles: [], roleName: string) {
|
||||
let newRoles = [];
|
||||
for (let role of roles) {
|
||||
const newRoles = [];
|
||||
for (const role of roles) {
|
||||
if (role !== roleName) newRoles.push(role);
|
||||
}
|
||||
return newRoles;
|
||||
@@ -398,10 +388,7 @@ function listReducer(state: any = {}, action: any = {}) {
|
||||
|
||||
// Delete single role actions
|
||||
case DELETE_ROLE_SUCCESS:
|
||||
const newRoleEntries = deleteRoleInEntries(
|
||||
state.entries,
|
||||
action.payload.name
|
||||
);
|
||||
const newRoleEntries = deleteRoleInEntries(state.entries, action.payload.name);
|
||||
return {
|
||||
...state,
|
||||
entries: newRoleEntries
|
||||
@@ -441,13 +428,13 @@ export default combineReducers({
|
||||
});
|
||||
|
||||
// selectors
|
||||
const selectList = (state: Object) => {
|
||||
const selectList = (state: object) => {
|
||||
if (state.roles && state.roles.list) {
|
||||
return state.roles.list;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
const selectListEntry = (state: Object): Object => {
|
||||
const selectListEntry = (state: object): object => {
|
||||
const list = selectList(state);
|
||||
if (list.entry) {
|
||||
return list.entry;
|
||||
@@ -455,88 +442,88 @@ const selectListEntry = (state: Object): Object => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const selectListAsCollection = (state: Object): PagedCollection => {
|
||||
export const selectListAsCollection = (state: object): PagedCollection => {
|
||||
return selectListEntry(state);
|
||||
};
|
||||
|
||||
export const isPermittedToCreateRoles = (state: Object): boolean => {
|
||||
export const isPermittedToCreateRoles = (state: object): boolean => {
|
||||
return !!selectListEntry(state).roleCreatePermission;
|
||||
};
|
||||
|
||||
export function getRolesFromState(state: Object) {
|
||||
export function getRolesFromState(state: object) {
|
||||
const roleNames = selectList(state).entries;
|
||||
if (!roleNames) {
|
||||
return null;
|
||||
}
|
||||
const roleEntries: RepositoryRole[] = [];
|
||||
|
||||
for (let roleName of roleNames) {
|
||||
for (const roleName of roleNames) {
|
||||
roleEntries.push(state.roles.byNames[roleName]);
|
||||
}
|
||||
|
||||
return roleEntries;
|
||||
}
|
||||
|
||||
export function getRoleCreateLink(state: Object) {
|
||||
export function getRoleCreateLink(state: object) {
|
||||
if (state && state.list && state.list._links && state.list._links.create) {
|
||||
return state.list._links.create.href;
|
||||
}
|
||||
}
|
||||
|
||||
export function getVerbsFromState(state: Object) {
|
||||
export function getVerbsFromState(state: object) {
|
||||
return state.roles.verbs.verbs;
|
||||
}
|
||||
|
||||
export function isFetchRolesPending(state: Object) {
|
||||
export function isFetchRolesPending(state: object) {
|
||||
return isPending(state, FETCH_ROLES);
|
||||
}
|
||||
|
||||
export function getFetchRolesFailure(state: Object) {
|
||||
export function getFetchRolesFailure(state: object) {
|
||||
return getFailure(state, FETCH_ROLES);
|
||||
}
|
||||
|
||||
export function isFetchVerbsPending(state: Object) {
|
||||
export function isFetchVerbsPending(state: object) {
|
||||
return isPending(state, FETCH_VERBS);
|
||||
}
|
||||
|
||||
export function getFetchVerbsFailure(state: Object) {
|
||||
export function getFetchVerbsFailure(state: object) {
|
||||
return getFailure(state, FETCH_VERBS);
|
||||
}
|
||||
|
||||
export function isCreateRolePending(state: Object) {
|
||||
export function isCreateRolePending(state: object) {
|
||||
return isPending(state, CREATE_ROLE);
|
||||
}
|
||||
|
||||
export function getCreateRoleFailure(state: Object) {
|
||||
export function getCreateRoleFailure(state: object) {
|
||||
return getFailure(state, CREATE_ROLE);
|
||||
}
|
||||
|
||||
export function getRoleByName(state: Object, name: string) {
|
||||
export function getRoleByName(state: object, name: string) {
|
||||
if (state.roles && state.roles.byNames) {
|
||||
return state.roles.byNames[name];
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchRolePending(state: Object, name: string) {
|
||||
export function isFetchRolePending(state: object, name: string) {
|
||||
return isPending(state, FETCH_ROLE, name);
|
||||
}
|
||||
|
||||
export function getFetchRoleFailure(state: Object, name: string) {
|
||||
export function getFetchRoleFailure(state: object, name: string) {
|
||||
return getFailure(state, FETCH_ROLE, name);
|
||||
}
|
||||
|
||||
export function isModifyRolePending(state: Object, name: string) {
|
||||
export function isModifyRolePending(state: object, name: string) {
|
||||
return isPending(state, MODIFY_ROLE, name);
|
||||
}
|
||||
|
||||
export function getModifyRoleFailure(state: Object, name: string) {
|
||||
export function getModifyRoleFailure(state: object, name: string) {
|
||||
return getFailure(state, MODIFY_ROLE, name);
|
||||
}
|
||||
|
||||
export function isDeleteRolePending(state: Object, name: string) {
|
||||
export function isDeleteRolePending(state: object, name: string) {
|
||||
return isPending(state, DELETE_ROLE, name);
|
||||
}
|
||||
|
||||
export function getDeleteRoleFailure(state: Object, name: string) {
|
||||
export function getDeleteRoleFailure(state: object, name: string) {
|
||||
return getFailure(state, DELETE_ROLE, name);
|
||||
}
|
||||
@@ -1,17 +1,13 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import type { InfoItem } from "./InfoItem";
|
||||
import { InfoItem } from "./InfoItem";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
type: "plugin" | "feature",
|
||||
item: InfoItem,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
type: "plugin" | "feature";
|
||||
item: InfoItem;
|
||||
};
|
||||
|
||||
const BottomMarginA = styled.a`
|
||||
@@ -60,13 +56,7 @@ class InfoBox extends React.Component<Props> {
|
||||
<div className="box media">
|
||||
<figure className="media-left">
|
||||
<FixedSizedIconWrapper
|
||||
className={classNames(
|
||||
"image",
|
||||
"box",
|
||||
"has-text-weight-bold",
|
||||
"has-text-white",
|
||||
"has-background-info"
|
||||
)}
|
||||
className={classNames("image", "box", "has-text-weight-bold", "has-text-white", "has-background-info")}
|
||||
>
|
||||
<LightBlueIcon className="fa-2x" name={icon} color="inherit" />
|
||||
<div className="is-size-4">{t("login." + type)}</div>
|
||||
@@ -80,4 +70,4 @@ class InfoBox extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(InfoBox);
|
||||
export default withTranslation("commons")(InfoBox);
|
||||
@@ -1,8 +0,0 @@
|
||||
// @flow
|
||||
import type { Link } from "@scm-manager/ui-types";
|
||||
|
||||
export type InfoItem = {
|
||||
title: string,
|
||||
summary: string,
|
||||
_links: {[string]: Link}
|
||||
};
|
||||
9
scm-ui/ui-webapp/src/components/InfoItem.ts
Normal file
9
scm-ui/ui-webapp/src/components/InfoItem.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Link } from "@scm-manager/ui-types";
|
||||
|
||||
export type InfoItem = {
|
||||
title: string;
|
||||
summary: string;
|
||||
_links: {
|
||||
[key: string]: Link;
|
||||
};
|
||||
};
|
||||
@@ -1,27 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
Image,
|
||||
ErrorNotification,
|
||||
InputField,
|
||||
SubmitButton,
|
||||
UnauthorizedError
|
||||
} from "@scm-manager/ui-components";
|
||||
import { Image, ErrorNotification, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
error?: Error,
|
||||
loading: boolean,
|
||||
loginHandler: (username: string, password: string) => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
error?: Error;
|
||||
loading: boolean;
|
||||
loginHandler: (username: string, password: string) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
username: string,
|
||||
password: string
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const TopMarginBox = styled.div`
|
||||
@@ -45,7 +35,10 @@ const AvatarImage = styled(Image)`
|
||||
class LoginForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { username: "", password: "" };
|
||||
this.state = {
|
||||
username: "",
|
||||
password: ""
|
||||
};
|
||||
}
|
||||
|
||||
handleSubmit = (event: Event) => {
|
||||
@@ -56,11 +49,15 @@ class LoginForm extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
handleUsernameChange = (value: string) => {
|
||||
this.setState({ username: value });
|
||||
this.setState({
|
||||
username: value
|
||||
});
|
||||
};
|
||||
|
||||
handlePasswordChange = (value: string) => {
|
||||
this.setState({ password: value });
|
||||
this.setState({
|
||||
password: value
|
||||
});
|
||||
};
|
||||
|
||||
isValid() {
|
||||
@@ -98,11 +95,7 @@ class LoginForm extends React.Component<Props, State> {
|
||||
type="password"
|
||||
onChange={this.handlePasswordChange}
|
||||
/>
|
||||
<SubmitButton
|
||||
label={t("login.submit")}
|
||||
fullWidth={true}
|
||||
loading={loading}
|
||||
/>
|
||||
<SubmitButton label={t("login.submit")} fullWidth={true} loading={loading} />
|
||||
</form>
|
||||
</TopMarginBox>
|
||||
</div>
|
||||
@@ -110,4 +103,4 @@ class LoginForm extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(LoginForm);
|
||||
export default withTranslation("commons")(LoginForm);
|
||||
@@ -1,29 +1,27 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import InfoBox from "./InfoBox";
|
||||
import type { InfoItem } from "./InfoItem";
|
||||
import { InfoItem } from "./InfoItem";
|
||||
import LoginForm from "./LoginForm";
|
||||
import { Loading } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
loginInfoLink?: string,
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
loginHandler: (username: string, password: string) => void,
|
||||
loginInfoLink?: string;
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
loginHandler: (username: string, password: string) => void;
|
||||
};
|
||||
|
||||
type LoginInfoResponse = {
|
||||
plugin?: InfoItem,
|
||||
feature?: InfoItem
|
||||
plugin?: InfoItem;
|
||||
feature?: InfoItem;
|
||||
};
|
||||
|
||||
type State = {
|
||||
info?: LoginInfoResponse,
|
||||
loading?: boolean,
|
||||
info?: LoginInfoResponse;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
class LoginInfo extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -56,12 +54,11 @@ class LoginInfo extends React.Component<Props, State> {
|
||||
if (!loginInfoLink) {
|
||||
return;
|
||||
}
|
||||
this.timeout(1000, this.fetchLoginInfo(loginInfoLink))
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
this.timeout(1000, this.fetchLoginInfo(loginInfoLink)).catch(() => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createInfoPanel = (info: LoginInfoResponse) => (
|
||||
@@ -74,7 +71,7 @@ class LoginInfo extends React.Component<Props, State> {
|
||||
render() {
|
||||
const { info, loading } = this.state;
|
||||
if (loading) {
|
||||
return <Loading/>;
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
let infoPanel;
|
||||
@@ -89,9 +86,6 @@ class LoginInfo extends React.Component<Props, State> {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LoginInfo;
|
||||
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
// @flow
|
||||
import React, { Component } from "react";
|
||||
import Main from "./Main";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import {
|
||||
fetchMe,
|
||||
getFetchMeFailure,
|
||||
getMe,
|
||||
isAuthenticated,
|
||||
isFetchMePending
|
||||
} from "../modules/auth";
|
||||
|
||||
import {
|
||||
ErrorPage,
|
||||
Footer,
|
||||
Header,
|
||||
Loading,
|
||||
PrimaryNavigation
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { Links, Me } from "@scm-manager/ui-types";
|
||||
import { fetchMe, getFetchMeFailure, getMe, isAuthenticated, isFetchMePending } from "../modules/auth";
|
||||
import { ErrorPage, Footer, Header, Loading, PrimaryNavigation } from "@scm-manager/ui-components";
|
||||
import { Links, Me } from "@scm-manager/ui-types";
|
||||
import {
|
||||
getFetchIndexResourcesFailure,
|
||||
getLinks,
|
||||
@@ -27,19 +13,16 @@ import {
|
||||
isFetchIndexResourcesPending
|
||||
} from "../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
authenticated: boolean,
|
||||
error: Error,
|
||||
loading: boolean,
|
||||
links: Links,
|
||||
meLink: string,
|
||||
type Props = WithTranslation & {
|
||||
me: Me;
|
||||
authenticated: boolean;
|
||||
error: Error;
|
||||
loading: boolean;
|
||||
links: Links;
|
||||
meLink: string;
|
||||
|
||||
// dispatcher functions
|
||||
fetchMe: (link: string) => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
fetchMe: (link: string) => void;
|
||||
};
|
||||
|
||||
class App extends Component<Props> {
|
||||
@@ -58,13 +41,7 @@ class App extends Component<Props> {
|
||||
if (loading) {
|
||||
content = <Loading />;
|
||||
} else if (error) {
|
||||
content = (
|
||||
<ErrorPage
|
||||
title={t("app.error.title")}
|
||||
subtitle={t("app.error.subtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
content = <ErrorPage title={t("app.error.title")} subtitle={t("app.error.subtitle")} error={error} />;
|
||||
} else {
|
||||
content = <Main authenticated={authenticated} links={links} />;
|
||||
}
|
||||
@@ -87,10 +64,8 @@ const mapDispatchToProps = (dispatch: any) => {
|
||||
const mapStateToProps = state => {
|
||||
const authenticated = isAuthenticated(state);
|
||||
const me = getMe(state);
|
||||
const loading =
|
||||
isFetchMePending(state) || isFetchIndexResourcesPending(state);
|
||||
const error =
|
||||
getFetchMeFailure(state) || getFetchIndexResourcesFailure(state);
|
||||
const loading = isFetchMePending(state) || isFetchIndexResourcesPending(state);
|
||||
const error = getFetchMeFailure(state) || getFetchIndexResourcesFailure(state);
|
||||
const links = getLinks(state);
|
||||
const meLink = getMeLink(state);
|
||||
return {
|
||||
@@ -107,5 +82,5 @@ export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("commons")(App))
|
||||
)(withTranslation("commons")(App))
|
||||
);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {
|
||||
ErrorNotification,
|
||||
@@ -7,22 +6,21 @@ import {
|
||||
PasswordConfirmation,
|
||||
SubmitButton
|
||||
} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { changePassword } from "../modules/changePassword";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
me: Me;
|
||||
};
|
||||
|
||||
type State = {
|
||||
oldPassword: string,
|
||||
password: string,
|
||||
loading: boolean,
|
||||
error?: Error,
|
||||
passwordChanged: boolean,
|
||||
passwordValid: boolean
|
||||
oldPassword: string;
|
||||
password: string;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
passwordChanged: boolean;
|
||||
passwordValid: boolean;
|
||||
};
|
||||
|
||||
class ChangeUserPassword extends React.Component<Props, State> {
|
||||
@@ -97,11 +95,7 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
|
||||
if (passwordChanged) {
|
||||
message = (
|
||||
<Notification
|
||||
type={"success"}
|
||||
children={t("password.changedSuccessfully")}
|
||||
onClose={() => this.onClose()}
|
||||
/>
|
||||
<Notification type={"success"} children={t("password.changedSuccessfully")} onClose={() => this.onClose()} />
|
||||
);
|
||||
} else if (error) {
|
||||
message = <ErrorNotification error={error} />;
|
||||
@@ -116,7 +110,10 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
label={t("password.currentPassword")}
|
||||
type="password"
|
||||
onChange={oldPassword =>
|
||||
this.setState({ ...this.state, oldPassword })
|
||||
this.setState({
|
||||
...this.state,
|
||||
oldPassword
|
||||
})
|
||||
}
|
||||
value={this.state.oldPassword ? this.state.oldPassword : ""}
|
||||
helpText={t("password.currentPasswordHelpText")}
|
||||
@@ -129,11 +126,7 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
/>
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("password.submit")}
|
||||
/>
|
||||
<SubmitButton disabled={!this.isValid()} loading={loading} label={t("password.submit")} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -156,4 +149,4 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("commons")(ChangeUserPassword);
|
||||
export default withTranslation("commons")(ChangeUserPassword);
|
||||
@@ -1,10 +1,8 @@
|
||||
// @flow
|
||||
import React, { Component } from "react";
|
||||
import App from "./App";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { withRouter } from "react-router-dom";
|
||||
|
||||
import { Loading, ErrorBoundary } from "@scm-manager/ui-components";
|
||||
import {
|
||||
fetchIndexResources,
|
||||
@@ -13,24 +11,21 @@ import {
|
||||
isFetchIndexResourcesPending
|
||||
} from "../modules/indexResource";
|
||||
import PluginLoader from "./PluginLoader";
|
||||
import type { IndexResources } from "@scm-manager/ui-types";
|
||||
import { IndexResources } from "@scm-manager/ui-types";
|
||||
import ScrollToTop from "./ScrollToTop";
|
||||
import IndexErrorPage from "./IndexErrorPage";
|
||||
|
||||
type Props = {
|
||||
error: Error,
|
||||
loading: boolean,
|
||||
indexResources: IndexResources,
|
||||
type Props = WithTranslation & {
|
||||
error: Error;
|
||||
loading: boolean;
|
||||
indexResources: IndexResources;
|
||||
|
||||
// dispatcher functions
|
||||
fetchIndexResources: () => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
fetchIndexResources: () => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
pluginsLoaded: boolean
|
||||
pluginsLoaded: boolean;
|
||||
};
|
||||
|
||||
class Index extends Component<Props, State> {
|
||||
@@ -56,17 +51,14 @@ class Index extends Component<Props, State> {
|
||||
const { pluginsLoaded } = this.state;
|
||||
|
||||
if (error) {
|
||||
return <IndexErrorPage error={error}/>;
|
||||
return <IndexErrorPage error={error} />;
|
||||
} else if (loading || !indexResources) {
|
||||
return <Loading />;
|
||||
} else {
|
||||
return (
|
||||
<ErrorBoundary fallback={IndexErrorPage}>
|
||||
<ScrollToTop>
|
||||
<PluginLoader
|
||||
loaded={pluginsLoaded}
|
||||
callback={this.pluginLoaderCallback}
|
||||
>
|
||||
<PluginLoader loaded={pluginsLoaded} callback={this.pluginLoaderCallback}>
|
||||
<App />
|
||||
</PluginLoader>
|
||||
</ScrollToTop>
|
||||
@@ -97,5 +89,5 @@ export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("commons")(Index))
|
||||
)(withTranslation("commons")(Index))
|
||||
);
|
||||
@@ -1,26 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate, type TFunction } from "react-i18next";
|
||||
import { ErrorPage } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
error: Error,
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
class IndexErrorPage extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { error, t } = this.props;
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("app.error.title")}
|
||||
subtitle={t("app.error.subtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate("commons")(IndexErrorPage);
|
||||
16
scm-ui/ui-webapp/src/containers/IndexErrorPage.tsx
Normal file
16
scm-ui/ui-webapp/src/containers/IndexErrorPage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { withTranslation, WithTranslation } from "react-i18next";
|
||||
import { ErrorPage } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
error: Error;
|
||||
};
|
||||
|
||||
class IndexErrorPage extends React.Component<Props> {
|
||||
render() {
|
||||
const { error, t } = this.props;
|
||||
return <ErrorPage title={t("app.error.title")} subtitle={t("app.error.subtitle")} error={error} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("commons")(IndexErrorPage);
|
||||
@@ -1,33 +1,25 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Redirect, withRouter } from "react-router-dom";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
getLoginFailure,
|
||||
isAuthenticated,
|
||||
isLoginPending,
|
||||
login
|
||||
} from "../modules/auth";
|
||||
import { getLoginFailure, isAuthenticated, isLoginPending, login } from "../modules/auth";
|
||||
import { getLoginInfoLink, getLoginLink } from "../modules/indexResource";
|
||||
import LoginInfo from "../components/LoginInfo";
|
||||
|
||||
type Props = {
|
||||
authenticated: boolean,
|
||||
loading: boolean,
|
||||
error?: Error,
|
||||
link: string,
|
||||
loginInfoLink?: string,
|
||||
authenticated: boolean;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
link: string;
|
||||
loginInfoLink?: string;
|
||||
|
||||
// dispatcher props
|
||||
login: (link: string, username: string, password: string) => void,
|
||||
login: (link: string, username: string, password: string) => void;
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
from: any,
|
||||
location: any
|
||||
from: any;
|
||||
location: any;
|
||||
};
|
||||
|
||||
const HeroSection = styled.section`
|
||||
@@ -41,7 +33,11 @@ class Login extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderRedirect = () => {
|
||||
const { from } = this.props.location.state || { from: { pathname: "/" } };
|
||||
const { from } = this.props.location.state || {
|
||||
from: {
|
||||
pathname: "/"
|
||||
}
|
||||
};
|
||||
return <Redirect to={from} />;
|
||||
};
|
||||
|
||||
@@ -83,8 +79,7 @@ const mapStateToProps = state => {
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
login: (loginLink: string, username: string, password: string) =>
|
||||
dispatch(login(loginLink, username, password))
|
||||
login: (loginLink: string, username: string, password: string) => dispatch(login(loginLink, username, password))
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Redirect } from "react-router-dom";
|
||||
|
||||
import {
|
||||
logout,
|
||||
isAuthenticated,
|
||||
isLogoutPending,
|
||||
getLogoutFailure, isRedirecting
|
||||
} from "../modules/auth";
|
||||
import { logout, isAuthenticated, isLogoutPending, getLogoutFailure, isRedirecting } from "../modules/auth";
|
||||
import { Loading, ErrorPage } from "@scm-manager/ui-components";
|
||||
import { getLogoutLink } from "../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
authenticated: boolean,
|
||||
loading: boolean,
|
||||
redirecting: boolean,
|
||||
error: Error,
|
||||
logoutLink: string,
|
||||
type Props = WithTranslation & {
|
||||
authenticated: boolean;
|
||||
loading: boolean;
|
||||
redirecting: boolean;
|
||||
error: Error;
|
||||
logoutLink: string;
|
||||
|
||||
// dispatcher functions
|
||||
logout: (link: string) => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
logout: (link: string) => void;
|
||||
};
|
||||
|
||||
class Logout extends React.Component<Props> {
|
||||
@@ -35,13 +26,7 @@ class Logout extends React.Component<Props> {
|
||||
render() {
|
||||
const { authenticated, redirecting, loading, error, t } = this.props;
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("logout.error.title")}
|
||||
subtitle={t("logout.error.subtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
return <ErrorPage title={t("logout.error.title")} subtitle={t("logout.error.subtitle")} error={error} />;
|
||||
} else if (loading || authenticated || redirecting) {
|
||||
return <Loading />;
|
||||
} else {
|
||||
@@ -74,4 +59,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("commons")(Logout));
|
||||
)(withTranslation("commons")(Logout));
|
||||
@@ -1,138 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
|
||||
import { Redirect, Route, Switch, withRouter } from "react-router-dom";
|
||||
import type { Links } from "@scm-manager/ui-types";
|
||||
|
||||
import Overview from "../repos/containers/Overview";
|
||||
import Users from "../users/containers/Users";
|
||||
import Login from "../containers/Login";
|
||||
import Logout from "../containers/Logout";
|
||||
|
||||
import { ProtectedRoute } from "@scm-manager/ui-components";
|
||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
import CreateUser from "../users/containers/CreateUser";
|
||||
import SingleUser from "../users/containers/SingleUser";
|
||||
import RepositoryRoot from "../repos/containers/RepositoryRoot";
|
||||
import Create from "../repos/containers/Create";
|
||||
|
||||
import Groups from "../groups/containers/Groups";
|
||||
import SingleGroup from "../groups/containers/SingleGroup";
|
||||
import CreateGroup from "../groups/containers/CreateGroup";
|
||||
|
||||
import Admin from "../admin/containers/Admin";
|
||||
|
||||
import Profile from "./Profile";
|
||||
|
||||
type Props = {
|
||||
authenticated?: boolean,
|
||||
links: Links
|
||||
};
|
||||
|
||||
class Main extends React.Component<Props> {
|
||||
render() {
|
||||
const { authenticated, links } = this.props;
|
||||
const redirectUrlFactory = binder.getExtension("main.redirect", this.props);
|
||||
let url = "/repos/";
|
||||
if (redirectUrlFactory) {
|
||||
url = redirectUrlFactory(this.props);
|
||||
}
|
||||
return (
|
||||
<div className="main">
|
||||
<Switch>
|
||||
<Redirect exact from="/" to={url} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route path="/logout" component={Logout} />
|
||||
<Redirect exact strict from="/repos" to="/repos/" />
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/repos/"
|
||||
component={Overview}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/repos/create"
|
||||
component={Create}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/repos/:page"
|
||||
component={Overview}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
path="/repo/:namespace/:name"
|
||||
component={RepositoryRoot}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<Redirect exact strict from="/users" to="/users/" />
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/users/"
|
||||
component={Users}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
authenticated={authenticated}
|
||||
path="/users/create"
|
||||
component={CreateUser}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/users/:page"
|
||||
component={Users}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
authenticated={authenticated}
|
||||
path="/user/:name"
|
||||
component={SingleUser}
|
||||
/>
|
||||
<Redirect exact strict from="/groups" to="/groups/" />
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/groups/"
|
||||
component={Groups}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
authenticated={authenticated}
|
||||
path="/group/:name"
|
||||
component={SingleGroup}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
authenticated={authenticated}
|
||||
path="/groups/create"
|
||||
component={CreateGroup}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/groups/:page"
|
||||
component={Groups}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
path="/admin"
|
||||
component={Admin}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
path="/me"
|
||||
component={Profile}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="main.route"
|
||||
renderAll={true}
|
||||
props={{ authenticated, links }}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Main);
|
||||
77
scm-ui/ui-webapp/src/containers/Main.tsx
Normal file
77
scm-ui/ui-webapp/src/containers/Main.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
|
||||
import { Redirect, Route, Switch, withRouter } from "react-router-dom";
|
||||
import { Links } from "@scm-manager/ui-types";
|
||||
|
||||
import Overview from "../repos/containers/Overview";
|
||||
import Users from "../users/containers/Users";
|
||||
import Login from "../containers/Login";
|
||||
import Logout from "../containers/Logout";
|
||||
|
||||
import { ProtectedRoute } from "@scm-manager/ui-components";
|
||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
import CreateUser from "../users/containers/CreateUser";
|
||||
import SingleUser from "../users/containers/SingleUser";
|
||||
import RepositoryRoot from "../repos/containers/RepositoryRoot";
|
||||
import Create from "../repos/containers/Create";
|
||||
|
||||
import Groups from "../groups/containers/Groups";
|
||||
import SingleGroup from "../groups/containers/SingleGroup";
|
||||
import CreateGroup from "../groups/containers/CreateGroup";
|
||||
|
||||
import Admin from "../admin/containers/Admin";
|
||||
|
||||
import Profile from "./Profile";
|
||||
|
||||
type Props = {
|
||||
authenticated?: boolean;
|
||||
links: Links;
|
||||
};
|
||||
|
||||
class Main extends React.Component<Props> {
|
||||
render() {
|
||||
const { authenticated, links } = this.props;
|
||||
const redirectUrlFactory = binder.getExtension("main.redirect", this.props);
|
||||
let url = "/repos/";
|
||||
if (redirectUrlFactory) {
|
||||
url = redirectUrlFactory(this.props);
|
||||
}
|
||||
return (
|
||||
<div className="main">
|
||||
<Switch>
|
||||
<Redirect exact from="/" to={url} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route path="/logout" component={Logout} />
|
||||
<Redirect exact strict from="/repos" to="/repos/" />
|
||||
<ProtectedRoute exact path="/repos/" component={Overview} authenticated={authenticated} />
|
||||
<ProtectedRoute exact path="/repos/create" component={Create} authenticated={authenticated} />
|
||||
<ProtectedRoute exact path="/repos/:page" component={Overview} authenticated={authenticated} />
|
||||
<ProtectedRoute path="/repo/:namespace/:name" component={RepositoryRoot} authenticated={authenticated} />
|
||||
<Redirect exact strict from="/users" to="/users/" />
|
||||
<ProtectedRoute exact path="/users/" component={Users} authenticated={authenticated} />
|
||||
<ProtectedRoute authenticated={authenticated} path="/users/create" component={CreateUser} />
|
||||
<ProtectedRoute exact path="/users/:page" component={Users} authenticated={authenticated} />
|
||||
<ProtectedRoute authenticated={authenticated} path="/user/:name" component={SingleUser} />
|
||||
<Redirect exact strict from="/groups" to="/groups/" />
|
||||
<ProtectedRoute exact path="/groups/" component={Groups} authenticated={authenticated} />
|
||||
<ProtectedRoute authenticated={authenticated} path="/group/:name" component={SingleGroup} />
|
||||
<ProtectedRoute authenticated={authenticated} path="/groups/create" component={CreateGroup} />
|
||||
<ProtectedRoute exact path="/groups/:page" component={Groups} authenticated={authenticated} />
|
||||
<ProtectedRoute path="/admin" component={Admin} authenticated={authenticated} />
|
||||
<ProtectedRoute path="/me" component={Profile} authenticated={authenticated} />
|
||||
<ExtensionPoint
|
||||
name="main.route"
|
||||
renderAll={true}
|
||||
props={{
|
||||
authenticated,
|
||||
links
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Main);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { apiClient, Loading } from "@scm-manager/ui-components";
|
||||
import { getUiPluginsLink } from "../modules/indexResource";
|
||||
@@ -6,19 +5,19 @@ import { connect } from "react-redux";
|
||||
import loadBundle from "./loadBundle";
|
||||
|
||||
type Props = {
|
||||
loaded: boolean,
|
||||
children: React.Node,
|
||||
link: string,
|
||||
callback: () => void
|
||||
loaded: boolean;
|
||||
children: React.Node;
|
||||
link: string;
|
||||
callback: () => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
message: string
|
||||
message: string;
|
||||
};
|
||||
|
||||
type Plugin = {
|
||||
name: string,
|
||||
bundles: string[]
|
||||
name: string;
|
||||
bundles: string[];
|
||||
};
|
||||
|
||||
class PluginLoader extends React.Component<Props, State> {
|
||||
@@ -57,7 +56,7 @@ class PluginLoader extends React.Component<Props, State> {
|
||||
|
||||
const promises = [];
|
||||
const sortedPlugins = plugins.sort(comparePluginsByName);
|
||||
for (let plugin of sortedPlugins) {
|
||||
for (const plugin of sortedPlugins) {
|
||||
promises.push(this.loadPlugin(plugin));
|
||||
}
|
||||
return promises.reduce((chain, current) => {
|
||||
@@ -73,7 +72,7 @@ class PluginLoader extends React.Component<Props, State> {
|
||||
});
|
||||
|
||||
const promises = [];
|
||||
for (let bundle of plugin.bundles) {
|
||||
for (const bundle of plugin.bundles) {
|
||||
promises.push(loadBundle(bundle));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
@@ -1,31 +1,20 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Route, withRouter } from "react-router-dom";
|
||||
import { getMe } from "../modules/auth";
|
||||
import { compose } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import {
|
||||
ErrorPage,
|
||||
Page,
|
||||
Navigation,
|
||||
SubNavigation,
|
||||
Section,
|
||||
NavLink
|
||||
} from "@scm-manager/ui-components";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { ErrorPage, Page, Navigation, SubNavigation, Section, NavLink } from "@scm-manager/ui-components";
|
||||
import ChangeUserPassword from "./ChangeUserPassword";
|
||||
import ProfileInfo from "./ProfileInfo";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
type Props = WithTranslation & {
|
||||
me: Me;
|
||||
|
||||
// Context props
|
||||
t: string => string,
|
||||
match: any
|
||||
match: any;
|
||||
};
|
||||
type State = {};
|
||||
|
||||
@@ -69,37 +58,16 @@ class Profile extends React.Component<Props, State> {
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Route path={url} exact render={() => <ProfileInfo me={me} />} />
|
||||
<Route
|
||||
path={`${url}/settings/password`}
|
||||
render={() => <ChangeUserPassword me={me} />}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="profile.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
<Route path={`${url}/settings/password`} render={() => <ChangeUserPassword me={me} />} />
|
||||
<ExtensionPoint name="profile.route" props={extensionProps} renderAll={true} />
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("profile.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("profile.informationNavLink")}
|
||||
/>
|
||||
<SubNavigation
|
||||
to={`${url}/settings/password`}
|
||||
label={t("profile.settingsNavLink")}
|
||||
>
|
||||
<NavLink
|
||||
to={`${url}/settings/password`}
|
||||
label={t("profile.changePasswordNavLink")}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="profile.setting"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
<NavLink to={`${url}`} icon="fas fa-info-circle" label={t("profile.informationNavLink")} />
|
||||
<SubNavigation to={`${url}/settings/password`} label={t("profile.settingsNavLink")}>
|
||||
<NavLink to={`${url}/settings/password`} label={t("profile.changePasswordNavLink")} />
|
||||
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
@@ -117,7 +85,7 @@ const mapStateToProps = state => {
|
||||
};
|
||||
|
||||
export default compose(
|
||||
translate("commons"),
|
||||
withTranslation("commons"),
|
||||
connect(mapStateToProps),
|
||||
withRouter
|
||||
)(Profile);
|
||||
@@ -1,18 +1,10 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import {
|
||||
MailLink,
|
||||
AvatarWrapper,
|
||||
AvatarImage
|
||||
} from "@scm-manager/ui-components";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { MailLink, AvatarWrapper, AvatarImage } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
|
||||
// Context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
me: Me;
|
||||
};
|
||||
|
||||
class ProfileInfo extends React.Component<Props> {
|
||||
@@ -74,4 +66,4 @@ class ProfileInfo extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(ProfileInfo);
|
||||
export default withTranslation("commons")(ProfileInfo);
|
||||
@@ -1,12 +1,10 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { withRouter } from "react-router-dom";
|
||||
|
||||
|
||||
type Props = {
|
||||
location: any,
|
||||
children: any
|
||||
}
|
||||
location: any;
|
||||
children: any;
|
||||
};
|
||||
|
||||
class ScrollToTop extends React.Component<Props> {
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -18,19 +18,42 @@ import * as UIExtensions from "@scm-manager/ui-extensions";
|
||||
import * as UIComponents from "@scm-manager/ui-components";
|
||||
import { urls } from "@scm-manager/ui-components";
|
||||
|
||||
// TODO add headers "Cache": "no-cache", "X-Requested-With": "XMLHttpRequest"
|
||||
type PluginModule = {
|
||||
name: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
const BundleLoader = {
|
||||
name: "bundle-loader",
|
||||
fetch: (plugin: PluginModule) => {
|
||||
return fetch(plugin.address, {
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Cache: "no-cache",
|
||||
// identify the request as ajax request
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
}
|
||||
}).then(response => {
|
||||
return response.text();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SystemJS.registry.set(BundleLoader.name, SystemJS.newModule(BundleLoader));
|
||||
|
||||
SystemJS.config({
|
||||
baseURL: urls.withContextPath("/assets"),
|
||||
meta: {
|
||||
"/*": {
|
||||
// @ts-ignore typing missing, but seems required
|
||||
esModule: true,
|
||||
authorization: true
|
||||
authorization: true,
|
||||
loader: BundleLoader.name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const expose = (name, cmp, defaultCmp) => {
|
||||
const expose = (name: string, cmp: any, defaultCmp?: any) => {
|
||||
let mod = cmp;
|
||||
if (defaultCmp) {
|
||||
// SystemJS default export:
|
||||
@@ -55,4 +78,4 @@ expose("query-string", QueryString, QueryStringDefault);
|
||||
expose("@scm-manager/ui-extensions", UIExtensions);
|
||||
expose("@scm-manager/ui-components", UIComponents);
|
||||
|
||||
export default (plugin) => SystemJS.import(plugin);
|
||||
export default (plugin: string) => SystemJS.import(plugin);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import thunk from "redux-thunk";
|
||||
import logger from "redux-logger";
|
||||
import { createStore, compose, applyMiddleware, combineReducers } from "redux";
|
||||
@@ -21,8 +20,7 @@ import plugins from "./admin/plugins/modules/plugins";
|
||||
import branches from "./repos/branches/modules/branches";
|
||||
|
||||
function createReduxStore() {
|
||||
const composeEnhancers =
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const reducer = combineReducers({
|
||||
pending,
|
||||
@@ -43,10 +41,7 @@ function createReduxStore() {
|
||||
plugins
|
||||
});
|
||||
|
||||
return createStore(
|
||||
reducer,
|
||||
composeEnhancers(applyMiddleware(thunk, logger))
|
||||
);
|
||||
return createStore(reducer, composeEnhancers(applyMiddleware(thunk, logger)));
|
||||
}
|
||||
|
||||
export default createReduxStore;
|
||||
@@ -1,6 +1,6 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Group, SelectValue } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
AutocompleteAddEntryToTableField,
|
||||
@@ -10,21 +10,19 @@ import {
|
||||
Textarea,
|
||||
Checkbox
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { Group, SelectValue } from "@scm-manager/ui-types";
|
||||
|
||||
import * as validator from "./groupValidation";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
submitForm: Group => void,
|
||||
loading?: boolean,
|
||||
group?: Group,
|
||||
loadUserSuggestions: string => any
|
||||
type Props = WithTranslation & {
|
||||
submitForm: (p: Group) => void;
|
||||
loading?: boolean;
|
||||
group?: Group;
|
||||
loadUserSuggestions: (p: string) => any;
|
||||
};
|
||||
|
||||
type State = {
|
||||
group: Group,
|
||||
nameValidationError: boolean
|
||||
group: Group;
|
||||
nameValidationError: boolean;
|
||||
};
|
||||
|
||||
class GroupForm extends React.Component<Props, State> {
|
||||
@@ -49,7 +47,12 @@ class GroupForm extends React.Component<Props, State> {
|
||||
componentDidMount() {
|
||||
const { group } = this.props;
|
||||
if (group) {
|
||||
this.setState({ ...this.state, group: { ...group } });
|
||||
this.setState({
|
||||
...this.state,
|
||||
group: {
|
||||
...group
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +84,7 @@ class GroupForm extends React.Component<Props, State> {
|
||||
const { loadUserSuggestions, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
<MemberNameTagGroup
|
||||
members={group.members}
|
||||
memberListChanged={this.memberListChanged}
|
||||
/>
|
||||
<MemberNameTagGroup members={group.members} memberListChanged={this.memberListChanged} />
|
||||
<AutocompleteAddEntryToTableField
|
||||
addEntry={this.addMember}
|
||||
disabled={false}
|
||||
@@ -154,11 +154,7 @@ class GroupForm extends React.Component<Props, State> {
|
||||
/>
|
||||
{this.renderExternalField(group)}
|
||||
{this.renderMemberfields(group)}
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
label={t("groupForm.submit")}
|
||||
loading={loading}
|
||||
/>
|
||||
<SubmitButton disabled={!this.isValid()} label={t("groupForm.submit")} loading={loading} />
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
@@ -195,21 +191,30 @@ class GroupForm extends React.Component<Props, State> {
|
||||
handleGroupNameChange = (name: string) => {
|
||||
this.setState({
|
||||
nameValidationError: !validator.isNameValid(name),
|
||||
group: { ...this.state.group, name }
|
||||
group: {
|
||||
...this.state.group,
|
||||
name
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleDescriptionChange = (description: string) => {
|
||||
this.setState({
|
||||
group: { ...this.state.group, description }
|
||||
group: {
|
||||
...this.state.group,
|
||||
description
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleExternalChange = (external: boolean) => {
|
||||
this.setState({
|
||||
group: { ...this.state.group, external }
|
||||
group: {
|
||||
...this.state.group,
|
||||
external
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("groups")(GroupForm);
|
||||
export default withTranslation("groups")(GroupForm);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { validation } from "@scm-manager/ui-components";
|
||||
|
||||
const isNameValid = validation.isNameValid;
|
||||
@@ -1,5 +1,3 @@
|
||||
//@flow
|
||||
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import "@scm-manager/ui-tests/enzyme";
|
||||
@@ -8,22 +6,22 @@ import EditGroupNavLink from "./EditGroupNavLink";
|
||||
|
||||
it("should render nothing, if the edit link is missing", () => {
|
||||
const group = {
|
||||
_links: {}
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(<EditGroupNavLink group={group} editUrl='/group/edit'/>);
|
||||
const navLink = shallow(<EditGroupNavLink group={group} editUrl="/group/edit" />);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const group = {
|
||||
_links: {
|
||||
update: {
|
||||
href: "/groups"
|
||||
}
|
||||
_links: {
|
||||
update: {
|
||||
href: "/groups"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = shallow(<EditGroupNavLink group={group} editUrl='/group/edit'/>);
|
||||
const navLink = shallow(<EditGroupNavLink group={group} editUrl="/group/edit" />);
|
||||
expect(navLink.text()).not.toBe("");
|
||||
});
|
||||
@@ -1,13 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
editUrl: string,
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
group: Group;
|
||||
editUrl: string;
|
||||
};
|
||||
|
||||
class EditGroupNavLink extends React.Component<Props> {
|
||||
@@ -25,4 +23,4 @@ class EditGroupNavLink extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(EditGroupNavLink);
|
||||
export default withTranslation("groups")(EditGroupNavLink);
|
||||
@@ -1,13 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
group: Group,
|
||||
permissionsUrl: String
|
||||
type Props = WithTranslation & {
|
||||
group: Group;
|
||||
permissionsUrl: string;
|
||||
};
|
||||
|
||||
class ChangePermissionNavLink extends React.Component<Props> {
|
||||
@@ -25,4 +23,4 @@ class ChangePermissionNavLink extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("groups")(ChangePermissionNavLink);
|
||||
export default withTranslation("groups")(ChangePermissionNavLink);
|
||||
@@ -1,15 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { DateFromNow, Checkbox } from "@scm-manager/ui-components";
|
||||
import GroupMember from "./GroupMember";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
|
||||
// Context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
group: Group;
|
||||
};
|
||||
|
||||
class Details extends React.Component<Props> {
|
||||
@@ -76,4 +72,4 @@ class Details extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(Details);
|
||||
export default withTranslation("groups")(Details);
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Member } from "@scm-manager/ui-types";
|
||||
import { Member } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
member: Member
|
||||
member: Member;
|
||||
};
|
||||
|
||||
export default class GroupMember extends React.Component<Props> {
|
||||
@@ -1,15 +1,11 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
group: Group;
|
||||
};
|
||||
|
||||
class GroupRow extends React.Component<Props> {
|
||||
@@ -28,11 +24,13 @@ class GroupRow extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{iconType} {this.renderLink(to, group.name)}</td>
|
||||
<td>
|
||||
{iconType} {this.renderLink(to, group.name)}
|
||||
</td>
|
||||
<td className="is-hidden-mobile">{group.description}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(GroupRow);
|
||||
export default withTranslation("groups")(GroupRow);
|
||||
@@ -1,12 +1,10 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import GroupRow from "./GroupRow";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
groups: Group[]
|
||||
type Props = WithTranslation & {
|
||||
groups: Group[];
|
||||
};
|
||||
|
||||
class GroupTable extends React.Component<Props> {
|
||||
@@ -30,4 +28,4 @@ class GroupTable extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(GroupTable);
|
||||
export default withTranslation("groups")(GroupTable);
|
||||
@@ -1,37 +1,24 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
|
||||
import { Page } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
createGroup,
|
||||
isCreateGroupPending,
|
||||
getCreateGroupFailure,
|
||||
createGroupReset
|
||||
} from "../modules/groups";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import type { History } from "history";
|
||||
import {
|
||||
getGroupsLink,
|
||||
getUserAutoCompleteLink
|
||||
} from "../../modules/indexResource";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { Page } from "@scm-manager/ui-components";
|
||||
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||
import { createGroup, isCreateGroupPending, getCreateGroupFailure, createGroupReset } from "../modules/groups";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
createGroup: (link: string, group: Group, callback?: () => void) => void,
|
||||
history: History,
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
resetForm: () => void,
|
||||
createLink: string,
|
||||
autocompleteLink: string
|
||||
type Props = WithTranslation & {
|
||||
createGroup: (link: string, group: Group, callback?: () => void) => void;
|
||||
history: History;
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
resetForm: () => void;
|
||||
createLink: string;
|
||||
autocompleteLink: string;
|
||||
};
|
||||
|
||||
type State = {};
|
||||
|
||||
class CreateGroup extends React.Component<Props, State> {
|
||||
class CreateGroup extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.resetForm();
|
||||
}
|
||||
@@ -39,11 +26,7 @@ class CreateGroup extends React.Component<Props, State> {
|
||||
render() {
|
||||
const { t, loading, error } = this.props;
|
||||
return (
|
||||
<Page
|
||||
title={t("add-group.title")}
|
||||
subtitle={t("add-group.subtitle")}
|
||||
error={error}
|
||||
>
|
||||
<Page title={t("add-group.title")} subtitle={t("add-group.subtitle")} error={error}>
|
||||
<div>
|
||||
<GroupForm
|
||||
submitForm={group => this.createGroup(group)}
|
||||
@@ -72,16 +55,13 @@ class CreateGroup extends React.Component<Props, State> {
|
||||
this.props.history.push("/group/" + group.name);
|
||||
};
|
||||
createGroup = (group: Group) => {
|
||||
this.props.createGroup(this.props.createLink, group, () =>
|
||||
this.groupCreated(group)
|
||||
);
|
||||
this.props.createGroup(this.props.createLink, group, () => this.groupCreated(group));
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
createGroup: (link: string, group: Group, callback?: () => void) =>
|
||||
dispatch(createGroup(link, group, callback)),
|
||||
createGroup: (link: string, group: Group, callback?: () => void) => dispatch(createGroup(link, group, callback)),
|
||||
resetForm: () => {
|
||||
dispatch(createGroupReset());
|
||||
}
|
||||
@@ -104,4 +84,4 @@ const mapStateToProps = state => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("groups")(CreateGroup));
|
||||
)(withTranslation("groups")(CreateGroup));
|
||||
@@ -1,32 +1,21 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
DeleteButton,
|
||||
confirmAlert,
|
||||
ErrorNotification
|
||||
} from "@scm-manager/ui-components";
|
||||
import {
|
||||
deleteGroup,
|
||||
getDeleteGroupFailure,
|
||||
isDeleteGroupPending
|
||||
} from "../modules/groups";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { deleteGroup, getDeleteGroupFailure, isDeleteGroupPending } from "../modules/groups";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
group: Group,
|
||||
confirmDialog?: boolean,
|
||||
deleteGroup: (group: Group, callback?: () => void) => void,
|
||||
type Props = WithTranslation & {
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
group: Group;
|
||||
confirmDialog?: boolean;
|
||||
deleteGroup: (group: Group, callback?: () => void) => void;
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
t: string => string
|
||||
history: History;
|
||||
};
|
||||
|
||||
export class DeleteGroup extends React.Component<Props> {
|
||||
@@ -78,11 +67,7 @@ export class DeleteGroup extends React.Component<Props> {
|
||||
<ErrorNotification error={error} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<DeleteButton
|
||||
label={t("deleteGroup.button")}
|
||||
action={action}
|
||||
loading={loading}
|
||||
/>
|
||||
<DeleteButton label={t("deleteGroup.button")} action={action} loading={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -110,4 +95,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(translate("groups")(DeleteGroup)));
|
||||
)(withRouter(withTranslation("groups")(DeleteGroup)));
|
||||
@@ -1,29 +1,23 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
import {
|
||||
modifyGroup,
|
||||
getModifyGroupFailure,
|
||||
isModifyGroupPending,
|
||||
modifyGroupReset
|
||||
} from "../modules/groups";
|
||||
import type { History } from "history";
|
||||
import { modifyGroup, getModifyGroupFailure, isModifyGroupPending, modifyGroupReset } from "../modules/groups";
|
||||
import { History } from "history";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
fetchGroup: (name: string) => void,
|
||||
modifyGroup: (group: Group, callback?: () => void) => void,
|
||||
modifyGroupReset: Group => void,
|
||||
autocompleteLink: string,
|
||||
history: History,
|
||||
loading?: boolean,
|
||||
error: Error
|
||||
group: Group;
|
||||
fetchGroup: (name: string) => void;
|
||||
modifyGroup: (group: Group, callback?: () => void) => void;
|
||||
modifyGroupReset: (p: Group) => void;
|
||||
autocompleteLink: string;
|
||||
history: History;
|
||||
loading?: boolean;
|
||||
error: Error;
|
||||
};
|
||||
|
||||
class EditGroup extends React.Component<Props> {
|
||||
@@ -1,17 +1,8 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import type { History } from "history";
|
||||
import type { Group, PagedCollection } from "@scm-manager/ui-types";
|
||||
import {
|
||||
fetchGroupsByPage,
|
||||
getGroupsFromState,
|
||||
isFetchGroupsPending,
|
||||
getFetchGroupsFailure,
|
||||
isPermittedToCreateGroups,
|
||||
selectListAsCollection
|
||||
} from "../modules/groups";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { Group, PagedCollection } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Page,
|
||||
PageActions,
|
||||
@@ -21,54 +12,46 @@ import {
|
||||
urls,
|
||||
CreateButton
|
||||
} from "@scm-manager/ui-components";
|
||||
import { GroupTable } from "./../components/table";
|
||||
import { getGroupsLink } from "../../modules/indexResource";
|
||||
import {
|
||||
fetchGroupsByPage,
|
||||
getGroupsFromState,
|
||||
isFetchGroupsPending,
|
||||
getFetchGroupsFailure,
|
||||
isPermittedToCreateGroups,
|
||||
selectListAsCollection
|
||||
} from "../modules/groups";
|
||||
import { GroupTable } from "./../components/table";
|
||||
|
||||
type Props = {
|
||||
groups: Group[],
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
canAddGroups: boolean,
|
||||
list: PagedCollection,
|
||||
page: number,
|
||||
groupLink: string,
|
||||
type Props = WithTranslation & {
|
||||
groups: Group[];
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
canAddGroups: boolean;
|
||||
list: PagedCollection;
|
||||
page: number;
|
||||
groupLink: string;
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
history: History,
|
||||
location: any,
|
||||
history: History;
|
||||
location: any;
|
||||
|
||||
// dispatch functions
|
||||
fetchGroupsByPage: (link: string, page: number, filter?: string) => void
|
||||
fetchGroupsByPage: (link: string, page: number, filter?: string) => void;
|
||||
};
|
||||
|
||||
class Groups extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const { fetchGroupsByPage, groupLink, page, location } = this.props;
|
||||
fetchGroupsByPage(
|
||||
groupLink,
|
||||
page,
|
||||
urls.getQueryStringFromLocation(location)
|
||||
);
|
||||
fetchGroupsByPage(groupLink, page, urls.getQueryStringFromLocation(location));
|
||||
}
|
||||
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const {
|
||||
loading,
|
||||
list,
|
||||
page,
|
||||
groupLink,
|
||||
location,
|
||||
fetchGroupsByPage
|
||||
} = this.props;
|
||||
const { loading, list, page, groupLink, location, fetchGroupsByPage } = this.props;
|
||||
if (list && page && !loading) {
|
||||
const statePage: number = list.page + 1;
|
||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||
fetchGroupsByPage(
|
||||
groupLink,
|
||||
page,
|
||||
urls.getQueryStringFromLocation(location)
|
||||
);
|
||||
fetchGroupsByPage(groupLink, page, urls.getQueryStringFromLocation(location));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -76,20 +59,11 @@ class Groups extends React.Component<Props> {
|
||||
render() {
|
||||
const { groups, loading, error, canAddGroups, t } = this.props;
|
||||
return (
|
||||
<Page
|
||||
title={t("groups.title")}
|
||||
subtitle={t("groups.subtitle")}
|
||||
loading={loading || !groups}
|
||||
error={error}
|
||||
>
|
||||
<Page title={t("groups.title")} subtitle={t("groups.subtitle")} loading={loading || !groups} error={error}>
|
||||
{this.renderGroupTable()}
|
||||
{this.renderCreateButton()}
|
||||
<PageActions>
|
||||
<OverviewPageActions
|
||||
showCreateButton={canAddGroups}
|
||||
link="groups"
|
||||
label={t("create-group-button.label")}
|
||||
/>
|
||||
<OverviewPageActions showCreateButton={canAddGroups} link="groups" label={t("create-group-button.label")} />
|
||||
</PageActions>
|
||||
</Page>
|
||||
);
|
||||
@@ -101,11 +75,7 @@ class Groups extends React.Component<Props> {
|
||||
return (
|
||||
<>
|
||||
<GroupTable groups={groups} />
|
||||
<LinkPaginator
|
||||
collection={list}
|
||||
page={page}
|
||||
filter={urls.getQueryStringFromLocation(location)}
|
||||
/>
|
||||
<LinkPaginator collection={list} page={page} filter={urls.getQueryStringFromLocation(location)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -115,12 +85,7 @@ class Groups extends React.Component<Props> {
|
||||
renderCreateButton() {
|
||||
const { canAddGroups, t } = this.props;
|
||||
if (canAddGroups) {
|
||||
return (
|
||||
<CreateButton
|
||||
label={t("create-group-button.label")}
|
||||
link="/groups/create"
|
||||
/>
|
||||
);
|
||||
return <CreateButton label={t("create-group-button.label")} link="/groups/create" />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -158,4 +123,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("groups")(Groups));
|
||||
)(withTranslation("groups")(Groups));
|
||||
@@ -1,186 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Page,
|
||||
ErrorPage,
|
||||
Loading,
|
||||
Navigation,
|
||||
SubNavigation,
|
||||
Section,
|
||||
NavLink
|
||||
} from "@scm-manager/ui-components";
|
||||
import { Route } from "react-router-dom";
|
||||
import { Details } from "./../components/table";
|
||||
import {
|
||||
EditGroupNavLink,
|
||||
SetPermissionsNavLink
|
||||
} from "./../components/navLinks";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import type { History } from "history";
|
||||
import {
|
||||
fetchGroupByName,
|
||||
getGroupByName,
|
||||
isFetchGroupPending,
|
||||
getFetchGroupFailure
|
||||
} from "../modules/groups";
|
||||
|
||||
import { translate } from "react-i18next";
|
||||
import EditGroup from "./EditGroup";
|
||||
import { getGroupsLink } from "../../modules/indexResource";
|
||||
import SetPermissions from "../../permissions/components/SetPermissions";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
group: Group,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
groupLink: string,
|
||||
|
||||
// dispatcher functions
|
||||
fetchGroupByName: (string, string) => void,
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
class SingleGroup extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.fetchGroupByName(this.props.groupLink, this.props.name);
|
||||
}
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
return url.substring(0, url.length - 2);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
matchedUrl = () => {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, loading, error, group } = this.props;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("singleGroup.errorTitle")}
|
||||
subtitle={t("singleGroup.errorSubtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!group || loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const url = this.matchedUrl();
|
||||
|
||||
const extensionProps = {
|
||||
group,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title={group.name}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Route
|
||||
path={url}
|
||||
exact
|
||||
component={() => <Details group={group} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/settings/general`}
|
||||
exact
|
||||
component={() => <EditGroup group={group} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/settings/permissions`}
|
||||
exact
|
||||
component={() => (
|
||||
<SetPermissions
|
||||
selectedPermissionsLink={group._links.permissions}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="group.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("singleGroup.menu.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("singleGroup.menu.informationNavLink")}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="group.navigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
<SubNavigation
|
||||
to={`${url}/settings/general`}
|
||||
label={t("singleGroup.menu.settingsNavLink")}
|
||||
>
|
||||
<EditGroupNavLink
|
||||
group={group}
|
||||
editUrl={`${url}/settings/general`}
|
||||
/>
|
||||
<SetPermissionsNavLink
|
||||
group={group}
|
||||
permissionsUrl={`${url}/settings/permissions`}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="group.setting"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const name = ownProps.match.params.name;
|
||||
const group = getGroupByName(state, name);
|
||||
const loading = isFetchGroupPending(state, name);
|
||||
const error = getFetchGroupFailure(state, name);
|
||||
const groupLink = getGroupsLink(state);
|
||||
|
||||
return {
|
||||
name,
|
||||
group,
|
||||
loading,
|
||||
error,
|
||||
groupLink
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchGroupByName: (link: string, name: string) => {
|
||||
dispatch(fetchGroupByName(link, name));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("groups")(SingleGroup));
|
||||
124
scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx
Normal file
124
scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Route } from "react-router-dom";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { History } from "history";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { Page, ErrorPage, Loading, Navigation, SubNavigation, Section, NavLink } from "@scm-manager/ui-components";
|
||||
import { getGroupsLink } from "../../modules/indexResource";
|
||||
import { fetchGroupByName, getGroupByName, isFetchGroupPending, getFetchGroupFailure } from "../modules/groups";
|
||||
import { Details } from "./../components/table";
|
||||
import { EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks";
|
||||
import EditGroup from "./EditGroup";
|
||||
import SetPermissions from "../../permissions/components/SetPermissions";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
name: string;
|
||||
group: Group;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
groupLink: string;
|
||||
|
||||
// dispatcher functions
|
||||
fetchGroupByName: (p1: string, p2: string) => void;
|
||||
|
||||
// context objects
|
||||
match: any;
|
||||
history: History;
|
||||
};
|
||||
|
||||
class SingleGroup extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.fetchGroupByName(this.props.groupLink, this.props.name);
|
||||
}
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
return url.substring(0, url.length - 2);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
matchedUrl = () => {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, loading, error, group } = this.props;
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage title={t("singleGroup.errorTitle")} subtitle={t("singleGroup.errorSubtitle")} error={error} />;
|
||||
}
|
||||
|
||||
if (!group || loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const url = this.matchedUrl();
|
||||
|
||||
const extensionProps = {
|
||||
group,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title={group.name}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Route path={url} exact component={() => <Details group={group} />} />
|
||||
<Route path={`${url}/settings/general`} exact component={() => <EditGroup group={group} />} />
|
||||
<Route
|
||||
path={`${url}/settings/permissions`}
|
||||
exact
|
||||
component={() => <SetPermissions selectedPermissionsLink={group._links.permissions} />}
|
||||
/>
|
||||
<ExtensionPoint name="group.route" props={extensionProps} renderAll={true} />
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("singleGroup.menu.navigationLabel")}>
|
||||
<NavLink to={`${url}`} icon="fas fa-info-circle" label={t("singleGroup.menu.informationNavLink")} />
|
||||
<ExtensionPoint name="group.navigation" props={extensionProps} renderAll={true} />
|
||||
<SubNavigation to={`${url}/settings/general`} label={t("singleGroup.menu.settingsNavLink")}>
|
||||
<EditGroupNavLink group={group} editUrl={`${url}/settings/general`} />
|
||||
<SetPermissionsNavLink group={group} permissionsUrl={`${url}/settings/permissions`} />
|
||||
<ExtensionPoint name="group.setting" props={extensionProps} renderAll={true} />
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const name = ownProps.match.params.name;
|
||||
const group = getGroupByName(state, name);
|
||||
const loading = isFetchGroupPending(state, name);
|
||||
const error = getFetchGroupFailure(state, name);
|
||||
const groupLink = getGroupsLink(state);
|
||||
|
||||
return {
|
||||
name,
|
||||
group,
|
||||
loading,
|
||||
error,
|
||||
groupLink
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchGroupByName: (link: string, name: string) => {
|
||||
dispatch(fetchGroupByName(link, name));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withTranslation("groups")(SingleGroup));
|
||||
@@ -1,4 +1,3 @@
|
||||
//@flow
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
@@ -130,7 +129,9 @@ const responseBody = {
|
||||
};
|
||||
|
||||
const response = {
|
||||
headers: { "content-type": "application/json" },
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
responseBody
|
||||
};
|
||||
|
||||
@@ -145,7 +146,9 @@ describe("groups fetch()", () => {
|
||||
fetchMock.getOnce(GROUPS_URL, response);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_GROUPS_PENDING },
|
||||
{
|
||||
type: FETCH_GROUPS_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_GROUPS_SUCCESS,
|
||||
payload: response
|
||||
@@ -459,7 +462,13 @@ describe("groups reducer", () => {
|
||||
describe("selector tests", () => {
|
||||
it("should return an empty object", () => {
|
||||
expect(selectListAsCollection({})).toEqual({});
|
||||
expect(selectListAsCollection({ groups: { a: "a" } })).toEqual({});
|
||||
expect(
|
||||
selectListAsCollection({
|
||||
groups: {
|
||||
a: "a"
|
||||
}
|
||||
})
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("should return a state slice collection", () => {
|
||||
@@ -480,12 +489,24 @@ describe("selector tests", () => {
|
||||
|
||||
it("should return false when groupCreatePermission is false", () => {
|
||||
expect(isPermittedToCreateGroups({})).toBe(false);
|
||||
expect(isPermittedToCreateGroups({ groups: { list: { entry: {} } } })).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
isPermittedToCreateGroups({
|
||||
groups: { list: { entry: { groupCreatePermission: false } } }
|
||||
groups: {
|
||||
list: {
|
||||
entry: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
isPermittedToCreateGroups({
|
||||
groups: {
|
||||
list: {
|
||||
entry: {
|
||||
groupCreatePermission: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
@@ -527,13 +548,24 @@ describe("selector tests", () => {
|
||||
entries: ["a", "b"]
|
||||
},
|
||||
byNames: {
|
||||
a: { name: "a" },
|
||||
b: { name: "b" }
|
||||
a: {
|
||||
name: "a"
|
||||
},
|
||||
b: {
|
||||
name: "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(getGroupsFromState(state)).toEqual([{ name: "a" }, { name: "b" }]);
|
||||
expect(getGroupsFromState(state)).toEqual([
|
||||
{
|
||||
name: "a"
|
||||
},
|
||||
{
|
||||
name: "b"
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return null when there are no groups in the state", () => {
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
import * as types from "../../modules/types";
|
||||
import { combineReducers, Dispatch } from "redux";
|
||||
import type { Action, PagedCollection, Group } from "@scm-manager/ui-types";
|
||||
import { Action, PagedCollection, Group } from "@scm-manager/ui-types";
|
||||
|
||||
export const FETCH_GROUPS = "scm/groups/FETCH_GROUPS";
|
||||
export const FETCH_GROUPS_PENDING = `${FETCH_GROUPS}_${types.PENDING_SUFFIX}`;
|
||||
@@ -43,9 +42,7 @@ export function fetchGroups(link: string) {
|
||||
export function fetchGroupsByPage(link: string, page: number, filter?: string) {
|
||||
// backend start counting by 0
|
||||
if (filter) {
|
||||
return fetchGroupsByLink(
|
||||
`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`
|
||||
);
|
||||
return fetchGroupsByLink(`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`);
|
||||
}
|
||||
return fetchGroupsByLink(`${link}?page=${page - 1}`);
|
||||
}
|
||||
@@ -287,34 +284,30 @@ export function deleteGroupFailure(group: Group, error: Error): Action {
|
||||
}
|
||||
|
||||
//reducer
|
||||
function extractGroupsByNames(
|
||||
groups: Group[],
|
||||
groupNames: string[],
|
||||
oldGroupsByNames: Object
|
||||
) {
|
||||
function extractGroupsByNames(groups: Group[], groupNames: string[], oldGroupsByNames: object) {
|
||||
const groupsByNames = {};
|
||||
|
||||
for (let group of groups) {
|
||||
for (const group of groups) {
|
||||
groupsByNames[group.name] = group;
|
||||
}
|
||||
|
||||
for (let groupName in oldGroupsByNames) {
|
||||
for (const groupName in oldGroupsByNames) {
|
||||
groupsByNames[groupName] = oldGroupsByNames[groupName];
|
||||
}
|
||||
return groupsByNames;
|
||||
}
|
||||
|
||||
function deleteGroupInGroupsByNames(groups: {}, groupName: string) {
|
||||
let newGroups = {};
|
||||
for (let groupname in groups) {
|
||||
const newGroups = {};
|
||||
for (const groupname in groups) {
|
||||
if (groupname !== groupName) newGroups[groupname] = groups[groupname];
|
||||
}
|
||||
return newGroups;
|
||||
}
|
||||
|
||||
function deleteGroupInEntries(groups: [], groupName: string) {
|
||||
let newGroups = [];
|
||||
for (let group of groups) {
|
||||
const newGroups = [];
|
||||
for (const group of groups) {
|
||||
if (group !== groupName) newGroups.push(group);
|
||||
}
|
||||
return newGroups;
|
||||
@@ -346,10 +339,7 @@ function listReducer(state: any = {}, action: any = {}) {
|
||||
};
|
||||
// Delete single group actions
|
||||
case DELETE_GROUP_SUCCESS:
|
||||
const newGroupEntries = deleteGroupInEntries(
|
||||
state.entries,
|
||||
action.payload.name
|
||||
);
|
||||
const newGroupEntries = deleteGroupInEntries(state.entries, action.payload.name);
|
||||
return {
|
||||
...state,
|
||||
entries: newGroupEntries
|
||||
@@ -372,10 +362,7 @@ function byNamesReducer(state: any = {}, action: any = {}) {
|
||||
case FETCH_GROUP_SUCCESS:
|
||||
return reducerByName(state, action.payload.name, action.payload);
|
||||
case DELETE_GROUP_SUCCESS:
|
||||
const newGroupByNames = deleteGroupInGroupsByNames(
|
||||
state,
|
||||
action.payload.name
|
||||
);
|
||||
const newGroupByNames = deleteGroupInGroupsByNames(state, action.payload.name);
|
||||
return newGroupByNames;
|
||||
|
||||
default:
|
||||
@@ -390,14 +377,14 @@ export default combineReducers({
|
||||
|
||||
// selectors
|
||||
|
||||
const selectList = (state: Object) => {
|
||||
const selectList = (state: object) => {
|
||||
if (state.groups && state.groups.list) {
|
||||
return state.groups.list;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const selectListEntry = (state: Object): Object => {
|
||||
const selectListEntry = (state: object): object => {
|
||||
const list = selectList(state);
|
||||
if (list.entry) {
|
||||
return list.entry;
|
||||
@@ -405,11 +392,11 @@ const selectListEntry = (state: Object): Object => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const selectListAsCollection = (state: Object): PagedCollection => {
|
||||
export const selectListAsCollection = (state: object): PagedCollection => {
|
||||
return selectListEntry(state);
|
||||
};
|
||||
|
||||
export const isPermittedToCreateGroups = (state: Object): boolean => {
|
||||
export const isPermittedToCreateGroups = (state: object): boolean => {
|
||||
const permission = selectListEntry(state).groupCreatePermission;
|
||||
if (permission) {
|
||||
return true;
|
||||
@@ -417,68 +404,67 @@ export const isPermittedToCreateGroups = (state: Object): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export function getCreateGroupLink(state: Object) {
|
||||
if (state.groups.list.entry && state.groups.list.entry._links)
|
||||
return state.groups.list.entry._links.create.href;
|
||||
export function getCreateGroupLink(state: object) {
|
||||
if (state.groups.list.entry && state.groups.list.entry._links) return state.groups.list.entry._links.create.href;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getGroupsFromState(state: Object) {
|
||||
export function getGroupsFromState(state: object) {
|
||||
const groupNames = selectList(state).entries;
|
||||
if (!groupNames) {
|
||||
return null;
|
||||
}
|
||||
const groupEntries: Group[] = [];
|
||||
|
||||
for (let groupName of groupNames) {
|
||||
for (const groupName of groupNames) {
|
||||
groupEntries.push(state.groups.byNames[groupName]);
|
||||
}
|
||||
|
||||
return groupEntries;
|
||||
}
|
||||
|
||||
export function isFetchGroupsPending(state: Object) {
|
||||
export function isFetchGroupsPending(state: object) {
|
||||
return isPending(state, FETCH_GROUPS);
|
||||
}
|
||||
|
||||
export function getFetchGroupsFailure(state: Object) {
|
||||
export function getFetchGroupsFailure(state: object) {
|
||||
return getFailure(state, FETCH_GROUPS);
|
||||
}
|
||||
|
||||
export function isCreateGroupPending(state: Object) {
|
||||
export function isCreateGroupPending(state: object) {
|
||||
return isPending(state, CREATE_GROUP);
|
||||
}
|
||||
|
||||
export function getCreateGroupFailure(state: Object) {
|
||||
export function getCreateGroupFailure(state: object) {
|
||||
return getFailure(state, CREATE_GROUP);
|
||||
}
|
||||
|
||||
export function isModifyGroupPending(state: Object, name: string) {
|
||||
export function isModifyGroupPending(state: object, name: string) {
|
||||
return isPending(state, MODIFY_GROUP, name);
|
||||
}
|
||||
|
||||
export function getModifyGroupFailure(state: Object, name: string) {
|
||||
export function getModifyGroupFailure(state: object, name: string) {
|
||||
return getFailure(state, MODIFY_GROUP, name);
|
||||
}
|
||||
|
||||
export function getGroupByName(state: Object, name: string) {
|
||||
export function getGroupByName(state: object, name: string) {
|
||||
if (state.groups && state.groups.byNames) {
|
||||
return state.groups.byNames[name];
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchGroupPending(state: Object, name: string) {
|
||||
export function isFetchGroupPending(state: object, name: string) {
|
||||
return isPending(state, FETCH_GROUP, name);
|
||||
}
|
||||
|
||||
export function getFetchGroupFailure(state: Object, name: string) {
|
||||
export function getFetchGroupFailure(state: object, name: string) {
|
||||
return getFailure(state, FETCH_GROUP, name);
|
||||
}
|
||||
|
||||
export function isDeleteGroupPending(state: Object, name: string) {
|
||||
export function isDeleteGroupPending(state: object, name: string) {
|
||||
return isPending(state, DELETE_GROUP, name);
|
||||
}
|
||||
|
||||
export function getDeleteGroupFailure(state: Object, name: string) {
|
||||
export function getDeleteGroupFailure(state: object, name: string) {
|
||||
return getFailure(state, DELETE_GROUP, name);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import i18n from "i18next";
|
||||
// @ts-ignore
|
||||
import Backend from "i18next-fetch-backend";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { reactI18nextModule } from "react-i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import { urls } from "@scm-manager/ui-components";
|
||||
|
||||
const loadPath = urls.withContextPath("/locales/{{lng}}/{{ns}}.json");
|
||||
@@ -11,7 +12,7 @@ const loadPath = urls.withContextPath("/locales/{{lng}}/{{ns}}.json");
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(LanguageDetector)
|
||||
.use(reactI18nextModule)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: "en",
|
||||
|
||||
@@ -29,7 +30,8 @@ i18n
|
||||
},
|
||||
|
||||
react: {
|
||||
wait: true
|
||||
wait: true,
|
||||
useSuspense: false
|
||||
},
|
||||
|
||||
backend: {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import Index from "./containers/Index";
|
||||
@@ -35,10 +35,7 @@ import reducer, {
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import {
|
||||
FETCH_INDEXRESOURCES_PENDING,
|
||||
FETCH_INDEXRESOURCES_SUCCESS
|
||||
} from "./indexResource";
|
||||
import { FETCH_INDEXRESOURCES_PENDING, FETCH_INDEXRESOURCES_SUCCESS } from "./indexResource";
|
||||
|
||||
const me = {
|
||||
name: "tricia",
|
||||
@@ -100,12 +97,16 @@ describe("auth actions", () => {
|
||||
username: "tricia",
|
||||
password: "secret123"
|
||||
},
|
||||
headers: { "content-type": "application/json" }
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
body: me,
|
||||
headers: { "content-type": "application/json" }
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
const meLink = {
|
||||
@@ -119,19 +120,29 @@ describe("auth actions", () => {
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: LOGIN_PENDING },
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{ type: FETCH_INDEXRESOURCES_SUCCESS, payload: { _links: meLink } },
|
||||
{ type: LOGIN_SUCCESS, payload: me }
|
||||
{
|
||||
type: LOGIN_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: {
|
||||
_links: meLink
|
||||
}
|
||||
},
|
||||
{
|
||||
type: LOGIN_SUCCESS,
|
||||
payload: me
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(login("/auth/access_token", "tricia", "secret123"))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
return store.dispatch(login("/auth/access_token", "tricia", "secret123")).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch login failure", () => {
|
||||
@@ -140,24 +151,26 @@ describe("auth actions", () => {
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(login("/auth/access_token", "tricia", "secret123"))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(LOGIN_PENDING);
|
||||
expect(actions[1].type).toEqual(LOGIN_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
return store.dispatch(login("/auth/access_token", "tricia", "secret123")).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(LOGIN_PENDING);
|
||||
expect(actions[1].type).toEqual(LOGIN_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch fetch me success", () => {
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
body: me,
|
||||
headers: { "content-type": "application/json" }
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_ME_PENDING },
|
||||
{
|
||||
type: FETCH_ME_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_ME_SUCCESS,
|
||||
payload: me
|
||||
@@ -189,8 +202,13 @@ describe("auth actions", () => {
|
||||
fetchMock.getOnce("/api/v2/me", 401);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_ME_PENDING },
|
||||
{ type: FETCH_ME_UNAUTHORIZED, resetPending: true }
|
||||
{
|
||||
type: FETCH_ME_PENDING
|
||||
},
|
||||
{
|
||||
type: FETCH_ME_UNAUTHORIZED,
|
||||
resetPending: true
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -219,9 +237,15 @@ describe("auth actions", () => {
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: LOGOUT_PENDING },
|
||||
{ type: LOGOUT_SUCCESS },
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING }
|
||||
{
|
||||
type: LOGOUT_PENDING
|
||||
},
|
||||
{
|
||||
type: LOGOUT_SUCCESS
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_PENDING
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -234,7 +258,9 @@ describe("auth actions", () => {
|
||||
it("should dispatch logout success and redirect", () => {
|
||||
fetchMock.deleteOnce("/api/v2/auth/access_token", {
|
||||
status: 200,
|
||||
body: { logoutRedirect: "http://example.com/cas/logout" }
|
||||
body: {
|
||||
logoutRedirect: "http://example.com/cas/logout"
|
||||
}
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
@@ -252,16 +278,18 @@ describe("auth actions", () => {
|
||||
window.location.assign = jest.fn();
|
||||
|
||||
const expectedActions = [
|
||||
{ type: LOGOUT_PENDING },
|
||||
{ type: LOGOUT_REDIRECT }
|
||||
{
|
||||
type: LOGOUT_PENDING
|
||||
},
|
||||
{
|
||||
type: LOGOUT_REDIRECT
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(logout("/auth/access_token")).then(() => {
|
||||
expect(window.location.assign.mock.calls[0][0]).toBe(
|
||||
"http://example.com/cas/logout"
|
||||
);
|
||||
expect(window.location.assign.mock.calls[0][0]).toBe("http://example.com/cas/logout");
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
@@ -286,21 +314,42 @@ describe("auth selectors", () => {
|
||||
|
||||
it("should return true if me exist and login Link does not exist", () => {
|
||||
expect(
|
||||
isAuthenticated({ auth: { me }, indexResources: { links: {} } })
|
||||
isAuthenticated({
|
||||
auth: {
|
||||
me
|
||||
},
|
||||
indexResources: {
|
||||
links: {}
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if me exist and login Link does exist", () => {
|
||||
expect(
|
||||
isAuthenticated({
|
||||
auth: { me },
|
||||
indexResources: { links: { login: { href: "login.href" } } }
|
||||
auth: {
|
||||
me
|
||||
},
|
||||
indexResources: {
|
||||
links: {
|
||||
login: {
|
||||
href: "login.href"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return me", () => {
|
||||
expect(getMe({ auth: { me } })).toBe(me);
|
||||
expect(
|
||||
getMe({
|
||||
auth: {
|
||||
me
|
||||
}
|
||||
})
|
||||
).toBe(me);
|
||||
});
|
||||
|
||||
it("should return undefined, if me is not set", () => {
|
||||
@@ -308,31 +357,67 @@ describe("auth selectors", () => {
|
||||
});
|
||||
|
||||
it("should return true, if FETCH_ME is pending", () => {
|
||||
expect(isFetchMePending({ pending: { [FETCH_ME]: true } })).toBe(true);
|
||||
expect(
|
||||
isFetchMePending({
|
||||
pending: {
|
||||
[FETCH_ME]: true
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if FETCH_ME is not in pending state", () => {
|
||||
expect(isFetchMePending({ pending: {} })).toBe(false);
|
||||
expect(
|
||||
isFetchMePending({
|
||||
pending: {}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, if LOGIN is pending", () => {
|
||||
expect(isLoginPending({ pending: { [LOGIN]: true } })).toBe(true);
|
||||
expect(
|
||||
isLoginPending({
|
||||
pending: {
|
||||
[LOGIN]: true
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if LOGIN is not in pending state", () => {
|
||||
expect(isLoginPending({ pending: {} })).toBe(false);
|
||||
expect(
|
||||
isLoginPending({
|
||||
pending: {}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, if LOGOUT is pending", () => {
|
||||
expect(isLogoutPending({ pending: { [LOGOUT]: true } })).toBe(true);
|
||||
expect(
|
||||
isLogoutPending({
|
||||
pending: {
|
||||
[LOGOUT]: true
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if LOGOUT is not in pending state", () => {
|
||||
expect(isLogoutPending({ pending: {} })).toBe(false);
|
||||
expect(
|
||||
isLogoutPending({
|
||||
pending: {}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return the error, if failure state is set for FETCH_ME", () => {
|
||||
expect(getFetchMeFailure({ failure: { [FETCH_ME]: error } })).toBe(error);
|
||||
expect(
|
||||
getFetchMeFailure({
|
||||
failure: {
|
||||
[FETCH_ME]: error
|
||||
}
|
||||
})
|
||||
).toBe(error);
|
||||
});
|
||||
|
||||
it("should return unknown, if failure state is not set for FETCH_ME", () => {
|
||||
@@ -340,7 +425,13 @@ describe("auth selectors", () => {
|
||||
});
|
||||
|
||||
it("should return the error, if failure state is set for LOGIN", () => {
|
||||
expect(getLoginFailure({ failure: { [LOGIN]: error } })).toBe(error);
|
||||
expect(
|
||||
getLoginFailure({
|
||||
failure: {
|
||||
[LOGIN]: error
|
||||
}
|
||||
})
|
||||
).toBe(error);
|
||||
});
|
||||
|
||||
it("should return unknown, if failure state is not set for LOGIN", () => {
|
||||
@@ -348,7 +439,13 @@ describe("auth selectors", () => {
|
||||
});
|
||||
|
||||
it("should return the error, if failure state is set for LOGOUT", () => {
|
||||
expect(getLogoutFailure({ failure: { [LOGOUT]: error } })).toBe(error);
|
||||
expect(
|
||||
getLogoutFailure({
|
||||
failure: {
|
||||
[LOGOUT]: error
|
||||
}
|
||||
})
|
||||
).toBe(error);
|
||||
});
|
||||
|
||||
it("should return unknown, if failure state is not set for LOGOUT", () => {
|
||||
@@ -360,10 +457,22 @@ describe("auth selectors", () => {
|
||||
});
|
||||
|
||||
it("should return false, if redirecting is false", () => {
|
||||
expect(isRedirecting({ auth: { redirecting: false } })).toBe(false);
|
||||
expect(
|
||||
isRedirecting({
|
||||
auth: {
|
||||
redirecting: false
|
||||
}
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, if redirecting is true", () => {
|
||||
expect(isRedirecting({ auth: { redirecting: true } })).toBe(true);
|
||||
expect(
|
||||
isRedirecting({
|
||||
auth: {
|
||||
redirecting: true
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import * as types from "./types";
|
||||
|
||||
import { apiClient, UnauthorizedError } from "@scm-manager/ui-components";
|
||||
@@ -37,8 +36,10 @@ export const LOGOUT_REDIRECT = `${LOGOUT}_REDIRECT`;
|
||||
const initialState = {};
|
||||
|
||||
export default function reducer(
|
||||
state: Object = initialState,
|
||||
action: Object = { type: "UNKNOWN" }
|
||||
state: object = initialState,
|
||||
action: object = {
|
||||
type: "UNKNOWN"
|
||||
}
|
||||
) {
|
||||
switch (action.type) {
|
||||
case LOGIN_SUCCESS:
|
||||
@@ -148,11 +149,7 @@ const callFetchMe = (link: string): Promise<Me> => {
|
||||
});
|
||||
};
|
||||
|
||||
export const login = (
|
||||
loginLink: string,
|
||||
username: string,
|
||||
password: string
|
||||
) => {
|
||||
export const login = (loginLink: string, username: string, password: string) => {
|
||||
const login_data = {
|
||||
cookie: true,
|
||||
grant_type: "password",
|
||||
@@ -234,45 +231,45 @@ export const logout = (link: string) => {
|
||||
|
||||
// selectors
|
||||
|
||||
const stateAuth = (state: Object): Object => {
|
||||
const stateAuth = (state: object): object => {
|
||||
return state.auth || {};
|
||||
};
|
||||
|
||||
export const isAuthenticated = (state: Object) => {
|
||||
export const isAuthenticated = (state: object) => {
|
||||
if (state.auth.me && !getLoginLink(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getMe = (state: Object): Me => {
|
||||
export const getMe = (state: object): Me => {
|
||||
return stateAuth(state).me;
|
||||
};
|
||||
|
||||
export const isFetchMePending = (state: Object) => {
|
||||
export const isFetchMePending = (state: object) => {
|
||||
return isPending(state, FETCH_ME);
|
||||
};
|
||||
|
||||
export const getFetchMeFailure = (state: Object) => {
|
||||
export const getFetchMeFailure = (state: object) => {
|
||||
return getFailure(state, FETCH_ME);
|
||||
};
|
||||
|
||||
export const isLoginPending = (state: Object) => {
|
||||
export const isLoginPending = (state: object) => {
|
||||
return isPending(state, LOGIN);
|
||||
};
|
||||
|
||||
export const getLoginFailure = (state: Object) => {
|
||||
export const getLoginFailure = (state: object) => {
|
||||
return getFailure(state, LOGIN);
|
||||
};
|
||||
|
||||
export const isLogoutPending = (state: Object) => {
|
||||
export const isLogoutPending = (state: object) => {
|
||||
return isPending(state, LOGOUT);
|
||||
};
|
||||
|
||||
export const getLogoutFailure = (state: Object) => {
|
||||
export const getLogoutFailure = (state: object) => {
|
||||
return getFailure(state, LOGOUT);
|
||||
};
|
||||
|
||||
export const isRedirecting = (state: Object) => {
|
||||
export const isRedirecting = (state: object) => {
|
||||
return !!stateAuth(state).redirecting;
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
export const CONTENT_TYPE_PASSWORD_CHANGE =
|
||||
"application/vnd.scmm-passwordChange+json;v=2";
|
||||
export function changePassword(
|
||||
url: string,
|
||||
oldPassword: string,
|
||||
newPassword: string
|
||||
) {
|
||||
return apiClient
|
||||
.put(url, { oldPassword, newPassword }, CONTENT_TYPE_PASSWORD_CHANGE)
|
||||
.then(response => {
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@@ -13,13 +13,13 @@ describe("change password", () => {
|
||||
|
||||
it("should update password", done => {
|
||||
fetchMock.put("/api/v2" + CHANGE_PASSWORD_URL, 204, {
|
||||
headers: { "content-type": CONTENT_TYPE_PASSWORD_CHANGE }
|
||||
headers: {
|
||||
"content-type": CONTENT_TYPE_PASSWORD_CHANGE
|
||||
}
|
||||
});
|
||||
|
||||
changePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then(
|
||||
content => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
changePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then(content => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
17
scm-ui/ui-webapp/src/modules/changePassword.ts
Normal file
17
scm-ui/ui-webapp/src/modules/changePassword.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
export const CONTENT_TYPE_PASSWORD_CHANGE = "application/vnd.scmm-passwordChange+json;v=2";
|
||||
export function changePassword(url: string, oldPassword: string, newPassword: string) {
|
||||
return apiClient
|
||||
.put(
|
||||
url,
|
||||
{
|
||||
oldPassword,
|
||||
newPassword
|
||||
},
|
||||
CONTENT_TYPE_PASSWORD_CHANGE
|
||||
)
|
||||
.then(response => {
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import reducer, { getFailure } from "./failure";
|
||||
|
||||
const err = new Error("something failed");
|
||||
@@ -6,13 +5,21 @@ const otherErr = new Error("something else failed");
|
||||
|
||||
describe("failure reducer", () => {
|
||||
it("should set the error for FETCH_ITEMS", () => {
|
||||
const newState = reducer({}, { type: "FETCH_ITEMS_FAILURE", payload: err });
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: "FETCH_ITEMS_FAILURE",
|
||||
payload: err
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBe(err);
|
||||
});
|
||||
|
||||
it("should do nothing for unknown action types", () => {
|
||||
const state = {};
|
||||
const newState = reducer(state, { type: "UNKNOWN" });
|
||||
const newState = reducer(state, {
|
||||
type: "UNKNOWN"
|
||||
});
|
||||
expect(newState).toBe(state);
|
||||
});
|
||||
|
||||
@@ -21,7 +28,10 @@ describe("failure reducer", () => {
|
||||
{},
|
||||
{
|
||||
type: "FETCH_ITEMS_FAILURE",
|
||||
payload: { something: "something", error: err }
|
||||
payload: {
|
||||
something: "something",
|
||||
error: err
|
||||
}
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBe(err);
|
||||
@@ -32,7 +42,10 @@ describe("failure reducer", () => {
|
||||
{
|
||||
FETCH_USERS: otherErr
|
||||
},
|
||||
{ type: "FETCH_ITEMS_FAILURE", payload: err }
|
||||
{
|
||||
type: "FETCH_ITEMS_FAILURE",
|
||||
payload: err
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBe(err);
|
||||
expect(newState["FETCH_USERS"]).toBe(otherErr);
|
||||
@@ -43,7 +56,9 @@ describe("failure reducer", () => {
|
||||
{
|
||||
FETCH_ITEMS: err
|
||||
},
|
||||
{ type: "FETCH_ITEMS_SUCCESS" }
|
||||
{
|
||||
type: "FETCH_ITEMS_SUCCESS"
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
@@ -53,7 +68,9 @@ describe("failure reducer", () => {
|
||||
{
|
||||
FETCH_ITEMS: err
|
||||
},
|
||||
{ type: "FETCH_ITEMS_RESET" }
|
||||
{
|
||||
type: "FETCH_ITEMS_RESET"
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
@@ -64,7 +81,9 @@ describe("failure reducer", () => {
|
||||
FETCH_ITEMS: err,
|
||||
FETCH_USERS: err
|
||||
},
|
||||
{ type: "FETCH_ITEMS_RESET" }
|
||||
{
|
||||
type: "FETCH_ITEMS_RESET"
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
expect(newState["FETCH_USERS"]).toBe(err);
|
||||
@@ -73,15 +92,25 @@ describe("failure reducer", () => {
|
||||
it("should set the error for a single item of FETCH_ITEM", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{ type: "FETCH_ITEM_FAILURE", payload: err, itemId: 42 }
|
||||
{
|
||||
type: "FETCH_ITEM_FAILURE",
|
||||
payload: err,
|
||||
itemId: 42
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEM/42"]).toBe(err);
|
||||
});
|
||||
|
||||
it("should reset error for a single item of FETCH_ITEM", () => {
|
||||
const newState = reducer(
|
||||
{ "FETCH_ITEM/42": err },
|
||||
{ type: "FETCH_ITEM_SUCCESS", payload: err, itemId: 42 }
|
||||
{
|
||||
"FETCH_ITEM/42": err
|
||||
},
|
||||
{
|
||||
type: "FETCH_ITEM_SUCCESS",
|
||||
payload: err,
|
||||
itemId: 42
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEM/42"]).toBeUndefined();
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user