This commit is contained in:
Florian Scholdei
2019-02-07 11:57:06 +01:00
199 changed files with 2057 additions and 1748 deletions

5
Jenkinsfile vendored
View File

@@ -50,6 +50,11 @@ node('docker') {
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
if (isMainBranch()) {
stage('Lifecycle') {
nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build'
}
stage('Archive') {
archiveArtifacts 'scm-webapp/target/scm-webapp.war'
archiveArtifacts 'scm-server/target/scm-server-app.*'

21
pom.xml
View File

@@ -351,21 +351,6 @@
<scope>test</scope>
</dependency>
<!-- utils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<!-- http -->
<dependency>
@@ -825,11 +810,11 @@
<logback.version>1.2.3</logback.version>
<servlet.version>3.0.1</servlet.version>
<jaxrs.version>2.0.1</jaxrs.version>
<resteasy.version>3.1.3.Final</resteasy.version>
<jaxrs.version>2.1.1</jaxrs.version>
<resteasy.version>3.6.2.Final</resteasy.version>
<jersey-client.version>1.19.4</jersey-client.version>
<enunciate.version>2.11.1</enunciate.version>
<jackson.version>2.8.6</jackson.version>
<jackson.version>2.9.8</jackson.version>
<guice.version>4.0</guice.version>
<jaxb.version>2.3.0</jaxb.version>

View File

@@ -93,6 +93,7 @@
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -235,7 +236,6 @@
<links>
<link>http://download.oracle.com/javase/6/docs/api/</link>
<link>http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/</link>
<link>http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/</link>
<link>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link>
<link>http://www.slf4j.org/api/</link>
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>

View File

@@ -36,7 +36,6 @@ package sonia.scm;
//~--- JDK imports ------------------------------------------------------------
import java.io.Closeable;
import java.io.IOException;
/**
* The base class of all handlers.

View File

@@ -0,0 +1,14 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.Context;
import org.mapstruct.Mapping;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.Repository;
public interface ChangesetToChangesetDtoMapper {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
ChangesetDto map(Changeset changeset, @Context Repository repository);
}

View File

@@ -1,24 +1,59 @@
package sonia.scm.filter;
import lombok.extern.slf4j.Slf4j;
import sonia.scm.util.WebUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Locale;
import java.util.zip.GZIPOutputStream;
@Provider
@Slf4j
public class GZipResponseFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
if (WebUtil.isGzipSupported(requestContext::getHeaderString)) {
log.trace("compress output with gzip");
GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream());
responseContext.getHeaders().add("Content-Encoding", "gzip");
responseContext.setEntityStream(wrappedResponse);
@javax.ws.rs.ext.Provider
public class GZipResponseFilter implements WriterInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class);
private final Provider<HttpServletRequest> requestProvider;
@Inject
public GZipResponseFilter(Provider<HttpServletRequest> requestProvider) {
this.requestProvider = requestProvider;
}
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
if (isGZipSupported()) {
LOG.trace("compress output with gzip");
encodeWithGZip(context);
} else {
context.proceed();
}
}
private void encodeWithGZip(WriterInterceptorContext context) throws IOException {
context.getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip");
OutputStream outputStream = context.getOutputStream();
GZIPOutputStream compressedOutputStream = new GZIPOutputStream(outputStream);
context.setOutputStream(compressedOutputStream);
try {
context.proceed();
} finally {
compressedOutputStream.finish();
context.setOutputStream(outputStream);
}
}
private boolean isGZipSupported() {
Object encoding = requestProvider.get().getHeader(HttpHeaders.ACCEPT_ENCODING);
return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip");
}
}

View File

@@ -35,7 +35,6 @@ package sonia.scm.net.ahc;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;

View File

@@ -35,8 +35,6 @@ package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Module;
//~--- JDK imports ------------------------------------------------------------
import java.util.Collection;

View File

@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.ConfigurationException;
import sonia.scm.io.CommandResult;
import sonia.scm.io.ExtendedCommand;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File;
@@ -67,11 +68,14 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
private final RepositoryLocationResolver repositoryLocationResolver;
private final PluginLoader pluginLoader;
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
RepositoryLocationResolver repositoryLocationResolver) {
RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader) {
super(storeFactory);
this.repositoryLocationResolver = repositoryLocationResolver;
this.pluginLoader = pluginLoader;
}
@Override
@@ -155,7 +159,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
String content = defaultContent;
try {
URL url = Resources.getResource(resource);
URL url = pluginLoader.getUberClassLoader().getResource(resource);
if (url != null) {
content = Resources.toString(url, Charsets.UTF_8);

View File

@@ -37,7 +37,6 @@ package sonia.scm.repository;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.apache.commons.collections.CollectionUtils;
import sonia.scm.security.PermissionObject;
import javax.xml.bind.annotation.XmlAccessType;
@@ -47,9 +46,10 @@ import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableSet;
//~--- JDK imports ------------------------------------------------------------
@@ -68,7 +68,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
private boolean groupPermission = false;
private String name;
@XmlElement(name = "verb")
private Collection<String> verbs;
private Set<String> verbs;
/**
* Constructs a new {@link RepositoryPermission}.
@@ -79,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission)
{
this.name = name;
this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs));
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
this.groupPermission = groupPermission;
}
@@ -109,7 +109,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
final RepositoryPermission other = (RepositoryPermission) obj;
return Objects.equal(name, other.name)
&& CollectionUtils.isEqualCollection(verbs, other.verbs)
&& verbs.containsAll(other.verbs)
&& verbs.size() == other.verbs.size()
&& Objects.equal(groupPermission, other.groupPermission);
}
@@ -209,6 +210,6 @@ public class RepositoryPermission implements PermissionObject, Serializable
*/
public void setVerbs(Collection<String> verbs)
{
this.verbs = verbs;
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
}
}

View File

@@ -5,7 +5,6 @@ import com.google.common.base.Objects;
import com.google.common.base.Strings;
import sonia.scm.Validateable;
import sonia.scm.repository.Person;
import sonia.scm.util.Util;
import java.io.Serializable;

View File

@@ -39,7 +39,6 @@ import com.google.common.base.Objects;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;

View File

@@ -34,8 +34,6 @@ package sonia.scm.security;
//~--- JDK imports ------------------------------------------------------------
import com.google.common.base.Objects;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

View File

@@ -0,0 +1,88 @@
package sonia.scm.util;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument;
public final class Comparables {
private static final CacheLoader<Class, BeanInfo> beanInfoCacheLoader = new CacheLoader<Class, BeanInfo>() {
@Override
public BeanInfo load(Class type) throws IntrospectionException {
return Introspector.getBeanInfo(type);
}
};
private static final LoadingCache<Class, BeanInfo> beanInfoCache = CacheBuilder.newBuilder()
.maximumSize(50) // limit the cache to avoid consuming to much memory on miss usage
.build(beanInfoCacheLoader);
private Comparables() {
}
public static <T> Comparator<T> comparator(Class<T> type, String sortBy) {
BeanInfo info = createBeanInfo(type);
PropertyDescriptor propertyDescriptor = findPropertyDescriptor(sortBy, info);
Method readMethod = propertyDescriptor.getReadMethod();
checkIfPropertyIsComparable(readMethod, sortBy);
return new MethodComparator<>(readMethod);
}
private static void checkIfPropertyIsComparable(Method readMethod, String sortBy) {
checkArgument(isReturnTypeComparable(readMethod), "property %s is not comparable", sortBy);
}
private static boolean isReturnTypeComparable(Method readMethod) {
return Comparable.class.isAssignableFrom(readMethod.getReturnType());
}
private static PropertyDescriptor findPropertyDescriptor(String sortBy, BeanInfo info) {
PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
Optional<PropertyDescriptor> sortByPropertyDescriptor = Arrays.stream(propertyDescriptors)
.filter(p -> p.getName().equals(sortBy))
.findFirst();
return sortByPropertyDescriptor.orElseThrow(() -> new IllegalArgumentException("could not find property " + sortBy));
}
private static <T> BeanInfo createBeanInfo(Class<T> type) {
return beanInfoCache.getUnchecked(type);
}
private static class MethodComparator<T> implements Comparator<T> {
private final Method readMethod;
private MethodComparator(Method readMethod) {
this.readMethod = readMethod;
}
@Override
@SuppressWarnings("unchecked")
public int compare(T left, T right) {
try {
Comparable leftResult = (Comparable) readMethod.invoke(left);
Comparable rightResult = (Comparable) readMethod.invoke(right);
return leftResult.compareTo(rightResult);
} catch (IllegalAccessException | InvocationTargetException ex) {
throw new IllegalArgumentException("failed to invoke read method", ex);
}
}
}
}

View File

@@ -0,0 +1,87 @@
package sonia.scm.filter;
import com.google.inject.util.Providers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class GZipResponseFilterTest {
@Mock
private HttpServletRequest request;
@Mock
private WriterInterceptorContext context;
@Mock
private MultivaluedMap<String,Object> headers;
private GZipResponseFilter filter;
@BeforeEach
void setupObjectUnderTest() {
filter = new GZipResponseFilter(Providers.of(request));
}
@Test
void shouldSkipGZipCompression() throws IOException {
when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br");
filter.aroundWriteTo(context);
verifySkipped();
}
@Test
void shouldSkipGZipCompressionWithoutAcceptEncodingHeader() throws IOException {
filter.aroundWriteTo(context);
verifySkipped();
}
private void verifySkipped() throws IOException {
verify(context, never()).getOutputStream();
verify(context).proceed();
}
@Nested
class AcceptGZipEncoding {
@BeforeEach
void setUpContext() {
when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br");
when(context.getHeaders()).thenReturn(headers);
when(context.getOutputStream()).thenReturn(new ByteArrayOutputStream());
}
@Test
void shouldEncode() throws IOException {
filter.aroundWriteTo(context);
verify(headers).remove(HttpHeaders.CONTENT_LENGTH);
verify(headers).add(HttpHeaders.CONTENT_ENCODING, "gzip");
verify(context).setOutputStream(any(GZIPOutputStream.class));
verify(context, times(2)).setOutputStream(any(OutputStream.class));
}
}
}

View File

@@ -36,7 +36,7 @@ import com.google.common.io.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

View File

@@ -46,4 +46,13 @@ class RepositoryPermissionTest {
assertThat(permission1).isNotEqualTo(permission2);
}
@Test
void shouldBeEqualWithRedundantVerbs() {
RepositoryPermission permission1 = new RepositoryPermission("name1", asList("one", "two"), false);
RepositoryPermission permission2 = new RepositoryPermission("name1", asList("one", "two"), false);
permission2.setVerbs(asList("one", "two", "two"));
assertThat(permission1).isEqualTo(permission2);
}
}

View File

@@ -1,7 +1,6 @@
package sonia.scm.security;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.UnknownAccountException;

View File

@@ -0,0 +1,57 @@
package sonia.scm.util;
import org.junit.jupiter.api.Test;
import java.util.Comparator;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ComparablesTest {
@Test
void shouldCompare() {
One a = new One("a");
One b = new One("b");
Comparator<One> comparable = Comparables.comparator(One.class, "value");
assertThat(comparable.compare(a, b)).isEqualTo(-1);
}
@Test
void shouldThrowAnExceptionForNonExistingField() {
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "awesome"));
}
@Test
void shouldThrowAnExceptionForNonComparableField() {
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "nonComparable"));
}
@Test
void shouldThrowAnExceptionIfTheFieldHasNoGetter() {
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "incredible"));
}
private static class One {
private String value;
private String incredible;
private NonComparable nonComparable;
One(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public NonComparable getNonComparable() {
return nonComparable;
}
}
private static class NonComparable {}
}

View File

@@ -37,9 +37,6 @@ package sonia.scm.store;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.security.KeyGenerator;

View File

@@ -38,7 +38,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.GenericDAO;
import sonia.scm.ModelObject;
import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.store.ConfigurationStore;
import java.util.Collection;

View File

@@ -1,6 +1,5 @@
package sonia.scm.xml;
import com.google.common.base.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -36,7 +36,6 @@ package sonia.scm.it;
import org.apache.http.HttpStatus;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

View File

@@ -5,10 +5,8 @@ import io.restassured.response.Response;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.user.User;
import sonia.scm.web.VndMediaType;
import java.net.ConnectException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

View File

@@ -18,8 +18,6 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
/**
* RESTful Web Service Resource to manage the configuration of the git plugin.
*/

View File

@@ -44,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.GitRepositoryServiceProvider;
import sonia.scm.schedule.Scheduler;
import sonia.scm.schedule.Task;
@@ -103,9 +104,10 @@ public class GitRepositoryHandler
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory,
Scheduler scheduler,
RepositoryLocationResolver repositoryLocationResolver,
GitWorkdirFactory workdirFactory)
GitWorkdirFactory workdirFactory,
PluginLoader pluginLoader)
{
super(storeFactory, repositoryLocationResolver);
super(storeFactory, repositoryLocationResolver, pluginLoader);
this.scheduler = scheduler;
this.workdirFactory = workdirFactory;
}

View File

@@ -35,7 +35,6 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;

View File

@@ -2,7 +2,7 @@
import React from "react";
import {apiClient, BranchSelector, ErrorPage, Loading, SubmitButton} from "@scm-manager/ui-components";
import {apiClient, BranchSelector, ErrorPage, Loading, Subtitle, SubmitButton} from "@scm-manager/ui-components";
import type {Branch, Repository} from "@scm-manager/ui-types";
import {translate} from "react-i18next";
@@ -113,6 +113,7 @@ class RepositoryConfig extends React.Component<Props, State> {
if (!(loadingBranches || loadingDefaultBranch)) {
return (
<>
<Subtitle subtitle={t("scm-git-plugin.repo-config.title")}/>
{this.renderBranchChangedNotification()}
<form onSubmit={this.submit}>
<BranchSelector
@@ -127,6 +128,7 @@ class RepositoryConfig extends React.Component<Props, State> {
disabled={!this.state.selectedBranchName}
/>
</form>
<hr />
</>
);
} else {

View File

@@ -27,14 +27,9 @@ binder.bind(
);
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
cfgBinder.bindRepository(
"/configuration",
"scm-git-plugin.repo-config.link",
"configuration",
RepositoryConfig
);
// global config
binder.bind("repo-config.route", RepositoryConfig, gitPredicate);
// global config
cfgBinder.bindGlobal(
"/git",
"scm-git-plugin.config.link",

View File

@@ -27,6 +27,7 @@
},
"repo-config": {
"link": "Konfiguration",
"title": "Git Einstellungen",
"default-branch": "Standard Branch",
"submit": "Speichern",
"error": {

View File

@@ -27,6 +27,7 @@
},
"repo-config": {
"link": "Configuration",
"title": "Git Settings",
"default-branch": "Default branch",
"submit": "Submit",
"error": {

View File

@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig;
import static org.junit.Assert.*;

View File

@@ -17,7 +17,7 @@ import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.GitRepositoryHandler;
@@ -29,6 +29,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.web.GitVndMediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -100,7 +101,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "readWrite")
public void shouldGetGitConfig() throws URISyntaxException {
public void shouldGetGitConfig() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
@@ -115,7 +116,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "readWrite")
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException {
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryHandler.getConfig()).thenReturn(null);
MockHttpResponse response = get();
@@ -126,7 +127,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "readOnly")
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException {
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
@@ -159,7 +160,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "writeOnly")
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException {
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
@@ -176,7 +177,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "readOnly")
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException {
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
@@ -193,7 +194,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "writeOnly")
public void shouldReadStoredRepositoryConfig() throws URISyntaxException {
public void shouldReadStoredRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig();
gitRepositoryConfig.setDefaultBranch("test");

View File

@@ -11,10 +11,9 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig;
import java.io.File;
import java.net.URI;
import static org.junit.Assert.assertEquals;

View File

@@ -14,9 +14,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.JsonEnricherContext;
import sonia.scm.web.VndMediaType;

View File

@@ -40,7 +40,7 @@ import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;

View File

@@ -94,7 +94,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
RepositoryLocationResolver locationResolver,
File directory) {
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
scheduler, locationResolver, gitWorkdirFactory);
scheduler, locationResolver, gitWorkdirFactory, null);
repositoryHandler.init(contextProvider);
GitConfig config = new GitConfig();
@@ -108,7 +108,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test
public void getDirectory() {
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
scheduler, locationResolver, gitWorkdirFactory);
scheduler, locationResolver, gitWorkdirFactory, null);
GitConfig config = new GitConfig();
config.setDisabled(false);
config.setGcExpression("gc exp");

View File

@@ -35,10 +35,8 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.After;
import org.junit.Before;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStore;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
/**

View File

@@ -35,11 +35,9 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.BlameLine;
import sonia.scm.repository.BlameResult;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;

View File

@@ -32,11 +32,9 @@
package sonia.scm.repository.spi;
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
import java.util.Collection;

View File

@@ -36,10 +36,8 @@ package sonia.scm.repository.spi;
import com.google.common.io.Files;
import org.junit.Test;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.ClearRepositoryCacheEvent;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Modifications;

View File

@@ -42,7 +42,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig;
import sonia.scm.repository.GitRepositoryHandler;

View File

@@ -35,14 +35,12 @@ package sonia.scm.installer;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import sonia.scm.net.ahc.AdvancedHttpClient;
/**

View File

@@ -39,7 +39,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection;
import sonia.scm.util.ValidationUtil;

View File

@@ -51,6 +51,7 @@ import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection;
import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.IOUtil;
@@ -111,9 +112,10 @@ public class HgRepositoryHandler
@Inject
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
Provider<HgContext> hgContextProvider,
RepositoryLocationResolver repositoryLocationResolver)
RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader)
{
super(storeFactory, repositoryLocationResolver);
super(storeFactory, repositoryLocationResolver, pluginLoader);
this.hgContextProvider = hgContextProvider;
try

View File

@@ -41,7 +41,6 @@ import com.aragost.javahg.internals.AbstractCommand;
import com.aragost.javahg.internals.HgInputStream;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.SubRepository;
@@ -52,7 +51,6 @@ import java.io.IOException;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
/**
* Mercurial command to list files of a repository.

View File

@@ -44,7 +44,6 @@ import sonia.scm.web.filter.PermissionFilter;
import sonia.scm.repository.HgRepositoryHandler;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.Set;

View File

@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;

View File

@@ -3,11 +3,9 @@ package sonia.scm.api.v2.resources;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

View File

@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import javax.inject.Provider;
import javax.servlet.http.HttpServletResponse;

View File

@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import java.net.URI;
import java.util.Arrays;

View File

@@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.installer.HgPackage;
import sonia.scm.installer.HgPackageReader;
import sonia.scm.net.ahc.AdvancedHttpClient;

View File

@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.installer.HgPackage;
import sonia.scm.installer.HgPackages;

View File

@@ -16,15 +16,15 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import javax.inject.Provider;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -99,7 +99,7 @@ public class HgConfigResourceTest {
@Test
@SubjectAware(username = "readWrite")
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException {
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryHandler.getConfig()).thenReturn(null);
MockHttpResponse response = get();
@@ -110,7 +110,7 @@ public class HgConfigResourceTest {
@Test
@SubjectAware(username = "readOnly")
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException {
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());

View File

@@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources;
import sonia.scm.installer.HgPackage;
import sonia.scm.repository.HgConfig;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

View File

@@ -11,7 +11,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import java.net.URI;

View File

@@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver);
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null);
handler.init(contextProvider);
HgTestUtil.checkForSkip(handler);
@@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test
public void getDirectory() {
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver);
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null);
HgConfig hgConfig = new HgConfig();
hgConfig.setHgBinary("hg");

View File

@@ -105,7 +105,7 @@ public final class HgTestUtil
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
HgRepositoryHandler handler =
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver);
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null);
Path repoDir = directory.toPath();
when(repoDao.getPath(any())).thenReturn(repoDir);
handler.init(context);

View File

@@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.mockito.Matchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

View File

@@ -48,8 +48,6 @@ import javax.servlet.http.HttpServletRequest;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES;
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;

View File

@@ -37,7 +37,7 @@ import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;

View File

@@ -53,9 +53,9 @@ import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection;
import sonia.scm.logging.SVNKitLogger;
import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.spi.SvnRepositoryServiceProvider;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.Util;
@@ -97,9 +97,10 @@ public class SvnRepositoryHandler
@Inject
public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory,
HookEventFacade eventFacade,
RepositoryLocationResolver repositoryLocationResolver)
RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader)
{
super(storeFactory, repositoryLocationResolver);
super(storeFactory, repositoryLocationResolver, pluginLoader);
// register logger
SVNDebugLog.setDefaultLog(new SVNKitLogger());

View File

@@ -36,7 +36,6 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNDirEntry;
@@ -53,7 +52,6 @@ import sonia.scm.repository.SvnUtil;
import sonia.scm.util.Util;
import java.util.Collection;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------

View File

@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.Compatibility;
import sonia.scm.repository.SvnConfig;

View File

@@ -16,14 +16,14 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.SvnConfig;
import sonia.scm.repository.SvnRepositoryHandler;
import sonia.scm.web.SvnVndMediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -98,7 +98,7 @@ public class SvnConfigResourceTest {
@Test
@SubjectAware(username = "readOnly")
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException {
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());

View File

@@ -11,11 +11,10 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.Compatibility;
import sonia.scm.repository.SvnConfig;
import java.io.File;
import java.net.URI;
import static org.junit.Assert.assertEquals;

View File

@@ -32,14 +32,10 @@
package sonia.scm.repository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File;
@@ -47,7 +43,7 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -93,7 +89,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
RepositoryLocationResolver locationResolver,
File directory) {
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver);
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver, null);
handler.init(contextProvider);
@@ -109,7 +105,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
public void getDirectory() {
when(factory.withType(any())).thenCallRealMethod();
SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory,
facade, locationResolver);
facade, locationResolver, null);
SvnConfig svnConfig = new SvnConfig();
repositoryHandler.setConfig(svnConfig);

View File

@@ -39,8 +39,6 @@ import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.Modifications;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

View File

@@ -59,7 +59,7 @@ public class DummyRepositoryHandler
private final Set<String> existingRepoNames = new HashSet<>();
public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) {
super(storeFactory, repositoryLocationResolver);
super(storeFactory, repositoryLocationResolver, null);
}
@Override

View File

@@ -13,10 +13,11 @@ const styles = {
minWidthOfLabel: {
minWidth: "4.5rem"
},
wrapper: {
padding: "1rem 1.5rem 0.25rem 1.5rem",
border: "1px solid #eee",
borderRadius: "5px 5px 0 0"
labelSizing: {
fontSize: "1rem !important"
},
noBottomMargin: {
marginBottom: "0 !important"
}
};
@@ -52,9 +53,9 @@ class BranchSelector extends React.Component<Props, State> {
return (
<div
className={classNames(
"has-background-light field",
"field",
"is-horizontal",
classes.wrapper
classes.noBottomMargin
)}
>
<div
@@ -65,10 +66,14 @@ class BranchSelector extends React.Component<Props, State> {
classes.minWidthOfLabel
)}
>
<label className="label">{label}</label>
<label className={classNames("label", classes.labelSizing)}>
{label}
</label>
</div>
<div className="field-body">
<div className="field is-narrow">
<div
className={classNames("field is-narrow", classes.noBottomMargin)}
>
<div className="control">
<DropDown
className="is-fullwidth"

View File

@@ -3,14 +3,17 @@ import React from "react";
type Props = {
displayName: string,
url: string
url: string,
disabled: boolean,
onClick?: () => void
};
class DownloadButton extends React.Component<Props> {
render() {
const { displayName, url } = this.props;
const { displayName, url, disabled, onClick } = this.props;
const onClickOrDefault = !!onClick ? onClick : () => {};
return (
<a className="button is-large is-link" href={url}>
<a className="button is-large is-link" href={url} disabled={disabled} onClick={onClickOrDefault}>
<span className="icon is-medium">
<i className="fas fa-arrow-circle-down" />
</span>

View File

@@ -72,6 +72,33 @@ class ConfigurationBinder {
binder.bind("repository.route", RepoRoute, repoPredicate);
}
bindRepositorySetting(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
// create predicate based on the link name of the current repository route
// if the linkname is not available, the navigation link and the route are not bound to the extension points
const repoPredicate = (props: Object) => {
return props.repository && props.repository._links && props.repository._links[linkName];
};
// create NavigationLink with translated label
const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => {
return this.navLink(url + "/settings" + to, labelI18nKey, t);
});
// bind navigation link to extension point
binder.bind("repository.subnavigation", RepoNavLink, repoPredicate);
// route for global configuration, passes the current repository to component
const RepoRoute = ({url, repository, ...additionalProps}) => {
const link = repository._links[linkName].href;
return this.route(url + "/settings" + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
};
// bind config route to extension point
binder.bind("repository.route", RepoRoute, repoPredicate);
}
}
export default new ConfigurationBinder();

View File

@@ -10,7 +10,8 @@ type Props = {
buttonLabel: string,
fieldLabel: string,
errorMessage: string,
helpText?: string
helpText?: string,
validateEntry?: string => boolean
};
type State = {
@@ -25,6 +26,15 @@ class AddEntryToTableField extends React.Component<Props, State> {
};
}
isValid = () => {
const {validateEntry} = this.props;
if (!this.state.entryToAdd || this.state.entryToAdd === "" || !validateEntry) {
return true;
} else {
return validateEntry(this.state.entryToAdd);
}
};
render() {
const {
disabled,
@@ -39,7 +49,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
label={fieldLabel}
errorMessage={errorMessage}
onChange={this.handleAddEntryChange}
validationError={false}
validationError={!this.isValid()}
value={this.state.entryToAdd}
onReturnPressed={this.appendEntry}
disabled={disabled}
@@ -48,7 +58,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
<AddButton
label={buttonLabel}
action={this.addButtonClicked}
disabled={disabled || this.state.entryToAdd ===""}
disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()}
/>
</div>
);

View File

@@ -28,7 +28,7 @@ class NavLink extends React.Component<Props> {
let showIcon = null;
if (icon) {
showIcon = (<><i className={icon}></i>{" "}</>);
showIcon = (<><i className={icon} />{" "}</>);
}
return (

View File

@@ -50,8 +50,19 @@ class PrimaryNavigation extends React.Component<Props> {
createNavigationItems = () => {
const navigationItems = [];
const { t, links } = this.props;
const props = {
links,
label: t("primary-navigation.first-menu")
};
const append = this.createNavigationAppender(navigationItems);
if (binder.hasExtension("primary-navigation.first-menu", props)) {
navigationItems.push(
<ExtensionPoint name="primary-navigation.first-menu" props={props} />
);
}
append("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories");
append("/users", "/(user|users)", "primary-navigation.users", "users");
append("/groups", "/(group|groups)", "primary-navigation.groups", "groups");

View File

@@ -0,0 +1,65 @@
//@flow
import * as React from "react";
import { Link, Route } from "react-router-dom";
type Props = {
to: string,
icon?: string,
label: string,
activeOnlyWhenExact?: boolean,
activeWhenMatch?: (route: any) => boolean,
children?: React.Node
};
class SubNavigation extends React.Component<Props> {
static defaultProps = {
activeOnlyWhenExact: false
};
isActive(route: any) {
const { activeWhenMatch } = this.props;
return route.match || (activeWhenMatch && activeWhenMatch(route));
}
renderLink = (route: any) => {
const { to, icon, label } = this.props;
let defaultIcon = "fas fa-cog";
if (icon) {
defaultIcon = icon;
}
let children = null;
if (this.isActive(route)) {
children = <ul className="sub-menu">{this.props.children}</ul>;
}
return (
<li>
<Link className={this.isActive(route) ? "is-active" : ""} to={to}>
<i className={defaultIcon} /> {label}
</Link>
{children}
</li>
);
};
render() {
const { to, activeOnlyWhenExact } = this.props;
// removes last part of url
let parents = to.split("/");
parents.splice(-1, 1);
let parent = parents.join("/");
return (
<Route
path={parent}
exact={activeOnlyWhenExact}
children={this.renderLink}
/>
);
}
}
export default SubNavigation;

View File

@@ -3,6 +3,7 @@
export { default as NavAction } from "./NavAction.js";
export { default as NavLink } from "./NavLink.js";
export { default as Navigation } from "./Navigation.js";
export { default as SubNavigation } from "./SubNavigation.js";
export { default as PrimaryNavigation } from "./PrimaryNavigation.js";
export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js";
export { default as Section } from "./Section.js";

View File

@@ -28,7 +28,7 @@ class ChangesetAuthor extends React.Component<Props> {
renderWithMail(name: string, mail: string) {
const { t } = this.props;
return (
<a href={"mailto: " + mail} title={t("changesets.author.mailto") + " " + mail}>
<a href={"mailto: " + mail} title={t("changeset.author.mailto") + " " + mail}>
{name}
</a>
);
@@ -38,7 +38,7 @@ class ChangesetAuthor extends React.Component<Props> {
const { t } = this.props;
return (
<>
{t("changesets.author.prefix")} {child}
{t("changeset.author.prefix")} {child}
<ExtensionPoint
name="changesets.author.suffix"
props={{ changeset: this.props.changeset }}

View File

@@ -29,7 +29,7 @@ class ChangesetButtonGroup extends React.Component<Props> {
<i className="fas fa-code-branch"></i>
</span>
<span className="is-hidden-mobile is-hidden-tablet-only">
{t("changesets.buttons.details")}
{t("changeset.buttons.details")}
</span>
</Button>
<Button link={sourcesLink}>
@@ -37,7 +37,7 @@ class ChangesetButtonGroup extends React.Component<Props> {
<i className="fas fa-code"></i>
</span>
<span className="is-hidden-mobile is-hidden-tablet-only">
{t("changesets.buttons.sources")}
{t("changeset.buttons.sources")}
</span>
</Button>
</ButtonGroup>

View File

@@ -25,7 +25,7 @@ class ChangesetDiff extends React.Component<Props> {
render() {
const { changeset, t } = this.props;
if (!this.isDiffSupported(changeset)) {
return <Notification type="danger">{t("changesets.diff.not-supported")}</Notification>;
return <Notification type="danger">{t("changeset.diffNotSupported")}</Notification>;
} else {
const url = this.createUrl(changeset);
return <LoadingDiff url={url} />;

View File

@@ -21,7 +21,7 @@ class ChangesetList extends React.Component<Props> {
/>
);
});
return <div className="box">{content}</div>;
return <>{content}</>;
}
}

View File

@@ -70,7 +70,7 @@ class ChangesetRow extends React.Component<Props> {
<h4 className="has-text-weight-bold is-ellipsis-overflow">
<ExtensionPoint
name="changesets.changeset.description"
name="changeset.description"
props={{ changeset, value: description.title }}
renderAll={false}
>
@@ -89,14 +89,14 @@ class ChangesetRow extends React.Component<Props> {
<div className={classNames(classes.metadata, "media-right")}>
<p className="is-hidden-mobile is-hidden-tablet-only">
<Interpolate
i18nKey="changesets.changeset.summary"
i18nKey="changeset.summary"
id={changesetId}
time={dateFromNow}
/>
</p>
<p className="is-hidden-desktop">
<Interpolate
i18nKey="changesets.changeset.short-summary"
i18nKey="changeset.shortSummary"
id={changesetId}
time={dateFromNow}
/>

View File

@@ -18,7 +18,7 @@ class ChangesetTagsCollapsed extends React.Component<Props> {
const message = tags.map((tag) => tag.name).join(", ");
return (
<Tooltip location="top" message={message}>
<ChangesetTagBase icon={"fa-tags"} label={ tags.length + " " + t("changesets.tags") } />
<ChangesetTagBase icon={"fa-tags"} label={ tags.length + " " + t("changeset.tags") } />
</Tooltip>
);
}

View File

@@ -43,8 +43,10 @@
"previous": "Zurück"
},
"profile": {
"navigation-label": "Navigation",
"actions-label": "Aktionen",
"navigationLabel": "Profil Navigation",
"informationNavLink": "Information",
"changePasswordNavLink": "Passwort ändern",
"settingsNavLink": "Einstellungen",
"username": "Benutzername",
"displayName": "Anzeigename",
"mail": "E-Mail",

View File

@@ -1,12 +1,10 @@
{
"config": {
"navigation-title": "Navigation"
},
"global-config": {
"navigationLabel": "Einstellungs Navigation",
"globalConfigurationNavLink": "Globale Einstellungen",
"title": "Einstellungen",
"navigation-label": "Globale Einstellungen",
"error-title": "Fehler",
"error-subtitle": "Unbekannter Einstellungen Fehler"
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Einstellungen Fehler"
},
"config-form": {
"submit": "Speichern",

View File

@@ -11,13 +11,16 @@
"title": "Gruppen",
"subtitle": "Verwaltung der Gruppen"
},
"single-group": {
"error-title": "Fehler",
"error-subtitle": "Unbekannter Gruppen Fehler",
"navigation-label": "Navigation",
"actions-label": "Aktionen",
"information-label": "Informationen",
"back-label": "Zurück"
"singleGroup": {
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Gruppen Fehler",
"menu": {
"navigationLabel": "Gruppen Navigation",
"informationNavLink": "Informationen",
"settingsNavLink": "Einstellungen",
"generalNavLink": "Generell",
"setPermissionsNavLink": "Berechtigungen"
}
},
"add-group": {
"title": "Gruppe erstellen",
@@ -43,28 +46,26 @@
"placeholder": "Benutzername eingeben",
"loading": "Suche...",
"no-options": "Kein Vorschlag für Benutzername verfügbar"
},
"group-form": {
},
"groupForm": {
"subtitle": "Gruppe bearbeiten",
"submit": "Speichern",
"name-error": "Name ist ungültig",
"description-error": "Beschreibung ist ungültig",
"nameError": "Name ist ungültig",
"descriptionError": "Beschreibung ist ungültig",
"help": {
"nameHelpText": "Eindeutiger Name der Gruppe",
"descriptionHelpText": "Eine kurze Beschreibung der Gruppe",
"memberHelpText": "Benutzername des Mitglieds der Gruppe"
}
},
"delete-group-button": {
"label": "Löschen",
"confirm-alert": {
"deleteGroup": {
"subtitle": "Gruppe löschen",
"button": "Löschen",
"confirmAlert": {
"title": "Gruppe löschen",
"message": "Soll die Gruppe wirklich gelöscht werden?",
"submit": "Ja",
"cancel": "Nein"
}
},
"set-permissions-button": {
"label": "Berechtigungen ändern"
}
}

View File

@@ -1,8 +1,6 @@
{
"form": {
"submit-button": {
"label": "Berechtigungen speichern"
},
"set-permissions-successful": "Berechtigungen erfolgreich gespeichert"
"setPermissions": {
"button": "Berechtigungen speichern",
"setPermissionsSuccessful": "Berechtigungen erfolgreich gespeichert"
}
}

View File

@@ -11,41 +11,58 @@
"name-invalid": "Der Name des Repository ist ungültig",
"contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein"
},
"help": {
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
"descriptionHelpText": "Eine kurze Beschreibung des Repository."
},
"repositoryRoot": {
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Repository Fehler",
"menu": {
"navigationLabel": "Repository Navigation",
"informationNavLink": "Informationen",
"historyNavLink": "Commits",
"sourcesNavLink": "Sources",
"settingsNavLink": "Einstellungen",
"generalNavLink": "Generell",
"permissionsNavLink": "Berechtigungen"
}
},
"overview": {
"title": "Repositories",
"subtitle": "Übersicht aller verfügbaren Repositories",
"create-button": "Repository erstellen"
},
"repository-root": {
"error-title": "Fehler",
"error-subtitle": "Unbekannter Repository Fehler",
"actions-label": "Aktionen",
"back-label": "Zurück",
"navigation-label": "Navigation",
"history": "Commits",
"information": "Informationen",
"permissions": "Berechtigungen",
"sources": "Sources"
"createButton": "Repository erstellen"
},
"create": {
"title": "Repository erstellen",
"subtitle": "Erstellen eines neuen Repository"
},
"repository-form": {
"submit": "Speichern"
"changesets": {
"errorTitle": "Fehler",
"errorSubtitle": "Changesets konnten nicht abgerufen werden",
"branchSelectorLabel": "Branches"
},
"edit-nav-link": {
"label": "Bearbeiten"
},
"delete-nav-action": {
"label": "Löschen",
"confirm-alert": {
"title": "Repository löschen",
"message": "Soll das Repository wirklich gelöscht werden?",
"submit": "Ja",
"cancel": "Nein"
"changeset": {
"description": "Beschreibung",
"summary": "Changeset {{id}} wurde committet {{time}}",
"shortSummary": "Committet {{id}} {{time}}",
"tags": "Tags",
"diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt",
"author": {
"prefix": "Verfasst von",
"mailto": "Mail senden an"
},
"buttons": {
"details": "Details",
"sources": "Sources"
}
},
"repositoryForm": {
"subtitle": "Repository bearbeiten",
"submit": "Speichern"
},
"sources": {
"file-tree": {
"name": "Name",
@@ -65,36 +82,8 @@
"size": "Größe"
}
},
"changesets": {
"diff": {
"not-supported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt"
},
"error-title": "Fehler",
"error-subtitle": "Changesets konnten nicht abgerufen werden",
"changeset": {
"id": "ID",
"description": "Beschreibung",
"contact": "Kontakt",
"date": "Datum",
"summary": "Changeset {{id}} wurde committet {{time}}",
"short-summary": "Committet {{id}} {{time}}"
},
"tags": "Tags",
"author": {
"name": "Autor",
"mail": "Mail",
"prefix": "Verfasst von",
"mailto": "Mail senden an"
},
"buttons": {
"details": "Details",
"sources": "Sources"
}
},
"branch-selector": {
"label": "Branches"
},
"permission": {
"title": "Berechtigungen bearbeiten",
"user": "Benutzer",
"group": "Gruppe",
"error-title": "Fehler",
@@ -106,7 +95,7 @@
"user-permission": "Benutzerberechtigung",
"edit-permission": {
"delete-button": "Löschen",
"save-button": "Änderungen Speichern"
"save-button": "Änderungen speichern"
},
"advanced-button": {
"label": "Erweitert"
@@ -146,10 +135,14 @@
}
}
},
"help": {
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
"descriptionHelpText": "Eine kurze Beschreibung des Repository."
"deleteRepo": {
"subtitle": "Repository löschen",
"button": "Löschen",
"confirmAlert": {
"title": "Repository löschen",
"message": "Soll das Repository wirklich gelöscht werden?",
"submit": "Ja",
"cancel": "Nein"
}
}
}

View File

@@ -10,59 +10,55 @@
"creationDate": "Erstellt",
"lastModified": "Zuletzt bearbeitet"
},
"users": {
"title": "Benutzer",
"subtitle": "Verwaltung der Benutzer"
},
"create-user-button": {
"label": "Benutzer erstellen"
},
"delete-user-button": {
"label": "Löschen",
"confirm-alert": {
"title": "Benutzer löschen",
"message": "Soll der Benutzer wirklich gelöscht werden?",
"submit": "Ja",
"cancel": "Nein"
}
},
"edit-user-button": {
"label": "Bearbeiten"
},
"set-password-button": {
"label": "Passwort ändern"
},
"set-permissions-button": {
"label": "Berechtigungen ändern"
},
"user-form": {
"submit": "Speichern"
},
"add-user": {
"title": "Benutzer erstellen",
"subtitle": "Erstellen eines neuen Benutzers"
},
"single-user": {
"error-title": "Fehler",
"error-subtitle": "Unbekannter Benutzer Fehler",
"navigation-label": "Navigation",
"actions-label": "Aktionen",
"information-label": "Informationen",
"back-label": "Zurück"
},
"validation": {
"mail-invalid": "Diese E-Mail ist ungültig",
"name-invalid": "Dieser Name ist ungültig",
"displayname-invalid": "Dieser Anzeigename ist ungültig"
},
"password": {
"set-password-successful": "Das Passwort wurde erfolgreich gespeichert."
},
"help": {
"usernameHelpText": "Einzigartiger Name des Benutzers",
"displayNameHelpText": "Anzeigename des Benutzers",
"mailHelpText": "E-Mail Adresse des Benutzers",
"adminHelpText": "Ein Administrator kann Repositories, Gruppen und Benutzer erstellen, bearbeiten und löschen.",
"activeHelpText": "Aktivierung oder Deaktivierung eines Benutzers"
},
"users": {
"title": "Benutzer",
"subtitle": "Verwaltung der Benutzer",
"createButton": "Benutzer erstellen"
},
"singleUser": {
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Benutzer Fehler",
"menu": {
"navigationLabel": "Benutzer Navigation",
"informationNavLink": "Informationen",
"settingsNavLink": "Einstellungen",
"generalNavLink": "Generell",
"setPasswordNavLink": "Passwort",
"setPermissionsNavLink": "Berechtigungen"
}
},
"addUser": {
"title": "Benutzer erstellen",
"subtitle": "Erstellen eines neuen Benutzers"
},
"deleteUser": {
"subtitle": "Benutzer löschen",
"button": "Löschen",
"confirmAlert": {
"title": "Benutzer löschen",
"message": "Soll der Benutzer wirklich gelöscht werden?",
"submit": "Ja",
"cancel": "Nein"
}
},
"singleUserPassword": {
"button": "Passwort setzen",
"setPasswordSuccessful": "Das Passwort wurde erfolgreich gespeichert."
},
"userForm": {
"subtitle": "Benutzer bearbeiten",
"button": "Speichern"
}
}

View File

@@ -43,8 +43,10 @@
"previous": "Previous"
},
"profile": {
"navigation-label": "Navigation",
"actions-label": "Actions",
"navigationLabel": "Profile Navigation",
"informationNavLink": "Information",
"changePasswordNavLink": "Change password",
"settingsNavLink": "Settings",
"username": "Username",
"displayName": "Display Name",
"mail": "E-Mail",
@@ -67,6 +69,6 @@
"passwordInvalid": "Password has to be between 6 and 32 characters",
"passwordConfirmFailed": "Passwords have to be identical",
"submit": "Submit",
"changedSuccessfully": "Password successfully changed"
"changedSuccessfully": "Password changed successfully"
}
}

View File

@@ -1,12 +1,10 @@
{
"config": {
"navigation-title": "Navigation"
},
"global-config": {
"navigationLabel": "Configuration Navigation",
"globalConfigurationNavLink": "Global Configuration",
"title": "Configuration",
"navigation-label": "Global Configuration",
"error-title": "Error",
"error-subtitle": "Unknown Config Error"
"errorTitle": "Error",
"errorSubtitle": "Unknown Config Error"
},
"config-form": {
"submit": "Submit",

View File

@@ -11,13 +11,16 @@
"title": "Groups",
"subtitle": "Create, read, update and delete groups"
},
"single-group": {
"error-title": "Error",
"error-subtitle": "Unknown group error",
"navigation-label": "Navigation",
"actions-label": "Actions",
"information-label": "Information",
"back-label": "Back"
"singleGroup": {
"errorTitle": "Error",
"errorSubtitle": "Unknown group error",
"menu": {
"navigationLabel": "Group Navigation",
"informationNavLink": "Information",
"settingsNavLink": "Settings",
"generalNavLink": "General",
"setPermissionsNavLink": "Permissions"
}
},
"add-group": {
"title": "Create Group",
@@ -43,28 +46,26 @@
"placeholder": "Enter Member",
"loading": "Loading...",
"no-options": "No suggestion available"
},
"group-form": {
},
"groupForm": {
"subtitle": "Edit Group",
"submit": "Submit",
"name-error": "Group name is invalid",
"description-error": "Description is invalid",
"nameError": "Group name is invalid",
"descriptionError": "Description is invalid",
"help": {
"nameHelpText": "Unique name of the group",
"descriptionHelpText": "A short description of the group",
"memberHelpText": "Usernames of the group members"
}
},
"delete-group-button": {
"label": "Delete",
"confirm-alert": {
"deleteGroup": {
"subtitle": "Delete Group",
"button": "Delete",
"confirmAlert": {
"title": "Delete Group",
"message": "Do you really want to delete the group?",
"submit": "Yes",
"cancel": "No"
}
},
"set-permissions-button": {
"label": "Set Permissions"
}
}

View File

@@ -1,8 +1,6 @@
{
"form": {
"submit-button": {
"label": "Set Permissions"
},
"set-permissions-successful": "Permissions set successfully"
"setPermissions": {
"button": "Set permissions",
"setPermissionsSuccessful": "Permissions set successfully"
}
}

View File

@@ -11,41 +11,58 @@
"name-invalid": "The repository name is invalid",
"contact-invalid": "Contact must be a valid mail address"
},
"help": {
"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).",
"contactHelpText": "Email address of the person who is responsible for this repository.",
"descriptionHelpText": "A short description of the repository."
},
"repositoryRoot": {
"errorTitle": "Error",
"errorSubtitle": "Unknown repository error",
"menu": {
"navigationLabel": "Repository Navigation",
"informationNavLink": "Information",
"historyNavLink": "Commits",
"sourcesNavLink": "Sources",
"settingsNavLink": "Settings",
"generalNavLink": "General",
"permissionsNavLink": "Permissions"
}
},
"overview": {
"title": "Repositories",
"subtitle": "Overview of available repositories",
"create-button": "Create Repository"
},
"repository-root": {
"error-title": "Error",
"error-subtitle": "Unknown repository error",
"actions-label": "Actions",
"back-label": "Back",
"navigation-label": "Navigation",
"history": "Commits",
"information": "Information",
"permissions": "Permissions",
"sources": "Sources"
"createButton": "Create Repository"
},
"create": {
"title": "Create Repository",
"subtitle": "Create a new repository"
},
"repository-form": {
"submit": "Save"
"changesets": {
"errorTitle": "Error",
"errorSubtitle": "Could not fetch changesets",
"branchSelectorLabel": "Branches"
},
"edit-nav-link": {
"label": "Edit"
},
"delete-nav-action": {
"label": "Delete",
"confirm-alert": {
"title": "Delete Repository",
"message": "Do you really want to delete the repository?",
"submit": "Yes",
"cancel": "No"
"changeset": {
"description": "Description",
"summary": "Changeset {{id}} was committed {{time}}",
"shortSummary": "Committed {{id}} {{time}}",
"tags": "Tags",
"diffNotSupported": "Diff of changesets is not supported by the type of repository",
"author": {
"prefix": "Authored by",
"mailto": "Send mail to"
},
"buttons": {
"details": "Details",
"sources": "Sources"
}
},
"repositoryForm": {
"subtitle": "Edit Repository",
"submit": "Save"
},
"sources": {
"file-tree": {
"name": "Name",
@@ -65,36 +82,8 @@
"size": "Size"
}
},
"changesets": {
"diff": {
"not-supported": "Diff of changesets is not supported by the type of repository"
},
"error-title": "Error",
"error-subtitle": "Could not fetch changesets",
"changeset": {
"id": "ID",
"description": "Description",
"contact": "Contact",
"date": "Date",
"summary": "Changeset {{id}} was committed {{time}}",
"short-summary": "Committed {{id}} {{time}}"
},
"tags": "Tags",
"author": {
"name": "Author",
"mail": "Mail",
"prefix": "Authored by",
"mailto": "Send mail to"
},
"buttons": {
"details": "Details",
"sources": "Sources"
}
},
"branch-selector": {
"label": "Branches"
},
"permission": {
"title": "Edit Permissions",
"user": "User",
"group": "Group",
"error-title": "Error",
@@ -114,7 +103,7 @@
"delete-permission-button": {
"label": "Delete",
"confirm-alert": {
"title": "Delete Permission",
"title": "Delete permission",
"message": "Do you really want to delete the permission?",
"submit": "Yes",
"cancel": "No"
@@ -146,10 +135,14 @@
}
}
},
"help": {
"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).",
"contactHelpText": "Email address of the person who is responsible for this repository.",
"descriptionHelpText": "A short description of the repository."
"deleteRepo": {
"subtitle": "Delete Repository",
"button": "Delete",
"confirmAlert": {
"title": "Delete repository",
"message": "Do you really want to delete the repository?",
"submit": "Yes",
"cancel": "No"
}
}
}

View File

@@ -10,59 +10,55 @@
"creationDate": "Creation Date",
"lastModified": "Last Modified"
},
"users": {
"title": "Users",
"subtitle": "Create, read, update and delete users"
},
"create-user-button": {
"label": "Create User"
},
"delete-user-button": {
"label": "Delete",
"confirm-alert": {
"title": "Delete User",
"message": "Do you really want to delete the user?",
"submit": "Yes",
"cancel": "No"
}
},
"edit-user-button": {
"label": "Edit"
},
"set-password-button": {
"label": "Set Password"
},
"set-permissions-button": {
"label": "Set Permissions"
},
"user-form": {
"submit": "Submit"
},
"add-user": {
"title": "Create User",
"subtitle": "Create a new user"
},
"single-user": {
"error-title": "Error",
"error-subtitle": "Unknown user error",
"navigation-label": "Navigation",
"actions-label": "Actions",
"information-label": "Information",
"back-label": "Back"
},
"validation": {
"mail-invalid": "This email is invalid",
"name-invalid": "This name is invalid",
"displayname-invalid": "This displayname is invalid"
},
"password": {
"set-password-successful": "Password successfully set"
},
"help": {
"usernameHelpText": "Unique name of the user.",
"displayNameHelpText": "Display name of the user.",
"mailHelpText": "Email address of the user.",
"adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.",
"activeHelpText": "Activate or deactivate the user."
},
"users": {
"title": "Users",
"subtitle": "Create, read, update and delete users",
"createButton": "Create User"
},
"singleUser": {
"errorTitle": "Error",
"errorSubtitle": "Unknown user error",
"menu": {
"navigationLabel": "User Navigation",
"informationNavLink": "Information",
"settingsNavLink": "Settings",
"generalNavLink": "General",
"setPasswordNavLink": "Password",
"setPermissionsNavLink": "Permissions"
}
},
"addUser": {
"title": "Create User",
"subtitle": "Create a new user"
},
"deleteUser": {
"subtitle": "Delete User",
"button": "Delete",
"confirmAlert": {
"title": "Delete user",
"message": "Do you really want to delete the user?",
"submit": "Yes",
"cancel": "No"
}
},
"singleUserPassword": {
"button": "Set password",
"setPasswordSuccessful": "Password successfully set"
},
"userForm": {
"subtitle": "Edit User",
"button": "Submit"
}
}

View File

@@ -1,86 +1,87 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import { Route } from "react-router";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Links } from "@scm-manager/ui-types";
import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components";
import GlobalConfig from "./GlobalConfig";
import type { History } from "history";
import {connect} from "react-redux";
import {compose} from "redux";
import { getLinks } from "../../modules/indexResource";
type Props = {
links: Links,
// context objects
t: string => string,
match: any,
history: History
};
class Config extends React.Component<Props> {
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 2);
}
return url;
};
matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url);
};
render() {
const { links, t } = this.props;
const url = this.matchedUrl();
const extensionProps = {
links,
url
};
return (
<Page>
<div className="columns">
<div className="column is-three-quarters">
<Route path={url} exact component={GlobalConfig} />
<ExtensionPoint name="config.route"
props={extensionProps}
renderAll={true}
/>
</div>
<div className="column is-one-quarter">
<Navigation>
<Section label={t("config.navigation-title")}>
<NavLink
to={`${url}`}
label={t("global-config.navigation-label")}
/>
<ExtensionPoint name="config.navigation"
props={extensionProps}
renderAll={true}
/>
</Section>
</Navigation>
</div>
</div>
</Page>
);
}
}
const mapStateToProps = (state: any) => {
const links = getLinks(state);
return {
links
};
};
export default compose(
connect(mapStateToProps),
translate("config")
)(Config);
// @flow
import React from "react";
import { translate } from "react-i18next";
import { Route } from "react-router";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Links } from "@scm-manager/ui-types";
import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components";
import GlobalConfig from "./GlobalConfig";
import type { History } from "history";
import { connect } from "react-redux";
import { compose } from "redux";
import { getLinks } from "../../modules/indexResource";
type Props = {
links: Links,
// context objects
t: string => string,
match: any,
history: History
};
class Config extends React.Component<Props> {
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 2);
}
return url;
};
matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url);
};
render() {
const { links, t } = this.props;
const url = this.matchedUrl();
const extensionProps = {
links,
url
};
return (
<Page>
<div className="columns">
<div className="column is-three-quarters">
<Route path={url} exact component={GlobalConfig} />
<ExtensionPoint
name="config.route"
props={extensionProps}
renderAll={true}
/>
</div>
<div className="column is-one-quarter">
<Navigation>
<Section label={t("config.navigationLabel")}>
<NavLink
to={`${url}`}
label={t("config.globalConfigurationNavLink")}
/>
<ExtensionPoint
name="config.navigation"
props={extensionProps}
renderAll={true}
/>
</Section>
</Navigation>
</div>
</div>
</Page>
);
}
}
const mapStateToProps = (state: any) => {
const links = getLinks(state);
return {
links
};
};
export default compose(
connect(mapStateToProps),
translate("config")
)(Config);

View File

@@ -78,8 +78,8 @@ class GlobalConfig extends React.Component<Props, State> {
if (error) {
return (
<ErrorPage
title={t("global-config.error-title")}
subtitle={t("global-config.error-subtitle")}
title={t("config.errorTitle")}
subtitle={t("config.errorSubtitle")}
error={error}
configUpdatePermission={configUpdatePermission}
/>
@@ -91,7 +91,7 @@ class GlobalConfig extends React.Component<Props, State> {
return (
<div>
<Title title={t("global-config.title")} />
<Title title={t("config.title")} />
{this.renderConfigChangedNotification()}
<ConfigForm
submitForm={config => this.modifyConfig(config)}

View File

@@ -143,7 +143,6 @@ class Login extends React.Component<Props, State> {
/>
<SubmitButton
label={t("login.submit")}
disabled={this.isInValid()}
fullWidth={true}
loading={loading}
/>

Some files were not shown because too many files have changed in this diff Show More