diff options
24 files changed, 677 insertions, 254 deletions
diff --git a/idrop-lite/src/main/java/org/irods/jargon/idrop/lite/Version.java b/idrop-lite/src/main/java/org/irods/jargon/idrop/lite/Version.java index 8fd48d3..d118c07 100644 --- a/idrop-lite/src/main/java/org/irods/jargon/idrop/lite/Version.java +++ b/idrop-lite/src/main/java/org/irods/jargon/idrop/lite/Version.java @@ -1,4 +1,8 @@ package org.irods.jargon.idrop.lite; public final class Version { +<<<<<<< HEAD public static String VERSION="20120828-1415"; +======= + public static String VERSION="20120828-1442"; +>>>>>>> development } diff --git a/idrop-web/application.properties b/idrop-web/application.properties index 7405b59..32f474a 100644 --- a/idrop-web/application.properties +++ b/idrop-web/application.properties @@ -1,5 +1,3 @@ -#Grails Metadata file -#Thu Aug 23 07:32:11 EDT 2012 app.grails.version=2.0.4 app.name=idrop-web app.servlet.version=2.5 @@ -7,3 +5,4 @@ app.version=1.0.1 plugins.hibernate=2.0.4 plugins.logging=0.1 plugins.tomcat=2.0.4 + diff --git a/idrop-web/grails-app/conf/BuildConfig.groovy b/idrop-web/grails-app/conf/BuildConfig.groovy index 259d4c5..4b60200 100644 --- a/idrop-web/grails-app/conf/BuildConfig.groovy +++ b/idrop-web/grails-app/conf/BuildConfig.groovy @@ -29,11 +29,12 @@ grails.project.dependency.resolution = { test 'org.mockito:mockito-all:1.8.1' compile 'commons-io:commons-io:2.1' provided 'junit:junit:4.8.1' - compile 'org.irods.jargon:jargon-core:3.1.4' - compile 'org.irods.jargon:jargon-security:3.1.3' - compile 'org.irods.jargon:jargon-data-utils:3.1.4' - compile 'org.irods.jargon:jargon-ticket:3.1.4' - compile ('org.irods.jargon:jargon-user-tagging:3.1.4') { exclude 'junit' } + compile 'org.irods.jargon:jargon-core:3.2.0-SNAPSHOT' + compile 'org.irods.jargon:jargon-security:3.2.0-SNAPSHOT' + compile 'org.irods.jargon:jargon-data-utils:3.2.0-SNAPSHOT' + compile 'org.irods.jargon:jargon-ticket:3.2.0-SNAPSHOT' + compile 'org.irods.jargon:jargon-user-profile:3.2.0-SNAPSHOT' + compile ('org.irods.jargon:jargon-user-tagging:3.2.0-SNAPSHOT') { exclude 'junit' } compile 'org.springframework.security:spring-security-core:3.0.5.RELEASE' compile 'org.springframework.security:spring-security-web:3.0.5.RELEASE' compile 'org.springframework.security:spring-security-config:3.0.5.RELEASE' diff --git a/idrop-web/grails-app/conf/Config.groovy b/idrop-web/grails-app/conf/Config.groovy index b9bbefa..5f306a6 100644 --- a/idrop-web/grails-app/conf/Config.groovy +++ b/idrop-web/grails-app/conf/Config.groovy @@ -18,7 +18,7 @@ environments { /*production { grails.serverURL = "http://lifetime-library.ils.unc.edu/${appName}" } production { grails.serverURL = "http://iren-web.renci.org:8080/${appName}" } production { grails.serverURL = "http://srbbrick15.ucsd.edu:1525//${appName}" } - production { grails.serverURL = "http://www.irods.org" } */ + production { grails.serverURL = "http://www.irods.org" } */2 production { grails.serverURL = "http://edit.your.server.info.in.your.etc.file.or.update.the.groovy.config" } development { grails.serverURL = "http://localhost:8080/${appName}" } test { grails.serverURL = "http://localhost:8080/${appName}" } @@ -34,6 +34,7 @@ environments { idrop.config.preset.resource="lifelibResc1" */ + /* * 3) iDROP web includes the idrop-lite Java applet, which is launched from the iDROP web interface. The interface needs to know where to find this jar file. * The Jar file should be placed on a web server in an accessible directory, and configured below @@ -70,6 +71,7 @@ idrop.config.idrop.jnlp="http://iren-web.renci.org/idrop-release/idrop.jnlp" // do I support tickets? This determies whether the ticket feature is available via the interface, it also requires ticket support in iRODS itself (version 3.1+) idrop.config.use.tickets=true idrop.config.max.thumbnail.size.mb=20 +idrop.config.use.userprofile=false /* * Some properties may be set in an external configuration file, as configured below diff --git a/idrop-web/grails-app/conf/spring/resources.groovy b/idrop-web/grails-app/conf/spring/resources.groovy index 2f5f391..a6561c1 100644 --- a/idrop-web/grails-app/conf/spring/resources.groovy +++ b/idrop-web/grails-app/conf/spring/resources.groovy @@ -1,6 +1,8 @@ // Place your Spring DSL code here beans = { + profileService(org.irods.mydrop.service.ProfileService) { irodsAccessObjectFactory = ref("irodsAccessObjectFactory") } + browseController(org.irods.mydrop.controller.BrowseController) { irodsAccessObjectFactory = ref("irodsAccessObjectFactory") taggingServiceFactory = ref("taggingServiceFactory") @@ -35,5 +37,7 @@ beans = { auditController(org.irods.mydrop.controller.AuditController) { irodsAccessObjectFactory = ref("irodsAccessObjectFactory") } + profileController(org.irods.mydrop.controller.ProfileController) { irodsAccessObjectFactory = ref("irodsAccessObjectFactory") } + } diff --git a/idrop-web/grails-app/controllers/org/irods/mydrop/controller/ProfileController.groovy b/idrop-web/grails-app/controllers/org/irods/mydrop/controller/ProfileController.groovy new file mode 100644 index 0000000..38b7a2b --- /dev/null +++ b/idrop-web/grails-app/controllers/org/irods/mydrop/controller/ProfileController.groovy @@ -0,0 +1,146 @@ +package org.irods.mydrop.controller + +import org.irods.jargon.core.connection.IRODSAccount +import org.irods.jargon.core.pub.IRODSAccessObjectFactory +import org.irods.jargon.core.pub.UserAO +import org.irods.jargon.userprofile.UserProfile +import org.irods.mydrop.service.ProfileService +import org.jsoup.Jsoup +import org.jsoup.safety.Whitelist + +class ProfileController { + IRODSAccessObjectFactory irodsAccessObjectFactory + IRODSAccount irodsAccount + ProfileService profileService + def grailsApplication + + /** + * Interceptor grabs IRODSAccount from the SecurityContextHolder + */ + def beforeInterceptor = [action:this.&auth] + + def auth() { + if(!session["SPRING_SECURITY_CONTEXT"]) { + redirect(controller:"login", action:"login") + return false + } + irodsAccount = session["SPRING_SECURITY_CONTEXT"] + } + + def afterInterceptor = { + log.debug("closing the session") + irodsAccessObjectFactory.closeSession() + } + + /** + * Initial listing of profile data, this will create a profile if none exists + * @return + */ + def index() { + log.info "index()" + try { + UserProfile userProfile = profileService.retrieveProfile(irodsAccount) + render(view:"profileData", model:[userProfile:userProfile]) + } catch (Exception e) { + response.sendError(500,e.message) + } + } + + /** + * Update the profile + * @return + */ + def updateProfile() { + + log.info("updateProfile") + + /* + * Massage the params into the user profile + */ + + UserProfile userProfile = profileService.retrieveProfile(irodsAccount) + + if (params['nickName'] == null) { + def message = message(code:"default.null.message", args: ['nickName', 'parms']) + response.sendError(500,message) + } + + def nickName = Jsoup.clean(params['nickName'], Whitelist.basic()) + + if ( params['description'] == null) { + def message = message(code:"default.null.message", args: ['description', 'parms']) + response.sendError(500,message) + } + def description = Jsoup.clean(params['description'], Whitelist.basic()) + + if (params['email'] == null) { + def message = message(code:"default.null.message", args: ['email', 'parms']) + response.sendError(500,message) + } + def email = Jsoup.clean(params['email'], Whitelist.basic()) + + userProfile.userProfilePublicFields.nickName = nickName + userProfile.userProfilePublicFields.description = description + userProfile.userProfileProtectedFields.mail = email + + log.info "updating profile...." + profileService.updateProfile(irodsAccount, userProfile) + log.info "updated" + + render(view:"profileData", model:[userProfile:userProfile]) + + } + + /** + * Show the password change dialog + * @return + */ + def showPasswordChangeDialog() { + PasswordCommand cmd = new PasswordCommand() + render (view:"changePasswordDialog", model:[password:cmd]) + } + + /** + * process a password change + * @return + */ + def changePassword(PasswordCommand cmd) { + log.info "changePassword()" + log.info "cmd: ${cmd}" + + /** + * If there is an error send back the view for redisplay with error messages + */ + if (!cmd.validate()) { + log.info("errors in page, returning with error info:${cmd}") + flash.error = message(code:"error.data.error") + render (view:"changePasswordDialog", model:[password:cmd]) + return + } + + log.info("edits pass") + + UserAO userAO = irodsAccessObjectFactory.getUserAO(irodsAccount) + userAO.changeAUserPasswordByThatUser(irodsAccount.userName, irodsAccount.password, cmd.password) + irodsAccount.password = cmd.password + log.info("password changed, fix account in session") + flash.message = message(code:"message.update.successful") + render (view:"changePasswordDialog", model:[password:cmd]) + + } +} + +class PasswordCommand { + + String password + String confirmPassword + + static constraints = { + password(blank:false) + confirmPassword validator: { + val, obj -> + if (!val) return ['error.confirm.password.missing'] + if (val != obj.password) return['error.passwords.dont.match'] + } + } +} diff --git a/idrop-web/grails-app/i18n/messages.properties b/idrop-web/grails-app/i18n/messages.properties index 70eb3a3..a7472e7 100644 --- a/idrop-web/grails-app/i18n/messages.properties +++ b/idrop-web/grails-app/i18n/messages.properties @@ -12,8 +12,11 @@ text.browse=Browse text.bulk.action=Bulk Action text.bulk.upload=Bulk Upload text.cancel=Cancel +text.changePassword=Change Password +text.change.password.header=Enter new password and enter again to confirm, then press Update text.check.out=Check Out Cart text.create.ticket=Create Ticket +text.confirm.password=Confirm Password text.content=Content text.delete.all=Delete all selected items text.delete.metadata=Delete Metadata @@ -21,6 +24,7 @@ text.description=Description text.display.option=Display Option text.download=Download text.edit=Edit +text.email=Email text.file=File text.forward=Page Forward text.find.path.in.tree=Open the path in the tree @@ -40,7 +44,10 @@ text.new.folder=New Folder text.path=Path text.password=Password text.permissions=Permissions +text.personal.blurb=Personal Blurb text.port=Port +text.profile=Profile +text.profile.header=Profile - Manage personal profile information and settings text.quick.transfers=Quick Transfer text.refresh=Refresh text.rename=Rename @@ -77,9 +84,11 @@ text.tags=Tags text.timestamp=Timestamp text.type=Type text.unit=Unit +text.update=Update text.upload=Upload text.upload.and.download=Upload and Download text.user=User Name +text.nickname=Nick Name text.value=Value text.view=View text.zone=Zone @@ -104,6 +113,7 @@ heading.upload.ticket.collection=Select a local file to upload to the ticket col browse.page.prompt=Select a directory or file to see info and tags based on the view option # messages +error.confirm.password.missing=Confirmation password is null or blank error.nothing.selected=Nothing was selected for the action error.no.audit.access=This user does not have permission to view audit data error.no.data.found=No data found @@ -119,6 +129,7 @@ error.duplicate.acl=User already has an ACL error.duplicate.file=File already exists error.duplicate.metadata=Metadata entry already exists error.no.resource=No resource name provided +error.passwords.dont.match=Confirm password not equal to password error.rename.to.self=File cannot be renamed to same name, ignored error.invalid.acl=Invalid ACL value {0} error.use.bulk.upload=File is too large for HTTP upload, select bulk upload from the upload menu, or use iDrop Desktop from right hand panel diff --git a/idrop-web/grails-app/services/org/irods/mydrop/service/ProfileService.groovy b/idrop-web/grails-app/services/org/irods/mydrop/service/ProfileService.groovy new file mode 100644 index 0000000..0892289 --- /dev/null +++ b/idrop-web/grails-app/services/org/irods/mydrop/service/ProfileService.groovy @@ -0,0 +1,73 @@ +package org.irods.mydrop.service + +import org.irods.jargon.core.connection.IRODSAccount +import org.irods.jargon.core.exception.DataNotFoundException +import org.irods.jargon.core.pub.IRODSAccessObjectFactory +import org.irods.jargon.userprofile.* + +class ProfileService { + static transactional = false + IRODSAccessObjectFactory irodsAccessObjectFactory + + /** + * Given an irods account, retrieve the existing user profile, or create a skeleton and return this new skeleton profile + * @param irodsAccount + * @return + */ + UserProfile retrieveProfile(IRODSAccount irodsAccount) { + log.info "retrieveProfile()" + if (irodsAccount == null) { + throw new IllegalArgumentException("null profile") + } + + UserProfileService userProfileService = new UserProfileServiceImpl(irodsAccessObjectFactory, irodsAccount) + log.info("attempting to retrieve profile for ${irodsAccount}") + + UserProfile userProfile + try { + userProfile = userProfileService.retrieveUserProfile(irodsAccount.userName) + } catch (DataNotFoundException dnf) { + log.info("no profile found, go ahead and create a basic one") + userProfile = addSkeletonUserProfile(irodsAccount, userProfileService) + } + + log.info("user profile ${userProfile}") + return userProfile + } + + private UserProfile addSkeletonUserProfile(IRODSAccount irodsAccount, UserProfileService userProfileService) { + UserProfile userProfile = new UserProfile() + userProfile.userName = irodsAccount.userName + userProfile.zone = irodsAccount.zone + userProfileService.addProfileForUser(irodsAccount.userName, userProfile) + return userProfile + } + + /** + * Given the user profile information, update the users profile and then return the new state + * @param irodsAccount + * @param userProfile + * @return + */ + UserProfile updateProfile(IRODSAccount irodsAccount, UserProfile userProfile) { + log.info "updateProfile" + + if (irodsAccount == null) { + throw new IllegalArgumentException("null profile") + } + + if (userProfile == null) { + throw new IllegalArgumentException("null userProfile") + } + + UserProfileService userProfileService = new UserProfileServiceImpl(irodsAccessObjectFactory, irodsAccount) + log.info("attempting to update profile for ${irodsAccount}") + log.info("desired profile information: ${userProfile}") + + userProfileService.updateUserProfile(userProfile) + log.info "updated...now retrieve and display" + UserProfile updatedProfile = userProfileService.retrieveUserProfile(irodsAccount.userName) + log.info("updated profile: ${updatedProfile}") + return updatedProfile + } +} diff --git a/idrop-web/grails-app/views/browse/_browseTabContent.gsp b/idrop-web/grails-app/views/browse/_browseTabContent.gsp new file mode 100644 index 0000000..37669f3 --- /dev/null +++ b/idrop-web/grails-app/views/browse/_browseTabContent.gsp @@ -0,0 +1,51 @@ + <div id="browseToolbar" style="display:block; width:100%;position:relative;"> + + <div id="infoDivPathArea" + style="overflow: hidden; display:block; margin: 3px; font-size: 120%;position:relative;"> + <!-- area for the path crumb-trails --> + </div> + + </div> <!-- browseToolbar --> + <div id="browseMenuDiv" style="display:block; width:100%;position:relative;"> + <g:render template="/common/topToolbar" /> + </div> + + <div id="browser" class="wrapper" style="height:85%;width:100%;clear:both;"> + <div id="dataTreeView" style="width: 100%; height: 700px; overflow: hidden;"> + + <div id="dataTreeDivWrapper" class="ui-layout-west" style="width: 25%; height: 100%; position:relative;"> + <div id="dataTreeToolbar" style="width:100%; height:3%;display:block;" class="fg-toolbar"> + + <div id="dataTreeMenu" class="fg-buttonset fg-buttonset-multi" + style="float: left; clear : both; display:block; overflow:hidden;"> + <button type="button" id="refreshTreeButton" + class="ui-state-default ui-corner-left" value="refreshTreeButton" + onclick="refreshTree()")> + <g:img dir="images" file="arrow-refresh.png" width="16" height="16"/> + </button> + <button type="button" id="homeTreeButton" + class="ui-state-default" value="homeTreeButton" + onclick="setTreeToUserHome()")> + <g:img dir="images" file="go-home-4.png" width="16" height="16"/> + </button> + <button type="button" id="rootTreeButton" + class="ui-state-default ui-corner-right" value="rootTreeButton" + onclick="setTreeToRoot()")> + <g:img dir="images" file="go-parent-folder.png" width="16" height="16"/> + </button> + </div> <!-- dataTreeMenu --> + + </div> <!-- dataTreeToolbar --> + + <div id="dataTreeDiv" class="clearfix" style="height:95%; width:100%; overflow:auto;"><!-- no empty div --></div> + </div> <!-- dataTreeDivWrapper --> + + <div id="infoDivOuter" style="display: block; width: 75%; height: 100%; position: relative; overflow: auto;" + class="ui-layout-center"> + + <div id="infoDiv" style=""><h2><g:message code="browse.page.prompt" /></h2> + </div> <!-- infoDiv --> + + </div> <!-- infoDivOuter --> + </div> <!-- data tree view --> + </div> <!-- browser --> diff --git a/idrop-web/grails-app/views/common/_topToolbar.gsp b/idrop-web/grails-app/views/common/_topToolbar.gsp index fb9933c..bf69c9d 100644 --- a/idrop-web/grails-app/views/common/_topToolbar.gsp +++ b/idrop-web/grails-app/views/common/_topToolbar.gsp @@ -77,7 +77,6 @@ code="text.add.to.cart" /></a></li> - </ul></li> <li id="menuBulkActionDetails" class="detailsToolbarMenuItem"><a href="#applyActionToAllDetails"><g:message code="text.apply.to.all"/></a> @@ -88,23 +87,6 @@ </ul> </li> - <!-- - <g:if test="${grailsApplication.config.idrop.config.use.tickets==true}"> - <li id="menuTicketDetails" class="detailsToolbarMenuItem"><a - href="#ticketsMenu"><g:message - code="text.tickets" /></a> - <ul> - - <li id="menuCreateAndEmailATicketDetails"><a href="#menuCreateAndEmailATicketDetails" - onclick="createAndEmailATicketViaBrowseDetailsToolbar()"><g:message - code="text.ticket.email" /></a></li> - <li id="menuUseATicket"><a href="#menuUseATicket" - onclick="useATicketViaBrowseDetailsToolbar()"><g:message - code="text.ticket.use" /></a></li> - - </ul></li> - </g:if>--> - <!-- info toolbar --> @@ -126,26 +108,7 @@ </ul></li> - <!-- - <g:if test="${grailsApplication.config.idrop.config.use.tickets==true}"> - <li id="menuTicket" class="toolbarMenuItem"><a - href="#ticketsMenu"><g:message - code="text.tickets" /></a> - <ul> - - <li id="menuCreateAndEmailATicket"><a href="#menuCreateAndEmailATicket" - onclick="createAndEmailATicketToolbar()"><g:message - code="text.ticket.email" /></a></li> - <li id="menuUseATicket"><a href="#menuUseATicket" - onclick="useATicket()"><g:message - code="text.ticket.use" /></a></li> - - </ul></li> - </g:if>--> - - - - </ul> + </ul> </div> @@ -180,7 +143,6 @@ showAuditView(selectedPath); } - /** * browse view selected diff --git a/idrop-web/grails-app/views/common/_topbar.gsp b/idrop-web/grails-app/views/common/_topbar.gsp index 0818efa..ce1c402 100644 --- a/idrop-web/grails-app/views/common/_topbar.gsp +++ b/idrop-web/grails-app/views/common/_topbar.gsp @@ -1,6 +1,3 @@ -<!-- <div id="bannercontainer"> --> - <!-- image banner --> -<!-- </div> --> <g:ifAuthenticated> <div id="headerSearchBox" class="box"> diff --git a/idrop-web/grails-app/views/home/index.gsp b/idrop-web/grails-app/views/home/index.gsp index 83b212d..0af5f0c 100644 --- a/idrop-web/grails-app/views/home/index.gsp +++ b/idrop-web/grails-app/views/home/index.gsp @@ -3,85 +3,26 @@ <g:javascript library="mydrop/home" />
<g:javascript library="mydrop/search" />
<g:javascript library="mydrop/metadata" />
+<g:javascript library="mydrop/profile" />
</head>
-<div id="tabs" class="wrapper"
- style="height: 820px; position: relative; overflow:hidden;">
+<div id="tabs" class="wrapper clearfix"
+ style="height: 820px; overflow:hidden;">
<ul>
<li><a href="#browseTab"><g:message code="text.browse" /> </a></li>
- <!-- <li><a href="#quickView"><g:message code="text.home" />
- </a>
- </li> -->
<li><a href="#searchTab"><g:message code="text.search" /> </a></li>
+ <g:if test="${grailsApplication.config.idrop.config.use.userprofile==true}">
+ <li><a href="#profileTab"><g:message code="text.profile" /> </a></li>
+ </g:if>
</ul>
-
-
- <div id="browseTab" style="padding:0;">
-
- <div id="browser" class="wrapper">
-
- <div id="browseToolbar">
-
- <span id="infoDivPathArea"
- style="overflow: hidden; position: relative; display: inline-block; margin: 3px; font-size: 120%;">
- <!-- area for the path crumb-trails -->
- </span>
- </div>
- <div id="browseMenuDiv">
- <g:render template="/common/topToolbar" />
- </div>
-
- <div id="dataTreeView"
- style="width: 100%; height: 700px; overflow: hidden;">
-
- <div id="dataTreeDivWrapper" class="ui-layout-west"
- style="width: 25%; height: 100%; position:relative;">
- <div id="dataTreeToolbar" style="width:100%; height:3%;display:block; position:relative;" class="fg-toolbar">
- <div id="dataTreeMenu" class="fg-buttonset fg-buttonset-multi"
- style="float: left, clear : both; display:block; overflow:hidden;">
- <button type="button" id="refreshTreeButton"
- class="ui-state-default ui-corner-left" value="refreshTreeButton"
- onclick="refreshTree()")>
- <!--<g:message code="text.refresh" />-->
- <g:img dir="images" file="arrow-refresh.png" width="16" height="16"/>
- </button>
- <button type="button" id="homeTreeButton"
- class="ui-state-default" value="homeTreeButton"
- onclick="setTreeToUserHome()")>
- <!--<g:message code="text.refresh" />-->
- <g:img dir="images" file="go-home-4.png" width="16" height="16"/>
- </button>
- <button type="button" id="rootTreeButton"
- class="ui-state-default ui-corner-right" value="rootTreeButton"
- onclick="setTreeToRoot()")>
- <!--<g:message code="text.refresh" />-->
- <g:img dir="images" file="go-parent-folder.png" width="16" height="16"/>
- </button>
- </div>
- </div>
- <div id="dataTreeDiv" style="width:auto; height:95%; overflow:visible;">
- </div>
- </div>
- <div id="infoDivOuter"
- style="display: block; width: 75%; height: 100%; position: relative; overflow: auto;"
- class="ui-layout-center">
-
- <div id="infoDiv" style="overflow: visible; position: relative;">
- <h2>
- <g:message code="browse.page.prompt" />
- </h2>
- </div>
- </div>
- </div>
- </div>
- </div>
+ <div id="browseTab" style="height:100%;">
+ <g:render template="/browse/browseTabContent" />
+ </div><!-- browse tab -->
-
<div id="searchTab">
-
<div id="searchDivOuter"
style="display: block; width: 95%; height: 90%; position: relative; overflow: hidden;"
class="ui-layout-center">
@@ -91,12 +32,18 @@ <h2>
<g:message code="heading.no.search.yet" />
</h2>
- <!-- search table display div -->
- </div>
- </div>
- </div>
+ </div> <!-- searchTableDiv -->
+ </div> <!-- searchDivOuter -->
+ </div> <!-- search tab -->
+
+ <g:if test="${grailsApplication.config.idrop.config.use.userprofile==true}">
+ <div id="profileTab" style="height:100%;overflow:hidden;">
+ <g:render template="/profile/profileTabContent" />
+ </div><!-- profile tab -->
+ </g:if>
+
-</div>
+</div> <!-- tabs -->
<script type="text/javascript">
var dataLayout;
var tabs;
diff --git a/idrop-web/grails-app/views/layouts/main.gsp b/idrop-web/grails-app/views/layouts/main.gsp index 21892ca..2da8db6 100644 --- a/idrop-web/grails-app/views/layouts/main.gsp +++ b/idrop-web/grails-app/views/layouts/main.gsp @@ -1,112 +1,109 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 //EN"> <html> -<head> -<title><g:layoutTitle default="iDrop-web - iRODS Cloud Browser" /></title> -<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'base.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'style.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'jqcloud.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'layout-default-latest.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'jquery.fileupload-ui.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'superfish.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'jquery.gritter.css')}" /> -<link rel="stylesheet" - href="${resource(dir:'css',file:'reset-fonts-grids.css')}" /> -<link rel="stylesheet" href="${resource(dir:'css',file:'start/jquery-ui-1.8.18.custom.css')}" /> + <head> + <title><g:layoutTitle default="iDrop-web - iRODS Cloud Browser" /></title> + <link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'base.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'style.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'jqcloud.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'layout-default-latest.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'jquery.fileupload-ui.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'superfish.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'jquery.gritter.css')}" /> + <link rel="stylesheet" + href="${resource(dir:'css',file:'reset-fonts-grids.css')}" /> + <link rel="stylesheet" href="${resource(dir:'css',file:'start/jquery-ui-1.8.18.custom.css')}" /> -<link rel="shortcut icon" - href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" /> -<g:layoutHead /> -<g:javascript library="jquery-1.7.2.min" /> -<g:javascript library="jquery-ui-1.8.7.custom.min" /> - <g:javascript library="jquery.hotkeys" /> - <g:javascript library="jquery.jstree" /> -<g:javascript library="jquery.jeditable.mini" /> -<g:javascript library="jquery.dataTables.min" /> -<g:javascript library="mydrop/ticket" /> -<g:javascript library="jquery.i18n.properties-min-1.0.9" /> -<g:javascript library="jqcloud-0.1.6" /> -<g:javascript library="jquery.fileupload-ui" /> -<g:javascript library="jquery.fileupload" /> -<g:javascript library="jquery.media" /> -<g:javascript library="mydrop/lingo_common" /> -<g:javascript library="mydrop/main" /> -<g:javascript library="jquery-ui-13" /> -<g:javascript library="jquery.blockUI" /> -<g:javascript library="jquery.ba-bbq.min" /> -<g:javascript library="jquery.layout-latest.min" /> -<g:javascript library="jquery-ui-13" /> -<g:javascript library="jquery.tools.min" /> -<g:javascript library="mydrop/shopping_cart" /> -<g:javascript library="mydrop/user" /> -<g:javascript library="galleria-1.2.6" /> -<g:javascript library="jquery.gritter.min" /> -<g:javascript library="jquery.opacityrollover" /> -<g:javascript library="superfish" /> -<!-- preserve the application context as a js variable for use in AJAX callbacks --> -<script type="text/javascript"> - context = "${request.contextPath}"; - scheme = "${request.scheme}"; - host = "${request.localName}"; - port = "${request.localPort}"; + <link rel="shortcut icon" + href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" /> + <g:layoutHead /> + <g:javascript library="jquery-1.7.2.min" /> + <g:javascript library="jquery-ui-1.8.7.custom.min" /> + <g:javascript library="jquery.hotkeys" /> + <g:javascript library="jquery.jstree" /> + <g:javascript library="jquery.jeditable.mini" /> + <g:javascript library="jquery.dataTables.min" /> + <g:javascript library="mydrop/ticket" /> + <g:javascript library="jquery.i18n.properties-min-1.0.9" /> + <g:javascript library="jqcloud-0.1.6" /> + <g:javascript library="jquery.fileupload-ui" /> + <g:javascript library="jquery.fileupload" /> + <g:javascript library="jquery.media" /> + <g:javascript library="mydrop/lingo_common" /> + <g:javascript library="mydrop/main" /> + <g:javascript library="jquery-ui-13" /> + <g:javascript library="jquery.blockUI" /> + <g:javascript library="jquery.ba-bbq.min" /> + <g:javascript library="jquery.layout-latest.min" /> + <g:javascript library="jquery-ui-13" /> + <g:javascript library="jquery.tools.min" /> + <g:javascript library="mydrop/shopping_cart" /> + <g:javascript library="mydrop/user" /> + <g:javascript library="galleria-1.2.6" /> + <g:javascript library="jquery.gritter.min" /> + <g:javascript library="jquery.opacityrollover" /> + <g:javascript library="superfish" /> + <!-- preserve the application context as a js variable for use in AJAX callbacks --> + <script type="text/javascript"> + context = "${request.contextPath}"; + scheme = "${request.scheme}"; + host = "${request.localName}"; + port = "${request.localPort}"; - $(function(){ - // Keep a mapping of url-to-container for caching purposes. - var cache = { - // If url is '' (no fragment), display this div's content. - '': $('.bbq-default') - }; + $(function(){ + // Keep a mapping of url-to-container for caching purposes. + var cache = { + // If url is '' (no fragment), display this div's content. + '': $('.bbq-default') + }; - // Bind an event to window.onhashchange that, when the history state changes, - // gets the url from the hash and displays either our cached content or fetches - // new content to be displayed. - $(window).bind( 'hashchange', function(e) { + // Bind an event to window.onhashchange that, when the history state changes, + // gets the url from the hash and displays either our cached content or fetches + // new content to be displayed. + $(window).bind( 'hashchange', function(e) { - processStateChange( $.bbq.getState()); + processStateChange( $.bbq.getState()); - }); + }); - jQuery.i18n.properties({ - name:'messages', - path:'js/bundles/', - mode:'both' - }); + jQuery.i18n.properties({ + name:'messages', + path:'js/bundles/', + mode:'both' + }); - // Since the event is only triggered when the hash changes, we need to trigger - // the event now, to handle the hash the page may have loaded with. - // $(window).trigger( 'hashchange' ); - }); + }); -</script> + </script> </head> <body style="height:100%;overflow:visible;"> -<div id="hd"><!-- PUT MASTHEAD CODE HERE --> -<g:render template="/common/topbar"/> -<g:render template="/common/messages"/> -</div> -<div id="bd" style="height:100%;"> -<div id="defaultDialogDiv"><!-- default for general jquery dialogs --></div> -<div id="yui-main" style="height:100%;"> -<div class="yui-b" style="height:100%;"> -<div id="mainDiv" class="yui-ge" style="height:100%;"> -<div id="mainDivCol1" class="yui-u first" style="height:100%;"><!-- PUT MAIN COLUMN 1 CODE HERE --> - <g:layoutBody /> -</div> -<div id="secondaryDiv" class="yui-u" style="height:100%;"><!-- PUT MAIN COLUMN 2 CODE HERE --> -<g:ifAuthenticated> -<g:render template="/common/secondarymain"/> -</g:ifAuthenticated> + <div id="hd"><!-- PUT MASTHEAD CODE HERE --> + <g:render template="/common/topbar"/> + <g:render template="/common/messages"/> + </div> + <div id="bd" style="height:100%;"> + <div id="defaultDialogDiv"><!-- default for general jquery dialogs --></div> + <div id="yui-main" style="height:100%;"> + <div class="yui-b" style="height:100%;"> + <div id="mainDiv" class="yui-ge" style="height:100%;"> + <div id="mainDivCol1" class="yui-u first" style="height:100%;"><!-- PUT MAIN COLUMN 1 CODE HERE --> + <g:layoutBody /> + </div> + <div id="secondaryDiv" class="yui-u" style="height:100%;"><!-- PUT MAIN COLUMN 2 CODE HERE --> + <g:ifAuthenticated> + <g:render template="/common/secondarymain"/> + </g:ifAuthenticated> -</div> -</div> -</div> -</div> + </div> + </div> + </div> + </div> -</div> -<div id="ft"><!-- PUT FOOTER CODE HERE --> -<g:render template="/common/footer" /> -</div> + </div> + <div id="ft"><!-- PUT FOOTER CODE HERE --> + <g:render template="/common/footer" /> + </div> </body> </html>
\ No newline at end of file diff --git a/idrop-web/grails-app/views/profile/_profileTabContent.gsp b/idrop-web/grails-app/views/profile/_profileTabContent.gsp new file mode 100644 index 0000000..32d6fab --- /dev/null +++ b/idrop-web/grails-app/views/profile/_profileTabContent.gsp @@ -0,0 +1,19 @@ +<div id="profileTabContent" class="clearfix" style="display:block;width:100%;height:98%;"> + <h1><g:message code="text.profile.header"/></h1> + <div id="profileToolbar" style="display:block;"> + <g:render template="/profile/profileToolbar" /> + </div> <!-- profileToolbar --> + <div id="profileDialogArea"><!-- div for optional profile dialogs --> + </div> + <div id="profileDataArea" style="clear:both;"> + <!-- area for profile data --> + </div> +</div> +<script> + +$(function() { + loadProfileData(); +}); + + +</script>
\ No newline at end of file diff --git a/idrop-web/grails-app/views/profile/_profileToolbar.gsp b/idrop-web/grails-app/views/profile/_profileToolbar.gsp new file mode 100644 index 0000000..154ae95 --- /dev/null +++ b/idrop-web/grails-app/views/profile/_profileToolbar.gsp @@ -0,0 +1,30 @@ +<div id="profileToolbar" + style="height: 100%; overflow: visible; margin-left: auto; margin-right: auto;"> + + <ul id="profileToolbarMenu" class="sf-menu"> + + <li id="menuProfileData" class="detailsToolbarMenuItem"><a + href="#view"><g:message code="text.view" /></a> + <ul> + <li id="menuRefresh"><a href="#refresh" onclick="loadProfileData()"><g:message + code="text.refresh" /></a></li> + </ul> + </li> + <li id="menuPassword" class="detailsToolbarMenuItem"><a href="#password"><g:message + code="text.password" /></a> + <ul> + <li id="menuChangePassword"><a href="#changePassword" onclick="showChangePasswordDialog()"><g:message + code="text.changePassword" /></a></li> + + </ul> + </li> + </ul> +</div> + +<script type="text/javascript"> + + $(function() { + $("ul.sf-menu").superfish(); + }); + +</script>
\ No newline at end of file diff --git a/idrop-web/grails-app/views/profile/changePasswordDialog.gsp b/idrop-web/grails-app/views/profile/changePasswordDialog.gsp new file mode 100644 index 0000000..29564c3 --- /dev/null +++ b/idrop-web/grails-app/views/profile/changePasswordDialog.gsp @@ -0,0 +1,37 @@ +<div id="changePasswordDialog" class="roundedContainer"> + <h1><g:message code="text.change.password.header" /></h1> + <g:form name="changePasswordForm" id="changePasswordForm"> + + <g:if test="${flash.error}"> + <script> + $(function() { setErrorMessage("${flash.error}"); }); + </script> + </g:if> + + <g:hasErrors bean="${password}"> + <div class="errors"> + <ul> + <g:eachError var="err" bean="${password}"> + <li><g:message error="${err}" /></li> + </g:eachError> + </ul> + </div> + </g:hasErrors> + <div id="container" style="height:100%;width:100%;"> + <div> + <div style="width:15%;"><label><g:message code="text.password"/></label></div> + <div><g:passwordField name="password" value="${password.password}" /></div> + </div> + <div> + <div style="width:15%;"><label><g:message code="text.confirm.password"/></label></div> + <div><g:passwordField name="confirmPassword" value="${password.confirmPassword}" /></div> + </div> + <div> + <div></div> + <div><button type="button" class="ui-state-default ui-corner-all" id="changePassword" value="changePassword" onclick="submitChangePassword()"><g:message code="text.update"/></button> + <button type="button" class="ui-state-default ui-corner-all" id="cancelChangePassword" value="cancelChangePassword" onclick="closePasswordDialog()"><g:message code="text.cancel"/></button> + </div> + </div> + </div> + </g:form> +</div> diff --git a/idrop-web/grails-app/views/profile/profileData.gsp b/idrop-web/grails-app/views/profile/profileData.gsp new file mode 100644 index 0000000..77e957b --- /dev/null +++ b/idrop-web/grails-app/views/profile/profileData.gsp @@ -0,0 +1,28 @@ +<div id="container" style="height:100%;width:100%;"> + <div> + <div style="width:15%;"><label><g:message code="text.user"/></label></div> + <div>${userProfile.userName}</div> + </div> + <div> + <div style="width:15%;"><label><g:message code="text.nickname"/></label></div> + <div><g:textField id="nickName" name="nickName" + value="${userProfile.userProfilePublicFields.nickName}" /> + </div> + </div> + <div> + <div style="width:15%;"><label><g:message code="text.personal.blurb"/></label></div> + <div><g:textArea id="description" name="description" rows="3" cols="40" + value="${userProfile.userProfilePublicFields.description}" /></div> + </div> + <div> + <div style="width:15%;"><label><g:message code="text.email"/></label></div> + <div><g:textField id="email" name="email" + value="${userProfile.userProfileProtectedFields.mail}" /></div> + </div> + <div> + <div></div> + <div><button type="button" class="ui-state-default ui-corner-all" id="updateProfile" value="updateProfile" onclick="updateProfile()"><g:message code="text.update"/></button> + <button type="button" class="ui-state-default ui-corner-all" id="reloadProfile" value="reloadProfile" onclick="loadProfileData()"><g:message code="text.cancel"/></button> + </div> + </div> +</div>
\ No newline at end of file diff --git a/idrop-web/idrop-web-config.groovy b/idrop-web/idrop-web-config.groovy index d1d9cc2..462c414 100644 --- a/idrop-web/idrop-web-config.groovy +++ b/idrop-web/idrop-web-config.groovy @@ -60,4 +60,7 @@ idrop.config.idrop.jnlp="http://iren-web.renci.org/idrop-snapshot/idrop.jnlp" */ // do I support tickets? This determies whether the ticket feature is available via the interface, it also requires ticket support in iRODS itself (version 3.1+) -idrop.config.use.tickets=true
\ No newline at end of file +idrop.config.use.tickets=true + +// do I want to display the profile tab and maintain user profile info +idrop.config.use.userprofile=false
\ No newline at end of file diff --git a/idrop-web/release_notes.txt b/idrop-web/release_notes.txt index f7582fe..f40e606 100644 --- a/idrop-web/release_notes.txt +++ b/idrop-web/release_notes.txt @@ -17,42 +17,12 @@ This is a release of iDrop web version 1.0.1 Note that the following bug and feature requests are logged in GForge with related commit information [[https://code.renci.org/gf/project/irodsidrop/tracker/]] ==Bug Fixes== - -* [#711] post copy via tree context menu - unable to browse to a location - -* [#873] login preset redisplay shows all - -* [#919] javascript errors null parent node in dataTable generation - -* [#935] idrop desktop launch works inconsistantly -** took cruft out of webstart launch link to make a direct link to the jnlp file - -* [#939] new folder tree refresh fails when in 'home' view - -* [#940] safari rename file to self fails to show data when tree node clicked in safari/mac - -* [#941] crummy message displayed for add duplicate avu - -* [#942] Bulk delete of coll with children fails silently +* [#959] firefox browse div float error * [#950] unable to browse to location message in idrop web tree for new safari/mac ** temp catch of icon assignment added as shim in jstree javascript ==Features== -* [#116] browse/search/tab memory -** added jquery bbq plug-in for history management and provisional 'back button' functionality - -* [#696] Add ticket support -** refactoring of 'info' pages to add accordion for various types of details, including ticket information -** development of 'landing page' alternative - -* [#851] add anonymous login and other login enhancments -** Remove spring security and simplify login process - -*[#479] audit trail support in idrop web -** added audit trail views in idrop-web - -* [#907] anonymous listing default behavior -** added default for 'home' listing when anonymous to display a 'public' dir as the top level - +* [#922] User Profile Management +** added profile management tab, including ability to change password by user
\ No newline at end of file diff --git a/idrop-web/test/unit/org/irods/mydrop/controller/ProfileControllerTests.groovy b/idrop-web/test/unit/org/irods/mydrop/controller/ProfileControllerTests.groovy new file mode 100644 index 0000000..30e8b0c --- /dev/null +++ b/idrop-web/test/unit/org/irods/mydrop/controller/ProfileControllerTests.groovy @@ -0,0 +1,19 @@ +package org.irods.mydrop.controller + + + +import grails.test.mixin.* + +import org.irods.mydrop.controller.ProfileController; +import org.junit.* + +/** + * See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions + */ +@TestFor(ProfileController) +class ProfileControllerTests { + + void testSomething() { + fail "Implement me" + } +} diff --git a/idrop-web/test/unit/org/irods/mydrop/service/ProfileServiceTests.groovy b/idrop-web/test/unit/org/irods/mydrop/service/ProfileServiceTests.groovy new file mode 100644 index 0000000..850cb5c --- /dev/null +++ b/idrop-web/test/unit/org/irods/mydrop/service/ProfileServiceTests.groovy @@ -0,0 +1,19 @@ +package org.irods.mydrop.service + + + +import grails.test.mixin.* + +import org.irods.mydrop.service.ProfileService; +import org.junit.* + +/** + * See the API for {@link grails.test.mixin.services.ServiceUnitTestMixin} for usage instructions + */ +@TestFor(ProfileService) +class ProfileServiceTests { + + void testSomething() { + fail "Implement me" + } +} diff --git a/idrop-web/web-app/css/main.css b/idrop-web/web-app/css/main.css index bc8f384..8e2a4ae 100644 --- a/idrop-web/web-app/css/main.css +++ b/idrop-web/web-app/css/main.css @@ -143,8 +143,8 @@ div#tabs { /*width: 100%; padding: 0; margin-left: auto; margin-right: auto; - background: #fff url(../images/iDropLogo.png) left center no-repeat; - /*background: #fff url(../images/Lifetime-Library.png) left center no-repeat;*/ + /*background: #fff url(../images/iDropLogo.png) left center no-repeat;*/ + background: #fff url(../images/Lifetime-Library.png) left center no-repeat; display: inline-block; position: relative; } diff --git a/idrop-web/web-app/js/bundles/messages.properties b/idrop-web/web-app/js/bundles/messages.properties index 25374b1..b12f2eb 100644 --- a/idrop-web/web-app/js/bundles/messages.properties +++ b/idrop-web/web-app/js/bundles/messages.properties @@ -2,10 +2,13 @@ msg_hello = Hello msg_world = World msg_complex = Good morning {0}! +msg_password_no_data = No password data found +msg_password_successful = Password change successful msg_ticket_update_successful = Ticket update successful msg_ticket_no_data = Error - No ticket data found msg_upload_complete = Upload complete msg_nothing_selected_for_edit = "Nothing was selected for editing" msg_confirm_delete = Delete selected items? msg_delete_successful = Delete successful -msg_resource_changed=Resource changed successfully
\ No newline at end of file +msg_resource_changed=Resource changed successfully +msg_profile_update_successful = Profile update successful
\ No newline at end of file diff --git a/idrop-web/web-app/js/mydrop/profile.js b/idrop-web/web-app/js/mydrop/profile.js new file mode 100644 index 0000000..6d57517 --- /dev/null +++ b/idrop-web/web-app/js/mydrop/profile.js @@ -0,0 +1,101 @@ +/** + * Javascript for profile functions + */ + +/** + * Accomplish the password change + */ +function submitChangePassword() { + var formData = $("#changePasswordForm").serializeArray(); + + if (formData == null) { + setErrorMessage(jQuery.i18n.prop('msg_no_password_data')); + return false; + } + + showBlockingPanel(); + + var jqxhr = $.post(context + "/profile/changePassword", formData, + function(data, status, xhr) { + }, "html").success(function(data, status, xhr) { + var continueReq = checkForSessionTimeout(data, xhr); + if (!continueReq) { + return false; + } + + $("#profileDialogArea").html(data); + closePasswordDialog(); + setMessage(jQuery.i18n.prop('msg_password_successful')); + unblockPanel(); + + }).error(function(xhr, status, error) { + + setErrorMessage(xhr.responseText); + unblockPanel(); + }); +} + + +/** + * load the profile details information + */ +function loadProfileData() { + var targetDiv = "#profileDataArea"; + lcSendValueAndCallbackHtmlAfterErrorCheckPreserveMessage( + "/profile/index", + targetDiv, targetDiv, null); +} + +/** + * Show the password change dialog + */ + +function showChangePasswordDialog() { + var targetDiv = "#profileDialogArea"; + $("#profileDataArea").hide("slow"); + $("#profileToolbar").hide("slow"); + lcSendValueAndCallbackHtmlAfterErrorCheckPreserveMessage( + "/profile/showPasswordChangeDialog", + targetDiv, targetDiv, null); +} + +/** + * close the password dialog + */ +function closePasswordDialog() { + $("#profileDialogArea").html(""); + $("#profileToolbar").show("slow"); + $("#profileDataArea").show("slow"); +} + + +/** + * Update the profile information + */ +function updateProfile() { + + var params = { + nickName : $("#nickName").val(), + description : $("#description").val(), + email : $("#email").val() + } + + showBlockingPanel(); + + var jqxhr = $.post(context + "/profile/updateProfile", params, + function(data, status, xhr) { + }, "html").success(function(returnedData, status, xhr) { + var continueReq = checkForSessionTimeout(returnedData, xhr); + if (!continueReq) { + return false; + } + setMessage(jQuery.i18n.prop('msg_profile_update_successful')); + $("#profileDataArea").html(returnedData); + unblockPanel(); + }).error(function(xhr, status, error) { + setErrorMessage(xhr.responseText); + unblockPanel(); + }); +} + + |