merge with feature/ui-extensions branch

This commit is contained in:
Sebastian Sdorra
2018-08-30 12:15:17 +02:00
1508 changed files with 29291 additions and 203236 deletions

View File

@@ -1,76 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
/**
* This class represents a web resource (Stylesheet or JavaScript file).
*
* @author Sebastian Sdorra
* @since 1.12
*/
public interface Resource
{
/**
* Copies the content of the resource to the given {@link OutputStream}.
*
*
* @param output stream to copy the content of the resource
*
* @throws IOException
*/
public void copyTo(OutputStream output) throws IOException;
//~--- get methods ----------------------------------------------------------
/**
* Returns the name of the resource.
*
*
* @return name of the resource
*/
public String getName();
/**
* Returns the type of the resource.
*
*
* @return type of resource
*/
public ResourceType getType();
}

View File

@@ -1,75 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.ExtensionPoint;
//~--- JDK imports ------------------------------------------------------------
import java.net.URL;
/**
*
* @author Sebastian Sdorra
*/
@ExtensionPoint
public interface ResourceHandler
{
/**
* Method description
*
*
* @return
*/
public String getName();
/**
* Method description
*
*
* @return
*/
public URL getResource();
/**
* Method description
*
*
* @return
*/
public ResourceType getType();
}

View File

@@ -1,73 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.Serializable;
import java.util.Comparator;
/**
*
* @author Sebastian Sdorra
*/
public class ResourceHandlerComparator
implements Comparator<ResourceHandler>, Serializable
{
/** Field description */
private static final long serialVersionUID = -1760229246326556762L;
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param handler
* @param otherHandler
*
* @return
*/
@Override
public int compare(ResourceHandler handler, ResourceHandler otherHandler)
{
return Util.compare(handler.getName(), otherHandler.getName());
}
}

View File

@@ -1,70 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
/**
* This class collects and manages {@link Resource}
* which are used by the web interface.
*
* @author Sebastian Sdorra
* @since 1.12
*/
public interface ResourceManager
{
/**
* Returns the resource with given name and type or
* null if no such resource exists.
*
*
* @param type type of the resource
* @param name name of the resource
*
* @return the resource with given name and type
*/
public Resource getResource(ResourceType type, String name);
/**
* Returns the resources of the given type.
*
*
* @param type type of the resources to return
*
* @return resources of the given type
*/
public List<Resource> getResources(ResourceType type);
}

View File

@@ -1,79 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.Serializable;
import java.util.Comparator;
/**
* Compare {@link Resource} objects by its name.
*
* @author Sebastian Sdorra
* @since 1.16
*/
public class ResourceNameComparator
implements Comparator<Resource>, Serializable
{
/** Field description */
public static final ResourceNameComparator INSTANCE =
new ResourceNameComparator();
/** Field description */
private static final long serialVersionUID = 3474356901608301437L;
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resource
* @param otherResource
*
* @return
*/
@Override
public int compare(Resource resource, Resource otherResource)
{
return Util.compare(resource.getName(), otherResource.getName());
}
}

View File

@@ -1,102 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
/**
* This class represents the type of {@link Resource}.
*
* @author Sebastian Sdorra
*/
public enum ResourceType
{
/**
* Resource type for javascript resources
*/
SCRIPT("text/javascript", "js"),
/**
* Resource type for stylesheet (css) resources
*/
STYLESHEET("text/css", "css");
/**
* Constructs a new resource type
*
*
* @param contentType content type of the resource type
* @param extension file extension of the resource type
*/
private ResourceType(String contentType, String extension)
{
this.contentType = contentType;
this.extension = extension;
}
//~--- get methods ----------------------------------------------------------
/**
* Returns the content type of the resource type.
*
*
* @return content type of the resource type
*
* @since 1.12
*/
public String getContentType()
{
return contentType;
}
/**
* Returns the file extension of the resource type.
*
*
* @return file extension of the resource type
*
* @since 1.12
*/
public String getExtension()
{
return extension;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final String contentType;
/** Field description */
private final String extension;
}

View File

@@ -25,6 +25,9 @@ public class VndMediaType {
public static final String CONFIG = PREFIX + "config" + 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_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX;
public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX;
public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX;
public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX;
public static final String ME = PREFIX + "me" + SUFFIX; public static final String ME = PREFIX + "me" + SUFFIX;
public static final String SOURCE = PREFIX + "source" + SUFFIX; public static final String SOURCE = PREFIX + "source" + SUFFIX;

View File

@@ -1,141 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.Test;
import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import java.net.URL;
import java.util.Arrays;
/**
*
* @author Sebastian Sdorra
*/
public class ResourceHandlerComparatorTest
{
/**
* Method description
*
*/
@Test
public void testCompare()
{
ResourceHandler[] handlers = new ResourceHandler[4];
handlers[0] = new DummyResourceHandler("xyz");
handlers[1] = new DummyResourceHandler("abc");
handlers[2] = new DummyResourceHandler(null);
handlers[3] = new DummyResourceHandler("mno");
Arrays.sort(handlers, new ResourceHandlerComparator());
assertEquals("abc", handlers[0].getName());
assertEquals("mno", handlers[1].getName());
assertEquals("xyz", handlers[2].getName());
assertEquals(null, handlers[3].getName());
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 2011-01-18
* @author Sebastian Sdorra
*/
private static class DummyResourceHandler implements ResourceHandler
{
/**
* Constructs ...
*
*
* @param name
*/
public DummyResourceHandler(String name)
{
this.name = name;
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
@Override
public URL getResource()
{
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Method description
*
*
* @return
*/
@Override
public ResourceType getType()
{
throw new UnsupportedOperationException("Not supported yet.");
}
//~--- fields -------------------------------------------------------------
/** Field description */
private String name;
}
}

View File

@@ -136,6 +136,39 @@
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId>
<version>2.0.1</version>
<configuration>
<node>
<version>8.11.3</version>
</node>
<pkgManager>
<type>YARN</type>
<version>1.7.0</version>
</pkgManager>
<failOnMissingPackageJson>false</failOnMissingPackageJson>
<script>build</script>
</configuration>
<executions>
<execution>
<id>install</id>
<phase>process-resources</phase>
<goals>
<goal>install</goal>
</goals>
</execution>
<execution>
<id>build</id>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>

View File

@@ -0,0 +1,13 @@
{
"name": "@scm-manager/scm-git-plugin",
"main": "src/main/js/index.js",
"scripts": {
"build": "ui-bundler plugin"
},
"dependencies": {
"@scm-manager/ui-extensions": "^0.0.7"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.7"
}
}

View File

@@ -0,0 +1,16 @@
//@flow
import React from 'react';
type Props = {
};
class GitAvatar extends React.Component<Props> {
render() {
// TODO we have to use Image from ui-components
return <img src="/scm/images/git-logo.png" alt="Git Logo" />;
}
}
export default GitAvatar;

View File

@@ -0,0 +1,54 @@
//@flow
import React from 'react';
// TODO flow types ???
type Props = {
repository: Object
}
class ProtocolInformation extends React.Component<Props> {
render() {
const { repository } = this.props;
if (!repository._links.httpProtocol) {
return null;
}
return (
<div>
<h4>Clone the repository</h4>
<pre>
<code>git clone {repository._links.httpProtocol.href}</code>
</pre>
<h4>Create a new repository</h4>
<pre>
<code>
git init {repository.name}
<br />
echo "# {repository.name}" > README.md
<br />
git add README.md
<br />
git commit -m "added readme"
<br />
git remote add origin {repository._links.httpProtocol.href}
<br />
git push -u origin master
<br />
</code>
</pre>
<h4>Push an existing repository</h4>
<pre>
<code>
git remote add origin {repository._links.httpProtocol.href}
<br />
git push -u origin master
<br />
</code>
</pre>
</div>
);
}
}
export default ProtocolInformation;

View File

@@ -0,0 +1,10 @@
import { binder } from "@scm-manager/ui-extensions";
import ProtocolInformation from './ProtocolInformation';
import GitAvatar from './GitAvatar';
const gitPredicate = (props: Object) => {
return props.repository && props.repository.type === "git";
};
binder.bind("repos.repository-details.information", ProtocolInformation, gitPredicate);
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);

View File

@@ -60,8 +60,4 @@
<min-version>${project.parent.version}</min-version> <min-version>${project.parent.version}</min-version>
</conditions> </conditions>
<resources>
<script>/sonia/scm/git.config.js</script>
</resources>
</plugin> </plugin>

View File

@@ -1,232 +0,0 @@
/*
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
Ext.ns("Sonia.git");
Sonia.git.ConfigPanel = Ext.extend(Sonia.config.SimpleConfigForm, {
// labels
titleText: 'Git Settings',
repositoryDirectoryText: 'Repository directory',
gcExpressionText: 'Git GC Cron Expression',
disabledText: 'Disabled',
// helpTexts
repositoryDirectoryHelpText: 'Location of the Git repositories.',
// TODO i18n
gcExpressionHelpText: '<p>Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.</p>\n\
<table>\n\
<tr><th><b>SECOND</b></th><td>Seconds within the minute (059)</td></tr>\n\
<tr><th><b>MINUTE</b></th><td>Minutes within the hour (059)</td></tr>\n\
<tr><th><b>HOUR</b></th><td>The hour of the day (023)</td></tr>\n\
<tr><th><b>DAYOFMONTH</b></th><td>The day of the month (131)</td></tr>\n\
<tr><th><b>MONTH</b></th><td>The month (112)</td></tr>\n\
<tr><th><b>DAYOFWEEK</b></th><td>The day of the week (MON, TUE, WED, THU, FRI, SAT, SUN)</td></tr>\n\
</table>\n\
<p>E.g.: To run the task on every sunday at two o\'clock in the morning: 0 0 2 ? * SUN</p>\n\
<p>For more informations please have a look at <a href="http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/crontrigger.html">Quartz CronTrigger</a></p>',
disabledHelpText: 'Enable or disable the Git plugin.\n\
Note you have to reload the page, after changing this value.',
initComponent: function(){
var config = {
title : this.titleText,
configUrl: restUrl + 'config/repositories/git',
items : [{
xtype: 'textfield',
name: 'repositoryDirectory',
fieldLabel: this.repositoryDirectoryText,
helpText: this.repositoryDirectoryHelpText,
allowBlank : false
},{
xtype: 'textfield',
name: 'gc-expression',
fieldLabel: this.gcExpressionText,
helpText: this.gcExpressionHelpText,
allowBlank : true
},{
xtype: 'checkbox',
name: 'disabled',
fieldLabel: this.disabledText,
inputValue: 'true',
helpText: this.disabledHelpText
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.git.ConfigPanel.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg("gitConfigPanel", Sonia.git.ConfigPanel);
// add default branch chooser to settings panel
Sonia.git.GitSettingsFormPanel = Ext.extend(Sonia.repository.SettingsFormPanel, {
defaultBranchText: 'Default Branch',
defaultBranchHelpText: 'The default branch which is show first on source or commit view.',
modifyDefaultConfig: function(config){
if (this.item) {
var position = -1;
for ( var i=0; i<config.items.length; i++ ) {
var field = config.items[i];
if (field.name === 'public') {
position = i;
break;
}
}
var defaultBranchComboxBox = {
fieldLabel: this.defaultBranchText,
name: 'defaultBranch',
repositoryId: this.item.id,
value: this.getDefaultBranch(this.item),
useNameAsValue: true,
xtype: 'repositoryBranchComboBox',
helpText: this.defaultBranchHelpText
};
if (position >= 0) {
config.items.splice(position, 0, defaultBranchComboxBox);
} else {
config.items.push(defaultBranchComboxBox);
}
}
},
getDefaultBranch: function(item){
if (item.properties) {
for ( var i=0; i<item.properties.length; i++ ) {
var prop = item.properties[i];
if (prop.key === 'git.default-branch') {
return prop.value;
}
}
}
return undefined;
},
setDefaultBranch: function(item, defaultBranch){
if (!item.properties) {
item.properties = [{
key: 'git.default-branch',
value: defaultBranch
}];
} else {
var found = false;
for ( var i=0; i<item.properties.length; i++ ) {
var prop = item.properties[i];
if (prop.key === 'git.default-branch') {
prop.value = defaultBranch;
found = true;
break;
}
}
if (!found) {
item.properties.push({
key: 'git.default-branch',
value: defaultBranch
});
}
}
},
prepareUpdate: function(item) {
if (item.defaultBranch) {
var defaultBranch = item.defaultBranch;
delete item.defaultBranch;
this.setDefaultBranch(item, defaultBranch);
}
}
});
Ext.reg("gitSettingsForm", Sonia.git.GitSettingsFormPanel);
// i18n
if ( i18n && i18n.country === 'de' ){
Ext.override(Sonia.git.ConfigPanel, {
// labels
titleText: 'Git Einstellungen',
repositoryDirectoryText: 'Repository-Verzeichnis',
disabledText: 'Deaktivieren',
// helpTexts
repositoryDirectoryHelpText: 'Verzeichnis der Git-Repositories.',
disabledHelpText: 'Aktivieren oder deaktivieren des Git Plugins.\n\
Die Seite muss neu geladen werden wenn dieser Wert geändert wird.'
});
Ext.override(Sonia.git.GitSettingsFormPanel, {
// labels
defaultBranchText: 'Standard Branch',
// helpTexts
defaultBranchHelpText: 'Der Standard Branch wird für die Source und Commit Ansicht verwendet, \n\
wenn kein anderer Branch eingestellt wurde.'
});
}
// register information panel
initCallbacks.push(function(main){
main.registerInfoPanel('git', {
checkoutTemplate: 'git clone <a href="{0}" target="_blank">{0}</a>',
xtype: 'repositoryExtendedInfoPanel'
});
main.registerSettingsForm('git', {
xtype: 'gitSettingsForm'
});
});
// register panel
registerConfigPanel({
xtype : 'gitConfigPanel'
});
// register type icon
Sonia.repository.typeIcons['git'] = 'resources/images/icons/16x16/git.png';

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"name": "@scm-manager/scm-hg-plugin",
"main": "src/main/js/index.js",
"scripts": {
"build": "ui-bundler plugin"
},
"dependencies": {
"@scm-manager/ui-extensions": "^0.0.7"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.7"
}
}

View File

@@ -0,0 +1,16 @@
//@flow
import React from 'react';
type Props = {
};
class HgAvatar extends React.Component<Props> {
render() {
// TODO we have to use Image from ui-components
return <img src="/scm/images/hg-logo.png" alt="Mercurial Logo" />;
}
}
export default HgAvatar;

View File

@@ -0,0 +1,60 @@
//@flow
import React from 'react';
// TODO flow types ???
type Props = {
repository: Object
}
class ProtocolInformation extends React.Component<Props> {
render() {
const { repository } = this.props;
if (!repository._links.httpProtocol) {
return null;
}
return (
<div>
<h4>Clone the repository</h4>
<pre>
<code>hg clone {repository._links.httpProtocol.href}</code>
</pre>
<h4>Create a new repository</h4>
<pre>
<code>
hg init {repository.name}
<br />
echo "[paths]" > .hg/hgrc
<br />
echo "default = {repository._links.httpProtocol.href}" > .hg/hgrc
<br />
echo "# {repository.name}" > README.md
<br />
hg add README.md
<br />
hg commit -m "added readme"
<br />
<br />
hg push
<br />
</code>
</pre>
<h4>Push an existing repository</h4>
<pre>
<code>
# add the repository url as default to your .hg/hgrc e.g:
<br />
default = {repository._links.httpProtocol.href}
<br />
# push to remote repository
<br />
hg push
</code>
</pre>
</div>
);
}
}
export default ProtocolInformation;

View File

@@ -0,0 +1,10 @@
import { binder } from "@scm-manager/ui-extensions";
import ProtocolInformation from './ProtocolInformation';
import HgAvatar from './HgAvatar';
const hgPredicate = (props: Object) => {
return props.repository && props.repository.type === "hg";
};
binder.bind("repos.repository-details.information", ProtocolInformation, hgPredicate);
binder.bind("repos.repository-avatar", HgAvatar, hgPredicate);

View File

@@ -29,7 +29,7 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
http://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager
jo
--> -->
@@ -61,9 +61,4 @@
<min-version>${project.parent.version}</min-version> <min-version>${project.parent.version}</min-version>
</conditions> </conditions>
<resources>
<script>/sonia/scm/hg.config.js</script>
<script>/sonia/scm/hg.config-wizard.js</script>
</resources>
</plugin> </plugin>

View File

@@ -1,449 +0,0 @@
/*
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
Ext.ns("Sonia.hg");
Sonia.hg.ConfigWizard = Ext.extend(Ext.Window,{
hgConfig: null,
title: 'Mercurial Configuration Wizard',
initComponent: function(){
this.addEvents('finish');
var config = {
title: this.title,
layout: 'fit',
width: 420,
height: 140,
closable: true,
resizable: true,
plain: true,
border: false,
modal: true,
bodyCssClass: 'x-panel-mc',
items: [{
id: 'hgConfigWizardPanel',
xtype: 'hgConfigWizardPanel',
hgConfig: this.hgConfig,
listeners: {
finish: {
fn: this.onFinish,
scope: this
}
}
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.hg.ConfigWizard.superclass.initComponent.apply(this, arguments);
},
onFinish: function(config){
this.fireEvent('finish', config);
this.close();
}
});
Sonia.hg.InstallationJsonReader = function(){
this.RecordType = Ext.data.Record.create([{
name: "path",
mapping: "path",
type: "string"
}]);
};
Ext.extend(Sonia.hg.InstallationJsonReader, Ext.data.JsonReader, {
readRecords: function(o){
this.jsonData = o;
if (debug){
console.debug('read installation data from json');
console.debug(o);
}
var records = [];
var paths = o.path;
for ( var i=0; i<paths.length; i++ ){
records.push(new this.RecordType({
'path': paths[i]
}));
}
return {
success: true,
records: records,
totalRecords: records.length
};
}
});
Sonia.hg.ConfigWizardPanel = Ext.extend(Ext.Panel,{
hgConfig: null,
packageTemplate: '<tpl for="."><div class="x-combo-list-item">\
{id} (hg: {hg-version}, py: {python-version}, size: {size:fileSize})\
</div></tpl>',
// text
backText: 'Back',
nextText: 'Next',
finishText: 'Finish',
configureLocalText: 'Configure local installation',
configureRemoteText: 'Download and install',
loadingText: 'Loading ...',
hgInstallationText: 'Mercurial Installation',
pythonInstallationText: 'Python Installation',
hgPackageText: 'Mercurial Package',
errorTitleText: 'Error',
packageInstallationFailedText: 'Package installation failed',
installPackageText: 'install mercurial package {0}',
initComponent: function(){
this.addEvents('finish');
var packageStore = new Ext.data.JsonStore({
storeId: 'pkgStore',
proxy: new Ext.data.HttpProxy({
url: restUrl + 'config/repositories/hg/packages',
disableCaching: false
}),
fields: [ 'id', 'hg-version', 'python-version', 'size' ],
root: 'package',
listeners: {
load: {
fn: this.checkIfPackageAvailable,
scope: this
}
}
});
var hgInstallationStore = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'config/repositories/hg/installations/hg'
}),
fields: [ 'path' ],
reader: new Sonia.hg.InstallationJsonReader(),
autoLoad: true,
autoDestroy: true
});
var pythonInstallationStore = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'config/repositories/hg/installations/python'
}),
fields: [ 'path' ],
reader: new Sonia.hg.InstallationJsonReader(),
autoLoad: true,
autoDestroy: true
});
var config = {
layout: 'card',
activeItem: 0,
bodyStyle: 'padding: 5px',
defaults: {
bodyCssClass: 'x-panel-mc',
border: false,
labelWidth: 120,
width: 250
},
bbar: ['->',{
id: 'move-prev',
text: this.backText,
handler: this.navHandler.createDelegate(this, [-1]),
disabled: true,
scope: this
},{
id: 'move-next',
text: this.nextText,
handler: this.navHandler.createDelegate(this, [1]),
scope: this
},{
id: 'finish',
text: this.finishText,
handler: this.applyChanges,
scope: this,
disabled: true
}],
items: [{
id: 'cod',
items: [{
id: 'configureOrDownload',
xtype: 'radiogroup',
name: 'configureOrDownload',
columns: 1,
items: [{
boxLabel: this.configureLocalText,
name: 'cod',
inputValue: 'localInstall',
checked: true
},{
id: 'remoteInstallRadio',
boxLabel: this.configureRemoteText,
name: 'cod',
inputValue: 'remoteInstall',
disabled: true
}]
}],
listeners: {
render: {
fn: function(panel){
panel.body.mask(this.loadingText);
var store = Ext.StoreMgr.lookup('pkgStore');
store.load.defer(100, store);
},
scope: this
}
}
},{
id: 'localInstall',
layout: 'form',
defaults: {
width: 250
},
items: [{
id: 'mercurial',
fieldLabel: this.hgInstallationText,
name: 'mercurial',
xtype: 'combo',
readOnly: false,
triggerAction: 'all',
lazyRender: true,
mode: 'local',
editable: true,
store: hgInstallationStore,
valueField: 'path',
displayField: 'path',
allowBlank: false,
value: this.hgConfig.hgBinary
},{
id: 'python',
fieldLabel: this.pythonInstallationText,
name: 'python',
xtype: 'combo',
readOnly: false,
triggerAction: 'all',
lazyRender: true,
mode: 'local',
editable: true,
store: pythonInstallationStore,
valueField: 'path',
displayField: 'path',
allowBlank: false,
value: this.hgConfig.pythonBinary
}]
},{
id: 'remoteInstall',
layout: 'form',
defaults: {
width: 250
},
items: [{
id: 'package',
fieldLabel: this.hgPackageText,
name: 'package',
xtype: 'combo',
readOnly: false,
triggerAction: 'all',
lazyRender: true,
mode: 'local',
editable: false,
store: packageStore,
valueField: 'id',
displayField: 'id',
allowBlank: false,
tpl: this.packageTemplate,
listeners: {
select: function(){
Ext.getCmp('finish').setDisabled(false);
}
}
}]
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.hg.ConfigWizardPanel.superclass.initComponent.apply(this, arguments);
},
checkIfPackageAvailable: function(store){
Ext.getCmp('cod').body.unmask();
var c = store.getTotalCount();
if ( debug ){
console.debug( "found " + c + " package(s)" );
}
if ( c > 0 ){
Ext.getCmp('remoteInstallRadio').setDisabled(false);
}
},
navHandler: function(direction){
var layout = this.getLayout();
var id = layout.activeItem.id;
var next = -1;
if ( id === 'cod' && direction === 1 ){
var v = Ext.getCmp('configureOrDownload').getValue().getRawValue();
var df = false;
if ( v === 'localInstall' ){
next = 1;
} else if ( v === 'remoteInstall' ){
next = 2;
df = true;
}
Ext.getCmp('move-prev').setDisabled(false);
Ext.getCmp('move-next').setDisabled(true);
Ext.getCmp('finish').setDisabled(df);
}
else if (direction === -1 && (id === 'localInstall' || id === 'remoteInstall')) {
next = 0;
Ext.getCmp('move-prev').setDisabled(true);
Ext.getCmp('move-next').setDisabled(false);
Ext.getCmp('finish').setDisabled(true);
}
if ( next >= 0 ){
layout.setActiveItem(next);
}
},
applyChanges: function(){
var v = Ext.getCmp('configureOrDownload').getValue().getRawValue();
if ( v === 'localInstall' ){
this.applyLocalConfiguration();
} else if ( v === 'remoteInstall' ){
this.applyRemoteConfiguration();
}
},
applyRemoteConfiguration: function(){
if ( debug ){
console.debug( "apply remote configuration" );
}
var pkg = Ext.getCmp('package').getValue();
if ( debug ){
console.debug( 'install mercurial package ' + pkg );
}
var lbox = Ext.MessageBox.show({
title: this.loadingText,
msg: String.format(this.installPackageText, pkg),
width: 300,
wait: true,
animate: true,
progress: true,
closable: false
});
Ext.Ajax.request({
url: restUrl + 'config/repositories/hg/packages/' + pkg,
method: 'POST',
scope: this,
timeout: 900000, // 15min
success: function(){
if ( debug ){
console.debug('package successfully installed');
}
lbox.hide();
this.fireEvent('finish');
},
failure: function(){
if ( debug ){
console.debug('package installation failed');
}
lbox.hide();
Ext.MessageBox.show({
title: this.errorTitleText,
msg: this.packageInstallationFailedText,
buttons: Ext.MessageBox.OK,
icon:Ext.MessageBox.ERROR
});
}
});
},
applyLocalConfiguration: function(){
if ( debug ){
console.debug( "apply remote configuration" );
}
var mercurial = Ext.getCmp('mercurial').getValue();
var python = Ext.getCmp('python').getValue();
if (debug){
console.debug( 'configure mercurial=' + mercurial + " and python=" + python );
}
delete this.hgConfig.pythonPath;
delete this.hgConfig.useOptimizedBytecode;
this.hgConfig.hgBinary = mercurial;
this.hgConfig.pythonBinary = python;
if ( debug ){
console.debug( this.hgConfig );
}
this.fireEvent('finish', this.hgConfig);
}
});
// register xtype
Ext.reg('hgConfigWizardPanel', Sonia.hg.ConfigWizardPanel);
// i18n
if ( i18n && i18n.country === 'de' ){
Ext.override(Sonia.hg.ConfigWizardPanel, {
backText: 'Zurück',
nextText: 'Weiter',
finishText: 'Fertigstellen',
configureLocalText: 'Eine lokale Installation Konfigurieren',
configureRemoteText: 'Herunterladen und installieren',
loadingText: 'Lade ...',
hgInstallationText: 'Mercurial Installation',
pythonInstallationText: 'Python Installation',
hgPackageText: 'Mercurial Package',
errorTitleText: 'Fehler',
packageInstallationFailedText: 'Package Installation fehlgeschlagen',
installPackageText: 'Installiere Mercurial-Package {0}'
});
}

View File

@@ -1,287 +0,0 @@
/*
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
Ext.ns("Sonia.hg");
Sonia.hg.ConfigPanel = Ext.extend(Sonia.config.ConfigForm, {
// labels
titleText: 'Mercurial Settings',
hgBinaryText: 'HG Binary',
pythonBinaryText: 'Python Binary',
pythonPathText: 'Python Module Search Path',
repositoryDirectoryText: 'Repository directory',
useOptimizedBytecodeText: 'Optimized Bytecode (.pyo)',
configWizardText: 'Start Configuration Wizard',
configWizardLabelText: 'Start Configuration Wizard',
encodingText: 'Encoding',
disabledText: 'Disabled',
showRevisionInIdText: 'Show Revision',
// helpText
hgBinaryHelpText: 'Location of Mercurial binary.',
pythonBinaryHelpText: 'Location of Python binary.',
pythonPathHelpText: 'Python Module Search Path (PYTHONPATH).',
repositoryDirectoryHelpText: 'Location of the Mercurial repositories.',
useOptimizedBytecodeHelpText: 'Use the Python "-O" switch.',
encodingHelpText: 'Repository Encoding.',
disabledHelpText: 'Enable or disable the Mercurial plugin. \n\
Note you have to reload the page, after changing this value.',
showRevisionInIdHelpText: 'Show revision as part of the node id. Note: \n\
You have to restart the ApplicationServer to affect cached changesets.',
initComponent: function(){
var config = {
title : this.titleText,
items : [{
xtype : 'textfield',
fieldLabel : this.hgBinaryText,
name : 'hgBinary',
allowBlank : false,
helpText: this.hgBinaryHelpText
},{
xtype : 'textfield',
fieldLabel : this.pythonBinaryText,
name : 'pythonBinary',
allowBlank : false,
helpText: this.pythonBinaryHelpText
},{
xtype : 'textfield',
fieldLabel : this.pythonPathText,
name : 'pythonPath',
helpText: this.pythonPathHelpText
},{
xtype: 'textfield',
name: 'repositoryDirectory',
fieldLabel: this.repositoryDirectoryText,
helpText: this.repositoryDirectoryHelpText,
allowBlank : false
},{
xtype: 'textfield',
name: 'encoding',
fieldLabel: this.encodingText,
helpText: this.encodingHelpText,
allowBlank : false
},{
xtype: 'checkbox',
name: 'useOptimizedBytecode',
fieldLabel: this.useOptimizedBytecodeText,
inputValue: 'true',
helpText: this.useOptimizedBytecodeHelpText
},{
xtype: 'checkbox',
name: 'showRevisionInId',
fieldLabel: this.showRevisionInIdText,
inputValue: 'true',
helpText: this.showRevisionInIdHelpText
},{
xtype: 'checkbox',
name: 'disabled',
fieldLabel: this.disabledText,
inputValue: 'true',
helpText: this.disabledHelpText
},{
xtype: 'button',
text: this.configWizardText,
fieldLabel: this.configWizardLabelText,
handler: function(){
var config = this.getForm().getValues();
var wizard = new Sonia.hg.ConfigWizard({
hgConfig: config
});
wizard.on('finish', function(config){
var self = Ext.getCmp('hgConfigForm');
if ( config ){
if (debug){
console.debug( 'load config from wizard and submit to server' );
}
self.loadConfig( self.el, 'config/repositories/hg/auto-configuration', 'POST', config );
} else {
if (debug){
console.debug( 'reload config' );
}
self.onLoad(self.el);
}
}, this);
wizard.show();
},
scope: this
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.hg.ConfigPanel.superclass.initComponent.apply(this, arguments);
},
onSubmit: function(values){
this.el.mask(this.submitText);
Ext.Ajax.request({
url: restUrl + 'config/repositories/hg',
method: 'POST',
jsonData: values,
scope: this,
disableCaching: true,
success: function(){
this.el.unmask();
},
failure: function(){
this.el.unmask();
alert('failure');
}
});
},
onLoad: function(el){
this.loadConfig(el, 'config/repositories/hg', 'GET');
},
loadConfig: function(el, url, method, config){
var tid = setTimeout( function(){ el.mask(this.loadingText); }, 100);
Ext.Ajax.request({
url: restUrl + url,
method: method,
jsonData: config,
scope: this,
disableCaching: true,
success: function(response){
var obj = Ext.decode(response.responseText);
this.load(obj);
clearTimeout(tid);
el.unmask();
},
failure: function(){
el.unmask();
clearTimeout(tid);
alert('failure');
}
});
}
});
Ext.reg("hgConfigPanel", Sonia.hg.ConfigPanel);
// i18n
if ( i18n && i18n.country === 'de' ){
Ext.override(Sonia.hg.ConfigPanel, {
// labels
titleText: 'Mercurial Einstellungen',
hgBinaryText: 'HG Pfad',
pythonBinaryText: 'Python Pfad',
pythonPathText: 'Python Modul Suchpfad',
repositoryDirectoryText: 'Repository-Verzeichnis',
useOptimizedBytecodeText: 'Optimierter Bytecode (.pyo)',
autoConfigText: 'Einstellungen automatisch laden',
autoConfigLabelText: 'Automatische Einstellung',
configWizardText: 'Konfigurations-Assistenten starten',
configWizardLabelText: 'Konfigurations-Assistent',
disabledText: 'Deaktivieren',
showRevisionInIdText: 'Zeige Revision an',
// helpText
hgBinaryHelpText: 'Pfad zum "hg" Befehl.',
pythonBinaryHelpText: 'Pfad zum "python" Befehl.',
pythonPathHelpText: 'Python Modul Suchpfad (PYTHONPATH).',
repositoryDirectoryHelpText: 'Verzeichnis der Mercurial-Repositories.',
useOptimizedBytecodeHelpText: 'Optimierten Bytecode verwenden (python -O).',
disabledHelpText: 'Aktivieren oder deaktivieren des Mercurial Plugins.\n\
Die Seite muss neu geladen werden wenn dieser Wert geändert wird.',
showRevisionInIdHelpText: 'Zeige die Revision als teil der NodeId an. \n\
Der ApplicationServer muss neugestartet werden um zwischengespeicherte\n\
Changesets zuändern.'
});
}
// register information panel
initCallbacks.push(function(main){
main.registerInfoPanel('hg', {
checkoutTemplate: 'hg clone <a href="{0}" target="_blank">{0}</a>',
xtype: 'repositoryExtendedInfoPanel'
});
});
// register config panel
registerConfigPanel({
id: 'hgConfigForm',
xtype : 'hgConfigPanel'
});
// register type icon
Sonia.repository.typeIcons['hg'] = 'resources/images/icons/16x16/mercurial.png';
// override ChangesetViewerGrid to render changeset id's with revisions
Ext.override(Sonia.repository.ChangesetViewerGrid, {
isMercurialRepository: function(){
return this.repository.type === 'hg';
},
getChangesetId: function(id, record){
if ( this.isMercurialRepository() ){
var rev = Sonia.util.getProperty(record.get('properties'), 'hg.rev');
if ( rev ){
id = rev + ':' + id;
}
}
return id;
},
getParentIds: function(id, record){
var parents = record.get('parents');
if ( this.isMercurialRepository() ){
if ( parents && parents.length > 0 ){
var properties = record.get('properties');
var rev = Sonia.util.getProperty(properties, 'hg.p1.rev');
if (rev){
parents[0] = rev + ':' + parents[0];
}
if ( parents.length > 1 ){
rev = Sonia.util.getProperty(properties, 'hg.p2.rev');
if (rev){
parents[1] = rev + ':' + parents[1];
}
}
}
}
return parents;
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"name": "@scm-manager/scm-svn-plugin",
"main": "src/main/js/index.js",
"scripts": {
"build": "ui-bundler plugin"
},
"dependencies": {
"@scm-manager/ui-extensions": "^0.0.7"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.7"
}
}

View File

@@ -0,0 +1,28 @@
//@flow
import React from 'react';
// TODO flow types ???
type Props = {
repository: Object
}
class ProtocolInformation extends React.Component<Props> {
render() {
const { repository } = this.props;
if (!repository._links.httpProtocol) {
return null;
}
return (
<div>
<h4>Checkout the repository</h4>
<pre>
<code>svn checkout {repository._links.httpProtocol.href}</code>
</pre>
</div>
);
}
}
export default ProtocolInformation;

View File

@@ -0,0 +1,16 @@
//@flow
import React from 'react';
type Props = {
};
class SvnAvatar extends React.Component<Props> {
render() {
// TODO we have to use Image from ui-components
return <img src="/scm/images/svn-logo.gif" alt="Subversion Logo" />;
}
}
export default SvnAvatar;

View File

@@ -0,0 +1,10 @@
import { binder } from "@scm-manager/ui-extensions";
import ProtocolInformation from './ProtocolInformation';
import SvnAvatar from './SvnAvatar';
const svnPredicate = (props: Object) => {
return props.repository && props.repository.type === "svn";
};
binder.bind("repos.repository-details.information", ProtocolInformation, svnPredicate);
binder.bind("repos.repository-avatar", SvnAvatar, svnPredicate);

View File

@@ -60,8 +60,4 @@
<min-version>${project.parent.version}</min-version> <min-version>${project.parent.version}</min-version>
</conditions> </conditions>
<resources>
<script>/sonia/scm/svn.config.js</script>
</resources>
</plugin> </plugin>

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
Ext.ns("Sonia.svn");
Sonia.svn.ConfigPanel = Ext.extend(Sonia.config.SimpleConfigForm, {
// labels
titleText: 'Subversion Settings',
repositoryDirectoryText: 'Repository directory',
noneCompatibility: 'No compatibility modus',
pre14CompatibleText: 'Pre 1.4 Compatible',
pre15CompatibleText: 'Pre 1.5 Compatible',
pre16CompatibleText: 'Pre 1.6 Compatible',
pre17CompatibleText: 'Pre 1.7 Compatible',
with17CompatibleText: 'With 1.7 Compatible',
enableGZipText: 'Enable GZip Encoding',
disabledText: 'Disabled',
// helpTexts
repositoryDirectoryHelpText: 'Location of the Suberversion repositories.',
disabledHelpText: 'Enable or disable the Subversion plugin.\n\
Note you have to reload the page, after changing this value.',
enableGZipHelpText: 'Enable GZip encoding for svn responses.',
initComponent: function(){
var config = {
title : this.titleText,
configUrl: restUrl + 'config/repositories/svn',
items : [{
xtype: 'textfield',
name: 'repositoryDirectory',
fieldLabel: this.repositoryDirectoryText,
helpText: this.repositoryDirectoryHelpText,
allowBlank : false
},{
xtype: 'radiogroup',
name: 'compatibility',
columns: 1,
items: [{
boxLabel: this.noneCompatibility,
inputValue: 'NONE',
name: 'compatibility'
},{
boxLabel: this.pre14CompatibleText,
inputValue: 'PRE14',
name: 'compatibility'
},{
boxLabel: this.pre15CompatibleText,
inputValue: 'PRE15',
name: 'compatibility'
},{
boxLabel: this.pre16CompatibleText,
inputValue: 'PRE16',
name: 'compatibility'
},{
boxLabel: this.pre17CompatibleText,
inputValue: 'PRE17',
name: 'compatibility'
},{
boxLabel: this.with17CompatibleText,
inputValue: 'WITH17',
name: 'compatibility'
}]
},{
xtype: 'checkbox',
name: 'enable-gzip',
fieldLabel: this.enableGZipText,
inputValue: 'true',
helpText: this.enableGZipHelpText
},{
xtype: 'checkbox',
name: 'disabled',
fieldLabel: this.disabledText,
inputValue: 'true',
helpText: this.disabledHelpText
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.svn.ConfigPanel.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg("svnConfigPanel", Sonia.svn.ConfigPanel);
// i18n
if ( i18n && i18n.country === 'de' ){
Ext.override(Sonia.svn.ConfigPanel, {
// labels
titleText: 'Subversion Einstellungen',
repositoryDirectoryText: 'Repository-Verzeichnis',
noneCompatibility: 'Kein Kompatiblitätsmodus',
pre14CompatibleText: 'Mit Versionen vor 1.4 kompatibel',
pre15CompatibleText: 'Mit Versionen vor 1.5 kompatibel',
pre16CompatibleText: 'Mit Versionen vor 1.6 kompatibel',
pre17CompatibleText: 'Mit Versionen vor 1.7 kompatibel',
with17CompatibleText: 'Mit Version 1.7 kompatibel',
disabledText: 'Deaktivieren',
// helpTexts
repositoryDirectoryHelpText: 'Verzeichnis der Subversion-Repositories.',
disabledHelpText: 'Aktivieren oder deaktivieren des Subversion Plugins.\n\
Die Seite muss neu geladen werden wenn dieser Wert geändert wird.'
});
}
// register information panel
initCallbacks.push(function(main){
main.registerInfoPanel('svn', {
checkoutTemplate: 'svn checkout <a href="{0}" target="_blank">{0}</a>',
xtype: 'repositoryExtendedInfoPanel'
});
});
// register panel
registerConfigPanel({
xtype : 'svnConfigPanel'
});
// register type icon
Sonia.repository.typeIcons['svn'] = 'resources/images/icons/16x16/subversion.png';

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because it is too large Load Diff

6
scm-ui/.babelrc Normal file
View File

@@ -0,0 +1,6 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}

View File

@@ -1,3 +1,29 @@
{ {
"extends": "react-app" "parser": "babel-eslint",
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:flowtype/recommended"
],
"plugins": [
"flowtype",
"react",
"jsx-a11y",
"import"
],
"rules": {
"quotes": ["error", "double"]
},
"env": {
"browser": true
},
"overrides": [
{
"files": [ "*.test.js" ],
"env": {
"jest": true,
"browser": true
}
}
]
} }

View File

@@ -1,4 +1,5 @@
[ignore] [ignore]
.*/node_modules/module-deps/.*
[include] [include]

View File

@@ -3,7 +3,10 @@
"name": "scm-ui", "name": "scm-ui",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"main": "src/index.js",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.3.1",
"@scm-manager/ui-extensions": "^0.0.7",
"bulma": "^0.7.1", "bulma": "^0.7.1",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
@@ -12,67 +15,51 @@
"i18next-browser-languagedetector": "^2.2.2", "i18next-browser-languagedetector": "^2.2.2",
"i18next-fetch-backend": "^0.1.0", "i18next-fetch-backend": "^0.1.0",
"moment": "^2.22.2", "moment": "^2.22.2",
"react": "^16.4.1", "react": "^16.4.2",
"react-dom": "^16.4.1", "react-dom": "^16.4.2",
"react-i18next": "^7.9.0", "react-i18next": "^7.9.0",
"react-jss": "^8.6.0", "react-jss": "^8.6.0",
"react-redux": "^5.0.7", "react-redux": "^5.0.7",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-router-redux": "^5.0.0-alpha.9", "react-router-redux": "^5.0.0-alpha.9",
"react-scripts": "1.1.4",
"redux": "^4.0.0", "redux": "^4.0.0",
"redux-devtools-extension": "^2.13.5", "redux-devtools-extension": "^2.13.5",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0" "redux-thunk": "^2.3.0"
}, },
"scripts": { "scripts": {
"build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/", "webfonts": "copyfiles -f node_modules/@fortawesome/fontawesome-free/webfonts/* target/styles/webfonts",
"watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive", "build-css": "node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/styles",
"start-js": "react-scripts start", "watch-css": "npm run build-css && node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/styles --watch --recursive",
"start": "npm-run-all -p watch-css start-js", "start-js": "ui-bundler serve",
"build-js": "react-scripts build", "start": "npm-run-all -p webfonts watch-css build-vendor start-js",
"build": "npm-run-all build-css build-js", "build-js": "ui-bundler bundle target/scm-ui.bundle.js",
"test": "jest", "build-vendor": "ui-bundler vendor target/vendor.bundle.js",
"test-coverage": "jest --coverage", "build": "npm-run-all webfonts build-css build-vendor build-js",
"test-ci": "jest --ci --coverage", "test": "ui-bundler test",
"eject": "react-scripts eject", "test-ci": "ui-bundler test --ci",
"flow": "flow", "flow": "flow",
"pre-commit": "jest && flow && eslint src" "pre-commit": "jest && flow && eslint src"
}, },
"proxy": {
"/scm/api": {
"target": "http://localhost:8081"
}
},
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.7",
"babel-eslint": "^8.2.6",
"copyfiles": "^2.0.0",
"enzyme": "^3.3.0", "enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1", "enzyme-adapter-react-16": "^1.1.1",
"eslint": "^5.3.0",
"eslint-plugin-flowtype": "^2.50.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.10.0",
"fetch-mock": "^6.5.0", "fetch-mock": "^6.5.0",
"flow-bin": "^0.77.0", "flow-bin": "^0.77.0",
"flow-typed": "^2.5.1", "flow-typed": "^2.5.1",
"jest-junit": "^5.1.0", "jest": "^23.5.0",
"node-sass-chokidar": "^1.3.0", "node-sass-chokidar": "^1.3.0",
"npm-run-all": "^4.1.3", "npm-run-all": "^4.1.3",
"prettier": "^1.13.7", "prettier": "^1.13.7",
"react-test-renderer": "^16.4.1", "react-test-renderer": "^16.4.1",
"redux-mock-store": "^1.5.3" "redux-mock-store": "^1.5.3"
},
"babel": {
"presets": [
"react-app"
]
},
"jest": {
"coverageDirectory": "target/jest-reports/coverage",
"coveragePathIgnorePatterns": [
"src/tests/.*"
],
"reporters": [
"default",
"jest-junit"
]
},
"jest-junit": {
"output": "./target/jest-reports/TEST-all.xml"
} }
} }

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -8,8 +8,8 @@
manifest.json provides metadata used when your web app is added to the manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="manifest" href="/scm/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <link rel="shortcut icon" href="/scm/favicon.ico">
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
@@ -19,6 +19,10 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<base href="/scm">
<link rel="stylesheet" type="text/css" href="/scm/styles/scm.css">
<title>SCM-Manager</title> <title>SCM-Manager</title>
</head> </head>
<body> <body>
@@ -36,5 +40,10 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<script>
window.ctxPath = "/scm";
</script>
<script src="/scm/vendor.bundle.js"></script>
<script src="/scm/scm-ui.bundle.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,7 +1,5 @@
// @flow // @flow
import { contextPath } from "./urls";
// get api base url from environment
const apiUrl = process.env.API_URL || process.env.PUBLIC_URL || "/scm";
export const NOT_FOUND_ERROR = Error("not found"); export const NOT_FOUND_ERROR = Error("not found");
export const UNAUTHORIZED_ERROR = Error("unauthorized"); export const UNAUTHORIZED_ERROR = Error("unauthorized");
@@ -34,7 +32,7 @@ export function createUrl(url: string) {
if (url.indexOf("/") !== 0) { if (url.indexOf("/") !== 0) {
urlWithStartingSlash = "/" + urlWithStartingSlash; urlWithStartingSlash = "/" + urlWithStartingSlash;
} }
return `${apiUrl}/api/rest/v2${urlWithStartingSlash}`; return `${contextPath}/api/rest/v2${urlWithStartingSlash}`;
} }
class ApiClient { class ApiClient {

View File

@@ -9,7 +9,7 @@ describe("create url", () => {
}); });
it("should add prefix for api", () => { it("should add prefix for api", () => {
expect(createUrl("/users")).toBe("/scm/api/rest/v2/users"); expect(createUrl("/users")).toBe("/api/rest/v2/users");
expect(createUrl("users")).toBe("/scm/api/rest/v2/users"); expect(createUrl("users")).toBe("/api/rest/v2/users");
}); });
}); });

View File

@@ -0,0 +1,18 @@
//@flow
import React from "react";
import { withContextPath } from "../urls";
type Props = {
src: string,
alt: string,
className?: any
};
class Image extends React.Component<Props> {
render() {
const { src, alt, className } = this.props;
return <img className={className} src={withContextPath(src)} alt={alt} />;
}
}
export default Image;

View File

@@ -2,7 +2,7 @@
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import injectSheet from "react-jss"; import injectSheet from "react-jss";
import Image from "../images/loading.svg"; import Image from "./Image";
const styles = { const styles = {
wrapper: { wrapper: {
@@ -26,16 +26,22 @@ const styles = {
type Props = { type Props = {
t: string => string, t: string => string,
message?: string,
classes: any classes: any
}; };
class Loading extends React.Component<Props> { class Loading extends React.Component<Props> {
render() { render() {
const { t, classes } = this.props; const { message, t, classes } = this.props;
return ( return (
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.loading}> <div className={classes.loading}>
<img className={classes.image} src={Image} alt={t("loading.alt")} /> <Image
className={classes.image}
src="/images/loading.svg"
alt={t("loading.alt")}
/>
<p className="has-text-centered">{message}</p>
</div> </div>
</div> </div>
); );

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import Image from "../images/logo.png"; import Image from "./Image";
type Props = { type Props = {
t: string => string t: string => string
@@ -10,7 +10,7 @@ type Props = {
class Logo extends React.Component<Props> { class Logo extends React.Component<Props> {
render() { render() {
const { t } = this.props; const { t } = this.props;
return <img src={Image} alt={t("logo.alt")} />; return <Image src="/images/logo.png" alt={t("logo.alt")} />;
} }
} }

View File

@@ -0,0 +1,90 @@
// @flow
import * as React from "react";
import Loading from "./Loading";
import { apiClient } from "../apiclient";
type Props = {
children: React.Node
};
type State = {
finished: boolean,
message: string
};
type Plugin = {
name: string,
bundles: string[]
};
class PluginLoader extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
finished: false,
message: "booting"
};
}
componentDidMount() {
this.setState({
message: "loading plugin information"
});
apiClient
.get("ui/plugins")
.then(response => response.text())
.then(JSON.parse)
.then(pluginCollection => pluginCollection._embedded.plugins)
.then(this.loadPlugins)
.then(() => {
this.setState({
finished: true
});
});
}
loadPlugins = (plugins: Plugin[]) => {
this.setState({
message: "loading plugins"
});
const promises = [];
for (let plugin of plugins) {
promises.push(this.loadPlugin(plugin));
}
return Promise.all(promises);
};
loadPlugin = (plugin: Plugin) => {
this.setState({
message: `loading ${plugin.name}`
});
const promises = [];
for (let bundle of plugin.bundles) {
promises.push(this.loadBundle(bundle));
}
return Promise.all(promises);
};
loadBundle = (bundle: string) => {
return fetch(bundle)
.then(response => {
return response.text();
})
.then(script => {
// TODO is this safe???
eval(script); // NOSONAR
});
};
render() {
const { message, finished } = this.state;
if (finished) {
return <div>{this.props.children}</div>;
}
return <Loading message={message} />;
}
}
export default PluginLoader;

View File

@@ -2,62 +2,67 @@
import React from "react"; import React from "react";
import { render, unmountComponentAtNode } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import "./ConfirmAlert.css";
type Props = { type Props = {
title:string, title: string,
message: string, message: string,
buttons: array, buttons: array
} };
class ConfirmAlert extends React.Component<Props> { class ConfirmAlert extends React.Component<Props> {
handleClickButton = button => { handleClickButton = button => {
if (button.onClick) button.onClick() if (button.onClick) button.onClick();
this.close() this.close();
} };
close = () => { close = () => {
removeElementReconfirm() removeElementReconfirm();
} };
render () { render() {
const { title, message, buttons } = this.props; const { title, message, buttons } = this.props;
return ( return (
<div className="react-confirm-alert-overlay"> <div className="react-confirm-alert-overlay">
<div className="react-confirm-alert"> <div className="react-confirm-alert">
{<div className="react-confirm-alert-body"> {
<div className="react-confirm-alert-body">
{title && <h1>{title}</h1>} {title && <h1>{title}</h1>}
{message} {message}
<div className="react-confirm-alert-button-group"> <div className="react-confirm-alert-button-group">
{buttons.map((button, i) => ( {buttons.map((button, i) => (
<button key={i} onClick={() => this.handleClickButton(button)}> <button
key={i}
onClick={() => this.handleClickButton(button)}
>
{button.label} {button.label}
</button> </button>
))} ))}
</div> </div>
</div>} </div>
}
</div> </div>
</div> </div>
) );
} }
} }
function createElementReconfirm (properties) { function createElementReconfirm(properties) {
const divTarget = document.createElement('div') const divTarget = document.createElement("div");
divTarget.id = 'react-confirm-alert' divTarget.id = "react-confirm-alert";
document.body.appendChild(divTarget) document.body.appendChild(divTarget);
render(<ConfirmAlert {...properties} />, divTarget) render(<ConfirmAlert {...properties} />, divTarget);
} }
function removeElementReconfirm () { function removeElementReconfirm() {
const target = document.getElementById('react-confirm-alert') const target = document.getElementById("react-confirm-alert");
unmountComponentAtNode(target) unmountComponentAtNode(target);
target.parentNode.removeChild(target) target.parentNode.removeChild(target);
} }
export function confirmAlert (properties) { export function confirmAlert(properties) {
createElementReconfirm(properties) createElementReconfirm(properties);
} }
export default ConfirmAlert; export default ConfirmAlert;

View File

@@ -1,3 +1,4 @@
// @flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import Title from "../../components/layout/Title"; import Title from "../../components/layout/Title";
@@ -12,7 +13,7 @@ import {
getModifyConfigFailure, getModifyConfigFailure,
modifyConfigReset modifyConfigReset
} from "../modules/config"; } from "../modules/config";
import connect from "react-redux/es/connect/connect"; import { connect } from "react-redux";
import ErrorPage from "../../components/ErrorPage"; import ErrorPage from "../../components/ErrorPage";
import type { Config } from "../types/Config"; import type { Config } from "../types/Config";
import ConfigForm from "../components/form/ConfigForm"; import ConfigForm from "../components/form/ConfigForm";

View File

@@ -23,7 +23,7 @@ import reducer, {
getConfigUpdatePermission getConfigUpdatePermission
} from "./config"; } from "./config";
const CONFIG_URL = "/scm/api/rest/v2/config"; const CONFIG_URL = "/api/rest/v2/config";
const error = new Error("You have an error!"); const error = new Error("You have an error!");
@@ -40,7 +40,7 @@ const config = {
anonymousAccessEnabled: false, anonymousAccessEnabled: false,
adminGroups: [], adminGroups: [],
adminUsers: [], adminUsers: [],
baseUrl: "http://localhost:8081/scm", baseUrl: "http://localhost:8081",
forceBaseUrl: false, forceBaseUrl: false,
loginAttemptLimit: -1, loginAttemptLimit: -1,
proxyExcludes: [], proxyExcludes: [],
@@ -51,8 +51,8 @@ const config = {
enabledXsrfProtection: true, enabledXsrfProtection: true,
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy", defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
_links: { _links: {
self: { href: "http://localhost:8081/scm/api/rest/v2/config" }, self: { href: "http://localhost:8081/api/rest/v2/config" },
update: { href: "http://localhost:8081/scm/api/rest/v2/config" } update: { href: "http://localhost:8081/api/rest/v2/config" }
} }
}; };
@@ -69,7 +69,7 @@ const configWithNullValues = {
anonymousAccessEnabled: false, anonymousAccessEnabled: false,
adminGroups: null, adminGroups: null,
adminUsers: null, adminUsers: null,
baseUrl: "http://localhost:8081/scm", baseUrl: "http://localhost:8081",
forceBaseUrl: false, forceBaseUrl: false,
loginAttemptLimit: -1, loginAttemptLimit: -1,
proxyExcludes: null, proxyExcludes: null,
@@ -80,8 +80,8 @@ const configWithNullValues = {
enabledXsrfProtection: true, enabledXsrfProtection: true,
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy", defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
_links: { _links: {
self: { href: "http://localhost:8081/scm/api/rest/v2/config" }, self: { href: "http://localhost:8081/api/rest/v2/config" },
update: { href: "http://localhost:8081/scm/api/rest/v2/config" } update: { href: "http://localhost:8081/api/rest/v2/config" }
} }
}; };
@@ -135,7 +135,7 @@ describe("config fetch()", () => {
}); });
it("should successfully modify config", () => { it("should successfully modify config", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/config", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/config", {
status: 204 status: 204
}); });
@@ -150,7 +150,7 @@ describe("config fetch()", () => {
}); });
it("should call the callback after modifying config", () => { it("should call the callback after modifying config", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/config", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/config", {
status: 204 status: 204
}); });
@@ -169,7 +169,7 @@ describe("config fetch()", () => {
}); });
it("should fail modifying config on HTTP 500", () => { it("should fail modifying config on HTTP 500", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/config", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/config", {
status: 500 status: 500
}); });
@@ -206,6 +206,7 @@ describe("config reducer", () => {
}); });
it("should return empty arrays for null values", () => { it("should return empty arrays for null values", () => {
// $FlowFixMe
const config = reducer({}, fetchConfigSuccess(configWithNullValues)) const config = reducer({}, fetchConfigSuccess(configWithNullValues))
.entries; .entries;
expect(config.adminUsers).toEqual([]); expect(config.adminUsers).toEqual([]);

View File

@@ -11,9 +11,6 @@ import {
getFetchMeFailure getFetchMeFailure
} from "../modules/auth"; } from "../modules/auth";
import "./App.css";
import "font-awesome/css/font-awesome.css";
import "../components/modals/ConfirmAlert.css";
import { PrimaryNavigation } from "../components/navigation"; import { PrimaryNavigation } from "../components/navigation";
import Loading from "../components/Loading"; import Loading from "../components/Loading";
import ErrorPage from "../components/ErrorPage"; import ErrorPage from "../components/ErrorPage";

View File

@@ -15,8 +15,8 @@ import { InputField } from "../components/forms";
import { SubmitButton } from "../components/buttons"; import { SubmitButton } from "../components/buttons";
import classNames from "classnames"; import classNames from "classnames";
import Avatar from "../images/blib.jpg";
import ErrorNotification from "../components/ErrorNotification"; import ErrorNotification from "../components/ErrorNotification";
import Image from "../components/Image";
const styles = { const styles = {
avatar: { avatar: {
@@ -106,9 +106,9 @@ class Login extends React.Component<Props, State> {
<p className="subtitle">{t("login.subtitle")}</p> <p className="subtitle">{t("login.subtitle")}</p>
<div className={classNames("box", classes.avatarSpacing)}> <div className={classNames("box", classes.avatarSpacing)}>
<figure className={classes.avatar}> <figure className={classes.avatar}>
<img <Image
className={classes.avatarImage} className={classes.avatarImage}
src={Avatar} src="/images/blib.jpg"
alt={t("login.logo-alt")} alt={t("login.logo-alt")}
/> />
</figure> </figure>

View File

@@ -44,7 +44,7 @@ import reducer, {
MODIFY_GROUP_SUCCESS, MODIFY_GROUP_SUCCESS,
MODIFY_GROUP_FAILURE MODIFY_GROUP_FAILURE
} from "./groups"; } from "./groups";
const GROUPS_URL = "/scm/api/rest/v2/groups"; const GROUPS_URL = "/api/rest/v2/groups";
const error = new Error("You have an error!"); const error = new Error("You have an error!");
@@ -57,13 +57,13 @@ const humanGroup = {
members: ["userZaphod"], members: ["userZaphod"],
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/groups/humanGroup" href: "http://localhost:8081/api/rest/v2/groups/humanGroup"
}, },
delete: { delete: {
href: "http://localhost:8081/scm/api/rest/v2/groups/humanGroup" href: "http://localhost:8081/api/rest/v2/groups/humanGroup"
}, },
update: { update: {
href:"http://localhost:8081/scm/api/rest/v2/groups/humanGroup" href:"http://localhost:8081/api/rest/v2/groups/humanGroup"
} }
}, },
_embedded: { _embedded: {
@@ -72,7 +72,7 @@ const humanGroup = {
name: "userZaphod", name: "userZaphod",
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/users/userZaphod" href: "http://localhost:8081/api/rest/v2/users/userZaphod"
} }
} }
} }
@@ -89,13 +89,13 @@ const emptyGroup = {
members: [], members: [],
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/groups/emptyGroup" href: "http://localhost:8081/api/rest/v2/groups/emptyGroup"
}, },
delete: { delete: {
href: "http://localhost:8081/scm/api/rest/v2/groups/emptyGroup" href: "http://localhost:8081/api/rest/v2/groups/emptyGroup"
}, },
update: { update: {
href:"http://localhost:8081/scm/api/rest/v2/groups/emptyGroup" href:"http://localhost:8081/api/rest/v2/groups/emptyGroup"
} }
}, },
_embedded: { _embedded: {
@@ -108,16 +108,16 @@ const responseBody = {
pageTotal: 1, pageTotal: 1,
_links: { _links: {
self: { self: {
href: "http://localhost:3000/scm/api/rest/v2/groups/?page=0&pageSize=10" href: "http://localhost:3000/api/rest/v2/groups/?page=0&pageSize=10"
}, },
first: { first: {
href: "http://localhost:3000/scm/api/rest/v2/groups/?page=0&pageSize=10" href: "http://localhost:3000/api/rest/v2/groups/?page=0&pageSize=10"
}, },
last: { last: {
href: "http://localhost:3000/scm/api/rest/v2/groups/?page=0&pageSize=10" href: "http://localhost:3000/api/rest/v2/groups/?page=0&pageSize=10"
}, },
create: { create: {
href: "http://localhost:3000/scm/api/rest/v2/groups/" href: "http://localhost:3000/api/rest/v2/groups/"
} }
}, },
_embedded: { _embedded: {
@@ -244,7 +244,7 @@ describe("groups fetch()", () => {
}); });
it("should successfully modify group", () => { it("should successfully modify group", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
status: 204 status: 204
}); });
@@ -259,7 +259,7 @@ describe("groups fetch()", () => {
}) })
it("should call the callback after modifying group", () => { it("should call the callback after modifying group", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
status: 204 status: 204
}); });
@@ -278,7 +278,7 @@ describe("groups fetch()", () => {
}) })
it("should fail modifying group on HTTP 500", () => { it("should fail modifying group on HTTP 500", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
status: 500 status: 500
}); });
@@ -293,7 +293,7 @@ describe("groups fetch()", () => {
}) })
it("should delete successfully group humanGroup", () => { it("should delete successfully group humanGroup", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", { fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
status: 204 status: 204
}); });
@@ -308,7 +308,7 @@ describe("groups fetch()", () => {
}); });
it("should call the callback, after successful delete", () => { it("should call the callback, after successful delete", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", { fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
status: 204 status: 204
}); });
@@ -324,7 +324,7 @@ describe("groups fetch()", () => {
}); });
it("should fail to delete group humanGroup", () => { it("should fail to delete group humanGroup", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", { fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
status: 500 status: 500
}); });

View File

@@ -2,8 +2,9 @@ import i18n from "i18next";
import Backend from "i18next-fetch-backend"; import Backend from "i18next-fetch-backend";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { reactI18nextModule } from "react-i18next"; import { reactI18nextModule } from "react-i18next";
import { withContextPath } from "./urls";
const loadPath = process.env.PUBLIC_URL + "/locales/{{lng}}/{{ns}}.json"; const loadPath = withContextPath("/locales/{{lng}}/{{ns}}.json");
// TODO load locales for moment // TODO load locales for moment

View File

@@ -14,12 +14,13 @@ import type { BrowserHistory } from "history/createBrowserHistory";
import createReduxStore from "./createReduxStore"; import createReduxStore from "./createReduxStore";
import { ConnectedRouter } from "react-router-redux"; import { ConnectedRouter } from "react-router-redux";
import PluginLoader from "./components/PluginLoader";
const publicUrl: string = process.env.PUBLIC_URL || ""; import { contextPath } from "./urls";
// Create a history of your choosing (we're using a browser history in this case) // Create a history of your choosing (we're using a browser history in this case)
const history: BrowserHistory = createHistory({ const history: BrowserHistory = createHistory({
basename: publicUrl basename: contextPath
}); });
// Add the reducer to your store on the `router` key // Add the reducer to your store on the `router` key
@@ -36,7 +37,9 @@ ReactDOM.render(
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
{/* ConnectedRouter will use the store from Provider automatically */} {/* ConnectedRouter will use the store from Provider automatically */}
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
<App /> <PluginLoader>
<App />
</PluginLoader>
</ConnectedRouter> </ConnectedRouter>
</I18nextProvider> </I18nextProvider>
</Provider>, </Provider>,

View File

@@ -78,7 +78,7 @@ describe("auth actions", () => {
}); });
it("should dispatch login success and dispatch fetch me", () => { it("should dispatch login success and dispatch fetch me", () => {
fetchMock.postOnce("/scm/api/rest/v2/auth/access_token", { fetchMock.postOnce("/api/rest/v2/auth/access_token", {
body: { body: {
cookie: true, cookie: true,
grant_type: "password", grant_type: "password",
@@ -88,7 +88,7 @@ describe("auth actions", () => {
headers: { "content-type": "application/json" } headers: { "content-type": "application/json" }
}); });
fetchMock.getOnce("/scm/api/rest/v2/me", { fetchMock.getOnce("/api/rest/v2/me", {
body: me, body: me,
headers: { "content-type": "application/json" } headers: { "content-type": "application/json" }
}); });
@@ -106,7 +106,7 @@ describe("auth actions", () => {
}); });
it("should dispatch login failure", () => { it("should dispatch login failure", () => {
fetchMock.postOnce("/scm/api/rest/v2/auth/access_token", { fetchMock.postOnce("/api/rest/v2/auth/access_token", {
status: 400 status: 400
}); });
@@ -120,7 +120,7 @@ describe("auth actions", () => {
}); });
it("should dispatch fetch me success", () => { it("should dispatch fetch me success", () => {
fetchMock.getOnce("/scm/api/rest/v2/me", { fetchMock.getOnce("/api/rest/v2/me", {
body: me, body: me,
headers: { "content-type": "application/json" } headers: { "content-type": "application/json" }
}); });
@@ -141,7 +141,7 @@ describe("auth actions", () => {
}); });
it("should dispatch fetch me failure", () => { it("should dispatch fetch me failure", () => {
fetchMock.getOnce("/scm/api/rest/v2/me", { fetchMock.getOnce("/api/rest/v2/me", {
status: 500 status: 500
}); });
@@ -155,7 +155,7 @@ describe("auth actions", () => {
}); });
it("should dispatch fetch me unauthorized", () => { it("should dispatch fetch me unauthorized", () => {
fetchMock.getOnce("/scm/api/rest/v2/me", { fetchMock.getOnce("/api/rest/v2/me", {
status: 401 status: 401
}); });
@@ -173,11 +173,11 @@ describe("auth actions", () => {
}); });
it("should dispatch logout success", () => { it("should dispatch logout success", () => {
fetchMock.deleteOnce("/scm/api/rest/v2/auth/access_token", { fetchMock.deleteOnce("/api/rest/v2/auth/access_token", {
status: 204 status: 204
}); });
fetchMock.getOnce("/scm/api/rest/v2/me", { fetchMock.getOnce("/api/rest/v2/me", {
status: 401 status: 401
}); });
@@ -194,7 +194,7 @@ describe("auth actions", () => {
}); });
it("should dispatch logout failure", () => { it("should dispatch logout failure", () => {
fetchMock.deleteOnce("/scm/api/rest/v2/auth/access_token", { fetchMock.deleteOnce("/api/rest/v2/auth/access_token", {
status: 500 status: 500
}); });

View File

@@ -0,0 +1,56 @@
//@flow
import React from "react";
import type { Repository } from "../types/Repositories";
import MailLink from "../../components/MailLink";
import DateFromNow from "../../components/DateFromNow";
import { translate } from "react-i18next";
type Props = {
repository: Repository,
// context props
t: string => string
};
class RepositoryDetailTable extends React.Component<Props> {
render() {
const { repository, t } = this.props;
return (
<table className="table">
<tbody>
<tr>
<td>{t("repository.name")}</td>
<td>{repository.name}</td>
</tr>
<tr>
<td>{t("repository.type")}</td>
<td>{repository.type}</td>
</tr>
<tr>
<td>{t("repository.contact")}</td>
<td>
<MailLink address={repository.contact} />
</td>
</tr>
<tr>
<td>{t("repository.description")}</td>
<td>{repository.description}</td>
</tr>
<tr>
<td>{t("repository.creationDate")}</td>
<td>
<DateFromNow date={repository.creationDate} />
</td>
</tr>
<tr>
<td>{t("repository.lastModified")}</td>
<td>
<DateFromNow date={repository.lastModified} />
</td>
</tr>
</tbody>
</table>
);
}
}
export default translate("repos")(RepositoryDetailTable);

View File

@@ -1,56 +1,29 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next";
import type { Repository } from "../types/Repositories"; import type { Repository } from "../types/Repositories";
import MailLink from "../../components/MailLink"; import RepositoryDetailTable from "./RepositoryDetailTable";
import DateFromNow from "../../components/DateFromNow"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
type Props = { type Props = {
repository: Repository, repository: Repository
// context props
t: string => string
}; };
class RepositoryDetails extends React.Component<Props> { class RepositoryDetails extends React.Component<Props> {
render() { render() {
const { repository, t } = this.props; const { repository } = this.props;
return ( return (
<table className="table"> <div>
<tbody> <RepositoryDetailTable repository={repository} />
<tr> <div className="content">
<td>{t("repository.name")}</td> <ExtensionPoint
<td>{repository.name}</td> name="repos.repository-details.information"
</tr> renderAll={true}
<tr> props={{ repository }}
<td>{t("repository.type")}</td> />
<td>{repository.type}</td> </div>
</tr> </div>
<tr>
<td>{t("repository.contact")}</td>
<td>
<MailLink address={repository.contact} />
</td>
</tr>
<tr>
<td>{t("repository.description")}</td>
<td>{repository.description}</td>
</tr>
<tr>
<td>{t("repository.creationDate")}</td>
<td>
<DateFromNow date={repository.creationDate} />
</td>
</tr>
<tr>
<td>{t("repository.lastModified")}</td>
<td>
<DateFromNow date={repository.lastModified} />
</td>
</tr>
</tbody>
</table>
); );
} }
} }
export default translate("repos")(RepositoryDetails); export default RepositoryDetails;

View File

@@ -0,0 +1,24 @@
//@flow
import React from "react";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Repository } from "../../types/Repositories";
import Image from "../../../components/Image";
type Props = {
repository: Repository
};
class RepositoryAvatar extends React.Component<Props> {
render() {
const { repository } = this.props;
return (
<p className="image is-64x64">
<ExtensionPoint name="repos.repository-avatar" props={{ repository }}>
<Image src="/images/blib.jpg" alt="Logo" />
</ExtensionPoint>
</p>
);
}
}
export default RepositoryAvatar;

View File

@@ -6,8 +6,8 @@ import type { Repository } from "../../types/Repositories";
import DateFromNow from "../../../components/DateFromNow"; import DateFromNow from "../../../components/DateFromNow";
import RepositoryEntryLink from "./RepositoryEntryLink"; import RepositoryEntryLink from "./RepositoryEntryLink";
import classNames from "classnames"; import classNames from "classnames";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import icon from "../../../images/blib.jpg"; import RepositoryAvatar from "./RepositoryAvatar";
const styles = { const styles = {
outer: { outer: {
@@ -45,7 +45,7 @@ class RepositoryEntry extends React.Component<Props> {
if (repository._links["changesets"]) { if (repository._links["changesets"]) {
return ( return (
<RepositoryEntryLink <RepositoryEntryLink
iconClass="fa-code-fork" iconClass="fa-code-branch"
to={repositoryLink + "/changesets"} to={repositoryLink + "/changesets"}
/> />
); );
@@ -85,9 +85,7 @@ class RepositoryEntry extends React.Component<Props> {
<Link className={classes.overlay} to={repositoryLink} /> <Link className={classes.overlay} to={repositoryLink} />
<article className={classNames("media", classes.inner)}> <article className={classNames("media", classes.inner)}>
<figure className="media-left"> <figure className="media-left">
<p className="image is-64x64"> <RepositoryAvatar repository={repository} />
<img src={icon} alt="Logo" />
</p>
</figure> </figure>
<div className="media-content"> <div className="media-content">
<div className="content"> <div className="content">

View File

@@ -58,36 +58,33 @@ const hitchhikerPuzzle42: Repository = {
type: "svn", type: "svn",
_links: { _links: {
self: { self: {
href: href: "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42"
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42"
}, },
delete: { delete: {
href: href: "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42"
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42"
}, },
update: { update: {
href: href: "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42"
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42"
}, },
permissions: { permissions: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/"
}, },
tags: { tags: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/tags/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/tags/"
}, },
branches: { branches: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/branches/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/branches/"
}, },
changesets: { changesets: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/changesets/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/changesets/"
}, },
sources: { sources: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/sources/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/sources/"
} }
} }
}; };
@@ -103,35 +100,35 @@ const hitchhikerRestatend: Repository = {
_links: { _links: {
self: { self: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend"
}, },
delete: { delete: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend"
}, },
update: { update: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend"
}, },
permissions: { permissions: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend/permissions/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/permissions/"
}, },
tags: { tags: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend/tags/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/tags/"
}, },
branches: { branches: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend/branches/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/branches/"
}, },
changesets: { changesets: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend/changesets/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/changesets/"
}, },
sources: { sources: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/restatend/sources/" "http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/sources/"
} }
} }
}; };
@@ -145,33 +142,32 @@ const slartiFjords: Repository = {
creationDate: "2018-07-31T08:59:05.653Z", creationDate: "2018-07-31T08:59:05.653Z",
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords" href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords"
}, },
delete: { delete: {
href: "http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords" href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords"
}, },
update: { update: {
href: "http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords" href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords"
}, },
permissions: { permissions: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords/permissions/" "http://localhost:8081/api/rest/v2/repositories/slarti/fjords/permissions/"
}, },
tags: { tags: {
href: href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords/tags/"
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords/tags/"
}, },
branches: { branches: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords/branches/" "http://localhost:8081/api/rest/v2/repositories/slarti/fjords/branches/"
}, },
changesets: { changesets: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords/changesets/" "http://localhost:8081/api/rest/v2/repositories/slarti/fjords/changesets/"
}, },
sources: { sources: {
href: href:
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords/sources/" "http://localhost:8081/api/rest/v2/repositories/slarti/fjords/sources/"
} }
} }
}; };
@@ -181,19 +177,16 @@ const repositoryCollection: RepositoryCollection = {
pageTotal: 1, pageTotal: 1,
_links: { _links: {
self: { self: {
href: href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
"http://localhost:8081/scm/api/rest/v2/repositories/?page=0&pageSize=10"
}, },
first: { first: {
href: href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
"http://localhost:8081/scm/api/rest/v2/repositories/?page=0&pageSize=10"
}, },
last: { last: {
href: href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
"http://localhost:8081/scm/api/rest/v2/repositories/?page=0&pageSize=10"
}, },
create: { create: {
href: "http://localhost:8081/scm/api/rest/v2/repositories/" href: "http://localhost:8081/api/rest/v2/repositories/"
} }
}, },
_embedded: { _embedded: {
@@ -206,19 +199,16 @@ const repositoryCollectionWithNames: RepositoryCollection = {
pageTotal: 1, pageTotal: 1,
_links: { _links: {
self: { self: {
href: href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
"http://localhost:8081/scm/api/rest/v2/repositories/?page=0&pageSize=10"
}, },
first: { first: {
href: href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
"http://localhost:8081/scm/api/rest/v2/repositories/?page=0&pageSize=10"
}, },
last: { last: {
href: href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
"http://localhost:8081/scm/api/rest/v2/repositories/?page=0&pageSize=10"
}, },
create: { create: {
href: "http://localhost:8081/scm/api/rest/v2/repositories/" href: "http://localhost:8081/api/rest/v2/repositories/"
} }
}, },
_embedded: { _embedded: {
@@ -231,7 +221,7 @@ const repositoryCollectionWithNames: RepositoryCollection = {
}; };
describe("repos fetch", () => { describe("repos fetch", () => {
const REPOS_URL = "/scm/api/rest/v2/repositories"; const REPOS_URL = "/api/rest/v2/repositories";
const SORT = "sortBy=namespaceAndName"; const SORT = "sortBy=namespaceAndName";
const REPOS_URL_WITH_SORT = REPOS_URL + "?" + SORT; const REPOS_URL_WITH_SORT = REPOS_URL + "?" + SORT;
const mockStore = configureMockStore([thunk]); const mockStore = configureMockStore([thunk]);
@@ -303,7 +293,7 @@ describe("repos fetch", () => {
it("should append sortby parameter and successfully fetch repos from link", () => { it("should append sortby parameter and successfully fetch repos from link", () => {
fetchMock.getOnce( fetchMock.getOnce(
"/scm/api/rest/v2/repositories?one=1&sortBy=namespaceAndName", "/api/rest/v2/repositories?one=1&sortBy=namespaceAndName",
repositoryCollection repositoryCollection
); );
@@ -431,7 +421,7 @@ describe("repos fetch", () => {
it("should successfully delete repo slarti/fjords", () => { it("should successfully delete repo slarti/fjords", () => {
fetchMock.delete( fetchMock.delete(
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords", "http://localhost:8081/api/rest/v2/repositories/slarti/fjords",
{ {
status: 204 status: 204
} }
@@ -458,7 +448,7 @@ describe("repos fetch", () => {
it("should successfully delete repo slarti/fjords and call the callback", () => { it("should successfully delete repo slarti/fjords and call the callback", () => {
fetchMock.delete( fetchMock.delete(
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords", "http://localhost:8081/api/rest/v2/repositories/slarti/fjords",
{ {
status: 204 status: 204
} }
@@ -478,7 +468,7 @@ describe("repos fetch", () => {
it("should disapatch failure on delete, if server returns status code 500", () => { it("should disapatch failure on delete, if server returns status code 500", () => {
fetchMock.delete( fetchMock.delete(
"http://localhost:8081/scm/api/rest/v2/repositories/slarti/fjords", "http://localhost:8081/api/rest/v2/repositories/slarti/fjords",
{ {
status: 500 status: 500
} }

View File

@@ -22,7 +22,7 @@ const git = {
displayName: "Git", displayName: "Git",
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/repositoryTypes/git" href: "http://localhost:8081/api/rest/v2/repositoryTypes/git"
} }
} }
}; };
@@ -32,7 +32,7 @@ const hg = {
displayName: "Mercurial", displayName: "Mercurial",
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/repositoryTypes/hg" href: "http://localhost:8081/api/rest/v2/repositoryTypes/hg"
} }
} }
}; };
@@ -42,7 +42,7 @@ const svn = {
displayName: "Subversion", displayName: "Subversion",
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/repositoryTypes/svn" href: "http://localhost:8081/api/rest/v2/repositoryTypes/svn"
} }
} }
}; };
@@ -53,7 +53,7 @@ const collection = {
}, },
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/repositoryTypes" href: "http://localhost:8081/api/rest/v2/repositoryTypes"
} }
} }
}; };
@@ -97,7 +97,7 @@ describe("repository types caching", () => {
}); });
describe("repository types fetch", () => { describe("repository types fetch", () => {
const URL = "/scm/api/rest/v2/repositoryTypes"; const URL = "/api/rest/v2/repositoryTypes";
const mockStore = configureMockStore([thunk]); const mockStore = configureMockStore([thunk]);
afterEach(() => { afterEach(() => {

6
scm-ui/src/urls.js Normal file
View File

@@ -0,0 +1,6 @@
// @flow
export const contextPath = window.ctxPath || "";
export function withContextPath(path: string) {
return contextPath + path;
}

View File

@@ -61,13 +61,13 @@ const userZaphod = {
properties: {}, properties: {},
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/users/zaphod" href: "http://localhost:8081/api/rest/v2/users/zaphod"
}, },
delete: { delete: {
href: "http://localhost:8081/scm/api/rest/v2/users/zaphod" href: "http://localhost:8081/api/rest/v2/users/zaphod"
}, },
update: { update: {
href: "http://localhost:8081/scm/api/rest/v2/users/zaphod" href: "http://localhost:8081/api/rest/v2/users/zaphod"
} }
} }
}; };
@@ -84,13 +84,13 @@ const userFord = {
properties: {}, properties: {},
_links: { _links: {
self: { self: {
href: "http://localhost:8081/scm/api/rest/v2/users/ford" href: "http://localhost:8081/api/rest/v2/users/ford"
}, },
delete: { delete: {
href: "http://localhost:8081/scm/api/rest/v2/users/ford" href: "http://localhost:8081/api/rest/v2/users/ford"
}, },
update: { update: {
href: "http://localhost:8081/scm/api/rest/v2/users/ford" href: "http://localhost:8081/api/rest/v2/users/ford"
} }
} }
}; };
@@ -100,16 +100,16 @@ const responseBody = {
pageTotal: 1, pageTotal: 1,
_links: { _links: {
self: { self: {
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10" href: "http://localhost:3000/api/rest/v2/users/?page=0&pageSize=10"
}, },
first: { first: {
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10" href: "http://localhost:3000/api/rest/v2/users/?page=0&pageSize=10"
}, },
last: { last: {
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10" href: "http://localhost:3000/api/rest/v2/users/?page=0&pageSize=10"
}, },
create: { create: {
href: "http://localhost:3000/scm/api/rest/v2/users/" href: "http://localhost:3000/api/rest/v2/users/"
} }
}, },
_embedded: { _embedded: {
@@ -122,7 +122,7 @@ const response = {
responseBody responseBody
}; };
const USERS_URL = "/scm/api/rest/v2/users"; const USERS_URL = "/api/rest/v2/users";
const error = new Error("KAPUTT"); const error = new Error("KAPUTT");
@@ -241,7 +241,7 @@ describe("users fetch()", () => {
}); });
it("successfully update user", () => { it("successfully update user", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
status: 204 status: 204
}); });
@@ -255,7 +255,7 @@ describe("users fetch()", () => {
}); });
it("should call callback, after successful modified user", () => { it("should call callback, after successful modified user", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
status: 204 status: 204
}); });
@@ -271,7 +271,7 @@ describe("users fetch()", () => {
}); });
it("should fail updating user on HTTP 500", () => { it("should fail updating user on HTTP 500", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", { fetchMock.putOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
status: 500 status: 500
}); });
@@ -285,7 +285,7 @@ describe("users fetch()", () => {
}); });
it("should delete successfully user zaphod", () => { it("should delete successfully user zaphod", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", { fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
status: 204 status: 204
}); });
@@ -300,7 +300,7 @@ describe("users fetch()", () => {
}); });
it("should call the callback, after successful delete", () => { it("should call the callback, after successful delete", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", { fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
status: 204 status: 204
}); });
@@ -316,7 +316,7 @@ describe("users fetch()", () => {
}); });
it("should fail to delete user zaphod", () => { it("should fail to delete user zaphod", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", { fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
status: 500 status: 500
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -46,3 +46,8 @@ $blue: #33B2E8;
box-shadow: $box-link-active-shadow; box-shadow: $box-link-active-shadow;
} }
} }
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
$fa-font-path: "webfonts";
@import "@fortawesome/fontawesome-free/scss/solid.scss";

File diff suppressed because it is too large Load Diff

View File

@@ -513,6 +513,10 @@
<name>java.awt.headless</name> <name>java.awt.headless</name>
<value>true</value> <value>true</value>
</systemProperty> </systemProperty>
<systemProperty>
<name>sonia.scm.ui.proxy</name>
<value>http://localhost:3000</value>
</systemProperty>
</systemProperties> </systemProperties>
<webApp> <webApp>
<contextPath>/scm</contextPath> <contextPath>/scm</contextPath>

View File

@@ -0,0 +1,24 @@
package sonia.scm;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* This dispatcher forwards every request to the index.html of the application.
*
* @since 2.0.0
*/
public class ForwardingPushStateDispatcher implements PushStateDispatcher {
@Override
public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher("/index.html");
try {
dispatcher.forward(request, response);
} catch (ServletException e) {
throw new IOException("failed to forward request", e);
}
}
}

View File

@@ -0,0 +1,132 @@
package sonia.scm;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.ByteStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
/**
* PushStateDispatcher which delegates the request to a different server. This dispatcher should only be used for
* development and never in production.
*
* @since 2.0.0
*/
public final class ProxyPushStateDispatcher implements PushStateDispatcher {
private static final Logger LOG = LoggerFactory.getLogger(ProxyPushStateDispatcher.class);
@FunctionalInterface
interface ConnectionFactory {
HttpURLConnection open(URL url) throws IOException;
}
private final String target;
private final ConnectionFactory connectionFactory;
/**
* Creates a new dispatcher for the given target. The target must be a valid url.
*
* @param target proxy target
*/
public ProxyPushStateDispatcher(String target) {
this(target, ProxyPushStateDispatcher::openConnection);
}
/**
* This Constructor should only be used for testing.
*
* @param target proxy target
* @param connectionFactory factory for creating an connection from a url
*/
@VisibleForTesting
ProxyPushStateDispatcher(String target, ConnectionFactory connectionFactory) {
this.target = target;
this.connectionFactory = connectionFactory;
}
@Override
public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException {
URL url = createProxyUrl(uri);
HttpURLConnection connection = connectionFactory.open(url);
connection.setRequestMethod(request.getMethod());
copyRequestHeaders(request, connection);
if (request.getContentLength() > 0) {
copyRequestBody(request, connection);
}
int responseCode = connection.getResponseCode();
response.setStatus(responseCode);
copyResponseHeaders(response, connection);
if (connection.getContentLength() > 0) {
copyResponseBody(response, connection);
}
}
private void copyResponseBody(HttpServletResponse response, HttpURLConnection connection) throws IOException {
try (InputStream input = getConnectionInput(connection); OutputStream output = response.getOutputStream()) {
ByteStreams.copy(input, output);
}
}
private InputStream getConnectionInput(HttpURLConnection connection) throws IOException {
if (connection.getErrorStream() != null) {
return connection.getErrorStream();
}
return connection.getInputStream();
}
private void copyResponseHeaders(HttpServletResponse response, HttpURLConnection connection) {
Map<String, List<String>> headerFields = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()) {
if (entry.getKey() != null && !"Transfer-Encoding".equalsIgnoreCase(entry.getKey())) {
for (String value : entry.getValue()) {
response.addHeader(entry.getKey(), value);
}
}
}
}
private void copyRequestBody(HttpServletRequest request, HttpURLConnection connection) throws IOException {
connection.setDoOutput(true);
try (InputStream input = request.getInputStream(); OutputStream output = connection.getOutputStream()) {
ByteStreams.copy(input, output);
}
}
private void copyRequestHeaders(HttpServletRequest request, HttpURLConnection connection) {
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String header = headers.nextElement();
Enumeration<String> values = request.getHeaders(header);
while (values.hasMoreElements()) {
String value = values.nextElement();
connection.setRequestProperty(header, value);
}
}
}
private URL createProxyUrl(String uri) throws MalformedURLException {
return new URL(target + uri);
}
private static HttpURLConnection openConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}

View File

@@ -0,0 +1,28 @@
package sonia.scm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* The PushStateDispatcher is responsible for dispatching the request, to the main entry point of the ui, if no resource
* could be found for the requested path. This allows us the implementation of a ui which work with "pushstate" of
* html5.
*
* @since 2.0.0
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API">HTML5 Push State</a>
*/
public interface PushStateDispatcher {
/**
* Dispatches the request to the main entry point of the ui.
*
* @param request http request
* @param response http response
* @param uri request uri
*
* @throws IOException
*/
void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException;
}

View File

@@ -0,0 +1,28 @@
package sonia.scm;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import javax.inject.Provider;
/**
* Injection Provider for the {@link PushStateDispatcher}. The provider will return a {@link ProxyPushStateDispatcher}
* if the system property {@code PushStateDispatcherProvider#PROPERTY_TARGET} is set to a proxy target url, otherwise
* a {@link ForwardingPushStateDispatcher} is used.
*
* @since 2.0.0
*/
public class PushStateDispatcherProvider implements Provider<PushStateDispatcher> {
@VisibleForTesting
static final String PROPERTY_TARGET = "sonia.scm.ui.proxy";
@Override
public PushStateDispatcher get() {
String target = System.getProperty(PROPERTY_TARGET);
if (Strings.isNullOrEmpty(target)) {
return new ForwardingPushStateDispatcher();
}
return new ProxyPushStateDispatcher(target);
}
}

View File

@@ -63,10 +63,6 @@ import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.xml.XmlRepositoryDAO; import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.resources.DefaultResourceManager;
import sonia.scm.resources.DevelopmentResourceManager;
import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ScriptResourceServlet;
import sonia.scm.schedule.QuartzScheduler; import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler; import sonia.scm.schedule.Scheduler;
import sonia.scm.security.*; import sonia.scm.security.*;
@@ -266,16 +262,6 @@ public class ScmServletModule extends ServletModule
transformers.addBinding().to(JsonContentTransformer.class); transformers.addBinding().to(JsonContentTransformer.class);
bind(AdvancedHttpClient.class).to(DefaultAdvancedHttpClient.class); bind(AdvancedHttpClient.class).to(DefaultAdvancedHttpClient.class);
// bind resourcemanager
if (context.getStage() == Stage.DEVELOPMENT)
{
bind(ResourceManager.class, DevelopmentResourceManager.class);
}
else
{
bind(ResourceManager.class, DefaultResourceManager.class);
}
// bind repository service factory // bind repository service factory
bind(RepositoryServiceFactory.class); bind(RepositoryServiceFactory.class);
@@ -295,9 +281,6 @@ public class ScmServletModule extends ServletModule
// debug servlet // debug servlet
serve(PATTERN_DEBUG).with(DebugServlet.class); serve(PATTERN_DEBUG).with(DebugServlet.class);
// plugin resources
serve(PATTERN_PLUGIN_SCRIPT).with(ScriptResourceServlet.class);
// template // template
serve(PATTERN_INDEX, "/").with(TemplateServlet.class); serve(PATTERN_INDEX, "/").with(TemplateServlet.class);
@@ -313,7 +296,7 @@ public class ScmServletModule extends ServletModule
// bind events // bind events
// bind(LastModifiedUpdateListener.class); // bind(LastModifiedUpdateListener.class);
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
} }

View File

@@ -0,0 +1,85 @@
package sonia.scm;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.filter.WebElement;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.UberWebResourceLoader;
import sonia.scm.util.HttpUtil;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
/**
* WebResourceServlet serves resources from the {@link UberWebResourceLoader}.
*
* @since 2.0.0
*/
@Singleton
@WebElement(value = WebResourceServlet.PATTERN, regex = true)
public class WebResourceServlet extends HttpServlet {
/**
* exclude api requests and the old frontend servlets.
*
* TODO remove old frontend servlets
*/
@VisibleForTesting
static final String PATTERN = "/(?!api/).*";
private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class);
private final UberWebResourceLoader webResourceLoader;
private final PushStateDispatcher pushStateDispatcher;
@Inject
public WebResourceServlet(PluginLoader pluginLoader, PushStateDispatcher dispatcher) {
this.webResourceLoader = pluginLoader.getUberWebResourceLoader();
this.pushStateDispatcher = dispatcher;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String uri = normalizeUri(request);
LOG.trace("try to load {}", uri);
URL url = webResourceLoader.getResource(uri);
if (url != null) {
serveResource(response, url);
} else {
dispatch(request, response, uri);
}
}
private void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) {
try {
pushStateDispatcher.dispatch(request, response, uri);
} catch (IOException ex) {
LOG.error("failed to dispatch: " + uri, ex);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private String normalizeUri(HttpServletRequest request) {
return HttpUtil.getStrippedURI(request);
}
private void serveResource(HttpServletResponse response, URL url) {
// TODO lastModifiedDate, if-... ???
try (OutputStream output = response.getOutputStream()) {
Resources.copy(url, output);
} catch (IOException ex) {
LOG.warn("failed to serve resource: {}", url);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -30,6 +30,10 @@ public class MapperModule extends AbstractModule {
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass()); bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
// no mapstruct required
bind(UIPluginDtoMapper.class);
bind(UIPluginDtoCollectionMapper.class);
bind(UriInfoStore.class).in(ServletScopes.REQUEST); bind(UriInfoStore.class).in(ServletScopes.REQUEST);
} }
} }

View File

@@ -349,4 +349,37 @@ class ResourceLinks {
return permissionLinkBuilder.method("getRepositoryResource").parameters(repositoryNamespace, repositoryName).method("permissions").parameters().method(methodName).parameters(permissionName).href(); return permissionLinkBuilder.method("getRepositoryResource").parameters(repositoryNamespace, repositoryName).method("permissions").parameters().method(methodName).parameters(permissionName).href();
} }
} }
public UIPluginLinks uiPlugin() {
return new UIPluginLinks(uriInfoStore.get());
}
static class UIPluginLinks {
private final LinkBuilder uiPluginLinkBuilder;
UIPluginLinks(UriInfo uriInfo) {
uiPluginLinkBuilder = new LinkBuilder(uriInfo, UIRootResource.class, UIPluginResource.class);
}
String self(String id) {
return uiPluginLinkBuilder.method("plugins").parameters().method("getInstalledPlugin").parameters(id).href();
}
}
public UIPluginCollectionLinks uiPluginCollection() {
return new UIPluginCollectionLinks(uriInfoStore.get());
}
static class UIPluginCollectionLinks {
private final LinkBuilder uiPluginCollectionLinkBuilder;
UIPluginCollectionLinks(UriInfo uriInfo) {
uiPluginCollectionLinkBuilder = new LinkBuilder(uriInfo, UIRootResource.class, UIPluginResource.class);
}
String self() {
return uiPluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href();
}
}
} }

View File

@@ -0,0 +1,25 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter @Setter @NoArgsConstructor
public class UIPluginDto extends HalRepresentation {
private String name;
private Iterable<String> bundles;
public UIPluginDto(String name, Iterable<String> bundles) {
this.name = name;
this.bundles = bundles;
}
@Override
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,46 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.PluginWrapper;
import java.util.Collection;
import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Links.linkingTo;
import static java.util.stream.Collectors.toList;
public class UIPluginDtoCollectionMapper {
private final ResourceLinks resourceLinks;
private final UIPluginDtoMapper mapper;
@Inject
public UIPluginDtoCollectionMapper(ResourceLinks resourceLinks, UIPluginDtoMapper mapper) {
this.resourceLinks = resourceLinks;
this.mapper = mapper;
}
public HalRepresentation map(Collection<PluginWrapper> plugins) {
List<UIPluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
return new HalRepresentation(createLinks(), embedDtos(dtos));
}
private Links createLinks() {
String baseUrl = resourceLinks.uiPluginCollection().self();
Links.Builder linksBuilder = linkingTo()
.with(Links.linkingTo().self(baseUrl).build());
return linksBuilder.build();
}
private Embedded embedDtos(List<UIPluginDto> dtos) {
return embeddedBuilder()
.with("plugins", dtos)
.build();
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.api.v2.resources;
import com.google.common.base.Strings;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.util.HttpUtil;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import static de.otto.edison.hal.Links.linkingTo;
public class UIPluginDtoMapper {
private final ResourceLinks resourceLinks;
private final HttpServletRequest request;
@Inject
public UIPluginDtoMapper(ResourceLinks resourceLinks, HttpServletRequest request) {
this.resourceLinks = resourceLinks;
this.request = request;
}
public UIPluginDto map(PluginWrapper plugin) {
UIPluginDto dto = new UIPluginDto(
plugin.getPlugin().getInformation().getName(),
getScriptResources(plugin)
);
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.uiPlugin()
.self(plugin.getId()));
dto.add(linksBuilder.build());
return dto;
}
private Set<String> getScriptResources(PluginWrapper wrapper) {
Set<String> scriptResources = wrapper.getPlugin().getResources().getScriptResources();
if (scriptResources != null) {
return scriptResources.stream()
.map(this::addContextPath)
.collect(Collectors.toSet());
}
return Collections.emptySet();
}
private String addContextPath(String resource) {
String ctxPath = request.getContextPath();
if (Strings.isNullOrEmpty(ctxPath)) {
return resource;
}
return HttpUtil.append(ctxPath, resource);
}
}

View File

@@ -0,0 +1,90 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class UIPluginResource {
private final PluginLoader pluginLoader;
private final UIPluginDtoCollectionMapper collectionMapper;
private final UIPluginDtoMapper mapper;
@Inject
public UIPluginResource(PluginLoader pluginLoader, UIPluginDtoCollectionMapper collectionMapper, UIPluginDtoMapper mapper) {
this.pluginLoader = pluginLoader;
this.collectionMapper = collectionMapper;
this.mapper = mapper;
}
/**
* Returns a collection of installed plugins and their ui bundles.
*
* @return collection of installed plugins.
*/
@GET
@Path("")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(CollectionDto.class)
@Produces(VndMediaType.UI_PLUGIN_COLLECTION)
public Response getInstalledPlugins() {
List<PluginWrapper> plugins = pluginLoader.getInstalledPlugins()
.stream()
.filter(this::filter)
.collect(Collectors.toList());
return Response.ok(collectionMapper.map(plugins)).build();
}
/**
* Returns the installed plugin with the given id.
*
* @param id id of plugin
*
* @return installed plugin with specified id
*/
@GET
@Path("{id}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(UIPluginDto.class)
@Produces(VndMediaType.UI_PLUGIN)
public Response getInstalledPlugin(@PathParam("id") String id) {
Optional<UIPluginDto> uiPluginDto = pluginLoader.getInstalledPlugins()
.stream()
.filter(this::filter)
.filter(plugin -> id.equals(plugin.getId()))
.map(mapper::map)
.findFirst();
if (uiPluginDto.isPresent()) {
return Response.ok(uiPluginDto.get()).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
private boolean filter(PluginWrapper plugin) {
return plugin.getPlugin().getResources() != null;
}
}

View File

@@ -0,0 +1,22 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
@Path("v2/ui")
public class UIRootResource {
private Provider<UIPluginResource> uiPluginResourceProvider;
@Inject
public UIRootResource(Provider<UIPluginResource> uiPluginResourceProvider) {
this.uiPluginResourceProvider = uiPluginResourceProvider;
}
@Path("plugins")
public UIPluginResource plugins() {
return uiPluginResourceProvider.get();
}
}

View File

@@ -62,7 +62,8 @@ import javax.servlet.http.HttpServletResponse;
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Priority(Filters.PRIORITY_AUTHORIZATION) @Priority(Filters.PRIORITY_AUTHORIZATION)
@WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = { Filters.PATTERN_DEBUG }) // TODO find a better way for unprotected resources
@WebElement(value = "/api/rest/(?!v2/ui).*", regex = true)
public class SecurityFilter extends HttpFilter public class SecurityFilter extends HttpFilter
{ {

View File

@@ -39,18 +39,18 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableList.Builder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------ import javax.servlet.ServletContext;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import javax.servlet.ServletContext; //~--- JDK imports ------------------------------------------------------------
/** /**
* Default implementation of the {@link UberWebResourceLoader}. * Default implementation of the {@link UberWebResourceLoader}.
@@ -133,7 +133,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
try try
{ {
URL ctxResource = servletContext.getResource(path); URL ctxResource = nonDirectory(servletContext.getResource(path));
if (ctxResource != null) if (ctxResource != null)
{ {
@@ -143,7 +143,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
for (PluginWrapper wrapper : plugins) for (PluginWrapper wrapper : plugins)
{ {
URL resource = wrapper.getWebResourceLoader().getResource(path); URL resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
if (resource != null) if (resource != null)
{ {
@@ -185,17 +185,17 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
*/ */
private URL find(String path) private URL find(String path)
{ {
URL resource = null; URL resource;
try try
{ {
resource = servletContext.getResource(path); resource = nonDirectory(servletContext.getResource(path));
if (resource == null) if (resource == null)
{ {
for (PluginWrapper wrapper : plugins) for (PluginWrapper wrapper : plugins)
{ {
resource = wrapper.getWebResourceLoader().getResource(path); resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
if (resource != null) if (resource != null)
{ {
@@ -218,6 +218,29 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
return resource; return resource;
} }
private URL nonDirectory(URL url) {
if (url == null) {
return null;
}
if (isDirectory(url)) {
return null;
}
return url;
}
private boolean isDirectory(URL url) {
if ("file".equals(url.getProtocol())) {
try {
return Files.isDirectory(Paths.get(url.toURI()));
} catch (URISyntaxException ex) {
throw Throwables.propagate(ex);
}
}
return false;
}
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */

View File

@@ -93,7 +93,7 @@ public class PathWebResourceLoader implements WebResourceLoader
URL resource = null; URL resource = null;
Path file = directory.resolve(filePath(path)); Path file = directory.resolve(filePath(path));
if (Files.exists(file)) if (Files.exists(file) && ! Files.isDirectory(file))
{ {
logger.trace("found path {} at {}", path, file); logger.trace("found path {} at {}", path, file);

View File

@@ -1,215 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public abstract class AbstractResource implements Resource
{
/**
* the logger for AbstractResource
*/
private static final Logger logger =
LoggerFactory.getLogger(AbstractResource.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param pluginLoader
* @param resources
* @param resourceHandlers
*/
public AbstractResource(PluginLoader pluginLoader, List<String> resources,
List<ResourceHandler> resourceHandlers)
{
this.pluginLoader = pluginLoader;
this.resources = resources;
this.resourceHandlers = resourceHandlers;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param stream
*
* @throws IOException
*/
protected void appendResources(OutputStream stream) throws IOException
{
if (Util.isNotEmpty(resources))
{
for (String resource : resources)
{
appendResource(stream, resource);
}
}
if (Util.isNotEmpty(resourceHandlers))
{
Collections.sort(resourceHandlers, new ResourceHandlerComparator());
for (ResourceHandler resourceHandler : resourceHandlers)
{
processResourceHandler(stream, resourceHandler);
}
}
}
/**
* Method description
*
*
* @param stream
* @param path
*
* @throws IOException
*/
private void appendResource(OutputStream stream, String path)
throws IOException
{
URL resource = getResourceAsURL(path);
if (resource != null)
{
Resources.copy(resource, stream);
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find resource {}", path);
}
}
/**
* Method description
*
*
* @param stream
* @param resourceHandler
*
* @throws IOException
*/
private void processResourceHandler(OutputStream stream,
ResourceHandler resourceHandler)
throws IOException
{
if (resourceHandler.getType() == getType())
{
if (logger.isTraceEnabled())
{
logger.trace("process resource handler {}", resourceHandler.getClass());
}
URL resource = resourceHandler.getResource();
if (resource != null)
{
Resources.copy(resource, stream);
}
else if (logger.isDebugEnabled())
{
logger.debug("resource handler {} does not return a resource",
resourceHandler.getClass());
}
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param path
*
* @return
*/
private URL getResourceAsURL(String path)
{
URL resource = null;
ClassLoader classLoader = pluginLoader.getUberClassLoader();
if (classLoader != null)
{
String classLoaderResource = path;
if (classLoaderResource.startsWith("/"))
{
classLoaderResource = classLoaderResource.substring(1);
}
resource = classLoader.getResource(classLoaderResource);
}
return resource;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected final List<ResourceHandler> resourceHandlers;
/** Field description */
protected final List<String> resources;
/** Field description */
private final PluginLoader pluginLoader;
}

View File

@@ -1,308 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginResources;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.ServletContext;
import sonia.scm.plugin.PluginWrapper;
/**
*
* @author Sebastian Sdorra
*/
public abstract class AbstractResourceManager implements ResourceManager
{
/**
* Constructs ...
*
* @param pluginLoader
* @param resourceHandlers
*/
protected AbstractResourceManager(PluginLoader pluginLoader,
Set<ResourceHandler> resourceHandlers)
{
this.pluginLoader = pluginLoader;
this.resourceHandlers = resourceHandlers;
collectResources(resourceMap);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resourceMap
*/
protected abstract void collectResources(Map<ResourceKey,
Resource> resourceMap);
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param type
* @param name
*
* @return
*/
@Override
public Resource getResource(ResourceType type, String name)
{
return resourceMap.get(new ResourceKey(name, type));
}
/**
* Method description
*
*
* @param type
*
* @return
*/
@Override
public List<Resource> getResources(ResourceType type)
{
List<Resource> resources = new ArrayList<>();
for (Entry<ResourceKey, Resource> e : resourceMap.entrySet())
{
if (e.getKey().getType() == type)
{
resources.add(e.getValue());
}
}
Collections.sort(resources, ResourceNameComparator.INSTANCE);
return resources;
}
/**
* Method description
*
*
* @return
*/
protected List<String> getScriptResources()
{
List<String> resources = new ArrayList<>();
Collection<PluginWrapper> wrappers = pluginLoader.getInstalledPlugins();
if (wrappers != null)
{
for (PluginWrapper plugin : wrappers)
{
processPlugin(resources, plugin.getPlugin());
}
}
// fix order of script resources, see https://goo.gl/ok03l4
Collections.sort(resources);
return resources;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resources
* @param plugin
*/
private void processPlugin(List<String> resources, Plugin plugin)
{
PluginResources pluginResources = plugin.getResources();
if (pluginResources != null)
{
Set<String> scriptResources = pluginResources.getScriptResources();
if (scriptResources != null)
{
resources.addAll(scriptResources);
}
}
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 12/02/03
* @author Enter your name here...
*/
protected static class ResourceKey
{
/**
* Constructs ...
*
*
* @param name
* @param type
*/
public ResourceKey(String name, ResourceType type)
{
this.name = name;
this.type = type;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final ResourceKey other = (ResourceKey) obj;
if ((this.name == null)
? (other.name != null)
: !this.name.equals(other.name))
{
return false;
}
return this.type == other.type;
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
int hash = 7;
hash = 53 * hash + ((this.name != null)
? this.name.hashCode()
: 0);
hash = 53 * hash + ((this.type != null)
? this.type.hashCode()
: 0);
return hash;
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public ResourceType getType()
{
return type;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final String name;
/** Field description */
private final ResourceType type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected PluginLoader pluginLoader;
/** Field description */
protected Set<ResourceHandler> resourceHandlers;
/** Field description */
protected Map<ResourceKey, Resource> resourceMap = new HashMap<>();
/** Field description */
protected ServletContext servletContext;
}

View File

@@ -1,165 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
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 */
private static final long serialVersionUID = -1774434741744054387L;
/**
* the logger for AbstractResourceServlet
*/
private static final Logger logger =
LoggerFactory.getLogger(AbstractResourceServlet.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param resourceManager
*/
public AbstractResourceServlet(ResourceManager resourceManager)
{
this.resourceManager = resourceManager;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
protected abstract ResourceType getType();
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
*
* @throws IOException
* @throws ServletException
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String uri = HttpUtil.getStrippedURI(request);
ResourceType type = getType();
String nameSeparator = HttpUtil.SEPARATOR_PATH.concat(
type.getExtension()).concat(
HttpUtil.SEPARATOR_PATH);
int index = uri.indexOf(nameSeparator);
if (index > 0)
{
String name = uri.substring(index + nameSeparator.length());
Resource resource = resourceManager.getResource(type, name);
if (resource != null)
{
printResource(response, resource);
}
else
{
if (logger.isWarnEnabled())
{
logger.warn("no resource with type {} and name {} found", type, name);
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
else
{
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/**
* Method description
*
*
* @param response
* @param resource
*
* @throws IOException
*/
private void printResource(HttpServletResponse response, Resource resource)
throws IOException
{
response.setContentType(resource.getType().getContentType());
try (OutputStream output = response.getOutputStream())
{
resource.copyTo(output);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ResourceManager resourceManager;
}

View File

@@ -1,135 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.PluginLoader;
import sonia.scm.util.ChecksumUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public final class DefaultResource extends AbstractResource
{
/**
* Constructs ...
*
*
* @param pluginLoader
* @param resources
* @param resourceHandlers
* @param type
*
* @throws IOException
*/
public DefaultResource(PluginLoader pluginLoader, List<String> resources,
List<ResourceHandler> resourceHandlers, ResourceType type)
throws IOException
{
super(pluginLoader, resources, resourceHandlers);
this.type = type;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
appendResources(baos);
this.content = baos.toByteArray();
this.name = ChecksumUtil.createChecksum(this.content).concat(".").concat(
type.getExtension());
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param output
*
* @throws IOException
*/
@Override
public void copyTo(OutputStream output) throws IOException
{
output.write(content);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
@Override
public ResourceType getType()
{
return type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final byte[] content;
/** Field description */
private final String name;
/** Field description */
private final ResourceType type;
}

View File

@@ -1,111 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class DefaultResourceManager extends AbstractResourceManager
{
/**
* the logger for DefaultResourceManager
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultResourceManager.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param pluginLoader
* @param resourceHandlers
*/
@Inject
public DefaultResourceManager(PluginLoader pluginLoader,
Set<ResourceHandler> resourceHandlers)
{
super(pluginLoader, resourceHandlers);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resourceMap
*/
@Override
protected void collectResources(Map<ResourceKey, Resource> resourceMap)
{
List<String> resources = getScriptResources();
try
{
Resource resource = new DefaultResource(pluginLoader, resources,
Lists.newArrayList(resourceHandlers),
ResourceType.SCRIPT);
resourceMap.put(new ResourceKey(resource.getName(), ResourceType.SCRIPT),
resource);
}
catch (IOException ex)
{
logger.error("could not collect resources", ex);
}
}
}

View File

@@ -1,135 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.PluginLoader;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public final class DevelopmentResource extends AbstractResource
{
/**
* Constructs ...
*
*
* @param pluginLoader
* @param resources
* @param resourceHandlers
* @param name
* @param type
*/
public DevelopmentResource(PluginLoader pluginLoader, List<String> resources,
List<ResourceHandler> resourceHandlers, String name, ResourceType type)
{
super(pluginLoader, resources, resourceHandlers);
this.type = type;
if (name.startsWith(HttpUtil.SEPARATOR_PATH))
{
name = name.substring(1);
}
String ext = ".".concat(type.getExtension());
if (!name.endsWith(ext))
{
name = name.concat(ext);
}
this.name = name;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param output
*
* @throws IOException
*/
@Override
public void copyTo(OutputStream output) throws IOException
{
appendResources(output);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
@Override
public ResourceType getType()
{
return type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final String name;
/** Field description */
private final ResourceType type;
}

View File

@@ -1,117 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Singleton;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class DevelopmentResourceManager extends AbstractResourceManager
{
/** Field description */
public static final String PREFIX_HANDLER = "handler/";
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param pluginLoader
* @param resourceHandlers
*/
@Inject
public DevelopmentResourceManager(PluginLoader pluginLoader,
Set<ResourceHandler> resourceHandlers)
{
super(pluginLoader, resourceHandlers);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resourceMap
*/
@Override
@SuppressWarnings("unchecked")
protected void collectResources(Map<ResourceKey, Resource> resourceMap)
{
List<String> scripts = getScriptResources();
for (String script : scripts)
{
Resource resource = new DevelopmentResource(pluginLoader,
Arrays.asList(script), Collections.EMPTY_LIST,
script, ResourceType.SCRIPT);
resourceMap.put(new ResourceKey(resource.getName(), ResourceType.SCRIPT),
resource);
}
for (ResourceHandler handler : resourceHandlers)
{
String name = handler.getName();
if (name.startsWith("/"))
{
name = name.substring(1);
}
name = PREFIX_HANDLER.concat(name);
resourceMap.put(new ResourceKey(name, ResourceType.SCRIPT),
new DevelopmentResource(pluginLoader, Collections.EMPTY_LIST,
Arrays.asList(handler), name, ResourceType.SCRIPT));
}
}
}

View File

@@ -1,78 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class ScriptResourceServlet extends AbstractResourceServlet
{
/** Field description */
private static final long serialVersionUID = 1279211769033477225L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param resourceManager
*/
@Inject
public ScriptResourceServlet(ResourceManager resourceManager)
{
super(resourceManager);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
protected ResourceType getType()
{
return ResourceType.SCRIPT;
}
}

View File

@@ -31,20 +31,19 @@
package sonia.scm.security; package sonia.scm.security;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import java.util.Date; import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import org.slf4j.Logger; import java.util.concurrent.TimeUnit;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
/** /**
* Generates cookies and invalidates access token cookies. * Generates cookies and invalidates access token cookies.
@@ -81,7 +80,7 @@ public final class AccessTokenCookieIssuer {
public void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken) { public void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken) {
LOG.trace("create and attach cookie for access token {}", accessToken.getId()); LOG.trace("create and attach cookie for access token {}", accessToken.getId());
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, accessToken.compact()); Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, accessToken.compact());
c.setPath(request.getContextPath()); c.setPath(contextPath(request));
c.setMaxAge(getMaxAge(accessToken)); c.setMaxAge(getMaxAge(accessToken));
c.setHttpOnly(isHttpOnly()); c.setHttpOnly(isHttpOnly());
c.setSecure(isSecure(request)); c.setSecure(isSecure(request));
@@ -100,7 +99,7 @@ public final class AccessTokenCookieIssuer {
LOG.trace("invalidates access token cookie"); LOG.trace("invalidates access token cookie");
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, Util.EMPTY_STRING); Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, Util.EMPTY_STRING);
c.setPath(request.getContextPath()); c.setPath(contextPath(request));
c.setMaxAge(0); c.setMaxAge(0);
c.setHttpOnly(isHttpOnly()); c.setHttpOnly(isHttpOnly());
c.setSecure(isSecure(request)); c.setSecure(isSecure(request));
@@ -109,6 +108,15 @@ public final class AccessTokenCookieIssuer {
response.addCookie(c); response.addCookie(c);
} }
@VisibleForTesting
String contextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (Strings.isNullOrEmpty(contextPath)) {
return "/";
}
return contextPath;
}
private int getMaxAge(AccessToken accessToken){ private int getMaxAge(AccessToken accessToken){
long maxAgeMs = accessToken.getExpiration().getTime() - new Date().getTime(); long maxAgeMs = accessToken.getExpiration().getTime() - new Date().getTime();
return (int) TimeUnit.MILLISECONDS.toSeconds(maxAgeMs); return (int) TimeUnit.MILLISECONDS.toSeconds(maxAgeMs);

View File

@@ -38,30 +38,23 @@ package sonia.scm.template;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ResourceType;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletException; //~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* *
@@ -100,15 +93,12 @@ public class TemplateServlet extends HttpServlet
* @param context * @param context
* @param templateEngineFactory * @param templateEngineFactory
* @param configuration * @param configuration
* @param resourceManager
*/ */
@Inject @Inject
public TemplateServlet(SCMContextProvider context, public TemplateServlet(SCMContextProvider context,
TemplateEngineFactory templateEngineFactory, TemplateEngineFactory templateEngineFactory, ScmConfiguration configuration)
ResourceManager resourceManager, ScmConfiguration configuration)
{ {
this.templateEngineFactory = templateEngineFactory; this.templateEngineFactory = templateEngineFactory;
this.resourceManager = resourceManager;
this.configuration = configuration; this.configuration = configuration;
this.version = context.getVersion(); this.version = context.getVersion();
} }
@@ -123,21 +113,17 @@ public class TemplateServlet extends HttpServlet
* @param response * @param response
* *
* @throws IOException * @throws IOException
* @throws ServletException
*/ */
@Override @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
throws ServletException, IOException
{ {
Map<String, Object> params = new HashMap<String, Object>(); Map<String, Object> params = new HashMap<>();
String contextPath = request.getContextPath(); String contextPath = request.getContextPath();
params.put("contextPath", contextPath); params.put("contextPath", contextPath);
params.put("configuration", configuration); params.put("configuration", configuration);
params.put("version", version); params.put("version", version);
params.put("scripts", resourceManager.getResources(ResourceType.SCRIPT));
Locale l = request.getLocale(); Locale l = request.getLocale();
if (l == null) if (l == null)
@@ -242,9 +228,6 @@ public class TemplateServlet extends HttpServlet
/** Field description */ /** Field description */
private final ScmConfiguration configuration; private final ScmConfiguration configuration;
/** Field description */
private final ResourceManager resourceManager;
/** Field description */ /** Field description */
private final TemplateEngineFactory templateEngineFactory; private final TemplateEngineFactory templateEngineFactory;

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@@ -1,247 +0,0 @@
<!--
Copyright (c) 2010, Sebastian Sdorra
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of SCM-Manager; nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
http://bitbucket.org/sdorra/scm-manager
-->
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=8">
<link rel="shortcut icon" type="image/vnd.microsoft.icon" href="resources/images/favicon.ico" />
<!--
<link rel="icon" type="image/png" href="resources/images/favicon.png" />
-->
<script type="text/javascript">
var i18n = {
locale: '{{locale}}',
country: '{{country}}'
};
var scmGlobalConfiguration = {
anonymousAccessEnabled: {{configuration.anonymousAccessEnabled}}
};
</script>
<!--compress-->
<link rel="stylesheet" type="text/css" href="resources/extjs/resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="resources/extjs/resources/css/xtheme-scmslate.css" />
<link rel="stylesheet" type="text/css" href="resources/css/style.css" />
<!-- moment.js -->
<script type="text/javascript" src="resources/moment/moment.js"></script>
<!-- core overrides -->
<script type="text/javascript" src="resources/js/sonia.core.js"></script>
<!-- extjs -->
<script type="text/javascript" src="resources/extjs/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="resources/extjs/ext-all-debug.js"></script>
<script type="text/javascript" src="resources/extjs/util/CheckColumn.js"></script>
<script type="text/javascript" src="resources/extjs/util/FileUploadField.js"></script>
<!-- sonia.global -->
<script type="text/javascript" src="resources/js/sonia.global.js"></script>
<script type="text/javascript" src="resources/js/sonia.history.js"></script>
<!-- sonia.util -->
<script type="text/javascript" src="resources/js/util/sonia.util.js"></script>
<script type="text/javascript" src="resources/js/util/sonia.util.link.js"></script>
<script type="text/javascript" src="resources/js/util/sonia.util.tip.js"></script>
<!-- sonia.override -->
<script type="text/javascript" src="resources/js/override/ext.form.vtypes.js"></script>
<script type="text/javascript" src="resources/js/override/ext.form.field.js"></script>
<script type="text/javascript" src="resources/js/override/ext.util.format.js"></script>
<script type="text/javascript" src="resources/js/override/ext.data.store.js"></script>
<script type="text/javascript" src="resources/js/override/ext.grid.columnmodel.js"></script>
<script type="text/javascript" src="resources/js/override/ext.grid.gridpanel.js"></script>
<script type="text/javascript" src="resources/js/override/ext.grid.groupingview.js"></script>
<!-- sonia.state -->
<script type="text/javascript" src="resources/js/uistate/sonia.uistate.js"></script>
<script type="text/javascript" src="resources/js/uistate/sonia.uistate.webstorageprovider.js"></script>
<!-- sonia.navigation -->
<script type="text/javascript" src="resources/js/navigation/sonia.navigation.js"></script>
<script type="text/javascript" src="resources/js/navigation/sonia.navigation.navsection.js"></script>
<script type="text/javascript" src="resources/js/navigation/sonia.navigation.navpanel.js"></script>
<!-- sonia.login -->
<script type="text/javascript" src="resources/js/login/sonia.login.js"></script>
<script type="text/javascript" src="resources/js/login/sonia.login.form.js"></script>
<script type="text/javascript" src="resources/js/login/sonia.login.window.js"></script>
<!-- sonia.panel -->
<script type="text/javascript" src="resources/js/panel/sonia.panel.js"></script>
<script type="text/javascript" src="resources/js/panel/sonia.panel.syntaxhighlighterpanel.js"></script>
<!-- sonia.rest -->
<script type="text/javascript" src="resources/js/rest/sonia.rest.js"></script>
<script type="text/javascript" src="resources/js/rest/sonia.rest.jsonstore.js"></script>
<script type="text/javascript" src="resources/js/rest/sonia.rest.panel.js"></script>
<script type="text/javascript" src="resources/js/rest/sonia.rest.grid.js"></script>
<script type="text/javascript" src="resources/js/rest/sonia.rest.formpanel.js"></script>
<!-- sonia.repository -->
<script type="text/javascript" src="resources/js/repository/sonia.repository.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.branchcombobox.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.tagcombobox.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.grid.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.infopanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.extendedinfopanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.formpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.settingsformpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.permissionformpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.panel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.propertiesformpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.changesetviewergrid.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.changesetviewerpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.blamepanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.diffpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.contentpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.repositorybrowser.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.importwindow.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.commitpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.changesetpanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.healthcheckfailure.js"></script>
<!-- sonia.user -->
<script type="text/javascript" src="resources/js/user/sonia.user.js"></script>
<script type="text/javascript" src="resources/js/user/sonia.user.grid.js"></script>
<script type="text/javascript" src="resources/js/user/sonia.user.formpanel.js"></script>
<script type="text/javascript" src="resources/js/user/sonia.user.panel.js"></script>
<!-- sonia.group -->
<script type="text/javascript" src="resources/js/group/sonia.group.js"></script>
<script type="text/javascript" src="resources/js/group/sonia.group.grid.js"></script>
<script type="text/javascript" src="resources/js/group/sonia.group.formpanel.js"></script>
<script type="text/javascript" src="resources/js/group/sonia.group.propertiesformpanel.js"></script>
<script type="text/javascript" src="resources/js/group/sonia.group.memberformpanel.js"></script>
<script type="text/javascript" src="resources/js/group/sonia.group.panel.js"></script>
<!-- sonia.security -->
<script type="text/javascript" src="resources/js/security/sonia.security.js"></script>
<script type="text/javascript" src="resources/js/security/sonia.security.permissionspanel.js"></script>
<!-- sonia.config -->
<script type="text/javascript" src="resources/js/config/sonia.config.js"></script>
<script type="text/javascript" src="resources/js/config/sonia.config.configpanel.js"></script>
<script type="text/javascript" src="resources/js/config/sonia.config.repositoryconfig.js"></script>
<script type="text/javascript" src="resources/js/config/sonia.config.scmconfigpanel.js"></script>
<script type="text/javascript" src="resources/js/config/sonia.config.configform.js"></script>
<script type="text/javascript" src="resources/js/config/sonia.config.simpleconfigform.js"></script>
<!-- sonia.action -->
<script type="text/javascript" src="resources/js/action/sonia.action.js"></script>
<script type="text/javascript" src="resources/js/action/sonia.action.changepasswordwindow.js"></script>
<script type="text/javascript" src="resources/js/action/sonia.action.exceptionwindow.js"></script>
<!-- sonia.plugin -->
<script type="text/javascript" src="resources/js/plugin/sonia.plugin.js"></script>
<script type="text/javascript" src="resources/js/plugin/sonia.plugin.uploadform.js"></script>
<script type="text/javascript" src="resources/js/plugin/sonia.plugin.center.js"></script>
<script type="text/javascript" src="resources/js/plugin/sonia.plugin.store.js"></script>
<script type="text/javascript" src="resources/js/plugin/sonia.plugin.grid.js"></script>
<!-- sonia.scm -->
<script type="text/javascript" src="resources/js/sonia.scm.js"></script>
<!--/compress-->
<!-- plugins -->
{{#scripts}}
<script type="text/javascript" src="plugins/resources/js/{{name}}"></script>
{{/scripts}}
{{#nonDefaultLocale}}
<script type="text/javascript" src="resources/moment/lang/{{country}}.js"></script>
<script type="text/javascript" src="resources/extjs/i18n/ext-lang-{{country}}.js"></script>
<script type="text/javascript" src="resources/js/i18n/{{country}}.js"></script>
{{/nonDefaultLocale}}
<title>SCM Manager</title>
</head>
<body>
<!-- use class="x-hide-display" to prevent a brief flicker of the content -->
<div id="north" class="x-hide-display">
<div id="header" style="visibility: visible; ">
<div id="appTitle" class="left-side">
<img src="resources/images/scm-logo.jpg" alt="SCM Manager">
</div>
<div id="logo" class="right-side">
<!--
<img src="resources/images/logo.gif" alt="">
-->
</div>
</div>
</div>
<div id="west" class="x-hide-display">
</div>
<div id="repository-tab" class="x-hide-display">
<h1>SCM Managers</h1>
</div>
<div id="props-panel" class="x-hide-display" style="width:200px;height:200px;overflow:hidden;">
</div>
<div id="south" class="x-hide-display">
<div id="footer" style="visibility: visible; ">
<div class="left-side">
&copy; <a target="_blank" href="http://bitbucket.org/sdorra/scm-manager">SCM Manager</a>
</div>
<div class="right-side">
<span id="scm-userinfo"></span> {{version}}
</div>
</div>
</div>
<!-- Fields required for history management -->
<form id="history-form" class="x-hidden">
<input type="hidden" id="x-history-field" />
<iframe id="x-history-frame"></iframe>
</form>
<noscript>
<div class="noscript-container">
<h1>SCM-Manager</h1>
<p>
<b>Warning:</b> SCM-Manager requires JavaScript.
Please enable JavaScript in your browser and try again.
<p>
</div>
</noscript>
</body>
</html>

View File

@@ -1,254 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
/*
Document : style
Created on : Aug 18, 2010, 3:14:05 PM
Author : Sebastian Sdorra
Description:
Purpose of the stylesheet follows.
*/
/*
TODO customize this sample style
Syntax recommendation http://www.w3.org/TR/REC-CSS2/
*/
body {
font-size: 12px;
}
a {
color: #004077;
text-decoration: none;
}
a:hover {
color: #004077;
}
a:visited {
color: #004077;
}
a.scm-browser:hover {
cursor: pointer;
}
a.scm-link:hover {
cursor: pointer;
}
#north-panel {
background-image: url(../images/header-backgound.jpg);
background-repeat: repeat-x;
}
.right-side {
float: right;
}
.left-side {
float: left;
}
#south {
font-size: 12px;
}
#footer a {
color: #666;
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
margin-top: 20px;
}
.scm-form-help-button {
vertical-align: middle;
margin-left: 2px;
}
.scm-form-combo-help-button {
vertical-align: middle;
margin-left: 19px;
}
.scm-form-textarea-help-button {
vertical-align: top;
margin-left: 2px;
}
.scm-form-fileupload-help-button {
position: absolute;
right: -19px;
}
.scm-nav-item {
cursor: pointer;
}
.cs-mod {
height: 16px;
}
.cs-mod img {
vertical-align: middle;
}
.cs-mod-txt {
margin: 0 3px;
}
.changeset-tags {
margin-bottom: 5px;
}
.cs-tag, .cs-branch {
height: 18px;
vertical-align: middle;
display: inline-block;
border: 1px solid gray;
border-radius: 4px;
}
.cs-tag a, .cs-branch a {
padding: 5px;
}
.cs-tag {
background-image: url(../images/tag.gif);
background-repeat: no-repeat;
background-position: 1px 1px;
}
.cs-tag a {
margin-left: 16px;
}
.scm-commit {
margin-bottom: 65px;
}
.scm-commit h1 {
margin-bottom: 5px;
}
ul.scm-modifications {
border-top: 1px solid darkgray;
border-bottom: 1px solid darkgray;
clear: both;
vertical-align: middle;
}
ul.scm-modifications li {
background-color: transparent;
background-repeat: no-repeat;
background-position: 0 0.2em;
padding: 3px 3px 3px 20px;
display: block;
}
li.scm-added {
background-image: url(../images/add.png);
}
li.scm-modified {
/* TODO create png image */
background-image: url(../images/modify.gif);
}
li.scm-removed {
background-image: url(../images/delete.png);
}
div.noscript-container {
background-color: #ffffff;
margin: 10px;
color: #202020;
font-family: Verdana,Helvetica,Arial,sans-serif;
font-size: 12px;
margin: 1em;
}
div.noscript-container h1 {
font-size: 18px;
font-family: Arial, "Arial CE", "Lucida Grande CE", lucida, "Helvetica CE", sans-serif;
font-weight: bold;
margin: 0.5em 0em;
padding: 0px;
color: #D20005;
border-bottom: 1px solid #AFAFAF;
}
/*
* FileUploadField component styles
*/
.x-form-file-wrap {
position: relative;
height: 22px;
}
.x-form-file-wrap .x-form-file {
position: absolute;
right: 0;
-moz-opacity: 0;
filter:alpha(opacity: 0);
opacity: 0;
z-index: 2;
height: 22px;
}
.x-form-file-wrap .x-form-file-btn {
position: absolute;
right: 0;
z-index: 1;
}
.x-form-file-wrap .x-form-file-text {
position: absolute;
left: 0;
z-index: 3;
color: #777;
}
.upload-icon {
background: url('../images/add.png') no-repeat 0 0 !important;
}
.unhealthy {
color: red;
}
/** import **/
.import-fu {
margin-right: 24px;
}
.import-fu input {
width: 215px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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