diff --git a/plugins/scm-hg-plugin/pom.xml b/plugins/scm-hg-plugin/pom.xml index 6526d7391c..e1b081c9a0 100644 --- a/plugins/scm-hg-plugin/pom.xml +++ b/plugins/scm-hg-plugin/pom.xml @@ -14,5 +14,15 @@ scm-hg-plugin 1.0-SNAPSHOT scm-hg-plugin - + + + + + sonia.scm + scm-web-api + 1.0-SNAPSHOT + + + + diff --git a/plugins/scm-hg-plugin/src/main/java/sonia/scm/HgPlugin.java b/plugins/scm-hg-plugin/src/main/java/sonia/scm/HgPlugin.java new file mode 100644 index 0000000000..555b38161b --- /dev/null +++ b/plugins/scm-hg-plugin/src/main/java/sonia/scm/HgPlugin.java @@ -0,0 +1,37 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.InputStream; + +/** + * + * @author Sebastian Sdorra + */ +public class HgPlugin extends ScmWebPluginAdapter +{ + + /** Field description */ + public static final String SCRIPT = "/sonia/scm/hg.config.js"; + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public InputStream getScript() + { + return HgPlugin.class.getResourceAsStream(SCRIPT); + } +} diff --git a/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java b/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java new file mode 100644 index 0000000000..a928fa2d62 --- /dev/null +++ b/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java @@ -0,0 +1,93 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm.api.rest.resources; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.repository.RepositoryManager; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +@Path("config/repositories/hg") +@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) +public class HgConfigResource +{ + + /** + * Constructs ... + * + * + * @param repositoryManager + */ + @Inject + public HgConfigResource(RepositoryManager repositoryManager) + { + handler = (HgRepositoryHandler) repositoryManager.getHandler( + HgRepositoryHandler.TYPE_NAME); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @GET + public HgConfig getConfig() + { + return handler.getConfig(); + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param uriInfo + * @param config + * + * @return + */ + @POST + @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response setConfig(@Context UriInfo uriInfo, HgConfig config) + { + handler.setConfig(config); + handler.storeConfig(); + + return Response.created(uriInfo.getRequestUri()).build(); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private HgRepositoryHandler handler; +} diff --git a/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index cd308b085b..82e537acbd 100644 --- a/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -153,19 +153,19 @@ public class HgRepositoryHandler implements RepositoryHandler File baseDirectory = context.getBaseDirectory(); AssertUtil.assertIsNotNull(baseDirectory); + configFile = new File(baseDirectory, CONFIG_FILE); + loadConfig(); + } - File configFile = new File(baseDirectory, CONFIG_FILE); - + /** + * Method description + * + */ + public void loadConfig() + { if (configFile.exists()) { config = JAXB.unmarshal(configFile, HgConfig.class); - - if (config.getConfigDirectory() == null) - { - File configDirectory = new File(baseDirectory, DEFAULT_CONFIGPATH); - - config.setConfigDirectory(configDirectory); - } } } @@ -213,6 +213,15 @@ public class HgRepositoryHandler implements RepositoryHandler } } + /** + * Method description + * + */ + public void storeConfig() + { + JAXB.marshal(config, configFile); + } + //~--- get methods ---------------------------------------------------------- /** @@ -285,6 +294,17 @@ public class HgRepositoryHandler implements RepositoryHandler return repositories; } + /** + * Method description + * + * + * @return + */ + public HgConfig getConfig() + { + return config; + } + /** * Method description * @@ -309,6 +329,19 @@ public class HgRepositoryHandler implements RepositoryHandler return config != null; } + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param config + */ + public void setConfig(HgConfig config) + { + this.config = config; + } + //~--- methods -------------------------------------------------------------- /** @@ -501,4 +534,7 @@ public class HgRepositoryHandler implements RepositoryHandler /** Field description */ private HgConfig config; + + /** Field description */ + private File configFile; } diff --git a/plugins/scm-hg-plugin/src/main/resources/META-INF/services/sonia.scm.ScmWebPlugin b/plugins/scm-hg-plugin/src/main/resources/META-INF/services/sonia.scm.ScmWebPlugin new file mode 100644 index 0000000000..3797a95535 --- /dev/null +++ b/plugins/scm-hg-plugin/src/main/resources/META-INF/services/sonia.scm.ScmWebPlugin @@ -0,0 +1 @@ +sonia.scm.HgPlugin \ No newline at end of file diff --git a/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js b/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js new file mode 100644 index 0000000000..3089378655 --- /dev/null +++ b/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js @@ -0,0 +1,6 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index ab593f2b9a..a8e5c37171 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -31,17 +31,17 @@ 1.0-SNAPSHOT + + sonia.scm + scm-web-api + 1.0-SNAPSHOT + + sonia.scm.plugins scm-hg-plugin 1.0-SNAPSHOT - - - com.sun.jersey - jersey-core - ${jersey.version} - com.sun.jersey @@ -67,18 +67,6 @@ ${jersey.version} - - com.google.inject - guice - ${guice.version} - - - - com.google.inject.extensions - guice-servlet - ${guice.version} - - net.sf.ehcache ehcache-core @@ -95,6 +83,21 @@ org.mortbay.jetty maven-jetty-plugin 6.1.25 + + + + org.apache.commons.logging.Log + org.apache.commons.logging.impl.Jdk14Logger + + + + + + commons-logging + commons-logging + 1.1.1 + + diff --git a/scm-webapp/src/main/java/sonia/scm/ContextListener.java b/scm-webapp/src/main/java/sonia/scm/ContextListener.java index 7865102e60..2486b9f802 100644 --- a/scm-webapp/src/main/java/sonia/scm/ContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ContextListener.java @@ -11,27 +11,20 @@ package sonia.scm; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.Module; import com.google.inject.servlet.GuiceServletContextListener; -import com.google.inject.servlet.ServletModule; +import javax.servlet.ServletContextEvent; -import sonia.scm.api.rest.UriExtensionsConfig; -import sonia.scm.filter.GZipFilter; -import sonia.scm.filter.SecurityFilter; -import sonia.scm.filter.StaticResourceFilter; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.security.Authenticator; -import sonia.scm.security.DemoAuthenticator; +import sonia.scm.util.Util; //~--- JDK imports ------------------------------------------------------------ -import com.sun.jersey.api.core.PackagesResourceConfig; -import com.sun.jersey.api.core.ResourceConfig; -import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; -import com.sun.jersey.spi.container.servlet.ServletContainer; - -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -40,32 +33,18 @@ import java.util.Map; public class ContextListener extends GuiceServletContextListener { - /** Field description */ - public static final String PATTERN_PAGE = "*.html"; + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) + { + Logger logger = Logger.getLogger(""); + logger.setLevel(Level.ALL); + ConsoleHandler handler = new ConsoleHandler(); + handler.setLevel(Level.ALL); + logger.addHandler( handler ); + super.contextInitialized(servletContextEvent); + } - /** Field description */ - public static final String PATTERN_RESTAPI = "/api/rest/*"; - /** Field description */ - public static final String PATTERN_SCRIPT = "*.js"; - - /** Field description */ - public static final String PATTERN_STYLESHEET = "*.css"; - - /** Field description */ - public static final String REST_PACKAGE = "sonia.scm.api.rest"; - - /** Field description */ - public static final String[] PATTERN_STATIC_RESOURCES = new String[] { - PATTERN_SCRIPT, - PATTERN_STYLESHEET, "*.jpg", "*.gif", "*.png" }; - - /** Field description */ - public static final String[] PATTERN_COMPRESSABLE = new String[] { - PATTERN_SCRIPT, - PATTERN_STYLESHEET, "*.json", "*.xml", "*.txt" }; - - //~--- get methods ---------------------------------------------------------- /** * Method description @@ -76,41 +55,25 @@ public class ContextListener extends GuiceServletContextListener @Override protected Injector getInjector() { - return Guice.createInjector(new ServletModule() + ScmWebPluginContext webPluginContext = new ScmWebPluginContext(); + List plugins = webPluginContext.getPlugins(); + List modules = new ArrayList(); + + modules.add(new ScmServletModule(webPluginContext)); + + if (Util.isNotEmpty(plugins)) { - @Override - protected void configureServlets() + for (ScmWebPlugin plugin : plugins) { - SCMContextProvider context = SCMContext.getContext(); + Module[] moduleArray = plugin.getModules(); - bind(Authenticator.class).to(DemoAuthenticator.class); - bind(SCMContextProvider.class).toInstance(context); - bind(RepositoryManager.class).toInstance( - context.getRepositoryManager()); - - // filters - filter(PATTERN_PAGE, - PATTERN_STATIC_RESOURCES).through(StaticResourceFilter.class); - filter(PATTERN_PAGE, PATTERN_COMPRESSABLE).through(GZipFilter.class); - filter(PATTERN_RESTAPI).through(SecurityFilter.class); - - // jersey - Map params = new HashMap(); - - /* - * params.put("com.sun.jersey.spi.container.ContainerRequestFilters", - * "com.sun.jersey.api.container.filter.LoggingFilter"); - * params.put("com.sun.jersey.spi.container.ContainerResponseFilters", - * "com.sun.jersey.api.container.filter.LoggingFilter"); - * params.put("com.sun.jersey.config.feature.Trace", "true"); - * params.put("com.sun.jersey.config.feature.TracePerRequest", "true"); - */ - params.put(ResourceConfig.FEATURE_REDIRECT, Boolean.TRUE.toString()); - params.put(ServletContainer.RESOURCE_CONFIG_CLASS, - UriExtensionsConfig.class.getName()); - params.put(PackagesResourceConfig.PROPERTY_PACKAGES, REST_PACKAGE); - serve(PATTERN_RESTAPI).with(GuiceContainer.class, params); + if (Util.isNotEmpty(moduleArray)) + { + modules.addAll(Arrays.asList(moduleArray)); + } } - }); + } + + return Guice.createInjector(modules); } } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java new file mode 100644 index 0000000000..c8789904b8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -0,0 +1,128 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.servlet.ServletModule; + +import sonia.scm.api.rest.UriExtensionsConfig; +import sonia.scm.filter.GZipFilter; +import sonia.scm.filter.SecurityFilter; +import sonia.scm.filter.StaticResourceFilter; +import sonia.scm.plugin.ScriptResourceServlet; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.security.Authenticator; +import sonia.scm.security.DemoAuthenticator; + +//~--- JDK imports ------------------------------------------------------------ + +import com.sun.jersey.api.core.PackagesResourceConfig; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; +import com.sun.jersey.spi.container.servlet.ServletContainer; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Sebastian Sdorra + */ +public class ScmServletModule extends ServletModule +{ + + /** Field description */ + public static final String PATTERN_PAGE = "*.html"; + + /** Field description */ + public static final String PATTERN_PLUGIN_SCRIPT = "/plugins/sonia.plugin.js"; + + /** Field description */ + public static final String PATTERN_RESTAPI = "/api/rest/*"; + + /** Field description */ + public static final String PATTERN_SCRIPT = "*.js"; + + /** Field description */ + public static final String PATTERN_STYLESHEET = "*.css"; + + /** Field description */ + public static final String REST_PACKAGE = "sonia.scm.api.rest"; + + /** Field description */ + public static final String[] PATTERN_STATIC_RESOURCES = new String[] { + PATTERN_SCRIPT, + PATTERN_STYLESHEET, "*.jpg", "*.gif", "*.png" }; + + /** Field description */ + public static final String[] PATTERN_COMPRESSABLE = new String[] { + PATTERN_SCRIPT, + PATTERN_STYLESHEET, "*.json", "*.xml", "*.txt" }; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param webPluginContext + */ + ScmServletModule(ScmWebPluginContext webPluginContext) + { + this.webPluginContext = webPluginContext; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Override + protected void configureServlets() + { + SCMContextProvider context = SCMContext.getContext(); + + bind(Authenticator.class).to(DemoAuthenticator.class); + bind(SCMContextProvider.class).toInstance(context); + bind(RepositoryManager.class).toInstance(context.getRepositoryManager()); + bind(ScmWebPluginContext.class).toInstance(webPluginContext); + + // filters + filter(PATTERN_PAGE, + PATTERN_STATIC_RESOURCES).through(StaticResourceFilter.class); + filter(PATTERN_PAGE, PATTERN_COMPRESSABLE).through(GZipFilter.class); + filter(PATTERN_RESTAPI).through(SecurityFilter.class); + + // plugin resources + serve(PATTERN_PLUGIN_SCRIPT).with(ScriptResourceServlet.class); + + // jersey + Map params = new HashMap(); + + /* + * params.put("com.sun.jersey.spi.container.ContainerRequestFilters", + * "com.sun.jersey.api.container.filter.LoggingFilter"); + * params.put("com.sun.jersey.spi.container.ContainerResponseFilters", + * "com.sun.jersey.api.container.filter.LoggingFilter"); + * params.put("com.sun.jersey.config.feature.Trace", "true"); + * params.put("com.sun.jersey.config.feature.TracePerRequest", "true"); + */ + params.put(ResourceConfig.FEATURE_REDIRECT, Boolean.TRUE.toString()); + params.put(ServletContainer.RESOURCE_CONFIG_CLASS, + UriExtensionsConfig.class.getName()); + params.put(PackagesResourceConfig.PROPERTY_PACKAGES, REST_PACKAGE); + serve(PATTERN_RESTAPI).with(GuiceContainer.class, params); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private ScmWebPluginContext webPluginContext; +} diff --git a/scm-webapp/src/main/java/sonia/scm/filter/StaticResourceFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/StaticResourceFilter.java index d196a2e9fc..5463360f24 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/StaticResourceFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/StaticResourceFilter.java @@ -75,7 +75,7 @@ public class StaticResourceFilter extends HttpFilter String uri = request.getRequestURI(); File resource = getResourceFile(request, uri); - if (resource.exists()) + if (!resource.exists()) { WebUtil.addETagHeader(response, resource); WebUtil.addStaticCacheControls(response, WebUtil.TIME_YEAR); @@ -100,7 +100,7 @@ public class StaticResourceFilter extends HttpFilter } else { - response.sendError(HttpServletResponse.SC_NOT_FOUND); + chain.doFilter(request, response); } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/AbstractResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/plugin/AbstractResourceServlet.java new file mode 100644 index 0000000000..177aa68c27 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/AbstractResourceServlet.java @@ -0,0 +1,186 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.util.Util; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class AbstractResourceServlet extends HttpServlet +{ + + /** Field description */ + public static final String DEFAULT_CHARSET = "UTF-8"; + + /** Field description */ + private static final long serialVersionUID = -7703282364120349053L; + + public AbstractResourceServlet() + { + System.out.println("CONSTRUCT !!!"); + } + + //~--- methods -------------------------------------------------------------- + + + + /** + * Method description + * + * + * @param stream + * + * @throws IOException + * @throws ServletException + */ + protected abstract void appendResources(OutputStream stream) + throws ServletException, IOException; + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + protected abstract String getContentType(); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @throws ServletException + */ + @Override + public void init() throws ServletException + { + System.out.println("INIT !!!"); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + try + { + appendResources(output); + } + catch (IOException ex) + { + throw new ServletException(ex); + } + finally + { + Util.close(output); + } + + this.content = output.toByteArray(); + } + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + processRequest(request, response); + } + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + processRequest(request, response); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + protected String getCharacterEncoding() + { + return DEFAULT_CHARSET; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + private void processRequest(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + System.out.println( "JA !!!" ); + + response.setCharacterEncoding(getCharacterEncoding()); + response.setContentType(getContentType()); + response.setContentLength(content.length); + + OutputStream output = response.getOutputStream(); + + try + { + output.write(content); + } + finally + { + Util.close(output); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private byte[] content; +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ScriptResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/plugin/ScriptResourceServlet.java new file mode 100644 index 0000000000..b344048026 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ScriptResourceServlet.java @@ -0,0 +1,123 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import sonia.scm.ScmWebPlugin; +import sonia.scm.ScmWebPluginContext; +import sonia.scm.util.Util; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import java.util.List; + +import javax.servlet.ServletException; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class ScriptResourceServlet extends AbstractResourceServlet +{ + + /** Field description */ + public static final String CONTENT_TYPE = "text/javascript"; + + /** Field description */ + private static final long serialVersionUID = -5769146163848821050L; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param stream + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void appendResources(OutputStream stream) + throws ServletException, IOException + { + stream.write( + "function sayPluginHello(){ alert('Plugin Hello !'); }".concat( + System.getProperty("line.separator")).getBytes()); + + List plugins = webPluginContext.getPlugins(); + + if (Util.isNotEmpty(plugins)) + { + for (ScmWebPlugin plugin : plugins) + { + appendResource(stream, plugin); + } + } + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + protected String getContentType() + { + return CONTENT_TYPE; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param stream + * @param plugin + * + * @throws IOException + * @throws ServletException + */ + private void appendResource(OutputStream stream, ScmWebPlugin plugin) + throws ServletException, IOException + { + InputStream input = plugin.getScript(); + + if (input != null) + { + try + { + Util.copy(input, stream); + } + finally + { + Util.close(input); + } + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Inject + private ScmWebPluginContext webPluginContext; +} diff --git a/scm-webapp/src/main/webapp/index.html b/scm-webapp/src/main/webapp/index.html index d58c0e7560..f9b7282209 100644 --- a/scm-webapp/src/main/webapp/index.html +++ b/scm-webapp/src/main/webapp/index.html @@ -19,6 +19,7 @@ + SCM-WebAPP