From 054ea07a3486f1746bbb628c03a9ec7eb21cbe1e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 27 Nov 2014 15:48:09 +0100 Subject: [PATCH 1/6] start implementation of new repository import wizard --- .../sonia.repository.importwindow.js | 433 ++++++++++++------ 1 file changed, 286 insertions(+), 147 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 53ea0dcf19..617eef95aa 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -31,9 +31,52 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{ - titleText: 'Import Repositories', - okText: 'Ok', - closeText: 'Close', + title: 'Repository Import 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: 'scmRepositoryImportWizard', + xtype: 'scmRepositoryImportWizard', + listeners: { + finish: { + fn: this.onFinish, + scope: this + } + } + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + Sonia.repository.ImportWindow.superclass.initComponent.apply(this, arguments); + }, + + onFinish: function(config){ + this.fireEvent('finish', config); + this.close(); + } + +}); + +Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { + + // text + backText: 'Back', + nextText: 'Next', + finishText: 'Finish', // cache importForm: null, @@ -42,114 +85,220 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{ importJobsFinished: 0, importJobs: 0, + // settings + repositoryType: null, + initComponent: function(){ - this.imported = []; - this.importJobsFinished = 0; - this.importJobs = 0; + this.addEvents('finish'); + + + var importedStore = new Ext.data.JsonStore({ + fields: ['type', 'name'] + }); + // store.loadData(this.imported); + + var importedColModel = new Ext.grid.ColumnModel({ + defaults: { + sortable: true, + scope: this + }, + columns: [ + {id: 'name', header: 'Name', dataIndex: 'name'}, + {id: 'type', header: 'Type', dataIndex: 'type'} + ] + }); + + var types = []; + + Ext.each(state.repositoryTypes, function(repositoryType){ + console.log(repositoryType); + types.push({ + boxLabel: repositoryType.displayName, + name: 'repositoryType', + inputValue: repositoryType.name, + checked: false + }); + }); var config = { - layout:'fit', - width:300, - height:170, - closable: true, - resizable: false, - plain: true, - border: false, - modal: true, - title: this.titleText, - items: [{ - id: 'importRepositoryForm', - frame: true, - xtype: 'form', - defaultType: 'checkbox' - }], - buttons: [{ - id: 'startRepositoryImportButton', - text: this.okText, - formBind: true, - scope: this, - handler: this.importRepositories + 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 },{ - text: this.closeText, - scope: this, - handler: this.close + id: 'move-next', + text: this.nextText, + handler: this.navHandler.createDelegate(this, [1]), + disabled: true, + scope: this + },{ + id: 'finish', + text: this.finishText, + handler: this.applyChanges, + disabled: true, + scope: this }], - listeners: { - afterrender: { - fn: this.readImportableTypes, - scope: this + items: [{ + id: 'repositoryTypeLayout', + items: [{ + id: 'chooseRepositoryType', + xtype: 'radiogroup', + name: 'chooseRepositoryType', + columns: 1, + items: [types], + listeners: { + change: function(){ + Ext.getCmp('move-next').setDisabled(false); + } + } + }] + },{ + id: 'importTypeLayout', + items: [{ + id: 'chooseImportType', + xtype: 'radiogroup', + name: 'chooseImportType', + columns: 1, + items: [{ + id: 'importTypeDirectory', + boxLabel: 'Import from directory', + name: 'importType', + inputValue: 'directory', + disabled: false + },{ + id: 'importTypeURL', + boxLabel: 'Import from URL', + name: 'importType', + inputValue: 'url', + checked: false, + disabled: true + },{ + id: 'importTypeFile', + boxLabel: 'Import from File', + name: 'importType', + inputValue: 'file', + checked: false, + disabled: true + }], + listeners: { + change: function(){ + Ext.getCmp('move-next').setDisabled(false); + } + } + }] + },{ + id: 'importUrlLayout', + layout: 'form', + defaults: { + width: 250 + }, + items: [{ + id: 'importUrlName', + xtype: 'textfield', + fieldLabel: 'Repository name', + name: 'importUrlName', + type: 'textfield', + disabled: false + },{ + id: 'importUrl', + xtype: 'textfield', + fieldLabel: 'Import URL', + name: 'importUrl', + disabled: false + }] + },{ + id: 'importFileLayout', + layout: 'form', + defaults: { + width: 250 + }, + items: [{ + id: 'importFileName', + xtype: 'textfield', + fieldLabel: 'Repository name', + name: 'importFileName', + type: 'textfield', + disabled: false + },{ + id: 'importFile', + xtype: 'textfield', + fieldLabel: 'Import File', + name: 'importFile', + disabled: false + }] + },{ + id: 'importFinishedLayout', + layout: 'form', + defaults: { + width: 250 + }, + items: [{ + id: 'importedGrid', + xtype: 'grid', + autoExpandColumn: 'name', + store: importedStore, + colModel: importedColModel, + height: 100 + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + Sonia.repository.ImportPanel.superclass.initComponent.apply(this, arguments); + }, + + navHandler: function(direction){ + var layout = this.getLayout(); + var id = layout.activeItem.id; + + var next = -1; + + if ( id === 'repositoryTypeLayout' && direction === 1 ){ + this.repositoryType = Ext.getCmp('chooseRepositoryType').getValue().getRawValue(); + console.log('rt: ' + this.repositoryType); + this.enableAvailableImportTypes(); + next = 1; + } + else if ( id === 'importTypeLayout' && direction === -1 ){ + next = 0; + Ext.getCmp('move-prev').setDisabled(true); + Ext.getCmp('move-next').setDisabled(false); + } + else if ( id === 'importTypeLayout' && direction === 1 ){ + var v = Ext.getCmp('chooseImportType').getValue(); + if ( v ){ + switch (v.getRawValue()){ + case 'directory': + this.importFromDirectory(layout); + break; + case 'url': + next = 2; + break; + case 'file': + next = 3; + break; } } - }; - Ext.apply(this, Ext.apply(this.initialConfig, config)); - Sonia.repository.ImportWindow.superclass.initComponent.apply(this, arguments); - }, - - readImportableTypes: function(){ - if (debug){ - console.debug('read importable types'); + } + else if ( (id === 'importUrlLayout' || id === 'importFileLayout') && direction === -1 ) + { + next = 1; } - Ext.Ajax.request({ - url: restUrl + 'import/repositories.json', - method: 'GET', - scope: this, - success: function(response){ - var obj = Ext.decode(response.responseText); - this.renderTypeCheckboxes(obj); - this.doLayout(); - }, - failure: function(result){ - main.handleRestFailure( - result, - this.errorTitleText, - this.errorMsgText - ); - } - }); - - }, - - renderTypeCheckboxes: function(types){ - Ext.each(types, function(type){ - this.renderCheckbox(type); - }, this); - }, - - getImportForm: function(){ - if (!this.importForm){ - this.importForm = Ext.getCmp('importRepositoryForm'); + if ( next >= 0 ){ + layout.setActiveItem(next); } - return this.importForm; - }, - - renderCheckbox: function(type){ - this.getImportForm().add({ - xtype: 'checkbox', - name: 'type', - fieldLabel: type.displayName, - inputValue: type.name - }); - }, - - importRepositories: function(){ - if (debug){ - console.debug('start import of repositories'); - } - var form = this.getImportForm().getForm(); - var values = form.getValues().type; - if ( values ){ - if ( Ext.isArray(values) ){ - this.importJobs = values.length; - } else { - this.importJobs = 1; - } - } else { - this.importJobs = 0; - } - Ext.each(values, function(value){ - this.importRepositoriesOfType(value); - }, this); }, appendImported: function(repositories){ @@ -161,60 +310,22 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{ if (debug){ console.debug( 'import of ' + this.importJobsFinished + ' jobs finished' ); } - this.printImported(); + Ext.getCmp('importedGrid').getStore().loadData(this.imported); + Ext.getCmp('move-next').setDisabled(true); + Ext.getCmp('move-prev').setDisabled(true); + Ext.getCmp('finish').setDisabled(false); } }, - printImported: function(){ - var store = new Ext.data.JsonStore({ - fields: ['type', 'name'] - }); - store.loadData(this.imported); - - var colModel = new Ext.grid.ColumnModel({ - defaults: { - sortable: true, - scope: this - }, - columns: [ - {id: 'name', header: 'Name', dataIndex: 'name'}, - {id: 'type', header: 'Type', dataIndex: 'type'} - ] - }); - - this.getImportForm().add({ - xtype: 'grid', - autoExpandColumn: 'name', - store: store, - colModel: colModel, - height: 100 - }); - var h = this.getHeight(); - this.setHeight( h + 100 ); - this.doLayout(); - - // reload repositories panel - var panel = Ext.getCmp('repositories'); - if (panel){ - panel.getGrid().reload(); - } - }, - - importRepositoriesOfType: function(type){ - if (debug){ - console.debug('start import of ' + type + ' repositories'); - } - var b = Ext.getCmp('startRepositoryImportButton'); - if ( b ){ - b.setDisabled(true); - } + importFromDirectory: function(layout){ Ext.Ajax.request({ - url: restUrl + 'import/repositories/' + type + '.json', + url: restUrl + 'import/repositories/' + this.repositoryType + '.json', method: 'POST', scope: this, success: function(response){ var obj = Ext.decode(response.responseText); this.appendImported(obj); + layout.setActiveItem(4); }, failure: function(result){ main.handleRestFailure( @@ -224,6 +335,34 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{ ); } }); + }, + + enableAvailableImportTypes: function(){ + var type = null; + Ext.each(state.repositoryTypes, function(repositoryType){ + if (repositoryType.name === this.repositoryType){ + type = repositoryType; + } + }, this); + + if ( type !== null ){ + Ext.getCmp('chooseImportType').setValue(null); + Ext.getCmp('move-next').setDisabled(true); + Ext.getCmp('move-prev').setDisabled(false); + Ext.getCmp('importTypeURL').setDisabled(type.supportedCommands.indexOf('PULL') < 0); + Ext.getCmp('importTypeFile').setDisabled(type.supportedCommands.indexOf('UNBUNDLE') < 0); + } + }, + + applyChanges: function(){ + var panel = Ext.getCmp('repositories'); + if (panel){ + panel.getGrid().reload(); + } + this.fireEvent('finish'); } -}); \ No newline at end of file +}); + +// register xtype +Ext.reg('scmRepositoryImportWizard', Sonia.repository.ImportPanel); \ No newline at end of file From c5bb3c70b633a0df3416d7062524bdaa63ad7f5a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Nov 2014 13:58:18 +0100 Subject: [PATCH 2/6] improve layout of import wizard --- scm-webapp/src/main/webapp/index.mustache | 1 + .../src/main/webapp/resources/css/style.css | 14 ++++ .../resources/js/override/ext.form.field.js | 3 + .../sonia.repository.importwindow.js | 80 ++++++++++++++----- .../resources/js/util/sonia.util.tip.js | 77 ++++++++++++++++++ 5 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js diff --git a/scm-webapp/src/main/webapp/index.mustache b/scm-webapp/src/main/webapp/index.mustache index e29c2cd556..4c2c3c913c 100644 --- a/scm-webapp/src/main/webapp/index.mustache +++ b/scm-webapp/src/main/webapp/index.mustache @@ -77,6 +77,7 @@ + diff --git a/scm-webapp/src/main/webapp/resources/css/style.css b/scm-webapp/src/main/webapp/resources/css/style.css index 2ce002295c..355e8afcbe 100644 --- a/scm-webapp/src/main/webapp/resources/css/style.css +++ b/scm-webapp/src/main/webapp/resources/css/style.css @@ -105,6 +105,11 @@ a.scm-link:hover { margin-left: 2px; } +.scm-form-fileupload-help-button { + position: absolute; + right: -19px; +} + .scm-nav-item { cursor: pointer; } @@ -238,3 +243,12 @@ div.noscript-container h1 { .unhealthy { color: red; } + +/** import **/ +.import-fu { + margin-right: 24px; +} + +.import-fu input { + width: 215px; +} diff --git a/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js b/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js index 548599e3a0..61abb54b88 100644 --- a/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js +++ b/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js @@ -77,6 +77,9 @@ Ext.override(Ext.form.Field, { cls = 'scm-form-combo-help-button'; } break; + case 'fileuploadfield': + cls = 'scm-form-fileupload-help-button'; + break; case 'textarea': cls = 'scm-form-textarea-help-button'; break; diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 617eef95aa..233b491f67 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -41,7 +41,7 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{ title: this.title, layout: 'fit', width: 420, - height: 140, + height: 190, closable: true, resizable: true, plain: true, @@ -85,6 +85,21 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { importJobsFinished: 0, importJobs: 0, + // help text + importTypeDirectoryHelpText: 'Imports all repositories that are located at the repository folder of SCM-Manager.', + importTypeURLHelpText: 'Imports a repository from a remote url.', + importTypeFileHelpText: 'Imports a repository from a file (e.g. SVN dump).', + + importUrlNameHelpText: 'The name of the repository in SCM-Manager.', + importUrlHelpText: 'The source url of the repository.', + + importFileNameHelpText: 'The name of the repository in SCM-Manager.', + importFileHelpText: 'Choose the dump file you want to import to SCM-Manager.', + + // tips + tipRepositoryType: 'Choose your repository type for the import.', + tipImportType: 'Select the type of import. Note: Not all repository types support all options.', + // settings repositoryType: null, @@ -108,11 +123,10 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { ] }); - var types = []; + var typeItems = []; Ext.each(state.repositoryTypes, function(repositoryType){ - console.log(repositoryType); - types.push({ + typeItems.push({ boxLabel: repositoryType.displayName, name: 'repositoryType', inputValue: repositoryType.name, @@ -120,6 +134,16 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }); }); + typeItems = typeItems.sort(function(a, b){ + return a.boxLabel > b.boxLabel; + }); + + typeItems.push({ + xtype: 'scmTip', + content: this.tipRepositoryType, + width: '100%' + }); + var config = { layout: 'card', activeItem: 0, @@ -156,7 +180,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { xtype: 'radiogroup', name: 'chooseRepositoryType', columns: 1, - items: [types], + items: [typeItems], listeners: { change: function(){ Ext.getCmp('move-next').setDisabled(false); @@ -175,21 +199,28 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { boxLabel: 'Import from directory', name: 'importType', inputValue: 'directory', - disabled: false + disabled: false, + helpText: this.importTypeDirectoryHelpText },{ id: 'importTypeURL', - boxLabel: 'Import from URL', + boxLabel: 'Import from url', name: 'importType', inputValue: 'url', checked: false, - disabled: true + disabled: true, + helpText: this.importTypeURLHelpText },{ id: 'importTypeFile', - boxLabel: 'Import from File', + boxLabel: 'Import from file', name: 'importType', inputValue: 'file', checked: false, - disabled: true + disabled: true, + helpText: this.importTypeFileHelpText + },{ + xtype: 'scmTip', + content: this.tipImportType, + width: '100%' }], listeners: { change: function(){ @@ -209,33 +240,46 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { fieldLabel: 'Repository name', name: 'importUrlName', type: 'textfield', - disabled: false + disabled: false, + helpText: this.importUrlNameHelpText },{ id: 'importUrl', xtype: 'textfield', fieldLabel: 'Import URL', name: 'importUrl', - disabled: false + disabled: false, + helpText: this.importUrlHelpText + },{ + xtype: 'scmTip', + content: 'Please insert name and remote url of the repository.', + width: '100%' }] },{ id: 'importFileLayout', layout: 'form', - defaults: { - width: 250 - }, items: [{ id: 'importFileName', xtype: 'textfield', fieldLabel: 'Repository name', name: 'importFileName', type: 'textfield', - disabled: false + width: 250, + helpText: this.importFileNameHelpText },{ id: 'importFile', - xtype: 'textfield', + xtype: 'fileuploadfield', fieldLabel: 'Import File', + ctCls: 'import-fu', name: 'importFile', - disabled: false + helpText: this.importFileHelpText, + cls: 'import-fu', + buttonCfg: { + iconCls: 'upload-icon' + } + },{ + xtype: 'scmTip', + content: 'Please insert name and upload the repository file.', + width: '100%' }] },{ id: 'importFinishedLayout', diff --git a/scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js b/scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js new file mode 100644 index 0000000000..18cadb9884 --- /dev/null +++ b/scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js @@ -0,0 +1,77 @@ +/* + * 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.util.Tip = Ext.extend(Ext.BoxComponent, { + + tpl: new Ext.XTemplate('\ +
\ +
\ +
\ +
\ +
\ + \ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + {content}\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
'), + + constructor: function(config) { + config = config || {}; + var cl = 'scm-tip'; + if (config['class']){ + cl += ' ' + config['class']; + } + config.xtype = 'box'; + this.html = this.tpl.apply({content: config.content}); + Sonia.util.Tip.superclass.constructor.apply(this, arguments); + } + +}); + +// register xtype +Ext.reg('scmTip', Sonia.util.Tip); \ No newline at end of file From 9a7c5e643ca69a2ba3a6474c24709fa06ca04151 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Nov 2014 14:19:55 +0100 Subject: [PATCH 3/6] implemented ui for repository import from url --- .../sonia.repository.importwindow.js | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 233b491f67..975ec04bf0 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -230,7 +230,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }] },{ id: 'importUrlLayout', - layout: 'form', + xtype: 'form', defaults: { width: 250 }, @@ -238,7 +238,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { id: 'importUrlName', xtype: 'textfield', fieldLabel: 'Repository name', - name: 'importUrlName', + name: 'name', type: 'textfield', disabled: false, helpText: this.importUrlNameHelpText @@ -246,7 +246,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { id: 'importUrl', xtype: 'textfield', fieldLabel: 'Import URL', - name: 'importUrl', + name: 'url', disabled: false, helpText: this.importUrlHelpText },{ @@ -339,6 +339,10 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { { next = 1; } + else if ( id === 'importUrlLayout' && direction === 1 ){ + var panel = Ext.getCmp('importUrlLayout'); + this.importFromUrl(layout, panel.getForm().getValues()); + } if ( next >= 0 ){ layout.setActiveItem(next); @@ -361,6 +365,29 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { } }, + importFromUrl: function(layout, repository){ + Ext.Ajax.request({ + url: restUrl + 'import/repositories/' + this.repositoryType + '/url.json', + method: 'POST', + scope: this, + jsonData: repository, + success: function(){ + this.appendImported([{ + name: repository.name, + type: this.repositoryType + }]); + layout.setActiveItem(4); + }, + failure: function(result){ + main.handleRestFailure( + result, + this.errorTitleText, + this.errorMsgText + ); + } + }); + }, + importFromDirectory: function(layout){ Ext.Ajax.request({ url: restUrl + 'import/repositories/' + this.repositoryType + '.json', From 45a6bf518374308c5a9673383c2ea92a9e236398 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Nov 2014 14:51:25 +0100 Subject: [PATCH 4/6] implemented ui for repository import from file --- .../resources/RepositoryImportResource.java | 136 +++++++++++------- .../sonia.repository.importwindow.js | 47 ++++-- 2 files changed, 121 insertions(+), 62 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index 6107abcd1b..1c9d2f673c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -50,6 +50,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.NotSupportedFeatuerException; import sonia.scm.Type; +import sonia.scm.api.rest.RestActionUploadResult; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryAllreadyExistExeption; import sonia.scm.repository.RepositoryException; @@ -161,54 +162,7 @@ public class RepositoryImportResource @PathParam("type") String type, @FormDataParam("name") String name, @FormDataParam("bundle") InputStream inputStream) { - SecurityUtils.getSubject().checkRole(Role.ADMIN); - - checkArgument(!Strings.isNullOrEmpty(name), - "request does not contain name of the repository"); - checkNotNull(inputStream, "bundle inputStream is required"); - - Repository repository; - - try - { - Type t = type(type); - - checkSupport(t, Command.UNBUNDLE, "bundle"); - - repository = create(type, name); - - RepositoryService service = null; - - File file = File.createTempFile("scm-import-", ".bundle"); - - try - { - long length = Files.asByteSink(file).writeFrom(inputStream); - - logger.info("copied {} bytes to temp, start bundle import", length); - service = serviceFactory.create(repository); - service.getUnbundleCommand().unbundle(file); - } - catch (RepositoryException ex) - { - handleImportFailure(ex, repository); - } - catch (IOException ex) - { - handleImportFailure(ex, repository); - } - finally - { - IOUtil.close(service); - IOUtil.delete(file); - } - } - catch (IOException ex) - { - logger.warn("could not create temporary file", ex); - - throw new WebApplicationException(ex); - } + Repository repository = doImportFromBundle(type, name, inputStream); return buildResponse(uriInfo, repository); } @@ -230,7 +184,6 @@ public class RepositoryImportResource * * * - * @param uriInfo uri info * @param type repository type * @param name name of the repository * @param inputStream input bundle @@ -242,11 +195,25 @@ public class RepositoryImportResource @Path("{type}/bundle.html") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_HTML) - public Response importFromBundleUI(@Context UriInfo uriInfo, - @PathParam("type") String type, @FormDataParam("name") String name, + public Response importFromBundleUI(@PathParam("type") String type, + @FormDataParam("name") String name, @FormDataParam("bundle") InputStream inputStream) { - return importFromBundle(uriInfo, type, name, inputStream); + Response response; + + try + { + doImportFromBundle(type, name, inputStream); + response = Response.ok(new RestActionUploadResult(true)).build(); + } + catch (WebApplicationException ex) + { + logger.warn("error durring bundle import", ex); + response = Response.fromResponse(ex.getResponse()).entity( + new RestActionUploadResult(false)).build(); + } + + return response; } /** @@ -503,6 +470,71 @@ public class RepositoryImportResource return repository; } + /** + * Start bundle import. + * + * + * @param type repository type + * @param name name of the repository + * @param inputStream bundle stream + * + * @return imported repository + */ + private Repository doImportFromBundle(String type, String name, + InputStream inputStream) + { + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + checkArgument(!Strings.isNullOrEmpty(name), + "request does not contain name of the repository"); + checkNotNull(inputStream, "bundle inputStream is required"); + + Repository repository; + + try + { + Type t = type(type); + + checkSupport(t, Command.UNBUNDLE, "bundle"); + + repository = create(type, name); + + RepositoryService service = null; + + File file = File.createTempFile("scm-import-", ".bundle"); + + try + { + long length = Files.asByteSink(file).writeFrom(inputStream); + + logger.info("copied {} bytes to temp, start bundle import", length); + service = serviceFactory.create(repository); + service.getUnbundleCommand().unbundle(file); + } + catch (RepositoryException ex) + { + handleImportFailure(ex, repository); + } + catch (IOException ex) + { + handleImportFailure(ex, repository); + } + finally + { + IOUtil.close(service); + IOUtil.delete(file); + } + } + catch (IOException ex) + { + logger.warn("could not create temporary file", ex); + + throw new WebApplicationException(ex); + } + + return repository; + } + /** * Method description * diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 975ec04bf0..43d870517f 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -78,9 +78,6 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { nextText: 'Next', finishText: 'Finish', - // cache - importForm: null, - imported: [], importJobsFinished: 0, importJobs: 0, @@ -105,7 +102,11 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { initComponent: function(){ this.addEvents('finish'); - + + // fix initialization bug + this.imported = []; + this.importJobsFinished = 0; + this.importJobs = 0; var importedStore = new Ext.data.JsonStore({ fields: ['type', 'name'] @@ -256,12 +257,13 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }] },{ id: 'importFileLayout', - layout: 'form', + xtype: 'form', + fileUpload: true, items: [{ id: 'importFileName', xtype: 'textfield', fieldLabel: 'Repository name', - name: 'importFileName', + name: 'name', type: 'textfield', width: 250, helpText: this.importFileNameHelpText @@ -270,7 +272,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { xtype: 'fileuploadfield', fieldLabel: 'Import File', ctCls: 'import-fu', - name: 'importFile', + name: 'bundle', helpText: this.importFileHelpText, cls: 'import-fu', buttonCfg: { @@ -339,9 +341,13 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { { next = 1; } - else if ( id === 'importUrlLayout' && direction === 1 ){ - var panel = Ext.getCmp('importUrlLayout'); - this.importFromUrl(layout, panel.getForm().getValues()); + else if ( id === 'importUrlLayout' && direction === 1 ) + { + this.importFromUrl(layout, Ext.getCmp('importUrlLayout').getForm().getValues()); + } + else if ( id === 'importFileLayout' && direction === 1 ) + { + this.importFromFile(layout, Ext.getCmp('importFileLayout').getForm()); } if ( next >= 0 ){ @@ -365,6 +371,27 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { } }, + importFromFile: function(layout, form){ + form.submit({ + url: restUrl + 'import/repositories/' + this.repositoryType + '/bundle.html', + scope: this, + success: function(form, action){ + this.appendImported([{ + name: form.getValues().name, + type: this.repositoryType + }]); + layout.setActiveItem(4); + }, + failure: function(form, action){ + main.handleRestFailure( + action.response, + this.errorTitleText, + this.errorMsgText + ); + } + }); + }, + importFromUrl: function(layout, repository){ Ext.Ajax.request({ url: restUrl + 'import/repositories/' + this.repositoryType + '/url.json', From d0bf865c3d141bf32b4a9639e8d7599988fa8732 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Nov 2014 15:25:20 +0100 Subject: [PATCH 5/6] improve form validation of repository import wizard --- .../sonia.repository.importwindow.js | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 43d870517f..8f5132607e 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -73,7 +73,7 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { - // text + // text backText: 'Back', nextText: 'Next', finishText: 'Finish', @@ -97,9 +97,16 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { tipRepositoryType: 'Choose your repository type for the import.', tipImportType: 'Select the type of import. Note: Not all repository types support all options.', + // cache + nextButton: null, + prevButton: null, + // settings repositoryType: null, + // active card + activeForm: null, + initComponent: function(){ this.addEvents('finish'); @@ -107,6 +114,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { this.imported = []; this.importJobsFinished = 0; this.importJobs = 0; + this.activeForm = null; var importedStore = new Ext.data.JsonStore({ fields: ['type', 'name'] @@ -232,23 +240,31 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { },{ id: 'importUrlLayout', xtype: 'form', + monitorValid: true, defaults: { width: 250 }, + listeners: { + clientvalidation: { + fn: this.urlFormValidityMonitor, + scope: this + } + }, items: [{ id: 'importUrlName', xtype: 'textfield', fieldLabel: 'Repository name', - name: 'name', - type: 'textfield', - disabled: false, + name: 'name', + allowBlank: false, + vtype: 'repositoryName', helpText: this.importUrlNameHelpText },{ id: 'importUrl', xtype: 'textfield', fieldLabel: 'Import URL', name: 'url', - disabled: false, + allowBlank: false, + vtype: 'url', helpText: this.importUrlHelpText },{ xtype: 'scmTip', @@ -259,6 +275,13 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { id: 'importFileLayout', xtype: 'form', fileUpload: true, + monitorValid: true, + listeners: { + clientvalidation: { + fn: this.urlFormValidityMonitor, + scope: this + } + }, items: [{ id: 'importFileName', xtype: 'textfield', @@ -266,6 +289,8 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { name: 'name', type: 'textfield', width: 250, + allowBlank: false, + vtype: 'repositoryName', helpText: this.importFileNameHelpText },{ id: 'importFile', @@ -273,6 +298,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { fieldLabel: 'Import File', ctCls: 'import-fu', name: 'bundle', + allowBlank: false, helpText: this.importFileHelpText, cls: 'import-fu', buttonCfg: { @@ -305,6 +331,8 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }, navHandler: function(direction){ + this.activeForm = null; + var layout = this.getLayout(); var id = layout.activeItem.id; @@ -322,6 +350,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { Ext.getCmp('move-next').setDisabled(false); } else if ( id === 'importTypeLayout' && direction === 1 ){ + Ext.getCmp('move-next').setDisabled(false); var v = Ext.getCmp('chooseImportType').getValue(); if ( v ){ switch (v.getRawValue()){ @@ -330,9 +359,11 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { break; case 'url': next = 2; + this.activeForm = 'url'; break; case 'file': next = 3; + this.activeForm = 'file'; break; } } @@ -355,6 +386,31 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { } }, + getNextButton: function(){ + if (!this.nextButton){ + this.nextButton = Ext.getCmp('move-next'); + } + return this.nextButton; + }, + + getPrevButton: function(){ + if (!this.prevButton){ + this.prevButton = Ext.getCmp('move-prev'); + } + return this.prevButton; + }, + + urlFormValidityMonitor: function(form, valid){ + if (this.activeForm === 'url' || this.activeForm === 'file'){ + var nbt = this.getNextButton(); + if (valid && nbt.disabled){ + nbt.setDisabled(false); + } else if (!valid && !nbt.disabled){ + nbt.setDisabled(true); + } + } + }, + appendImported: function(repositories){ for (var i=0; i Date: Fri, 28 Nov 2014 15:35:36 +0100 Subject: [PATCH 6/6] added loading indicator to repository import wizard --- .../sonia.repository.importwindow.js | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 8f5132607e..aa097626ab 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -278,7 +278,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { monitorValid: true, listeners: { clientvalidation: { - fn: this.urlFormValidityMonitor, + fn: this.fileFormValidityMonitor, scope: this } }, @@ -399,15 +399,37 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { } return this.prevButton; }, + + showLoadingBox: function(){ + return Ext.MessageBox.show({ + title: 'Loading', + msg: 'Import repository', + width: 300, + wait: true, + animate: true, + progress: true, + closable: false + }); + }, urlFormValidityMonitor: function(form, valid){ - if (this.activeForm === 'url' || this.activeForm === 'file'){ - var nbt = this.getNextButton(); - if (valid && nbt.disabled){ - nbt.setDisabled(false); - } else if (!valid && !nbt.disabled){ - nbt.setDisabled(true); - } + if (this.activeForm === 'url'){ + this.formValidityMonitor(form, valid); + } + }, + + fileFormValidityMonitor: function(form, valid){ + if (this.activeForm === 'file'){ + this.formValidityMonitor(form, valid); + } + }, + + formValidityMonitor: function(form, valid){ + var nbt = this.getNextButton(); + if (valid && nbt.disabled){ + nbt.setDisabled(false); + } else if (!valid && !nbt.disabled){ + nbt.setDisabled(true); } }, @@ -428,6 +450,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }, importFromFile: function(layout, form){ + var lbox = this.showLoadingBox(); form.submit({ url: restUrl + 'import/repositories/' + this.repositoryType + '/bundle.html', scope: this, @@ -436,9 +459,11 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { name: form.getValues().name, type: this.repositoryType }]); + lbox.hide(); layout.setActiveItem(4); }, failure: function(form, action){ + lbox.hide(); main.handleRestFailure( action.response, this.errorTitleText, @@ -449,6 +474,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }, importFromUrl: function(layout, repository){ + var lbox = this.showLoadingBox(); Ext.Ajax.request({ url: restUrl + 'import/repositories/' + this.repositoryType + '/url.json', method: 'POST', @@ -459,9 +485,11 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { name: repository.name, type: this.repositoryType }]); + lbox.hide(); layout.setActiveItem(4); }, failure: function(result){ + lbox.hide(); main.handleRestFailure( result, this.errorTitleText, @@ -472,6 +500,7 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { }, importFromDirectory: function(layout){ + var lbox = this.showLoadingBox(); Ext.Ajax.request({ url: restUrl + 'import/repositories/' + this.repositoryType + '.json', method: 'POST', @@ -479,9 +508,11 @@ Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, { success: function(response){ var obj = Ext.decode(response.responseText); this.appendImported(obj); + lbox.hide(); layout.setActiveItem(4); }, failure: function(result){ + lbox.hide(); main.handleRestFailure( result, this.errorTitleText,