1. Don Brown
  2. remoteapps-plugin

Commits

Bob Bergman  committed 70d05c0

More AUI support

* add RenderContext
* move aui flatpack
* update servlet-kit with aui support
* add request to RequestContext
* support AUI in ServletResourceFilter
* move gzipping from maven plugin build to StaticResourceFilter

Change-Id: I83f1eeaa1930fd332a82de83da5b6218b39e4262

  • Participants
  • Parent commits 25a6f06
  • Branches master

Comments (0)

Files changed (182)

File api/src/main/java/com/atlassian/labs/remoteapps/api/service/RenderContext.java

View file
  • Ignore whitespace
+package com.atlassian.labs.remoteapps.api.service;
+
+import com.atlassian.sal.api.message.I18nResolver;
+
+import java.util.Locale;
+import java.util.Map;
+
+public interface RenderContext
+{
+    String getHostContextPath();
+
+    String getHostBaseUrl();
+
+    String getHostBaseResourceUrl();
+
+    String getHostStylesheetUrl();
+
+    String getHostScriptUrl();
+
+    String getClientKey();
+
+    String getUserId();
+
+    Locale getLocale();
+
+    I18nResolver getI18n();
+
+    Map<String, Object> toContextMap();
+}

File api/src/main/java/com/atlassian/labs/remoteapps/api/service/RequestContext.java

View file
  • Ignore whitespace
     String getUserId();
 
     String getHostBaseUrl();
-
-    public void clear();
 }

File container/src/main/java/com/atlassian/labs/remoteapps/container/Container.java

View file
  • Ignore whitespace
 import com.atlassian.jira.rest.client.p3.JiraSearchClient;
 import com.atlassian.jira.rest.client.p3.JiraUserClient;
 import com.atlassian.jira.rest.client.p3.JiraVersionClient;
-import com.atlassian.labs.remoteapps.api.service.EmailSender;
-import com.atlassian.labs.remoteapps.api.service.HttpResourceMounter;
-import com.atlassian.labs.remoteapps.api.service.RequestContext;
-import com.atlassian.labs.remoteapps.api.service.SignedRequestHandler;
+import com.atlassian.labs.remoteapps.api.service.*;
 import com.atlassian.labs.remoteapps.api.service.confluence.ConfluenceAdminClient;
 import com.atlassian.labs.remoteapps.api.service.confluence.ConfluenceAttachmentClient;
 import com.atlassian.labs.remoteapps.api.service.confluence.ConfluenceBlogClient;
 import com.atlassian.labs.remoteapps.host.common.descriptor.DescriptorAccessor;
 import com.atlassian.labs.remoteapps.host.common.descriptor.DescriptorPermissionsReader;
 import com.atlassian.labs.remoteapps.host.common.descriptor.PolyglotDescriptorAccessor;
+import com.atlassian.labs.remoteapps.host.common.service.RenderContextServiceFactory;
 import com.atlassian.labs.remoteapps.host.common.service.RequestContextServiceFactory;
 import com.atlassian.labs.remoteapps.host.common.service.confluence.ConfluenceAdminClientServiceFactory;
 import com.atlassian.labs.remoteapps.host.common.service.confluence.ConfluenceAttachmentClientServiceFactory;
         final ContainerEventPublisher containerEventPublisher = new ContainerEventPublisher();
         final DefaultHttpClient httpClient = new DefaultHttpClient(requestKiller, containerEventPublisher);
         final HostHttpClientServiceFactory hostHttpClientServiceFactory = new HostHttpClientServiceFactory(httpClient, requestContextServiceFactory, oAuthSignedRequestHandlerServiceFactory);
+        final HostXmlRpcClientServiceFactory hostXmlRpcClientHostServiceFactory = new HostXmlRpcClientServiceFactory(hostHttpClientServiceFactory);
+        final ContainerLocaleResolver localeResolver = new ContainerLocaleResolver();
+        final ContainerI18nResolver i18nResolver = new ContainerI18nResolver(pluginManager, pluginEventManager, new ResourceBundleResolverImpl());
+        final RenderContextServiceFactory renderContextServiceFactory = new RenderContextServiceFactory(requestContextServiceFactory, localeResolver, i18nResolver);
 
         hostComponents.put(SignedRequestHandler.class, oAuthSignedRequestHandlerServiceFactory);
         hostComponents.put(HttpResourceMounter.class, containerHttpResourceMounterServiceFactory);
         hostComponents.put(RequestContext.class, requestContextServiceFactory);
         hostComponents.put(HttpClient.class, httpClient);
         hostComponents.put(HostHttpClient.class, hostHttpClientServiceFactory);
-        final HostXmlRpcClientServiceFactory hostXmlRpcClientHostServiceFactory = new HostXmlRpcClientServiceFactory(hostHttpClientServiceFactory);
         hostComponents.put(HostXmlRpcClient.class, hostXmlRpcClientHostServiceFactory);
         hostComponents.put(EmailSender.class, new HostHttpClientConsumerServiceFactory<EmailSender>(hostHttpClientServiceFactory, ContainerEmailSender.class));
-        hostComponents.put(LocaleResolver.class, new ContainerLocaleResolver());
-        hostComponents.put(I18nResolver.class, new ContainerI18nResolver(pluginManager, pluginEventManager, new ResourceBundleResolverImpl()));
+        hostComponents.put(LocaleResolver.class, localeResolver);
+        hostComponents.put(I18nResolver.class, i18nResolver);
         hostComponents.put(WebResourceManager.class, new NoOpWebResourceManager());
+        hostComponents.put(RenderContext.class, renderContextServiceFactory);
 
         hostComponents.put(DataSourceProvider.class, new RemoteAppsDataSourceProviderServiceFactory(applicationPropertiesServiceFactory));
         hostComponents.put(TransactionTemplate.class, new NoOpTransactionTemplate());

File host-common/src/main/java/com/atlassian/labs/remoteapps/host/common/service/AuthenticationFilter.java

View file
  • Ignore whitespace
         try
         {
             HttpServletRequest req = (HttpServletRequest) request;
+            requestContext.setRequest(req);
             URI uri = URI.create(req.getRequestURI());
 
             AuthenticationInfo info;

File host-common/src/main/java/com/atlassian/labs/remoteapps/host/common/service/DefaultRenderContext.java

View file
  • Ignore whitespace
+package com.atlassian.labs.remoteapps.host.common.service;
+
+import com.atlassian.labs.remoteapps.api.service.RenderContext;
+import com.atlassian.plugin.util.PluginUtils;
+import com.atlassian.sal.api.message.I18nResolver;
+import com.atlassian.sal.api.message.LocaleResolver;
+import com.google.common.collect.ImmutableMap;
+
+import java.net.URI;
+import java.util.Locale;
+import java.util.Map;
+
+public class DefaultRenderContext implements RenderContext
+{
+    public static final String HOST_RESOURCE_PATH = "/remoteapps";
+
+    private final boolean devMode = Boolean.getBoolean(PluginUtils.ATLASSIAN_DEV_MODE);
+
+    private DefaultRequestContext requestContext;
+    private final LocaleResolver localeResolver;
+    private final I18nResolver i18nResolver;
+
+    public DefaultRenderContext(DefaultRequestContext requestContext, LocaleResolver localeResolver, I18nResolver i18nResolver)
+    {
+        this.requestContext = requestContext;
+        this.localeResolver = localeResolver;
+        this.i18nResolver = i18nResolver;
+    }
+
+    @Override
+    public String getHostContextPath()
+    {
+        return URI.create(getHostBaseUrl()).getPath();
+    }
+
+    @Override
+    public String getHostBaseUrl()
+    {
+        return requestContext.getHostBaseUrl();
+    }
+
+    @Override
+    public String getHostBaseResourceUrl()
+    {
+        return requestContext.getHostBaseUrl() + HOST_RESOURCE_PATH;
+    }
+
+    @Override
+    public String getHostStylesheetUrl()
+    {
+        return getHostResourceUrl("all", "css");
+    }
+
+    @Override
+    public String getHostScriptUrl()
+    {
+        return getHostResourceUrl("all", "js");
+    }
+
+    @Override
+    public String getClientKey()
+    {
+        return requestContext.getClientKey();
+    }
+
+    @Override
+    public String getUserId()
+    {
+        return requestContext.getUserId();
+    }
+
+    @Override
+    public Locale getLocale()
+    {
+        return localeResolver.getLocale(requestContext.getRequest());
+    }
+
+    @Override
+    public I18nResolver getI18n()
+    {
+        return i18nResolver;
+    }
+
+    @Override
+    public Map<String, Object> toContextMap()
+    {
+        return ImmutableMap.<String, Object>builder()
+            .put("hostContextPath", getHostContextPath())
+            .put("hostBaseUrl", getHostBaseUrl())
+            .put("hostBaseResourceUrl", getHostBaseResourceUrl())
+            .put("hostStylesheetUrl", getHostStylesheetUrl())
+            .put("hostScriptUrl", getHostScriptUrl())
+            .put("userId", getUserId())
+            .put("clientKey", getClientKey())
+            .put("locale", getLocale())
+            .put("i18n", getI18n())
+            .build();
+    }
+
+    private String getHostResourceUrl(String name, String ext)
+    {
+        return getHostBaseResourceUrl() + "/" + name + (devMode ? "-debug" : "") + "." + ext;
+    }
+}

File host-common/src/main/java/com/atlassian/labs/remoteapps/host/common/service/DefaultRequestContext.java

View file
  • Ignore whitespace
 import com.atlassian.labs.remoteapps.api.service.RequestContext;
 import com.atlassian.labs.remoteapps.api.service.SignedRequestHandler;
 
+import javax.servlet.http.HttpServletRequest;
+
 public class DefaultRequestContext implements RequestContext
 {
     private static final ThreadLocal<RequestData> requestContextHolder = new ThreadLocal<RequestData>();
-    private static final RequestData EMPTY_DATA = new RequestData(null, null);
+    private static final RequestData EMPTY_DATA = new RequestData(null, null, null);
 
     private final SignedRequestHandler signedRequestHandler;
 
 
     public void setClientKey(String clientKey)
     {
-        setRequestData(new RequestData(clientKey, getRequestData().getUserId()));
+        RequestData data = getRequestData();
+        setRequestData(new RequestData(data.getRequest(), clientKey, data.getUserId()));
     }
 
     private RequestData getRequestData()
     public void setUserId(String userId)
     {
         RequestData data = getRequestData();
-        setRequestData(new RequestData(data.getClientKey(), userId));
+        setRequestData(new RequestData(data.getRequest(), data.getClientKey(), userId));
     }
 
-
-
     public <P, R> RequestCallable<P, R> createCallableForCurrentRequest(final RequestCallable<P, R> callable)
     {
         final RequestData old = getRequestData();
         return signedRequestHandler.getHostBaseUrl(getClientKey());
     }
 
-    @Override
+    public HttpServletRequest getRequest()
+    {
+        return requestContextHolder.get().getRequest();
+    }
+
+    public void setRequest(HttpServletRequest request)
+    {
+        RequestData data = getRequestData();
+        setRequestData(new RequestData(request, data.getClientKey(), data.getUserId()));
+    }
+
     public void clear()
     {
         requestContextHolder.remove();
     {
         private final String clientKey;
         private final String userId;
+        private final HttpServletRequest request;
 
-        private RequestData(String clientKey, String userId)
+        private RequestData(HttpServletRequest request, String clientKey, String userId)
         {
+            this.request = request;
             this.clientKey = clientKey;
             this.userId = userId;
         }
 
+        public HttpServletRequest getRequest()
+        {
+            return request;
+        }
+
         public String getClientKey()
         {
             return clientKey;

File host-common/src/main/java/com/atlassian/labs/remoteapps/host/common/service/RenderContextServiceFactory.java

View file
  • Ignore whitespace
+package com.atlassian.labs.remoteapps.host.common.service;
+
+import com.atlassian.labs.remoteapps.api.service.RenderContext;
+import com.atlassian.sal.api.message.I18nResolver;
+import com.atlassian.sal.api.message.LocaleResolver;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceRegistration;
+
+public class RenderContextServiceFactory implements TypedServiceFactory<RenderContext>
+{
+    private RequestContextServiceFactory requestContextServiceFactory;
+    private final LocaleResolver localeResolver;
+    private final I18nResolver i18nResolver;
+
+    public RenderContextServiceFactory(RequestContextServiceFactory requestContextServiceFactory,
+                                       LocaleResolver localeResolver,
+                                       I18nResolver i18nResolver)
+    {
+        this.requestContextServiceFactory = requestContextServiceFactory;
+        this.localeResolver = localeResolver;
+        this.i18nResolver = i18nResolver;
+    }
+
+    @Override
+    public Object getService(Bundle bundle, ServiceRegistration registration)
+    {
+        return getService(bundle);
+    }
+
+    public DefaultRenderContext getService(Bundle bundle)
+    {
+        DefaultRequestContext requestContext = requestContextServiceFactory.getService(bundle);
+        return new DefaultRenderContext(requestContext, localeResolver, i18nResolver);
+    }
+
+    @Override
+    public void ungetService(Bundle bundle, ServiceRegistration registration, Object service)
+    {
+    }
+}

File plugin/pom.xml

View file
  • Ignore whitespace
                             <goal>compress</goal>
                         </goals>
                         <configuration>
-                            <gzip>true</gzip>
+                            <gzip>false</gzip>
                             <excludeResources>true</excludeResources>
                             <sourceDirectory>${project.build.outputDirectory}/css/iframe</sourceDirectory>
                             <outputDirectory>${project.build.outputDirectory}/css/iframe-min</outputDirectory>
                             <goal>compress</goal>
                         </goals>
                         <configuration>
-                            <gzip>true</gzip>
+                            <gzip>false</gzip>
                             <nocompress>true</nocompress>
                             <excludeResources>true</excludeResources>
                             <aggregations>
                             <goal>compress</goal>
                         </goals>
                         <configuration>
-                            <gzip>true</gzip>
+                            <gzip>false</gzip>
                             <jswarn>false</jswarn>
                             <excludeResources>true</excludeResources>
                             <sourceDirectory>${project.build.outputDirectory}/js/iframe</sourceDirectory>
                             <goal>compress</goal>
                         </goals>
                         <configuration>
-                            <gzip>true</gzip>
+                            <gzip>false</gzip>
                             <nocompress>true</nocompress>
                             <jswarn>false</jswarn>
                             <excludeResources>true</excludeResources>

File plugin/src/main/java/com/atlassian/labs/remoteapps/plugin/iframe/StaticResourcesFilter.java

View file
  • Ignore whitespace
 package com.atlassian.labs.remoteapps.plugin.iframe;
 
+import com.atlassian.labs.remoteapps.host.common.service.DefaultRenderContext;
 import com.atlassian.plugin.Plugin;
 import com.atlassian.plugin.osgi.bridge.external.PluginRetrievalService;
 import com.atlassian.plugin.util.PluginUtils;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Calendar;
 import java.util.Map;
 import java.util.regex.Pattern;
+import java.util.zip.GZIPOutputStream;
 
 /**
- * Provides the aggregated js for iframes
+ * Provides static host resources for plugin iframes
  */
 public class StaticResourcesFilter implements Filter
 {
-    // todo: support languages
-    private static final Pattern RESOURCE_PATTERN = Pattern.compile("/[a-zA-Z0-9\\-_]+\\.(?:js|css)");
-    private static final Logger log = LoggerFactory.getLogger(StaticResourcesFilter.class);
-    private static Map<String,CacheEntry> resCache = new MapMaker().makeComputingMap(new Function<String, CacheEntry>() {
+    public static final int PLUGIN_TTL_NEAR_FUTURE  = 60 * 30;               // 30 min
+    public static final int AUI_TTL_FAR_FUTURE      = 60 * 60 * 24 * 365;    // 1 year
 
-        @Override
-        public CacheEntry apply(String from)
-        {
-            return new CacheEntry(from);
-        }
-    });
+    private static final Pattern RESOURCE_PATTERN = Pattern.compile("(all(-debug)?\\.(js|css))|(aui/.*)");
+    private static final Logger log = LoggerFactory.getLogger(StaticResourcesFilter.class);
     private static Plugin plugin;
+
     private final boolean devMode;
+    private FilterConfig config;
+    private Map<String, CacheEntry> cache;
 
     public StaticResourcesFilter(PluginRetrievalService pluginRetreivalService)
     {
         plugin = pluginRetreivalService.getPlugin();
         devMode = Boolean.getBoolean(PluginUtils.ATLASSIAN_DEV_MODE);
     }
+
     @Override
-    public void init(FilterConfig filterConfig) throws ServletException
+    public void init(FilterConfig config) throws ServletException
     {
+        this.config = config;
+        cache = new MapMaker().makeComputingMap(new Function<String, CacheEntry>()
+        {
+            @Override
+            public CacheEntry apply(String from)
+            {
+                return new CacheEntry(from);
+            }
+        });
     }
 
     @Override
     {
         HttpServletRequest req = (HttpServletRequest) request;
         HttpServletResponse res = (HttpServletResponse) response;
-        String path = req.getRequestURI().substring(req.getContextPath().length() + "/remoteapps".length());
-        if (RESOURCE_PATTERN.matcher(path).matches())
+
+        // compute the starting resource path from the request
+        String fullPath = req.getRequestURI().substring(req.getContextPath().length());
+
+        // only serve resources in the host resource path, though this is precautionary only since no other
+        // paths should be mapped to this filter in the first place
+        if (!fullPath.startsWith(DefaultRenderContext.HOST_RESOURCE_PATH))
         {
-            String localPath = path.substring(1);
-            if (req.getHeader("Accept-Encoding").contains("gzip"))
-            {
-                localPath += ".gz";
-            }
-            CacheEntry entry = resCache.get(localPath);
-            if (entry.getData().length == 0)
-            {
-                send404(path, res);
-                return;
-            }
-            res.setHeader("ETag", entry.getEtag());
-            res.setHeader("Content-Encoding", "gzip");
-            res.setHeader("Vary", "Accept-Encoding");
-            res.setContentType(entry.getContentType());
-            res.setContentLength(entry.getData().length);
-            setCacheControl(res);
+            send404(fullPath, res);
+            return;
+        }
 
-  	        String previousToken = req.getHeader("If-None-Match");
-            if (previousToken != null && previousToken.equals(entry.getEtag()))
-            {
-                res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-            }
-            else
-            {
-                res.setHeader("Connection", "keep-alive");
-                ServletOutputStream sos = res.getOutputStream();
-                sos.write(entry.getData());
-                sos.flush();
-                sos.close();
-            }
-            if (devMode)
-            {
-                resCache.remove(localPath);
-            }
+        // prepare a local path suitable for use with plugin.getResourceAsStream
+        String localPath = fullPath.substring(DefaultRenderContext.HOST_RESOURCE_PATH.length() + 1);
+
+        // only make selected resources available
+        if (!RESOURCE_PATTERN.matcher(localPath).matches())
+        {
+            send404(fullPath, res);
+            return;
+        }
+
+        CacheEntry entry;
+        String encoding;
+        if (req.getHeader("Accept-Encoding").contains("gzip"))
+        {
+            // check if the request accepts gzip, then get a gzipped version of the resource from the cache
+            localPath += ".gz";
+            encoding = "gzip";
         }
         else
         {
-            send404(path, res);
+            encoding = "identity";
+        }
+
+        // ask the cache for an entry for the named resource
+        entry = cache.get(localPath);
+
+        // the entry's data will be empty if the resource was not found
+        if (entry.getData().length == 0)
+        {
+            // if not found, 404
+            send404(fullPath, res);
+            return;
+        }
+
+        String previousToken = req.getHeader("If-None-Match");
+        if (previousToken != null && previousToken.equals(entry.getEtag()))
+        {
+            res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+        }
+        else
+        {
+            res.setStatus(HttpServletResponse.SC_OK);
+            res.setContentLength(entry.getData().length);
+            res.setHeader("Content-Encoding", encoding);
+            ServletOutputStream sos = res.getOutputStream();
+            sos.write(entry.getData());
+            sos.flush();
+            sos.close();
+        }
+
+        res.setContentType(entry.getContentType());
+        res.setHeader("ETag", entry.getEtag());
+        res.setHeader("Vary", "Accept-Encoding");
+        setCacheControl(res, entry.getTTLSeconds());
+        res.setHeader("Connection", "keep-alive");
+
+        if (devMode)
+        {
+            cache.remove(localPath);
         }
     }
 
-    private void setCacheControl(HttpServletResponse res)
+    private void setCacheControl(HttpServletResponse res, int ttl)
     {
         Calendar cal = Calendar.getInstance();
         cal.set(Calendar.MILLISECOND, 0);
         res.setDateHeader("Date", cal.getTimeInMillis());
-        int expiry = 30 * 60;
-        cal.add(Calendar.SECOND, expiry);
-        res.setHeader("Cache-Control", "public, max-age=" + expiry);
+        cal.add(Calendar.SECOND, ttl);
+        res.setHeader("Cache-Control", "public, max-age=" + ttl);
         res.setDateHeader("Expires", cal.getTime().getTime());
     }
 
     private void send404(String path, HttpServletResponse res) throws IOException
     {
-        res.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot find resource");
+        res.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot find resource: " + path);
     }
 
     @Override
     public void destroy()
     {
-        resCache.clear();
+        cache.clear();
     }
 
-    private static class CacheEntry
+    private class CacheEntry
     {
         private String etag;
         private String contentType;
         private byte[] data;
+        private int ttl;
 
         public CacheEntry(String path)
         {
-            InputStream in = null;
+            boolean gzip = path.endsWith(".gz");
+            if (gzip)
+            {
+                path = path.substring(0, path.length() - 3);
+            }
+
+            InputStream in;
             try
             {
                 in = plugin.getResourceAsStream(path);
                 if (in == null)
                 {
-                    data = new byte[0];
-                    etag = "";
+                    clear();
                 }
                 else
                 {
-                    data = IOUtils.toByteArray(in);
+                    if (gzip)
+                    {
+                        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+                        GZIPOutputStream out = new GZIPOutputStream(bytes);
+                        IOUtils.copy(in, out);
+                        out.finish();
+                        out.close();
+                        data = bytes.toByteArray();
+                    }
+                    else
+                    {
+                        data = IOUtils.toByteArray(in);
+                    }
                     etag = DigestUtils.md5Hex(data);
                 }
-
             }
             catch (IOException e)
             {
-                log.error("Unable to retrieve content", e);
-                data = new byte[0];
-                etag = "";
+                log.error("Unable to retrieve content: " + path, e);
+                clear();
             }
-            if (path.endsWith(".js"))
-            {
-                contentType = "application/x-javascript; charset=utf-8";
-            } else if (path.endsWith(".css"))
+
+            contentType = config.getServletContext().getMimeType(path);
+            // covers anything not mapped in default servlet context config, such as web fonts
+            if (contentType == null)
             {
-                contentType = "text/css";
+                contentType = "application/octet-stream";
             }
+
+            ttl = path.startsWith("aui/") ? AUI_TTL_FAR_FUTURE : PLUGIN_TTL_NEAR_FUTURE;
         }
 
         public String getEtag()
         {
             return contentType;
         }
+
+        public int getTTLSeconds()
+        {
+            return ttl;
+        }
+
+        private void clear()
+        {
+            data = new byte[0];
+            etag = "";
+        }
     }
 }

File plugin/src/main/resources/aui/5_0/css/arrow.png

  • Ignore whitespace
Removed
Old image

File plugin/src/main/resources/aui/5_0/css/atlassian-icons.eot

  • Ignore whitespace
Binary file removed.

File plugin/src/main/resources/aui/5_0/css/atlassian-icons.svg

  • Ignore whitespace
Removed
Old image

File plugin/src/main/resources/aui/5_0/css/atlassian-icons.ttf

  • Ignore whitespace
Binary file removed.

File plugin/src/main/resources/aui/5_0/css/atlassian-icons.woff

  • Ignore whitespace
Binary file removed.

File plugin/src/main/resources/aui/5_0/css/aui-experimental.css

  • Ignore whitespace
-/*! AUI Flat Pack - version 5.0-m13 - generated 2012-10-02 21:18:14 -0400 */
-
-
-/**
- * PAGE LAYOUT
- */
-.aui-layout .aui-header,
-.aui-layout #footer {
-    clear: both;
-    float: left;
-    width: 100%;
-}
-
-.aui-layout #content {
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-    clear: both;
-    position: relative;
-}
-
-.aui-layout #content:before {
-    content: "";
-    clear: both;
-    display: table;
-}
-
-.aui-layout #footer .footer-body a {
-    color: #707070;
-}
-
-.aui-layout #footer .footer-body > ul,
-.aui-layout #footer .footer-body > p {
-    margin: 10px 0 0 0;
-}
-
-.aui-layout #footer .footer-body > ul:first-child,
-.aui-layout #footer .footer-body > p:first-child {
-    margin: 0;
-}
-
-.aui-layout #footer .footer-body > ul {
-    display: block;
-    font-size: 0;
-    list-style: none;
-    padding: 0;
-}
-
-.aui-layout #footer .footer-body > ul > li {
-    display: inline-block;
-    font-size: 12px;
-    line-height: 1.66666666666667;
-    padding: 0;
-    white-space: nowrap;
-}
-
-.aui-layout #footer .footer-body > ul > li + li {
-    margin-left: 10px;
-}
-
-.aui-layout #footer .footer-body > ul > li:after {
-    content: "\b7"; /* mid dot */
-    margin-left: 10px;
-    speak: none;
-}
-.aui-layout #footer .footer-body > ul > li:last-child:after {
-    display: none;
-}
-
-
-/**
- * GROUP/ITEM
- */
-
-.aui-group {
-    display: table;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-    border-spacing: 0;
-    table-layout: fixed;
-    width: 100%;
-}
-
-.aui-group > .aui-item {
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-    display: table-cell;
-    margin: 0;
-    vertical-align: top;
-}
-
-.aui-group > .aui-item + .aui-item {
-    padding-left: 20px;
-}
-
-/* defensive header allowance */
-.aui-layout .aui-group > header {
-    display: table-caption;
-}
-
-/* .aui-group-split: two items; alignment is left, then right (splits the layout). */
-.aui-group.aui-group-split > .aui-item {
-	text-align: right;
-}
-.aui-group.aui-group-split > .aui-item:first-child {
-	text-align: left;
-}
-
-/* .aui-group-trio: three items; alignment is left, center, right */
-.aui-group.aui-group-trio > .aui-item {
-	text-align: left;
-}
-.aui-group.aui-group-trio > .aui-item + .aui-item {
-	text-align: center;
-}
-.aui-group.aui-group-trio > .aui-item + .aui-item + .aui-item {
-	text-align: right;
-}
-
-/**
- * DEFAULT THEME SPACING
- */
-
-.aui-theme-default {
-    margin: 0;
-    padding: 0;
-}
-
-.aui-theme-default #content {
-    margin: 0;
-    padding: 0;
-}
-
-/**
- * PAGE DESIGN
- */
-.aui-theme-default {
-    background: #f5f5f5;
-    color: #333;
-}
-
-.aui-theme-default a {
-    color: #3b73af;
-    text-decoration: none;
-}
-.aui-theme-default a:focus,
-.aui-theme-default a:hover,
-.aui-theme-default a:active {
-    text-decoration: underline;
-}
-
-.aui-theme-default #footer {
-    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAuCAYAAABqK0pRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNDI3QTM5RTEwMzM2ODExODA4NjlCM0YyRDQ4RkI0MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCMUY1REQ0Qzg1MDAxMUUxQkFCQzhENkQwNkQ3NDI2NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCMUY1REQ0Qjg1MDAxMUUxQkFCQzhENkQwNkQ3NDI2NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RkE3RjExNzQwNzIwNjgxMThDMTRDRkQ1NTI3RURFN0QiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjQyN0EzOUUxMDMzNjgxMTgwODY5QjNGMkQ0OEZCNDIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7XZaOHAAADFElEQVR42tSZjW6jMAyACQ2FsnLbPUjf/z36IFN10BXaUI5MRrKshJgklM1SpGo0qT//h4nT6ZQnSSLH9TWuIfldshtXMa5OA/yBPx7GdfslQBqgBAgtmQZR4BEBD/VqAUr9MIAcLSxKA1zG9QYemaSApQDqPq5+4/DJ4TOV5nw+3ySEUaPjbFwV+bJ+fpyoAah7gacyUHxvUV7LAyC+dZFCCPzgE3lHkI0SVom+/wCoJ3xeKgLO3KHzM8eeHvK4pcpRucIXSwsQtlpmsRQXYIkMc8VIzmzCQPmMi02AMcXoAS4IBbqimM1nvBRLBshFduVc4t4OVg0wU2jJSMpPOedVTHCyJx5QOJx2aKWMcHmCwn2MKsix5gG5enBYc40esoffDcqRDPWRCmDuoPRaDXIqwQXRLzjZTSNCgrr+LRJAhUI0emgpx96jYXwRoFQKedCjMEnhzIacVaABMPHpTZxk7wxDGpYSKhmO69wRriYQX4N+S8rw2t3xnMaycliwNex3NdE2BkjHuJ9UhjHHlnNfJAyPDG9E8cjAsIgkCj0sRaAh+fTOmBJYxYTbEK8QAsLRbxSCvpLO3xKDHJmTbstRMGVWt4FpmQopPsClbSrRNcmLA+O8mlt+uSCThTmjxAeBqUmVKgw5ZQupxxogk4VciS8IDG2oHIh+pmBEGRqnZlYxYS7Ii5IJoQ31b+mbnKUeSRaMJdgzEj5zrNZ4jfGec1EDShVMGLHg3NZHoTTxl5ppORHZ01EvVpNcZhJ7abjWIQekgQoMJKE3gYgBEgoTBSIWiC9MNIiYID4wz6j348Bkt8FwCkCJ7v8/xiPCMCxyOjNnjH8piPbAX9QguTCCOba8BOQNeaNCMApgXJInEd4Xh+aIMNwrKnQ9VVCZXFbXzz+39EhpifGPmZuhSXaMuW01EDFzy6OxT+/qtqvyJiCufy9IUpZdFyUZMrOFgOwXvv3oGD0j38ojS1/juHLFu3r5Vi3XDypLTrTwGkjEBklX2nd3QEa/tfqChLzFd+WJWEMhm3SOMFCee5++l7T/AgwAB4DnTld9IesAAAAASUVORK5CYII=) center bottom no-repeat;
-    color: #707070;
-    font-size: 12px;
-    margin: 20px 0;
-    padding: 0 0 65px 0;
-    text-align: center;
-}
-
-.aui-theme-default #footer .footer-body {
-    background: transparent;
-    color: #707070;
-    font-size: 12px;
-    line-height: 1.66666666666667;
-    margin: 0;
-    padding: 0 10px;
-    text-align: center;
-}
-
-/**
- * CONTENT PANEL
- */
-.aui-theme-default #content > .aui-panel,
-.aui-theme-default .aui-page-panel {
-    background: #fff;
-    margin: 20px 0 0 0;
-    padding: 20px;
-    border-color: #ddd;
-    border-style: solid;
-    border-width: 1px 0;
-}
-
-.aui-theme-default #content > .aui-page-header {
-    padding: 20px;
-}
-.aui-theme-default #content > .aui-page-header + .aui-page-panel,
-.aui-theme-default #content > .aui-page-header + .aui-panel {
-    margin-top: 0;
-}
-
-.aui-theme-default #content > .aui-page-header:first-child {
-    margin-top: 0;
-}
-.aui-theme-default .aui-panel + .aui-panel,
-.aui-theme-default .aui-page-panel + .aui-page-panel {
-    margin-top: 20px;
-}
-
-/**
- * TABS AS FIRST CHILD IN CONTENT
- * Explicitly sets bg to white, changes horizontal hovers to work on grey.
- * Remember these extend the standard component styles.
- */
-
-.aui-theme-default #content > .aui-tabs {
-    margin: 20px;
-    background: transparent;
-}
-
-.aui-theme-default #content > .aui-tabs > .tabs-pane {
-    padding: 20px;
-}
-
-.aui-theme-default #content > .aui-tabs.horizontal-tabs > .tabs-pane {
-    border: 1px solid #ddd;
-    border-top: none;
-    border-radius: 0 0 3px 3px;
-    background: #fff;
-}
-
-.aui-theme-default #content > .aui-tabs.horizontal-tabs > .tabs-menu {
-    display: table; /* stops a gap appearing */
-}
-/**
- * TYPOGRAPHY - 14px base font size, agnostic font stack
- */
-body {
-    color: #333;
-    font-family: sans-serif;
-    font-size: 14px;
-    line-height: 1.42857142857143; /* 20px equiv line-height */
-}
-
-/* Default margins */
-p,
-ul,
-ol,
-dl,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-blockquote,
-pre,
-form.aui,
-table.aui,
-.aui-tabs,
-.aui-panel,
-.aui-group {
-    margin: 10px 0 0 0;
-}
-
-/* No top margin to interfere with box padding */
-p:first-child,
-ul:first-child,
-ol:first-child,
-dl:first-child,
-h1:first-child,
-h2:first-child,
-h3:first-child,
-h4:first-child,
-h5:first-child,
-h6:first-child,
-blockquote:first-child,
-pre:first-child,
-form.aui:first-child,
-table.aui:first-child,
-.aui-tabs:first-child,
-.aui-panel:first-child,
-.aui-group:first-child {
-    margin-top: 0;
-}
-
-/* Headings */
-h1 {
-    font-size: 24px;
-    font-weight: normal;
-    line-height: 1.25; /* 30px equiv line-height */
-    margin: 40px 0 0 0;
-}
-h2 {
-    font-size: 20px;
-    font-weight: normal;
-    line-height: 1.5; /* 30px equiv line-height */
-    margin: 40px 0 0 0;
-}
-h3 {
-    font-size: 16px;
-    line-height: 1.5625; /* 25px equiv line-height */
-    margin: 30px 0 0 0;
-}
-h4 {
-    font-size: 14px;
-    line-height: 1.5; /* 20px equiv line-height */
-    margin: 20px 0 0 0;
-}
-h5 {
-    color: #707070;
-    font-size: 12px;
-    line-height: 1.66666666666667;
-    margin: 20px 0 0 0;
-    text-transform: uppercase;
-}
-h6 {
-    color: #707070;
-    font-size: 12px;
-    line-height: 1.66666666666667;
-    margin: 20px 0 0 0;
-}
-h1:first-child,
-h2:first-child,
-h3:first-child,
-h4:first-child,
-h5:first-child,
-h6:first-child {
-    margin-top: 0;
-}
-/* Nice styles for using subheadings */
-h1 + h2,
-h2 + h3,
-h3 + h4,
-h4 + h5,
-h5 + h6 {
-    margin-top: 10px;
-}
-/* Increase the margins on all headings when used in the group/item pattern ... */
-.aui-group > .aui-item > h1:first-child,
-.aui-group > .aui-item > h2:first-child,
-.aui-group > .aui-item > h3:first-child,
-.aui-group > .aui-item > h4:first-child,
-.aui-group > .aui-item > h5:first-child,
-.aui-group > .aui-item > h6:first-child {
-    margin-top: 20px;
-}
-/* ... unless they're the first-child */
-.aui-group:first-child > .aui-item > h1:first-child,
-.aui-group:first-child > .aui-item > h2:first-child,
-.aui-group:first-child > .aui-item > h3:first-child,
-.aui-group:first-child > .aui-item > h4:first-child,
-.aui-group:first-child > .aui-item > h5:first-child,
-.aui-group:first-child > .aui-item > h6:first-child {
-    margin-top: 0;
-}
-
-/* Other typographical elements */
-small {
-    color: #707070;
-    font-size: 12px;
-    line-height: 1.33333333333333; /* 16px equiv line-height */
-}
-code,
-kbd {
-    font-family: monospace;
-}
-var,
-address,
-dfn,
-cite {
-    font-style: italic;
-}
-cite:before {
-    content: "\2014 \2009";
-}
-blockquote {
-    border-left: 1px solid #ccc;
-    color: #707070;
-    padding: 10px 30px;
-}
-blockquote > cite {
-    display: block;
-    margin-top: 10px;
-}
-q {
-    color: #707070;
-}
-q:before {
-    content: open-quote;
-}
-q:after {
-    content: close-quote;
-}
-abbr {
-    border-bottom: 1px #707070 dotted;
-    cursor: help;
-}
-/**
- * AUI Module
- */
-.aui-module {
-    margin-top: 20px;
-}
-.aui-module:first-child {
-    margin-top: 0;
-}
-.aui-module-header,
-.aui-module-content,
-.aui-module-footer {
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-.aui-header {
-    background: #205081;
-    border-bottom: 1px solid #2e3d54;
-    color: #fff;
-    padding: 0 10px;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-
-.aui-header:after,
-.aui-header .aui-header-logo a:after {
-    content: "";
-    display: table;
-    clear: both;
-}
-
-.aui-header .aui-header-logo,
-.aui-header .aui-nav {
-    margin: 0;
-    padding: 0;
-    float: left;
-}
-
-.aui-header .aui-header-secondary .aui-nav {
-    float: right;
-}
-
-.aui-header .aui-nav > li {
-    float: left;
-    padding: 0;
-}
-
-.aui-header a {
-    color: #fff;
-    display: block;
-    line-height: 1;
-    padding: 13px 10px;
-    text-decoration: none;
-}
-
-.aui-header a:focus,
-.aui-header a:hover,
-.aui-header a:active {
-    text-decoration: none;
-}
-
-.aui-header .aui-header-logo a {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    float: left;
-    height: 40px;
-    margin-right: 13px; /* 8px (arrow width) + 5px (margin between arrow and logo) */
-    padding: 0 10px;
-}
-
-.aui-header .aui-header-logo.aui-header-logo-textonly a {
-    padding: 5px 10px;
-}
-.aui-header .aui-header-logo-textonly .aui-header-logo-device {
-    float: left;
-}
-.aui-header .aui-header-logo-textonly .aui-header-logo-device + .aui-header-logo-text {
-    padding: 5px 0 5px 10px;
-}
-
-/* for extra visible text, eg. instance names. */
-.aui-header .aui-header-logo .aui-header-logo-text {
-    display: block;
-    float: left;
-    font-size: 14px;
-    line-height: 1.4286;
-    margin: 0;
-    padding: 10px 0 10px 10px;
-}
-
-.aui-header .aui-header-logo-aui .aui-header-logo-device,
-.aui-header .aui-header-logo-bamboo .aui-header-logo-device,
-.aui-header .aui-header-logo-bitbucket .aui-header-logo-device,
-.aui-header .aui-header-logo-confluence .aui-header-logo-device,
-.aui-header .aui-header-logo-fecru  .aui-header-logo-device,
-.aui-header .aui-header-logo-hipchat .aui-header-logo-device,
-.aui-header .aui-header-logo-jira .aui-header-logo-device,
-.aui-header .aui-header-logo-stash .aui-header-logo-device {
-    background-repeat: no-repeat;
-    background-position: 0 50%;
-    display: block;
-    float: left;
-    height: 24px;
-    padding: 8px 0;
-    text-indent: -9999px;
-    text-align: left;
-}
-
-.aui-header .aui-header-logo-aui .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAARCAYAAAC4qX7BAAAA50lEQVR42s2RYQ2DMBCFJwEJlTAHWJgEJNTBcDAkTEIdrA6KA3DAHNzC0h9vd30ZWSjZl/QHd+X4uHf6a0TEi8WRuxHu+EK/h34k9cBERrH4DSL9jyKxNNhJmfFoES8cd5gIxpKlEj7XFeGxOLWhsZ4IjyUxuSNEAg6F+oTxVBGBRiOfnKE3EPsqIh00Jqj1eVNIU0fExjLk2lPKdHuLsFguazTCCeoHTJ2I3rmIjWUBuRbOVccD7yLzutF8ZrJJKhK0NQJibGiS7zxgHBVZ1Ac0TBglbzAHWfI2GyOCke6NirPdcP99XrEI2FFFhYV6AAAAAElFTkSuQmCC);
-    width: 34px;
-}
-.aui-header .aui-header-logo-bamboo .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF8AAAAYCAMAAACrz9ETAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAECAwQFBgcICPn6+/z9/vIxqCigAAAcZJREFUeNrtlNt25SAIhj1gQhI8vP/Tlh+s6V7da09nOr3rf2FYqF8QkfCrL+hkij/KH2O0LZQc/k5EFL6g0ocqpdGLO/ow9TO+xuuaP9PjZTAOu46XEce75D/wk0XbIvidafKrCPzf5+dKtVMtWC8kIzufNG3+yaSKCIRSCLBhUnY+HMnjhDWZt6lRt5JCWonfF5/s0/0qthBEL6TDPhpcGxa4WLMsZklUupvz8qom/WMh1cXHfUTwTUn5D2rgr8k6rRqi75jYjEgWn0Radn6ZS/hgPtTcnd9sd8cJjN8OmwRGWLBxQ+xc7a+qwntc/LhzmfEzOIe7gWYMkgIrkwztg0+iNtRs+iu2HEQwEH8TKYuPoN/jl+ZHK8zcjI8BfAmf+O69TWfM/J/P8+9Tnth/5/vJoBQStVU/vlpofJOfh+QLVqnUK9X8EP/l2ZRXfCRv0ygj6vsx/xBt8IbYrDDy/X7hsIvjsz/nr0qhjFIyM2/YbU8k3v2Hse9z/+kpvc6Pq+GELsGxXUeY0gMk60J3/3RdWXnwC054YcB9XfZs+uxcVrUhnuvResPswE9lDqVh9mn/e9Hko/cml5of3E/W8hl+9ag3syks4FZfIT8AAAAASUVORK5CYII=);
-    width: 95px;
-}
-.aui-header .aui-header-logo-bitbucket .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGUAAAAYCAMAAADUHsPiAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAECAwQFBgcICPn6+/z9/vIxqCigAAAd5JREFUeNrtlevO2yAMhjkYcDgE3//VDmMn5Ctap1Xqfu1V1eLktR8MoTH/UhZY7puIWDqpavoSyZ9E7QgwhGWA8Ctr1anDHblGFI0BDNccHssYEH4/V4zvKEB0PMIwmyGqGq+RgXd9qu/NfSr3zLETxY2Sav2A4mv1d3ASq43R0YkFG6USfUDBWUpVaWqNJMcCeNmXJ8Xtj7sHAPukDI/fKEhTiChdnUwRNWs6FTVUIFE2nihNVzFekoQSeXZ1FvGXWxctSnRg0mqLQpmzF0UVde3Y22lR3AhAF6QHNVuhaDJobc3vRxi+k7M9N4kIMOIDElFRipBbgJAmpREdbpgCZKKEg5cxGZWuTq06U8mXfdp3X2vrKInfaM9tmtS69mU9ZAmupv6GIpWk+/HxbDprbTul/qSYzyisfK//TkFinbXJ76eUc/XCCi+UKGydRl2U9p5imfKyL1ZN23nR6g2L0CSnASQmXBQHbvgyQOZvohJzZ950QmAf27KXCxEtUyIsDj0Vn1dQKOXneQFPqmpfz0tsl8kkPS+qTEvNPiiHrh3MO1q7R81oJxUTBOOmL/DWNHF5Y+fIP16WFwMVDSx7v1/cjOx91cxfB1atTn0evPyxSe3tJe/ZbP7rz/oF1YozzIKriK8AAAAASUVORK5CYII=);
-    width: 101px;
-}
-.aui-header .aui-header-logo-confluence .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHYAAAAYCAMAAAAYh/kJAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAECAwQFBgcICPn6+/z9/vIxqCigAAAf5JREFUeNrtlX9vAyEIhv2BiJ6nfv9PO6Ayb81uWbr92Tdp5SjwRCBX99Zb/66zJm92zC2pkUpr7aB4l1QmK3JQehU75uzOF6LkiGsBu3ybD7WbnDwVy2H0KrZygewZjvI15OY0l8pNDvN6ae4v2DAZFpFvdhqoswcBkOAeS3a8qqLc6KucOua5y3mSm1UeMrUjFn44vKfO/aes2MA7IA3SMaMGe8dOxNpnx1VhtODi0ec4kmH9qVyhTlCPGPs3FTBjqdoMFAts2MXrVA0P0xQsD8y3ucOC0G3s3p0un41td9gocRKWNxaj8U9J1f01xWHUZ2zj0AfcS6sKFDGBS1SIz1jiml5mdoAgEDo782VySRfJPXGz+w5L3A5xPEylLPd3WCK22ldns6KVyLAXbrvDut9iVXdYk9vDrcrNG5v+gj1+g03UV/qoGFZEMaxYshbxR2zoRlB9xZ67hSZfxhQN2/4WnTsfc6gN2TkwybO7wUbJkYBJKFsJkEu4YJPcATI57QKkwn0UQi9FcnTJuiwEOJxLFT6tO+xuHoWxrHTB+uWEen3Pz8PZP8CpLpSCZENyOB6x/hZbtFlq4kJkw16cYG+eLtjKN/cAwGYAiIHG6aX3SEQZPFuJjag/6yMHLdsODuV0sGBKQZ40ZTsFEPnEuKZfx+FMaZ7o3Vv/qw8yDjdafGp5YgAAAABJRU5ErkJggg==);
-    width: 118px;
-}
-.aui-header .aui-header-logo-fecru .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAAYCAMAAACcAR4nAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAECAwQFBgcICPn6+/z9/vIxqCigAAARRJREFUeNrlk8GWwyAIRcFGxYjh/f/XjiemYpt2MatZzF2pcBGjoX8HZwWgmb9nbFEkbrcEMQxMPoup4aIlWtnh7OSsCValU21mTPMoUto3d4Olub9h80AXxywoAI+IzIzK88x1URWN1wbUS/rIZH4VVwMOpkkFEN7VMA+yY8QHEYkcBhDP8jgZmx3oZKKMzuEXc3QdaJmfLcuLOicWo82lk9iHdYSokwHxhr1jJ9AKBmkI8aaSuqn0Ampo170kLytLa65GWnmgkl7HKGh0IxRXy8eGra+al/XShgV70MIx1wSVbqhFasNrFE1v718DUfKXtYZtI94NsJ1ps50WQtVyttGKmw4XKF9DRU/5Dex/DtPf8gNRKhRt+v7XRAAAAABJRU5ErkJggg==);
-    width: 58px;
-}
-.aui-header .aui-header-logo-hipchat .aui-header-logo-device {
-    background-image: url(data:image/png;base64,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);
-    width: 91px;
-}
-.aui-header .aui-header-logo-jira .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADkAAAAYCAMAAAB3NqUkAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAECAwQFBgcICPn6+/z9/vIxqCigAAAT1JREFUOMvVk9F2wyAMQ2UwhBQS7v9/7R5CWtZ0Pafb0/wUnAjJsiL9uVa2X6A2khoo9d0+AkZoimtSAf9QaYvHFfX2EWdJ08FLkMzdpehuMh916nP3cH4MFFn2ZFo6RMmB0XdG9SJJBjys3IAYgWwdul4jIUvKAJykGdjsRlMDyhVZSqlAk1QBWGbSLRS7Abtdkce6aYfYPsmNfbACUa+RBdohL05yFTnuOmZ5g6xQ1Se5Y/AT+Ix097RDkwFZN+ag+sT4k7dFGTClWe6YdX2HbKYKLZR1djeQE9xTe9nKDkQZsKx8C0ODvDwaF4cisFsGQmMOQwCq2rkUrRdvC1ArbIru6SF3AbLimaAG1KetDKr1jM5QVwCTOlRJaSSiz0jr3IOiZTQlc4+SwviBOpsfR5OOV1JwPx8fzf9TX5ZXHLWEENZUAAAAAElFTkSuQmCC);
-    width: 57px;
-}
-.aui-header .aui-header-logo-stash .aui-header-logo-device {
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAAYCAMAAABwdHsxAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAECAwQFBgcICPn6+/z9/vIxqCigAAAahJREFUeNrdk9uuJCEIRRHRwvLG/3/tgGilcjqdzGTeDg/d2wsL3VLwmwMRAv4vJHdhYBkczkwgi3+ChC4iF1z6O6LPFPFggG+nJJH5k8JogqfMxSnyYLJI+4aR97hpbrhaaxywy7B7Ta10l9bkAv47jJUjTZcmisO57iEiyVfjLdKJVCTmvI6KqhI5RvXe2OWGNEsANScrdDpm2yseVP2/aVlXbJhh6t4bYyRlNA5AKRjAknsKb8y9xRWOIDmBfkWosmoUGB2aZLPLohdUutZkDsRMWYs3y846woUZfR+cxFKB7cQmmpkTh9eZ6W3x0nR8Iwc8GIFbMK3XnUbKoJFKdy8OJjFzU+2Xqtktdsz2JkvVbVlT2Iann8zWgymqD9e9+YHhy7yoCFisaVKFE/zCbIppvKqJN8bLxtDEXBkR0bvl9PLBWE5w7SufmDhnhsSQs0q3z6q17ndYGu0h72kT0iuPD4x/UzXtjp3eBCfS1jR87Nmf3hwvz66wJvyZpKd9tRmtBaYep2ITmygQRYZ/fhF2UD3P+AQSPquLHZ9x0KVvEYngt8QfKdMiULx1yyEAAAAASUVORK5CYII=);
-    width: 70px;
-}
-
-/* Custom IMG elements can be set in most products */
-.aui-header .aui-header-logo img {
-    border: 0;
-    float: left;
-    max-height: 30px;
-    padding: 5px 0;
-}
-
-.aui-header .aui-header-logo a.aui-dropdown2-trigger {
-    margin-right: 0;
-}
-
-/* Positioning icons in the header */
-.aui-header .aui-icon {
-    margin: -1px 0;
-    vertical-align: top;
-}
-
-/* In case showIcon is not set to false for header dropdown triggers */
-.aui-header .aui-dropdown2-trigger .aui-icon-dropdown {
-    display: none;
-}
-/* Styling the dropdown2 triggers differently in the header to avoid inline-block spacing issues with other icons */
-.aui-header .aui-dropdown2-trigger {
-    padding-right: 23px !important; /* 8px (arrow width) + 10px (right padding) + 5px (margin between arrow and logo) */
-    position: relative;
-}
-.aui-header .aui-dropdown2-trigger:after {
-    border: 4px solid transparent;
-    border-top-color: #fff;
-    content: "";
-    height: 0;
-    margin-left: -18px;
-    margin-top: -2px;
-    opacity: 0.8;
-    left: 100%; /* "left" + "margin-left" required because of webkit not working properly with "right" */
-    position: absolute;
-    text-indent: -99999px;
-    top: 50%;
-    width: 0;
-}
-.aui-header .aui-dropdown2-trigger:hover:after,
-.aui-header .aui-dropdown2-trigger.active:after {
-    opacity: 1;
-}
-.aui-header .aui-button.aui-dropdown2-trigger:after {
-    margin-top: 0;
-    top: 12px;
-}
-
-.aui-header a > .aui-avatar {
-    display: inline-block;
-    vertical-align: top;
-}
-
-.aui-header a > .aui-avatar-tiny {
-    margin: -1px 0; /* (16px Tiny Avatar height - 14px font size (line-height 1 in the header)) / 2 */
-}
-
-.aui-header a > .aui-avatar-small {
-    margin: -5px 0; /* (24px Small Avatar height - 14px font size (line-height 1 in the header)) / 2 */
-}
-
-/**
- * Buttons in header
- */
-.aui-header .aui-button.aui-button-primary.aui-style {
-    background: #366EA7;
-    background-image: -o-linear-gradient(top, #3b7fc4, #2c66a2);
-    background-image: -moz-linear-gradient(top, #3b7fc4, #2c66a2);
-    background-image: -webkit-linear-gradient(top, #3b7fc4, #2c66a2);
-    background-image: -ms-linear-gradient(top, #3b7fc4, #2c66a2);
-    background-image: linear-gradient(top, #3b7fc4, #2c66a2);
-    border: 0;
-    -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0 0 inset, rgba(0, 0, 0, 0.2) 0 1px 1px 0;
-    box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0 0 inset, rgba(0, 0, 0, 0.2) 0 1px 1px 0;
-    margin: 6px 10px 0 10px;
-}
-
-.aui-header .aui-button.aui-button-primary.aui-style:focus,
-.aui-header .aui-button.aui-button-primary.aui-style:hover,
-.aui-header .aui-button.aui-button-primary.aui-style:active {
-    background: #2D5F9C;
-    background-image: -o-linear-gradient(top, #6299D0, #2C5E9B);
-    background-image: -moz-linear-gradient(top, #6299D0, #2C5E9B);
-    background-image: -webkit-linear-gradient(top, #6299D0, #2C5E9B);
-    background-image: -ms-linear-gradient(top, #6299D0, #2C5E9B);
-    background-image: linear-gradient(top, #6299D0, #2C5E9B);
-}
-
-/**
- * Dropdown2 triggers in header
- */
-.aui-header .aui-dropdown2-trigger.active,
-.aui-header a:focus,
-.aui-header a:hover,
-.aui-header a:active {
-    background-color: #3b73af;
-}
-
-/* Icons in Dropdown2 triggers and links in header */
-.aui-header .aui-dropdown2-trigger.active .aui-icon,
-.aui-header a:focus .aui-icon,
-.aui-header a:hover .aui-icon,
-.aui-header a:active .aui-icon {
-    opacity: 1;
-    -ms-filter: alpha(opacity=100);
-}
-
-/**
- * Quick search for header
- */
-.aui-header .aui-quicksearch {
-    padding: 0 10px;
-}
-.aui-header .aui-quicksearch input {
-    -moz-appearance: textfield;
-    -webkit-appearance: textfield;
-    background: #f5f5f5 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAb0lEQVR42p2RMQ7AIAhFWXsG1h7Gtffo6urqyJGpwyetomnwJ38BH/wgfZSaS7PABbVOqkqm3Fzx6IQTatlDb5PJi21YBy1iuNgjJIgzFXqyCcXj1Z1DXNGTiw01aP252DCCgH7Fk41h8KaAGMDxADnaOPucd/m3AAAAAElFTkSuQmCC) no-repeat 7px 6px;
-    border: none;
-    border-radius: 5em;
-    box-shadow: inset 1px 2px 3px rgba(0, 0, 0, 0.3);
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-    color: #333;
-    font-family: inherit;
-    font-size: inherit;
-    height: 1.71428571428571em; /* 24px effective - need height in ems so that user-specified font-sizes apply */
-    line-height: 1.71428571428571;
-    margin: 8px 0;
-    padding: 2px 10px 2px 25px;
-    vertical-align: baseline;
-    width: 220px;
-}
-.aui-header .aui-quicksearch input:focus {
-    background-color: #fff;
-    outline: none;
-}
-/* Placeholder styling
- * - You have to use two rules, because user agents are required to ignore a rule with an unknown selector.
- *   Since WebKit doesn’t know the proprietary Mozilla selector and vice versa, you have to include them separately.
- *   See http://stackoverflow.com/questions/2610497/change-an-inputs-html5-placeholder-color-with-css#answer-2610741
- */
-.aui-header .aui-quicksearch input::-webkit-input-placeholder {
-    color: #707070;
-}
-.aui-header .aui-quicksearch input:-moz-placeholder {
-    color: #707070;
-}
-
-/**
- * AUI Page Header
- */
-
-.aui-page-header-inner {
-    border-spacing: 0;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-    display: table;
-    table-layout: auto;
-    width: 100%;
-}
-
-.aui-page-header-image,
-.aui-page-header-main,
-.aui-page-header-actions {
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-    display: table-cell;
-    margin: 0;
-    padding: 0;
-    text-align: left;
-    vertical-align: top;
-}
-/* collapse the cell to fit its content */
-.aui-page-header-image {
-    white-space: nowrap;
-    width: 1px;
-}
-.aui-page-header-main {
-    vertical-align: middle;
-}
-.aui-page-header-image + .aui-page-header-main {
-    padding-left: 10px;
-}
-.aui-page-header-actions {
-    padding-left: 20px;
-    text-align: right;
-    vertical-align: middle;
-}
-.aui-page-header-main > h1,
-.aui-page-header-main > h2,
-.aui-page-header-main > h3,
-.aui-page-header-main > h4,
-.aui-page-header-main > h5,
-.aui-page-header-main > h6 {
-    margin: 0;
-}
-.aui-page-header-actions > .aui-buttons {
-    margin: 5px 0; /* spaces out button groups when they wrap to 2 lines */
-    vertical-align: top;
-    white-space: nowrap;
-}
-
-/*! AUI Navigation */
-
-/* Nav defaults - put very little here!
--------------------- */
-.aui-nav,
-.aui-nav > li {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-}
-
-/* Horizontal, breadcrumbs and pagination are all horizontal */
-.aui-nav-breadcrumbs:after,
-.aui-nav-pagination:after,
-.aui-nav-horizontal:after {
-    clear: both;
-    content: " ";
-    display: table;
-}
-.aui-nav-breadcrumbs > li,
-.aui-nav-pagination > li,
-.aui-nav-horizontal > li {
-    float: left;
-}
-
-.aui-nav-horizontal .aui-icon-dropdown,
-.aui-nav-vertical .aui-icon-dropdown {
-    border-left: 4px solid transparent;
-    border-right: 4px solid transparent;
-    border-top: 4px solid #8e8e8e;
-    content: "";
-    display: inline-block;
-    height: 0;
-    width: 0;
-    text-indent: -99999px;
-    position: relative;
-}
-
-.aui-nav-horizontal a.active .aui-icon-dropdown,
-.aui-nav-horizontal a:active .aui-icon-dropdown,
-.aui-nav-vertical a.active .aui-icon-dropdown,
-.aui-nav-vertical a:active .aui-icon-dropdown {
-    border-top-color: #fff;
-}
-
-
-
-/* Breadcrumb navigation
--------------------- */
-.aui-nav-breadcrumbs > li {
-    padding: 0 10px 0 0;
-}
-
-.aui-nav-breadcrumbs > li + li:before {
-    content: "/";
-    padding-right: 10px
-}
-
-/* last of type for where it works */
-.aui-nav-breadcrumbs > li.aui-nav-selected a,
-.aui-nav-breadcrumbs > li:last-child a {
-    color: #333;
-}
-
-