diff --git a/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java b/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f0337e268af035f1c599b72d867c55398ae0d454 --- /dev/null +++ b/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java @@ -0,0 +1,71 @@ +/* + * org.nrg.xnat.turbine.modules.screens.ManageProtocol + * XNAT http://www.xnat.org + * Copyright (c) 2013, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + * + * Author: Justin Cleveland <clevelandj@wustl.edu> + * Last modified 2/8/2016 11:29 AM + */ + +package org.nrg.xnat.configuration; + +import java.util.ArrayList; + +/** + * Created by jcleve01 on 2/8/2016. + */ +public class ThemeConfig { + private String name = null; + private String path = null; + private boolean enabled = true; + private ArrayList roles = new ArrayList(); + + /** + * Dummy constructor to make Jackson mapper happy. Don't explictly use this. + */ + public ThemeConfig() { + } + public ThemeConfig(String themeName) { + this.name = themeName; + } + public ThemeConfig(String themeName, String themePath, boolean enabled) { + this.name = themeName; + this.path = themePath; + this.enabled = enabled; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public ArrayList getRoles() { + return roles; + } + + public void setRoles(ArrayList roles) { + this.roles = roles; + } +} diff --git a/src/main/java/org/nrg/xnat/restlet/extensions/ThemeRestlet.java b/src/main/java/org/nrg/xnat/restlet/extensions/ThemeRestlet.java new file mode 100644 index 0000000000000000000000000000000000000000..d072e0c0e17b9e6e64335db6262ea84dd07119ac --- /dev/null +++ b/src/main/java/org/nrg/xnat/restlet/extensions/ThemeRestlet.java @@ -0,0 +1,344 @@ +/* + * org.nrg.xnat.turbine.modules.screens.ManageProtocol + * XNAT http://www.xnat.org + * Copyright (c) 2013, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + * + * Author: Justin Cleveland <clevelandj@wustl.edu> + * Last modified 1/25/2016 1:52 PM + */ + +package org.nrg.xnat.restlet.extensions; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.fileupload.*; +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nrg.action.ClientException; +import org.nrg.xdat.security.helpers.Roles; +import org.nrg.xnat.configuration.ThemeConfig; +import org.nrg.xnat.restlet.XnatRestlet; +import org.nrg.xnat.restlet.resources.SecureResource; +import org.nrg.xnat.restlet.util.FileWriterWrapperI; +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.StringRepresentation; +import org.restlet.resource.Variant; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Created by jcleve01 on 1/26/2016. + */ +@XnatRestlet({"/theme"}) +public class ThemeRestlet extends SecureResource { + private static final Log _log = LogFactory.getLog(ThemeRestlet.class); + private static String themesPath; + protected final ObjectMapper mapper = new ObjectMapper(); + private static final int FILE_BUFFER_SIZE = 4096; + private File themeFile = null; + + public ThemeRestlet(Context context, Request request, Response response) throws UnsupportedEncodingException { + super(context, request, response); + setModifiable(true); + getVariants().add(new Variant(MediaType.APPLICATION_JSON)); + themesPath = this.getHttpSession().getServletContext().getRealPath(File.separator)+"themes"; + themeFile = new File(themesPath + File.separator + "theme.json"); + File checkThemesPath = new File(themesPath); + if (!checkThemesPath.exists()) { + checkThemesPath.mkdir(); + } + } + + @Override + public boolean allowDelete() { + return true; + } + + /** // TODO: convert to swagger doc tags + * Administrator use only + * Deletes the entire theme package folder specified by the "theme" query parameter. If it happens to be the + * currently applied theme, the current theme is set to null and the default XNAT theme will be restored. + */ + @Override + public void handleDelete() { + String theme = this.getQueryVariable("theme"); + if(theme != null && !theme.isEmpty()) { + File f = new File(themesPath + File.separator + theme); + if (Roles.isSiteAdmin(user)) { + try { + FileUtils.deleteDirectory(f); + ThemeConfig tc = getTheme(); + String themeName = (tc!=null)?tc.getName():null; + if(theme != null && theme.equals(themeName)){ + setTheme(null); + } + } catch (Exception e) { + e.printStackTrace(); + getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, "Theme directory deletion failed."); + } + } else { + getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, "Only site administrators can delete themes."); + } + } + } + + @Override + public boolean allowPut() { + return true; + } + + /** // TODO: convert to swagger doc tags + * Administrator use only + * Sets the currently selected global theme configuration package by updating the theme.json file under the + * application's theme folder and caches a copy of it in the app server's application context for reference by any + * other pages or servlets that may need to alter their styles and behaviors to fit the overriding theme. + */ + @Override + public void handlePut() { + if (Roles.isSiteAdmin(user)) { + boolean enabled = true; + String name = this.getQueryVariable("theme"), path = themesPath + File.separator + name; + if("true".equals(this.getQueryVariable("disabled"))){ + enabled = false; + } + setTheme(new ThemeConfig(name, themesPath, enabled)); + } else { + getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, "Only site administrators can set the site theme."); + } + } + + @Override + public boolean allowPost() { + return true; + } + + /** // TODO: convert to swagger doc tags + * Administrator use only + * Accepts a multipart form with a zip file upload and extracts its contents in the theme system folder. + * The structure of the zipped package must have only directories at it's root + * If successful, the first (root) directory name (or theme name) unzipped is returned in the response. + * This will overwrite any other directories already existing with the same name without warning. + */ + @Override + public void handlePost() { //Representation entity) { // Upload Theme package + Representation result = null; + if (Roles.isSiteAdmin(user)) { + try { + String firstDirName = ""; + FileWriterWrapperI fw; + List<FileWriterWrapperI> fws = this.getFileWriters(); + if (fws.size() == 0) { + this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + getResponse().setEntity("No valid files were uploaded.", MediaType.TEXT_PLAIN); + return; + } + if (fws.size() > 1) { + this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + getResponse().setEntity("Theme importer is limited to one uploaded theme package per request.", MediaType.TEXT_PLAIN); + return; + } + fw = fws.get(0); //TODO: inspect this fw object a little more for metadata + // TODO: validate zipped media type (fail otherwise) + final InputStream is = fw.getInputStream(); + ZipInputStream zipIn = new ZipInputStream(is); + ZipEntry entry = zipIn.getNextEntry(); + int dirCount = 0; + while (entry != null) { // iterate over entries in the zip file + String filePath = themesPath + File.separator + entry.getName(); + if (!entry.isDirectory()) { // if the entry is a file, extract it // TODO: Make sure we get a directory the first iteration through (fail otherwise) so that no files get dumped in the root themes directory + extractFile(zipIn, filePath); + } else { // if the entry is a directory, make the directory + if(dirCount == 0) { + firstDirName = entry.getName(); + int slashIndex = firstDirName.indexOf('/'); + if(slashIndex>1){ + firstDirName = firstDirName.substring(0, slashIndex); + } + } + dirCount++; + File dir = new File(filePath); + dir.mkdir(); + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + zipIn.close(); + is.close(); + getResponse().setEntity(firstDirName, MediaType.TEXT_PLAIN); + } catch (FileUploadBase.InvalidContentTypeException icte){ + this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + getResponse().setEntity("Invalid Content Type: "+icte.getMessage(), MediaType.TEXT_PLAIN); + } catch (FileUploadException fue) { + this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + getResponse().setEntity("File Upload Exception: " + fue.getMessage(), MediaType.TEXT_PLAIN); + } catch (ClientException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, "Only site administrators can upload new themes."); + } + } + + /** + * Extracts a zip entry (file entry) + * @param zip + * @param path + * @throws IOException + */ + private void extractFile(ZipInputStream zip, String path) throws IOException { + BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(path)); + byte[] bytes = new byte[FILE_BUFFER_SIZE]; + int length = 0; + while ((length = zip.read(bytes)) != -1) { + os.write(bytes, 0, length); + } + os.close(); + } + + /** // TODO: convert to swagger doc tags + * Reports the currently selected global theme (if there is one), whether or not it's enabled, and a list of + * available themes on the system in a JSON string. + */ + @Override + public Representation represent(Variant variant){ + String themeOptions = null, themeName = null; + boolean themeEnabled = false; + ThemeConfig theme = getTheme(); + try { + themeOptions = mapper.writeValueAsString(loadExistingThemes().toArray()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + if(theme != null) { + themeName="\""+theme.getName()+"\""; + themeEnabled=theme.isEnabled(); + } + return new StringRepresentation( + "{\"theme\":"+themeName+", \"enabled\":"+themeEnabled+", \"themeOptions\":"+themeOptions+"}", + MediaType.APPLICATION_JSON); + } + + /** + * Loads the system theme options + * @return The list of the available theme packages (folder names) available under the system themes directory + */ + public ArrayList<TypeOption> loadExistingThemes() { + ArrayList<TypeOption> themeOptions = new ArrayList<>(); + themeOptions.add(new TypeOption(null, "None")); + File f = new File(themesPath); // current directory + FileFilter directoryFilter = new FileFilter() { + public boolean accept(File file) { + return file.isDirectory(); + } + }; + File[] files = f.listFiles(directoryFilter); + if(files != null) { + for (File file : files) { + if (file.isDirectory()) { + themeOptions.add(new TypeOption(file.getName(), file.getName())); + } + } + } + return themeOptions; + } + + /** + * Gets the currently selected system theme from an application servlet context cache, or secondarily from the + * theme.json file in the themes folder. + * @return The currently selected system theme configuration + */ + public ThemeConfig getTheme() { + ThemeConfig themeConfig = null, cachedTheme = (ThemeConfig) this.getHttpSession().getServletContext().getAttribute("theme"); + if(cachedTheme != null){ + return cachedTheme; + } else { // Read the last saved theme selection from the theme.json file in the themes + if (themeFile.exists()) { // directory in the event it can't be found in the application context. + try { // (ie. the server was just started/restarted) + BufferedReader reader = new BufferedReader(new FileReader(themeFile)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + String contents = sb.toString(); + themeConfig = mapper.readValue(contents, ThemeConfig.class); + } catch (IOException e) { + e.printStackTrace(); + } + } + setTheme(themeConfig); + } + return themeConfig; + } + + /** + * Sets the currently selected system theme in the theme.json file in the web application's themes folder and + * stores it in the application servlet context cache + * @param themeConfig the theme configuration object to apply + */ + public void setTheme(ThemeConfig themeConfig) { + try { + if (themeConfig != null) { + String themeJson = mapper.writeValueAsString(themeConfig); + if (!themeFile.exists()) { + themeFile.createNewFile(); + } + FileWriter writer = new FileWriter(themeFile); + writer.write(themeJson); + writer.flush(); + writer.close(); + } else { + themeFile.delete(); + if (themeFile.exists()) { // Backup hack in case the file deletion does not + FileWriter writer = new FileWriter(themeFile); // succeed due to a file lock which sometimes + writer.write("{\"name\":null}"); // strangely happens in Windows + writer.flush(); // ...in that case wipe it out. + writer.close(); + } + } + } catch (JsonProcessingException e) { + e.printStackTrace(); + // TODO: rethrow this and respond as an internal server error + } catch (IOException e) { + e.printStackTrace(); + } + this.getHttpSession().getServletContext().setAttribute("theme", themeConfig); + } + + /** + * Helper class to organize the available themes for display in a select dropdown form + */ + public static class TypeOption implements Comparable<TypeOption> { + String value, label; + private TypeOption(String value, String label) { + this.value = value; + this.label = label; + } + public String getValue() { + return value; + } + public String getLabel() { + return label; + } + @Override + public int compareTo(TypeOption that) { + return this.label.compareToIgnoreCase(that.label); + } + } +} diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java new file mode 100644 index 0000000000000000000000000000000000000000..54957d5e43cb9116eeb4a604a67e99f92696938b --- /dev/null +++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java @@ -0,0 +1,30 @@ +/* + * org.nrg.xnat.turbine.modules.screens.ManageProtocol + * XNAT http://www.xnat.org + * Copyright (c) 2013, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + * + * Author: Justin Cleveland <clevelandj@wustl.edu> + * Last modified 1/22/2016 3:20 PM + */ + +package org.nrg.xnat.turbine.modules.screens; + +import org.apache.log4j.Logger; +import org.apache.turbine.util.RunData; +import org.apache.velocity.context.Context; +import org.nrg.xdat.turbine.modules.screens.SecureScreen; +import org.nrg.xnat.restlet.extensions.ThemeRestlet; + + +/** + * Created by jcleve01 on 1/22/2016. + */ +public class XDATScreen_themes extends SecureScreen { + public final static Logger logger = Logger.getLogger(XDATScreen_themes.class); + @Override + protected void doBuildTemplate(RunData data, Context context) throws Exception { + } +} diff --git a/src/main/webapp/WEB-INF/conf/InstanceSettings.xml b/src/main/webapp/WEB-INF/conf/InstanceSettings.xml deleted file mode 100644 index f7fff52df3be155efcf77337fbd2df6e4f09540a..0000000000000000000000000000000000000000 --- a/src/main/webapp/WEB-INF/conf/InstanceSettings.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- edited with XMLSPY v2004 rel. 3 U (http://www.xmlspy.com) by Tim Olsen (Washington University) --> -<Instance_Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="schemas/xdat/instance.xsd" - site_url="http://localhost:8080" admin_email="administrator@xnat.org" archive_root_path="/data/xnat/archive" - prearchive_path="/data/xnat/prearchive" cache_path="/data/xnat/cache" - smtp_server="irony.wusm.wustl.edu" ftp_path="/data/xnat/ftp" build_path="/data/xnat/build" - pipeline_path="/data/xnat/pipeline" require_login="true" user_registration="false" enable_csrf_token="true"> - <Databases> - <Database Type="POSTGRESQL" Id="xnat" Driver="org.postgresql.Driver" Url="jdbc:postgresql://localhost/xnat" User="xnat" Pass="xnat" MaxConnections="10"/> - </Databases> - <Models> - <Data_Model File_Name="security.xsd" File_Location="schemas/security" DB="xnat"/> - <Data_Model File_Name="birnprov.xsd" File_Location="schemas/birn" DB="xnat"/> - <Data_Model File_Name="xnat.xsd" File_Location="schemas/xnat" DB="xnat"/> - <Data_Model File_Name="workflow.xsd" File_Location="schemas/pipeline" DB="xnat"/> - <Data_Model File_Name="repository.xsd" File_Location="schemas/pipeline" DB="xnat"/> - <Data_Model File_Name="project.xsd" File_Location="schemas/project" DB="xnat"/> - <Data_Model File_Name="assessments.xsd" File_Location="schemas/assessments" DB="xnat"/> - <Data_Model File_Name="catalog.xsd" File_Location="schemas/catalog" DB="xnat"/> - <Data_Model File_Name="protocolValidation.xsd" File_Location="schemas/validation" DB="xnat"/> - <Data_Model File_Name="screeningAssessment.xsd" File_Location="schemas/screening" DB="xnat"/> - -<!--<Data_Model File_Name="ext.xsd" File_Location="schemas/ext" DB="xnat"/>--> - <!-- Start modules schemas: Reserved for xnat_builder, don't modify anything between this line and the end modules line. --> - <!-- End modules schemas: Reserved for xnat_builder, don't modify anything between this line and the start modules line. --> - </Models> -</Instance_Settings> diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 4eb56411ccd19b6c6f6b5900afe04c7c2073677f..0000000000000000000000000000000000000000 --- a/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,326 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - ~ D:/Development/XNAT/1.6/xnat_builder_1_6dev/plugin-resources/originals/application-web.xml - ~ XNAT http://www.xnat.org - ~ Copyright (c) 2014, Washington University School of Medicine - ~ All Rights Reserved - ~ - ~ Released under the Simplified BSD. - ~ - ~ Last modified 2/7/14 12:19 PM - --> -<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> - <!-- ======================================================================== --> - <!-- --> - <!-- Uncomment the following block if you want to use the Session Service --> - <!-- --> - <!-- ======================================================================== --> - <!-- - <listener> - <listener-class>org.apache.turbine.services.session.SessionListener</listener-class> - </listener> - --> - <!-- HttpSessionEventPublisher enables session counting for the concurrent session filter, XnatSessionEventPublisher requires the contextAttribute parameter --> - <context-param> - <param-name>contextAttribute</param-name> - <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring-mvc</param-value> - </context-param> - <context-param> - <param-name>org.restlet.component</param-name> - <param-value>org.nrg.xnat.restlet.XNATComponent</param-value> - </context-param> - <!-- - This setting will be automatically configured according to xdat.url in build.properties. - You can still set it manually if desired (not here - do it in xnat/projects/xnat/src/web-conf/web-projectMerge.xml). - --> - <context-param> - <param-name>org.restlet.autoWire</param-name> - <param-value>false</param-value> - </context-param> - <filter> - <filter-name>springSecurityFilterChain</filter-name> - <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> - <init-param> - <param-name>contextAttribute</param-name> - <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring-mvc</param-value> - </init-param> - </filter> - <filter> - <filter-name>updateExpirationCookie</filter-name> - <filter-class>org.nrg.xnat.restlet.util.UpdateExpirationCookie</filter-class> - </filter> - <filter-mapping> - <filter-name>springSecurityFilterChain</filter-name> - <url-pattern>/*</url-pattern> - </filter-mapping> - <filter-mapping> - <filter-name>updateExpirationCookie</filter-name> - <url-pattern>/*</url-pattern> - </filter-mapping> - <listener> - <listener-class>org.nrg.xnat.security.XnatSessionEventPublisher</listener-class> - </listener> - <listener> - <listener-class>org.apache.axis.transport.http.AxisHTTPSessionListener</listener-class> - </listener> - <servlet> - <servlet-name>xnat17</servlet-name> - <servlet-class>org.apache.turbine.Turbine</servlet-class> - <init-param> - <param-name>properties</param-name> - <param-value>WEB-INF/conf/TurbineResources.properties</param-value> - </init-param> - <load-on-startup>1</load-on-startup> - </servlet> - <!-- BEGIN XNAT CUSTOMIZATIONS --> - <!-- ======================================================================== --> - <!-- INSERTED FOR Spring Framework --> - <!-- ======================================================================== --> - <servlet> - <servlet-name>spring-mvc</servlet-name> - <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> - <init-param> - <param-name>contextConfigLocation</param-name> - <param-value>/WEB-INF/conf/root-spring-config.xml</param-value> - </init-param> - <load-on-startup>1</load-on-startup> - </servlet> - <servlet> - <servlet-name>xdat</servlet-name> - <servlet-class>org.nrg.xdat.servlet.XDATServlet</servlet-class> - <load-on-startup>1</load-on-startup> - </servlet> - <!-- ======================================================================== --> - <!-- INSERTED FOR RESTServlet --> - <!-- ======================================================================== --> - <servlet> - <servlet-name>RestletServlet</servlet-name> - <servlet-class>org.nrg.xnat.restlet.servlet.XNATRestletServlet</servlet-class> - <load-on-startup>2</load-on-startup> - </servlet> - <!-- ======================================================================== --> - <!-- INSERTED FOR ArchiveServlet --> - <!-- ======================================================================== --> - <servlet> - <servlet-name>ArchiveServlet</servlet-name> - <servlet-class>org.nrg.xnat.servlet.ArchiveServlet</servlet-class> - <load-on-startup>2</load-on-startup> - </servlet> - <!-- ======================================================================== --> - <!-- INSERTED FOR Ajax --> - <!-- ======================================================================== --> - <servlet> - <servlet-name>XDATAjaxServlet</servlet-name> - <servlet-class>org.nrg.xdat.servlet.XDATAjaxServlet</servlet-class> - </servlet> - <!-- ======================================================================== --> - <!-- INSERTED FOR Apache Axis --> - <!-- ======================================================================== --> - <servlet> - <servlet-name>AxisServlet</servlet-name> - <servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class> - </servlet> - <servlet> - <servlet-name>AdminServlet</servlet-name> - <servlet-class>org.apache.axis.transport.http.AdminServlet</servlet-class> - <load-on-startup>100</load-on-startup> - </servlet> - <servlet> - <servlet-name>SOAPMonitorService</servlet-name> - <servlet-class>org.apache.axis.monitor.SOAPMonitorService</servlet-class> - <init-param> - <param-name>SOAPMonitorPort</param-name> - <param-value>5001</param-value> - </init-param> - <load-on-startup>100</load-on-startup> - </servlet> - <!-- ======================================================================== --> - <!-- INSERTED FOR Image Viewer --> - <!-- ======================================================================== --> - <servlet> - <servlet-name>PopulateServlet</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.PopulateServlet</servlet-class> - </servlet> - <servlet> - <servlet-name>ImageDistributorServlet</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.ImageDistributorServlet</servlet-class> - </servlet> - <servlet> - <servlet-name>ImageLoaderServlet</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.ImageLoaderServlet</servlet-class> - </servlet> - <servlet> - <servlet-name>MontageImageLoaderServlet</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.MontageImageLoaderServlet</servlet-class> - </servlet> - <servlet> - <servlet-name>GetRecFileContents</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.GetRecFileContents</servlet-class> - </servlet> - <servlet> - <servlet-name>getAsegRegionVolumes</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.getAsegRegionVolumes</servlet-class> - </servlet> - <servlet> - <servlet-name>PublisherServlet</servlet-name> - <servlet-class>org.nrg.plexiViewer.Servlet.PublisherServlet</servlet-class> - </servlet> - <servlet-mapping> - <servlet-name>spring-mvc</servlet-name> - <url-pattern>/admin/*</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>spring-mvc</servlet-name> - <url-pattern>/xapi/*</url-pattern> - </servlet-mapping> - <!-- END XNAT CUSTOMIZATIONS --> - <servlet-mapping> - <servlet-name>xnat17</servlet-name> - <url-pattern>/app/*</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>xdat</servlet-name> - <url-pattern>/xdat/*</url-pattern> - </servlet-mapping> - <!-- ======================================================================== --> - <!-- INSERTED FOR RESTServlet --> - <!-- ======================================================================== --> - <servlet-mapping> - <servlet-name>RestletServlet</servlet-name> - <url-pattern>/REST/*</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>RestletServlet</servlet-name> - <url-pattern>/data/*</url-pattern> - </servlet-mapping> - <!-- ======================================================================== --> - <!-- INSERTED FOR Ajax --> - <!-- ======================================================================== --> - <servlet-mapping> - <servlet-name>XDATAjaxServlet</servlet-name> - <url-pattern>/ajax/*</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>XDATAjaxServlet</servlet-name> - <url-pattern>/servlet/XDATAjaxServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>XDATAjaxServlet</servlet-name> - <url-pattern>/servlet/AjaxServlet</url-pattern> - </servlet-mapping> - <!-- ======================================================================== --> - <!-- INSERTED FOR Apache Axis --> - <!-- ======================================================================== --> - <servlet-mapping> - <servlet-name>AdminServlet</servlet-name> - <url-pattern>/servlet/AdminServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>AxisServlet</servlet-name> - <url-pattern>/servlet/AxisServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>AxisServlet</servlet-name> - <url-pattern>*.jws</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>AxisServlet</servlet-name> - <url-pattern>/services/*</url-pattern> - </servlet-mapping> - <!-- ======================================================================== --> - <!-- INSERTED FOR Image Viewer --> - <!-- ======================================================================== --> - <servlet-mapping> - <servlet-name>SOAPMonitorService</servlet-name> - <url-pattern>/SOAPMonitor</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>PopulateServlet</servlet-name> - <url-pattern>/servlet/PopulateServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>ImageDistributorServlet</servlet-name> - <url-pattern>/servlet/ImageDistributorServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>ImageLoaderServlet</servlet-name> - <url-pattern>/servlet/ImageLoaderServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>MontageImageLoaderServlet</servlet-name> - <url-pattern>/servlet/MontageImageLoaderServlet</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>GetRecFileContents</servlet-name> - <url-pattern>/servlet/GetRecFileContents</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>getAsegRegionVolumes</servlet-name> - <url-pattern>/servlet/getAsegRegionVolumes</url-pattern> - </servlet-mapping> - <servlet-mapping> - <servlet-name>PublisherServlet</servlet-name> - <url-pattern>/servlet/PublisherServlet</url-pattern> - </servlet-mapping> - <!-- ======================================================================== --> - <!-- INSERTED FOR ArchiveServlet --> - <!-- ======================================================================== --> - <servlet-mapping> - <servlet-name>ArchiveServlet</servlet-name> - <url-pattern>/archive/*</url-pattern> - </servlet-mapping> - <session-config> - <!-- Default to 15 minute session timeouts --> - <session-timeout>15</session-timeout> - </session-config> - <mime-mapping> - <extension>wsdl</extension> - <mime-type>text/xml</mime-type> - </mime-mapping> - <mime-mapping> - <extension>xsd</extension> - <mime-type>text/xml</mime-type> - </mime-mapping> - <!-- ======================================================================== --> - <!-- --> - <!-- Redirect the home page of the application to the turbine servlet --> - <!-- --> - <!-- ======================================================================== --> - <welcome-file-list> - <welcome-file>index.jsp</welcome-file> - <welcome-file>app</welcome-file> - </welcome-file-list> - <!-- ======================================================================== --> - <!-- --> - <!-- Mapping HTTP error codes and exceptions to custom error pages to make --> - <!-- the display a bit more pleasant and preserve system confidentiality. --> - <!-- --> - <!-- ======================================================================== --> - <error-page> - <exception-type>java.lang.Throwable</exception-type> - <location>/app/template/Error.vm</location> - </error-page> - <!-- ======================================================================== --> - <!-- --> - <!-- Make sure that templates, resources and logs are not available through --> - <!-- the servlet container. Remove security constraints or add an authen- --> - <!-- tication role if you need access to these paths. --> - <!-- --> - <!-- ======================================================================== --> - <security-constraint> - <web-resource-collection> - <web-resource-name>templates</web-resource-name> - <url-pattern>/templates/*</url-pattern> - </web-resource-collection> - <web-resource-collection> - <web-resource-name>logs</web-resource-name> - <url-pattern>/logs/*</url-pattern> - </web-resource-collection> - <web-resource-collection> - <web-resource-name>resources</web-resource-name> - <url-pattern>/resources/*</url-pattern> - </web-resource-collection> - <auth-constraint /> - </security-constraint> -</web-app> - diff --git a/src/main/webapp/scripts/footer.js b/src/main/webapp/scripts/footer.js index 177a65065693896b8f25c68ae7f7634feed7e6af..98590d8ac31767d4ba254ddc8eeb2f76a5764c07 100644 --- a/src/main/webapp/scripts/footer.js +++ b/src/main/webapp/scripts/footer.js @@ -544,6 +544,23 @@ $(function(){ }); jq(window).load(function(){ + // adding shortcut methods to put and delete AJAX calls for clarity + jq.each(["put", "delete"], function(i, method) { + jq[method] = function(url, data, callback, type) { + if (jq.isFunction(data)) { + type = type || callback; + callback = data; + data = undefined; + } + return jq.ajax({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }); + }; + }); // trying to make the text readable jq('[style*="font-size:8px"]').addClass('smallest_text'); diff --git a/src/main/webapp/xnat-templates/screens/XDATScreen_admin_options.vm b/src/main/webapp/xnat-templates/screens/XDATScreen_admin_options.vm index a1a2af2ffbb51a55223c8bce529a4f75ee557a02..98c311be4ebdd84fec77db45264e344d9af4cd29 100644 --- a/src/main/webapp/xnat-templates/screens/XDATScreen_admin_options.vm +++ b/src/main/webapp/xnat-templates/screens/XDATScreen_admin_options.vm @@ -53,6 +53,8 @@ $!template.setLayoutTemplate("DefaultExptList.vm") <a href="$link.setPage("XDATScreen_add_xnat_projectData.vm")">Create $displayManager.getSingularDisplayNameForProject()</a> + <a href="$link.setPage("XDATScreen_themes.vm")">Manage Themes</a> + #addGlobalCustomScreens("admin/options") </div> diff --git a/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm b/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm new file mode 100644 index 0000000000000000000000000000000000000000..f9d016eda9441f8c2ea6f97ac5cd1799f05038ca --- /dev/null +++ b/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm @@ -0,0 +1,112 @@ +<!-- Begin XDATScreen_themes.vm --> +<style> + .bold{ + font-weight: bold; + } +</style> +<div style="margin-left: 20px;"> + <h2><span id="titleAppName"></span> Theme Management</h2> + <div id="currentThemeDiv"> + <span class="label bold">Current theme: </span> + <span id="currentTheme">None</span> + </div> + <br> + <div id="selectThemeDiv"> + <span class="label bold">Select an existing theme: </span> + <select id="themeSelection" name="theme" style="width: 270px;"></select> + <button id="submitThemeButton" onclick="setTheme();">Set Theme</button> + <button id="removeThemeButton" onclick="removeTheme();">Remove Theme</button> + </div> + <br> + <form id="uploadThemeForm" method="POST" class="optOutOfXnatDefaultFormValidation"> + <span class="label bold">Upload a theme package: </span> + <input type="file" id="themeFileUpload" name="themeFileUpload" multiple style="width: 270px;"/> + <button type="submit" id="submitThemeUploadButton">Upload</button> + </form> +</div> + +<script> + var themeUrl = "$content.getURI('/data/theme')"; + var q = '?', a = '&'; + var csrf = 'XNAT_CSRF='+window.csrfToken; + $('#titleAppName').text(XNAT.app.siteId); + var currentTheme = $('#currentTheme'); + var themeSelector = $('#themeSelection'); + var uploadForm = document.getElementById('uploadThemeForm'); + var themeUploader = document.getElementById('themeFileUpload'); + var themeUploadSubmit = document.getElementById('submitThemeUploadButton'); + var selectedTheme = null; + function populateThemes(themeToSelect){ + $.get(themeUrl+q+csrf, null, function (data){ + themeSelector.empty(); + $(data.themeOptions).each(function(i, opt) { + var selected = '', selectedTheme = null; + if(data.theme == opt.value){ + selectedTheme = data.theme; + selected = ' selected="selected"'; + currentTheme.text(selectedTheme?selectedTheme:'None'); + } + themeSelector.append('<option value="'+opt.value+'"'+selected+'>'+opt.label+'</option>'); + }); + if(themeToSelect && typeof themeToSelect === 'string'){ + themeSelector.val(themeToSelect); + } + }, 'json'); + } + function setTheme(){ + xmodal.confirm({ + content: 'Theme selection appearances may not fully take effect until users log out, clear their browser cache and log back in.'+ + '<br><br>Are you sure you wish to change the global theme?', + action: function(){ + $.put(themeUrl+q+'theme='+encodeURI(themeSelector.val())+a+csrf, null, populateThemes); + } + }); + } + function removeTheme(){ + xmodal.confirm({ + content: 'Are you sure you wish to delete the selected theme?', + action: function(){ + $.delete(themeUrl+q+'theme='+encodeURI(themeSelector.val())+a+csrf, null, populateThemes); + } + }); + } + + /*** Theme Package Upload Functions ***/ + uploadForm.action = themeUrl+q+csrf; + uploadForm.onsubmit = function(event) { + event.preventDefault(); + $(themeUploadSubmit).text('Uploading...'); + $(themeUploadSubmit).attr('disabled', 'disabled'); + var files = themeUploader.files; + var formData = new FormData(); + var uploaded = false; + for (var i = 0; i < files.length; i++) { + var file = files[i]; + if (!file.type.match('zip.*')) { + continue; + } + formData.append('themes[]', file, file.name); + var xhr = new XMLHttpRequest(); + xhr.open('POST', uploadForm.action, true); + xhr.onload = function () { + if (xhr.status !== 200) { + xmodal.message('Upload Error', xhr.responseText); + } + var uploadedTheme = xhr.responseText; + $(themeUploadSubmit).text('Upload'); + $(themeUploadSubmit).removeAttr('disabled'); + populateThemes(uploadedTheme); + }; + xhr.send(formData); + uploaded = true; + } + if(!uploaded){ + xmodal.message('Nothing Uploaded', 'No valid theme package files were selected for upload.'); + $(themeUploadSubmit).text('Upload'); + $(themeUploadSubmit).removeAttr('disabled'); + } + } + $(populateThemes); // ...called once DOM is fully loaded "ready" +</script> + +<!-- End XDATScreen_themes.vm --> \ No newline at end of file