diff --git a/scm-core/src/main/java/sonia/scm/ScmState.java b/scm-core/src/main/java/sonia/scm/ScmState.java index 5610a40c08..cd36aa707c 100644 --- a/scm-core/src/main/java/sonia/scm/ScmState.java +++ b/scm-core/src/main/java/sonia/scm/ScmState.java @@ -35,6 +35,7 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.repository.RepositoryType; import sonia.scm.security.PermissionDescriptor; import sonia.scm.user.User; @@ -82,9 +83,9 @@ public final class ScmState * @since 2.0.0 */ public ScmState(String version, User user, Collection groups, - String token, Collection repositoryTypes, String defaultUserType, - ScmClientConfig clientConfig, List assignedPermission, - List availablePermissions) + String token, Collection repositoryTypes, String defaultUserType, + ScmClientConfig clientConfig, List assignedPermission, + List availablePermissions) { this.version = version; this.user = user; @@ -165,7 +166,7 @@ public final class ScmState * * @return all available repository types */ - public Collection getRepositoryTypes() + public Collection getRepositoryTypes() { return repositoryTypes; } @@ -244,7 +245,7 @@ public final class ScmState /** Field description */ @XmlElement(name = "repositoryTypes") - private Collection repositoryTypes; + private Collection repositoryTypes; /** Field description */ private User user; diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java index 1aa6f474f0..7b71078f67 100644 --- a/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java +++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java @@ -5,7 +5,7 @@ import com.google.common.base.Strings; import java.util.Objects; -public class NamespaceAndName { +public class NamespaceAndName implements Comparable { private final String namespace; private final String name; @@ -47,4 +47,13 @@ public class NamespaceAndName { public int hashCode() { return Objects.hash(namespace, name); } + + @Override + public int compareTo(NamespaceAndName o) { + int result = namespace.compareTo(o.namespace); + if (result == 0) { + return name.compareTo(o.name); + } + return result; + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java index f972956adf..d3529294ed 100644 --- a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java +++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java @@ -2,7 +2,18 @@ package sonia.scm.repository; import sonia.scm.plugin.ExtensionPoint; +/** + * Strategy to create a namespace for the new repository. Namespaces are used to order and identify repositories. + */ @ExtensionPoint public interface NamespaceStrategy { - String getNamespace(); + + /** + * Create new namespace for the given repository. + * + * @param repository repository + * + * @return namespace + */ + String createNamespace(Repository repository); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java index 8dc5f8418b..739d0d0177 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java @@ -82,4 +82,7 @@ public interface RepositoryHandler * @since 1.15 */ public String getVersionInformation(); + + @Override + RepositoryType getType(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java index 172108aa1b..4fc9db5c32 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java @@ -35,7 +35,6 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.Type; import sonia.scm.TypeManager; import javax.servlet.http.HttpServletRequest; @@ -99,7 +98,7 @@ public interface RepositoryManager * * @return all configured repository types */ - public Collection getConfiguredTypes(); + public Collection getConfiguredTypes(); /** * Returns the {@link Repository} associated to the request uri. diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java index 6990baf7c5..b504359bc0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java @@ -103,7 +103,7 @@ public class RepositoryManagerDecorator * @return */ @Override - public Collection getConfiguredTypes() + public Collection getConfiguredTypes() { return decorated.getConfiguredTypes(); } diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 7c764685f1..41efc60cde 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -6,6 +6,7 @@ import javax.ws.rs.core.MediaType; * Vendor media types used by SCMM. */ public class VndMediaType { + private static final String VERSION = "2"; private static final String TYPE = "application"; private static final String SUBTYPE_PREFIX = "vnd.scmm-"; @@ -18,8 +19,10 @@ public class VndMediaType { public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; - public static final String CONFIG = PREFIX + "config" + SUFFIX; + public static final String REPOSITORY_TYPE_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX; + public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; + public static final String ME = PREFIX + "me" + SUFFIX; private VndMediaType() { } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java index 2338cc3b46..87f96f850f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java @@ -41,7 +41,6 @@ import com.google.inject.Singleton; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import sonia.scm.Type; import sonia.scm.io.FileSystem; import sonia.scm.plugin.Extension; import sonia.scm.repository.spi.GitRepositoryServiceProvider; @@ -88,7 +87,7 @@ public class GitRepositoryHandler private static final Logger logger = LoggerFactory.getLogger(GitRepositoryHandler.class); /** Field description */ - public static final Type TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, GitRepositoryServiceProvider.COMMANDS); @@ -167,7 +166,7 @@ public class GitRepositoryHandler * @return */ @Override - public Type getType() + public RepositoryType getType() { return TYPE; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index 40987fa4da..aad546f651 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -44,7 +44,6 @@ import org.slf4j.LoggerFactory; import sonia.scm.ConfigurationException; import sonia.scm.SCMContextProvider; -import sonia.scm.Type; import sonia.scm.installer.HgInstaller; import sonia.scm.installer.HgInstallerFactory; import sonia.scm.io.DirectoryFileFilter; @@ -98,7 +97,7 @@ public class HgRepositoryHandler public static final String TYPE_NAME = "hg"; /** Field description */ - public static final Type TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, HgRepositoryServiceProvider.COMMANDS, HgRepositoryServiceProvider.FEATURES); @@ -259,7 +258,7 @@ public class HgRepositoryHandler * @return */ @Override - public Type getType() + public RepositoryType getType() { return TYPE; } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java index dcadd5c1c6..58ada0738b 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java @@ -49,7 +49,6 @@ import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.util.SVNDebugLog; -import sonia.scm.Type; import sonia.scm.io.FileSystem; import sonia.scm.logging.SVNKitLogger; import sonia.scm.plugin.Extension; @@ -87,7 +86,7 @@ public class SvnRepositoryHandler public static final String TYPE_NAME = "svn"; /** Field description */ - public static final Type TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, SvnRepositoryServiceProvider.COMMANDS); @@ -150,7 +149,7 @@ public class SvnRepositoryHandler * @return */ @Override - public Type getType() + public RepositoryType getType() { return TYPE; } diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index bf68c27b19..db4cfe0090 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -33,7 +33,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.Type; +import com.google.common.collect.Sets; import sonia.scm.io.DefaultFileSystem; import sonia.scm.store.ConfigurationStoreFactory; @@ -55,7 +55,7 @@ public class DummyRepositoryHandler public static final String TYPE_NAME = "dummy"; - public static final Type TYPE = new Type(TYPE_NAME, TYPE_DISPLAYNAME); + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, Sets.newHashSet()); private final Set existingRepoNames = new HashSet<>(); @@ -64,7 +64,7 @@ public class DummyRepositoryHandler } @Override - public Type getType() { + public RepositoryType getType() { return TYPE; } diff --git a/scm-ui/package.json b/scm-ui/package.json index bd182cd972..6ce2e8880e 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -6,10 +6,12 @@ "dependencies": { "bulma": "^0.7.1", "classnames": "^2.2.5", + "font-awesome": "^4.7.0", "history": "^4.7.2", "i18next": "^11.4.0", "i18next-browser-languagedetector": "^2.2.2", "i18next-fetch-backend": "^0.1.0", + "moment": "^2.22.2", "react": "^16.4.1", "react-dom": "^16.4.1", "react-i18next": "^7.9.0", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json new file mode 100644 index 0000000000..7db2623247 --- /dev/null +++ b/scm-ui/public/locales/en/repos.json @@ -0,0 +1,46 @@ +{ + "repository": { + "name": "Name", + "type": "Type", + "contact": "Contact", + "description": "Description", + "creationDate": "Creation Date", + "lastModified": "Last Modified" + }, + "validation": { + "name-invalid": "The repository name is invalid", + "contact-invalid": "Contact must be a valid mail address" + }, + "overview": { + "title": "Repositories", + "subtitle": "Overview of available repositories", + "create-button": "Create" + }, + "repository-root": { + "error-title": "Error", + "error-subtitle": "Unknown repository error", + "actions-label": "Actions", + "back-label": "Back", + "navigation-label": "Navigation", + "information": "Information" + }, + "create": { + "title": "Create Repository", + "subtitle": "Create a new repository" + }, + "repository-form": { + "submit": "Save" + }, + "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" + } + } +} diff --git a/scm-ui/public/locales/en/repositories.json b/scm-ui/public/locales/en/repositories.json deleted file mode 100644 index 4cfd96a25f..0000000000 --- a/scm-ui/public/locales/en/repositories.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "repositories": { - "title": "Repositories", - "subtitle": "Repositories will be shown here", - "body": "Coming soon ..." - } -} diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index c0b55adf7e..f1d71d8efc 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -5,7 +5,10 @@ "mail": "E-Mail", "password": "Password", "admin": "Admin", - "active": "Active" + "active": "Active", + "type": "Type", + "creationDate": "Creation Date", + "lastModified": "Last Modified" }, "users": { "title": "Users", diff --git a/scm-ui/src/apiclient.js b/scm-ui/src/apiclient.js index bcddf040aa..f8cfc4db70 100644 --- a/scm-ui/src/apiclient.js +++ b/scm-ui/src/apiclient.js @@ -27,7 +27,7 @@ function handleStatusCode(response: Response) { } export function createUrl(url: string) { - if (url.indexOf("://") > 0) { + if (url.includes("://")) { return url; } let urlWithStartingSlash = url; @@ -42,26 +42,12 @@ class ApiClient { return fetch(createUrl(url), fetchOptions).then(handleStatusCode); } - post(url: string, payload: any) { - return this.httpRequestWithJSONBody(url, payload, "POST"); + post(url: string, payload: any, contentType: string = "application/json") { + return this.httpRequestWithJSONBody("POST", url, contentType, payload); } - postWithContentType(url: string, payload: any, contentType: string) { - return this.httpRequestWithContentType( - url, - "POST", - JSON.stringify(payload), - contentType - ); - } - - putWithContentType(url: string, payload: any, contentType: string) { - return this.httpRequestWithContentType( - url, - "PUT", - JSON.stringify(payload), - contentType - ); + put(url: string, payload: any, contentType: string = "application/json") { + return this.httpRequestWithJSONBody("PUT", url, contentType, payload); } delete(url: string): Promise { @@ -73,37 +59,14 @@ class ApiClient { } httpRequestWithJSONBody( - url: string, - payload: any, - method: string - ): Promise { - // let options: RequestOptions = { - // method: method, - // body: JSON.stringify(payload) - // }; - // options = Object.assign(options, fetchOptions); - // // $FlowFixMe - // options.headers["Content-Type"] = "application/json"; - - // return fetch(createUrl(url), options).then(handleStatusCode); - - return this.httpRequestWithContentType( - url, - method, - JSON.stringify(payload), - "application/json" - ).then(handleStatusCode); - } - - httpRequestWithContentType( - url: string, method: string, - payload: any, - contentType: string + url: string, + contentType: string, + payload: any ): Promise { let options: RequestOptions = { method: method, - body: payload + body: JSON.stringify(payload) }; options = Object.assign(options, fetchOptions); // $FlowFixMe diff --git a/scm-ui/src/components/DateFromNow.js b/scm-ui/src/components/DateFromNow.js new file mode 100644 index 0000000000..b47de49a3d --- /dev/null +++ b/scm-ui/src/components/DateFromNow.js @@ -0,0 +1,32 @@ +//@flow +import React from "react"; +import moment from "moment"; +import { translate } from "react-i18next"; + +type Props = { + date?: string, + + // context props + i18n: any +}; + +class DateFromNow extends React.Component { + static format(locale: string, date?: string) { + let fromNow = ""; + if (date) { + fromNow = moment(date) + .locale(locale) + .fromNow(); + } + return fromNow; + } + + render() { + const { i18n, date } = this.props; + + const fromNow = DateFromNow.format(i18n.language, date); + return {fromNow}; + } +} + +export default translate()(DateFromNow); diff --git a/scm-ui/src/components/MailLink.js b/scm-ui/src/components/MailLink.js new file mode 100644 index 0000000000..7d009cde85 --- /dev/null +++ b/scm-ui/src/components/MailLink.js @@ -0,0 +1,18 @@ +// @flow +import React from "react"; + +type Props = { + address?: string +}; + +class MailLink extends React.Component { + render() { + const { address } = this.props; + if (!address) { + return null; + } + return {address}; + } +} + +export default MailLink; diff --git a/scm-ui/src/components/buttons/AddButton.js b/scm-ui/src/components/buttons/AddButton.js deleted file mode 100644 index 6668aa32d1..0000000000 --- a/scm-ui/src/components/buttons/AddButton.js +++ /dev/null @@ -1,11 +0,0 @@ -//@flow -import React from "react"; -import Button, { type ButtonProps } from "./Button"; - -class AddButton extends React.Component { - render() { - return