mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 06:25:45 +01:00
merge
This commit is contained in:
@@ -184,8 +184,8 @@ public class ScmConfiguration implements Configuration {
|
|||||||
@XmlElement(name = "xsrf-protection")
|
@XmlElement(name = "xsrf-protection")
|
||||||
private boolean enabledXsrfProtection = true;
|
private boolean enabledXsrfProtection = true;
|
||||||
|
|
||||||
@XmlElement(name = "default-namespace-strategy")
|
@XmlElement(name = "namespace-strategy")
|
||||||
private String defaultNamespaceStrategy = "sonia.scm.repository.DefaultNamespaceStrategy";
|
private String namespaceStrategy = "UsernameNamespaceStrategy";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,7 +227,7 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.loginAttemptLimit = other.loginAttemptLimit;
|
this.loginAttemptLimit = other.loginAttemptLimit;
|
||||||
this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout;
|
this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout;
|
||||||
this.enabledXsrfProtection = other.enabledXsrfProtection;
|
this.enabledXsrfProtection = other.enabledXsrfProtection;
|
||||||
this.defaultNamespaceStrategy = other.defaultNamespaceStrategy;
|
this.namespaceStrategy = other.namespaceStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getAdminGroups() {
|
public Set<String> getAdminGroups() {
|
||||||
@@ -366,8 +366,8 @@ public class ScmConfiguration implements Configuration {
|
|||||||
return loginAttemptLimit > 0;
|
return loginAttemptLimit > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultNamespaceStrategy() {
|
public String getNamespaceStrategy() {
|
||||||
return defaultNamespaceStrategy;
|
return namespaceStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -501,8 +501,8 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.enabledXsrfProtection = enabledXsrfProtection;
|
this.enabledXsrfProtection = enabledXsrfProtection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultNamespaceStrategy(String defaultNamespaceStrategy) {
|
public void setNamespaceStrategy(String namespaceStrategy) {
|
||||||
this.defaultNamespaceStrategy = defaultNamespaceStrategy;
|
this.namespaceStrategy = namespaceStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -248,7 +248,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
/**
|
/**
|
||||||
* Returns true if the {@link Repository} is valid.
|
* Returns true if the {@link Repository} is valid.
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The name is not empty and contains only A-z, 0-9, _, -, /</li>
|
* <li>The namespace is valid</li>
|
||||||
|
* <li>The name is valid</li>
|
||||||
* <li>The type is not empty</li>
|
* <li>The type is not empty</li>
|
||||||
* <li>The contact is empty or contains a valid email address</li>
|
* <li>The contact is empty or contains a valid email address</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
@@ -257,9 +258,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return ValidationUtil.isRepositoryNameValid(name) && Util.isNotEmpty(type)
|
return ValidationUtil.isRepositoryNameValid(namespace)
|
||||||
&& ((Util.isEmpty(contact))
|
&& ValidationUtil.isRepositoryNameValid(name)
|
||||||
|| ValidationUtil.isMailAddressValid(contact));
|
&& Util.isNotEmpty(type)
|
||||||
|
&& ((Util.isEmpty(contact)) || ValidationUtil.isMailAddressValid(contact));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,14 +35,12 @@ package sonia.scm.util;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
|
|
||||||
import sonia.scm.Validateable;
|
import sonia.scm.Validateable;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -52,15 +50,16 @@ public final class ValidationUtil
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String REGEX_MAIL =
|
private static final String REGEX_MAIL =
|
||||||
"^[A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]*\\.[A-z0-9][A-z0-9-]+$";
|
"^[A-Za-z0-9][\\w.-]*@[A-Za-z0-9][\\w\\-\\.]*\\.[A-Za-z0-9][A-Za-z0-9-]+$";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String REGEX_NAME =
|
private static final String REGEX_NAME =
|
||||||
"^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$";
|
"^[A-Za-z0-9\\.\\-_@]|[^ ]([A-Za-z0-9\\.\\-_@ ]*[A-Za-z0-9\\.\\-_@]|[^ ])?$";
|
||||||
|
|
||||||
|
public static final String REGEX_REPOSITORYNAME = "(?!^\\.\\.$)(?!^\\.$)(?!.*[\\\\\\[\\]])^[A-Za-z0-9\\.][A-Za-z0-9\\.\\-_]*$";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String REGEX_REPOSITORYNAME =
|
private static final Pattern PATTERN_REPOSITORYNAME = Pattern.compile(REGEX_REPOSITORYNAME);
|
||||||
"(?!^\\.\\.$)(?!^\\.$)(?!.*[\\\\\\[\\]])^[A-z0-9\\.][A-z0-9\\.\\-_/]*$";
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
@@ -142,37 +141,15 @@ public final class ValidationUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Returns {@code true} if the repository name is valid.
|
||||||
*
|
*
|
||||||
*
|
* @param name repository name
|
||||||
* @param name
|
|
||||||
* @since 1.9
|
* @since 1.9
|
||||||
*
|
*
|
||||||
* @return
|
* @return {@code true} if repository name is valid
|
||||||
*/
|
*/
|
||||||
public static boolean isRepositoryNameValid(String name)
|
public static boolean isRepositoryNameValid(String name) {
|
||||||
{
|
return PATTERN_REPOSITORYNAME.matcher(name).matches();
|
||||||
Pattern pattern = Pattern.compile(REGEX_REPOSITORYNAME);
|
|
||||||
boolean result = true;
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(name))
|
|
||||||
{
|
|
||||||
for (String p : Splitter.on('/').split(name))
|
|
||||||
{
|
|
||||||
if (!pattern.matcher(p).matches())
|
|
||||||
{
|
|
||||||
result = false;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public class VndMediaType {
|
|||||||
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
||||||
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
||||||
|
|
||||||
|
public static final String NAMESPACE_STRATEGIES = PREFIX + "namespaceStrategies" + SUFFIX;
|
||||||
|
|
||||||
public static final String ME = PREFIX + "me" + SUFFIX;
|
public static final String ME = PREFIX + "me" + SUFFIX;
|
||||||
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
||||||
public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX;
|
public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX;
|
||||||
|
|||||||
@@ -143,51 +143,21 @@ public class ValidationUtilTest
|
|||||||
assertFalse(ValidationUtil.isNotContaining("test", "t"));
|
assertFalse(ValidationUtil.isNotContaining("test", "t"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsRepositoryNameValid()
|
public void testIsRepositoryNameValid() {
|
||||||
{
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid("scm"));
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid("scm/main"));
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid("scm/plugins/git-plugin"));
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid("s"));
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid("sc"));
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid(".scm/plugins"));
|
|
||||||
|
|
||||||
// issue 142
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("."));
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("/"));
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("scm/plugins/."));
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("scm/../plugins"));
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("scm/main/"));
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("/scm/main/"));
|
|
||||||
|
|
||||||
// issue 144
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("scm/./main"));
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid("scm//main"));
|
|
||||||
|
|
||||||
// issue 148
|
|
||||||
//J-
|
|
||||||
String[] validPaths = {
|
String[] validPaths = {
|
||||||
"scm",
|
"scm",
|
||||||
"scm/main",
|
|
||||||
"scm/plugins/git-plugin",
|
|
||||||
"s",
|
"s",
|
||||||
"sc",
|
"sc",
|
||||||
".scm/plugins",
|
|
||||||
".hiddenrepo",
|
".hiddenrepo",
|
||||||
"b.",
|
"b.",
|
||||||
"...",
|
"...",
|
||||||
"..c",
|
"..c",
|
||||||
"d..",
|
"d..",
|
||||||
"a/b..",
|
"a..c"
|
||||||
"a/..b",
|
|
||||||
"a..c",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// issue 142, 144 and 148
|
||||||
String[] invalidPaths = {
|
String[] invalidPaths = {
|
||||||
".",
|
".",
|
||||||
"/",
|
"/",
|
||||||
@@ -228,17 +198,22 @@ public class ValidationUtilTest
|
|||||||
"abc)abc",
|
"abc)abc",
|
||||||
"abc[abc",
|
"abc[abc",
|
||||||
"abc]abc",
|
"abc]abc",
|
||||||
"abc|abc"
|
"abc|abc",
|
||||||
|
"scm/main",
|
||||||
|
"scm/plugins/git-plugin",
|
||||||
|
".scm/plugins",
|
||||||
|
"a/b..",
|
||||||
|
"a/..b",
|
||||||
|
"scm/main",
|
||||||
|
"scm/plugins/git-plugin",
|
||||||
|
"scm/plugins/git-plugin"
|
||||||
};
|
};
|
||||||
//J+
|
|
||||||
|
|
||||||
for (String path : validPaths)
|
for (String path : validPaths) {
|
||||||
{
|
|
||||||
assertTrue(ValidationUtil.isRepositoryNameValid(path));
|
assertTrue(ValidationUtil.isRepositoryNameValid(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String path : invalidPaths)
|
for (String path : invalidPaths) {
|
||||||
{
|
|
||||||
assertFalse(ValidationUtil.isRepositoryNameValid(path));
|
assertFalse(ValidationUtil.isRepositoryNameValid(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,9 @@
|
|||||||
"react-i18next": "^7.11.0",
|
"react-i18next": "^7.11.0",
|
||||||
"react-jss": "^8.6.1",
|
"react-jss": "^8.6.1",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-select": "^2.1.2"
|
"react-select": "^2.1.2",
|
||||||
|
"react-markdown": "^4.0.6",
|
||||||
|
"react-syntax-highlighter": "^10.2.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"gitdiff-parser": "https://github.com/cloudogu/gitdiff-parser#3a72da4a8e3d9bfb4b9e01a43e85628c19f26cc4"
|
"gitdiff-parser": "https://github.com/cloudogu/gitdiff-parser#3a72da4a8e3d9bfb4b9e01a43e85628c19f26cc4"
|
||||||
|
|||||||
45
scm-ui-components/packages/ui-components/src/MarkdownView.js
Normal file
45
scm-ui-components/packages/ui-components/src/MarkdownView.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import SyntaxHighlighter from "./SyntaxHighlighter";
|
||||||
|
import Markdown from "react-markdown/with-html";
|
||||||
|
import {binder} from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
content: string,
|
||||||
|
renderContext?: Object,
|
||||||
|
renderers?: Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MarkdownView extends React.Component<Props> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {content, renderers, renderContext} = this.props;
|
||||||
|
|
||||||
|
const rendererFactory = binder.getExtension("markdown-renderer-factory");
|
||||||
|
let rendererList = renderers;
|
||||||
|
|
||||||
|
if (rendererFactory){
|
||||||
|
rendererList = rendererFactory(renderContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rendererList){
|
||||||
|
rendererList = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rendererList.code){
|
||||||
|
rendererList.code = SyntaxHighlighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Markdown
|
||||||
|
className="content"
|
||||||
|
skipHtml={true}
|
||||||
|
escapeHtml={true}
|
||||||
|
source={content}
|
||||||
|
renderers={rendererList}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MarkdownView;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import ReactSyntaxHighlighter from "react-syntax-highlighter";
|
||||||
|
import { arduinoLight } from "react-syntax-highlighter/dist/styles/hljs";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
language: string,
|
||||||
|
value: string
|
||||||
|
};
|
||||||
|
|
||||||
|
class SyntaxHighlighter extends React.Component<Props> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ReactSyntaxHighlighter
|
||||||
|
showLineNumbers="false"
|
||||||
|
language={this.props.language}
|
||||||
|
style={arduinoLight}
|
||||||
|
>
|
||||||
|
{this.props.value}
|
||||||
|
</ReactSyntaxHighlighter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SyntaxHighlighter;
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//@flow
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
content: string
|
|
||||||
};
|
|
||||||
|
|
||||||
class MarkdownView extends React.Component<Props> {
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {content } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MarkdownView;
|
|
||||||
@@ -11,5 +11,4 @@ export { default as Textarea } from "./Textarea.js";
|
|||||||
export { default as PasswordConfirmation } from "./PasswordConfirmation.js";
|
export { default as PasswordConfirmation } from "./PasswordConfirmation.js";
|
||||||
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
||||||
export { default as DropDown } from "./DropDown.js";
|
export { default as DropDown } from "./DropDown.js";
|
||||||
export { default as MarkdownView } from "./MarkdownView.js";
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export { default as Tooltip } from "./Tooltip";
|
|||||||
export { getPageFromMatch } from "./urls";
|
export { getPageFromMatch } from "./urls";
|
||||||
export { default as Autocomplete} from "./Autocomplete";
|
export { default as Autocomplete} from "./Autocomplete";
|
||||||
export { default as BranchSelector } from "./BranchSelector";
|
export { default as BranchSelector } from "./BranchSelector";
|
||||||
|
export { default as MarkdownView } from "./MarkdownView";
|
||||||
|
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
||||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||||
|
|
||||||
export { apiClient } from "./apiclient.js";
|
export { apiClient } from "./apiclient.js";
|
||||||
|
|||||||
@@ -582,6 +582,12 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.12.0"
|
regenerator-runtime "^0.12.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.3.1":
|
||||||
|
version "7.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.12.0"
|
||||||
|
|
||||||
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
||||||
@@ -1291,6 +1297,10 @@ backo2@1.0.2:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
|
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
|
||||||
|
|
||||||
|
bail@^1.0.0:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3"
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
@@ -1787,6 +1797,18 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
|
|||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
supports-color "^5.3.0"
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
|
character-entities-legacy@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c"
|
||||||
|
|
||||||
|
character-entities@^1.0.0:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363"
|
||||||
|
|
||||||
|
character-reference-invalid@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed"
|
||||||
|
|
||||||
chardet@^0.7.0:
|
chardet@^0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||||
@@ -1888,6 +1910,14 @@ cli-width@^2.0.0:
|
|||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||||
|
|
||||||
|
clipboard@^2.0.0:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
|
||||||
|
dependencies:
|
||||||
|
good-listener "^1.2.2"
|
||||||
|
select "^1.1.2"
|
||||||
|
tiny-emitter "^2.0.0"
|
||||||
|
|
||||||
cliui@^3.2.0:
|
cliui@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
|
||||||
@@ -1944,6 +1974,10 @@ code-point-at@^1.0.0:
|
|||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||||
|
|
||||||
|
collapse-white-space@^1.0.2:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091"
|
||||||
|
|
||||||
collection-visit@^1.0.0:
|
collection-visit@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
||||||
@@ -1988,6 +2022,12 @@ combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.5, combined
|
|||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream "~1.0.0"
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
|
comma-separated-tokens@^1.0.0:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db"
|
||||||
|
dependencies:
|
||||||
|
trim "0.0.1"
|
||||||
|
|
||||||
commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0:
|
commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0:
|
||||||
version "2.19.0"
|
version "2.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
|
||||||
@@ -2376,6 +2416,10 @@ delayed-stream@~1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
|
||||||
|
delegate@^3.1.2:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
|
||||||
|
|
||||||
delegates@^1.0.0:
|
delegates@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
@@ -2494,6 +2538,10 @@ domelementtype@1, domelementtype@^1.3.0:
|
|||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
|
||||||
|
|
||||||
|
domelementtype@^1.3.1:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||||
|
|
||||||
domelementtype@~1.1.1:
|
domelementtype@~1.1.1:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
|
||||||
@@ -2504,7 +2552,7 @@ domexception@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^4.0.2"
|
webidl-conversions "^4.0.2"
|
||||||
|
|
||||||
domhandler@^2.3.0:
|
domhandler@^2.3.0, domhandler@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3113,6 +3161,12 @@ fast-xml-parser@^3.12.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
nimnjs "^1.3.2"
|
nimnjs "^1.3.2"
|
||||||
|
|
||||||
|
fault@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa"
|
||||||
|
dependencies:
|
||||||
|
format "^0.2.2"
|
||||||
|
|
||||||
fb-watchman@^2.0.0:
|
fb-watchman@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
|
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
|
||||||
@@ -3322,6 +3376,10 @@ form-data@~2.3.2:
|
|||||||
combined-stream "^1.0.6"
|
combined-stream "^1.0.6"
|
||||||
mime-types "^2.1.12"
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
format@^0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
|
||||||
|
|
||||||
fragment-cache@^0.2.1:
|
fragment-cache@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
|
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
|
||||||
@@ -3593,6 +3651,12 @@ glogg@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sparkles "^1.0.0"
|
sparkles "^1.0.0"
|
||||||
|
|
||||||
|
good-listener@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
|
||||||
|
dependencies:
|
||||||
|
delegate "^3.1.2"
|
||||||
|
|
||||||
got@^7.1.0:
|
got@^7.1.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
|
resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
|
||||||
@@ -3831,6 +3895,19 @@ hash.js@^1.0.0, hash.js@^1.0.3:
|
|||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
minimalistic-assert "^1.0.1"
|
minimalistic-assert "^1.0.1"
|
||||||
|
|
||||||
|
hast-util-parse-selector@^2.2.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.1.tgz#4ddbae1ae12c124e3eb91b581d2556441766f0ab"
|
||||||
|
|
||||||
|
hastscript@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-5.0.0.tgz#fee10382c1bc4ba3f1be311521d368c047d2c43a"
|
||||||
|
dependencies:
|
||||||
|
comma-separated-tokens "^1.0.0"
|
||||||
|
hast-util-parse-selector "^2.2.0"
|
||||||
|
property-information "^5.0.1"
|
||||||
|
space-separated-tokens "^1.0.0"
|
||||||
|
|
||||||
hawk@~3.1.3:
|
hawk@~3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
|
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
|
||||||
@@ -3840,6 +3917,10 @@ hawk@~3.1.3:
|
|||||||
hoek "2.x.x"
|
hoek "2.x.x"
|
||||||
sntp "1.x.x"
|
sntp "1.x.x"
|
||||||
|
|
||||||
|
highlight.js@~9.13.0:
|
||||||
|
version "9.13.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
|
||||||
|
|
||||||
history@^4.7.2:
|
history@^4.7.2:
|
||||||
version "4.7.2"
|
version "4.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
|
||||||
@@ -3895,10 +3976,31 @@ html-parse-stringify2@2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
void-elements "^2.0.1"
|
void-elements "^2.0.1"
|
||||||
|
|
||||||
|
html-to-react@^1.3.4:
|
||||||
|
version "1.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.3.4.tgz#647b3a54fdec73a6461864b129fb0d1eec7d4589"
|
||||||
|
dependencies:
|
||||||
|
domhandler "^2.4.2"
|
||||||
|
escape-string-regexp "^1.0.5"
|
||||||
|
htmlparser2 "^3.10.0"
|
||||||
|
lodash.camelcase "^4.3.0"
|
||||||
|
ramda "^0.26"
|
||||||
|
|
||||||
htmlescape@^1.1.0:
|
htmlescape@^1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
|
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
|
||||||
|
|
||||||
|
htmlparser2@^3.10.0:
|
||||||
|
version "3.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^1.3.1"
|
||||||
|
domhandler "^2.3.0"
|
||||||
|
domutils "^1.5.1"
|
||||||
|
entities "^1.1.1"
|
||||||
|
inherits "^2.0.1"
|
||||||
|
readable-stream "^3.1.1"
|
||||||
|
|
||||||
htmlparser2@^3.9.1:
|
htmlparser2@^3.9.1:
|
||||||
version "3.9.2"
|
version "3.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
||||||
@@ -4111,6 +4213,17 @@ is-accessor-descriptor@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^6.0.0"
|
kind-of "^6.0.0"
|
||||||
|
|
||||||
|
is-alphabetical@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41"
|
||||||
|
|
||||||
|
is-alphanumerical@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40"
|
||||||
|
dependencies:
|
||||||
|
is-alphabetical "^1.0.0"
|
||||||
|
is-decimal "^1.0.0"
|
||||||
|
|
||||||
is-arrayish@^0.2.1:
|
is-arrayish@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||||
@@ -4125,7 +4238,7 @@ is-boolean-object@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
|
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
|
||||||
|
|
||||||
is-buffer@^1.1.0, is-buffer@^1.1.5, is-buffer@~1.1.1:
|
is-buffer@^1.1.0, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
|
|
||||||
@@ -4161,6 +4274,10 @@ is-date-object@^1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
|
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
|
||||||
|
|
||||||
|
is-decimal@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff"
|
||||||
|
|
||||||
is-descriptor@^0.1.0:
|
is-descriptor@^0.1.0:
|
||||||
version "0.1.6"
|
version "0.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
||||||
@@ -4251,6 +4368,10 @@ is-glob@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.1"
|
is-extglob "^2.1.1"
|
||||||
|
|
||||||
|
is-hexadecimal@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
|
||||||
|
|
||||||
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
|
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
|
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
|
||||||
@@ -4397,6 +4518,10 @@ is-utf8@^0.2.0:
|
|||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||||
|
|
||||||
|
is-whitespace-character@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
|
||||||
|
|
||||||
is-windows@^0.2.0:
|
is-windows@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
|
||||||
@@ -4405,6 +4530,10 @@ is-windows@^1.0.1, is-windows@^1.0.2:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||||
|
|
||||||
|
is-word-character@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553"
|
||||||
|
|
||||||
is-wsl@^1.1.0:
|
is-wsl@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
|
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
|
||||||
@@ -5187,6 +5316,10 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6:
|
|||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
|
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
|
||||||
|
|
||||||
|
lodash.camelcase@^4.3.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||||
|
|
||||||
lodash.debounce@^4.0.8:
|
lodash.debounce@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
@@ -5292,6 +5425,13 @@ lowercase-keys@^1.0.0:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
|
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
|
||||||
|
|
||||||
|
lowlight@~1.11.0:
|
||||||
|
version "1.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
|
||||||
|
dependencies:
|
||||||
|
fault "^1.0.2"
|
||||||
|
highlight.js "~9.13.0"
|
||||||
|
|
||||||
lru-cache@2:
|
lru-cache@2:
|
||||||
version "2.7.3"
|
version "2.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
|
||||||
@@ -5345,6 +5485,10 @@ map-visit@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
|
markdown-escapes@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122"
|
||||||
|
|
||||||
math-random@^1.0.1:
|
math-random@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
|
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
|
||||||
@@ -5365,6 +5509,12 @@ md5@^2.1.0:
|
|||||||
crypt "~0.0.1"
|
crypt "~0.0.1"
|
||||||
is-buffer "~1.1.1"
|
is-buffer "~1.1.1"
|
||||||
|
|
||||||
|
mdast-add-list-metadata@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf"
|
||||||
|
dependencies:
|
||||||
|
unist-util-visit-parents "1.1.2"
|
||||||
|
|
||||||
mem@^1.1.0:
|
mem@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
|
resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
|
||||||
@@ -6049,6 +6199,17 @@ parse-asn1@^5.0.0:
|
|||||||
evp_bytestokey "^1.0.0"
|
evp_bytestokey "^1.0.0"
|
||||||
pbkdf2 "^3.0.3"
|
pbkdf2 "^3.0.3"
|
||||||
|
|
||||||
|
parse-entities@^1.1.0, parse-entities@^1.1.2:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.1.tgz#2c761ced065ba7dc68148580b5a225e4918cdd69"
|
||||||
|
dependencies:
|
||||||
|
character-entities "^1.0.0"
|
||||||
|
character-entities-legacy "^1.0.0"
|
||||||
|
character-reference-invalid "^1.0.0"
|
||||||
|
is-alphanumerical "^1.0.0"
|
||||||
|
is-decimal "^1.0.0"
|
||||||
|
is-hexadecimal "^1.0.0"
|
||||||
|
|
||||||
parse-filepath@^1.0.1:
|
parse-filepath@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
|
resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
|
||||||
@@ -6280,6 +6441,12 @@ pretty-hrtime@^1.0.0:
|
|||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
resolved "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||||
|
|
||||||
|
prismjs@^1.8.4, prismjs@~1.15.0:
|
||||||
|
version "1.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9"
|
||||||
|
optionalDependencies:
|
||||||
|
clipboard "^2.0.0"
|
||||||
|
|
||||||
private@^0.1.6, private@^0.1.8:
|
private@^0.1.6, private@^0.1.8:
|
||||||
version "0.1.8"
|
version "0.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
||||||
@@ -6314,6 +6481,12 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
|
|||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
property-information@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.0.1.tgz#c3b09f4f5750b1634c0b24205adbf78f18bdf94f"
|
||||||
|
dependencies:
|
||||||
|
xtend "^4.0.1"
|
||||||
|
|
||||||
pseudomap@^1.0.2:
|
pseudomap@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||||
@@ -6375,6 +6548,10 @@ railroad-diagrams@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
|
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
|
||||||
|
|
||||||
|
ramda@^0.26:
|
||||||
|
version "0.26.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
|
||||||
|
|
||||||
randexp@0.4.6:
|
randexp@0.4.6:
|
||||||
version "0.4.6"
|
version "0.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
|
resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
|
||||||
@@ -6487,6 +6664,18 @@ react-lifecycles-compat@^3.0.4:
|
|||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
|
||||||
|
react-markdown@^4.0.6:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.0.6.tgz#927d44421735cd90b7634bb221e9d7d8656e01e9"
|
||||||
|
dependencies:
|
||||||
|
html-to-react "^1.3.4"
|
||||||
|
mdast-add-list-metadata "1.0.1"
|
||||||
|
prop-types "^15.6.1"
|
||||||
|
remark-parse "^5.0.0"
|
||||||
|
unified "^6.1.5"
|
||||||
|
unist-util-visit "^1.3.0"
|
||||||
|
xtend "^4.0.1"
|
||||||
|
|
||||||
react-router-dom@^4.3.1:
|
react-router-dom@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
|
||||||
@@ -6529,6 +6718,16 @@ react-select@^2.1.2:
|
|||||||
react-input-autosize "^2.2.1"
|
react-input-autosize "^2.2.1"
|
||||||
react-transition-group "^2.2.1"
|
react-transition-group "^2.2.1"
|
||||||
|
|
||||||
|
react-syntax-highlighter@^10.2.0:
|
||||||
|
version "10.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-10.2.0.tgz#c44c2a1e89326bbd808cf7a8b2f08dc5ed025d91"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.3.1"
|
||||||
|
highlight.js "~9.13.0"
|
||||||
|
lowlight "~1.11.0"
|
||||||
|
prismjs "^1.8.4"
|
||||||
|
refractor "^2.4.1"
|
||||||
|
|
||||||
react-test-renderer@^16.0.0-0:
|
react-test-renderer@^16.0.0-0:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae"
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae"
|
||||||
@@ -6622,6 +6821,14 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable
|
|||||||
string_decoder "~1.1.1"
|
string_decoder "~1.1.1"
|
||||||
util-deprecate "~1.0.1"
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
|
readable-stream@^3.1.1:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d"
|
||||||
|
dependencies:
|
||||||
|
inherits "^2.0.3"
|
||||||
|
string_decoder "^1.1.1"
|
||||||
|
util-deprecate "^1.0.1"
|
||||||
|
|
||||||
readable-stream@~1.1.9:
|
readable-stream@~1.1.9:
|
||||||
version "1.1.14"
|
version "1.1.14"
|
||||||
resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||||
@@ -6663,6 +6870,14 @@ rechoir@^0.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve "^1.1.6"
|
resolve "^1.1.6"
|
||||||
|
|
||||||
|
refractor@^2.4.1:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.7.0.tgz#3ed9a96a619e75326a429e644241dea51be070a3"
|
||||||
|
dependencies:
|
||||||
|
hastscript "^5.0.0"
|
||||||
|
parse-entities "^1.1.2"
|
||||||
|
prismjs "~1.15.0"
|
||||||
|
|
||||||
regenerate-unicode-properties@^7.0.0:
|
regenerate-unicode-properties@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
|
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
|
||||||
@@ -6729,6 +6944,26 @@ regjsparser@^0.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jsesc "~0.5.0"
|
jsesc "~0.5.0"
|
||||||
|
|
||||||
|
remark-parse@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95"
|
||||||
|
dependencies:
|
||||||
|
collapse-white-space "^1.0.2"
|
||||||
|
is-alphabetical "^1.0.0"
|
||||||
|
is-decimal "^1.0.0"
|
||||||
|
is-whitespace-character "^1.0.0"
|
||||||
|
is-word-character "^1.0.0"
|
||||||
|
markdown-escapes "^1.0.0"
|
||||||
|
parse-entities "^1.1.0"
|
||||||
|
repeat-string "^1.5.4"
|
||||||
|
state-toggle "^1.0.0"
|
||||||
|
trim "0.0.1"
|
||||||
|
trim-trailing-lines "^1.0.0"
|
||||||
|
unherit "^1.0.4"
|
||||||
|
unist-util-remove-position "^1.0.0"
|
||||||
|
vfile-location "^2.0.0"
|
||||||
|
xtend "^4.0.1"
|
||||||
|
|
||||||
remove-trailing-separator@^1.0.1:
|
remove-trailing-separator@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
@@ -6737,7 +6972,7 @@ repeat-element@^1.1.2:
|
|||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
|
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
|
||||||
|
|
||||||
repeat-string@^1.5.2, repeat-string@^1.6.1:
|
repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1:
|
||||||
version "1.6.1"
|
version "1.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||||
|
|
||||||
@@ -6751,7 +6986,7 @@ replace-ext@0.0.1:
|
|||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
|
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
|
||||||
|
|
||||||
replace-ext@^1.0.0:
|
replace-ext@1.0.0, replace-ext@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
|
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
|
||||||
|
|
||||||
@@ -6994,6 +7229,10 @@ scheduler@^0.10.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
select@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1:
|
"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||||
@@ -7256,6 +7495,12 @@ source-map@^0.7.2:
|
|||||||
version "0.7.3"
|
version "0.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
|
|
||||||
|
space-separated-tokens@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"
|
||||||
|
dependencies:
|
||||||
|
trim "0.0.1"
|
||||||
|
|
||||||
sparkles@^1.0.0:
|
sparkles@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
|
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
|
||||||
@@ -7310,6 +7555,10 @@ stack-utils@^1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
||||||
|
|
||||||
|
state-toggle@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a"
|
||||||
|
|
||||||
static-extend@^0.1.1:
|
static-extend@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||||
@@ -7625,6 +7874,10 @@ timers-ext@^0.1.5:
|
|||||||
es5-ext "~0.10.46"
|
es5-ext "~0.10.46"
|
||||||
next-tick "1"
|
next-tick "1"
|
||||||
|
|
||||||
|
tiny-emitter@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
@@ -7710,6 +7963,18 @@ trim-right@^1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
|
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
|
||||||
|
|
||||||
|
trim-trailing-lines@^1.0.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9"
|
||||||
|
|
||||||
|
trim@0.0.1:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
|
||||||
|
|
||||||
|
trough@^1.0.0:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24"
|
||||||
|
|
||||||
tslib@^1.9.0:
|
tslib@^1.9.0:
|
||||||
version "1.9.3"
|
version "1.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||||
@@ -7778,6 +8043,13 @@ underscore@~1.4.4:
|
|||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
|
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
|
||||||
|
|
||||||
|
unherit@^1.0.4:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c"
|
||||||
|
dependencies:
|
||||||
|
inherits "^2.0.1"
|
||||||
|
xtend "^4.0.1"
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@^1.0.4:
|
unicode-canonical-property-names-ecmascript@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
|
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
|
||||||
@@ -7797,6 +8069,17 @@ unicode-property-aliases-ecmascript@^1.0.4:
|
|||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
|
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
|
||||||
|
|
||||||
|
unified@^6.1.5:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba"
|
||||||
|
dependencies:
|
||||||
|
bail "^1.0.0"
|
||||||
|
extend "^3.0.0"
|
||||||
|
is-plain-obj "^1.1.0"
|
||||||
|
trough "^1.0.0"
|
||||||
|
vfile "^2.0.0"
|
||||||
|
x-is-string "^0.1.0"
|
||||||
|
|
||||||
union-value@^1.0.0:
|
union-value@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
|
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
|
||||||
@@ -7810,6 +8093,36 @@ unique-stream@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b"
|
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b"
|
||||||
|
|
||||||
|
unist-util-is@^2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
|
||||||
|
|
||||||
|
unist-util-remove-position@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb"
|
||||||
|
dependencies:
|
||||||
|
unist-util-visit "^1.1.0"
|
||||||
|
|
||||||
|
unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
|
||||||
|
|
||||||
|
unist-util-visit-parents@1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06"
|
||||||
|
|
||||||
|
unist-util-visit-parents@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217"
|
||||||
|
dependencies:
|
||||||
|
unist-util-is "^2.1.2"
|
||||||
|
|
||||||
|
unist-util-visit@^1.1.0, unist-util-visit@^1.3.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1"
|
||||||
|
dependencies:
|
||||||
|
unist-util-visit-parents "^2.0.0"
|
||||||
|
|
||||||
universal-user-agent@^2.0.0:
|
universal-user-agent@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-2.0.1.tgz#18e591ca52b1cb804f6b9cbc4c336cf8191f80e1"
|
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-2.0.1.tgz#18e591ca52b1cb804f6b9cbc4c336cf8191f80e1"
|
||||||
@@ -7888,7 +8201,7 @@ user-home@^1.1.1:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
|
resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
|
||||||
|
|
||||||
util-deprecate@~1.0.1:
|
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
|
|
||||||
@@ -7944,6 +8257,25 @@ verror@1.10.0:
|
|||||||
core-util-is "1.0.2"
|
core-util-is "1.0.2"
|
||||||
extsprintf "^1.2.0"
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
|
vfile-location@^2.0.0:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.4.tgz#2a5e7297dd0d9e2da4381464d04acc6b834d3e55"
|
||||||
|
|
||||||
|
vfile-message@^1.0.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
|
||||||
|
dependencies:
|
||||||
|
unist-util-stringify-position "^1.1.1"
|
||||||
|
|
||||||
|
vfile@^2.0.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a"
|
||||||
|
dependencies:
|
||||||
|
is-buffer "^1.1.4"
|
||||||
|
replace-ext "1.0.0"
|
||||||
|
unist-util-stringify-position "^1.0.0"
|
||||||
|
vfile-message "^1.0.0"
|
||||||
|
|
||||||
vinyl-buffer@^1.0.1:
|
vinyl-buffer@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz#96c1a3479b8c5392542c612029013b5b27f88bbf"
|
resolved "https://registry.yarnpkg.com/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz#96c1a3479b8c5392542c612029013b5b27f88bbf"
|
||||||
@@ -8161,6 +8493,10 @@ ws@~3.3.1:
|
|||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
ultron "~1.1.0"
|
ultron "~1.1.0"
|
||||||
|
|
||||||
|
x-is-string@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
|
||||||
|
|
||||||
xml-name-validator@^3.0.0:
|
xml-name-validator@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ export type Config = {
|
|||||||
pluginUrl: string,
|
pluginUrl: string,
|
||||||
loginAttemptLimitTimeout: number,
|
loginAttemptLimitTimeout: number,
|
||||||
enabledXsrfProtection: boolean,
|
enabledXsrfProtection: boolean,
|
||||||
defaultNamespaceStrategy: string,
|
namespaceStrategy: string,
|
||||||
_links: Links
|
_links: Links
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import type { Links } from "./hal";
|
||||||
|
|
||||||
|
export type NamespaceStrategies = {
|
||||||
|
current: string,
|
||||||
|
available: string[],
|
||||||
|
_links: Links
|
||||||
|
};
|
||||||
@@ -26,3 +26,5 @@ export type { SubRepository, File } from "./Sources";
|
|||||||
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
||||||
|
|
||||||
export type { AvailableRepositoryPermissions, RepositoryRole } from "./AvailableRepositoryPermissions";
|
export type { AvailableRepositoryPermissions, RepositoryRole } from "./AvailableRepositoryPermissions";
|
||||||
|
|
||||||
|
export type { NamespaceStrategies } from "./NamespaceStrategies";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@
|
|||||||
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
|
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
|
||||||
"plugin-url": "Plugin URL",
|
"plugin-url": "Plugin URL",
|
||||||
"enabled-xsrf-protection": "XSRF Protection aktivieren",
|
"enabled-xsrf-protection": "XSRF Protection aktivieren",
|
||||||
"default-namespace-strategy": "Default Namespace Strategie"
|
"namespace-strategy": "Namespace Strategie"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"date-format-invalid": "Das Datumsformat ist ungültig",
|
"date-format-invalid": "Das Datumsformat ist ungültig",
|
||||||
@@ -87,6 +87,6 @@
|
|||||||
"proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.",
|
"proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.",
|
||||||
"proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.",
|
"proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.",
|
||||||
"enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.",
|
"enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.",
|
||||||
"defaultNameSpaceStrategyHelpText": "Die Standardstrategie für Namespaces."
|
"nameSpaceStrategyHelpText": "Strategie für Namespaces."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"repository": {
|
"repository": {
|
||||||
|
"namespace": "Namespace",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"type": "Typ",
|
"type": "Typ",
|
||||||
"contact": "Kontakt",
|
"contact": "Kontakt",
|
||||||
@@ -8,10 +9,12 @@
|
|||||||
"lastModified": "Zuletzt bearbeitet"
|
"lastModified": "Zuletzt bearbeitet"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
|
"namespace-invalid": "Der Namespace des Repository ist ungültig",
|
||||||
"name-invalid": "Der Name des Repository ist ungültig",
|
"name-invalid": "Der Name des Repository ist ungültig",
|
||||||
"contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein"
|
"contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
|
"namespaceHelpText": "Der Namespace des Repository. Dieser wird Teil der URL des Repository sein.",
|
||||||
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
|
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
|
||||||
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
|
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
|
||||||
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
|
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"skip-failed-authenticators": "Skip Failed Authenticators",
|
"skip-failed-authenticators": "Skip Failed Authenticators",
|
||||||
"plugin-url": "Plugin URL",
|
"plugin-url": "Plugin URL",
|
||||||
"enabled-xsrf-protection": "Enabled XSRF Protection",
|
"enabled-xsrf-protection": "Enabled XSRF Protection",
|
||||||
"default-namespace-strategy": "Default Namespace Strategy"
|
"namespace-strategy": "Namespace Strategy"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"date-format-invalid": "The date format is not valid",
|
"date-format-invalid": "The date format is not valid",
|
||||||
@@ -87,6 +87,6 @@
|
|||||||
"proxyUserHelpText": "The username for the proxy server authentication.",
|
"proxyUserHelpText": "The username for the proxy server authentication.",
|
||||||
"proxyExcludesHelpText": "Glob patterns for hostnames, which should be excluded from proxy settings.",
|
"proxyExcludesHelpText": "Glob patterns for hostnames, which should be excluded from proxy settings.",
|
||||||
"enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.",
|
"enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.",
|
||||||
"defaultNameSpaceStrategyHelpText": "The default namespace strategy."
|
"nameSpaceStrategyHelpText": "The namespace strategy."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"repository": {
|
"repository": {
|
||||||
|
"namespace": "Namespace",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
@@ -8,10 +9,12 @@
|
|||||||
"lastModified": "Last Modified"
|
"lastModified": "Last Modified"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
|
"namespace-invalid": "The repository namespace is invalid",
|
||||||
"name-invalid": "The repository name is invalid",
|
"name-invalid": "The repository name is invalid",
|
||||||
"contact-invalid": "Contact must be a valid mail address"
|
"contact-invalid": "Contact must be a valid mail address"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
|
"namespaceHelpText": "The namespace of the repository. This name will be part of the repository url.",
|
||||||
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
||||||
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
||||||
"contactHelpText": "Email address of the person who is responsible for this repository.",
|
"contactHelpText": "Email address of the person who is responsible for this repository.",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { SubmitButton, Notification } from "@scm-manager/ui-components";
|
import { SubmitButton, Notification } from "@scm-manager/ui-components";
|
||||||
import type { Config } from "@scm-manager/ui-types";
|
import type { NamespaceStrategies, Config } from "@scm-manager/ui-types";
|
||||||
import ProxySettings from "./ProxySettings";
|
import ProxySettings from "./ProxySettings";
|
||||||
import GeneralSettings from "./GeneralSettings";
|
import GeneralSettings from "./GeneralSettings";
|
||||||
import BaseUrlSettings from "./BaseUrlSettings";
|
import BaseUrlSettings from "./BaseUrlSettings";
|
||||||
@@ -13,9 +13,11 @@ type Props = {
|
|||||||
submitForm: Config => void,
|
submitForm: Config => void,
|
||||||
config?: Config,
|
config?: Config,
|
||||||
loading?: boolean,
|
loading?: boolean,
|
||||||
t: string => string,
|
|
||||||
configReadPermission: boolean,
|
configReadPermission: boolean,
|
||||||
configUpdatePermission: boolean
|
configUpdatePermission: boolean,
|
||||||
|
namespaceStrategies?: NamespaceStrategies,
|
||||||
|
// context props
|
||||||
|
t: string => string,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -54,7 +56,7 @@ class ConfigForm extends React.Component<Props, State> {
|
|||||||
pluginUrl: "",
|
pluginUrl: "",
|
||||||
loginAttemptLimitTimeout: 0,
|
loginAttemptLimitTimeout: 0,
|
||||||
enabledXsrfProtection: true,
|
enabledXsrfProtection: true,
|
||||||
defaultNamespaceStrategy: "",
|
namespaceStrategy: "",
|
||||||
_links: {}
|
_links: {}
|
||||||
},
|
},
|
||||||
showNotification: false,
|
showNotification: false,
|
||||||
@@ -88,6 +90,7 @@ class ConfigForm extends React.Component<Props, State> {
|
|||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
t,
|
t,
|
||||||
|
namespaceStrategies,
|
||||||
configReadPermission,
|
configReadPermission,
|
||||||
configUpdatePermission
|
configUpdatePermission
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -118,6 +121,7 @@ class ConfigForm extends React.Component<Props, State> {
|
|||||||
<form onSubmit={this.submit}>
|
<form onSubmit={this.submit}>
|
||||||
{noPermissionNotification}
|
{noPermissionNotification}
|
||||||
<GeneralSettings
|
<GeneralSettings
|
||||||
|
namespaceStrategies={namespaceStrategies}
|
||||||
realmDescription={config.realmDescription}
|
realmDescription={config.realmDescription}
|
||||||
enableRepositoryArchive={config.enableRepositoryArchive}
|
enableRepositoryArchive={config.enableRepositoryArchive}
|
||||||
disableGroupingGrid={config.disableGroupingGrid}
|
disableGroupingGrid={config.disableGroupingGrid}
|
||||||
@@ -126,7 +130,7 @@ class ConfigForm extends React.Component<Props, State> {
|
|||||||
skipFailedAuthenticators={config.skipFailedAuthenticators}
|
skipFailedAuthenticators={config.skipFailedAuthenticators}
|
||||||
pluginUrl={config.pluginUrl}
|
pluginUrl={config.pluginUrl}
|
||||||
enabledXsrfProtection={config.enabledXsrfProtection}
|
enabledXsrfProtection={config.enabledXsrfProtection}
|
||||||
defaultNamespaceStrategy={config.defaultNamespaceStrategy}
|
namespaceStrategy={config.namespaceStrategy}
|
||||||
onChange={(isValid, changedValue, name) =>
|
onChange={(isValid, changedValue, name) =>
|
||||||
this.onChange(isValid, changedValue, name)
|
this.onChange(isValid, changedValue, name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { Checkbox, InputField } from "@scm-manager/ui-components";
|
import { Checkbox, InputField} from "@scm-manager/ui-components";
|
||||||
|
import type { NamespaceStrategies } from "@scm-manager/ui-types";
|
||||||
|
import NamespaceStrategySelect from "./NamespaceStrategySelect";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
realmDescription: string,
|
realmDescription: string,
|
||||||
@@ -12,13 +14,16 @@ type Props = {
|
|||||||
skipFailedAuthenticators: boolean,
|
skipFailedAuthenticators: boolean,
|
||||||
pluginUrl: string,
|
pluginUrl: string,
|
||||||
enabledXsrfProtection: boolean,
|
enabledXsrfProtection: boolean,
|
||||||
defaultNamespaceStrategy: string,
|
namespaceStrategy: string,
|
||||||
t: string => string,
|
namespaceStrategies?: NamespaceStrategies,
|
||||||
onChange: (boolean, any, string) => void,
|
onChange: (boolean, any, string) => void,
|
||||||
hasUpdatePermission: boolean
|
hasUpdatePermission: boolean,
|
||||||
|
// context props
|
||||||
|
t: string => string
|
||||||
};
|
};
|
||||||
|
|
||||||
class GeneralSettings extends React.Component<Props> {
|
class GeneralSettings extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
t,
|
t,
|
||||||
@@ -30,8 +35,9 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
skipFailedAuthenticators,
|
skipFailedAuthenticators,
|
||||||
pluginUrl,
|
pluginUrl,
|
||||||
enabledXsrfProtection,
|
enabledXsrfProtection,
|
||||||
defaultNamespaceStrategy,
|
namespaceStrategy,
|
||||||
hasUpdatePermission
|
hasUpdatePermission,
|
||||||
|
namespaceStrategies
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -67,13 +73,14 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-half">
|
<div className="column is-half">
|
||||||
<InputField
|
<NamespaceStrategySelect
|
||||||
label={t("general-settings.default-namespace-strategy")}
|
label={t("general-settings.namespace-strategy")}
|
||||||
onChange={this.handleDefaultNamespaceStrategyChange}
|
onChange={this.handleNamespaceStrategyChange}
|
||||||
value={defaultNamespaceStrategy}
|
value={namespaceStrategy}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
helpText={t("help.defaultNameSpaceStrategyHelpText")}
|
namespaceStrategies={namespaceStrategies}
|
||||||
/>
|
helpText={t("help.nameSpaceStrategyHelpText")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
@@ -146,19 +153,17 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
handleAnonymousAccessEnabledChange = (value: string) => {
|
handleAnonymousAccessEnabledChange = (value: string) => {
|
||||||
this.props.onChange(true, value, "anonymousAccessEnabled");
|
this.props.onChange(true, value, "anonymousAccessEnabled");
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSkipFailedAuthenticatorsChange = (value: string) => {
|
handleSkipFailedAuthenticatorsChange = (value: string) => {
|
||||||
this.props.onChange(true, value, "skipFailedAuthenticators");
|
this.props.onChange(true, value, "skipFailedAuthenticators");
|
||||||
};
|
};
|
||||||
handlePluginUrlChange = (value: string) => {
|
handlePluginUrlChange = (value: string) => {
|
||||||
this.props.onChange(true, value, "pluginUrl");
|
this.props.onChange(true, value, "pluginUrl");
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEnabledXsrfProtectionChange = (value: boolean) => {
|
handleEnabledXsrfProtectionChange = (value: boolean) => {
|
||||||
this.props.onChange(true, value, "enabledXsrfProtection");
|
this.props.onChange(true, value, "enabledXsrfProtection");
|
||||||
};
|
};
|
||||||
handleDefaultNamespaceStrategyChange = (value: string) => {
|
handleNamespaceStrategyChange = (value: string) => {
|
||||||
this.props.onChange(true, value, "defaultNamespaceStrategy");
|
this.props.onChange(true, value, "namespaceStrategy");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
67
scm-ui/src/config/components/form/NamespaceStrategySelect.js
Normal file
67
scm-ui/src/config/components/form/NamespaceStrategySelect.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate, type TFunction } from "react-i18next";
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
class NamespaceStrategySelect extends React.Component<Props> {
|
||||||
|
createNamespaceOptions = () => {
|
||||||
|
const { namespaceStrategies, t } = this.props;
|
||||||
|
let available = [];
|
||||||
|
if (namespaceStrategies && namespaceStrategies.available) {
|
||||||
|
available = namespaceStrategies.available;
|
||||||
|
}
|
||||||
|
|
||||||
|
return available.map(ns => {
|
||||||
|
const key = "namespaceStrategies." + ns;
|
||||||
|
let label = t(key);
|
||||||
|
if (label === key) {
|
||||||
|
label = ns;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: ns,
|
||||||
|
label: label
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
findSelected = () => {
|
||||||
|
const { namespaceStrategies, value } = this.props;
|
||||||
|
if (
|
||||||
|
!namespaceStrategies ||
|
||||||
|
!namespaceStrategies.available ||
|
||||||
|
namespaceStrategies.available.indexOf(value) < 0
|
||||||
|
) {
|
||||||
|
return namespaceStrategies.current;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { label, helpText, disabled, onChange } = this.props;
|
||||||
|
const nsOptions = this.createNamespaceOptions();
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
label={label}
|
||||||
|
onChange={onChange}
|
||||||
|
value={this.findSelected()}
|
||||||
|
disabled={disabled}
|
||||||
|
options={nsOptions}
|
||||||
|
helpText={helpText}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("plugins")(NamespaceStrategySelect);
|
||||||
@@ -14,9 +14,15 @@ import {
|
|||||||
modifyConfigReset
|
modifyConfigReset
|
||||||
} from "../modules/config";
|
} from "../modules/config";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import type { Config } from "@scm-manager/ui-types";
|
import type { Config, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||||
import ConfigForm from "../components/form/ConfigForm";
|
import ConfigForm from "../components/form/ConfigForm";
|
||||||
import { getConfigLink } from "../../modules/indexResource";
|
import { getConfigLink } from "../../modules/indexResource";
|
||||||
|
import {
|
||||||
|
fetchNamespaceStrategiesIfNeeded,
|
||||||
|
getFetchNamespaceStrategiesFailure,
|
||||||
|
getNamespaceStrategies,
|
||||||
|
isFetchNamespaceStrategiesPending
|
||||||
|
} from "../modules/namespaceStrategies";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
@@ -24,11 +30,13 @@ type Props = {
|
|||||||
config: Config,
|
config: Config,
|
||||||
configUpdatePermission: boolean,
|
configUpdatePermission: boolean,
|
||||||
configLink: string,
|
configLink: string,
|
||||||
|
namespaceStrategies?: NamespaceStrategies,
|
||||||
|
|
||||||
// dispatch functions
|
// dispatch functions
|
||||||
modifyConfig: (config: Config, callback?: () => void) => void,
|
modifyConfig: (config: Config, callback?: () => void) => void,
|
||||||
fetchConfig: (link: string) => void,
|
fetchConfig: (link: string) => void,
|
||||||
configReset: void => void,
|
configReset: void => void,
|
||||||
|
fetchNamespaceStrategiesIfNeeded: void => void,
|
||||||
|
|
||||||
// context objects
|
// context objects
|
||||||
t: string => string
|
t: string => string
|
||||||
@@ -51,6 +59,7 @@ class GlobalConfig extends React.Component<Props, State> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.configReset();
|
this.props.configReset();
|
||||||
|
this.props.fetchNamespaceStrategiesIfNeeded();
|
||||||
if (this.props.configLink) {
|
if (this.props.configLink) {
|
||||||
this.props.fetchConfig(this.props.configLink);
|
this.props.fetchConfig(this.props.configLink);
|
||||||
} else {
|
} else {
|
||||||
@@ -103,7 +112,7 @@ class GlobalConfig extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderContent = () => {
|
renderContent = () => {
|
||||||
const { error, loading, config, configUpdatePermission } = this.props;
|
const { error, loading, config, configUpdatePermission, namespaceStrategies } = this.props;
|
||||||
const { configReadPermission } = this.state;
|
const { configReadPermission } = this.state;
|
||||||
if (!error) {
|
if (!error) {
|
||||||
return (
|
return (
|
||||||
@@ -113,6 +122,7 @@ class GlobalConfig extends React.Component<Props, State> {
|
|||||||
submitForm={config => this.modifyConfig(config)}
|
submitForm={config => this.modifyConfig(config)}
|
||||||
config={config}
|
config={config}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
namespaceStrategies={namespaceStrategies}
|
||||||
configUpdatePermission={configUpdatePermission}
|
configUpdatePermission={configUpdatePermission}
|
||||||
configReadPermission={configReadPermission}
|
configReadPermission={configReadPermission}
|
||||||
/>
|
/>
|
||||||
@@ -133,23 +143,33 @@ const mapDispatchToProps = dispatch => {
|
|||||||
},
|
},
|
||||||
configReset: () => {
|
configReset: () => {
|
||||||
dispatch(modifyConfigReset());
|
dispatch(modifyConfigReset());
|
||||||
|
},
|
||||||
|
fetchNamespaceStrategiesIfNeeded: () => {
|
||||||
|
dispatch(fetchNamespaceStrategiesIfNeeded());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const loading = isFetchConfigPending(state) || isModifyConfigPending(state);
|
const loading = isFetchConfigPending(state)
|
||||||
const error = getFetchConfigFailure(state) || getModifyConfigFailure(state);
|
|| isModifyConfigPending(state)
|
||||||
|
|| isFetchNamespaceStrategiesPending(state);
|
||||||
|
const error = getFetchConfigFailure(state)
|
||||||
|
|| getModifyConfigFailure(state)
|
||||||
|
|| getFetchNamespaceStrategiesFailure(state);
|
||||||
|
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
const configUpdatePermission = getConfigUpdatePermission(state);
|
const configUpdatePermission = getConfigUpdatePermission(state);
|
||||||
const configLink = getConfigLink(state);
|
const configLink = getConfigLink(state);
|
||||||
|
const namespaceStrategies = getNamespaceStrategies(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
config,
|
config,
|
||||||
configUpdatePermission,
|
configUpdatePermission,
|
||||||
configLink
|
configLink,
|
||||||
|
namespaceStrategies
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const config = {
|
|||||||
"http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false",
|
"http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false",
|
||||||
loginAttemptLimitTimeout: 300,
|
loginAttemptLimitTimeout: 300,
|
||||||
enabledXsrfProtection: true,
|
enabledXsrfProtection: true,
|
||||||
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
|
namespaceStrategy: "UsernameNamespaceStrategy",
|
||||||
_links: {
|
_links: {
|
||||||
self: { href: "http://localhost:8081/api/v2/config" },
|
self: { href: "http://localhost:8081/api/v2/config" },
|
||||||
update: { href: "http://localhost:8081/api/v2/config" }
|
update: { href: "http://localhost:8081/api/v2/config" }
|
||||||
@@ -79,7 +79,7 @@ const configWithNullValues = {
|
|||||||
"http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false",
|
"http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false",
|
||||||
loginAttemptLimitTimeout: 300,
|
loginAttemptLimitTimeout: 300,
|
||||||
enabledXsrfProtection: true,
|
enabledXsrfProtection: true,
|
||||||
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
|
namespaceStrategy: "UsernameNamespaceStrategy",
|
||||||
_links: {
|
_links: {
|
||||||
self: { href: "http://localhost:8081/api/v2/config" },
|
self: { href: "http://localhost:8081/api/v2/config" },
|
||||||
update: { href: "http://localhost:8081/api/v2/config" }
|
update: { href: "http://localhost:8081/api/v2/config" }
|
||||||
|
|||||||
115
scm-ui/src/config/modules/namespaceStrategies.js
Normal file
115
scm-ui/src/config/modules/namespaceStrategies.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as types from "../../modules/types";
|
||||||
|
import type { 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 function fetchNamespaceStrategiesIfNeeded() {
|
||||||
|
return function(dispatch: any, getState: () => Object) {
|
||||||
|
const state = getState();
|
||||||
|
if (shouldFetchNamespaceStrategies(state)) {
|
||||||
|
return fetchNamespaceStrategies(
|
||||||
|
dispatch,
|
||||||
|
state.indexResources.links.namespaceStrategies.href
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchNamespaceStrategies(dispatch: any, url: string) {
|
||||||
|
dispatch(fetchNamespaceStrategiesPending());
|
||||||
|
return apiClient
|
||||||
|
.get(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(namespaceStrategies => {
|
||||||
|
dispatch(fetchNamespaceStrategiesSuccess(namespaceStrategies));
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch(fetchNamespaceStrategiesFailure(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldFetchNamespaceStrategies(state: Object) {
|
||||||
|
if (
|
||||||
|
isFetchNamespaceStrategiesPending(state) ||
|
||||||
|
getFetchNamespaceStrategiesFailure(state)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !state.namespaceStrategies || !state.namespaceStrategies.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchNamespaceStrategiesPending(): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_NAMESPACESTRATEGIES_TYPES_PENDING
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchNamespaceStrategiesSuccess(
|
||||||
|
namespaceStrategies: NamespaceStrategies
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS,
|
||||||
|
payload: namespaceStrategies
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchNamespaceStrategiesFailure(error: Error): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_NAMESPACESTRATEGIES_TYPES_FAILURE,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// reducers
|
||||||
|
|
||||||
|
export default function reducer(
|
||||||
|
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;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
current: config.namespaceStrategy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectors
|
||||||
|
|
||||||
|
export function getNamespaceStrategies(state: Object) {
|
||||||
|
if (state.namespaceStrategies) {
|
||||||
|
return state.namespaceStrategies;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchNamespaceStrategiesPending(state: Object) {
|
||||||
|
return isPending(state, FETCH_NAMESPACESTRATEGIES_TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFetchNamespaceStrategiesFailure(state: Object) {
|
||||||
|
return getFailure(state, FETCH_NAMESPACESTRATEGIES_TYPES);
|
||||||
|
}
|
||||||
199
scm-ui/src/config/modules/namespaceStrategies.test.js
Normal file
199
scm-ui/src/config/modules/namespaceStrategies.test.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// @flow
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import configureMockStore from "redux-mock-store";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import {
|
||||||
|
FETCH_NAMESPACESTRATEGIES_TYPES,
|
||||||
|
FETCH_NAMESPACESTRATEGIES_TYPES_FAILURE,
|
||||||
|
FETCH_NAMESPACESTRATEGIES_TYPES_PENDING,
|
||||||
|
FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS,
|
||||||
|
fetchNamespaceStrategiesIfNeeded,
|
||||||
|
fetchNamespaceStrategiesSuccess,
|
||||||
|
shouldFetchNamespaceStrategies,
|
||||||
|
default as reducer,
|
||||||
|
getNamespaceStrategies,
|
||||||
|
isFetchNamespaceStrategiesPending,
|
||||||
|
getFetchNamespaceStrategiesFailure
|
||||||
|
} from "./namespaceStrategies";
|
||||||
|
import { MODIFY_CONFIG_SUCCESS } from "./config";
|
||||||
|
|
||||||
|
const strategies = {
|
||||||
|
current: "UsernameNamespaceStrategy",
|
||||||
|
available: [
|
||||||
|
"UsernameNamespaceStrategy",
|
||||||
|
"CustomNamespaceStrategy",
|
||||||
|
"CurrentYearNamespaceStrategy",
|
||||||
|
"RepositoryTypeNamespaceStrategy"
|
||||||
|
],
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: "http://localhost:8081/scm/api/v2/namespaceStrategies"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("namespace strategy caching", () => {
|
||||||
|
it("should fetch strategies, on empty state", () => {
|
||||||
|
expect(shouldFetchNamespaceStrategies({})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch strategies, on empty namespaceStrategies node", () => {
|
||||||
|
const state = {
|
||||||
|
namespaceStrategies: {}
|
||||||
|
};
|
||||||
|
expect(shouldFetchNamespaceStrategies(state)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not fetch strategies, on pending state", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_NAMESPACESTRATEGIES_TYPES]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(shouldFetchNamespaceStrategies(state)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not fetch strategies, on failure state", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_NAMESPACESTRATEGIES_TYPES]: new Error("no...")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(shouldFetchNamespaceStrategies(state)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not fetch strategies, if they are already fetched", () => {
|
||||||
|
const state = {
|
||||||
|
namespaceStrategies: {
|
||||||
|
current: "some"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(shouldFetchNamespaceStrategies(state)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("namespace strategies fetch", () => {
|
||||||
|
const URL = "http://scm.hitchhiker.com/api/v2/namespaceStrategies";
|
||||||
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.reset();
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const createStore = (initialState = {}) => {
|
||||||
|
return mockStore({
|
||||||
|
...initialState,
|
||||||
|
indexResources: {
|
||||||
|
links: {
|
||||||
|
namespaceStrategies: {
|
||||||
|
href: URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should successfully fetch strategies", () => {
|
||||||
|
fetchMock.getOnce(URL, strategies);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: FETCH_NAMESPACESTRATEGIES_TYPES_PENDING },
|
||||||
|
{
|
||||||
|
type: FETCH_NAMESPACESTRATEGIES_TYPES_SUCCESS,
|
||||||
|
payload: strategies
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = createStore();
|
||||||
|
return store.dispatch(fetchNamespaceStrategiesIfNeeded()).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch FETCH_NAMESPACESTRATEGIES_TYPES_FAILURE on server error", () => {
|
||||||
|
fetchMock.getOnce(URL, {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createStore();
|
||||||
|
return store.dispatch(fetchNamespaceStrategiesIfNeeded()).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toBe(FETCH_NAMESPACESTRATEGIES_TYPES_PENDING);
|
||||||
|
expect(actions[1].type).toBe(FETCH_NAMESPACESTRATEGIES_TYPES_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not dispatch any action, if the strategies are already fetched", () => {
|
||||||
|
const store = createStore({
|
||||||
|
namespaceStrategies: strategies
|
||||||
|
});
|
||||||
|
store.dispatch(fetchNamespaceStrategiesIfNeeded());
|
||||||
|
expect(store.getActions().length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("namespace strategies reducer", () => {
|
||||||
|
it("should return unmodified state on unknown action", () => {
|
||||||
|
const state = {};
|
||||||
|
expect(reducer(state)).toBe(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should store the strategies on success", () => {
|
||||||
|
const newState = reducer({}, fetchNamespaceStrategiesSuccess(strategies));
|
||||||
|
expect(newState).toBe(strategies);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clear store if config was modified", () => {
|
||||||
|
const modifyConfigAction = {
|
||||||
|
type: MODIFY_CONFIG_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
namespaceStrategy: "CustomNamespaceStrategy"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const newState = reducer(strategies, modifyConfigAction);
|
||||||
|
expect(newState.current).toEqual("CustomNamespaceStrategy");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("namespace strategy selectors", () => {
|
||||||
|
const error = new Error("The end of the universe");
|
||||||
|
|
||||||
|
it("should return an empty object", () => {
|
||||||
|
expect(getNamespaceStrategies({})).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the namespace strategies", () => {
|
||||||
|
const state = {
|
||||||
|
namespaceStrategies: strategies
|
||||||
|
};
|
||||||
|
expect(getNamespaceStrategies(state)).toBe(strategies);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when fetch namespace strategies is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_NAMESPACESTRATEGIES_TYPES]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(isFetchNamespaceStrategiesPending(state)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when fetch strategies is not pending", () => {
|
||||||
|
expect(isFetchNamespaceStrategiesPending({})).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when fetch namespace strategies did fail", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_NAMESPACESTRATEGIES_TYPES]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getFetchNamespaceStrategiesFailure(state)).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when fetch strategies did not fail", () => {
|
||||||
|
expect(getFetchNamespaceStrategiesFailure({})).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,6 +15,7 @@ import pending from "./modules/pending";
|
|||||||
import failure from "./modules/failure";
|
import failure from "./modules/failure";
|
||||||
import permissions from "./repos/permissions/modules/permissions";
|
import permissions from "./repos/permissions/modules/permissions";
|
||||||
import config from "./config/modules/config";
|
import config from "./config/modules/config";
|
||||||
|
import namespaceStrategies from "./config/modules/namespaceStrategies";
|
||||||
import indexResources from "./modules/indexResource";
|
import indexResources from "./modules/indexResource";
|
||||||
|
|
||||||
import type { BrowserHistory } from "history/createBrowserHistory";
|
import type { BrowserHistory } from "history/createBrowserHistory";
|
||||||
@@ -38,7 +39,8 @@ function createReduxStore(history: BrowserHistory) {
|
|||||||
groups,
|
groups,
|
||||||
auth,
|
auth,
|
||||||
config,
|
config,
|
||||||
sources
|
sources,
|
||||||
|
namespaceStrategies
|
||||||
});
|
});
|
||||||
|
|
||||||
return createStore(
|
return createStore(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SubmitButton,
|
SubmitButton,
|
||||||
Textarea
|
Textarea
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
|
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||||
import type { Repository, RepositoryType } from "@scm-manager/ui-types";
|
import type { Repository, RepositoryType } from "@scm-manager/ui-types";
|
||||||
import * as validator from "./repositoryValidation";
|
import * as validator from "./repositoryValidation";
|
||||||
|
|
||||||
@@ -15,16 +16,20 @@ type Props = {
|
|||||||
submitForm: Repository => void,
|
submitForm: Repository => void,
|
||||||
repository?: Repository,
|
repository?: Repository,
|
||||||
repositoryTypes: RepositoryType[],
|
repositoryTypes: RepositoryType[],
|
||||||
|
namespaceStrategy: string,
|
||||||
loading?: boolean,
|
loading?: boolean,
|
||||||
t: string => string
|
t: string => string
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
repository: Repository,
|
repository: Repository,
|
||||||
|
namespaceValidationError: boolean,
|
||||||
nameValidationError: boolean,
|
nameValidationError: boolean,
|
||||||
contactValidationError: boolean
|
contactValidationError: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
|
||||||
|
|
||||||
class RepositoryForm extends React.Component<Props, State> {
|
class RepositoryForm extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -38,9 +43,9 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
description: "",
|
description: "",
|
||||||
_links: {}
|
_links: {}
|
||||||
},
|
},
|
||||||
|
namespaceValidationError: false,
|
||||||
nameValidationError: false,
|
nameValidationError: false,
|
||||||
contactValidationError: false,
|
contactValidationError: false
|
||||||
descriptionValidationError: false
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,11 +64,14 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isValid = () => {
|
isValid = () => {
|
||||||
const repository = this.state.repository;
|
const { namespaceStrategy } = this.props;
|
||||||
|
const { repository } = this.state;
|
||||||
return !(
|
return !(
|
||||||
|
this.state.namespaceValidationError ||
|
||||||
this.state.nameValidationError ||
|
this.state.nameValidationError ||
|
||||||
this.state.contactValidationError ||
|
this.state.contactValidationError ||
|
||||||
this.isFalsy(repository.name)
|
this.isFalsy(repository.name) ||
|
||||||
|
(namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && this.isFalsy(repository.namespace))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,6 +135,31 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderNamespaceField = () => {
|
||||||
|
const { namespaceStrategy, t } = this.props;
|
||||||
|
const repository = this.state.repository;
|
||||||
|
const props = {
|
||||||
|
label: t("repository.namespace"),
|
||||||
|
helpText: t("help.namespaceHelpText"),
|
||||||
|
value: repository ? repository.namespace : "",
|
||||||
|
onChange: this.handleNamespaceChange,
|
||||||
|
errorMessage: t("validation.namespace-invalid"),
|
||||||
|
validationError: this.state.namespaceValidationError
|
||||||
|
};
|
||||||
|
|
||||||
|
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
|
||||||
|
return <InputField {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExtensionPoint
|
||||||
|
name="repos.create.namespace"
|
||||||
|
props={props}
|
||||||
|
renderAll={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
renderCreateOnlyFields() {
|
renderCreateOnlyFields() {
|
||||||
if (!this.isCreateMode()) {
|
if (!this.isCreateMode()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -135,6 +168,7 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
const repository = this.state.repository;
|
const repository = this.state.repository;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{this.renderNamespaceField()}
|
||||||
<InputField
|
<InputField
|
||||||
label={t("repository.name")}
|
label={t("repository.name")}
|
||||||
onChange={this.handleNameChange}
|
onChange={this.handleNameChange}
|
||||||
@@ -154,6 +188,13 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNamespaceChange = (namespace: string) => {
|
||||||
|
this.setState({
|
||||||
|
namespaceValidationError: !validator.isNameValid(namespace),
|
||||||
|
repository: { ...this.state.repository, namespace }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleNameChange = (name: string) => {
|
handleNameChange = (name: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
nameValidationError: !validator.isNameValid(name),
|
nameValidationError: !validator.isNameValid(name),
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { validation } from "@scm-manager/ui-components";
|
import { validation } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[\\\[\]])^[A-Za-z0-9\.][A-Za-z0-9\.\-_]*$/;
|
||||||
|
|
||||||
export const isNameValid = (name: string) => {
|
export const isNameValid = (name: string) => {
|
||||||
return validation.isNameValid(name);
|
return nameRegex.test(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isContactValid(mail: string) {
|
export function isContactValid(mail: string) {
|
||||||
|
|||||||
@@ -11,6 +11,81 @@ describe("repository name validation", () => {
|
|||||||
expect(validator.isNameValid("scm/manager")).toBe(false);
|
expect(validator.isNameValid("scm/manager")).toBe(false);
|
||||||
expect(validator.isNameValid("scm/ma/nager")).toBe(false);
|
expect(validator.isNameValid("scm/ma/nager")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should allow same names as the backend", () => {
|
||||||
|
const validPaths = [
|
||||||
|
"scm",
|
||||||
|
"s",
|
||||||
|
"sc",
|
||||||
|
".hiddenrepo",
|
||||||
|
"b.",
|
||||||
|
"...",
|
||||||
|
"..c",
|
||||||
|
"d..",
|
||||||
|
"a..c"
|
||||||
|
];
|
||||||
|
|
||||||
|
validPaths.forEach((path) =>
|
||||||
|
expect(validator.isNameValid(path)).toBe(true)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deny same names as the backend", () => {
|
||||||
|
const invalidPaths = [
|
||||||
|
".",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"..",
|
||||||
|
"/.",
|
||||||
|
"/..",
|
||||||
|
"./",
|
||||||
|
"../",
|
||||||
|
"/../",
|
||||||
|
"/./",
|
||||||
|
"/...",
|
||||||
|
"/abc",
|
||||||
|
".../",
|
||||||
|
"/sdf/",
|
||||||
|
"asdf/",
|
||||||
|
"./b",
|
||||||
|
"scm/plugins/.",
|
||||||
|
"scm/../plugins",
|
||||||
|
"scm/main/",
|
||||||
|
"/scm/main/",
|
||||||
|
"scm/./main",
|
||||||
|
"scm//main",
|
||||||
|
"scm\\main",
|
||||||
|
"scm/main-$HOME",
|
||||||
|
"scm/main-${HOME}-home",
|
||||||
|
"scm/main-%HOME-home",
|
||||||
|
"scm/main-%HOME%-home",
|
||||||
|
"abc$abc",
|
||||||
|
"abc%abc",
|
||||||
|
"abc<abc",
|
||||||
|
"abc>abc",
|
||||||
|
"abc#abc",
|
||||||
|
"abc+abc",
|
||||||
|
"abc{abc",
|
||||||
|
"abc}abc",
|
||||||
|
"abc(abc",
|
||||||
|
"abc)abc",
|
||||||
|
"abc[abc",
|
||||||
|
"abc]abc",
|
||||||
|
"abc|abc",
|
||||||
|
"scm/main",
|
||||||
|
"scm/plugins/git-plugin",
|
||||||
|
".scm/plugins",
|
||||||
|
"a/b..",
|
||||||
|
"a/..b",
|
||||||
|
"scm/main",
|
||||||
|
"scm/plugins/git-plugin",
|
||||||
|
"scm/plugins/git-plugin"
|
||||||
|
];
|
||||||
|
|
||||||
|
invalidPaths.forEach((path) =>
|
||||||
|
expect(validator.isNameValid(path)).toBe(false)
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("repository contact validation", () => {
|
describe("repository contact validation", () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { connect } from "react-redux";
|
|||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { Page } from "@scm-manager/ui-components";
|
import { Page } from "@scm-manager/ui-components";
|
||||||
import RepositoryForm from "../components/form";
|
import RepositoryForm from "../components/form";
|
||||||
import type { Repository, RepositoryType } from "@scm-manager/ui-types";
|
import type { Repository, RepositoryType, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
fetchRepositoryTypesIfNeeded,
|
fetchRepositoryTypesIfNeeded,
|
||||||
getFetchRepositoryTypesFailure,
|
getFetchRepositoryTypesFailure,
|
||||||
@@ -19,15 +19,21 @@ import {
|
|||||||
} from "../modules/repos";
|
} from "../modules/repos";
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import { getRepositoriesLink } from "../../modules/indexResource";
|
import { getRepositoriesLink } from "../../modules/indexResource";
|
||||||
|
import {
|
||||||
|
fetchNamespaceStrategiesIfNeeded,
|
||||||
|
getFetchNamespaceStrategiesFailure, getNamespaceStrategies, isFetchNamespaceStrategiesPending
|
||||||
|
} from "../../config/modules/namespaceStrategies";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repositoryTypes: RepositoryType[],
|
repositoryTypes: RepositoryType[],
|
||||||
typesLoading: boolean,
|
namespaceStrategies: NamespaceStrategies,
|
||||||
|
pageLoading: boolean,
|
||||||
createLoading: boolean,
|
createLoading: boolean,
|
||||||
error: Error,
|
error: Error,
|
||||||
repoLink: string,
|
repoLink: string,
|
||||||
|
|
||||||
// dispatch functions
|
// dispatch functions
|
||||||
|
fetchNamespaceStrategiesIfNeeded: () => void,
|
||||||
fetchRepositoryTypesIfNeeded: () => void,
|
fetchRepositoryTypesIfNeeded: () => void,
|
||||||
createRepo: (
|
createRepo: (
|
||||||
link: string,
|
link: string,
|
||||||
@@ -45,6 +51,7 @@ class Create extends React.Component<Props> {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.resetForm();
|
this.props.resetForm();
|
||||||
this.props.fetchRepositoryTypesIfNeeded();
|
this.props.fetchRepositoryTypesIfNeeded();
|
||||||
|
this.props.fetchNamespaceStrategiesIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
repoCreated = (repo: Repository) => {
|
repoCreated = (repo: Repository) => {
|
||||||
@@ -55,9 +62,10 @@ class Create extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
typesLoading,
|
pageLoading,
|
||||||
createLoading,
|
createLoading,
|
||||||
repositoryTypes,
|
repositoryTypes,
|
||||||
|
namespaceStrategies,
|
||||||
createRepo,
|
createRepo,
|
||||||
error
|
error
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -67,13 +75,14 @@ class Create extends React.Component<Props> {
|
|||||||
<Page
|
<Page
|
||||||
title={t("create.title")}
|
title={t("create.title")}
|
||||||
subtitle={t("create.subtitle")}
|
subtitle={t("create.subtitle")}
|
||||||
loading={typesLoading}
|
loading={pageLoading}
|
||||||
error={error}
|
error={error}
|
||||||
showContentOnError={true}
|
showContentOnError={true}
|
||||||
>
|
>
|
||||||
<RepositoryForm
|
<RepositoryForm
|
||||||
repositoryTypes={repositoryTypes}
|
repositoryTypes={repositoryTypes}
|
||||||
loading={createLoading}
|
loading={createLoading}
|
||||||
|
namespaceStrategy={namespaceStrategies.current}
|
||||||
submitForm={repo => {
|
submitForm={repo => {
|
||||||
createRepo(repoLink, repo, (repo: Repository) =>
|
createRepo(repoLink, repo, (repo: Repository) =>
|
||||||
this.repoCreated(repo)
|
this.repoCreated(repo)
|
||||||
@@ -87,14 +96,18 @@ class Create extends React.Component<Props> {
|
|||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const repositoryTypes = getRepositoryTypes(state);
|
const repositoryTypes = getRepositoryTypes(state);
|
||||||
const typesLoading = isFetchRepositoryTypesPending(state);
|
const namespaceStrategies = getNamespaceStrategies(state);
|
||||||
|
const pageLoading = isFetchRepositoryTypesPending(state)
|
||||||
|
|| isFetchNamespaceStrategiesPending(state);
|
||||||
const createLoading = isCreateRepoPending(state);
|
const createLoading = isCreateRepoPending(state);
|
||||||
const error =
|
const error = getFetchRepositoryTypesFailure(state)
|
||||||
getFetchRepositoryTypesFailure(state) || getCreateRepoFailure(state);
|
|| getCreateRepoFailure(state)
|
||||||
|
|| getFetchNamespaceStrategiesFailure(state);
|
||||||
const repoLink = getRepositoriesLink(state);
|
const repoLink = getRepositoriesLink(state);
|
||||||
return {
|
return {
|
||||||
repositoryTypes,
|
repositoryTypes,
|
||||||
typesLoading,
|
namespaceStrategies,
|
||||||
|
pageLoading,
|
||||||
createLoading,
|
createLoading,
|
||||||
error,
|
error,
|
||||||
repoLink
|
repoLink
|
||||||
@@ -106,6 +119,9 @@ const mapDispatchToProps = dispatch => {
|
|||||||
fetchRepositoryTypesIfNeeded: () => {
|
fetchRepositoryTypesIfNeeded: () => {
|
||||||
dispatch(fetchRepositoryTypesIfNeeded());
|
dispatch(fetchRepositoryTypesIfNeeded());
|
||||||
},
|
},
|
||||||
|
fetchNamespaceStrategiesIfNeeded: () => {
|
||||||
|
dispatch(fetchNamespaceStrategiesIfNeeded());
|
||||||
|
},
|
||||||
createRepo: (
|
createRepo: (
|
||||||
link: string,
|
link: string,
|
||||||
repository: Repository,
|
repository: Repository,
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { apiClient } from "@scm-manager/ui-components";
|
import { apiClient, SyntaxHighlighter } from "@scm-manager/ui-components";
|
||||||
import type { File } from "@scm-manager/ui-types";
|
import type { File } from "@scm-manager/ui-types";
|
||||||
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
||||||
import { arduinoLight } from "react-syntax-highlighter/styles/hljs";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
@@ -68,12 +66,9 @@ class SourcecodeViewer extends React.Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
showLineNumbers="true"
|
|
||||||
language={getLanguage(language)}
|
language={getLanguage(language)}
|
||||||
style={arduinoLight}
|
value= {content}
|
||||||
>
|
/>
|
||||||
{content}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class ConfigDto extends HalRepresentation {
|
|||||||
private String pluginUrl;
|
private String pluginUrl;
|
||||||
private long loginAttemptLimitTimeout;
|
private long loginAttemptLimitTimeout;
|
||||||
private boolean enabledXsrfProtection;
|
private boolean enabledXsrfProtection;
|
||||||
private String defaultNamespaceStrategy;
|
private String namespaceStrategy;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import sonia.scm.config.ConfigurationPermissions;
|
import sonia.scm.config.ConfigurationPermissions;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.repository.NamespaceStrategyValidator;
|
||||||
import sonia.scm.util.ScmConfigurationUtil;
|
import sonia.scm.util.ScmConfigurationUtil;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
@@ -27,12 +28,16 @@ public class ConfigResource {
|
|||||||
private final ConfigDtoToScmConfigurationMapper dtoToConfigMapper;
|
private final ConfigDtoToScmConfigurationMapper dtoToConfigMapper;
|
||||||
private final ScmConfigurationToConfigDtoMapper configToDtoMapper;
|
private final ScmConfigurationToConfigDtoMapper configToDtoMapper;
|
||||||
private final ScmConfiguration configuration;
|
private final ScmConfiguration configuration;
|
||||||
|
private final NamespaceStrategyValidator namespaceStrategyValidator;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) {
|
public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper,
|
||||||
|
ScmConfigurationToConfigDtoMapper configToDtoMapper,
|
||||||
|
ScmConfiguration configuration, NamespaceStrategyValidator namespaceStrategyValidator) {
|
||||||
this.dtoToConfigMapper = dtoToConfigMapper;
|
this.dtoToConfigMapper = dtoToConfigMapper;
|
||||||
this.configToDtoMapper = configToDtoMapper;
|
this.configToDtoMapper = configToDtoMapper;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
|
this.namespaceStrategyValidator = namespaceStrategyValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +83,9 @@ public class ConfigResource {
|
|||||||
// But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later.
|
// But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later.
|
||||||
ConfigurationPermissions.write(configuration).check();
|
ConfigurationPermissions.write(configuration).check();
|
||||||
|
|
||||||
|
// ensure the namespace strategy is valid
|
||||||
|
namespaceStrategyValidator.check(configDto.getNamespaceStrategy());
|
||||||
|
|
||||||
ScmConfiguration config = dtoToConfigMapper.map(configDto);
|
ScmConfiguration config = dtoToConfigMapper.map(configDto);
|
||||||
synchronized (ScmConfiguration.class) {
|
synchronized (ScmConfiguration.class) {
|
||||||
configuration.load(config);
|
configuration.load(config);
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ public class IndexDtoGenerator extends HalAppenderMapper {
|
|||||||
builder.single(link("permissions", resourceLinks.permissions().self()));
|
builder.single(link("permissions", resourceLinks.permissions().self()));
|
||||||
}
|
}
|
||||||
builder.single(link("availableRepositoryPermissions", resourceLinks.availableRepositoryPermissions().self()));
|
builder.single(link("availableRepositoryPermissions", resourceLinks.availableRepositoryPermissions().self()));
|
||||||
|
|
||||||
|
builder.single(link("repositoryTypes", resourceLinks.repositoryTypeCollection().self()));
|
||||||
|
builder.single(link("namespaceStrategies", resourceLinks.namespaceStrategies().self()));
|
||||||
} else {
|
} else {
|
||||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class NamespaceStrategiesDto extends HalRepresentation {
|
||||||
|
|
||||||
|
private String current;
|
||||||
|
private List<String> available;
|
||||||
|
|
||||||
|
public NamespaceStrategiesDto(String current, List<String> available, Links links) {
|
||||||
|
super(links);
|
||||||
|
this.current = current;
|
||||||
|
this.available = available;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import sonia.scm.repository.NamespaceStrategy;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTFul WebService Endpoint for namespace strategies.
|
||||||
|
*/
|
||||||
|
@Path(NamespaceStrategyResource.PATH)
|
||||||
|
public class NamespaceStrategyResource {
|
||||||
|
|
||||||
|
static final String PATH = "v2/namespaceStrategies";
|
||||||
|
|
||||||
|
private Set<NamespaceStrategy> namespaceStrategies;
|
||||||
|
private Provider<NamespaceStrategy> namespaceStrategyProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NamespaceStrategyResource(Set<NamespaceStrategy> namespaceStrategies, Provider<NamespaceStrategy> namespaceStrategyProvider) {
|
||||||
|
this.namespaceStrategies = namespaceStrategies;
|
||||||
|
this.namespaceStrategyProvider = namespaceStrategyProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all available namespace strategies and the current selected.
|
||||||
|
*
|
||||||
|
* @param uriInfo uri info
|
||||||
|
*
|
||||||
|
* @return available and current namespace strategies
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("")
|
||||||
|
@Produces(VndMediaType.NAMESPACE_STRATEGIES)
|
||||||
|
public NamespaceStrategiesDto get(@Context UriInfo uriInfo) {
|
||||||
|
String currentStrategy = strategyAsString(namespaceStrategyProvider.get());
|
||||||
|
List<String> availableStrategies = collectStrategyNames();
|
||||||
|
|
||||||
|
return new NamespaceStrategiesDto(currentStrategy, availableStrategies, createLinks(uriInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Links createLinks(@Context UriInfo uriInfo) {
|
||||||
|
return Links.linkingTo().self(uriInfo.getAbsolutePath().toASCIIString()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String strategyAsString(NamespaceStrategy namespaceStrategy) {
|
||||||
|
return namespaceStrategy.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> collectStrategyNames() {
|
||||||
|
return namespaceStrategies.stream().map(this::strategyAsString).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.hibernate.validator.constraints.Email;
|
import org.hibernate.validator.constraints.Email;
|
||||||
import org.hibernate.validator.constraints.NotEmpty;
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
import sonia.scm.util.ValidationUtil;
|
||||||
|
|
||||||
import javax.validation.constraints.Pattern;
|
import javax.validation.constraints.Pattern;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -25,8 +26,9 @@ public class RepositoryDto extends HalRepresentation {
|
|||||||
private List<HealthCheckFailureDto> healthCheckFailures;
|
private List<HealthCheckFailureDto> healthCheckFailures;
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
private Instant lastModified;
|
private Instant lastModified;
|
||||||
|
// we could not validate the namespace, this must be done by the namespace strategy
|
||||||
private String namespace;
|
private String namespace;
|
||||||
@Pattern(regexp = "^[A-z0-9\\-_]+$")
|
@Pattern(regexp = ValidationUtil.REGEX_REPOSITORYNAME)
|
||||||
private String name;
|
private String name;
|
||||||
private boolean archived = false;
|
private boolean archived = false;
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
|
|||||||
@@ -277,6 +277,23 @@ class ResourceLinks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NamespaceStrategiesLinks namespaceStrategies() {
|
||||||
|
return new NamespaceStrategiesLinks(scmPathInfoStore.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class NamespaceStrategiesLinks {
|
||||||
|
|
||||||
|
private final LinkBuilder namespaceStrategiesLinkBuilder;
|
||||||
|
|
||||||
|
NamespaceStrategiesLinks(ScmPathInfo pathInfo) {
|
||||||
|
namespaceStrategiesLinkBuilder = new LinkBuilder(pathInfo, NamespaceStrategyResource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
String self() {
|
||||||
|
return namespaceStrategiesLinkBuilder.method("get").parameters().href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public RepositoryTypeLinks repositoryType() {
|
public RepositoryTypeLinks repositoryType() {
|
||||||
return new RepositoryTypeLinks(scmPathInfoStore.get());
|
return new RepositoryTypeLinks(scmPathInfoStore.get());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Year;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class CurrentYearNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CurrentYearNamespaceStrategy() {
|
||||||
|
this(Clock.systemDefaultZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
CurrentYearNamespaceStrategy(Clock clock) {
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return String.valueOf(Year.now(clock).getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
import sonia.scm.util.ValidationUtil;
|
||||||
|
|
||||||
|
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class CustomNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
String namespace = repository.getNamespace();
|
||||||
|
|
||||||
|
doThrow()
|
||||||
|
.violation("invalid namespace", "namespace")
|
||||||
|
.when(!ValidationUtil.isRepositoryNameValid(namespace));
|
||||||
|
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package sonia.scm.repository;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import sonia.scm.plugin.Extension;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DefaultNamespaceStrategy returns the predefined namespace of the given repository, if the namespace was not set
|
|
||||||
* the username of the currently loggedin user is used.
|
|
||||||
*
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
@Extension
|
|
||||||
public class DefaultNamespaceStrategy implements NamespaceStrategy {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String createNamespace(Repository repository) {
|
|
||||||
String namespace = repository.getNamespace();
|
|
||||||
if (Strings.isNullOrEmpty(namespace)) {
|
|
||||||
namespace = SecurityUtils.getSubject().getPrincipal().toString();
|
|
||||||
}
|
|
||||||
return namespace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,6 +52,7 @@ import sonia.scm.util.CollectionAppender;
|
|||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -85,7 +86,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
private final KeyGenerator keyGenerator;
|
private final KeyGenerator keyGenerator;
|
||||||
private final RepositoryDAO repositoryDAO;
|
private final RepositoryDAO repositoryDAO;
|
||||||
private final Set<Type> types;
|
private final Set<Type> types;
|
||||||
private NamespaceStrategy namespaceStrategy;
|
private final Provider<NamespaceStrategy> namespaceStrategyProvider;
|
||||||
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
|
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
|
||||||
|
|
||||||
|
|
||||||
@@ -93,11 +94,11 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
public DefaultRepositoryManager(ScmConfiguration configuration,
|
public DefaultRepositoryManager(ScmConfiguration configuration,
|
||||||
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
|
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
|
||||||
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
|
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
|
||||||
NamespaceStrategy namespaceStrategy) {
|
Provider<NamespaceStrategy> namespaceStrategyProvider) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.keyGenerator = keyGenerator;
|
this.keyGenerator = keyGenerator;
|
||||||
this.repositoryDAO = repositoryDAO;
|
this.repositoryDAO = repositoryDAO;
|
||||||
this.namespaceStrategy = namespaceStrategy;
|
this.namespaceStrategyProvider = namespaceStrategyProvider;
|
||||||
|
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder()
|
ThreadFactory factory = new ThreadFactoryBuilder()
|
||||||
.setNameFormat(THREAD_NAME).build();
|
.setNameFormat(THREAD_NAME).build();
|
||||||
@@ -131,7 +132,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
|
|
||||||
public Repository create(Repository repository, boolean initRepository) {
|
public Repository create(Repository repository, boolean initRepository) {
|
||||||
repository.setId(keyGenerator.createKey());
|
repository.setId(keyGenerator.createKey());
|
||||||
repository.setNamespace(namespaceStrategy.createNamespace(repository));
|
repository.setNamespace(namespaceStrategyProvider.get().createNamespace(repository));
|
||||||
|
|
||||||
logger.info("create repository {}/{} of type {} in namespace {}", repository.getNamespace(), repository.getName(), repository.getType(), repository.getNamespace());
|
logger.info("create repository {}/{} of type {} in namespace {}", repository.getNamespace(), repository.getName(), repository.getType(), repository.getNamespace());
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -8,6 +10,8 @@ import java.util.Set;
|
|||||||
|
|
||||||
public class NamespaceStrategyProvider implements Provider<NamespaceStrategy> {
|
public class NamespaceStrategyProvider implements Provider<NamespaceStrategy> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(NamespaceStrategyProvider.class);
|
||||||
|
|
||||||
private final Set<NamespaceStrategy> strategies;
|
private final Set<NamespaceStrategy> strategies;
|
||||||
private final ScmConfiguration scmConfiguration;
|
private final ScmConfiguration scmConfiguration;
|
||||||
|
|
||||||
@@ -19,14 +23,16 @@ public class NamespaceStrategyProvider implements Provider<NamespaceStrategy> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NamespaceStrategy get() {
|
public NamespaceStrategy get() {
|
||||||
String namespaceStrategy = scmConfiguration.getDefaultNamespaceStrategy();
|
String namespaceStrategy = scmConfiguration.getNamespaceStrategy();
|
||||||
|
|
||||||
for (NamespaceStrategy s : this.strategies) {
|
for (NamespaceStrategy s : this.strategies) {
|
||||||
if (s.getClass().getCanonicalName().equals(namespaceStrategy)) {
|
if (s.getClass().getSimpleName().equals(namespaceStrategy)) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
LOG.warn("could not find namespace strategy {}, using default strategy", namespaceStrategy);
|
||||||
|
return new UsernameNamespaceStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow;
|
||||||
|
|
||||||
|
public class NamespaceStrategyValidator {
|
||||||
|
|
||||||
|
private final Set<NamespaceStrategy> strategies;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NamespaceStrategyValidator(Set<NamespaceStrategy> strategies) {
|
||||||
|
this.strategies = strategies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check(String name) {
|
||||||
|
doThrow()
|
||||||
|
.violation("unknown NamespaceStrategy " + name, "namespaceStrategy")
|
||||||
|
.when(!isValid(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValid(String name) {
|
||||||
|
return strategies.stream().anyMatch(ns -> ns.getClass().getSimpleName().equals(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class RepositoryTypeNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return repository.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class UsernameNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return SecurityUtils.getSubject().getPrincipal().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,6 +103,7 @@
|
|||||||
"errorCode": "Fehlercode",
|
"errorCode": "Fehlercode",
|
||||||
"transactionId": "Transaktions-ID",
|
"transactionId": "Transaktions-ID",
|
||||||
"moreInfo": "Für mehr Informationen, siehe",
|
"moreInfo": "Für mehr Informationen, siehe",
|
||||||
|
"violations": "Ungültige Werte:",
|
||||||
"AGR7UzkhA1": {
|
"AGR7UzkhA1": {
|
||||||
"displayName": "Nicht gefunden",
|
"displayName": "Nicht gefunden",
|
||||||
"description": "Der gewünschte Datensatz konnte nicht gefunden werden. Möglicherweise wurde er in einer weiteren Session gelöscht."
|
"description": "Der gewünschte Datensatz konnte nicht gefunden werden. Möglicherweise wurde er in einer weiteren Session gelöscht."
|
||||||
@@ -147,5 +148,11 @@
|
|||||||
"displayName": "Ungültige Eingabe",
|
"displayName": "Ungültige Eingabe",
|
||||||
"description": "Die eingegebenen Daten konnten nicht validiert werden. Bitte korrigieren Sie die Eingaben und senden Sie sie erneut."
|
"description": "Die eingegebenen Daten konnten nicht validiert werden. Bitte korrigieren Sie die Eingaben und senden Sie sie erneut."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"namespaceStrategies": {
|
||||||
|
"UsernameNamespaceStrategy": "Benutzername",
|
||||||
|
"CustomNamespaceStrategy": "Benutzerdefiniert",
|
||||||
|
"CurrentYearNamespaceStrategy": "Aktuelles Jahr",
|
||||||
|
"RepositoryTypeNamespaceStrategy": "Repository Typ"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
"errorCode": "Error Code",
|
"errorCode": "Error Code",
|
||||||
"transactionId": "Transaction ID",
|
"transactionId": "Transaction ID",
|
||||||
"moreInfo": "For more information, see",
|
"moreInfo": "For more information, see",
|
||||||
|
"violations": "Violations:",
|
||||||
"AGR7UzkhA1": {
|
"AGR7UzkhA1": {
|
||||||
"displayName": "Not found",
|
"displayName": "Not found",
|
||||||
"description": "The requested entity could not be found. It may have been deleted in another session."
|
"description": "The requested entity could not be found. It may have been deleted in another session."
|
||||||
@@ -147,5 +148,11 @@
|
|||||||
"displayName": "Illegal input",
|
"displayName": "Illegal input",
|
||||||
"description": "The values could not be validated. Please correct your input and try again."
|
"description": "The values could not be validated. Please correct your input and try again."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"namespaceStrategies": {
|
||||||
|
"UsernameNamespaceStrategy": "Username",
|
||||||
|
"CustomNamespaceStrategy": "Custom",
|
||||||
|
"CurrentYearNamespaceStrategy": "Current year",
|
||||||
|
"RepositoryTypeNamespaceStrategy": "Repository type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
|||||||
assertEquals("https://plug.ins" , config.getPluginUrl());
|
assertEquals("https://plug.ins" , config.getPluginUrl());
|
||||||
assertEquals(40 , config.getLoginAttemptLimitTimeout());
|
assertEquals(40 , config.getLoginAttemptLimitTimeout());
|
||||||
assertTrue(config.isEnabledXsrfProtection());
|
assertTrue(config.isEnabledXsrfProtection());
|
||||||
assertEquals("username", config.getDefaultNamespaceStrategy());
|
assertEquals("username", config.getNamespaceStrategy());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigDto createDefaultDto() {
|
private ConfigDto createDefaultDto() {
|
||||||
@@ -76,7 +76,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
|||||||
configDto.setPluginUrl("https://plug.ins");
|
configDto.setPluginUrl("https://plug.ins");
|
||||||
configDto.setLoginAttemptLimitTimeout(40);
|
configDto.setLoginAttemptLimitTimeout(40);
|
||||||
configDto.setEnabledXsrfProtection(true);
|
configDto.setEnabledXsrfProtection(true);
|
||||||
configDto.setDefaultNamespaceStrategy("username");
|
configDto.setNamespaceStrategy("username");
|
||||||
|
|
||||||
return configDto;
|
return configDto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.repository.NamespaceStrategyValidator;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -22,10 +24,12 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
|
||||||
@SubjectAware(
|
@SubjectAware(
|
||||||
@@ -46,6 +50,9 @@ public class ConfigResourceTest {
|
|||||||
@SuppressWarnings("unused") // Is injected
|
@SuppressWarnings("unused") // Is injected
|
||||||
private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NamespaceStrategyValidator namespaceStrategyValidator;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper;
|
private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper;
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
@@ -62,7 +69,7 @@ public class ConfigResourceTest {
|
|||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
|
|
||||||
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration());
|
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator);
|
||||||
|
|
||||||
dispatcher.getRegistry().addSingletonResource(configResource);
|
dispatcher.getRegistry().addSingletonResource(configResource);
|
||||||
}
|
}
|
||||||
@@ -140,6 +147,21 @@ public class ConfigResourceTest {
|
|||||||
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SubjectAware(username = "readWrite")
|
||||||
|
public void shouldValidateNamespaceStrategy() throws URISyntaxException {
|
||||||
|
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||||
|
.contentType(VndMediaType.CONFIG)
|
||||||
|
.content("{ \"namespaceStrategy\": \"AwesomeStrategy\" }".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
|
verify(namespaceStrategyValidator).check("AwesomeStrategy");
|
||||||
|
}
|
||||||
|
|
||||||
private MockHttpRequest post(String resourceName) throws IOException, URISyntaxException {
|
private MockHttpRequest post(String resourceName) throws IOException, URISyntaxException {
|
||||||
URL url = Resources.getResource(resourceName);
|
URL url = Resources.getResource(resourceName);
|
||||||
byte[] configJson = Resources.toByteArray(url);
|
byte[] configJson = Resources.toByteArray(url);
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.repository.NamespaceStrategy;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class NamespaceStrategyResourceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UriInfo uriInfo;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnNamespaceStrategies() {
|
||||||
|
when(uriInfo.getAbsolutePath()).thenReturn(URI.create("/namespace-strategies"));
|
||||||
|
|
||||||
|
Set<NamespaceStrategy> namespaceStrategies = allStrategies();
|
||||||
|
Provider<NamespaceStrategy> current = Providers.of(new MegaNamespaceStrategy());
|
||||||
|
|
||||||
|
NamespaceStrategyResource resource = new NamespaceStrategyResource(namespaceStrategies, current);
|
||||||
|
|
||||||
|
NamespaceStrategiesDto dto = resource.get(uriInfo);
|
||||||
|
assertThat(dto.getCurrent()).isEqualTo(MegaNamespaceStrategy.class.getSimpleName());
|
||||||
|
assertThat(dto.getAvailable()).contains(
|
||||||
|
AwesomeNamespaceStrategy.class.getSimpleName(),
|
||||||
|
SuperNamespaceStrategy.class.getSimpleName(),
|
||||||
|
MegaNamespaceStrategy.class.getSimpleName()
|
||||||
|
);
|
||||||
|
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo("/namespace-strategies");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<NamespaceStrategy> allStrategies() {
|
||||||
|
return strategies(new AwesomeNamespaceStrategy(), new SuperNamespaceStrategy(), new MegaNamespaceStrategy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<NamespaceStrategy> strategies(NamespaceStrategy... strategies) {
|
||||||
|
return new LinkedHashSet<>(Lists.newArrayList(strategies));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AwesomeNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return "awesome";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SuperNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return "super";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MegaNamespaceStrategy implements NamespaceStrategy {
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return "mega";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,8 @@ public class ResourceLinksMock {
|
|||||||
when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo));
|
when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo));
|
||||||
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
|
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
|
||||||
when(resourceLinks.availableRepositoryPermissions()).thenReturn(new ResourceLinks.AvailableRepositoryPermissionLinks(uriInfo));
|
when(resourceLinks.availableRepositoryPermissions()).thenReturn(new ResourceLinks.AvailableRepositoryPermissionLinks(uriInfo));
|
||||||
|
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
|
||||||
|
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(uriInfo));
|
||||||
|
|
||||||
return resourceLinks;
|
return resourceLinks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
|||||||
assertEquals("pluginurl" , dto.getPluginUrl());
|
assertEquals("pluginurl" , dto.getPluginUrl());
|
||||||
assertEquals(2 , dto.getLoginAttemptLimitTimeout());
|
assertEquals(2 , dto.getLoginAttemptLimitTimeout());
|
||||||
assertTrue(dto.isEnabledXsrfProtection());
|
assertTrue(dto.isEnabledXsrfProtection());
|
||||||
assertEquals("username", dto.getDefaultNamespaceStrategy());
|
assertEquals("username", dto.getNamespaceStrategy());
|
||||||
|
|
||||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
||||||
@@ -121,7 +121,7 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
|||||||
config.setPluginUrl("pluginurl");
|
config.setPluginUrl("pluginurl");
|
||||||
config.setLoginAttemptLimitTimeout(2);
|
config.setLoginAttemptLimitTimeout(2);
|
||||||
config.setEnabledXsrfProtection(true);
|
config.setEnabledXsrfProtection(true);
|
||||||
config.setDefaultNamespaceStrategy("username");
|
config.setNamespaceStrategy("username");
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CurrentYearNamespaceStrategyTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Clock clock;
|
||||||
|
private NamespaceStrategy namespaceStrategy;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setupObjectUnderTest() {
|
||||||
|
namespaceStrategy = new CurrentYearNamespaceStrategy(clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturn1985() {
|
||||||
|
LocalDateTime dateTime = LocalDateTime.of(1985, 4, 9, 21, 42);
|
||||||
|
when(clock.instant()).thenReturn(dateTime.toInstant(ZoneOffset.UTC));
|
||||||
|
when(clock.getZone()).thenReturn(ZoneId.systemDefault());
|
||||||
|
|
||||||
|
String namespace = namespaceStrategy.createNamespace(RepositoryTestData.createHeartOfGold());
|
||||||
|
assertThat(namespace).isEqualTo("1985");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.ScmConstraintViolationException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class CustomNamespaceStrategyTest {
|
||||||
|
|
||||||
|
private final NamespaceStrategy namespaceStrategy = new CustomNamespaceStrategy();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnNamespaceFromRepository() {
|
||||||
|
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||||
|
assertThat(namespaceStrategy.createNamespace(heartOfGold)).isEqualTo(RepositoryTestData.NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowAnValidationExceptionForAnInvalidNamespace() {
|
||||||
|
Repository repository = new Repository();
|
||||||
|
repository.setNamespace("..");
|
||||||
|
repository.setName(".");
|
||||||
|
|
||||||
|
assertThrows(ScmConstraintViolationException.class, () -> namespaceStrategy.createNamespace(repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package sonia.scm.repository;
|
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
|
||||||
public class DefaultNamespaceStrategyTest {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiroRule = new ShiroRule();
|
|
||||||
|
|
||||||
private DefaultNamespaceStrategy namespaceStrategy = new DefaultNamespaceStrategy();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void testNamespaceStrategyWithoutPreset() {
|
|
||||||
assertEquals("trillian", namespaceStrategy.createNamespace(new Repository()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void testNamespaceStrategyWithPreset() {
|
|
||||||
Repository repository = new Repository();
|
|
||||||
repository.setNamespace("awesome");
|
|
||||||
assertEquals("awesome", namespaceStrategy.createNamespace(repository));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -34,6 +34,7 @@ import com.google.common.base.Stopwatch;
|
|||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.authc.AuthenticationException;
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
@@ -118,7 +119,7 @@ public class DefaultRepositoryManagerPerfTest {
|
|||||||
keyGenerator,
|
keyGenerator,
|
||||||
repositoryDAO,
|
repositoryDAO,
|
||||||
handlerSet,
|
handlerSet,
|
||||||
namespaceStrategy
|
Providers.of(namespaceStrategy)
|
||||||
);
|
);
|
||||||
|
|
||||||
setUpTestRepositories();
|
setUpTestRepositories();
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import com.github.sdorra.shiro.ShiroRule;
|
|||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
import org.apache.shiro.authz.UnauthorizedException;
|
import org.apache.shiro.authz.UnauthorizedException;
|
||||||
import org.apache.shiro.util.ThreadContext;
|
import org.apache.shiro.util.ThreadContext;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -436,7 +437,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
|||||||
when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
|
when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
|
||||||
|
|
||||||
return new DefaultRepositoryManager(configuration, contextProvider,
|
return new DefaultRepositoryManager(configuration, contextProvider,
|
||||||
keyGenerator, repositoryDAO, handlerSet, namespaceStrategy);
|
keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRepository(RepositoryManager m, Repository repository) {
|
private void createRepository(RepositoryManager m, Repository repository) {
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class NamespaceStrategyProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnConfiguredStrategy() {
|
||||||
|
Set<NamespaceStrategy> strategies = allStrategiesAsSet();
|
||||||
|
|
||||||
|
ScmConfiguration configuration = new ScmConfiguration();
|
||||||
|
configuration.setNamespaceStrategy("Arthur");
|
||||||
|
|
||||||
|
NamespaceStrategyProvider provider = new NamespaceStrategyProvider(strategies, configuration);
|
||||||
|
NamespaceStrategy strategy = provider.get();
|
||||||
|
|
||||||
|
assertThat(strategy).isInstanceOf(Arthur.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnUsernameStrategyForUnknown() {
|
||||||
|
Set<NamespaceStrategy> strategies = Collections.emptySet();
|
||||||
|
|
||||||
|
ScmConfiguration configuration = new ScmConfiguration();
|
||||||
|
configuration.setNamespaceStrategy("Arthur");
|
||||||
|
|
||||||
|
NamespaceStrategyProvider provider = new NamespaceStrategyProvider(strategies, configuration);
|
||||||
|
NamespaceStrategy strategy = provider.get();
|
||||||
|
|
||||||
|
assertThat(strategy).isInstanceOf(UsernameNamespaceStrategy.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedHashSet<NamespaceStrategy> allStrategiesAsSet() {
|
||||||
|
return new LinkedHashSet<>(Arrays.asList(new Trillian(), new Zaphod(), new Arthur()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Trillian implements NamespaceStrategy{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return "trillian";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Zaphod implements NamespaceStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return "zaphod";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Arthur implements NamespaceStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return "arthur";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.ScmConstraintViolationException;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class NamespaceStrategyValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowConstraintValidationException() {
|
||||||
|
NamespaceStrategyValidator validator = new NamespaceStrategyValidator(Collections.emptySet());
|
||||||
|
assertThrows(ScmConstraintViolationException.class, () -> validator.check("AwesomeStrategy"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDoNotThrowAnException() {
|
||||||
|
NamespaceStrategyValidator validator = new NamespaceStrategyValidator(Sets.newHashSet(new AwesomeStrategy()));
|
||||||
|
validator.check("AwesomeStrategy");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AwesomeStrategy implements NamespaceStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createNamespace(Repository repository) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class RepositoryTypeNamespaceStrategyTest {
|
||||||
|
|
||||||
|
private final RepositoryTypeNamespaceStrategy namespaceStrategy = new RepositoryTypeNamespaceStrategy();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnTypeOfRepository() {
|
||||||
|
Repository git = RepositoryTestData.create42Puzzle("git");
|
||||||
|
assertThat(namespaceStrategy.createNamespace(git)).isEqualTo("git");
|
||||||
|
|
||||||
|
Repository hg = RepositoryTestData.create42Puzzle("hg");
|
||||||
|
assertThat(namespaceStrategy.createNamespace(hg)).isEqualTo("hg");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class UsernameNamespaceStrategyTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
private final NamespaceStrategy usernameNamespaceStrategy = new UsernameNamespaceStrategy();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setupSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void clearThreadContext() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnPrimaryPrincipal() {
|
||||||
|
when(subject.getPrincipal()).thenReturn("trillian");
|
||||||
|
|
||||||
|
String namespace = usernameNamespaceStrategy.createNamespace(RepositoryTestData.createHeartOfGold());
|
||||||
|
assertThat(namespace).isEqualTo("trillian");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user