Commits

Sebastian Sdorra committed 92aa64e

added ui form to install plugin packages

Comments (0)

Files changed (7)

scm-webapp/src/main/java/sonia/scm/api/rest/RestActionResultMessageWriter.java

+/**
+ * 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.api.rest;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@Provider
+public class RestActionResultMessageWriter
+  implements MessageBodyWriter<RestActionResult>
+{
+
+  /**
+   * Method description
+   *
+   *
+   * @param result
+   * @param type
+   * @param genericType
+   * @param annotations
+   * @param mediaType
+   * @param httpHeaders
+   * @param entityStream
+   *
+   * @throws IOException
+   * @throws WebApplicationException
+   */
+  @Override
+  public void writeTo(RestActionResult result, Class<?> type, Type genericType,
+    Annotation[] annotations, MediaType mediaType,
+    MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+    throws IOException, WebApplicationException
+  {
+    String v =
+      "{\"success\": ".concat(String.valueOf(result.isSuccess())).concat("}");
+
+    entityStream.write(v.getBytes(Charsets.UTF_8));
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Method description
+   *
+   *
+   * @param result
+   * @param type
+   * @param genericType
+   * @param annotations
+   * @param mediaType
+   *
+   * @return
+   */
+  @Override
+  public long getSize(RestActionResult result, Class<?> type, Type genericType,
+    Annotation[] annotations, MediaType mediaType)
+  {
+    return -1;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param type
+   * @param genericType
+   * @param annotations
+   * @param mediaType
+   *
+   * @return
+   */
+  @Override
+  public boolean isWriteable(Class<?> type, Type genericType,
+    Annotation[] annotations, MediaType mediaType)
+  {
+    return RestActionResult.class.isAssignableFrom(type);
+  }
+}

scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java

 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import sonia.scm.api.rest.RestActionResult;
 import sonia.scm.plugin.DefaultPluginManager;
 import sonia.scm.plugin.OverviewPluginFilter;
 import sonia.scm.plugin.PluginConditionFailedException;
    *   <li>500 internal server error</li>
    * </ul>
    *
-   *
-   *
    * @param uploadedInputStream
    * @return
    *
   @POST
   @Path("install-package")
   @Consumes(MediaType.MULTIPART_FORM_DATA)
+  @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
   public Response install(
     @FormDataParam("package") InputStream uploadedInputStream)
     throws IOException
     try
     {
       pluginManager.installPackage(uploadedInputStream);
-      response = Response.ok().build();
+      response = Response.ok(new RestActionResult(true)).build();
     }
     catch (PluginConditionFailedException ex)
     {
       logger.warn(
         "could not install plugin package, because the condition failed", ex);
-      response = Response.status(Status.CONFLICT).build();
+      response = Response.status(Status.CONFLICT).entity(
+        new RestActionResult(false)).build();
+    }
+    catch (Exception ex)
+    {
+      logger.warn("plugin installation failed", ex);
+      response =
+        Response.serverError().entity(new RestActionResult(false)).build();
     }
 
     return response;
   }
 
   /**
+   * Installs a plugin from a package. This method is a workaround for ExtJS 
+   * file upload, which requires text/html as content-type.<br />
+   * <br />
+   * <ul>
+   *   <li>200 success</li>
+   *   <li>412 conflict</li>
+   *   <li>500 internal server error</li>
+   * </ul>
+   *
+   * @param uploadedInputStream
+   * @return
+   *
+   * @throws IOException
+   */
+  @POST
+  @Path("install-package.html")
+  @Consumes(MediaType.MULTIPART_FORM_DATA)
+  @Produces(MediaType.TEXT_HTML)
+  public Response installFromUI(
+    @FormDataParam("package") InputStream uploadedInputStream)
+    throws IOException
+  {
+    return install(uploadedInputStream);
+  }
+
+  /**
    * Uninstalls a plugin.<br />
    * <br />
    * <ul>

scm-webapp/src/main/webapp/index.mustache

     <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>
 
     <!-- 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>

scm-webapp/src/main/webapp/resources/css/style.css

   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;
+}

scm-webapp/src/main/webapp/resources/extjs/util/FileUploadField.js

+/*!
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.FileUploadField
+ * @extends Ext.form.TextField
+ * Creates a file upload field.
+ * @xtype fileuploadfield
+ */
+Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
+    /**
+     * @cfg {String} buttonText The button text to display on the upload button (defaults to
+     * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
+     * value will be used instead if available.
+     */
+    buttonText: 'Browse...',
+    /**
+     * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
+     * text field (defaults to false).  If true, all inherited TextField members will still be available.
+     */
+    buttonOnly: false,
+    /**
+     * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
+     * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
+     */
+    buttonOffset: 3,
+    /**
+     * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
+     */
+
+    // private
+    readOnly: true,
+
+    /**
+     * @hide
+     * @method autoSize
+     */
+    autoSize: Ext.emptyFn,
+
+    // private
+    initComponent: function(){
+        Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
+
+        this.addEvents(
+            /**
+             * @event fileselected
+             * Fires when the underlying file input field's value has changed from the user
+             * selecting a new file from the system file selection dialog.
+             * @param {Ext.ux.form.FileUploadField} this
+             * @param {String} value The file value returned by the underlying file input field
+             */
+            'fileselected'
+        );
+    },
+
+    // private
+    onRender : function(ct, position){
+        Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);
+
+        this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
+        this.el.addClass('x-form-file-text');
+        this.el.dom.removeAttribute('name');
+        this.createFileInput();
+
+        var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
+            text: this.buttonText
+        });
+        this.button = new Ext.Button(Ext.apply(btnCfg, {
+            renderTo: this.wrap,
+            cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
+        }));
+
+        if(this.buttonOnly){
+            this.el.hide();
+            this.wrap.setWidth(this.button.getEl().getWidth());
+        }
+
+        this.bindListeners();
+        this.resizeEl = this.positionEl = this.wrap;
+    },
+    
+    bindListeners: function(){
+        this.fileInput.on({
+            scope: this,
+            mouseenter: function() {
+                this.button.addClass(['x-btn-over','x-btn-focus'])
+            },
+            mouseleave: function(){
+                this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
+            },
+            mousedown: function(){
+                this.button.addClass('x-btn-click')
+            },
+            mouseup: function(){
+                this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
+            },
+            change: function(){
+                var v = this.fileInput.dom.value;
+                this.setValue(v);
+                this.fireEvent('fileselected', this, v);    
+            }
+        }); 
+    },
+    
+    createFileInput : function() {
+        this.fileInput = this.wrap.createChild({
+            id: this.getFileInputId(),
+            name: this.name||this.getId(),
+            cls: 'x-form-file',
+            tag: 'input',
+            type: 'file',
+            size: 1
+        });
+    },
+    
+    reset : function(){
+        if (this.rendered) {
+            this.fileInput.remove();
+            this.createFileInput();
+            this.bindListeners();
+        }
+        Ext.ux.form.FileUploadField.superclass.reset.call(this);
+    },
+
+    // private
+    getFileInputId: function(){
+        return this.id + '-file';
+    },
+
+    // private
+    onResize : function(w, h){
+        Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
+
+        this.wrap.setWidth(w);
+
+        if(!this.buttonOnly){
+            var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
+            this.el.setWidth(w);
+        }
+    },
+
+    // private
+    onDestroy: function(){
+        Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
+        Ext.destroy(this.fileInput, this.button, this.wrap);
+    },
+    
+    onDisable: function(){
+        Ext.ux.form.FileUploadField.superclass.onDisable.call(this);
+        this.doDisable(true);
+    },
+    
+    onEnable: function(){
+        Ext.ux.form.FileUploadField.superclass.onEnable.call(this);
+        this.doDisable(false);
+
+    },
+    
+    // private
+    doDisable: function(disabled){
+        this.fileInput.dom.disabled = disabled;
+        this.button.setDisabled(disabled);
+    },
+
+
+    // private
+    preFocus : Ext.emptyFn,
+
+    // private
+    alignErrorIcon : function(){
+        this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+    }
+
+});
+
+Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
+
+// backwards compat
+Ext.form.FileUploadField = Ext.ux.form.FileUploadField;

scm-webapp/src/main/webapp/resources/js/plugin/sonia.plugin.grid.js

   // grid
   emptyText: 'No plugins avaiable',
   
+  // TODO i18n
+  
   // buttons
   btnReload: 'Reload',
+  btnIconReload: 'resources/images/reload.png',
+  btnInstallPackage: 'Install Package',
+  btnIconInstallPackage: 'resources/images/add.png',
+  
+  uploadWindowTitle: 'Upload Plugin-Package',
 
   actionLinkTemplate: '<a style="cursor: pointer;" onclick="Sonia.plugin.CenterInstance.{1}(\'{2}\')">{0}</a>',
 
         groupTextTpl: '{group} ({[values.rs.length]} {[values.rs.length > 1 ? "Plugins" : "Plugin"]})'
       }),
       tbar: [{
+        text: this.btnInstallPackage,
+        icon: this.btnIconInstallPackage,
+        handler: function(){
+          var window = new Ext.Window({
+            title: this.uploadWindowTitle
+          });
+          window.add({
+            xtype: 'pluginPackageUploadForm',
+            listeners: {
+              success: {
+                fn: function(){
+                  this.close();
+                  Ext.MessageBox.alert(
+                    Sonia.plugin.CenterInstance.installSuccessText,
+                    Sonia.plugin.CenterInstance.restartText
+                  );
+                },
+                scope: window
+              },
+              failure: {
+                fn: function(){
+                  this.close();
+                  Ext.MessageBox.alert(
+                    Sonia.plugin.CenterInstance.errorTitleText,
+                    Sonia.plugin.CenterInstance.installFailedText
+                  );
+                },
+                scope: window
+              }
+            }
+          });
+          window.show();
+        },
+        scope: this
+      },'|',{
         text: this.btnReload,
-        icon: 'resources/images/reload.png',
+        icon: this.btnIconReload,
         handler: function(){
           this.getStore().reload();
         },

scm-webapp/src/main/webapp/resources/js/plugin/sonia.plugin.uploadform.js

+/* *
+ * 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
+ * 
+ */
+
+Sonia.plugin.UploadForm = Ext.extend(Ext.FormPanel, {
+  
+  // TODO i18n
+  
+  emptyText: 'Select an Plugin-Package',
+  uploadFieldLabel: 'Package',
+  waitMsg: 'Uploading your package ...',
+  btnUpload: 'Upload',
+  btnReset: 'Reset',
+  
+  initComponent: function(){
+    this.addEvents('success', 'failure');
+    
+    var config = {
+      fileUpload: true,
+      width: 500,
+      frame: false,
+      autoHeight: true,
+      labelWidth: 50,
+      bodyStyle: 'padding: 5px 0 0 10px;',
+      defaults: {
+        anchor: '95%',
+        allowBlank: false,
+        msgTarget: 'side'
+      },
+      items: [{
+        xtype: 'fileuploadfield',
+        id: 'form-file',
+        emptyText: this.emptyText,
+        fieldLabel: this.uploadFieldLabel,
+        name: 'package',
+        buttonText: '',
+        buttonCfg: {
+          iconCls: 'upload-icon'
+        }
+      }],
+      buttons: [{
+        text: this.btnUpload,
+        handler: function(){
+          if(this.getForm().isValid()){
+            this.getForm().submit({
+              url: restUrl + 'plugins/install-package.html',
+              waitMsg: this.waitMsg,
+              success: function(form, action){
+                if (debug){
+                  console.debug('upload success');
+                }
+                this.fireEvent('success');
+              },
+              failure: function(form, action){
+                if (debug){
+                  console.debug('upload failed');
+                }
+                this.fireEvent('failure');
+              },
+              scope: this
+            });
+          }
+        },
+        scope: this
+      },{
+        text: this.btnReset,
+        handler: function(){
+          this.getForm().reset();
+        },
+        scope: this
+      }]
+    };
+    
+    Ext.apply(this, Ext.apply(this.initialConfig, config));
+    Sonia.plugin.UploadForm.superclass.initComponent.apply(this, arguments);
+  }
+  
+});
+
+Ext.reg('pluginPackageUploadForm', Sonia.plugin.UploadForm);