diff --git a/.bowerrc b/.bowerrc index 0c2550968f..1a5e5dd689 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "src/main/webapp/static/lib/" + "directory": "src/main/webapp/static/bower_components/" } diff --git a/.gitignore b/.gitignore index 089344eb8a..a9f63d4860 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,21 @@ target/ # Archetype filter-dev.properties -src/main/webapp/lib/ src/test/js/lib/ .vagrant/ + +src/main/webapp/static/bower_components/ +node_modules/ +.grunt/ +spec-runner.html + +# Vagrant +cookbooks/ +tmp + +# Git +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*.orig diff --git a/.travis.yml b/.travis.yml index 7ac0b70e77..e66c09fcfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,3 @@ branches: - develop install: true script: mvn test -P production - diff --git a/Cheffile b/Cheffile new file mode 100644 index 0000000000..916eae297e --- /dev/null +++ b/Cheffile @@ -0,0 +1,7 @@ +site 'https://supermarket.getchef.com/api/v1' + +cookbook 'apt' + +cookbook 'redis', + :git => 'git@github.com:phlipper/chef-redis.git', + :ref => 'v0.5.6' diff --git a/Cheffile.lock b/Cheffile.lock new file mode 100644 index 0000000000..28e15fa93f --- /dev/null +++ b/Cheffile.lock @@ -0,0 +1,17 @@ +SITE + remote: https://supermarket.getchef.com/api/v1 + specs: + apt (2.7.0) + +GIT + remote: git@github.com:phlipper/chef-redis.git + ref: v0.5.6 + sha: 7476279fc9c8727f082b8d77b5e1922dc2ef437b + specs: + redis (0.5.6) + apt (>= 0.0.0) + +DEPENDENCIES + apt (>= 0) + redis (>= 0) + diff --git a/Gruntfile.coffee b/Gruntfile.coffee new file mode 100644 index 0000000000..12297ffebc --- /dev/null +++ b/Gruntfile.coffee @@ -0,0 +1,57 @@ +module.exports = (grunt) -> + jasmineRequireTemplate = require 'grunt-template-jasmine-requirejs' + + jasmineSpecRunner = 'spec-runner.html' + + sourcePath = 'src/main/webapp/static/js/find/**/*.js' + + documentation = 'doc' + + testRequireConfig = [ + 'src/main/webapp/static/js/require-config.js' + 'src/test/js/test-require-config.js' + ] + + specs = 'src/test/js/spec/**/*.js' + serverPort = 8000 + host = "http://localhost:#{serverPort}/" + + grunt.initConfig + pkg: grunt.file.readJSON 'package.json' + clean: [ + jasmineSpecRunner + 'bin' + '.grunt' + documentation + ] + connect: + server: + options: + port: serverPort + jasmine: + test: + src: sourcePath + options: + host: host + keepRunner: true + outfile: jasmineSpecRunner + specs: specs + template: jasmineRequireTemplate + templateOptions: + requireConfigFile: testRequireConfig + watch: + test: + files: [ + 'src/**/*.js' + 'test/**/*.js' + ] + tasks: ['test'] + + grunt.loadNpmTasks 'grunt-contrib-connect' + grunt.loadNpmTasks 'grunt-contrib-jasmine' + grunt.loadNpmTasks 'grunt-contrib-watch' + + grunt.registerTask 'default', ['test'] + grunt.registerTask 'test', ['connect:server', 'jasmine:test'] + grunt.registerTask 'browser-test', ['connect:server:keepalive'] + grunt.registerTask 'watch-test', ['watch:test'] diff --git a/README.md b/README.md index 895fc88d14..fe88bd669f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # HP Find [![Build Status](https://travis-ci.org/hpautonomy/find.svg?branch=master)](https://travis-ci.org/hpautonomy/find) -HP Find is a web application backed by [IDOL OnDemand](https://www.idolondemand.com) +HP Find is a web application backed by [Haven OnDemand](https://www.idolondemand.com) A live preview of HP Find can be found at [find.idolondemand.com](http://find.idolondemand.com). ## Key Features -* Querying IDOL OnDemand indexes -* Viewing IDOL OnDemand results -* Suggested related searches from IDOL OnDemand +* Querying Haven OnDemand indexes +* Viewing Haven OnDemand results +* Suggested related searches from Haven OnDemand ## Building HP Find Building HP Find requires the following to be installed @@ -16,12 +16,16 @@ Building HP Find requires the following to be installed * [Apache Maven 3](http://maven.apache.org) * [NodeJS](http://nodejs.org) -The jetty:run goal will stand up a local web server for development. The package goal will build a war file. +[NPM](https://www.npmjs.com/) and [Bower](http://bower.io/) are used to manage dependencies. These are automatically +run in the maven process-sources phase. -Running with the production profile will minify the Javascript and CSS, and bless the CSS for older versions of Internet Explorer. +The maven jetty:run goal will stand up a local web server for development. The package goal will build a war file. + +Running with the production profile will minify the Javascript and CSS, and bless the CSS for older versions of Internet +Explorer. When developing the develop profile should be used. You will need to create a copy of src/main/filters/filter-dev.properties.example in the same directory. -This should be named filter-dev.properties. This file should be ignored by git. +This should be named filter-dev.properties. ## HP Find setup You'll need to install [Tomcat](http://tomcat.apache.org) to run the HP Find war file. @@ -33,18 +37,60 @@ If using the jetty:run goal, the properties can be set on the command line The properties you'll need to set are: * -Dhp.find.home . This is the directory where the webapp will store log files and the config.json file. -* -Dfind.https.proxyHost . Optional property. The host for the https proxy. Set this if you need a proxy server to talk to IDOL OnDemand. -* -Dfind.https.proxyPort . Optional property. The port for the https proxy. Set this if you need a proxy server to talk to IDOL OnDemand. Defaults to 80 if find.https.proxyHost is defined. +* -Dhp.find.persistentState . Optional property. The persistence mode for the application, which determines where +sessions, token proxies and caches are stored. Possible options are REDIS or INMEMORY. Defaults to REDIS. +* -Dfind.https.proxyHost . Optional property. The host for the https proxy. Set this if you need a proxy server to talk +to Haven OnDemand. +* -Dfind.https.proxyPort . Optional property. The port for the https proxy. Set this if you need a proxy server to talk +to Haven OnDemand. Defaults to 80 if find.https.proxyHost is defined. -## Configuring HP Find -Once you've started HP Find, you'll need to configure HP Find. When run for the first time, a login screen will appear. The credentials for this are in the config file. +## Vagrant +HP Find includes a Vagrant file, which will provision an Ubuntu 12.04 VM running a Redis server, which will by default +be used to store sessions. -This will take you to the Settings Page. You'll need an IOD API key. Once provided, you can configure which IOD indexes you wish to allow searching against. +The Vagrantfile requires several plugins, which will be installed if they are not installed already. -You can also configure a user to allow these settings to be changed later. The current password is the password you used to login. -The new password will be stored in the config file as a BCrypt hash. +The VM has the IP address 192.168.242.242, and can be accessed via DNS with the name hp-find-backend. -Once you've configured HP Find, save the config file with the save changes button, and logout. You will be redirected to the Search page. You can access the settings page again by pointing your browser at /find/login (there is no link to this in the UI). +The Redis runs on port 6379. + +## Configuring HP Find +Earlier versions of Find had a settings page, but this is currently unavailable. To configure Find, create a config.json +file in your Find home directory. + +Below is an example config file: + + { + "login": { + "method": "singleUser", + "singleUser": { + "username": "admin", + "hashedPassword": "", + "passwordRedacted" : false + }, + "name": "SingleUserAuthentication" + }, + "iod": { + "apiKey": "YOUR API KEY", + "application": "YOUR APPLICATION", + "domain": "YOUR DOMAIN", + "activeIndexes": [{ + "domain": "PUBLIC_INDEXES", + "name": "wiki_eng" + }] + }, + "allowedOrigins": [ + "http://mydomain.example.com:8080" + ], + "redis": { + "address": { + "host": "hp-find-backend", + "port": 6379 + }, + "database": 0, + "sentinels": [] + } + } ## Is it any good? Yes. @@ -52,4 +98,4 @@ Yes. ## License Copyright 2014-2015 Hewlett-Packard Development Company, L.P. -Licensed under the MIT License (the "License"); you may not use this project except in compliance with the License. \ No newline at end of file +Licensed under the MIT License (the "License"); you may not use this project except in compliance with the License. diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000000..6582324804 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,52 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +vm_name = 'hp-find-backend' + +required_plugins = %w(vagrant-hostsupdater vagrant-librarian-chef vagrant-proxyconf) + +required_plugins.each do |plugin| + system "vagrant plugin install #{plugin}" unless Vagrant.has_plugin? plugin +end + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "hashicorp/precise64" + + config.vm.network "private_network", ip: '192.168.242.242' + + config.vm.hostname = vm_name + + config.vm.provider :virtualbox do |vb| + # Don't boot with headless mode + # Uncomment this if you get stuck at the bootloader + # vb.gui = true + + vb.name = vm_name + end + + config.vm.provider 'vmware_workstation' do |hv| + hv.name = vm_name + end + + config.proxy.http = ENV["http_proxy"] + config.proxy.https = ENV["https_proxy"] + config.proxy.no_proxy = ENV["no_proxy"] + + # Enable provisioning with chef solo, specifying a cookbooks path, roles + # path, and data_bags path (all relative to this Vagrantfile), and adding + # some recipes and/or roles. + # + config.vm.provision :chef_solo do |chef| + chef.cookbooks_path = "cookbooks" + chef.roles_path = "vagrant/roles" + chef.add_role "redis-server" + end +end diff --git a/bower.json b/bower.json index 9757f0d7d3..9a9479c92a 100644 --- a/bower.json +++ b/bower.json @@ -3,7 +3,8 @@ "private": true, "authors": [ "Alex Scown ", - "Alyona Ivanova " + "Alyona Ivanova ", + "Daniel Grayling =2" } } diff --git a/install/find.conf b/install/find.conf new file mode 100644 index 0000000000..5e95cb0fb2 --- /dev/null +++ b/install/find.conf @@ -0,0 +1,30 @@ +#!upstart +description "HP Find" +author "Copyright 2015 Hewlett-Packard Development Company, L.P. Licensed under the MIT License (the 'License'); you may not use this file except in compliance with the License." + +start on startup +stop on shutdown + +env HOME=/opt/find #ISO installation directory +env USER=find # Linux user to run ISO as +env PORT=8080 # Port to run ISO on + +env SYSLOG=/var/log/find.sys.log # Where to write startup logging to +env PID=/var/run/find.pid # ISO process id file + +env JAVA_BIN=/usr/bin/java # Path to Java 7 binary + +script + cd $HOME + echo $$ > $PID + exec sudo -u $USER $JAVA_BIN -jar $HOME/find.jar -Dhp.find.home="$HOME/home/" -Djava.security.egd=file:/dev/./urandom -httpPort $PORT -uriEncoding utf-8 >> $SYSLOG +end script + +pre-start script + echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> $ISO_SYSLOG +end script + +pre-stop script + rm $ISO_PID + echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> $ISO_SYSLOG +end script diff --git a/install/install-find.sh b/install/install-find.sh new file mode 100644 index 0000000000..3eb4322597 --- /dev/null +++ b/install/install-find.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + +## INSTALLATION SETTINGS ## +NAME="find" +BASE_DIR="/opt/$NAME" +HOME_DIR="$BASE_DIR/home" +USER="$NAME" +GROUP="$NAME" +## + +## TODO - cd to directory script is in? + +useradd $USER +mkdir $BASE_DIR +mkdir $HOME_DIR + +cp ../../$NAME.jar $BASE_DIR + +chown -R $USER:$GROUP $BASE_DIR + +cp init/upstart/$NAME.conf /etc/init +chmod +x /etc/init/$NAME.conf + +service $NAME start diff --git a/package.json b/package.json index ca4039e616..210f5d264b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,17 @@ { - "name": "hp-autonomy-find", + "name": "hp-find", "repository": "https://github.com/hpautonomy/find", - "version": "1.0.1", + "private": true, + "license": "MIT", "devDependencies": { "bless": "^3.0.3", "bower": "^1.3.12", + "grunt": "^0.4.5", + "grunt-cli": "^0.1.13", + "grunt-contrib-connect": "^0.10.1", + "grunt-contrib-jasmine": "^0.8.2", + "grunt-contrib-watch": "^0.6.1", + "grunt-template-jasmine-requirejs": "^0.2.3", "requirejs": "^2.1.15" } } diff --git a/pom-sonar-js.xml b/pom-sonar-js.xml index 89a1f1e238..4906a367e0 100644 --- a/pom-sonar-js.xml +++ b/pom-sonar-js.xml @@ -52,4 +52,4 @@ - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index bec5b7dcda..b4f3e07f34 100644 --- a/pom.xml +++ b/pom.xml @@ -14,30 +14,20 @@ com.hp.autonomy.frontend.find find - 1.0.2 + 2.0.0 war - 3.2.8.RELEASE + 4.1.6.RELEASE + 4.0.1.RELEASE dev + 7.0.54 - cmbg-maven-releases - http://cmbg-maven.autonomy.com/nexus/content/repositories/releases - default - - true - - - false - - - - cmbg-maven-snapshots - http://cmbg-maven.autonomy.com/nexus/content/repositories/snapshots - default + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots false @@ -59,24 +49,6 @@ ${project.artifactId}-${env} - - org.apache.maven.plugins - maven-dependency-plugin - 2.8 - - - copy - process-sources - - unpack-dependencies - - - META-INF/test-resources/lib/**/*,META-INF/resources/lib/**/* - ${basedir}/target/web-resources - - - - org.codehaus.mojo exec-maven-plugin @@ -113,31 +85,6 @@ - - org.apache.maven.plugins - maven-antrun-plugin - 1.7 - - - move-webjars - process-sources - - run - - - - - - - - - - - - - - - @@ -146,6 +93,13 @@ production + + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcatEmbedWebsocket.version} + + ${project.artifactId} @@ -233,115 +187,29 @@ - org.apache.maven.plugins - maven-dependency-plugin - 2.8 + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.3-SNAPSHOT - copy - process-sources + package - unpack-dependencies - - - META-INF/resources/**/*,META-INF/test-resources/**/* - ${basedir}/target/web-resources - - - - - - org.apache.maven.plugins - maven-antrun-plugin - 1.7 - - - move-webjars - process-sources - - run - - - - - - - - - - - - - - - clean-up-relics - prepare-package - - run - - - - - - - - - - - com.github.timurstrekalov - saga-maven-plugin - 1.5.2 - - - verify - - coverage + exec-war-only - http://localhost:${jasmine.serverPort} - ${project.build.directory}/coverage - - .*/spec/.* - .*/mock/.* - .*/lib/.* - + /${project.build.finalName} + ${project.artifactId}.jar + UTF-8 - - - com.github.goldin - copy-maven-plugin - 0.2.5 - - - verify - - copy - - - - - false - - - ${project.build.directory}/coverage - ${project.build.directory}/coverage/total-coverage.dat - jsTestDriver.conf-coverage.dat - - - src/ - src/main/webapp/ - - - - - - - - org.codehaus.mojo - sonar-maven-plugin - 2.2 + + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcatEmbedWebsocket.version} + + @@ -356,22 +224,26 @@ - org.apache.maven.plugins - maven-clean-plugin - 2.5 - - - - src/main/webapp/static/lib - - - src/test/js/lib - - - node_modules - - - + org.codehaus.mojo + exec-maven-plugin + 1.3.1 + + + jasmine + test + + exec + + + node + ${basedir} + + ${project.basedir}/node_modules/grunt-cli/bin/grunt + test + + + + org.eclipse.jetty @@ -421,21 +293,83 @@ httpclient 4.3.1 + + commons-io + commons-io + 2.4 + + + commons-validator + commons-validator + 1.4.1 + + com.hp.autonomy.frontend.configuration configuration-impl - 1.0.0 + 1.2.0 + + + com.hp.autonomy.frontend.configuration + configuration-idol + 3.1.0 com.hp.autonomy.frontend.configuration configuration-authentication - 0.1.0 + 0.3.0 + + + com.hp.autonomy.frontend.user + idol-user-service + 1.0.0 com.hp.autonomy.frontend logging 1.1.1 + + com.hp.autonomy.hod + java-hod-client + 0.14.0-SNAPSHOT + + + com.hp.autonomy.hod + hod-sso-spring-security + 0.4.0-SNAPSHOT + + + com.hp.autonomy.hod + java-parametric-databases + 0.3.0-SNAPSHOT + + + com.hp.autonomy.frontend.view + view-proxy-components + 0.3.0-SNAPSHOT + + + com.hp.autonomy.hod.redis + redis-hod-token-repository + 0.4.0-SNAPSHOT + + + com.hp.autonomy.hod + hod-spring-caching + 0.2.0-SNAPSHOT + + + + com.hp.autonomy.aci.client + aci-api + 5.0.0 + + + com.hp.autonomy.aci.client + aci-annotations-processor + 1.0.0 + com.fasterxml.jackson.core @@ -447,6 +381,11 @@ slf4j-api 1.7.5 + + org.slf4j + jcl-over-slf4j + 1.7.5 + org.springframework spring-webmvc @@ -466,7 +405,7 @@ org.springframework.security spring-security-web - 3.2.5.RELEASE + ${org.springsecurity.version} commons-logging @@ -477,7 +416,7 @@ org.springframework.security spring-security-config - 3.2.5.RELEASE + ${org.springsecurity.version} commons-logging @@ -485,6 +424,16 @@ + + org.springframework.data + spring-data-redis + 1.6.0.RELEASE + + + org.springframework.session + spring-session-data-redis + 1.0.2.RELEASE + com.googlecode.ehcache-spring-annotations @@ -516,6 +465,12 @@ ${org.springframework.version} test + + junit + junit + 4.12 + test + ch.qos.logback @@ -543,12 +498,5 @@ 1.12.2 provided - - - junit - junit - 4.12 - - - \ No newline at end of file + diff --git a/scripts/init-find-dependencies.sh b/scripts/init-find-dependencies.sh new file mode 100644 index 0000000000..3c4a0184de --- /dev/null +++ b/scripts/init-find-dependencies.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +INDEX="search_default_index" +PROFILE="search_default_profile" + +ENDPOINT="api.idolondemand.com" + +# Read arguments +while [[ $# > 0 ]] +do + key="$1" + + case $key in + -e|--endpoint) + ENDPOINT="$2" + shift + ;; + + -a|--apikey) + APIKEY="$2" + shift + ;; + esac + shift +done + +echo "Using $ENDPOINT v1 and API key $APIKEY" + +# Create index +echo "Creating index $INDEX" +curl "https://$ENDPOINT/1/api/sync/createtextindex/v1?flavor=querymanipulation&apikey=$APIKEY&index=$INDEX" +echo -e "\n" + +# Create query profile +echo "Creating query profile $PROFILE" +curl "https://$ENDPOINT/1/api/sync/createqueryprofile/v1?query_manipulation_index=$INDEX&promotions_enabled=true&promotion_categories=default&promotions_identified=true&synonyms_enabled=true&synonym_categories=default&blacklists_enabled=true&blacklist_categories=default&query_profile=$PROFILE&apikey=$APIKEY" +echo -e "\nDone" + diff --git a/src/main/filters/filter-common.properties b/src/main/filters/filter-common.properties index 2998a9935a..2c91e1b168 100644 --- a/src/main/filters/filter-common.properties +++ b/src/main/filters/filter-common.properties @@ -8,3 +8,4 @@ log.level.autn=info log.level.root=info log.level.ehcache=info log.level.spring=info +log.level.apache=info diff --git a/src/main/java/com/hp/autonomy/frontend/find/ConfigApiKeyService.java b/src/main/java/com/hp/autonomy/frontend/find/ConfigApiKeyService.java deleted file mode 100644 index cf29d55966..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/ConfigApiKeyService.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find; - -import com.hp.autonomy.frontend.configuration.ConfigService; -import com.hp.autonomy.frontend.find.configuration.FindConfig; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class ConfigApiKeyService implements ApiKeyService { - - @Autowired - private ConfigService configService; - - @Override - public String getApiKey() { - return configService.getConfig().getIod().getApiKey(); - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/FindController.java b/src/main/java/com/hp/autonomy/frontend/find/FindController.java index 87269f306b..a3c8cb463c 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/FindController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/FindController.java @@ -5,32 +5,63 @@ package com.hp.autonomy.frontend.find; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.hp.autonomy.frontend.configuration.AuthenticationConfig; import com.hp.autonomy.frontend.configuration.ConfigService; import com.hp.autonomy.frontend.configuration.LoginTypes; -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.hp.autonomy.frontend.find.authentication.HodCombinedRequestController; +import com.hp.autonomy.frontend.find.web.ErrorController; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.sso.HodAuthenticationRequestService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public abstract class FindController { -@Controller -public class FindController { + public static final String PUBLIC_PATH = "/public/"; @Autowired - private ConfigService> configService; + private ConfigService> authenticationConfigService; + + @Autowired + private ObjectMapper contextObjectMapper; @RequestMapping("/") public void index(final HttpServletRequest request, final HttpServletResponse response) throws IOException { final String contextPath = request.getContextPath(); - if(LoginTypes.DEFAULT.equals(configService.getConfig().getAuthentication().getMethod())) { + if(LoginTypes.DEFAULT.equals(authenticationConfigService.getConfig().getAuthentication().getMethod())) { response.sendRedirect(contextPath + "/loginPage"); } else { - response.sendRedirect(contextPath + "/public/"); + response.sendRedirect(contextPath + PUBLIC_PATH); } } + @RequestMapping(value = PUBLIC_PATH, method = RequestMethod.GET) + public ModelAndView userName() throws JsonProcessingException, HodErrorException { + final String username = SecurityContextHolder.getContext().getAuthentication().getName(); + final Map config = new HashMap<>(); + config.put("username", username); + + final Map attributes = new HashMap<>(); + attributes.put("configJson", convertToJson(config)); + + return new ModelAndView("public", attributes); + } + + protected String convertToJson(final Object object) throws JsonProcessingException { + return contextObjectMapper.writeValueAsString(object).replace(" configService; + + @Autowired + private ObjectMapper contextObjectMapper; + + @RequestMapping(value = SSO_PAGE, method = RequestMethod.GET) + public ModelAndView sso() throws JsonProcessingException, HodErrorException { + final Map ssoConfig = new HashMap<>(); + ssoConfig.put("authenticatePath", SSO_AUTHENTICATION_URI); + ssoConfig.put("combinedRequestApi", HodCombinedRequestController.COMBINED_REQUEST); + ssoConfig.put("errorPage", ErrorController.CLIENT_AUTHENTICATION_ERROR); + ssoConfig.put("listApplicationRequest", hodAuthenticationRequestService.getListApplicationRequest()); + ssoConfig.put("listApplicationRequestApi", HodCombinedRequestController.LIST_APPLICATION_REQUEST); + ssoConfig.put("ssoPage", System.getProperty("find.hod.sso", "https://www.idolondemand.com/sso.html")); + ssoConfig.put("ssoEntryPage", SSO_PAGE); + + final Map attributes = new HashMap<>(); + attributes.put("configJson", convertToJson(ssoConfig)); + + return new ModelAndView("sso", attributes); + } + + @RequestMapping(value = SSO_LOGOUT_PAGE, method = RequestMethod.GET) + public ModelAndView ssoLogoutPage() throws JsonProcessingException { + final HodFindConfig hodFindConfig = (HodFindConfig) configService.getConfig(); + + final Map ssoConfig = new HashMap<>(); + ssoConfig.put("endpoint", HOD_ENDPOINT); + ssoConfig.put("redirectUrl", hodFindConfig.getHsod().getLandingPageUrl()); + + final Map attributes = new HashMap<>(); + attributes.put("configJson", contextObjectMapper.writeValueAsString(ssoConfig)); + return new ModelAndView("sso-logout", attributes); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/IdolFindController.java b/src/main/java/com/hp/autonomy/frontend/find/IdolFindController.java new file mode 100644 index 0000000000..52568626f0 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/IdolFindController.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find; + +import com.hp.autonomy.frontend.find.beanconfiguration.IdolCondition; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@Conditional(IdolCondition.class) +public class IdolFindController extends FindController { + + @RequestMapping("/api/config/test") + @ResponseBody + public String foo() { + return "It works!"; + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/authentication/HavenSearchUserMetadata.java b/src/main/java/com/hp/autonomy/frontend/find/authentication/HavenSearchUserMetadata.java new file mode 100644 index 0000000000..c903928268 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/authentication/HavenSearchUserMetadata.java @@ -0,0 +1,19 @@ +package com.hp.autonomy.frontend.find.authentication; + +import com.google.common.collect.ImmutableMap; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Map; + +@NoArgsConstructor(access = AccessLevel.NONE) +public class HavenSearchUserMetadata { + + public static final String USERNAME = "HAVEN_SEARCH_ONDEMAND_USERNAME"; + + public static final Map> METADATA_TYPES = ImmutableMap.>builder() + .put(USERNAME, String.class) + .build(); + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/authentication/HodCombinedRequestController.java b/src/main/java/com/hp/autonomy/frontend/find/authentication/HodCombinedRequestController.java new file mode 100644 index 0000000000..ce9e5c412f --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/authentication/HodCombinedRequestController.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.authentication; + +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.hod.client.api.authentication.SignedRequest; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.sso.HodAuthenticationRequestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Conditional(HodCondition.class) +public class HodCombinedRequestController { + + public static final String COMBINED_REQUEST = "/api/authentication/combined-request"; + public static final String LIST_APPLICATION_REQUEST = "/api/authentication/list-application-request"; + + @Autowired + private HodAuthenticationRequestService tokenService; + + @RequestMapping(value = COMBINED_REQUEST, method = RequestMethod.GET) + public SignedRequest getCombinedRequest( + @RequestParam("domain") final String domain, + @RequestParam("application") final String application, + @RequestParam("user-store-domain") final String userStoreDomain, + @RequestParam("user-store-name") final String userStoreName + ) throws HodErrorException { + return tokenService.getCombinedRequest(domain, application, userStoreDomain, userStoreName); + } + + @RequestMapping(value = "/list-application-request", method = RequestMethod.GET) + public SignedRequest getListApplicationRequest() throws HodErrorException { + return tokenService.getListApplicationRequest(); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/authentication/HsodUsernameResolver.java b/src/main/java/com/hp/autonomy/frontend/find/authentication/HsodUsernameResolver.java new file mode 100644 index 0000000000..4ac68f1232 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/authentication/HsodUsernameResolver.java @@ -0,0 +1,14 @@ +package com.hp.autonomy.frontend.find.authentication; + +import com.hp.autonomy.hod.sso.HodUsernameResolver; + +import java.io.Serializable; +import java.util.Map; + +public class HsodUsernameResolver implements HodUsernameResolver { + @Override + public String resolve(final Map metadata) { + final Serializable serializableName = metadata.get(HavenSearchUserMetadata.USERNAME); + return serializableName instanceof String ? (String) serializableName : null; + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/authentication/LoginController.java b/src/main/java/com/hp/autonomy/frontend/find/authentication/LoginController.java index c9b9d11266..4cd7f598c8 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/authentication/LoginController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/authentication/LoginController.java @@ -52,4 +52,4 @@ public void login( response.sendRedirect(redirectUrl); } -} \ No newline at end of file +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractBackendCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractBackendCondition.java new file mode 100644 index 0000000000..fdf424994d --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractBackendCondition.java @@ -0,0 +1,14 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +public abstract class AbstractBackendCondition extends AbstractEnumCondition { + private static final BackendConfig DEFAULT_VALUE = BackendConfig.HAVEN_ON_DEMAND; + + protected AbstractBackendCondition(final BackendConfig backendConfig) { + super("hp.find.backend", backendConfig, DEFAULT_VALUE, BackendConfig.class); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractEnumCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractEnumCondition.java new file mode 100644 index 0000000000..3c415b5277 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractEnumCondition.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public abstract class AbstractEnumCondition> implements Condition { + + private final String systemProperty; + private final T specifiedValue; + private final T defaultValue; + private final Class typeToken; + + protected AbstractEnumCondition(final String systemProperty, final T specifiedValue, final T defaultValue, final Class typeToken) { + this.systemProperty = systemProperty; + this.specifiedValue = specifiedValue; + this.defaultValue = defaultValue; + this.typeToken = typeToken; + } + + @Override + public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) { + return getProperty(context) == specifiedValue; + } + + private T getProperty(final ConditionContext context) { + return Enum.valueOf(typeToken, context.getEnvironment().getProperty(systemProperty, defaultValue.name())); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractPersistentStateCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractPersistentStateCondition.java new file mode 100644 index 0000000000..a9bbba480f --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AbstractPersistentStateCondition.java @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +public abstract class AbstractPersistentStateCondition extends AbstractEnumCondition { + + private static final PersistentStateConfig DEFAULT_VALUE = PersistentStateConfig.REDIS; + + protected AbstractPersistentStateCondition(final PersistentStateConfig persistentStateConfig) { + super("hp.find.persistentState", persistentStateConfig, DEFAULT_VALUE, PersistentStateConfig.class); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AppConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AppConfiguration.java new file mode 100644 index 0000000000..91e6e8402c --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/AppConfiguration.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + + +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.configuration.filter.ConfigEnvironmentVariableFilter; +import com.hp.autonomy.frontend.find.configuration.TextEncryptorPasswordFactory; +import com.hp.autonomy.frontend.logging.ApplicationStartLogger; +import org.jasypt.util.text.BasicTextEncryptor; +import org.jasypt.util.text.TextEncryptor; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.PropertiesFactoryBean; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +import java.util.Locale; +import java.util.Set; + +/** + * Contains beans useful in all configurations + */ +@Configuration +@ComponentScan( + basePackages = "com.hp.autonomy.frontend.find", + excludeFilters = { + @ComponentScan.Filter(Controller.class), + @ComponentScan.Filter(RestController.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = DispatcherServletConfiguration.class) + } +) +public class AppConfiguration { + + @Autowired + private ConfigService configService; + + @Bean + public TextEncryptor textEncryptor() { + final TextEncryptorPasswordFactory passwordFactory = new TextEncryptorPasswordFactory(); + + final BasicTextEncryptor basicTextEncryptor = new BasicTextEncryptor(); + + try { + basicTextEncryptor.setPassword(passwordFactory.getObject()); + } catch (final Exception e) { + throw new BeanInitializationException("Failed to initialize TextEncryptor for some reason", e); + } + + return basicTextEncryptor; + } + + @Bean + public SimpleFilterProvider filterProvider() { + final Set set = ImmutableSet.of( + "productType", + "indexErrorMessage", + "enabled", + "plaintextPassword", + "currentPassword" + ); + + final SimpleBeanPropertyFilter.SerializeExceptFilter filter = new SimpleBeanPropertyFilter.SerializeExceptFilter(set); + + return new SimpleFilterProvider(ImmutableMap.of("configurationFilter", filter)); + } + + //TODO: merge properties files + @Bean + public PropertiesFactoryBean dispatcherProperties() { + return getPropertiesFactoryBean(new ClassPathResource("com/hp/autonomy/frontend/find/dispatcher.properties")); + } + + @Bean + public PropertiesFactoryBean applicationProperties() { + return getPropertiesFactoryBean(new ClassPathResource("com/hp/autonomy/frontend/find/find.properties")); + } + + private PropertiesFactoryBean getPropertiesFactoryBean(final ClassPathResource location) { + final PropertiesFactoryBean bean = new PropertiesFactoryBean(); + bean.setLocation(location); + + return bean; + } + + @Bean + public ApplicationStartLogger applicationStartLogger() { + return new ApplicationStartLogger(); + } + + @Bean + public LocaleResolver localeResolver() { + final SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver(); + sessionLocaleResolver.setDefaultLocale(Locale.ENGLISH); + + return sessionLocaleResolver; + } + + @Bean + public MessageSource messageSource() { + final ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); + resourceBundleMessageSource.setBasename("com.hp.autonomy.frontend.find.i18n"); + + return resourceBundleMessageSource; + } + + @Bean + public ConfigEnvironmentVariableFilter configEnvironmentVariableFilter() { + final ConfigEnvironmentVariableFilter configEnvironmentVariableFilter = new ConfigEnvironmentVariableFilter(); + configEnvironmentVariableFilter.setConfigPage("/configError"); + configEnvironmentVariableFilter.setConfigService(configService); + + return configEnvironmentVariableFilter; + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/BackendConfig.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/BackendConfig.java new file mode 100644 index 0000000000..55432b326e --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/BackendConfig.java @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +/** + * What kind of backend to use + */ +public enum BackendConfig { + + /** + * Use Haven OnDemand as the backend for Find. + */ + HAVEN_ON_DEMAND, + + /** + * Use your own IDOL servers (sold separately) as the backend for Find. + */ + IDOL + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/DispatcherServletConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/DispatcherServletConfiguration.java new file mode 100644 index 0000000000..25b27871c9 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/DispatcherServletConfiguration.java @@ -0,0 +1,89 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hp.autonomy.frontend.find.converter.StringToResourceIdentifierConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import org.springframework.web.servlet.view.InternalResourceViewResolver; +import org.springframework.web.servlet.view.JstlView; + +import java.util.List; +import java.util.Properties; + +@Configuration +@ComponentScan( + basePackages = { + "com.hp.autonomy.frontend.find" + }, + includeFilters = { + @ComponentScan.Filter(Controller.class), + @ComponentScan.Filter(RestController.class), + }, + excludeFilters = { + @ComponentScan.Filter(Configuration.class) + } +) +public class DispatcherServletConfiguration extends WebMvcConfigurationSupport { + + @Autowired + private ObjectMapper dispatcherObjectMapper; + + @Autowired + private Properties dispatcherProperties; + + @Bean + public ViewResolver internalResourceViewResolver() { + final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); + viewResolver.setViewClass(JstlView.class); + viewResolver.setPrefix("/WEB-INF/jsps/"); + viewResolver.setSuffix(".jsp"); + + return viewResolver; + } + + @Override + public void extendMessageConverters(final List> converters) { + final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(dispatcherObjectMapper); + + converters.add(converter); + } + + @Override + public void addFormatters(final FormatterRegistry registry) { + registry.addConverter(new StringToResourceIdentifierConverter()); + + registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory()); + } + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/static-" + dispatcherProperties.getProperty("application.version") + "/**").addResourceLocations("/static/"); + } + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + registry.addViewController("/p/").setViewName("private"); + registry.addViewController("/config/").setViewName("config"); + registry.addViewController("/login").setViewName("login"); + registry.addViewController("/configError").setViewName("configError"); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodCondition.java new file mode 100644 index 0000000000..6899e2f230 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodCondition.java @@ -0,0 +1,12 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +public class HodCondition extends AbstractBackendCondition { + protected HodCondition() { + super(BackendConfig.HAVEN_ON_DEMAND); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodConfigFileConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodConfigFileConfiguration.java new file mode 100644 index 0000000000..98a0e2aaba --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodConfigFileConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.ser.FilterProvider; +import com.hp.autonomy.frontend.configuration.Authentication; +import com.hp.autonomy.frontend.configuration.BCryptUsernameAndPassword; +import com.hp.autonomy.frontend.configuration.ConfigurationFilterMixin; +import com.hp.autonomy.frontend.find.configuration.HodAuthenticationMixins; +import com.hp.autonomy.frontend.find.configuration.HodFindConfigFileService; +import org.jasypt.util.text.TextEncryptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Conditional(HodCondition.class) +public class HodConfigFileConfiguration { + + @Autowired + private TextEncryptor textEncryptor; + + @Autowired + private FilterProvider filterProvider; + + @Bean + public HodFindConfigFileService configService() { + final HodFindConfigFileService configService = new HodFindConfigFileService(); + configService.setConfigFileLocation("hp.find.home"); + configService.setConfigFileName("config.json"); + configService.setDefaultConfigFile("/com/hp/autonomy/frontend/find/configuration/defaultHodConfigFile.json"); + configService.setMapper(objectMapper()); + configService.setTextEncryptor(textEncryptor); + configService.setFilterProvider(filterProvider); + + return configService; + } + + @Bean(name = "contextObjectMapper") + public ObjectMapper objectMapper() { + final ObjectMapper mapper = new ObjectMapper(); + + mapper.enable(SerializationFeature.INDENT_OUTPUT); + + mapper.addMixInAnnotations(Authentication.class, HodAuthenticationMixins.class); + mapper.addMixInAnnotations(BCryptUsernameAndPassword.class, ConfigurationFilterMixin.class); + + return mapper; + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodConfiguration.java new file mode 100644 index 0000000000..2e02734b2e --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/HodConfiguration.java @@ -0,0 +1,263 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hp.autonomy.databases.DatabasesService; +import com.hp.autonomy.databases.DatabasesServiceImpl; +import com.hp.autonomy.fields.IndexFieldsService; +import com.hp.autonomy.fields.IndexFieldsServiceImpl; +import com.hp.autonomy.frontend.configuration.Authentication; +import com.hp.autonomy.frontend.configuration.SingleUserAuthenticationValidator; +import com.hp.autonomy.frontend.configuration.ValidationService; +import com.hp.autonomy.frontend.configuration.ValidationServiceImpl; +import com.hp.autonomy.frontend.configuration.Validator; +import com.hp.autonomy.frontend.find.configuration.HodAuthenticationMixins; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.frontend.find.configuration.HodFindConfigFileService; +import com.hp.autonomy.frontend.find.configuration.IodConfigValidator; +import com.hp.autonomy.frontend.find.parametricfields.CacheableIndexFieldsService; +import com.hp.autonomy.frontend.find.parametricfields.CacheableParametricValuesService; +import com.hp.autonomy.frontend.view.hod.HodViewService; +import com.hp.autonomy.frontend.view.hod.HodViewServiceImpl; +import com.hp.autonomy.hod.caching.HodApplicationCacheResolver; +import com.hp.autonomy.hod.client.api.analysis.viewdocument.ViewDocumentService; +import com.hp.autonomy.hod.client.api.analysis.viewdocument.ViewDocumentServiceImpl; +import com.hp.autonomy.hod.client.api.authentication.AuthenticationService; +import com.hp.autonomy.hod.client.api.authentication.AuthenticationServiceImpl; +import com.hp.autonomy.hod.client.api.authentication.EntityType; +import com.hp.autonomy.hod.client.api.authentication.TokenType; +import com.hp.autonomy.hod.client.api.resource.ResourcesService; +import com.hp.autonomy.hod.client.api.resource.ResourcesServiceImpl; +import com.hp.autonomy.hod.client.api.textindex.query.content.GetContentService; +import com.hp.autonomy.hod.client.api.textindex.query.content.GetContentServiceImpl; +import com.hp.autonomy.hod.client.api.textindex.query.fields.RetrieveIndexFieldsService; +import com.hp.autonomy.hod.client.api.textindex.query.fields.RetrieveIndexFieldsServiceImpl; +import com.hp.autonomy.hod.client.api.textindex.query.parametric.GetParametricValuesService; +import com.hp.autonomy.hod.client.api.textindex.query.parametric.GetParametricValuesServiceImpl; +import com.hp.autonomy.hod.client.api.textindex.query.search.Documents; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindRelatedConceptsService; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindRelatedConceptsServiceImpl; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindSimilarService; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindSimilarServiceImpl; +import com.hp.autonomy.hod.client.api.textindex.query.search.QueryTextIndexService; +import com.hp.autonomy.hod.client.api.textindex.query.search.QueryTextIndexServiceImpl; +import com.hp.autonomy.hod.client.api.userstore.user.UserStoreUsersService; +import com.hp.autonomy.hod.client.api.userstore.user.UserStoreUsersServiceImpl; +import com.hp.autonomy.hod.client.config.HodServiceConfig; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.client.token.TokenProxyService; +import com.hp.autonomy.hod.client.token.TokenRepository; +import com.hp.autonomy.hod.sso.HodAuthenticationRequestService; +import com.hp.autonomy.hod.sso.HodAuthenticationRequestServiceImpl; +import com.hp.autonomy.hod.sso.SpringSecurityTokenProxyService; +import com.hp.autonomy.hod.sso.UnboundTokenService; +import com.hp.autonomy.hod.sso.UnboundTokenServiceImpl; +import com.hp.autonomy.parametricvalues.ParametricValuesService; +import com.hp.autonomy.parametricvalues.ParametricValuesServiceImpl; +import org.apache.http.HttpHost; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.CacheResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; +import java.util.HashSet; + +@Configuration +@Conditional({HodCondition.class}) +@EnableCaching +public class HodConfiguration extends CachingConfigurerSupport { + + @Autowired + private TokenRepository tokenRepository; + + @Autowired + private CacheManager cacheManager; + + @Autowired + private HodFindConfigFileService configService; + + @Bean(name = "dispatcherObjectMapper") + public ObjectMapper dispatcherObjectMapper() { + final ObjectMapper mapper = new ObjectMapper(); + + mapper.addMixInAnnotations(Authentication.class, HodAuthenticationMixins.class); + + return mapper; + } + + @Bean + public SingleUserAuthenticationValidator singleUserAuthenticationValidator() { + final SingleUserAuthenticationValidator singleUserAuthenticationValidator = new SingleUserAuthenticationValidator(); + singleUserAuthenticationValidator.setConfigService(configService); + + return singleUserAuthenticationValidator; + } + + @Bean + public IodConfigValidator iodConfigValidator() { + return new IodConfigValidator(); + } + + @Bean + public ValidationService validationService() { + final ValidationServiceImpl validationService = new ValidationServiceImpl<>(); + + // The type annotation here is required to make it compile + //noinspection Convert2Diamond + validationService.setValidators(new HashSet>(Arrays.asList( + singleUserAuthenticationValidator(), + iodConfigValidator() + ))); + + // fix circular dependency + configService.setValidationService(validationService); + + return validationService; + } + + @Override + @Bean + public CacheResolver cacheResolver() { + final HodApplicationCacheResolver hodApplicationCacheResolver = new HodApplicationCacheResolver(); + hodApplicationCacheResolver.setCacheManager(cacheManager); + + return hodApplicationCacheResolver; + } + + @Bean + public HttpClient httpClient() { + final HttpClientBuilder builder = HttpClientBuilder.create(); + + final String proxyHost = System.getProperty("find.https.proxyHost"); + + if(proxyHost != null) { + final Integer proxyPort = Integer.valueOf(System.getProperty("find.https.proxyPort", "8080")); + builder.setProxy(new HttpHost(proxyHost, proxyPort)); + } + + return builder.build(); + } + + private HodServiceConfig.Builder hodServiceConfigBuilder() { + final String endpoint = System.getProperty("find.iod.api", "https://api.havenondemand.com"); + + return new HodServiceConfig.Builder(endpoint) + .setHttpClient(httpClient()) + .setTokenRepository(tokenRepository); + } + + @Bean + public HodServiceConfig initialHodServiceConfig() { + return hodServiceConfigBuilder() + .build(); + } + + @Bean + public AuthenticationService authenticationService() { + return new AuthenticationServiceImpl(initialHodServiceConfig()); + } + + @Bean + public TokenProxyService tokenProxyService() { + return new SpringSecurityTokenProxyService(); + } + + @Bean + public HodServiceConfig hodServiceConfig() { + return hodServiceConfigBuilder() + .setTokenProxyService(tokenProxyService()) + .build(); + } + + @Bean + public ResourcesService resourcesService() { + return new ResourcesServiceImpl(hodServiceConfig()); + } + + @Bean + public QueryTextIndexService queryTextIndexService() { + return QueryTextIndexServiceImpl.documentsService(hodServiceConfig()); + } + + @Bean + public FindRelatedConceptsService relatedConceptsService() { + return new FindRelatedConceptsServiceImpl(hodServiceConfig()); + } + + @Bean + public RetrieveIndexFieldsService retrieveIndexFieldsService() { + return new RetrieveIndexFieldsServiceImpl(hodServiceConfig()); + } + + @Bean + public IndexFieldsService indexFieldsService() { + return new CacheableIndexFieldsService(new IndexFieldsServiceImpl(retrieveIndexFieldsService())); + } + + @Bean + public DatabasesService databasesService() { + return new DatabasesServiceImpl(resourcesService(), indexFieldsService()); + } + + @Bean + public GetParametricValuesService getParametricValuesService() { + return new GetParametricValuesServiceImpl(hodServiceConfig()); + } + + @Bean + public ParametricValuesService parametricValuesService() { + return new CacheableParametricValuesService(new ParametricValuesServiceImpl(getParametricValuesService())); + } + + @Bean + public GetContentService getContentService() { + return GetContentServiceImpl.documentsService(hodServiceConfig()); + } + + @Bean + public ViewDocumentService viewDocumentService() { + return new ViewDocumentServiceImpl(hodServiceConfig()); + } + + @Bean + public HodAuthenticationRequestService hodAuthenticationRequestService() { + return new HodAuthenticationRequestServiceImpl(configService, authenticationService(), unboundTokenService()); + } + + @Bean + public UnboundTokenService unboundTokenService() { + try { + return new UnboundTokenServiceImpl(authenticationService(), configService); + } catch (final HodErrorException e) { + throw new BeanInitializationException("Exception creating UnboundTokenService", e); + } + } + + @Bean + public HodViewService hodViewService() { + return new HodViewServiceImpl(viewDocumentService(), getContentService(), queryTextIndexService()); + } + + @Bean + public FindSimilarService findSimilarService() { + return FindSimilarServiceImpl.documentsService(hodServiceConfig()); + } + + @Bean + public UserStoreUsersService userStoreUsersService() { + return new UserStoreUsersServiceImpl(hodServiceConfig()); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/IdolCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/IdolCondition.java new file mode 100644 index 0000000000..44b106efe1 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/IdolCondition.java @@ -0,0 +1,13 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +public class IdolCondition extends AbstractBackendCondition { + + public IdolCondition() { + super(BackendConfig.IDOL); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/IdolConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/IdolConfiguration.java new file mode 100644 index 0000000000..9b99b661dc --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/IdolConfiguration.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.autonomy.aci.client.annotations.IdolAnnotationsProcessorFactory; +import com.autonomy.aci.client.annotations.IdolAnnotationsProcessorFactoryImpl; +import com.autonomy.aci.client.services.AciService; +import com.autonomy.aci.client.services.impl.AciServiceImpl; +import com.autonomy.aci.client.transport.impl.AciHttpClientImpl; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.ser.FilterProvider; +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.configuration.CommunityAciService; +import com.hp.autonomy.frontend.find.configuration.ContentAciService; +import com.hp.autonomy.frontend.find.configuration.IdolFindConfig; +import com.hp.autonomy.frontend.find.configuration.IdolFindConfigFileService; +import com.hp.autonomy.user.UserService; +import com.hp.autonomy.user.UserServiceImpl; +import org.apache.http.client.HttpClient; +import org.apache.http.config.SocketConfig; +import org.apache.http.impl.client.HttpClientBuilder; +import org.jasypt.util.text.TextEncryptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Conditional(IdolCondition.class) +public class IdolConfiguration { + + @Autowired + private TextEncryptor textEncryptor; + + @Autowired + private FilterProvider filterProvider; + + @Bean + public ConfigService configFileService() { + final IdolFindConfigFileService configService = new IdolFindConfigFileService(); + configService.setConfigFileLocation("hp.find.home"); + configService.setConfigFileName("config.json"); + configService.setDefaultConfigFile("/com/hp/autonomy/frontend/find/configuration/defaultIdolConfigFile.json"); + configService.setMapper(objectMapper()); + configService.setTextEncryptor(textEncryptor); + configService.setFilterProvider(filterProvider); + + return configService; + } + + @Bean(name = "contextObjectMapper") + public ObjectMapper objectMapper() { + final ObjectMapper mapper = new ObjectMapper(); + + mapper.enable(SerializationFeature.INDENT_OUTPUT); + + // TODO add mixins + + return mapper; + } + + @Bean + public HttpClient httpClient() { + final SocketConfig socketConfig = SocketConfig.custom() + .setSoTimeout(90000) + .build(); + + return HttpClientBuilder.create() + .setMaxConnPerRoute(20) + .setMaxConnTotal(120) + .setDefaultSocketConfig(socketConfig) + .build(); + } + + @Bean + public HttpClient testHttpClient() { + final SocketConfig socketConfig = SocketConfig.custom() + .setSoTimeout(2000) + .build(); + + return HttpClientBuilder.create() + .setMaxConnPerRoute(5) + .setMaxConnTotal(5) + .setDefaultSocketConfig(socketConfig) + .build(); + } + + @Bean + public IdolAnnotationsProcessorFactory processorFactory() { + return new IdolAnnotationsProcessorFactoryImpl(); + } + + @Bean + public UserService userService() { + return new UserServiceImpl(configFileService(), aciService(), processorFactory()); + } + + @Bean + public AciService contentAciService() { + return new ContentAciService(aciService(), configFileService()); + } + + @Bean + public AciService communutyAciService() { + return new CommunityAciService(aciService(), configFileService()); + } + + @Bean + public AciService aciService() { + return new AciServiceImpl(new AciHttpClientImpl(httpClient())); + } + + @Bean + public AciService testAciService() { + return new AciServiceImpl(new AciHttpClientImpl(testHttpClient())); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryCondition.java new file mode 100644 index 0000000000..e343e78065 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryCondition.java @@ -0,0 +1,14 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +public class InMemoryCondition extends AbstractPersistentStateCondition { + + public InMemoryCondition() { + super(PersistentStateConfig.INMEMORY); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryConfiguration.java new file mode 100644 index 0000000000..3ef08f364b --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.hp.autonomy.frontend.find.configuration.AutoCreatingEhCacheCacheManager; +import net.sf.ehcache.config.CacheConfiguration; +import net.sf.ehcache.store.MemoryStoreEvictionPolicy; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.ExpiringSession; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.web.http.SessionRepositoryFilter; + +@Configuration +@Conditional(InMemoryCondition.class) +public class InMemoryConfiguration { + + @Bean + @Conditional(InMemoryCondition.class) + public SessionRepositoryFilter springSessionRepositoryFilter() { + return new SessionRepositoryFilter<>(sessionRepository()); + } + + @Bean + @Conditional(InMemoryCondition.class) + public SessionRepository sessionRepository() { + return new MapSessionRepository(); + } + + @Bean + public CacheConfiguration defaultCacheConfiguration() { + return new CacheConfiguration() + .eternal(false) + .maxElementsInMemory(1000) + .overflowToDisk(false) + .diskPersistent(false) + .timeToIdleSeconds(0) + .timeToLiveSeconds(30 * 60) + .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU); + } + + @Bean(destroyMethod = "shutdown") + public net.sf.ehcache.CacheManager ehCacheManager() { + final net.sf.ehcache.config.Configuration configuration = new net.sf.ehcache.config.Configuration() + .defaultCache(defaultCacheConfiguration()) + .updateCheck(false); + + return new net.sf.ehcache.CacheManager(configuration); + } + + @Bean + public CacheManager cacheManager() { + return new AutoCreatingEhCacheCacheManager(ehCacheManager(), defaultCacheConfiguration()); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryHodConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryHodConfiguration.java new file mode 100644 index 0000000000..7fbd556689 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/InMemoryHodConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.hp.autonomy.hod.client.token.InMemoryTokenRepository; +import com.hp.autonomy.hod.client.token.TokenRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +/** + * Beans which only need to exist when using Haven OnDemand without Redis + */ +@Configuration +@Conditional({InMemoryCondition.class, HodCondition.class}) +public class InMemoryHodConfiguration { + + @Bean + public TokenRepository tokenRepository() { + return new InMemoryTokenRepository(); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/PersistentStateConfig.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/PersistentStateConfig.java new file mode 100644 index 0000000000..f2ec6deb0b --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/PersistentStateConfig.java @@ -0,0 +1,17 @@ +/* + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +/** + * Where to store the Haven OnDemand persistent state (Haven OnDemand tokens, sessions etc.) + */ +public enum PersistentStateConfig { + /** Store the token in memory - only good for a single Find node */ + INMEMORY, + + /** Store the token in Redis - use this for clustered Find */ + REDIS +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisCondition.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisCondition.java new file mode 100644 index 0000000000..10c6d038ca --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisCondition.java @@ -0,0 +1,14 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +public class RedisCondition extends AbstractPersistentStateCondition { + + public RedisCondition() { + super(PersistentStateConfig.REDIS); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisConfiguration.java new file mode 100644 index 0000000000..738d337f25 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.configuration.HostAndPort; +import com.hp.autonomy.frontend.configuration.RedisConfig; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.frontend.find.web.CacheNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.DefaultRedisCachePrefix; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; + +import java.util.Properties; + +@Configuration +@Conditional(RedisCondition.class) +@EnableRedisHttpSession +public class RedisConfiguration { + + @Autowired + private ConfigService configService; + + @Autowired + private Properties dispatcherProperties; + + @Bean + public JedisConnectionFactory redisConnectionFactory() { + final RedisConfig config = configService.getConfig().getRedis(); + final JedisConnectionFactory connectionFactory; + + //If we haven't specified any sentinels then assume non-distributed setup + if (config.getSentinels().isEmpty()) { + connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName(config.getAddress().getHost()); + connectionFactory.setPort(config.getAddress().getPort()); + } else { + final RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration().master(config.getMasterName()); + for (final HostAndPort node : config.getSentinels()) { + sentinelConfig.sentinel(node.getHost(), node.getPort()); + } + + connectionFactory = new JedisConnectionFactory(sentinelConfig); + } + + final Integer database = config.getDatabase(); + + if (database != null) { + connectionFactory.setDatabase(database); + } + + connectionFactory.setPassword(config.getPassword()); + + return connectionFactory; + } + + @Bean + public CacheManager cacheManager() { + final RedisCacheManager cacheManager = new RedisCacheManager(cachingRedisTemplate()); + cacheManager.setUsePrefix(true); + cacheManager.setCachePrefix(new DefaultRedisCachePrefix(":cache:" + dispatcherProperties.getProperty("application.version") + ':')); + + cacheManager.setDefaultExpiration(30 * 60); + cacheManager.setExpires(CacheNames.CACHE_EXPIRES); + + return cacheManager; + } + + @Bean + public RedisTemplate cachingRedisTemplate() { + final RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + return template; + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisHodConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisHodConfiguration.java new file mode 100644 index 0000000000..36538b6062 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/RedisHodConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.configuration.HostAndPort; +import com.hp.autonomy.frontend.configuration.RedisConfig; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.hod.redis.RedisTokenRepository; +import com.hp.autonomy.hod.redis.RedisTokenRepositoryConfig; +import com.hp.autonomy.hod.redis.RedisTokenRepositorySentinelConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import java.util.HashSet; +import java.util.Set; + +@Configuration +@Conditional({RedisCondition.class, HodCondition.class}) +public class RedisHodConfiguration { + + @Autowired + private ConfigService configService; + + @Bean(destroyMethod = "destroy") + public RedisTokenRepository tokenRepository() { + final RedisConfig redisConfig = configService.getConfig().getRedis(); + final Integer database = redisConfig.getDatabase(); + + if(redisConfig.getSentinels().isEmpty()) { + final HostAndPort address = redisConfig.getAddress(); + + return new RedisTokenRepository(new RedisTokenRepositoryConfig.Builder() + .setHost(address.getHost()) + .setPort(address.getPort()) + .setDatabase(database) + .build()); + } + else { + final Set sentinels = new HashSet<>(); + + for(final HostAndPort hostAndPort : redisConfig.getSentinels()) { + sentinels.add(new RedisTokenRepositorySentinelConfig.HostAndPort(hostAndPort.getHost(), hostAndPort.getPort())); + } + + return new RedisTokenRepository(new RedisTokenRepositorySentinelConfig.Builder() + .setHostsAndPorts(sentinels) + .setMasterName(redisConfig.getMasterName()) + .setDatabase(database) + .build()); + } + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/SecurityConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/SecurityConfiguration.java new file mode 100644 index 0000000000..916d3fa39b --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/beanconfiguration/SecurityConfiguration.java @@ -0,0 +1,278 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.beanconfiguration; + +import com.google.common.collect.ImmutableMap; +import com.hp.autonomy.frontend.configuration.AuthenticationConfig; +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.configuration.authentication.CommunityAuthenticationProvider; +import com.hp.autonomy.frontend.configuration.authentication.DefaultLoginAuthenticationProvider; +import com.hp.autonomy.frontend.configuration.authentication.LoginSuccessHandler; +import com.hp.autonomy.frontend.configuration.authentication.OneToOneOrZeroSimpleAuthorityMapper; +import com.hp.autonomy.frontend.configuration.authentication.Role; +import com.hp.autonomy.frontend.configuration.authentication.Roles; +import com.hp.autonomy.frontend.configuration.authentication.SingleUserAuthenticationProvider; +import com.hp.autonomy.frontend.find.FindController; +import com.hp.autonomy.frontend.find.HodFindController; +import com.hp.autonomy.frontend.find.authentication.HavenSearchUserMetadata; +import com.hp.autonomy.frontend.find.authentication.HsodUsernameResolver; +import com.hp.autonomy.frontend.find.web.HodLogoutSuccessHandler; +import com.hp.autonomy.hod.client.api.authentication.AuthenticationService; +import com.hp.autonomy.hod.client.api.authentication.TokenType; +import com.hp.autonomy.hod.client.api.userstore.user.UserStoreUsersService; +import com.hp.autonomy.hod.client.token.TokenRepository; +import com.hp.autonomy.hod.sso.HodAuthenticationProvider; +import com.hp.autonomy.hod.sso.HodTokenLogoutSuccessHandler; +import com.hp.autonomy.hod.sso.SsoAuthenticationEntryPoint; +import com.hp.autonomy.hod.sso.SsoAuthenticationFilter; +import com.hp.autonomy.hod.sso.UnboundTokenService; +import com.hp.autonomy.user.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + public static final String USER_ROLE = "PUBLIC"; + public static final String ADMIN_ROLE = "ADMIN"; + public static final String CONFIG_ROLE = "DEFAULT"; + + @Configuration + public static class AppSecurity extends WebSecurityConfigurerAdapter { + @Override + public void configure(final WebSecurity web) { + web.ignoring() + .antMatchers("/static-*/**"); + } + + @SuppressWarnings("ProhibitedExceptionDeclared") + @Override + protected void configure(final HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/api/public/**").hasRole(USER_ROLE) + .antMatchers("/api/useradmin/**").hasRole(ADMIN_ROLE) + .antMatchers("/api/config/**").hasRole(CONFIG_ROLE) + .and() + .csrf() + .disable() + .headers() + .defaultsDisabled() + .frameOptions() + .sameOrigin(); + } + } + + @Configuration + @Conditional(IdolCondition.class) + @Order(99) + public static class IdolSecurity extends WebSecurityConfigurerAdapter { + + public static final String IDOL_USER_ROLE = "FindUser"; + public static final String IDOL_ADMIN_ROLE = "FindAdmin"; + + @Autowired + private ConfigService> configService; + + @Autowired + private UserService userService; + + @SuppressWarnings("ProhibitedExceptionDeclared") + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth + .authenticationProvider(new DefaultLoginAuthenticationProvider(configService, CONFIG_ROLE)) + .authenticationProvider(communityAuthenticationProvider()); + } + + @Override + protected void configure(final HttpSecurity http) throws Exception { + final AuthenticationSuccessHandler successHandler = new LoginSuccessHandler(role(CONFIG_ROLE), "/config", FindController.PUBLIC_PATH); + + http + .csrf() + .disable() + .logout() + .logoutUrl("/logout") + .logoutSuccessUrl("/login") + .and() + .formLogin() + .loginPage("/login") + .loginProcessingUrl("/authenticate") + .successHandler(successHandler) + .failureUrl("/loginPage?error=auth") + .and() + .authorizeRequests() + .antMatchers(FindController.PUBLIC_PATH + "/**").hasAnyRole(ADMIN_ROLE, USER_ROLE) + .antMatchers("/api/public/**").hasAnyRole(ADMIN_ROLE, USER_ROLE) + .antMatchers("/api/config/**").hasRole(CONFIG_ROLE) + .antMatchers("/config/**").hasRole(CONFIG_ROLE) + .antMatchers("/api/admin/**").hasRole(ADMIN_ROLE) + .anyRequest().permitAll() + .and() + .headers() + .defaultsDisabled() + .frameOptions() + .sameOrigin(); + } + + private CommunityAuthenticationProvider communityAuthenticationProvider() { + final Role user = new Role.Builder() + .setName(IDOL_USER_ROLE) + .setPrivileges(Collections.singleton("login")) + .build(); + + final Role admin = new Role.Builder() + .setName(IDOL_ADMIN_ROLE) + .setParent(Collections.singleton(user)) + .build(); + + final Map rolesMap = ImmutableMap.builder() + .put(IDOL_USER_ROLE, role(USER_ROLE)) + .put(IDOL_ADMIN_ROLE, role(ADMIN_ROLE)) + .build(); + + final Roles roles = new Roles(Arrays.asList(admin, user)); + + return new CommunityAuthenticationProvider( + configService, + userService, + roles, + Collections.singleton("login"), + new OneToOneOrZeroSimpleAuthorityMapper(rolesMap) + ); + } + + private String role(final String applicationRole) { + return "ROLE_" + applicationRole; + } + } + + @Configuration + @Conditional(HodCondition.class) + @Order(99) + public static class HodSecurity extends WebSecurityConfigurerAdapter { + + @Autowired + private TokenRepository tokenRepository; + + @Autowired + private AuthenticationService authenticationService; + + @Autowired + private UnboundTokenService unboundTokenService; + + @Autowired + private UserStoreUsersService userStoreUsersService; + + @SuppressWarnings("ProhibitedExceptionDeclared") + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(new HodAuthenticationProvider( + tokenRepository, + "ROLE_PUBLIC", + authenticationService, + unboundTokenService, + userStoreUsersService, + HavenSearchUserMetadata.METADATA_TYPES, + usernameResolver() + )); + } + + @SuppressWarnings("ProhibitedExceptionDeclared") + @Override + protected void configure(final HttpSecurity http) throws Exception { + final AuthenticationEntryPoint ssoEntryPoint = new SsoAuthenticationEntryPoint(HodFindController.SSO_PAGE); + + final SsoAuthenticationFilter ssoAuthenticationFilter = new SsoAuthenticationFilter(HodFindController.SSO_AUTHENTICATION_URI); + ssoAuthenticationFilter.setAuthenticationManager(authenticationManager()); + + final LogoutSuccessHandler logoutSuccessHandler = new HodTokenLogoutSuccessHandler(HodFindController.SSO_LOGOUT_PAGE, tokenRepository); + + http.regexMatcher("/public/.*|/sso|/authenticate-sso|/api/authentication/.*|/logout") + .csrf() + .disable() + .exceptionHandling() + .authenticationEntryPoint(ssoEntryPoint) + .accessDeniedPage("/authentication-error") + .and() + .authorizeRequests() + .antMatchers("/public/**").hasRole("PUBLIC") + .and() + .logout() + .logoutSuccessHandler(logoutSuccessHandler) + .and() + .addFilterAfter(ssoAuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class); + } + + @Bean + public HsodUsernameResolver usernameResolver() { + return new HsodUsernameResolver(); + } + + } + + @Configuration + @Order(98) + @Conditional({HodCondition.class, InMemoryCondition.class}) + public static class InMemoryHodSecurity extends WebSecurityConfigurerAdapter { + @Autowired + private ConfigService> configService; + + @Autowired + private TokenRepository tokenRepository; + + @SuppressWarnings("ProhibitedExceptionDeclared") + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(new DefaultLoginAuthenticationProvider(configService, "ROLE_DEFAULT")); + auth.authenticationProvider(new SingleUserAuthenticationProvider(configService, "ROLE_ADMIN")); + } + + @SuppressWarnings("ProhibitedExceptionDeclared") + @Override + protected void configure(final HttpSecurity http) throws Exception { + final LoginSuccessHandler loginSuccessHandler = new LoginSuccessHandler("ROLE_DEFAULT", "/config/", "/p/"); + + http.regexMatcher("/p/.*|/config/.*|/authenticate|/logout") + .authorizeRequests() + .antMatchers("/p/**").hasRole("ADMIN") + .antMatchers("/config/**").hasRole("DEFAULT") + .and() + .formLogin() + .loginPage("/loginPage") + .loginProcessingUrl("/authenticate") + .successHandler(loginSuccessHandler) + .failureUrl("/loginPage?error=auth") + .and() + .logout() + .logoutSuccessHandler(new HodLogoutSuccessHandler(new HodTokenLogoutSuccessHandler(HodFindController.SSO_LOGOUT_PAGE, tokenRepository), "/public/")) + .and() + .csrf() + .disable(); + } + } + + + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/AppConfiguration.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/AppConfiguration.java deleted file mode 100644 index e01ea2861c..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/AppConfiguration.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.configuration; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.hp.autonomy.frontend.configuration.Authentication; -import com.hp.autonomy.frontend.configuration.BCryptUsernameAndPassword; -import com.hp.autonomy.frontend.configuration.ConfigurationFilterMixin; -import org.apache.http.HttpHost; -import org.apache.http.impl.client.HttpClientBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; - -@Configuration -public class AppConfiguration { - - @Bean - public RestTemplate restTemplate() { - final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - - httpClientBuilder - .setMaxConnPerRoute(20) - .setMaxConnTotal(120); - - final String proxyHost = System.getProperty("find.https.proxyHost"); - - if(proxyHost != null) { - final Integer proxyPort = Integer.valueOf(System.getProperty("find.https.proxyPort", "80")); - httpClientBuilder.setProxy(new HttpHost(proxyHost, proxyPort)); - } - - final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build()); - - return new RestTemplate(requestFactory); - } - - @Bean(name = "dispatcherObjectMapper") - public ObjectMapper dispatcherObjectMapper() { - final ObjectMapper mapper = new ObjectMapper(); - - mapper.addMixInAnnotations(Authentication.class, AuthenticationMixins.class); - - return mapper; - } - - @Bean(name = "contextObjectMapper") - public ObjectMapper objectMapper() { - final ObjectMapper mapper = new ObjectMapper(); - - mapper.enable(SerializationFeature.INDENT_OUTPUT); - - mapper.addMixInAnnotations(Authentication.class, AuthenticationMixins.class); - mapper.addMixInAnnotations(BCryptUsernameAndPassword.class, ConfigurationFilterMixin.class); - - return mapper; - } - -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/AutoCreatingEhCacheCacheManager.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/AutoCreatingEhCacheCacheManager.java new file mode 100644 index 0000000000..f36e1f1502 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/AutoCreatingEhCacheCacheManager.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.caching.HodApplicationCacheResolver; +import net.sf.ehcache.config.CacheConfiguration; +import org.springframework.cache.Cache; +import org.springframework.cache.ehcache.EhCacheCache; +import org.springframework.cache.ehcache.EhCacheCacheManager; + +public class AutoCreatingEhCacheCacheManager extends EhCacheCacheManager { + + private final CacheConfiguration defaults; + + public AutoCreatingEhCacheCacheManager(final net.sf.ehcache.CacheManager cacheManager, final CacheConfiguration defaults) { + super(cacheManager); + this.defaults = defaults; + } + + @Override + protected Cache getMissingCache(final String name) { + final Cache missingCache = super.getMissingCache(name); + + if(missingCache == null) { + final CacheConfiguration cacheConfiguration = defaults.clone() + .name(name); + + final String cacheName = HodApplicationCacheResolver.getOriginalName(name); + + if(CacheNames.CACHE_EXPIRES.containsKey(cacheName)) { + cacheConfiguration.setTimeToLiveSeconds(CacheNames.CACHE_EXPIRES.get(cacheName)); + } + + final net.sf.ehcache.Cache ehcache = new net.sf.ehcache.Cache(cacheConfiguration); + ehcache.initialise(); + + return new EhCacheCache(ehcache); + } + else { + return missingCache; + } + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/CommunityAciService.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/CommunityAciService.java new file mode 100644 index 0000000000..8f6c02572e --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/CommunityAciService.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.autonomy.aci.client.services.AciService; +import com.autonomy.aci.client.transport.AciServerDetails; +import com.hp.autonomy.frontend.configuration.AbstractConfigurableAciService; +import com.hp.autonomy.frontend.configuration.ConfigService; + +public class CommunityAciService extends AbstractConfigurableAciService { + + private final ConfigService configService; + + public CommunityAciService(final AciService aciService, final ConfigService configService) { + super(aciService); + + this.configService = configService; + } + + @Override + public AciServerDetails getServerDetails() { + return configService.getConfig().getCommunityDetails(); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/ContentAciService.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/ContentAciService.java new file mode 100644 index 0000000000..cfeccdc0bd --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/ContentAciService.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.autonomy.aci.client.services.AciService; +import com.autonomy.aci.client.transport.AciServerDetails; +import com.hp.autonomy.frontend.configuration.AbstractConfigurableAciService; +import com.hp.autonomy.frontend.configuration.ConfigService; + +public class ContentAciService extends AbstractConfigurableAciService { + + private final ConfigService configService; + + public ContentAciService(final AciService aciService, final ConfigService configService) { + super(aciService); + + this.configService = configService; + } + + @Override + public AciServerDetails getServerDetails() { + return configService.getConfig().getContent().toAciServerDetails(); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/FindConfig.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/FindConfig.java deleted file mode 100644 index 01f156a345..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/FindConfig.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.configuration; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.hp.autonomy.frontend.configuration.AbstractConfig; -import com.hp.autonomy.frontend.configuration.Authentication; -import com.hp.autonomy.frontend.configuration.AuthenticationConfig; -import com.hp.autonomy.frontend.configuration.ConfigException; -import com.hp.autonomy.frontend.configuration.PasswordsConfig; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import org.jasypt.util.text.TextEncryptor; - -@JsonDeserialize(builder = FindConfig.Builder.class) -@Getter -@EqualsAndHashCode(callSuper = false) -public class FindConfig extends AbstractConfig implements AuthenticationConfig, PasswordsConfig { - - private final Authentication login; - private final IodConfig iod; - - private FindConfig(final Builder builder) { - this.login = builder.login; - this.iod = builder.iod; - } - - @Override - public FindConfig merge(final FindConfig config) { - if(config != null) { - final Builder builder = new Builder(); - - builder.setLogin(this.login == null ? config.login : this.login.merge(config.login)); - builder.setIod(this.iod == null ? config.iod : this.iod.merge(config.iod)); - - return builder.build(); - } - else { - return this; - } - } - - @Override - public FindConfig withoutDefaultLogin() { - final Builder builder = new Builder(this); - - builder.login = builder.login.withoutDefaultLogin(); - - return builder.build(); - } - - @Override - public FindConfig generateDefaultLogin() { - final Builder builder = new Builder(this); - - builder.login = builder.login.generateDefaultLogin(); - - return builder.build(); - } - - @Override - public FindConfig withHashedPasswords() { - final Builder builder = new Builder(this); - - builder.login = builder.login.withHashedPasswords(); - - return builder.build(); - } - - @Override - public void basicValidate() throws ConfigException { - if(!this.login.getMethod().equalsIgnoreCase("default")){ - this.login.basicValidate(); - } - } - - @Override - public FindConfig withoutPasswords() { - final Builder builder = new Builder(this); - - builder.login = login.withoutPasswords(); - - return builder.build(); - } - - @Override - public FindConfig withEncryptedPasswords(final TextEncryptor encryptor) { - return this; - } - - @Override - public FindConfig withDecryptedPasswords(final TextEncryptor encryptor) { - return this; - } - - @Override - @JsonIgnore - public Authentication getAuthentication() { - return login; - } - - @JsonPOJOBuilder(withPrefix = "set") - @Setter - @Accessors(chain = true) - public static class Builder { - - private Authentication login; - private IodConfig iod; - - public Builder() {} - - public Builder(final FindConfig config) { - this.login = config.login; - this.iod = config.iod; - } - - public FindConfig build() { - return new FindConfig(this); - } - } - -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/FindConfigFileService.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/FindConfigFileService.java deleted file mode 100644 index fc850d9c6a..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/FindConfigFileService.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.configuration; - -import com.hp.autonomy.frontend.configuration.AbstractAuthenticatingConfigFileService; - -public class FindConfigFileService extends AbstractAuthenticatingConfigFileService { - - @Override - public FindConfig preUpdate(final FindConfig config) { - return config; - } - - @Override - public void postUpdate(final FindConfig config) throws Exception { - - } - - @Override - public void postInitialise(final FindConfig config) throws Exception { - postUpdate(config); - } - - @Override - public Class getConfigClass() { - return FindConfig.class; - } - - @Override - public FindConfig getEmptyConfig() { - return new FindConfig.Builder().build(); - } - -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/AuthenticationMixins.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodAuthenticationMixins.java similarity index 93% rename from src/main/java/com/hp/autonomy/frontend/find/configuration/AuthenticationMixins.java rename to src/main/java/com/hp/autonomy/frontend/find/configuration/HodAuthenticationMixins.java index 770071435e..e1e8f3f6fe 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/AuthenticationMixins.java +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodAuthenticationMixins.java @@ -10,5 +10,5 @@ import com.hp.autonomy.frontend.configuration.SingleUserAuthentication; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name", defaultImpl = SingleUserAuthentication.class) -public class AuthenticationMixins { +public class HodAuthenticationMixins { } diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/ConfigurationController.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodConfigurationController.java similarity index 77% rename from src/main/java/com/hp/autonomy/frontend/find/configuration/ConfigurationController.java rename to src/main/java/com/hp/autonomy/frontend/find/configuration/HodConfigurationController.java index ccf65c666a..9c91097422 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/ConfigurationController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodConfigurationController.java @@ -9,10 +9,12 @@ import com.hp.autonomy.frontend.configuration.ConfigFileService; import com.hp.autonomy.frontend.configuration.ConfigResponse; import com.hp.autonomy.frontend.configuration.ConfigValidationException; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; import com.hp.autonomy.frontend.logging.Markers; import java.util.Collections; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -24,32 +26,33 @@ @Controller @RequestMapping({"/api/useradmin/config", "/api/config/config"}) @Slf4j -public class ConfigurationController { +@Conditional(HodCondition.class) // TODO have a think about this (maybe make it a generic type and instantiate in config class) +public class HodConfigurationController { @Autowired - private ConfigFileService configService; + private ConfigFileService configService; - @RequestMapping(value = "/config", method = RequestMethod.GET) + @RequestMapping(value = "/config", method = RequestMethod.GET) @ResponseBody - public ConfigResponse config() { + public ConfigResponse config() { return configService.getConfigResponse(); } @SuppressWarnings("ProhibitedExceptionDeclared") @RequestMapping(value = "/config", method = {RequestMethod.POST, RequestMethod.PUT}) - @ResponseBody - public ResponseEntity saveConfig(@RequestBody final ConfigResponse configResponse) throws Exception { + @ResponseBody + public ResponseEntity saveConfig(@RequestBody final ConfigResponse configResponse) throws Exception { try { log.info(Markers.AUDIT, "REQUESTED CHANGE APPLICATION CONFIGURATION"); configService.updateConfig(configResponse.getConfig()); log.info(Markers.AUDIT, "CHANGED APPLICATION CONFIGURATION"); return new ResponseEntity<>(configService.getConfigResponse(), HttpStatus.OK); - } catch (ConfigException ce) { + } catch (final ConfigException ce) { log.info(Markers.AUDIT, "CHANGE APPLICATION CONFIGURATION FAILED"); return new ResponseEntity<>(Collections.singletonMap("exception", ce.getMessage()), HttpStatus.NOT_ACCEPTABLE); - } catch (ConfigValidationException cve) { + } catch (final ConfigValidationException cve) { log.info(Markers.AUDIT, "CHANGE APPLICATION CONFIGURATION FAILED"); return new ResponseEntity<>(Collections.singletonMap("validation", cve.getValidationErrors()), HttpStatus.NOT_ACCEPTABLE); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/HodFindConfig.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodFindConfig.java new file mode 100644 index 0000000000..5a7db3bc56 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodFindConfig.java @@ -0,0 +1,158 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.hp.autonomy.frontend.configuration.AbstractConfig; +import com.hp.autonomy.frontend.configuration.Authentication; +import com.hp.autonomy.frontend.configuration.AuthenticationConfig; +import com.hp.autonomy.frontend.configuration.ConfigException; +import com.hp.autonomy.frontend.configuration.PasswordsConfig; +import com.hp.autonomy.hod.sso.HodSsoConfig; +import com.hp.autonomy.frontend.configuration.RedisConfig; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jasypt.util.text.TextEncryptor; + +import java.util.Set; + +@JsonDeserialize(builder = HodFindConfig.Builder.class) +@Getter +@EqualsAndHashCode(callSuper = false) +public class HodFindConfig extends AbstractConfig implements AuthenticationConfig, PasswordsConfig, HodSsoConfig { + + private final Authentication login; + private final HsodConfig hsod; + private final IodConfig iod; + private final QueryManipulationConfig queryManipulation; + private final Set allowedOrigins; + private final RedisConfig redis; + + private HodFindConfig(final Builder builder) { + this.login = builder.login; + this.hsod = builder.hsod; + this.iod = builder.iod; + this.allowedOrigins = builder.allowedOrigins; + this.redis = builder.redis; + this.queryManipulation = builder.queryManipulation; + } + + @Override + public HodFindConfig merge(final HodFindConfig config) { + if (config != null) { + return new Builder() + .setLogin(this.login == null ? config.login : this.login.merge(config.login)) + .setIod(this.iod == null ? config.iod : this.iod.merge(config.iod)) + .setAllowedOrigins(this.allowedOrigins == null ? config.allowedOrigins : this.allowedOrigins) + .setRedis(this.redis == null ? config.redis : this.redis.merge(config.redis)) + .setQueryManipulation(queryManipulation == null ? config.queryManipulation : queryManipulation.merge(config.queryManipulation)) + .setHsod(hsod == null ? config.hsod : hsod.merge(config.hsod)) + .build(); + } else { + return this; + } + } + + @Override + public HodFindConfig withoutDefaultLogin() { + final Builder builder = new Builder(this); + + builder.login = builder.login.withoutDefaultLogin(); + + return builder.build(); + } + + @Override + public HodFindConfig generateDefaultLogin() { + final Builder builder = new Builder(this); + + builder.login = builder.login.generateDefaultLogin(); + + return builder.build(); + } + + @Override + public HodFindConfig withHashedPasswords() { + final Builder builder = new Builder(this); + + builder.login = builder.login.withHashedPasswords(); + + return builder.build(); + } + + @Override + public void basicValidate() throws ConfigException { + redis.basicValidate(); + queryManipulation.basicValidate(); + + if (!this.login.getMethod().equalsIgnoreCase("default")) { + this.login.basicValidate(); + } + } + + @Override + public HodFindConfig withoutPasswords() { + final Builder builder = new Builder(this); + + builder.login = login.withoutPasswords(); + + return builder.build(); + } + + @Override + public HodFindConfig withEncryptedPasswords(final TextEncryptor encryptor) { + return this; + } + + @Override + public HodFindConfig withDecryptedPasswords(final TextEncryptor encryptor) { + return this; + } + + @Override + @JsonIgnore + public Authentication getAuthentication() { + return login; + } + + @Override + @JsonIgnore + public String getApiKey() { + return getIod().getApiKey(); + } + + @JsonPOJOBuilder(withPrefix = "set") + @Setter + @Accessors(chain = true) + public static class Builder { + + private Authentication login; + private HsodConfig hsod; + private IodConfig iod; + private Set allowedOrigins; + private RedisConfig redis; + private QueryManipulationConfig queryManipulation; + + public Builder() {} + + public Builder(final HodFindConfig config) { + this.login = config.login; + this.hsod = config.hsod; + this.iod = config.iod; + this.allowedOrigins = config.allowedOrigins; + this.redis = config.redis; + this.queryManipulation = config.queryManipulation; + } + + public HodFindConfig build() { + return new HodFindConfig(this); + } + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/HodFindConfigFileService.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodFindConfigFileService.java new file mode 100644 index 0000000000..3279f9a6e4 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/HodFindConfigFileService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.hp.autonomy.frontend.configuration.AbstractAuthenticatingConfigFileService; + +public class HodFindConfigFileService extends AbstractAuthenticatingConfigFileService { + + @Override + public HodFindConfig preUpdate(final HodFindConfig config) { + return config; + } + + @Override + public void postUpdate(final HodFindConfig config) throws Exception { + + } + + @Override + public void postInitialise(final HodFindConfig config) throws Exception { + postUpdate(config); + } + + @Override + public Class getConfigClass() { + return HodFindConfig.class; + } + + @Override + public HodFindConfig getEmptyConfig() { + return new HodFindConfig.Builder().build(); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/HsodConfig.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/HsodConfig.java new file mode 100644 index 0000000000..d658266201 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/HsodConfig.java @@ -0,0 +1,41 @@ +package com.hp.autonomy.frontend.find.configuration; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.hp.autonomy.frontend.configuration.ConfigException; +import lombok.Data; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.net.URL; + +@Data +@JsonDeserialize(builder = HsodConfig.Builder.class) +public class HsodConfig { + private final URL landingPageUrl; + + private HsodConfig(final Builder builder) { + landingPageUrl = builder.landingPageUrl; + } + + public HsodConfig merge(final HsodConfig other) { + if (other == null) { + return this; + } + + return new Builder() + .setLandingPageUrl(landingPageUrl == null ? other.landingPageUrl : landingPageUrl) + .build(); + } + + @Setter + @JsonPOJOBuilder(withPrefix = "set") + @Accessors(chain = true) + public static class Builder { + private URL landingPageUrl; + + public HsodConfig build() { + return new HsodConfig(this); + } + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/IdolFindConfig.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/IdolFindConfig.java new file mode 100644 index 0000000000..48cbfff8e4 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/IdolFindConfig.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.autonomy.aci.client.transport.AciServerDetails; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.hp.autonomy.frontend.configuration.AbstractConfig; +import com.hp.autonomy.frontend.configuration.Authentication; +import com.hp.autonomy.frontend.configuration.AuthenticationConfig; +import com.hp.autonomy.frontend.configuration.CommunityAuthentication; +import com.hp.autonomy.frontend.configuration.ConfigException; +import com.hp.autonomy.frontend.configuration.ServerConfig; +import com.hp.autonomy.user.UserServiceConfig; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Data +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@EqualsAndHashCode(callSuper = false) +@JsonDeserialize(builder = IdolFindConfig.Builder.class) +public class IdolFindConfig extends AbstractConfig implements UserServiceConfig, AuthenticationConfig { + + private final CommunityAuthentication login; + private final ServerConfig content; + + @Override + public IdolFindConfig merge(final IdolFindConfig other) { + if (other == null) { + return this; + } + + return new IdolFindConfig.Builder() + .setContent(content == null ? other.content : content.merge(other.content)) + .setLogin(login == null ? other.login : login.merge(other.login)) + .build(); + } + + @JsonIgnore + @Override + public AciServerDetails getCommunityDetails() { + return login.getCommunity().toAciServerDetails(); + } + + @JsonIgnore + @Override + public Authentication getAuthentication() { + return login; + } + + @Override + public IdolFindConfig withoutDefaultLogin() { + return new Builder(this) + .setLogin(login.withoutDefaultLogin()) + .build(); + } + + @Override + public IdolFindConfig generateDefaultLogin() { + return new Builder(this) + .setLogin(login.generateDefaultLogin()) + .build(); + } + + @Override + public IdolFindConfig withHashedPasswords() { + // no work to do yet + return this; + } + + @Override + public void basicValidate() throws ConfigException { + login.basicValidate(); + content.basicValidate("content"); + } + + @Setter + @Accessors(chain = true) + @NoArgsConstructor + @JsonPOJOBuilder(withPrefix = "set") + public static class Builder { + private CommunityAuthentication login; + private ServerConfig content; + + public Builder(final IdolFindConfig config) { + login = config.login; + content = config.content; + } + + public IdolFindConfig build() { + return new IdolFindConfig(login, content); + } + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/IdolFindConfigFileService.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/IdolFindConfigFileService.java new file mode 100644 index 0000000000..ab570cd506 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/IdolFindConfigFileService.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.configuration; + +import com.hp.autonomy.frontend.configuration.AbstractAuthenticatingConfigFileService; + +public class IdolFindConfigFileService extends AbstractAuthenticatingConfigFileService { + @Override + public void postInitialise(final IdolFindConfig config) throws Exception { + + } + + @Override + public Class getConfigClass() { + return IdolFindConfig.class; + } + + @Override + public IdolFindConfig getEmptyConfig() { + return new IdolFindConfig.Builder().build(); + } + + @Override + public IdolFindConfig preUpdate(final IdolFindConfig config) { + return config; + } + + @Override + public void postUpdate(final IdolFindConfig config) throws Exception { + + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfig.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfig.java index bdeb9f6858..bed6fd7f85 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfig.java +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfig.java @@ -10,25 +10,42 @@ import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.hp.autonomy.frontend.configuration.ConfigurationComponent; import com.hp.autonomy.frontend.configuration.ValidationResult; -import com.hp.autonomy.frontend.find.search.Index; -import com.hp.autonomy.frontend.find.search.Indexes; import com.hp.autonomy.frontend.find.search.IndexesService; -import java.util.List; +import com.hp.autonomy.hod.client.api.authentication.ApiKey; +import com.hp.autonomy.hod.client.api.authentication.AuthenticationService; +import com.hp.autonomy.hod.client.api.authentication.EntityType; +import com.hp.autonomy.hod.client.api.authentication.TokenType; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.resource.Resources; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.client.token.TokenProxy; import lombok.Data; import lombok.Setter; import org.apache.commons.lang.StringUtils; -import org.springframework.web.client.RestClientException; + +import java.util.ArrayList; +import java.util.List; @Data @JsonDeserialize(builder = IodConfig.Builder.class) public class IodConfig implements ConfigurationComponent { private final String apiKey; - private final List activeIndexes; + private final String application; + private final String domain; + private final List activeIndexes; + private final Boolean publicIndexesEnabled; - private IodConfig(final String apiKey, final List activeIndexes) { + private IodConfig(final String apiKey, final String application, final String domain, final List activeIndexes, final Boolean publicIndexesEnabled) { this.apiKey = apiKey; + this.application = application; + this.domain = domain; this.activeIndexes = activeIndexes; + this.publicIndexesEnabled = publicIndexesEnabled; + } + + public List getActiveIndexes() { + return new ArrayList<>(activeIndexes); } @Override @@ -37,19 +54,33 @@ public boolean isEnabled() { return true; } - public ValidationResult validate(final IndexesService indexesService) { + public ValidationResult validate(final IndexesService indexesService, final AuthenticationService authenticationService) { try { if(StringUtils.isBlank(apiKey)) { return new ValidationResult<>(false, "API Key is blank"); } - final Indexes indexes = indexesService.listIndexes(apiKey); - final List activeIndexes = indexesService.listActiveIndexes(); + if(StringUtils.isBlank(apiKey)) { + return new ValidationResult<>(false, "Application is blank"); + } + + if(StringUtils.isBlank(apiKey)) { + return new ValidationResult<>(false, "Domain is blank"); + } + + final TokenProxy tokenProxy = authenticationService.authenticateApplication( + new ApiKey(apiKey), + application, + domain, + TokenType.Simple.INSTANCE + ); + + final Resources indexes = indexesService.listIndexes(tokenProxy); + final List activeIndexes = indexesService.listActiveIndexes(); return new ValidationResult<>(true, new IndexResponse(indexes, activeIndexes)); - } catch (RestClientException e) { - // TODO better handling of IOD errors - return new ValidationResult<>(false, "Invalid API Key"); + } catch (final HodErrorException e) { + return new ValidationResult<>(false, "Unable to list indexes"); } } @@ -58,7 +89,10 @@ public IodConfig merge(final IodConfig iod) { final Builder builder = new Builder(); builder.setApiKey(this.apiKey == null ? iod.apiKey : this.apiKey); + builder.setApplication(this.application == null ? iod.application : this.application); + builder.setDomain(this.domain == null ? iod.domain : this.domain); builder.setActiveIndexes(this.activeIndexes == null ? iod.activeIndexes : this.activeIndexes); + builder.setPublicIndexesEnabled(this.publicIndexesEnabled == null ? iod.publicIndexesEnabled : this.publicIndexesEnabled); return builder.build(); } @@ -71,19 +105,22 @@ public IodConfig merge(final IodConfig iod) { @JsonPOJOBuilder(withPrefix = "set") public static class Builder { private String apiKey; - private List activeIndexes; + private String application; + private String domain; + private List activeIndexes; + private Boolean publicIndexesEnabled; public IodConfig build() { - return new IodConfig(apiKey, activeIndexes); + return new IodConfig(apiKey, application, domain, activeIndexes, publicIndexesEnabled); } } @Data private static class IndexResponse { - private final Indexes indexes; - private final List activeIndexes; + private final Resources indexes; + private final List activeIndexes; - private IndexResponse(final Indexes indexes, final List activeIndexes) { + private IndexResponse(final Resources indexes, final List activeIndexes) { this.indexes = indexes; this.activeIndexes = activeIndexes; } diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfigValidator.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfigValidator.java index 81ccb79d17..ec22b2a99a 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfigValidator.java +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/IodConfigValidator.java @@ -8,6 +8,7 @@ import com.hp.autonomy.frontend.configuration.ValidationResult; import com.hp.autonomy.frontend.configuration.Validator; import com.hp.autonomy.frontend.find.search.IndexesService; +import com.hp.autonomy.hod.client.api.authentication.AuthenticationService; import org.springframework.beans.factory.annotation.Autowired; public class IodConfigValidator implements Validator { @@ -15,9 +16,12 @@ public class IodConfigValidator implements Validator { @Autowired private IndexesService indexesService; + @Autowired + private AuthenticationService authenticationService; + @Override public ValidationResult validate(final IodConfig iodConfig) { - return iodConfig.validate(indexesService); + return iodConfig.validate(indexesService, authenticationService); } @Override diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/QueryManipulationConfig.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/QueryManipulationConfig.java new file mode 100644 index 0000000000..ab0e72de4c --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/QueryManipulationConfig.java @@ -0,0 +1,43 @@ +package com.hp.autonomy.frontend.find.configuration; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.hp.autonomy.frontend.configuration.ConfigException; +import lombok.Data; +import org.apache.commons.lang.StringUtils; + +@Data +public class QueryManipulationConfig { + public static final String SECTION = "queryManipulation"; + + private final String profile; + private final String index; + + QueryManipulationConfig( + @JsonProperty("profile") final String profile, + @JsonProperty("index") final String index + ) { + this.profile = profile; + this.index = index; + } + + public QueryManipulationConfig merge(final QueryManipulationConfig other) { + if (other == null) { + return this; + } + + return new QueryManipulationConfig( + profile == null ? other.profile : profile, + index == null ? other.index : index + ); + } + + public void basicValidate() throws ConfigException { + if (StringUtils.isBlank(profile)) { + throw new ConfigException(SECTION, "Query profile is required"); + } + + if (StringUtils.isBlank(index)) { + throw new ConfigException(SECTION, "Query manipulation index is required"); + } + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/TextEncryptorPasswordFactory.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/TextEncryptorPasswordFactory.java index 91e2df8d32..c3f3858059 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/TextEncryptorPasswordFactory.java +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/TextEncryptorPasswordFactory.java @@ -29,4 +29,4 @@ public Class getObjectType() { public boolean isSingleton() { return true; } -} \ No newline at end of file +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/configuration/ValidationController.java b/src/main/java/com/hp/autonomy/frontend/find/configuration/ValidationController.java index 246c6ccf80..b7110129cb 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/configuration/ValidationController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/configuration/ValidationController.java @@ -7,7 +7,9 @@ import com.hp.autonomy.frontend.configuration.ValidationResults; import com.hp.autonomy.frontend.configuration.ValidationService; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,15 +18,15 @@ @Controller @RequestMapping({"/api/useradmin/config", "/api/config/config"}) +@Conditional(HodCondition.class) // TODO make this a generic type public class ValidationController { @Autowired - private ValidationService validationService; + private ValidationService validationService; @RequestMapping(value = "/config-validation", method = {RequestMethod.POST, RequestMethod.PUT}) @ResponseBody - public ValidationResults validConfig(@RequestBody final FindConfig config){ + public ValidationResults validConfig(@RequestBody final HodFindConfig config){ return validationService.validateConfig(config); } - } diff --git a/src/main/java/com/hp/autonomy/frontend/find/converter/StringToResourceIdentifierConverter.java b/src/main/java/com/hp/autonomy/frontend/find/converter/StringToResourceIdentifierConverter.java new file mode 100644 index 0000000000..11ba0db41d --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/converter/StringToResourceIdentifierConverter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.converter; + +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import org.springframework.core.convert.converter.Converter; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +public class StringToResourceIdentifierConverter implements Converter { + @Override + public ResourceIdentifier convert(final String s) { + final String[] parts = s.split(":"); + + return new ResourceIdentifier(decodeUriComponent(parts[0]), decodeUriComponent(parts[1])); + } + + private String decodeUriComponent(final String part) { + try { + return URLDecoder.decode(part, "UTF-8"); + } catch (final UnsupportedEncodingException e) { + throw new AssertionError("All JVMs must support UTF-8", e); + } + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/parametricfields/CacheableIndexFieldsService.java b/src/main/java/com/hp/autonomy/frontend/find/parametricfields/CacheableIndexFieldsService.java new file mode 100644 index 0000000000..c70ff4a672 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/parametricfields/CacheableIndexFieldsService.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.parametricfields; + +import com.hp.autonomy.fields.IndexFieldsService; +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.error.HodErrorException; +import org.springframework.cache.annotation.Cacheable; + +import java.util.Set; + +public class CacheableIndexFieldsService implements IndexFieldsService { + + private final IndexFieldsService indexFieldsService; + + public CacheableIndexFieldsService(final IndexFieldsService indexFieldsService) { + this.indexFieldsService = indexFieldsService; + } + + @Override + @Cacheable(CacheNames.PARAMETRIC_FIELDS) + public Set getParametricFields(final ResourceIdentifier index) throws HodErrorException { + return indexFieldsService.getParametricFields(index); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/parametricfields/CacheableParametricValuesService.java b/src/main/java/com/hp/autonomy/frontend/find/parametricfields/CacheableParametricValuesService.java new file mode 100644 index 0000000000..4ebe77a457 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/parametricfields/CacheableParametricValuesService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.parametricfields; + +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.parametricvalues.ParametricFieldName; +import com.hp.autonomy.parametricvalues.ParametricRequest; +import com.hp.autonomy.parametricvalues.ParametricValuesService; +import org.springframework.cache.annotation.Cacheable; + +import java.util.Set; + +public class CacheableParametricValuesService implements ParametricValuesService { + + private final ParametricValuesService parametricValuesService; + + public CacheableParametricValuesService(final ParametricValuesService parametricValuesService) { + this.parametricValuesService = parametricValuesService; + } + + @Override + @Cacheable(CacheNames.PARAMETRIC_VALUES) + public Set getAllParametricValues(final ParametricRequest parametricRequest) throws HodErrorException { + return parametricValuesService.getAllParametricValues(parametricRequest); + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/parametricfields/ParametricValuesController.java b/src/main/java/com/hp/autonomy/frontend/find/parametricfields/ParametricValuesController.java new file mode 100644 index 0000000000..89ef2cc944 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/parametricfields/ParametricValuesController.java @@ -0,0 +1,55 @@ +package com.hp.autonomy.frontend.find.parametricfields; + +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.sso.HodAuthentication; +import com.hp.autonomy.parametricvalues.ParametricFieldName; +import com.hp.autonomy.parametricvalues.ParametricRequest; +import com.hp.autonomy.parametricvalues.ParametricValuesService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Set; + +@Controller +@RequestMapping("/api/public/parametric") +@Conditional(HodCondition.class) +public class ParametricValuesController { + + @Autowired + private ParametricValuesService parametricValuesService; + + @Autowired + private ConfigService configService; + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Set getParametricValues( + @RequestParam("databases") final Set databases, + @RequestParam(value = "fieldNames", required = false) final Set fieldNames, + @RequestParam("queryText") final String queryText, + @RequestParam("fieldText") final String fieldText + ) throws HodErrorException { + final String profileName = configService.getConfig().getQueryManipulation().getProfile(); + final String domain = ((HodAuthentication) SecurityContextHolder.getContext().getAuthentication()).getPrincipal().getApplication().getDomain(); + + final ParametricRequest parametricRequest = new ParametricRequest.Builder() + .setQueryProfile(new ResourceIdentifier(domain, profileName)) + .setDatabases(databases) + .setFieldNames(fieldNames) + .setQuery(queryText) + .setFieldText(fieldText) + .build(); + + return parametricValuesService.getAllParametricValues(parametricRequest); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/Document.java b/src/main/java/com/hp/autonomy/frontend/find/search/Document.java deleted file mode 100644 index 3d8487fe5b..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/Document.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.Data; -import lombok.Setter; - -import java.util.List; - -@Data -@JsonDeserialize(builder = Document.Builder.class) -public class Document { - - private final String reference; - private final double weight; - private final List links; - private final String indexes; - private final String title; - private final String summary; - - private Document(final String reference, final double weight, final List links, final String indexes, final String title, final String summary) { - this.reference = reference; - this.weight = weight; - this.links = links; - this.indexes = indexes; - this.title = title; - this.summary = summary; - } - - @Setter - @JsonPOJOBuilder(withPrefix = "set") - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Builder { - - private String reference; - private double weight; - private List links; - private String indexes; - private String title; - - @SuppressWarnings("FieldMayBeFinal") - private String summary = ""; - - public Document build() { - return new Document(reference, weight, links, indexes, title, summary); - } - - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/Documents.java b/src/main/java/com/hp/autonomy/frontend/find/search/Documents.java deleted file mode 100644 index 101ceba5df..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/Documents.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Setter; - -import java.util.List; - -@Data -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@JsonDeserialize(builder = Documents.Builder.class) -public class Documents { - - private final List documents; - - @Setter - @JsonPOJOBuilder(withPrefix = "set") - public static class Builder { - - private List documents; - - public Documents build() { - return new Documents(documents); - } - - } - -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsController.java b/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsController.java index 2ce42848b4..e2e1fba8e0 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsController.java @@ -5,7 +5,16 @@ package com.hp.autonomy.frontend.find.search; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Documents; +import com.hp.autonomy.hod.client.api.textindex.query.search.Sort; +import com.hp.autonomy.hod.client.api.textindex.query.search.Summary; +import com.hp.autonomy.hod.client.error.HodErrorException; +import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -15,21 +24,40 @@ import java.util.List; @Controller -@RequestMapping("/api/search/query-text-index") +@RequestMapping("/api/public/search/query-text-index") +@Conditional(HodCondition.class) // TODO remove this public class DocumentsController { @Autowired private DocumentsService documentsService; - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(value = "results", method = RequestMethod.GET) @ResponseBody - public List query( + public Documents query( @RequestParam("text") final String text, @RequestParam("max_results") final int maxResults, - @RequestParam("summary") final String summary, - @RequestParam("index") final String index - ) { - return documentsService.queryTextIndex(text, maxResults, summary, index); + @RequestParam("summary") final Summary summary, + @RequestParam("index") final List index, + @RequestParam(value = "field_text", defaultValue = "") final String fieldText, + @RequestParam(value = "sort", required = false) final Sort sort, + @RequestParam(value = "min_date", required = false) @DateTimeFormat(iso=DateTimeFormat.ISO.DATE_TIME) final DateTime minDate, + @RequestParam(value = "max_date", required = false) @DateTimeFormat(iso=DateTimeFormat.ISO.DATE_TIME) final DateTime maxDate + ) throws HodErrorException { + return documentsService.queryTextIndex(text, maxResults, summary, index, fieldText, sort, minDate, maxDate); } + @RequestMapping(value="promotions", method = RequestMethod.GET) + @ResponseBody + public Documents queryForPromotions( + @RequestParam("text") final String text, + @RequestParam("max_results") final int maxResults, + @RequestParam("summary") final Summary summary, + @RequestParam("index") final List index, + @RequestParam("field_text") final String fieldText, + @RequestParam(value = "sort", required = false) final Sort sort, + @RequestParam(value = "min_date", required = false) @DateTimeFormat(iso=DateTimeFormat.ISO.DATE_TIME) final DateTime minDate, + @RequestParam(value = "max_date", required = false) @DateTimeFormat(iso=DateTimeFormat.ISO.DATE_TIME) final DateTime maxDate + ) throws HodErrorException { + return documentsService.queryTextIndexForPromotions(text, maxResults, summary, index, fieldText, sort, minDate, maxDate); + } } diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsService.java b/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsService.java index f4c8b123ef..b27e9e8418 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsService.java +++ b/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsService.java @@ -5,10 +5,19 @@ package com.hp.autonomy.frontend.find.search; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Documents; +import com.hp.autonomy.hod.client.api.textindex.query.search.Sort; +import com.hp.autonomy.hod.client.api.textindex.query.search.Summary; +import com.hp.autonomy.hod.client.error.HodErrorException; +import org.joda.time.DateTime; + import java.util.List; public interface DocumentsService { - List queryTextIndex(String text, int maxResults, String summary, String indexes); + Documents queryTextIndex(String text, int maxResults, Summary summary, List indexes, String fieldText, Sort sort, DateTime minDate, DateTime maxDate) throws HodErrorException; + + Documents queryTextIndexForPromotions(String text, int maxResults, Summary summary, List indexes, String fieldText, Sort sort, DateTime minDate, DateTime maxDate) throws HodErrorException; } diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsServiceImpl.java b/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsServiceImpl.java deleted file mode 100644 index 1d8c94ae4d..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/DocumentsServiceImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.hp.autonomy.frontend.find.ApiKeyService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Service -public class DocumentsServiceImpl implements DocumentsService { - - @Autowired - private RestTemplate restTemplate; - - @Autowired - private ApiKeyService apiKeyService; - - @Override - public List queryTextIndex(final String text, final int maxResults, final String summary, final String indexes) { - final Map parameters = new HashMap<>(); - parameters.put("text", text); - parameters.put("max_results", maxResults); - parameters.put("summary", summary); - parameters.put("indexes", indexes); - parameters.put("apikey", apiKeyService.getApiKey()); - - return restTemplate.getForObject("https://api.idolondemand.com/1/api/sync/querytextindex/v1?apikey={apikey}&max_results={max_results}&text={text}&summary={summary}&indexes={indexes}", Documents.class, parameters).getDocuments(); - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/Entities.java b/src/main/java/com/hp/autonomy/frontend/find/search/Entities.java deleted file mode 100644 index db3e0d3eb6..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/Entities.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Setter; - -import java.util.List; - -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Data -@JsonDeserialize(builder = Entities.Builder.class) -public class Entities { - - private final List entities; - - @Setter - @JsonPOJOBuilder(withPrefix = "set") - public static class Builder { - - private List entities; - - public Entities build() { - return new Entities(entities); - } - - } - -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/Entity.java b/src/main/java/com/hp/autonomy/frontend/find/search/Entity.java deleted file mode 100644 index 27df2c9f67..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/Entity.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.Data; -import lombok.Setter; - -@Data -@JsonDeserialize(builder = Entity.Builder.class) -public class Entity { - - private final String text; - private final int docsWithPhrase; - private final int occurrences; - private final int docsWithAllTerms; - private final int cluster; - - private Entity(final String text, final int docsWithPhrase, final int occurrences, final int docsWithAllTerms, final int cluster) { - this.text = text; - this.docsWithPhrase = docsWithPhrase; - this.occurrences = occurrences; - this.docsWithAllTerms = docsWithAllTerms; - this.cluster = cluster; - } - - @Setter - @JsonPOJOBuilder(withPrefix = "set") - public static class Builder { - private String text; - private int docsWithPhrase; - private int occurrences; - private int docsWithAllTerms; - private int cluster; - - public Entity build() { - return new Entity(text, docsWithPhrase, occurrences, docsWithAllTerms, cluster); - } - - @JsonProperty("docs_with_phrase") - public void setDocsWithPhrase(final int docsWithPhrase) { - this.docsWithPhrase = docsWithPhrase; - } - - @JsonProperty("docs_with_all_terms") - public void setDocsWithAllTerms(final int docsWithAllTerms) { - this.docsWithAllTerms = docsWithAllTerms; - } - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/HodDocumentsService.java b/src/main/java/com/hp/autonomy/frontend/find/search/HodDocumentsService.java new file mode 100644 index 0000000000..8b4428b93d --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/search/HodDocumentsService.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.search; + +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Documents; +import com.hp.autonomy.hod.client.api.textindex.query.search.Print; +import com.hp.autonomy.hod.client.api.textindex.query.search.QueryRequestBuilder; +import com.hp.autonomy.hod.client.api.textindex.query.search.QueryTextIndexService; +import com.hp.autonomy.hod.client.api.textindex.query.search.Sort; +import com.hp.autonomy.hod.client.api.textindex.query.search.Summary; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.sso.HodAuthentication; +import org.joda.time.DateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Conditional; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; + +@Service +@Conditional(HodCondition.class) +public class HodDocumentsService implements DocumentsService { + @Autowired + private ConfigService configService; + + @Autowired + private QueryTextIndexService queryTextIndexService; + + @Override + @Cacheable(CacheNames.DOCUMENTS) + public Documents queryTextIndex(final String text, final int maxResults, final Summary summary, final List indexes, final String fieldText, final Sort sort, final DateTime minDate, final DateTime maxDate) throws HodErrorException { + return queryTextIndex(text, maxResults, summary, indexes, fieldText, sort, minDate, maxDate, false); + } + + @Override + @Cacheable(CacheNames.PROMOTED_DOCUMENTS) + public Documents queryTextIndexForPromotions(final String text, final int maxResults, final Summary summary, final List indexes, final String fieldText , final Sort sort, final DateTime minDate, final DateTime maxDate) throws HodErrorException { + return queryTextIndex(text, maxResults, summary, indexes, fieldText, sort, minDate, maxDate, true); + } + + private Documents queryTextIndex(final String text, final int maxResults, final Summary summary, final List indexes, final String fieldText, final Sort sort, final DateTime minDate, final DateTime maxDate, final boolean doPromotions) throws HodErrorException { + final String domain = ((HodAuthentication) SecurityContextHolder.getContext().getAuthentication()).getPrincipal().getApplication().getDomain(); + final String profileName = configService.getConfig().getQueryManipulation().getProfile(); + + final QueryRequestBuilder params = new QueryRequestBuilder() + .setAbsoluteMaxResults(maxResults) + .setSummary(summary) + .setIndexes(indexes) + .setFieldText(fieldText) + .setQueryProfile(new ResourceIdentifier(domain, profileName)) + .setSort(sort) + .setMinDate(minDate) + .setMaxDate(maxDate) + .setPromotions(doPromotions) + .setPrint(Print.fields) + .setPrintFields(Arrays.asList("url", "offset", "content_type", "date")); // Need these fields for audio playback + + return queryTextIndexService.queryTextIndexWithText(text, params); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/HodIndexesService.java b/src/main/java/com/hp/autonomy/frontend/find/search/HodIndexesService.java new file mode 100644 index 0000000000..60cfcbb741 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/search/HodIndexesService.java @@ -0,0 +1,125 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.search; + +import com.hp.autonomy.databases.Database; +import com.hp.autonomy.databases.DatabasesService; +import com.hp.autonomy.fields.IndexFieldsService; +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.client.api.authentication.TokenType; +import com.hp.autonomy.hod.client.api.resource.ListResourcesRequestBuilder; +import com.hp.autonomy.hod.client.api.resource.Resource; +import com.hp.autonomy.hod.client.api.resource.ResourceFlavour; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.resource.ResourceType; +import com.hp.autonomy.hod.client.api.resource.Resources; +import com.hp.autonomy.hod.client.api.resource.ResourcesService; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.client.token.TokenProxy; +import com.hp.autonomy.hod.sso.HodAuthentication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Conditional; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +@Service +@Conditional(HodCondition.class) +public class HodIndexesService implements IndexesService { + + private static final Set FLAVOURS_TO_REMOVE = ResourceFlavour.of(ResourceFlavour.QUERY_MANIPULATION, ResourceFlavour.CATEGORIZATION); + + @Autowired + private ConfigService configService; + + @Autowired + private ResourcesService resourcesService; + + @Autowired + private IndexFieldsService indexFieldsService; + + @Autowired + private DatabasesService databasesService; + + @Override + @Cacheable(CacheNames.INDEXES) // TODO: the caching here doesn't work from the settings page + public Resources listIndexes(final TokenProxy tokenProxy) throws HodErrorException { + final Set types = new HashSet<>(); + types.add(ResourceType.CONTENT); + + final ListResourcesRequestBuilder params = new ListResourcesRequestBuilder() + .setTypes(types); + + final Resources indexes = resourcesService.list(tokenProxy, params); + + final Iterator iterator = indexes.getResources().iterator(); + + while (iterator.hasNext()) { + final Resource i = iterator.next(); + + if (FLAVOURS_TO_REMOVE.contains(i.getFlavour())) { + iterator.remove(); + } + } + + return indexes; + } + + @Override + public List listActiveIndexes() { + return configService.getConfig().getIod().getActiveIndexes(); + } + + @Override + @Cacheable(value = CacheNames.VISIBLE_INDEXES, key = "#root.methodName") + public List listVisibleIndexes() throws HodErrorException { + final List activeIndexes = configService.getConfig().getIod().getActiveIndexes(); + + if(activeIndexes.isEmpty()) { + final HodAuthentication auth = (HodAuthentication) SecurityContextHolder.getContext().getAuthentication(); + final String domain = auth.getPrincipal().getApplication().getDomain(); + + final Set validDatabases; + final Set allDatabases = databasesService.getDatabases(domain); + + if (configService.getConfig().getIod().getPublicIndexesEnabled()) { + validDatabases = allDatabases; + } else { + validDatabases = new HashSet<>(); + + for (final Database database : allDatabases) { + if (!database.isPublic()) { + validDatabases.add(database); + } + } + } + + return new ArrayList<>(validDatabases); + } + else { + final List activeDatabases = new ArrayList<>(); + + for(final ResourceIdentifier index: activeIndexes) { + activeDatabases.add(new Database.Builder() + .setDomain(index.getDomain()) + .setName(index.getName()) + .setIndexFields(indexFieldsService.getParametricFields(index)) + .build()); + } + + return activeDatabases; + } + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/HodRelatedConceptsService.java b/src/main/java/com/hp/autonomy/frontend/find/search/HodRelatedConceptsService.java new file mode 100644 index 0000000000..793d5dad28 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/search/HodRelatedConceptsService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.search; + +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Entity; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindRelatedConceptsRequestBuilder; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindRelatedConceptsService; +import com.hp.autonomy.hod.client.error.HodErrorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Conditional(HodCondition.class) +public class HodRelatedConceptsService implements RelatedConceptsService { + + @Autowired + private FindRelatedConceptsService findRelatedConceptsService; + + @Override + @Cacheable(CacheNames.RELATED_CONCEPTS) + public List findRelatedConcepts(final String text, final List indexes, final String fieldText) throws HodErrorException { + + final FindRelatedConceptsRequestBuilder params = new FindRelatedConceptsRequestBuilder() + .setIndexes(indexes) + .setFieldText(fieldText); + + return findRelatedConceptsService.findRelatedConceptsWithText(text, params); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/Index.java b/src/main/java/com/hp/autonomy/frontend/find/search/Index.java deleted file mode 100644 index 7941e9563e..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/Index.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.Data; -import lombok.Setter; -import lombok.experimental.Accessors; - -@Data -@JsonDeserialize(builder = Index.Builder.class) -public class Index { - private final String index; - private final String type; - - private Index(final String index, final String type) { - this.index = index; - this.type = type; - } - - @Setter - @Accessors(chain = true) - @JsonPOJOBuilder(withPrefix = "set") - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Builder { - - private String index; - private String type; - - public Index build() { - return new Index(index, type); - } - - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/Indexes.java b/src/main/java/com/hp/autonomy/frontend/find/search/Indexes.java deleted file mode 100644 index f0d0b4e45c..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/Indexes.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import java.util.ArrayList; -import java.util.Collections; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Setter; - -import java.util.List; - -@Data -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@JsonDeserialize(builder = Indexes.Builder.class) -public class Indexes { - - private final List publicIndexes; - private final List privateIndexes; - - public List getPrivateIndexes() { - return new ArrayList<>(privateIndexes); - } - - public List getPublicIndexes() { - return new ArrayList<>(publicIndexes); - } - - @Setter - @JsonPOJOBuilder(withPrefix = "set") - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Builder { - - @JsonProperty("public_index") - private List publicIndex = Collections.emptyList(); - - @JsonProperty("index") - private List privateIndexes = Collections.emptyList(); - - public Indexes build() { - return new Indexes(publicIndex, privateIndexes); - } - - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/IndexesService.java b/src/main/java/com/hp/autonomy/frontend/find/search/IndexesService.java index 8099758151..6ec72593d6 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/search/IndexesService.java +++ b/src/main/java/com/hp/autonomy/frontend/find/search/IndexesService.java @@ -5,15 +5,20 @@ package com.hp.autonomy.frontend.find.search; +import com.hp.autonomy.databases.Database; +import com.hp.autonomy.hod.client.api.authentication.TokenType; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.resource.Resources; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.client.token.TokenProxy; + import java.util.List; public interface IndexesService { - Indexes listIndexes(); - - Indexes listIndexes(String apiKey); + Resources listIndexes(TokenProxy tokenProxy) throws HodErrorException; - List listActiveIndexes(); + List listActiveIndexes(); - List listVisibleIndexes(); + List listVisibleIndexes() throws HodErrorException; } diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/IndexesServiceImpl.java b/src/main/java/com/hp/autonomy/frontend/find/search/IndexesServiceImpl.java deleted file mode 100644 index bbe73da4f0..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/IndexesServiceImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.hp.autonomy.frontend.configuration.ConfigService; -import com.hp.autonomy.frontend.find.ApiKeyService; -import com.hp.autonomy.frontend.find.configuration.FindConfig; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -@Service -public class IndexesServiceImpl implements IndexesService { - - @Autowired - private RestTemplate restTemplate; - - @Autowired - private ApiKeyService apiKeyService; - - @Autowired - private ConfigService configService; - - @Override - public Indexes listIndexes() { - return listIndexes(apiKeyService.getApiKey()); - } - - @Override - public Indexes listIndexes(final String apiKey) { - final Map parameters = new HashMap<>(); - parameters.put("apikey", apiKey); - - return restTemplate.getForObject("https://api.idolondemand.com/1/api/sync/listindexes/v1?apikey={apikey}", Indexes.class, parameters); - } - - @Override - public List listActiveIndexes() { - return configService.getConfig().getIod().getActiveIndexes(); - } - - @Override - public List listVisibleIndexes() { - final List activeIndexes = configService.getConfig().getIod().getActiveIndexes(); - - if(activeIndexes.isEmpty()) { - final Indexes indexes = listIndexes(); - final List mergedIndexes = indexes.getPublicIndexes(); - - for(final PrivateIndex privateIndex : indexes.getPrivateIndexes()) { - mergedIndexes.add(privateIndex.toIndex()); - } - - return mergedIndexes; - } - else { - return activeIndexes; - } - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/ListIndexesController.java b/src/main/java/com/hp/autonomy/frontend/find/search/ListIndexesController.java index 81d5cd6519..cf017b69d6 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/search/ListIndexesController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/search/ListIndexesController.java @@ -5,23 +5,28 @@ package com.hp.autonomy.frontend.find.search; +import com.hp.autonomy.databases.Database; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.hod.client.error.HodErrorException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; + import java.util.List; @Controller +@Conditional(HodCondition.class) // TODO remove this public class ListIndexesController { @Autowired private IndexesService indexesService; - @RequestMapping(value = "/api/search/list-indexes", method = RequestMethod.GET) + @RequestMapping(value = "/api/public/search/list-indexes", method = RequestMethod.GET) @ResponseBody - public List listActiveIndexes() { + public List listActiveIndexes() throws HodErrorException { return indexesService.listVisibleIndexes(); } - -} \ No newline at end of file +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/PrivateIndex.java b/src/main/java/com/hp/autonomy/frontend/find/search/PrivateIndex.java deleted file mode 100644 index 52090e7dce..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/PrivateIndex.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.Data; -import lombok.Setter; -import lombok.experimental.Accessors; - -@Data -@JsonDeserialize(builder = PrivateIndex.Builder.class) -public class PrivateIndex { - - private final String flavor; - private final String index; - private final String type; - private final int numComponents; - private final String subType; - private final String description; - - // this should probably be a Joda DateTime but parsing the IOD output seems non trivial and at the moment we don't need it - private final String dateCreated; - - private PrivateIndex(final Builder builder) { - this.flavor = builder.flavor; - this.index = builder.index; - this.type = builder.type; - this.numComponents = builder.numComponents; - this.dateCreated = builder.dateCreated; - this.subType = builder.subType; - this.description = builder.description; - } - - public Index toIndex() { - return new Index.Builder() - .setIndex(index) - .setType(type) - .build(); - } - - @Setter - @Accessors(chain = true) - @JsonPOJOBuilder(withPrefix = "set") - public static class Builder { - - private String flavor; - private String index; - private String type; - private String description; - - @JsonProperty("subtype") - private String subType; - - @JsonProperty("num_components") - private int numComponents; - - @JsonProperty("date_created") - private String dateCreated; - - public PrivateIndex build() { - return new PrivateIndex(this); - } - - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsController.java b/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsController.java index ce1d367074..818e781e7d 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsController.java +++ b/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsController.java @@ -5,7 +5,12 @@ package com.hp.autonomy.frontend.find.search; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Entity; +import com.hp.autonomy.hod.client.error.HodErrorException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -15,7 +20,8 @@ import java.util.List; @Controller -@RequestMapping("/api/search/find-related-concepts") +@RequestMapping("/api/public/search/find-related-concepts") +@Conditional(HodCondition.class) // TODO remove this public class RelatedConceptsController { @Autowired @@ -25,9 +31,9 @@ public class RelatedConceptsController { @ResponseBody public List findRelatedConcepts( @RequestParam("text") final String text, - @RequestParam(value = "index") final String index - ) { - return relatedConceptsService.findRelatedConcepts(text, index); + @RequestParam("index") final List index, + @RequestParam("field_text") final String fieldText + ) throws HodErrorException { + return relatedConceptsService.findRelatedConcepts(text, index, fieldText); } - } diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsService.java b/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsService.java index e297174d86..6ede59c2ff 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsService.java +++ b/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsService.java @@ -5,10 +5,14 @@ package com.hp.autonomy.frontend.find.search; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Entity; +import com.hp.autonomy.hod.client.error.HodErrorException; + import java.util.List; public interface RelatedConceptsService { - public List findRelatedConcepts(String text, String indexes); + List findRelatedConcepts(String text, List indexes, String fieldText) throws HodErrorException; } diff --git a/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsServiceImpl.java b/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsServiceImpl.java deleted file mode 100644 index b02b1c0d60..0000000000 --- a/src/main/java/com/hp/autonomy/frontend/find/search/RelatedConceptsServiceImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.hp.autonomy.frontend.find.ApiKeyService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Service -public class RelatedConceptsServiceImpl implements RelatedConceptsService { - - @Autowired - private RestTemplate restTemplate; - - @Autowired - private ApiKeyService apiKeyService; - - @Override - public List findRelatedConcepts(final String text, final String indexes) { - final Map parameters = new HashMap<>(); - parameters.put("text", text); - parameters.put("indexes", indexes); - parameters.put("apikey", apiKeyService.getApiKey()); - - return restTemplate.getForObject("https://api.idolondemand.com/1/api/sync/findrelatedconcepts/v1?apikey={apikey}&text={text}&indexes={indexes}", Entities.class, parameters).getEntities(); - } -} diff --git a/src/main/java/com/hp/autonomy/frontend/find/similar/HodSimilarDocumentsService.java b/src/main/java/com/hp/autonomy/frontend/find/similar/HodSimilarDocumentsService.java new file mode 100644 index 0000000000..87ed3770e6 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/similar/HodSimilarDocumentsService.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.similar; + +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.frontend.find.web.CacheNames; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Document; +import com.hp.autonomy.hod.client.api.textindex.query.search.Documents; +import com.hp.autonomy.hod.client.api.textindex.query.search.FindSimilarService; +import com.hp.autonomy.hod.client.api.textindex.query.search.Print; +import com.hp.autonomy.hod.client.api.textindex.query.search.QueryRequestBuilder; +import com.hp.autonomy.hod.client.api.textindex.query.search.Summary; +import com.hp.autonomy.hod.client.error.HodErrorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; + +@Service +@Conditional(HodCondition.class) +public class HodSimilarDocumentsService implements SimilarDocumentsService { + private static final int MAX_RESULTS = 3; + + @Autowired + private FindSimilarService findSimilarService; + + @Override + @Cacheable(CacheNames.SIMILAR_DOCUMENTS) + public List findSimilar(final Set indexes, final String reference) throws HodErrorException { + final QueryRequestBuilder requestBuilder = new QueryRequestBuilder() + .setIndexes(indexes) + .setPrint(Print.none) + .setAbsoluteMaxResults(MAX_RESULTS) + .setSummary(Summary.concept); + + final Documents result = findSimilarService.findSimilarDocumentsToIndexReference(reference, requestBuilder); + return result.getDocuments(); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/similar/SimilarDocumentsController.java b/src/main/java/com/hp/autonomy/frontend/find/similar/SimilarDocumentsController.java new file mode 100644 index 0000000000..13d880f10c --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/similar/SimilarDocumentsController.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.similar; + +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Document; +import com.hp.autonomy.hod.client.error.HodErrorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Set; + +@RestController +@RequestMapping("/api/public/search/similar-documents") +@Conditional(HodCondition.class) // TODO remove this +public class SimilarDocumentsController { + @Autowired + private SimilarDocumentsService similarDocumentsService; + + @RequestMapping(method = RequestMethod.GET) + public List findSimilar( + @RequestParam("reference") final String reference, + @RequestParam("indexes") final Set indexes + ) throws HodErrorException { + return similarDocumentsService.findSimilar(indexes, reference); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/similar/SimilarDocumentsService.java b/src/main/java/com/hp/autonomy/frontend/find/similar/SimilarDocumentsService.java new file mode 100644 index 0000000000..f6fc2aa70c --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/similar/SimilarDocumentsService.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.similar; + +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.api.textindex.query.search.Document; +import com.hp.autonomy.hod.client.error.HodErrorException; + +import java.util.List; +import java.util.Set; + +public interface SimilarDocumentsService { + + /** + * Find documents from the given indexes which are similar to documents from the indexes with the given reference. + * The reference must be present in at least one of the indexes. + * @param indexes The domain and names of the target indexes + * @param reference The reference string + * @return List of similar documents + * @throws HodErrorException + */ + List findSimilar(Set indexes, String reference) throws HodErrorException; + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/view/AbstractViewController.java b/src/main/java/com/hp/autonomy/frontend/find/view/AbstractViewController.java new file mode 100644 index 0000000000..d645f63aa7 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/view/AbstractViewController.java @@ -0,0 +1,86 @@ +package com.hp.autonomy.frontend.find.view; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Locale; +import java.util.UUID; + +@Slf4j +public class AbstractViewController { + + protected static final String ERROR_PAGE = "error"; + + @Autowired + private MessageSource messageSource; + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ModelAndView handleGeneralException( + final Exception e, + final HttpServletRequest request, + final HttpServletResponse response + ) { + response.reset(); + + final UUID uuid = UUID.randomUUID(); + log.error("Unhandled exception with uuid {}", uuid); + log.error("Stack trace", e); + + final Locale locale = Locale.ENGLISH; + + return buildErrorModelAndView( + request, + messageSource.getMessage("error.internalServerErrorMain", null, locale), + messageSource.getMessage("error.internalServerErrorSub", new Object[]{uuid}, locale) + ); + } + + protected ModelAndView buildErrorModelAndView( + final HttpServletRequest request, + final String mainMessage, + final String subMessage + ) { + return buildErrorModelAndView(request, mainMessage, subMessage, true); + } + + protected ModelAndView buildErrorModelAndView( + final HttpServletRequest request, + final String mainMessage, + final String subMessage, + final boolean contactSupport + ) { + final ModelAndView modelAndView = new ModelAndView(ERROR_PAGE); + modelAndView.addObject("mainMessage", mainMessage); + modelAndView.addObject("subMessage", subMessage); + modelAndView.addObject("baseUrl", getBaseUrl(request)); + modelAndView.addObject("contactSupport", contactSupport); + + return modelAndView; + } + + private String getBaseUrl(final HttpServletRequest request) { + final String path = request.getRequestURI().replaceFirst(request.getContextPath(), ""); + + final int depth = StringUtils.countMatches(path, "/") - 1; + + final String baseUrl; + + if (depth == 0) { + baseUrl = "."; + } else { + baseUrl = StringUtils.repeat("../", depth); + } + + return baseUrl; + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/view/HodViewController.java b/src/main/java/com/hp/autonomy/frontend/find/view/HodViewController.java new file mode 100644 index 0000000000..6481e48026 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/view/HodViewController.java @@ -0,0 +1,135 @@ +package com.hp.autonomy.frontend.find.view; + +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.beanconfiguration.HodCondition; +import com.hp.autonomy.frontend.find.configuration.HodFindConfig; +import com.hp.autonomy.frontend.view.ViewContentSecurityPolicy; +import com.hp.autonomy.frontend.view.hod.HodViewService; +import com.hp.autonomy.hod.client.api.authentication.HodAuthenticationFailedException; +import com.hp.autonomy.hod.client.api.queryprofile.QueryProfile; +import com.hp.autonomy.hod.client.api.resource.ResourceIdentifier; +import com.hp.autonomy.hod.client.error.HodErrorException; +import com.hp.autonomy.hod.sso.HodAuthentication; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.annotation.Conditional; +import org.springframework.http.MediaType; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Locale; + +@Controller +@RequestMapping({"/api/public/view"}) +@Slf4j +@Conditional(HodCondition.class) +public class HodViewController extends AbstractViewController { + @Autowired + private ConfigService configService; + + @Autowired + private HodViewService hodViewService; + + @Autowired + private MessageSource messageSource; + + @RequestMapping(value = "/viewDocument", method = RequestMethod.GET) + public void viewDocument( + @RequestParam("reference") final String reference, + @RequestParam("indexes") final ResourceIdentifier indexes, + final HttpServletResponse response + ) throws HodErrorException, IOException { + response.setContentType(MediaType.TEXT_HTML_VALUE); + ViewContentSecurityPolicy.addContentSecurityPolicy(response); + hodViewService.viewDocument(reference, indexes, response.getOutputStream()); + } + + @RequestMapping(value = "/viewStaticContentPromotion", method = RequestMethod.GET) + public void viewStaticContentPromotion( + @RequestParam("reference") final String reference, + final HttpServletResponse response + ) throws IOException, HodErrorException { + response.setContentType(MediaType.TEXT_HTML_VALUE); + ViewContentSecurityPolicy.addContentSecurityPolicy(response); + + final String domain = ((HodAuthentication) SecurityContextHolder.getContext().getAuthentication()).getPrincipal().getApplication().getDomain(); + final String queryManipulationIndex = configService.getConfig().getQueryManipulation().getIndex(); + hodViewService.viewStaticContentPromotion(reference, new ResourceIdentifier(domain, queryManipulationIndex), response.getOutputStream()); + } + + @ExceptionHandler + public ModelAndView handleIodErrorException( + final HodErrorException e, + final HttpServletRequest request, + final HttpServletResponse response + ) { + response.reset(); + + log.error("IodErrorException thrown while viewing document", e); + + final Locale locale = Locale.ENGLISH; + + final String errorKey = "error.iodErrorCode." + e.getErrorCode(); + String iodErrorMessage; + + try { + iodErrorMessage = messageSource.getMessage(errorKey, null, locale); + } catch (final NoSuchMessageException e1) { + // we don't have a key in the bundle for this error code + iodErrorMessage = messageSource.getMessage("error.unknownError", null, locale); + } + + final int errorCode; + + if (e.isServerError()) { + errorCode = 500; + } else { + errorCode = 400; + } + + final String subMessage; + + if (iodErrorMessage != null) { + subMessage = messageSource.getMessage("error.iodErrorSub", new String[]{iodErrorMessage}, locale); + } else { + subMessage = messageSource.getMessage("error.iodErrorSubNull", null, locale); + } + + response.setStatus(errorCode); + + return buildErrorModelAndView( + request, + messageSource.getMessage("error.iodErrorMain", null, locale), + subMessage + ); + } + + @ExceptionHandler + public ModelAndView hodAuthenticationFailedException( + final HodAuthenticationFailedException e, + final HttpServletRequest request, + final HttpServletResponse response + ) throws IOException { + response.reset(); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + + log.error("HodAuthenticationFailedException thrown while viewing document", e); + + return buildErrorModelAndView( + request, + messageSource.getMessage("error.iodErrorMain", null, Locale.ENGLISH), + messageSource.getMessage("error.iodTokenExpired", null, Locale.ENGLISH), + false + ); + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/web/CacheNames.java b/src/main/java/com/hp/autonomy/frontend/find/web/CacheNames.java new file mode 100644 index 0000000000..13a8e95a73 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/web/CacheNames.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.web; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class CacheNames { + + public static final String DOCUMENTS = "documents"; + public static final String INDEXES = "indexes"; + public static final String PARAMETRIC_FIELDS = "parametric-fields"; + public static final String PARAMETRIC_VALUES = "parametric-values"; + public static final String PROMOTED_DOCUMENTS = "promoted-documents"; + public static final String RELATED_CONCEPTS = "related-concepts"; + public static final String SIMILAR_DOCUMENTS = "similar-documents"; + public static final String VISIBLE_INDEXES = "visible-indexes"; + + public static final Map CACHE_EXPIRES = new ImmutableMap.Builder() + .put(DOCUMENTS, 60L * 5L) + .put(PROMOTED_DOCUMENTS, 60L * 5L) + .put(RELATED_CONCEPTS, 60L * 5L) + .put(SIMILAR_DOCUMENTS, 60L * 5L) + .build(); + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/web/ErrorController.java b/src/main/java/com/hp/autonomy/frontend/find/web/ErrorController.java new file mode 100644 index 0000000000..697050dfde --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/web/ErrorController.java @@ -0,0 +1,117 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.web; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Locale; +import java.util.UUID; + +@Controller +@Slf4j +public class ErrorController { + + public static final String CLIENT_AUTHENTICATION_ERROR = "/client-authentication-error"; + + @Autowired + private MessageSource messageSource; + + @RequestMapping("/authentication-error") + public ModelAndView authenticationErrorPage(final HttpServletRequest request) throws ServletException, IOException { + return buildModelAndView(request, "error.authenticationErrorMain", "error.authenticationErrorSub", null, null, false); + } + + @RequestMapping(CLIENT_AUTHENTICATION_ERROR) + public ModelAndView clientAuthenticationErrorPage( + @RequestParam("statusCode") final int statusCode, + final HttpServletRequest request + ) throws ServletException, IOException { + return buildModelAndView(request, "error.clientAuthenticationErrorMain", "error.clientAuthenticationErrorSub", null, statusCode, false); + } + + @RequestMapping("/server-error") + public ModelAndView serverErrorPage(final HttpServletRequest request) { + final Exception exception = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); + final String subMessageCode; + final Object[] subMessageArguments; + + if (exception != null) { + final UUID uuid = UUID.randomUUID(); + log.error("Exception with UUID {}", uuid); + log.error("Stack trace", exception); + subMessageCode = "error.internalServerErrorSub"; + subMessageArguments = new Object[]{uuid}; + } else { + subMessageCode = "error.internalServerErrorSub.noUuid"; + subMessageArguments = null; + } + + return buildModelAndView(request, "error.internalServerErrorMain", subMessageCode, subMessageArguments, null, true); + } + + @RequestMapping("/not-found-error") + public ModelAndView notFoundError(final HttpServletRequest request) { + return buildModelAndView(request, "error.notFoundMain", "error.notFoundSub", null, null, true); + } + + private ModelAndView buildModelAndView( + final HttpServletRequest request, + final String mainMessageCode, + final String subMessageCode, + final Object[] subMessageArguments, + final Integer statusCode, + final boolean contactSupport + ) { + final Locale locale = Locale.ENGLISH; + + final ModelAndView modelAndView = new ModelAndView("error"); + modelAndView.addObject("mainMessage", messageSource.getMessage(mainMessageCode, null, locale)); + modelAndView.addObject("subMessage", messageSource.getMessage(subMessageCode, subMessageArguments, locale)); + modelAndView.addObject("baseUrl", getBaseUrl(request)); + modelAndView.addObject("statusCode", statusCode); + modelAndView.addObject("contactSupport", contactSupport); + + return modelAndView; + } + + private String getBaseUrl(final HttpServletRequest request) { + final String originalUri = (String) request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); + final String requestUri; + + if (originalUri != null) { + requestUri = originalUri; + } + else { + requestUri = request.getRequestURI(); + } + + final String path = requestUri.replaceFirst(request.getContextPath(), ""); + + final int depth = StringUtils.countMatches(path, "/") - 1; + + final String baseUrl; + + if (depth <= 0) { + baseUrl = "."; + } else { + baseUrl = StringUtils.repeat("../", depth); + } + + return baseUrl; + } + +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/web/ErrorResponse.java b/src/main/java/com/hp/autonomy/frontend/find/web/ErrorResponse.java index b30faf33ff..b03c0cd623 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/web/ErrorResponse.java +++ b/src/main/java/com/hp/autonomy/frontend/find/web/ErrorResponse.java @@ -19,5 +19,4 @@ class ErrorResponse { public ErrorResponse(final String message) { this.message = message; } - -} \ No newline at end of file +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/web/GlobalExceptionHandler.java b/src/main/java/com/hp/autonomy/frontend/find/web/GlobalExceptionHandler.java index 9d3869e8db..59d76975f1 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/web/GlobalExceptionHandler.java +++ b/src/main/java/com/hp/autonomy/frontend/find/web/GlobalExceptionHandler.java @@ -5,9 +5,12 @@ package com.hp.autonomy.frontend.find.web; +import com.hp.autonomy.hod.client.api.authentication.HodAuthenticationFailedException; +import com.hp.autonomy.hod.client.error.HodErrorException; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -33,6 +36,28 @@ public ErrorResponse messageNotReadableHandler(final HttpMessageNotReadableExcep return handler(exception); } + @ExceptionHandler(HodAuthenticationFailedException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ResponseBody + public ErrorResponse authenticationFailedHandler(final HodAuthenticationFailedException exception) { + return new ErrorResponse("TOKEN HAS EXPIRED"); + } + + @ExceptionHandler(HodErrorException.class) + @ResponseBody + public ResponseEntity hodErrorHandler(final HodErrorException exception) { + final HodErrorResponse hodErrorResponse = new HodErrorResponse("HOD Error", exception.getErrorCode()); + final ResponseEntity entity; + + if (exception.isServerError()) { + entity = new ResponseEntity<>(hodErrorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } else { + entity = new ResponseEntity<>(hodErrorResponse, HttpStatus.BAD_REQUEST); + } + + return entity; + } + @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody @@ -49,5 +74,4 @@ public ErrorResponse handler(final Exception exception) throws Exception { return errorResponse; } - -} \ No newline at end of file +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/web/HodErrorResponse.java b/src/main/java/com/hp/autonomy/frontend/find/web/HodErrorResponse.java new file mode 100644 index 0000000000..4430ffd800 --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/web/HodErrorResponse.java @@ -0,0 +1,17 @@ +package com.hp.autonomy.frontend.find.web; + +import com.hp.autonomy.hod.client.error.HodErrorCode; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@EqualsAndHashCode(callSuper = true) +public class HodErrorResponse extends ErrorResponse { + + private final HodErrorCode hodErrorCode; + + public HodErrorResponse(final String message, final HodErrorCode hodErrorCode) { + super(message); + this.hodErrorCode = hodErrorCode; + } +} diff --git a/src/main/java/com/hp/autonomy/frontend/find/web/HodLogoutSuccessHandler.java b/src/main/java/com/hp/autonomy/frontend/find/web/HodLogoutSuccessHandler.java new file mode 100644 index 0000000000..dd37c3056e --- /dev/null +++ b/src/main/java/com/hp/autonomy/frontend/find/web/HodLogoutSuccessHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.web; + +import com.hp.autonomy.hod.sso.HodAuthentication; +import com.hp.autonomy.hod.sso.HodTokenLogoutSuccessHandler; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class HodLogoutSuccessHandler implements LogoutSuccessHandler { + + private final String settingsLogoutSuccessUrl; + private final HodTokenLogoutSuccessHandler hodTokenLogoutSuccessHandler; + private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + + public HodLogoutSuccessHandler(final HodTokenLogoutSuccessHandler hodTokenLogoutSuccessHandler, final String settingsLogoutSuccessUrl) { + this.hodTokenLogoutSuccessHandler = hodTokenLogoutSuccessHandler; + this.settingsLogoutSuccessUrl = settingsLogoutSuccessUrl; + } + + @Override + public void onLogoutSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException, ServletException { + if (authentication instanceof HodAuthentication) { + hodTokenLogoutSuccessHandler.onLogoutSuccess(request, response, authentication); + } + else { + redirectStrategy.sendRedirect(request, response, settingsLogoutSuccessUrl); + } + } +} diff --git a/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultConfigFile.json b/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultConfigFile.json deleted file mode 100644 index 5290bca00f..0000000000 --- a/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultConfigFile.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "login": { - "method": "default", - "singleUser": { - "username": "admin", - "hashedPassword": "" - }, - "className": "com.autonomy.frontend.configuration.SingleUserAuthentication" - }, - "iod": { - "apiKey": "", - "activeIndexes": [] - } -} diff --git a/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultHodConfigFile.json b/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultHodConfigFile.json new file mode 100644 index 0000000000..bf9875308d --- /dev/null +++ b/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultHodConfigFile.json @@ -0,0 +1,33 @@ +{ + "login": { + "method": "default", + "singleUser": { + "username": "admin", + "hashedPassword": "" + }, + "name": "SingleUserAuthentication" + }, + "hsod": { + "landingPageUrl": "https://search.int.havenondemand.com" + }, + "iod": { + "apiKey": "", + "application": "", + "domain": "", + "activeIndexes": [], + "publicIndexesEnabled": false + }, + "queryManipulation": { + "profile": "search_default_profile", + "index": "search_default_index" + }, + "allowedOrigins": [], + "redis": { + "address": { + "host": "hp-find-backend", + "port": 6379 + }, + "database": 0, + "sentinels": [] + } +} diff --git a/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultIdolConfigFile.json b/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultIdolConfigFile.json new file mode 100644 index 0000000000..b94ba2a70e --- /dev/null +++ b/src/main/resources/com/hp/autonomy/frontend/find/configuration/defaultIdolConfigFile.json @@ -0,0 +1,18 @@ +{ + "login": { + "method": "autonomy", + "community": { + "protocol": "HTTP", + "host": "find-backend", + "port": 9030, + "productType": ["UASERVER", "DAH", "IDOLPROXY"] + }, + "name": "CommunityAuthentication" + }, + "content": { + "protocol": "HTTP", + "host": "find-backend", + "port": 16000, + "productType": ["AXE", "QMS", "DAH", "IDOLPROXY"] + } +} diff --git a/src/main/resources/com/hp/autonomy/frontend/find/dispatcher.properties b/src/main/resources/com/hp/autonomy/frontend/find/dispatcher.properties index 8511088b37..a846168bbf 100644 --- a/src/main/resources/com/hp/autonomy/frontend/find/dispatcher.properties +++ b/src/main/resources/com/hp/autonomy/frontend/find/dispatcher.properties @@ -3,4 +3,4 @@ # Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. # -application.version=${application.buildNumber} \ No newline at end of file +application.version=${application.buildNumber} diff --git a/src/main/resources/com/hp/autonomy/frontend/find/find.properties b/src/main/resources/com/hp/autonomy/frontend/find/find.properties index ab780f33f9..827246c484 100644 --- a/src/main/resources/com/hp/autonomy/frontend/find/find.properties +++ b/src/main/resources/com/hp/autonomy/frontend/find/find.properties @@ -4,4 +4,4 @@ # hibernate.show_sql=${hibernate.show_sql} -hibernate.update_schema=${hibernate.update_schema} \ No newline at end of file +hibernate.update_schema=${hibernate.update_schema} diff --git a/src/main/resources/com/hp/autonomy/frontend/find/i18n.properties b/src/main/resources/com/hp/autonomy/frontend/find/i18n.properties index 62a99c1eb0..28fe7341a6 100644 --- a/src/main/resources/com/hp/autonomy/frontend/find/i18n.properties +++ b/src/main/resources/com/hp/autonomy/frontend/find/i18n.properties @@ -3,3 +3,32 @@ # Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. # +error.internalServerErrorMain=Internal Server Error +error.internalServerErrorSub=Your error UUID is {0} +error.internalServerErrorSub.noUuid=An error has occurred + +error.authenticationErrorMain=Authentication Failed +error.authenticationErrorSub=You do not have permission to view this page + +error.clientAuthenticationErrorMain=Authentication Error +error.clientAuthenticationErrorSub=An error occurred during authentication + +error.notFoundMain=Page Not Found +error.notFoundSub=The page you were looking for could not be found + +error.contactSupport=Please contact Haven OnDemand support + +error.iodErrorMain=Haven OnDemand Error +error.iodErrorSub=Haven OnDemand encountered an error processing your request. The error reported is: {0} +error.iodErrorSubNull=Haven OnDemand encountered an unknown error processing your request + +error.iodTokenExpired=Your token has expired + +# These are generated based on the error code returned from Haven OnDemand +error.iodErrorCode.INVALID_URL=The url provided is invalid +error.iodErrorCode.REFERENCE_DOES_NOT_EXIST=Reference does not exist +error.iodErrorCode.INDEX_NAME_INVALID=The index name is invalid +error.iodErrorCode.INVALID_JOB_ACTION_PARAMETER=A job action parameter is invalid +error.iodErrorCode.INVALID_ACTION_PARAMETERS=There are some invalid action parameters +error.iodErrorCode.BACKEND_REQUEST_FAILED=The backend request failed +error.unknownError=An unknown error occurred \ No newline at end of file diff --git a/src/main/resources/com/hp/autonomy/frontend/find/i18n_en_GB.properties b/src/main/resources/com/hp/autonomy/frontend/find/i18n_en_GB.properties deleted file mode 100644 index 62a99c1eb0..0000000000 --- a/src/main/resources/com/hp/autonomy/frontend/find/i18n_en_GB.properties +++ /dev/null @@ -1,5 +0,0 @@ -# -# Copyright 2014-2015 Hewlett-Packard Development Company, L.P. -# Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. -# - diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index a27cc26221..82238be9e6 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -59,6 +59,11 @@ + + + + + diff --git a/src/main/webapp/WEB-INF/applicationContext.xml b/src/main/webapp/WEB-INF/applicationContext.xml deleted file mode 100644 index 9a7c98a4ad..0000000000 --- a/src/main/webapp/WEB-INF/applicationContext.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - productType - indexErrorMessage - enabled - plaintextPassword - currentPassword - - - - - - - - - - - - - - - diff --git a/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/src/main/webapp/WEB-INF/dispatcher-servlet.xml deleted file mode 100644 index c0a4a3c05a..0000000000 --- a/src/main/webapp/WEB-INF/dispatcher-servlet.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsps/config.jsp b/src/main/webapp/WEB-INF/jsps/config.jsp index 8db1cded41..7bfed05211 100644 --- a/src/main/webapp/WEB-INF/jsps/config.jsp +++ b/src/main/webapp/WEB-INF/jsps/config.jsp @@ -20,9 +20,9 @@ - +
- \ No newline at end of file + diff --git a/src/main/webapp/WEB-INF/jsps/configError.jsp b/src/main/webapp/WEB-INF/jsps/configError.jsp index 6bdd5794be..c3b7c0badc 100644 --- a/src/main/webapp/WEB-INF/jsps/configError.jsp +++ b/src/main/webapp/WEB-INF/jsps/configError.jsp @@ -17,4 +17,4 @@ You need to set the Java system property hp.find.home to your preferred config file location (e.g. /opt/find), restart your web server, and then click here. - \ No newline at end of file + diff --git a/src/main/webapp/WEB-INF/jsps/error.jsp b/src/main/webapp/WEB-INF/jsps/error.jsp new file mode 100644 index 0000000000..9dbc98142b --- /dev/null +++ b/src/main/webapp/WEB-INF/jsps/error.jsp @@ -0,0 +1,40 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%-- + ~ Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + ~ Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + --%> + + + + + + + Find - Error + + + + + + + + + +
+

+ + ${pageContext.response.status} + ${statusCode} + +

+

${mainMessage}

+
+

${subMessage}

+

+
+
+ + diff --git a/src/main/webapp/WEB-INF/jsps/login.jsp b/src/main/webapp/WEB-INF/jsps/login.jsp index aa1f24e7f7..4967cc92d8 100644 --- a/src/main/webapp/WEB-INF/jsps/login.jsp +++ b/src/main/webapp/WEB-INF/jsps/login.jsp @@ -15,9 +15,9 @@ Find - Login - - - + + + diff --git a/src/main/webapp/WEB-INF/jsps/private.jsp b/src/main/webapp/WEB-INF/jsps/private.jsp index c904600a80..7ddf42c1a1 100644 --- a/src/main/webapp/WEB-INF/jsps/private.jsp +++ b/src/main/webapp/WEB-INF/jsps/private.jsp @@ -20,7 +20,7 @@ - +
@@ -31,4 +31,4 @@
- \ No newline at end of file + diff --git a/src/main/webapp/WEB-INF/jsps/public.jsp b/src/main/webapp/WEB-INF/jsps/public.jsp index 4e274bdcd0..ce2e1162b1 100644 --- a/src/main/webapp/WEB-INF/jsps/public.jsp +++ b/src/main/webapp/WEB-INF/jsps/public.jsp @@ -1,10 +1,12 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%-- ~ Copyright 2014-2015 Hewlett-Packard Development Company, L.P. ~ Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. --%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + @@ -20,7 +22,8 @@ - + +
@@ -31,4 +34,4 @@
- \ No newline at end of file + diff --git a/src/main/webapp/WEB-INF/jsps/sso-logout.jsp b/src/main/webapp/WEB-INF/jsps/sso-logout.jsp new file mode 100644 index 0000000000..2e28ae70ac --- /dev/null +++ b/src/main/webapp/WEB-INF/jsps/sso-logout.jsp @@ -0,0 +1,19 @@ +<%-- + ~ Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + ~ Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + --%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + Find + + + + + + + diff --git a/src/main/webapp/WEB-INF/jsps/sso.jsp b/src/main/webapp/WEB-INF/jsps/sso.jsp new file mode 100644 index 0000000000..57bf6a4d1a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsps/sso.jsp @@ -0,0 +1,21 @@ +<%-- + ~ Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + ~ Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + --%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + Find + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index f1784a4196..e1fd4045ec 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,9 +5,9 @@ --> + 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"> Find Find @@ -16,9 +16,27 @@ org.springframework.web.context.ContextLoaderListener + + contextConfigLocation + com.hp.autonomy.frontend.find.beanconfiguration.AppConfiguration + + + + contextClass + org.springframework.web.context.support.AnnotationConfigWebApplicationContext + + dispatcher org.springframework.web.servlet.DispatcherServlet + + contextClass + org.springframework.web.context.support.AnnotationConfigWebApplicationContext + + + contextConfigLocation + com.hp.autonomy.frontend.find.beanconfiguration.DispatcherServletConfiguration + 1 @@ -47,6 +65,27 @@ /* + + springSessionRepositoryFilter + org.springframework.web.filter.DelegatingFilterProxy + true + + + + + springSessionRepositoryFilter + /p/* + /api/* + /config/* + /authentication/* + /public/* + /login + /authenticate + /authenticate-sso + /logout + /sso + + configEnvironmentVariableFilter @@ -86,4 +125,25 @@ 60 - \ No newline at end of file + + + + 403 + /authentication-error + + + + 401 + /authentication-error + + + + 500 + /server-error + + + + 404 + /not-found-error + + diff --git a/src/main/webapp/static/css/app-include.css b/src/main/webapp/static/css/app-include.css index f4e14a4fd7..27078378e7 100644 --- a/src/main/webapp/static/css/app-include.css +++ b/src/main/webapp/static/css/app-include.css @@ -13,13 +13,147 @@ html, body { min-width: 640px; } +.date input { + width: 75% !important; + padding-right: 0 !important; +} + +.input-append.date { + margin-bottom: 0; +} + +.search-dates-wrapper .form-group { + display: inline-flex; +} + +.date { + position: relative; + display: inline-block; +} +.date > input { + padding: 8px 42px 8px 8px; + padding-left: 30px; +} +.date > .date-select { + position: absolute; + top: 0; + left: 0; + padding: 10px 8px; + background: transparent; + text-decoration: none; + cursor: pointer; + text-align: center; + width: 33px; + border: 0; + z-index: 1000; +} + +.break-all { + word-break: break-all; +} + +.m-t-xs { + margin-top: 5px; +} + +.m-t-sm { + margin-top: 10px; +} + +.m-b-xs { + margin-bottom: 5px; +} + +.m-b-sm { + margin-bottom: 10px; +} + +.m-b-md { + margin-bottom: 20px; +} + +.m-b-lg { + margin-bottom: 30px; +} + +.m-b-xl { + margin-bottom: 40px; +} + +.m-b-xxl { + margin-bottom: 60px; +} + +.m-b-nil { + margin-bottom: 0; +} + +.m-l-xs { + margin-left: 5px; +} + +.m-l-sm { + margin-left: 10px; +} + +.m-r-sm { + margin-right: 10px; +} + +.m-r-xs { + margin-right: 5px; +} + +.full-height { + height: 100%; +} + +.bold { + font-weight: 600 !important; +} + +.no-box-shadow { + box-shadow: none !important; +} + +.right-side-container td, +.left-side-container td { + border-bottom: 1px solid #ddd; + word-break: break-all; +} + +.bootstrap-datetimepicker-widget td.day.active { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #2f92e7; + *background-color: #2f76e7; + background-image: -moz-linear-gradient(top, #2fa4e7, #2f76e7); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#2f76e7)); + background-image: -webkit-linear-gradient(top, #2fa4e7, #2f76e7); + background-image: -o-linear-gradient(top, #2fa4e7, #2f76e7); + background-image: linear-gradient(to bottom, #2fa4e7, #2f76e7); + background-repeat: repeat-x; + border-color: #2f76e7 #2f76e7 #1553b5; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff2f76e7', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.input-append.date .add-on i { + display: inline; +} + +.input-append.date .add-on { + cursor: pointer; +} + .initial-loading-indicator-container { position: relative; height: 100%; } .initial-loading-indicator { - border: 1px solid #2FA4E7; + border: 1px solid #00b388; border-radius: 5px; height: 32px; left: 50%; @@ -32,6 +166,11 @@ html, body { width: 200px; } +.input-append, +.input-prepend .date { + font-size: 100%; +} + .page .header { min-height: 50px; } @@ -59,14 +198,112 @@ html, body { .main-content { background-color: white; height: 100%; + overflow-x: hidden; +} + +.main-content .container-fluid { + height:80%; +} + +.find-main-subcontainer, +.main-content .content { + height:100%; +} + +.left-side-container { + font-size:13px; +} + +.right-side-container { + font-size: 14px; +} + +.right-side-container h4, +.left-side-container h4 { + font-weight: 900; + font-size: 13px; + color:#555; +} + +.rotating-chevron { + display: inline-block; + color: lightgrey; +} + +.right-side-container h4 .rotating-chevron, +.left-side-container h4 .rotating-chevron { + font-weight: bold; +} + +.middle-container h4 { + color: #425563; + font-weight: bold; + font-size: 18px; + width: 70%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .find { text-align: center; } +.app-logo { + width: 72%; +} + .find-logo { - font-size: 24px; + font-size: 25px; + font-weight: bolder; +} + +.find-logo-large { + font-size: 90px; + color: #425563; + display: inline-block; + vertical-align: middle; +} + +.hp-logo { + background: url('../img/HPE_header_logo.png') no-repeat; + background-size: 210px 28px; + width: 210px; + height: 28px; +} + +.find-logo-large { + background-image: url('../img/Find_logo.png'); + background-size: 170px 102px; + width: 170px; + height: 102px; + display: inline-block; + vertical-align: middle; +} + +.find-banner { + background-image: url('../img/Find_banner.png'); + height: 125px; + position: absolute; + top: 0; + left: 0; + right: 0; +} + +.hp-logo-footer { + background: url('../img/HPE_logo.png') no-repeat center; + background-size: 140px 55px; + height: 55px; +} + +a.unstyled { + color:#fff; + text-decoration: none; +} + +a.unstyled:hover { + color:#fff !important; + text-decoration: none !important; } input.find-input { @@ -75,65 +312,72 @@ input.find-input { line-height: initial; } -.find-navbar .navbar .nav > li > a:focus, -.navbar .nav > li > a:hover, -.navbar .nav > .active > a:hover { - background-color: #A3877A; +::-webkit-input-placeholder { /* WebKit, Blink, Edge */ + color: #a9a9a9; +} +:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #a9a9a9; + opacity: 1; +} +::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #a9a9a9; + opacity: 1; +} +:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: #a9a9a9; } -/*main results bubbles styling*/ -.main-results-container { - display: inline-block; - -webkit-transition-duration: 0.3s; - transition-duration: 0.3s; - -webkit-transition-property: -webkit-transform; - transition-property: transform; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); - transform: translateZ(0); - box-shadow: 0 0 1px rgba(0, 0, 0, 0); +.sort-container a { + cursor: pointer; } -.main-results-container:hover { - -webkit-transform: scale(1.02); - -ms-transform: scale(1.02); - transform: scale(1.02); +.sort-container a:hover { + text-decoration: none; } -.main-results-content { - display: flex; - flex-wrap: wrap; +.current-search-sort { + font-size: 13px; + color: #425563; } -.main-results-content .span5 { - margin-right:15px; - margin-bottom: 20px; +.main-results-container { + border-bottom: 1px solid #F2F2F2; + width: 100%; + padding: 0 10px; + min-height: 140px; +} + +.document-reference { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 100%; + display: inline-block; + color: #aeaeae; + font-size: 13px; } -.main-results-container:not(:first-child) { - margin-left: 0; +.content-type i { + font-size: 23px; } -.main-results-container p { - line-height: 31px; - font-size: 17px; +.result-container-control { + color: #425563; + font-size: 13px; + cursor: default; + text-decoration: none; } -.drop-shadow { - position:relative; - float:left; - width:40%; - padding:1em; - background: white; - -webkit-box-shadow:0px 3px 6px rgba(3, 2, 2, 0.29), 0 0 6px rgba(255, 255, 255, 0.1) inset; - -moz-box-shadow:0px 3px 6px rgba(3, 2, 2, 0.29), 0 0 6px rgba(255, 255, 255, 0.1) inset; - box-shadow:0px 3px 6px rgba(3, 2, 2, 0.29), 0 0 6px rgba(255, 255, 255, 0.1) inset; +.document-date { + color: #aeaeae; + font-style: italic; + font-size: small; + cursor: default; } -/* end of main results bubble styling*/ -.suggested-links-container.span2 { - display: none; +.promoted-label { + font-size: 12px; + color: #2ad2c9; } .suggestions-content li { @@ -149,11 +393,23 @@ input.find-input { position: relative; } +.find { + /* We need to be on top of the static navbar */ + z-index: 9999; +} + +/* username */ +.username { + max-width: 250px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + /* fancy animation */ .animated-container { position: absolute; - margin-top: 10px; - top: 0; + top: -60px; transition: all 1000ms ease; } @@ -164,73 +420,85 @@ input.find-input { } .animated-container .find-input { - height: 20px; font-size: 18px; + border-bottom: none; transition: all 1000ms ease; } /* fancy reverse animation */ .reverse-animated-container { position: absolute; - top: 50%; + top: 30%; transition: all 1000ms ease; } .reverse-animated-container .find-form { - width:80%; + width:75%; transition: all 1000ms ease; transition-property:width; } .reverse-animated-container .find-input { - height: 40px !important; - font-size: 34px !important; + height: 45px !important; + font-size: 23px !important; + border: 2px solid #CCC !important; transition: all 1000ms ease; + padding: 10px; + font-family: inherit; + font-style: italic; +} + +.reverse-animated-container .find-form:focus, +.reverse-animated-container .find-input:focus, +.animated-container .find-form:focus, +.animated-container .find-input:focus { + outline: 0; } /* end of fancy animation */ .suggestions-content a { - color: rgba(138, 113, 102, 0.96) + color: #425563; } -.main-content h4 { - color: rgba(138, 113, 102, 0.96); +.suggestions-content { + height: 320px; + overflow-y: auto; } -.suggestion-head-text { - font-size: 16px; - font-weight: 900; +.main-content h5, +.error-body h1, +.error-body h3 { + color: #425563; } -.main-results-content .loading-spinner { - height: 32px; - left: 50%; - line-height: 32px; - margin: -16px 0 0 -100px; - padding: 20px; - position: absolute; +.error-body { + margin-top: 250px; text-align: center; - top: 50%; - width: 200px; - font-size: 28px; - color: rgba(138, 113, 102, 0.96); } -.main-results-content .no-results { - font-size: 28px; - color: rgba(138, 113, 102, 0.96); +.suggestion-head-text { + font-size: 14px; + font-weight: 800; } - -.main-results-content .loading-spinner i, .suggested-links-container .loading-spinner i { - color:rgb(177, 85, 85); +.results-container .result-message { + font-size: 28px; + color: #425563; } -.suggested-links-container .loading-spinner { +/* Loading spinner */ +.loading-spinner { text-align: center; font-size: 18px; - color: rgba(138, 113, 102, 0.96); } +.loading-spinner, .loading-spinner i { + color: #00b388; +} + +.loading-spinner.loading-spinner-lg { + font-size: 28px; + line-height: 32px; +} /* fancy colourbox buttons */ .btn-colorbox, .btn-colorbox:focus { @@ -261,18 +529,21 @@ input.find-input { .result-summary { text-align: justify; + overflow: hidden; + text-overflow: clip; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + max-height: 40px; } -.popover .arrow { - display: none; -} - -.suggestion-cluster .popover { +.search-page .popover { width: 250px; } -.popover-content h6 { +.popover-content h5 { margin:0; + font-weight: 700; } .find-form .popover label { @@ -282,7 +553,7 @@ input.find-input { vertical-align: baseline; } -.find-form .popover label input[type="radio"] { +.find-form .popover label input[type="radio"], .find-form .popover label input[type="checkbox"] { margin-left: -20px; margin-right: 0; vertical-align: baseline; @@ -300,6 +571,7 @@ input.find-input { a.entity-to-summary { cursor:pointer; color:white; + font-size: 12px; } .entity-to-summary a { @@ -310,8 +582,13 @@ a.entity-to-summary { text-decoration: none; } -.entity-to-summary.label-info { - background-color: #b15555; +.entity-to-summary { + padding: 2px; + background-color: #425563; +} + +.service-view-container .icon-ok { + color: #00b388; } @media only screen and (max-width: 1210px) { @@ -319,22 +596,36 @@ a.entity-to-summary { height:25px; font-size: 24px; } - .list-indexes { - top: 8px; - } + form.form-search:before { top:9px; } } -.list-indexes { +.drop-list { position: absolute; - top: 5px; - right: -65px; + right: 20px; z-index: 1000; - font-size: 22px; - cursor:pointer; - color:lightgrey; + font-size: 18px; +} + +.find-navbar .dropdown-toggle { + color: #fff !important; +} + +.find-navbar .dropdown-toggle:hover, +.find-navbar .open .dropdown-toggle { + color: #23527c !important; +} + +.list-indexes { + right: -65px; +} + +.disabled-index { + color: #aaa; + cursor: not-allowed; + pointer-events: none; } .indexes-list-group .controls { @@ -349,13 +640,14 @@ a.entity-to-summary { input.find-input[type="text"], input.find-input[type="text"]:focus { border-color: transparent; box-shadow: none; - border-bottom: 3px solid #b15555; border-radius: 0; + height: 30px; + padding-right: 40px; } input::-webkit-input-placeholder, input::-moz-placeholder { - color: indianred; + color: #425563; opacity: 0.5; } @@ -365,16 +657,25 @@ input::-moz-placeholder { input:-moz-placeholder::before { font-family: fontAwesome; content: '\f105'; - color: indianred + color: #425563; } .page .header { margin-bottom: 20px; } -.navbar-inner { - border: none; - background: #555; +.find-navbar { + border-bottom: 3px solid #425563; + background-color: #425563; + color: #fff; + opacity: 0; + -webkit-transition: all 0.5s ease; + transition: all 0.5s ease; +} + + +.find-navbar.visible { + opacity: 1; } .navbar .nav > .active > a { @@ -382,13 +683,33 @@ input:-moz-placeholder::before { } .popover h1, .popover h2, .popover h3, .popover h4, .popover h5, .popover h6 { - color: rgba(138, 113, 102, 0.96); + color: #00b388; } -@media (max-width: 767px) { - .page-wrap { - padding: 0 20px; - } +/*TODO use bootstrap theme customiser for these */ +.popover { + border-radius: 0; +} + +.popover, .popover-title { + background-color:#353535; +} + +.popover-content p { + color: #fff; + font-size: 12px; +} + +.popover-content { + padding: 15px 14px 9px; +} + +.popover-title { + display: none; +} + +.popover.bottom > .arrow:after { + border-bottom-color:#353535; } @media (min-width: 980px) { @@ -405,4 +726,155 @@ input:-moz-placeholder::before { -webkit-transition: bottom 0.5s ease; transition: bottom 0.5s ease; } +} + +@media (max-width: 991px) { + .reverse-animated-container .find-form { + width: 100%; + } +} + +.filter-checkbox { + margin-right: 10px; +} + +.filter-display-view { + line-height: 2.0em; +} + +.shorten { + max-width: 130px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.filter-display-text { + vertical-align: middle; + line-height: 1.2em; +} + +.filters-margin { + margin-top: 0; + margin-right: 5px; +} + +.filters-remove-icon { + margin-left: 5px; +} + +.no-bottom-margin { + margin-bottom: 0; +} + +.inline-block { + display: inline-block; +} + +.filter-label { + background-color: #FFFFFF !important; + padding: 5px; + font-size: 100%; + font-weight: normal; + color: #5e5e5e; + border: solid 1px #dddddd; + border-radius: 0; +} + +.filter-label span { + color: #425563; +} + +.label-white.border { + border: 1px solid #e7eaec; +} + +/* Rotating chevrons on collapsible panels *******************/ +.collapsed .rotating-chevron, +.collapsed.rotating-chevron { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} + +/*** Cursor *************************************************/ + +.clickable { + cursor: pointer; +} + +.not-clickable { + cursor: default; + pointer-events: none; +} + +/* Indexes View */ +.database-name, +.category-name { + padding: 0 8px; /* Same as table padding so it lines up */ +} + +.category-name, +.category-name-icon, +.child-categories li { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; +} + +.category-name, +.category-name-icon { + font-weight: bold; +} + +.database-input > span, +.category-input > span { + display: inline-block; +} + +.category-input { + border-top: 1px solid lightgrey; + border-bottom: none !important; +} + +.child-categories ul { + margin-bottom: 20px; +} + +.child-categories li { + border-bottom: 1px solid #ddd; + word-break: break-all; +} + +.child-categories li:first-child { + border-top: 1px solid lightgrey; +} + +/* View server */ +.view-server-loading-indicator { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 32px; + color: #00b388; + text-align: center; +} + +.view-server-button, +.view-server-button:hover, +.view-server-button:focus, +.view-server-button:active { + background-color: #425563; + border-color: #425563; + color: #fff; + border-radius: 3px; + background-image: none; + box-shadow: none; +} + +#cboxLoadedContent .table > tbody > tr > th, +#cboxLoadedContent .table > tbody > tr > td { + border-top: 0; } \ No newline at end of file diff --git a/src/main/webapp/static/css/app.css b/src/main/webapp/static/css/app.css index e49deee5b9..8cf1b3083c 100644 --- a/src/main/webapp/static/css/app.css +++ b/src/main/webapp/static/css/app.css @@ -3,11 +3,14 @@ * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. */ -@import 'bootstrap-custom.css'; -@import '../lib/hp-autonomy-bootstrap-2/bootstrap/css/bootstrap-responsive.css'; -@import '../lib/fontawesome/css/font-awesome.css'; -@import "../lib/colorbox/example1/colorbox.css"; -@import "../lib/hp-autonomy-settings-page/src/css/settings.css"; -@import "../lib/hp-autonomy-js-whatever/src/css/javascript-utils.css"; +@import '../lib/bootstrap/css/bootstrap.css'; +@import '../lib/bootstrap/css/bootstrap-theme.css'; +@import '../bower_components/fontawesome/css/font-awesome.css'; +@import '../bower_components/bootstrap-timepicker/build/css/bootstrap-datetimepicker.min.css'; +@import "../bower_components/colorbox/example1/colorbox.css"; +@import "../bower_components/hp-autonomy-settings-page/src/css/settings.css"; +@import "../bower_components/hp-autonomy-js-whatever/src/css/javascript-utils.css"; +@import "../bower_components/icheck/skins/all.css"; @import 'app-include.css'; -@import 'colorbox.css'; \ No newline at end of file +@import 'colorbox.css'; +@import 'icons.css'; diff --git a/src/main/webapp/static/css/bootstrap-custom.css b/src/main/webapp/static/css/bootstrap-custom.css deleted file mode 100644 index f5d0746648..0000000000 --- a/src/main/webapp/static/css/bootstrap-custom.css +++ /dev/null @@ -1,5913 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -@import url('https://fonts.googleapis.com/css?family=Nunito'); -/*! - * Bootstrap v2.3.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - -.clearfix { - *zoom: 1; -} - -.clearfix:before, -.clearfix:after { - display: table; - line-height: 0; - content: ""; -} - -.clearfix:after { - clear: both; -} - -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.input-block-level { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -nav, -section { - display: block; -} - -audio, -canvas, -video { - display: inline-block; - *display: inline; - *zoom: 1; -} - -audio:not([controls]) { - display: none; -} - -html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -a:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -a:hover, -a:active { - outline: 0; -} - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -img { - width: auto\9; - height: auto; - max-width: 100%; - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} - -#map_canvas img, -.google-maps img { - max-width: none; -} - -button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} - -button, -input { - *overflow: visible; - line-height: normal; -} - -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} - -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} - -label, -select, -button, -input[type="button"], -input[type="reset"], -input[type="submit"], -input[type="radio"], -input[type="checkbox"] { - cursor: pointer; -} - -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} - -input[type="search"]::-webkit-search-decoration, -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; -} - -textarea { - overflow: auto; - vertical-align: top; -} - -@media print { - * { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - box-shadow: none !important; - } - a, - a:visited { - text-decoration: underline; - } - a[href]:after { - content: " (" attr(href) ")"; - } - abbr[title]:after { - content: " (" attr(title) ")"; - } - .ir a:after, - a[href^="javascript:"]:after, - a[href^="#"]:after { - content: ""; - } - pre, - blockquote { - border: 1px solid #999; - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - img { - max-width: 100% !important; - } - @page { - margin: 0.5cm; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } -} - -body { - margin: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - color: #555555; - background-color: #ffffff; -} - -a { - color: #2fa4e7; - text-decoration: none; -} - -a:hover, -a:focus { - color: #157ab5; - text-decoration: underline; -} - -.img-rounded { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.img-polaroid { - padding: 4px; - background-color: #fff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.img-circle { - -webkit-border-radius: 500px; - -moz-border-radius: 500px; - border-radius: 500px; -} - -.row { - margin-left: -20px; - *zoom: 1; -} - -.row:before, -.row:after { - display: table; - line-height: 0; - content: ""; -} - -.row:after { - clear: both; -} - -[class*="span"] { - float: left; - min-height: 1px; - margin-left: 20px; -} - -.container, -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} - -.span12 { - width: 940px; -} - -.span11 { - width: 860px; -} - -.span10 { - width: 780px; -} - -.span9 { - width: 700px; -} - -.span8 { - width: 620px; -} - -.span7 { - width: 540px; -} - -.span6 { - width: 460px; -} - -.span5 { - width: 380px; -} - -.span4 { - width: 300px; -} - -.span3 { - width: 220px; -} - -.span2 { - width: 140px; -} - -.span1 { - width: 60px; -} - -.offset12 { - margin-left: 980px; -} - -.offset11 { - margin-left: 900px; -} - -.offset10 { - margin-left: 820px; -} - -.offset9 { - margin-left: 740px; -} - -.offset8 { - margin-left: 660px; -} - -.offset7 { - margin-left: 580px; -} - -.offset6 { - margin-left: 500px; -} - -.offset5 { - margin-left: 420px; -} - -.offset4 { - margin-left: 340px; -} - -.offset3 { - margin-left: 260px; -} - -.offset2 { - margin-left: 180px; -} - -.offset1 { - margin-left: 100px; -} - -.row-fluid { - width: 100%; - *zoom: 1; -} - -.row-fluid:before, -.row-fluid:after { - display: table; - line-height: 0; - content: ""; -} - -.row-fluid:after { - clear: both; -} - -.row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.127659574468085%; - *margin-left: 2.074468085106383%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.row-fluid [class*="span"]:first-child { - margin-left: 0; -} - -.row-fluid .controls-row [class*="span"] + [class*="span"] { - margin-left: 2.127659574468085%; -} - -.row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; -} - -.row-fluid .span11 { - width: 91.48936170212765%; - *width: 91.43617021276594%; -} - -.row-fluid .span10 { - width: 82.97872340425532%; - *width: 82.92553191489361%; -} - -.row-fluid .span9 { - width: 74.46808510638297%; - *width: 74.41489361702126%; -} - -.row-fluid .span8 { - width: 65.95744680851064%; - *width: 65.90425531914893%; -} - -.row-fluid .span7 { - width: 57.44680851063829%; - *width: 57.39361702127659%; -} - -.row-fluid .span6 { - width: 48.93617021276595%; - *width: 48.88297872340425%; -} - -.row-fluid .span5 { - width: 40.42553191489362%; - *width: 40.37234042553192%; -} - -.row-fluid .span4 { - width: 31.914893617021278%; - *width: 31.861702127659576%; -} - -.row-fluid .span3 { - width: 23.404255319148934%; - *width: 23.351063829787233%; -} - -.row-fluid .span2 { - width: 14.893617021276595%; - *width: 14.840425531914894%; -} - -.row-fluid .span1 { - width: 6.382978723404255%; - *width: 6.329787234042553%; -} - -.row-fluid .offset12 { - margin-left: 104.25531914893617%; - *margin-left: 104.14893617021275%; -} - -.row-fluid .offset12:first-child { - margin-left: 102.12765957446808%; - *margin-left: 102.02127659574467%; -} - -.row-fluid .offset11 { - margin-left: 95.74468085106382%; - *margin-left: 95.6382978723404%; -} - -.row-fluid .offset11:first-child { - margin-left: 93.61702127659574%; - *margin-left: 93.51063829787232%; -} - -.row-fluid .offset10 { - margin-left: 87.23404255319149%; - *margin-left: 87.12765957446807%; -} - -.row-fluid .offset10:first-child { - margin-left: 85.1063829787234%; - *margin-left: 84.99999999999999%; -} - -.row-fluid .offset9 { - margin-left: 78.72340425531914%; - *margin-left: 78.61702127659572%; -} - -.row-fluid .offset9:first-child { - margin-left: 76.59574468085106%; - *margin-left: 76.48936170212764%; -} - -.row-fluid .offset8 { - margin-left: 70.2127659574468%; - *margin-left: 70.10638297872339%; -} - -.row-fluid .offset8:first-child { - margin-left: 68.08510638297872%; - *margin-left: 67.9787234042553%; -} - -.row-fluid .offset7 { - margin-left: 61.70212765957446%; - *margin-left: 61.59574468085106%; -} - -.row-fluid .offset7:first-child { - margin-left: 59.574468085106375%; - *margin-left: 59.46808510638297%; -} - -.row-fluid .offset6 { - margin-left: 53.191489361702125%; - *margin-left: 53.085106382978715%; -} - -.row-fluid .offset6:first-child { - margin-left: 51.063829787234035%; - *margin-left: 50.95744680851063%; -} - -.row-fluid .offset5 { - margin-left: 44.68085106382979%; - *margin-left: 44.57446808510638%; -} - -.row-fluid .offset5:first-child { - margin-left: 42.5531914893617%; - *margin-left: 42.4468085106383%; -} - -.row-fluid .offset4 { - margin-left: 36.170212765957444%; - *margin-left: 36.06382978723405%; -} - -.row-fluid .offset4:first-child { - margin-left: 34.04255319148936%; - *margin-left: 33.93617021276596%; -} - -.row-fluid .offset3 { - margin-left: 27.659574468085104%; - *margin-left: 27.5531914893617%; -} - -.row-fluid .offset3:first-child { - margin-left: 25.53191489361702%; - *margin-left: 25.425531914893618%; -} - -.row-fluid .offset2 { - margin-left: 19.148936170212764%; - *margin-left: 19.04255319148936%; -} - -.row-fluid .offset2:first-child { - margin-left: 17.02127659574468%; - *margin-left: 16.914893617021278%; -} - -.row-fluid .offset1 { - margin-left: 10.638297872340425%; - *margin-left: 10.53191489361702%; -} - -.row-fluid .offset1:first-child { - margin-left: 8.51063829787234%; - *margin-left: 8.404255319148938%; -} - -[class*="span"].hide, -.row-fluid [class*="span"].hide { - display: none; -} - -[class*="span"].pull-right, -.row-fluid [class*="span"].pull-right { - float: right; -} - -.container { - margin-right: auto; - margin-left: auto; - *zoom: 1; -} - -.container:before, -.container:after { - display: table; - line-height: 0; - content: ""; -} - -.container:after { - clear: both; -} - -.container-fluid { - padding-right: 20px; - padding-left: 20px; - *zoom: 1; -} - -.container-fluid:before, -.container-fluid:after { - display: table; - line-height: 0; - content: ""; -} - -.container-fluid:after { - clear: both; -} - -p { - margin: 0 0 10px; -} - -.lead { - margin-bottom: 20px; - font-size: 21px; - font-weight: 200; - line-height: 30px; -} - -small { - font-size: 85%; -} - -strong { - font-weight: bold; -} - -em { - font-style: italic; -} - -cite { - font-style: normal; -} - -.muted { - color: #999999; -} - -a.muted:hover, -a.muted:focus { - color: #808080; -} - -.text-warning { - color: #dd5600; -} - -a.text-warning:hover, -a.text-warning:focus { - color: #aa4200; -} - -.text-error { - color: #bd4247; -} - -a.text-error:hover, -a.text-error:focus { - color: #983538; -} - -.text-info { - color: #178acc; -} - -a.text-info:hover, -a.text-info:focus { - color: #126b9e; -} - -.text-success { - color: #669533; -} - -a.text-success:hover, -a.text-success:focus { - color: #4c6f26; -} - -.text-left { - text-align: left; -} - -.text-right { - text-align: right; -} - -.text-center { - text-align: center; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin: 10px 0; - font-family: 'Nunito', sans-serif; - font-weight: bold; - line-height: 20px; - color: #317eac; - text-rendering: optimizelegibility; -} - -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small { - font-weight: normal; - line-height: 1; - color: #999999; -} - -h1, -h2, -h3 { - line-height: 40px; -} - -h1 { - font-size: 38.5px; -} - -h2 { - font-size: 31.5px; -} - -h3 { - font-size: 24.5px; -} - -h4 { - font-size: 17.5px; -} - -h5 { - font-size: 14px; -} - -h6 { - font-size: 11.9px; -} - -h1 small { - font-size: 24.5px; -} - -h2 small { - font-size: 17.5px; -} - -h3 small { - font-size: 14px; -} - -h4 small { - font-size: 14px; -} - -.page-header { - margin: 0 0 20px; - border-bottom: 1px solid #f5f5f5; -} - -ul, -ol { - padding: 0; - margin: 0 0 10px 25px; -} - -ul ul, -ul ol, -ol ol, -ol ul { - margin-bottom: 0; -} - -li { - line-height: 20px; -} - -ul.unstyled, -ol.unstyled { - margin-left: 0; - list-style: none; -} - -ul.inline, -ol.inline { - margin-left: 0; - list-style: none; -} - -ul.inline > li, -ol.inline > li { - display: inline-block; - *display: inline; - padding-right: 5px; - padding-left: 5px; - *zoom: 1; -} - -dl { - margin-bottom: 20px; -} - -dt, -dd { - line-height: 20px; -} - -dt { - font-weight: bold; -} - -dd { - margin-left: 10px; -} - -.dl-horizontal { - *zoom: 1; -} - -.dl-horizontal:before, -.dl-horizontal:after { - display: table; - line-height: 0; - content: ""; -} - -.dl-horizontal:after { - clear: both; -} - -.dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; -} - -.dl-horizontal dd { - margin-left: 180px; -} - -hr { - margin: 20px 0; - border: 0; - border-top: 1px solid #f5f5f5; - border-bottom: 1px solid #ffffff; -} - -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #999999; -} - -abbr.initialism { - font-size: 90%; - text-transform: uppercase; -} - -blockquote { - padding: 0 0 0 15px; - margin: 0 0 20px; - border-left: 5px solid #f5f5f5; -} - -blockquote p { - margin-bottom: 0; - font-size: 17.5px; - font-weight: 300; - line-height: 1.25; -} - -blockquote small { - display: block; - line-height: 20px; - color: #999999; -} - -blockquote small:before { - content: '\2014 \00A0'; -} - -blockquote.pull-right { - float: right; - padding-right: 15px; - padding-left: 0; - border-right: 5px solid #f5f5f5; - border-left: 0; -} - -blockquote.pull-right p, -blockquote.pull-right small { - text-align: right; -} - -blockquote.pull-right small:before { - content: ''; -} - -blockquote.pull-right small:after { - content: '\00A0 \2014'; -} - -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} - -address { - display: block; - margin-bottom: 20px; - font-style: normal; - line-height: 20px; -} - -code, -pre { - padding: 0 3px 2px; - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 12px; - color: #333333; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -code { - padding: 2px 4px; - color: #d14; - white-space: nowrap; - background-color: #f7f7f9; - border: 1px solid #e1e1e8; -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 20px; - word-break: break-all; - word-wrap: break-word; - white-space: pre; - white-space: pre-wrap; - background-color: #f5f5f5; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.15); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -pre.prettyprint { - margin-bottom: 20px; -} - -pre code { - padding: 0; - color: inherit; - white-space: pre; - white-space: pre-wrap; - background-color: transparent; - border: 0; -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} - -form { - margin: 0 0 20px; -} - -fieldset { - padding: 0; - margin: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: 40px; - color: #333333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} - -legend small { - font-size: 15px; - color: #999999; -} - -label, -input, -button, -select, -textarea { - font-size: 14px; - font-weight: normal; - line-height: 20px; -} - -input, -button, -select, -textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -label { - display: block; - margin-bottom: 5px; -} - -select, -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - display: inline-block; - height: 20px; - padding: 4px 6px; - margin-bottom: 10px; - font-size: 14px; - line-height: 20px; - color: #555555; - vertical-align: middle; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -input, -textarea, -.uneditable-input { - width: 206px; -} - -textarea { - height: auto; -} - -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - background-color: #ffffff; - border: 1px solid #cccccc; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -o-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; -} - -textarea:focus, -input[type="text"]:focus, -input[type="password"]:focus, -input[type="datetime"]:focus, -input[type="datetime-local"]:focus, -input[type="date"]:focus, -input[type="month"]:focus, -input[type="time"]:focus, -input[type="week"]:focus, -input[type="number"]:focus, -input[type="email"]:focus, -input[type="url"]:focus, -input[type="search"]:focus, -input[type="tel"]:focus, -input[type="color"]:focus, -.uneditable-input:focus { - border-color: rgba(82, 168, 236, 0.8); - outline: 0; - outline: thin dotted \9; - /* IE6-9 */ - - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); -} - -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - *margin-top: 0; - line-height: normal; -} - -input[type="file"], -input[type="image"], -input[type="submit"], -input[type="reset"], -input[type="button"], -input[type="radio"], -input[type="checkbox"] { - width: auto; -} - -select, -input[type="file"] { - height: 30px; - /* In IE7, the height of the select element cannot be changed by height, only font-size */ - - *margin-top: 4px; - /* For IE7, add top margin to align select with labels */ - - line-height: 30px; -} - -select { - width: 220px; - background-color: #ffffff; - border: 1px solid #cccccc; -} - -select[multiple], -select[size] { - height: auto; -} - -select:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.uneditable-input, -.uneditable-textarea { - color: #999999; - cursor: not-allowed; - background-color: #fcfcfc; - border-color: #cccccc; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); -} - -.uneditable-input { - overflow: hidden; - white-space: nowrap; -} - -.uneditable-textarea { - width: auto; - height: auto; -} - -input:-moz-placeholder, -textarea:-moz-placeholder { - color: #999999; -} - -input:-ms-input-placeholder, -textarea:-ms-input-placeholder { - color: #999999; -} - -input::-webkit-input-placeholder, -textarea::-webkit-input-placeholder { - color: #999999; -} - -.radio, -.checkbox { - min-height: 20px; - padding-left: 20px; -} - -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: -20px; -} - -.controls > .radio:first-child, -.controls > .checkbox:first-child { - padding-top: 5px; -} - -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} - -.radio.inline + .radio.inline, -.checkbox.inline + .checkbox.inline { - margin-left: 10px; -} - -.input-mini { - width: 60px; -} - -.input-small { - width: 90px; -} - -.input-medium { - width: 150px; -} - -.input-large { - width: 210px; -} - -.input-xlarge { - width: 270px; -} - -.input-xxlarge { - width: 530px; -} - -input[class*="span"], -select[class*="span"], -textarea[class*="span"], -.uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"] { - float: none; - margin-left: 0; -} - -.input-append input[class*="span"], -.input-append .uneditable-input[class*="span"], -.input-prepend input[class*="span"], -.input-prepend .uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"], -.row-fluid .input-prepend [class*="span"], -.row-fluid .input-append [class*="span"] { - display: inline-block; -} - -input, -textarea, -.uneditable-input { - margin-left: 0; -} - -.controls-row [class*="span"] + [class*="span"] { - margin-left: 20px; -} - -input.span12, -textarea.span12, -.uneditable-input.span12 { - width: 926px; -} - -input.span11, -textarea.span11, -.uneditable-input.span11 { - width: 846px; -} - -input.span10, -textarea.span10, -.uneditable-input.span10 { - width: 766px; -} - -input.span9, -textarea.span9, -.uneditable-input.span9 { - width: 686px; -} - -input.span8, -textarea.span8, -.uneditable-input.span8 { - width: 606px; -} - -input.span7, -textarea.span7, -.uneditable-input.span7 { - width: 526px; -} - -input.span6, -textarea.span6, -.uneditable-input.span6 { - width: 446px; -} - -input.span5, -textarea.span5, -.uneditable-input.span5 { - width: 366px; -} - -input.span4, -textarea.span4, -.uneditable-input.span4 { - width: 286px; -} - -input.span3, -textarea.span3, -.uneditable-input.span3 { - width: 206px; -} - -input.span2, -textarea.span2, -.uneditable-input.span2 { - width: 126px; -} - -input.span1, -textarea.span1, -.uneditable-input.span1 { - width: 46px; -} - -.controls-row { - *zoom: 1; -} - -.controls-row:before, -.controls-row:after { - display: table; - line-height: 0; - content: ""; -} - -.controls-row:after { - clear: both; -} - -.controls-row [class*="span"], -.row-fluid .controls-row [class*="span"] { - float: left; -} - -.controls-row .checkbox[class*="span"], -.controls-row .radio[class*="span"] { - padding-top: 5px; -} - -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - cursor: not-allowed; - background-color: #f5f5f5; -} - -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"][readonly], -input[type="checkbox"][readonly] { - background-color: transparent; -} - -.control-group.warning .control-label, -.control-group.warning .help-block, -.control-group.warning .help-inline { - color: #dd5600; -} - -.control-group.warning .checkbox, -.control-group.warning .radio, -.control-group.warning input, -.control-group.warning select, -.control-group.warning textarea { - color: #dd5600; -} - -.control-group.warning input, -.control-group.warning select, -.control-group.warning textarea { - border-color: #dd5600; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.warning input:focus, -.control-group.warning select:focus, -.control-group.warning textarea:focus { - border-color: #aa4200; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ff8d44; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ff8d44; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ff8d44; -} - -.control-group.warning .input-prepend .add-on, -.control-group.warning .input-append .add-on { - color: #dd5600; - background-color: #f1ceab; - border-color: #dd5600; -} - -.control-group.error .control-label, -.control-group.error .help-block, -.control-group.error .help-inline { - color: #bd4247; -} - -.control-group.error .checkbox, -.control-group.error .radio, -.control-group.error input, -.control-group.error select, -.control-group.error textarea { - color: #bd4247; -} - -.control-group.error input, -.control-group.error select, -.control-group.error textarea { - border-color: #bd4247; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.error input:focus, -.control-group.error select:focus, -.control-group.error textarea:focus { - border-color: #983538; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90; -} - -.control-group.error .input-prepend .add-on, -.control-group.error .input-append .add-on { - color: #bd4247; - background-color: #f2bdb1; - border-color: #bd4247; -} - -.control-group.success .control-label, -.control-group.success .help-block, -.control-group.success .help-inline { - color: #669533; -} - -.control-group.success .checkbox, -.control-group.success .radio, -.control-group.success input, -.control-group.success select, -.control-group.success textarea { - color: #669533; -} - -.control-group.success input, -.control-group.success select, -.control-group.success textarea { - border-color: #669533; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.success input:focus, -.control-group.success select:focus, -.control-group.success textarea:focus { - border-color: #4c6f26; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63; -} - -.control-group.success .input-prepend .add-on, -.control-group.success .input-append .add-on { - color: #669533; - background-color: #d5ecbf; - border-color: #669533; -} - -.control-group.info .control-label, -.control-group.info .help-block, -.control-group.info .help-inline { - color: #178acc; -} - -.control-group.info .checkbox, -.control-group.info .radio, -.control-group.info input, -.control-group.info select, -.control-group.info textarea { - color: #178acc; -} - -.control-group.info input, -.control-group.info select, -.control-group.info textarea { - border-color: #178acc; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.info input:focus, -.control-group.info select:focus, -.control-group.info textarea:focus { - border-color: #126b9e; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #5db8ec; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #5db8ec; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #5db8ec; -} - -.control-group.info .input-prepend .add-on, -.control-group.info .input-append .add-on { - color: #178acc; - background-color: #a7dff1; - border-color: #178acc; -} - -input:focus:invalid, -textarea:focus:invalid, -select:focus:invalid { - color: #b94a48; - border-color: #ee5f5b; -} - -input:focus:invalid:focus, -textarea:focus:invalid:focus, -select:focus:invalid:focus { - border-color: #e9322d; - -webkit-box-shadow: 0 0 6px #f8b9b7; - -moz-box-shadow: 0 0 6px #f8b9b7; - box-shadow: 0 0 6px #f8b9b7; -} - -.form-actions { - padding: 19px 20px 20px; - margin-top: 20px; - margin-bottom: 20px; - background-color: #f5f5f5; - border-top: 1px solid #e5e5e5; - *zoom: 1; -} - -.form-actions:before, -.form-actions:after { - display: table; - line-height: 0; - content: ""; -} - -.form-actions:after { - clear: both; -} - -.help-block, -.help-inline { - color: #7b7b7b; -} - -.help-block { - display: block; - margin-bottom: 10px; -} - -.help-inline { - display: inline-block; - *display: inline; - padding-left: 5px; - vertical-align: middle; - *zoom: 1; -} - -.input-append, -.input-prepend { - display: inline-block; - margin-bottom: 10px; - font-size: 0; - white-space: nowrap; - vertical-align: middle; -} - -.input-append input, -.input-prepend input, -.input-append select, -.input-prepend select, -.input-append .uneditable-input, -.input-prepend .uneditable-input, -.input-append .dropdown-menu, -.input-prepend .dropdown-menu, -.input-append .popover, -.input-prepend .popover { - font-size: 14px; -} - -.input-append input, -.input-prepend input, -.input-append select, -.input-prepend select, -.input-append .uneditable-input, -.input-prepend .uneditable-input { - position: relative; - margin-bottom: 0; - *margin-left: 0; - vertical-align: top; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-append input:focus, -.input-prepend input:focus, -.input-append select:focus, -.input-prepend select:focus, -.input-append .uneditable-input:focus, -.input-prepend .uneditable-input:focus { - /* active element are not hidden if z-index is set to two*/ - /* z-index: 2; */ -} - -.input-append .add-on, -.input-prepend .add-on { - display: inline-block; - width: auto; - height: 20px; - min-width: 16px; - padding: 4px 5px; - font-size: 14px; - font-weight: normal; - line-height: 20px; - text-align: center; - text-shadow: 0 1px 0 #ffffff; - background-color: #f5f5f5; - border: 1px solid #ccc; -} - -.input-append .add-on, -.input-prepend .add-on, -.input-append .btn, -.input-prepend .btn, -.input-append .btn-group > .dropdown-toggle, -.input-prepend .btn-group > .dropdown-toggle { - vertical-align: top; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.input-append .active, -.input-prepend .active { - background-color: #bede9c; - border-color: #73a839; -} - -.input-prepend .add-on, -.input-prepend .btn { - margin-right: -1px; -} - -.input-prepend .add-on:first-child, -.input-prepend .btn:first-child { - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-append input, -.input-append select, -.input-append .uneditable-input { - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-append input + .btn-group .btn:last-child, -.input-append select + .btn-group .btn:last-child, -.input-append .uneditable-input + .btn-group .btn:last-child { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-append .add-on, -.input-append .btn, -.input-append .btn-group { - margin-left: -1px; -} - -.input-append .add-on:last-child, -.input-append .btn:last-child, -.input-append .btn-group:last-child > .dropdown-toggle { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append input, -.input-prepend.input-append select, -.input-prepend.input-append .uneditable-input { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.input-prepend.input-append input + .btn-group .btn, -.input-prepend.input-append select + .btn-group .btn, -.input-prepend.input-append .uneditable-input + .btn-group .btn { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append .add-on:first-child, -.input-prepend.input-append .btn:first-child { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-prepend.input-append .add-on:last-child, -.input-prepend.input-append .btn:last-child { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append .btn-group:first-child { - margin-left: 0; -} - -input.search-query { - padding-right: 14px; - padding-right: 4px \9; - padding-left: 14px; - padding-left: 4px \9; - /* IE7-8 doesn't have border-radius, so don't indent the padding */ - - margin-bottom: 0; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -/* Allow for input prepend/append in search forms */ - -.form-search .input-append .search-query, -.form-search .input-prepend .search-query { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.form-search .input-append .search-query { - -webkit-border-radius: 14px 0 0 14px; - -moz-border-radius: 14px 0 0 14px; - border-radius: 14px 0 0 14px; -} - -.form-search .input-append .btn { - -webkit-border-radius: 0 14px 14px 0; - -moz-border-radius: 0 14px 14px 0; - border-radius: 0 14px 14px 0; -} - -.form-search .input-prepend .search-query { - -webkit-border-radius: 0 14px 14px 0; - -moz-border-radius: 0 14px 14px 0; - border-radius: 0 14px 14px 0; -} - -.form-search .input-prepend .btn { - -webkit-border-radius: 14px 0 0 14px; - -moz-border-radius: 14px 0 0 14px; - border-radius: 14px 0 0 14px; -} - -.form-search input, -.form-inline input, -.form-horizontal input, -.form-search textarea, -.form-inline textarea, -.form-horizontal textarea, -.form-search select, -.form-inline select, -.form-horizontal select, -.form-search .help-inline, -.form-inline .help-inline, -.form-horizontal .help-inline, -.form-search .uneditable-input, -.form-inline .uneditable-input, -.form-horizontal .uneditable-input, -.form-search .input-prepend, -.form-inline .input-prepend, -.form-horizontal .input-prepend, -.form-search .input-append, -.form-inline .input-append, -.form-horizontal .input-append { - display: inline-block; - *display: inline; - margin-bottom: 0; - vertical-align: middle; - *zoom: 1; -} - -.form-search .hide, -.form-inline .hide, -.form-horizontal .hide { - display: none; -} - -.form-search label, -.form-inline label, -.form-search .btn-group, -.form-inline .btn-group { - display: inline-block; -} - -.form-search .input-append, -.form-inline .input-append, -.form-search .input-prepend, -.form-inline .input-prepend { - margin-bottom: 0; -} - -.form-search .radio, -.form-search .checkbox, -.form-inline .radio, -.form-inline .checkbox { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} - -.form-search .radio input[type="radio"], -.form-search .checkbox input[type="checkbox"], -.form-inline .radio input[type="radio"], -.form-inline .checkbox input[type="checkbox"] { - float: left; - margin-right: 3px; - margin-left: 0; -} - -.control-group { - margin-bottom: 10px; -} - -legend + .control-group { - margin-top: 20px; - -webkit-margin-top-collapse: separate; -} - -.form-horizontal .control-group { - margin-bottom: 20px; - *zoom: 1; -} - -.form-horizontal .control-group:before, -.form-horizontal .control-group:after { - display: table; - line-height: 0; - content: ""; -} - -.form-horizontal .control-group:after { - clear: both; -} - -.form-horizontal .control-label { - float: left; - width: 160px; - padding-top: 5px; - text-align: right; -} - -.form-horizontal .controls { - *display: inline-block; - *padding-left: 20px; - margin-left: 180px; - *margin-left: 0; -} - -.form-horizontal .controls:first-child { - *padding-left: 180px; -} - -.form-horizontal .help-block { - margin-bottom: 0; -} - -.form-horizontal input + .help-block, -.form-horizontal select + .help-block, -.form-horizontal textarea + .help-block, -.form-horizontal .uneditable-input + .help-block, -.form-horizontal .input-prepend + .help-block, -.form-horizontal .input-append + .help-block { - margin-top: 10px; -} - -.form-horizontal .form-actions { - padding-left: 180px; -} - -table { - max-width: 100%; - background-color: transparent; - border-collapse: collapse; - border-spacing: 0; -} - -.table { - width: 100%; - margin-bottom: 20px; -} - -.table th, -.table td { - padding: 8px; - line-height: 20px; - text-align: left; - vertical-align: top; - border-top: 1px solid #dddddd; -} - -.table th { - font-weight: bold; -} - -.table thead th { - vertical-align: bottom; -} - -.table caption + thead tr:first-child th, -.table caption + thead tr:first-child td, -.table colgroup + thead tr:first-child th, -.table colgroup + thead tr:first-child td, -.table thead:first-child tr:first-child th, -.table thead:first-child tr:first-child td { - border-top: 0; -} - -.table tbody + tbody { - border-top: 2px solid #dddddd; -} - -.table .table { - background-color: #ffffff; -} - -.table-condensed th, -.table-condensed td { - padding: 4px 5px; -} - -.table-bordered { - border: 1px solid #dddddd; - border-collapse: separate; - *border-collapse: collapse; - border-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.table-bordered th, -.table-bordered td { - border-left: 1px solid #dddddd; -} - -.table-bordered caption + thead tr:first-child th, -.table-bordered caption + tbody tr:first-child th, -.table-bordered caption + tbody tr:first-child td, -.table-bordered colgroup + thead tr:first-child th, -.table-bordered colgroup + tbody tr:first-child th, -.table-bordered colgroup + tbody tr:first-child td, -.table-bordered thead:first-child tr:first-child th, -.table-bordered tbody:first-child tr:first-child th, -.table-bordered tbody:first-child tr:first-child td { - border-top: 0; -} - -.table-bordered thead:first-child tr:first-child > th:first-child, -.table-bordered tbody:first-child tr:first-child > td:first-child, -.table-bordered tbody:first-child tr:first-child > th:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} - -.table-bordered thead:first-child tr:first-child > th:last-child, -.table-bordered tbody:first-child tr:first-child > td:last-child, -.table-bordered tbody:first-child tr:first-child > th:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} - -.table-bordered thead:last-child tr:last-child > th:first-child, -.table-bordered tbody:last-child tr:last-child > td:first-child, -.table-bordered tbody:last-child tr:last-child > th:first-child, -.table-bordered tfoot:last-child tr:last-child > td:first-child, -.table-bordered tfoot:last-child tr:last-child > th:first-child { - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; -} - -.table-bordered thead:last-child tr:last-child > th:last-child, -.table-bordered tbody:last-child tr:last-child > td:last-child, -.table-bordered tbody:last-child tr:last-child > th:last-child, -.table-bordered tfoot:last-child tr:last-child > td:last-child, -.table-bordered tfoot:last-child tr:last-child > th:last-child { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-bottomright: 4px; -} - -.table-bordered tfoot + tbody:last-child tr:last-child td:first-child { - -webkit-border-bottom-left-radius: 0; - border-bottom-left-radius: 0; - -moz-border-radius-bottomleft: 0; -} - -.table-bordered tfoot + tbody:last-child tr:last-child td:last-child { - -webkit-border-bottom-right-radius: 0; - border-bottom-right-radius: 0; - -moz-border-radius-bottomright: 0; -} - -.table-bordered caption + thead tr:first-child th:first-child, -.table-bordered caption + tbody tr:first-child td:first-child, -.table-bordered colgroup + thead tr:first-child th:first-child, -.table-bordered colgroup + tbody tr:first-child td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} - -.table-bordered caption + thead tr:first-child th:last-child, -.table-bordered caption + tbody tr:first-child td:last-child, -.table-bordered colgroup + thead tr:first-child th:last-child, -.table-bordered colgroup + tbody tr:first-child td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} - -.table-striped tbody > tr:nth-child(odd) > td, -.table-striped tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; -} - -.table-hover tbody tr:hover > td, -.table-hover tbody tr:hover > th { - background-color: #f5f5f5; -} - -table td[class*="span"], -table th[class*="span"], -.row-fluid table td[class*="span"], -.row-fluid table th[class*="span"] { - display: table-cell; - float: none; - margin-left: 0; -} - -.table td.span1, -.table th.span1 { - float: none; - width: 44px; - margin-left: 0; -} - -.table td.span2, -.table th.span2 { - float: none; - width: 124px; - margin-left: 0; -} - -.table td.span3, -.table th.span3 { - float: none; - width: 204px; - margin-left: 0; -} - -.table td.span4, -.table th.span4 { - float: none; - width: 284px; - margin-left: 0; -} - -.table td.span5, -.table th.span5 { - float: none; - width: 364px; - margin-left: 0; -} - -.table td.span6, -.table th.span6 { - float: none; - width: 444px; - margin-left: 0; -} - -.table td.span7, -.table th.span7 { - float: none; - width: 524px; - margin-left: 0; -} - -.table td.span8, -.table th.span8 { - float: none; - width: 604px; - margin-left: 0; -} - -.table td.span9, -.table th.span9 { - float: none; - width: 684px; - margin-left: 0; -} - -.table td.span10, -.table th.span10 { - float: none; - width: 764px; - margin-left: 0; -} - -.table td.span11, -.table th.span11 { - float: none; - width: 844px; - margin-left: 0; -} - -.table td.span12, -.table th.span12 { - float: none; - width: 924px; - margin-left: 0; -} - -.table tbody tr.success > td { - background-color: #d5ecbf; -} - -.table tbody tr.error > td { - background-color: #f2bdb1; -} - -.table tbody tr.warning > td { - background-color: #f1ceab; -} - -.table tbody tr.info > td { - background-color: #a7dff1; -} - -.table-hover tbody tr.success:hover > td { - background-color: #c8e6ab; -} - -.table-hover tbody tr.error:hover > td { - background-color: #eeab9b; -} - -.table-hover tbody tr.warning:hover > td { - background-color: #edc195; -} - -.table-hover tbody tr.info:hover > td { - background-color: #91d7ee; -} - - - - -.dropup, -.dropdown { - position: relative; -} - -.dropdown-toggle { - *margin-bottom: -3px; -} - -.dropdown-toggle:active, -.open .dropdown-toggle { - outline: 0; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid #000000; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; -} - -.dropdown .caret { - margin-top: 8px; - margin-left: 2px; -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - list-style: none; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - *border-right-width: 2px; - *border-bottom-width: 2px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.dropdown-menu .divider { - *width: 100%; - height: 1px; - margin: 9px 1px; - *margin: -5px 0 5px; - overflow: hidden; - background-color: #e5e5e5; - border-bottom: 1px solid #ffffff; -} - -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 20px; - color: #333333; - white-space: nowrap; -} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { - color: #ffffff; - text-decoration: none; - background-color: #27a0e5; - background-image: -moz-linear-gradient(top, #2fa4e7, #1a99e2); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#1a99e2)); - background-image: -webkit-linear-gradient(top, #2fa4e7, #1a99e2); - background-image: -o-linear-gradient(top, #2fa4e7, #1a99e2); - background-image: linear-gradient(to bottom, #2fa4e7, #1a99e2); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff1a99e2', GradientType=0); -} - -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #ffffff; - text-decoration: none; - background-color: #27a0e5; - background-image: -moz-linear-gradient(top, #2fa4e7, #1a99e2); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#1a99e2)); - background-image: -webkit-linear-gradient(top, #2fa4e7, #1a99e2); - background-image: -o-linear-gradient(top, #2fa4e7, #1a99e2); - background-image: linear-gradient(to bottom, #2fa4e7, #1a99e2); - background-repeat: repeat-x; - outline: 0; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff1a99e2', GradientType=0); -} - -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #999999; -} - -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: default; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.open { - *z-index: 1000; -} - -.open > .dropdown-menu { - display: block; -} - -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} - -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} - -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - border-top: 0; - border-bottom: 4px solid #000000; - content: ""; -} - -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; -} - -.dropdown-submenu { - position: relative; -} - -.dropdown-submenu > .dropdown-menu { - top: 0; - left: 100%; - margin-top: -6px; - margin-left: -1px; - -webkit-border-radius: 0 6px 6px 6px; - -moz-border-radius: 0 6px 6px 6px; - border-radius: 0 6px 6px 6px; -} - -.dropdown-submenu:hover > .dropdown-menu { - display: block; -} - -.dropup .dropdown-submenu > .dropdown-menu { - top: auto; - bottom: 0; - margin-top: 0; - margin-bottom: -2px; - -webkit-border-radius: 5px 5px 5px 0; - -moz-border-radius: 5px 5px 5px 0; - border-radius: 5px 5px 5px 0; -} - -.dropdown-submenu > a:after { - display: block; - float: right; - width: 0; - height: 0; - margin-top: 5px; - margin-right: -10px; - border-color: transparent; - border-left-color: #cccccc; - border-style: solid; - border-width: 5px 0 5px 5px; - content: " "; -} - -.dropdown-submenu:hover > a:after { - border-left-color: #ffffff; -} - -.dropdown-submenu.pull-left { - float: none; -} - -.dropdown-submenu.pull-left > .dropdown-menu { - left: -100%; - margin-left: 10px; - -webkit-border-radius: 6px 0 6px 6px; - -moz-border-radius: 6px 0 6px 6px; - border-radius: 6px 0 6px 6px; -} - -.dropdown .dropdown-menu .nav-header { - padding-right: 20px; - padding-left: 20px; -} - -.typeahead { - z-index: 1051; - margin-top: 2px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, 0.15); -} - -.well-large { - padding: 24px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.well-small { - padding: 9px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.fade { - opacity: 0; - -webkit-transition: opacity 0.15s linear; - -moz-transition: opacity 0.15s linear; - -o-transition: opacity 0.15s linear; - transition: opacity 0.15s linear; -} - -.fade.in { - opacity: 1; -} - -.collapse { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height 0.35s ease; - -moz-transition: height 0.35s ease; - -o-transition: height 0.35s ease; - transition: height 0.35s ease; -} - -.collapse.in { - height: auto; -} - -.close { - float: right; - font-size: 20px; - font-weight: bold; - line-height: 20px; - color: #000000; - text-shadow: 0 1px 0 #ffffff; - opacity: 0.2; - filter: alpha(opacity=20); -} - -.close:hover, -.close:focus { - color: #000000; - text-decoration: none; - cursor: pointer; - opacity: 0.4; - filter: alpha(opacity=40); -} - -button.close { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; -} - -.btn { - display: inline-block; - *display: inline; - padding: 4px 12px; - margin-bottom: 0; - *margin-left: .3em; - font-size: 14px; - line-height: 20px; - color: #333333; - text-align: center; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - vertical-align: middle; - cursor: pointer; - background-color: #f5f5f5; - *background-color: #e6e6e6; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); - background-repeat: repeat-x; - border: 1px solid #cccccc; - *border: 0; - border-color: #e6e6e6 #e6e6e6 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - border-bottom-color: #b3b3b3; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - *zoom: 1; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn:hover, -.btn:focus, -.btn:active, -.btn.active, -.btn.disabled, -.btn[disabled] { - color: #333333; - background-color: #e6e6e6; - *background-color: #d9d9d9; -} - -.btn:active, -.btn.active { - background-color: #cccccc \9; -} - -.btn:first-child { - *margin-left: 0; -} - -.btn:hover, -.btn:focus { - color: #333333; - text-decoration: none; - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; -} - -.btn:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.btn.active, -.btn:active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn.disabled, -.btn[disabled] { - cursor: default; - background-image: none; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -.btn-large { - padding: 11px 19px; - font-size: 17.5px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.btn-large [class^="icon-"], -.btn-large [class*=" icon-"] { - margin-top: 4px; -} - -.btn-small { - padding: 2px 10px; - font-size: 11.9px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.btn-small [class^="icon-"], -.btn-small [class*=" icon-"] { - margin-top: 0; -} - -.btn-mini [class^="icon-"], -.btn-mini [class*=" icon-"] { - margin-top: -1px; -} - -.btn-mini { - padding: 0 6px; - font-size: 10.5px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.btn-block { - display: block; - width: 100%; - padding-right: 0; - padding-left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.btn-block + .btn-block { - margin-top: 5px; -} - -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} - -.btn-primary.active, -.btn-warning.active, -.btn-danger.active, -.btn-success.active, -.btn-info.active, -.btn-inverse.active { - color: rgba(255, 255, 255, 0.75); -} - -.btn-primary { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #2f92e7; - *background-color: #2f76e7; - background-image: -moz-linear-gradient(top, #2fa4e7, #2f76e7); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#2f76e7)); - background-image: -webkit-linear-gradient(top, #2fa4e7, #2f76e7); - background-image: -o-linear-gradient(top, #2fa4e7, #2f76e7); - background-image: linear-gradient(to bottom, #2fa4e7, #2f76e7); - background-repeat: repeat-x; - border-color: #2f76e7 #2f76e7 #1553b5; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff2f76e7', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-primary:hover, -.btn-primary:focus, -.btn-primary:active, -.btn-primary.active, -.btn-primary.disabled, -.btn-primary[disabled] { - color: #ffffff; - background-color: #2f76e7; - *background-color: #1a67e2; -} - -.btn-primary:active, -.btn-primary.active { - background-color: #175dcc \9; -} - -.btn-warning { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #dd5600; - *background-color: #dd5600; - background-image: -moz-linear-gradient(top, #dd5600, #dd5600); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#dd5600), to(#dd5600)); - background-image: -webkit-linear-gradient(top, #dd5600, #dd5600); - background-image: -o-linear-gradient(top, #dd5600, #dd5600); - background-image: linear-gradient(to bottom, #dd5600, #dd5600); - background-repeat: repeat-x; - border-color: #dd5600 #dd5600 #913800; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd5600', endColorstr='#ffdd5600', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-warning:hover, -.btn-warning:focus, -.btn-warning:active, -.btn-warning.active, -.btn-warning.disabled, -.btn-warning[disabled] { - color: #ffffff; - background-color: #dd5600; - *background-color: #c44c00; -} - -.btn-warning:active, -.btn-warning.active { - background-color: #aa4200 \9; -} - -.btn-danger { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #c32627; - *background-color: #bd362f; - background-image: -moz-linear-gradient(top, #c71c22, #bd362f); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#c71c22), to(#bd362f)); - background-image: -webkit-linear-gradient(top, #c71c22, #bd362f); - background-image: -o-linear-gradient(top, #c71c22, #bd362f); - background-image: linear-gradient(to bottom, #c71c22, #bd362f); - background-repeat: repeat-x; - border-color: #bd362f #bd362f #802420; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc71c22', endColorstr='#ffbd362f', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-danger:hover, -.btn-danger:focus, -.btn-danger:active, -.btn-danger.active, -.btn-danger.disabled, -.btn-danger[disabled] { - color: #ffffff; - background-color: #bd362f; - *background-color: #a9302a; -} - -.btn-danger:active, -.btn-danger.active { - background-color: #942a25 \9; -} - -.btn-success { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #65a643; - *background-color: #51a351; - background-image: -moz-linear-gradient(top, #73a839, #51a351); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#73a839), to(#51a351)); - background-image: -webkit-linear-gradient(top, #73a839, #51a351); - background-image: -o-linear-gradient(top, #73a839, #51a351); - background-image: linear-gradient(to bottom, #73a839, #51a351); - background-repeat: repeat-x; - border-color: #51a351 #51a351 #387038; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff73a839', endColorstr='#ff51a351', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-success:hover, -.btn-success:focus, -.btn-success:active, -.btn-success.active, -.btn-success.disabled, -.btn-success[disabled] { - color: #ffffff; - background-color: #51a351; - *background-color: #499249; -} - -.btn-success:active, -.btn-success.active { - background-color: #408140 \9; -} - -.btn-info { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #6d76b3; - *background-color: #2f96b4; - background-image: -moz-linear-gradient(top, #9760b3, #2f96b4); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#9760b3), to(#2f96b4)); - background-image: -webkit-linear-gradient(top, #9760b3, #2f96b4); - background-image: -o-linear-gradient(top, #9760b3, #2f96b4); - background-image: linear-gradient(to bottom, #9760b3, #2f96b4); - background-repeat: repeat-x; - border-color: #2f96b4 #2f96b4 #1f6377; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff9760b3', endColorstr='#ff2f96b4', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-info:hover, -.btn-info:focus, -.btn-info:active, -.btn-info.active, -.btn-info.disabled, -.btn-info[disabled] { - color: #ffffff; - background-color: #2f96b4; - *background-color: #2a85a0; -} - -.btn-info:active, -.btn-info.active { - background-color: #24748c \9; -} - -.btn-inverse { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #0f3253; - *background-color: #222222; - background-image: -moz-linear-gradient(top, #033c73, #222222); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#033c73), to(#222222)); - background-image: -webkit-linear-gradient(top, #033c73, #222222); - background-image: -o-linear-gradient(top, #033c73, #222222); - background-image: linear-gradient(to bottom, #033c73, #222222); - background-repeat: repeat-x; - border-color: #222222 #222222 #000000; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff033c73', endColorstr='#ff222222', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-inverse:hover, -.btn-inverse:focus, -.btn-inverse:active, -.btn-inverse.active, -.btn-inverse.disabled, -.btn-inverse[disabled] { - color: #ffffff; - background-color: #222222; - *background-color: #151515; -} - -.btn-inverse:active, -.btn-inverse.active { - background-color: #080808 \9; -} - -button.btn, -input[type="submit"].btn { - *padding-top: 3px; - *padding-bottom: 3px; -} - -button.btn::-moz-focus-inner, -input[type="submit"].btn::-moz-focus-inner { - padding: 0; - border: 0; -} - -button.btn.btn-large, -input[type="submit"].btn.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; -} - -button.btn.btn-small, -input[type="submit"].btn.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; -} - -button.btn.btn-mini, -input[type="submit"].btn.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; -} - -.btn-link, -.btn-link:active, -.btn-link[disabled] { - background-color: transparent; - background-image: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -.btn-link { - color: #2fa4e7; - cursor: pointer; - border-color: transparent; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-link:hover, -.btn-link:focus { - color: #157ab5; - text-decoration: underline; - background-color: transparent; -} - -.btn-link[disabled]:hover, -.btn-link[disabled]:focus { - color: #333333; - text-decoration: none; -} - -.btn-group { - position: relative; - display: inline-block; - *display: inline; - *margin-left: .3em; - font-size: 0; - white-space: nowrap; - vertical-align: middle; - *zoom: 1; -} - -.btn-group:first-child { - *margin-left: 0; -} - -.btn-group + .btn-group { - margin-left: 5px; -} - -.btn-toolbar { - margin-top: 10px; - margin-bottom: 10px; - font-size: 0; -} - -.btn-toolbar > .btn + .btn, -.btn-toolbar > .btn-group + .btn, -.btn-toolbar > .btn + .btn-group { - margin-left: 5px; -} - -.btn-group > .btn { - position: relative; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-group > .btn + .btn { - margin-left: -1px; -} - -.btn-group > .btn, -.btn-group > .dropdown-menu, -.btn-group > .popover { - font-size: 14px; -} - -.btn-group > .btn-mini { - font-size: 10.5px; -} - -.btn-group > .btn-small { - font-size: 11.9px; -} - -.btn-group > .btn-large { - font-size: 17.5px; -} - -.btn-group > .btn:first-child { - margin-left: 0; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 4px; -} - -.btn-group > .btn:last-child, -.btn-group > .dropdown-toggle { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; -} - -.btn-group > .btn.large:first-child { - margin-left: 0; - -webkit-border-bottom-left-radius: 6px; - border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; -} - -.btn-group > .btn.large:last-child, -.btn-group > .large.dropdown-toggle { - -webkit-border-top-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-topright: 6px; - -moz-border-radius-bottomright: 6px; -} - -.btn-group > .btn:hover, -.btn-group > .btn:focus, -.btn-group > .btn:active, -.btn-group > .btn.active { - /* active element are not hidden if z-index is set to two*/ - /*z-index: 2;*/ -} - -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} - -.btn-group > .btn + .dropdown-toggle { - *padding-top: 5px; - padding-right: 8px; - *padding-bottom: 5px; - padding-left: 8px; - -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group > .btn-mini + .dropdown-toggle { - *padding-top: 2px; - padding-right: 5px; - *padding-bottom: 2px; - padding-left: 5px; -} - -.btn-group > .btn-small + .dropdown-toggle { - *padding-top: 5px; - *padding-bottom: 4px; -} - -.btn-group > .btn-large + .dropdown-toggle { - *padding-top: 7px; - padding-right: 12px; - *padding-bottom: 7px; - padding-left: 12px; -} - -.btn-group.open .dropdown-toggle { - background-image: none; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group.open .btn.dropdown-toggle { - background-color: #e6e6e6; -} - -.btn-group.open .btn-primary.dropdown-toggle { - background-color: #2f76e7; -} - -.btn-group.open .btn-warning.dropdown-toggle { - background-color: #dd5600; -} - -.btn-group.open .btn-danger.dropdown-toggle { - background-color: #bd362f; -} - -.btn-group.open .btn-success.dropdown-toggle { - background-color: #51a351; -} - -.btn-group.open .btn-info.dropdown-toggle { - background-color: #2f96b4; -} - -.btn-group.open .btn-inverse.dropdown-toggle { - background-color: #222222; -} - -.btn .caret { - margin-top: 8px; - margin-left: 0; -} - -.btn-large .caret { - margin-top: 6px; -} - -.btn-large .caret { - border-top-width: 5px; - border-right-width: 5px; - border-left-width: 5px; -} - -.btn-mini .caret, -.btn-small .caret { - margin-top: 8px; -} - -.dropup .btn-large .caret { - border-bottom-width: 5px; -} - -.btn-primary .caret, -.btn-warning .caret, -.btn-danger .caret, -.btn-info .caret, -.btn-success .caret, -.btn-inverse .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.btn-group-vertical { - display: inline-block; - *display: inline; - /* IE7 inline-block hack */ - - *zoom: 1; -} - -.btn-group-vertical > .btn { - display: block; - float: none; - max-width: 100%; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-group-vertical > .btn + .btn { - margin-top: -1px; - margin-left: 0; -} - -.btn-group-vertical > .btn:first-child { - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} - -.btn-group-vertical > .btn:last-child { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.btn-group-vertical > .btn-large:first-child { - -webkit-border-radius: 6px 6px 0 0; - -moz-border-radius: 6px 6px 0 0; - border-radius: 6px 6px 0 0; -} - -.btn-group-vertical > .btn-large:last-child { - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; -} - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 20px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #f1ceab; - border: 1px solid #efb99e; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.alert, -.alert h4 { - color: #dd5600; -} - -.alert h4 { - margin: 0; -} - -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 20px; -} - -.alert-success { - color: #669533; - background-color: #d5ecbf; - border-color: #d2e6ab; -} - -.alert-success h4 { - color: #669533; -} - -.alert-danger, -.alert-error { - color: #bd4247; - background-color: #f2bdb1; - border-color: #f0a5a4; -} - -.alert-danger h4, -.alert-error h4 { - color: #bd4247; -} - -.alert-info { - color: #178acc; - background-color: #a7dff1; - border-color: #88e4ec; -} - -.alert-info h4 { - color: #178acc; -} - -.alert-block { - padding-top: 14px; - padding-bottom: 14px; -} - -.alert-block > p, -.alert-block > ul { - margin-bottom: 0; -} - -.alert-block p + p { - margin-top: 5px; -} - -.nav { - margin-bottom: 20px; - margin-left: 0; - list-style: none; -} - -.nav > li > a { - display: block; -} - -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #f5f5f5; -} - -.nav > li > a > img { - max-width: none; -} - -.nav > .pull-right { - float: right; -} - -.nav-header { - display: block; - padding: 3px 15px; - font-size: 11px; - font-weight: bold; - line-height: 20px; - color: #999999; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-transform: uppercase; -} - -.nav li + .nav-header { - margin-top: 9px; -} - -.nav-list { - padding-right: 15px; - padding-left: 15px; - margin-bottom: 0; -} - -.nav-list > li > a, -.nav-list .nav-header { - margin-right: -15px; - margin-left: -15px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} - -.nav-list > li > a { - padding: 3px 15px; -} - -.nav-list > .active > a, -.nav-list > .active > a:hover, -.nav-list > .active > a:focus { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); - background-color: #2fa4e7; -} - -.nav-list [class^="icon-"], -.nav-list [class*=" icon-"] { - margin-right: 2px; -} - -.nav-list .divider { - *width: 100%; - height: 1px; - margin: 9px 1px; - *margin: -5px 0 5px; - overflow: hidden; - background-color: #e5e5e5; - border-bottom: 1px solid #ffffff; -} - -.nav-tabs, -.nav-pills { - *zoom: 1; -} - -.nav-tabs:before, -.nav-pills:before, -.nav-tabs:after, -.nav-pills:after { - display: table; - line-height: 0; - content: ""; -} - -.nav-tabs:after, -.nav-pills:after { - clear: both; -} - -.nav-tabs > li, -.nav-pills > li { - float: left; -} - -.nav-tabs > li > a, -.nav-pills > li > a { - padding-right: 12px; - padding-left: 12px; - margin-right: 2px; - line-height: 14px; -} - -.nav-tabs { - border-bottom: 1px solid #ddd; -} - -.nav-tabs > li { - margin-bottom: -1px; -} - -.nav-tabs > li > a { - padding-top: 8px; - padding-bottom: 8px; - line-height: 20px; - border: 1px solid transparent; - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} - -.nav-tabs > li > a:hover, -.nav-tabs > li > a:focus { - border-color: #f5f5f5 #f5f5f5 #dddddd; -} - -.nav-tabs > .active > a, -.nav-tabs > .active > a:hover, -.nav-tabs > .active > a:focus { - color: #555555; - cursor: default; - background-color: #ffffff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} - -.nav-pills > li > a { - padding-top: 8px; - padding-bottom: 8px; - margin-top: 2px; - margin-bottom: 2px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} - -.nav-pills > .active > a, -.nav-pills > .active > a:hover, -.nav-pills > .active > a:focus { - color: #ffffff; - background-color: #2fa4e7; -} - -.nav-stacked > li { - float: none; -} - -.nav-stacked > li > a { - margin-right: 0; -} - -.nav-tabs.nav-stacked { - border-bottom: 0; -} - -.nav-tabs.nav-stacked > li > a { - border: 1px solid #ddd; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.nav-tabs.nav-stacked > li:first-child > a { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-topleft: 4px; -} - -.nav-tabs.nav-stacked > li:last-child > a { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomright: 4px; - -moz-border-radius-bottomleft: 4px; -} - -.nav-tabs.nav-stacked > li > a:hover, -.nav-tabs.nav-stacked > li > a:focus { - z-index: 2; - border-color: #ddd; -} - -.nav-pills.nav-stacked > li > a { - margin-bottom: 3px; -} - -.nav-pills.nav-stacked > li:last-child > a { - margin-bottom: 1px; -} - -.nav-tabs .dropdown-menu { - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; -} - -.nav-pills .dropdown-menu { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.nav .dropdown-toggle .caret { - margin-top: 6px; - border-top-color: #2fa4e7; - border-bottom-color: #2fa4e7; -} - -.nav .dropdown-toggle:hover .caret, -.nav .dropdown-toggle:focus .caret { - border-top-color: #157ab5; - border-bottom-color: #157ab5; -} - -/* move down carets for tabs */ - -.nav-tabs .dropdown-toggle .caret { - margin-top: 8px; -} - -.nav .active .dropdown-toggle .caret { - border-top-color: #fff; - border-bottom-color: #fff; -} - -.nav-tabs .active .dropdown-toggle .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.nav > .dropdown.active > a:hover, -.nav > .dropdown.active > a:focus { - cursor: pointer; -} - -.nav-tabs .open .dropdown-toggle, -.nav-pills .open .dropdown-toggle, -.nav > li.dropdown.open.active > a:hover, -.nav > li.dropdown.open.active > a:focus { - color: #ffffff; - background-color: #999999; - border-color: #999999; -} - -.nav li.dropdown.open .caret, -.nav li.dropdown.open.active .caret, -.nav li.dropdown.open a:hover .caret, -.nav li.dropdown.open a:focus .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; - opacity: 1; - filter: alpha(opacity=100); -} - -.tabs-stacked .open > a:hover, -.tabs-stacked .open > a:focus { - border-color: #999999; -} - -.tabbable { - *zoom: 1; -} - -.tabbable:before, -.tabbable:after { - display: table; - line-height: 0; - content: ""; -} - -.tabbable:after { - clear: both; -} - -.tab-content { - overflow: auto; -} - -.tabs-below > .nav-tabs, -.tabs-right > .nav-tabs, -.tabs-left > .nav-tabs { - border-bottom: 0; -} - -.tab-content > .tab-pane, -.pill-content > .pill-pane { - display: none; -} - -.tab-content > .active, -.pill-content > .active { - display: block; -} - -.tabs-below > .nav-tabs { - border-top: 1px solid #ddd; -} - -.tabs-below > .nav-tabs > li { - margin-top: -1px; - margin-bottom: 0; -} - -.tabs-below > .nav-tabs > li > a { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.tabs-below > .nav-tabs > li > a:hover, -.tabs-below > .nav-tabs > li > a:focus { - border-top-color: #ddd; - border-bottom-color: transparent; -} - -.tabs-below > .nav-tabs > .active > a, -.tabs-below > .nav-tabs > .active > a:hover, -.tabs-below > .nav-tabs > .active > a:focus { - border-color: transparent #ddd #ddd #ddd; -} - -.tabs-left > .nav-tabs > li, -.tabs-right > .nav-tabs > li { - float: none; -} - -.tabs-left > .nav-tabs > li > a, -.tabs-right > .nav-tabs > li > a { - min-width: 74px; - margin-right: 0; - margin-bottom: 3px; -} - -.tabs-left > .nav-tabs { - float: left; - margin-right: 19px; - border-right: 1px solid #ddd; -} - -.tabs-left > .nav-tabs > li > a { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.tabs-left > .nav-tabs > li > a:hover, -.tabs-left > .nav-tabs > li > a:focus { - border-color: #f5f5f5 #dddddd #f5f5f5 #f5f5f5; -} - -.tabs-left > .nav-tabs .active > a, -.tabs-left > .nav-tabs .active > a:hover, -.tabs-left > .nav-tabs .active > a:focus { - border-color: #ddd transparent #ddd #ddd; - *border-right-color: #ffffff; -} - -.tabs-right > .nav-tabs { - float: right; - margin-left: 19px; - border-left: 1px solid #ddd; -} - -.tabs-right > .nav-tabs > li > a { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.tabs-right > .nav-tabs > li > a:hover, -.tabs-right > .nav-tabs > li > a:focus { - border-color: #f5f5f5 #f5f5f5 #f5f5f5 #dddddd; -} - -.tabs-right > .nav-tabs .active > a, -.tabs-right > .nav-tabs .active > a:hover, -.tabs-right > .nav-tabs .active > a:focus { - border-color: #ddd #ddd #ddd transparent; - *border-left-color: #ffffff; -} - -.nav > .disabled > a { - color: #999999; -} - -.nav > .disabled > a:hover, -.nav > .disabled > a:focus { - text-decoration: none; - cursor: default; - background-color: transparent; -} - -.navbar { - *position: relative; - *z-index: 2; - margin-bottom: 20px; - overflow: visible; -} - -.navbar-inner { - min-height: 50px; - padding-right: 20px; - padding-left: 20px; - background-color: #45aeea; - background-image: -moz-linear-gradient(top, #54b4eb, #2fa4e7); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#54b4eb), to(#2fa4e7)); - background-image: -webkit-linear-gradient(top, #54b4eb, #2fa4e7); - background-image: -o-linear-gradient(top, #54b4eb, #2fa4e7); - background-image: linear-gradient(to bottom, #54b4eb, #2fa4e7); - background-repeat: repeat-x; - border: 1px solid #1990d5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff54b4eb', endColorstr='#ff2fa4e7', GradientType=0); - *zoom: 1; - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); -} - -.navbar-inner:before, -.navbar-inner:after { - display: table; - line-height: 0; - content: ""; -} - -.navbar-inner:after { - clear: both; -} - -.navbar .container { - width: auto; -} - -.nav-collapse.collapse { - height: auto; - overflow: visible; -} - -.navbar .brand { - display: block; - float: left; - padding: 15px 20px 15px; - margin-left: -20px; - font-size: 20px; - font-weight: 200; - color: #ffffff; - text-shadow: 0 1px 0 #54b4eb; -} - -.navbar .brand:hover, -.navbar .brand:focus { - text-decoration: none; -} - -.navbar-text { - margin-bottom: 0; - line-height: 50px; - color: #f5f5f5; -} - -.navbar-link { - color: #ffffff; -} - -.navbar-link:hover, -.navbar-link:focus { - color: #ffffff; -} - -.navbar .divider-vertical { - height: 50px; - margin: 0 9px; - border-right: 1px solid #54b4eb; - border-left: 1px solid #2fa4e7; -} - -.navbar .btn, -.navbar .btn-group { - margin-top: 10px; -} - -.navbar .btn-group .btn, -.navbar .input-prepend .btn, -.navbar .input-append .btn, -.navbar .input-prepend .btn-group, -.navbar .input-append .btn-group { - margin-top: 0; -} - -.navbar-form { - margin-bottom: 0; - *zoom: 1; -} - -.navbar-form:before, -.navbar-form:after { - display: table; - line-height: 0; - content: ""; -} - -.navbar-form:after { - clear: both; -} - -.navbar-form input, -.navbar-form select, -.navbar-form .radio, -.navbar-form .checkbox { - margin-top: 10px; -} - -.navbar-form input, -.navbar-form select, -.navbar-form .btn { - display: inline-block; - margin-bottom: 0; -} - -.navbar-form input[type="image"], -.navbar-form input[type="checkbox"], -.navbar-form input[type="radio"] { - margin-top: 3px; -} - -.navbar-form .input-append, -.navbar-form .input-prepend { - margin-top: 5px; - white-space: nowrap; -} - -.navbar-form .input-append input, -.navbar-form .input-prepend input { - margin-top: 0; -} - -.navbar-search { - position: relative; - float: left; - margin-top: 10px; - margin-bottom: 0; -} - -.navbar-search .search-query { - padding: 4px 14px; - margin-bottom: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: 1; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.navbar-static-top { - position: static; - margin-bottom: 0; -} - -.navbar-static-top .navbar-inner { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; - margin-bottom: 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - border-width: 0 0 1px; -} - -.navbar-fixed-bottom .navbar-inner { - border-width: 1px 0 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-fixed-bottom .navbar-inner { - padding-right: 0; - padding-left: 0; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} - -.navbar-fixed-top { - top: 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); -} - -.navbar-fixed-bottom { - bottom: 0; -} - -.navbar-fixed-bottom .navbar-inner { - -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); - box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); -} - -.navbar .nav { - position: relative; - left: 0; - display: block; - float: left; - margin: 0 10px 0 0; -} - -.navbar .nav.pull-right { - float: right; - margin-right: 0; -} - -.navbar .nav > li { - float: left; -} - -.navbar .nav > li > a { - float: none; - padding: 15px 15px 15px; - color: #ffffff; - text-decoration: none; - text-shadow: 0 1px 0 #54b4eb; -} - -.navbar .nav .dropdown-toggle .caret { - margin-top: 8px; -} - -.navbar .nav > li > a:focus, -.navbar .nav > li > a:hover { - color: #ffffff; - text-decoration: none; - background-color: #1684c2; -} - -.navbar .nav > .active > a, -.navbar .nav > .active > a:hover, -.navbar .nav > .active > a:focus { - color: #ffffff; - text-decoration: none; - background-color: #1684c2; - -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); - -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); -} - -.navbar .btn-navbar { - display: none; - float: right; - padding: 7px 10px; - margin-right: 5px; - margin-left: 5px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #2fa3e6; - *background-color: #1a99e2; - background-image: -moz-linear-gradient(top, #3daae9, #1a99e2); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#3daae9), to(#1a99e2)); - background-image: -webkit-linear-gradient(top, #3daae9, #1a99e2); - background-image: -o-linear-gradient(top, #3daae9, #1a99e2); - background-image: linear-gradient(to bottom, #3daae9, #1a99e2); - background-repeat: repeat-x; - border-color: #1a99e2 #1a99e2 #126b9e; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3daae9', endColorstr='#ff1a99e2', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); -} - -.navbar .btn-navbar:hover, -.navbar .btn-navbar:focus, -.navbar .btn-navbar:active, -.navbar .btn-navbar.active, -.navbar .btn-navbar.disabled, -.navbar .btn-navbar[disabled] { - color: #ffffff; - background-color: #1a99e2; - *background-color: #178acc; -} - -.navbar .btn-navbar:active, -.navbar .btn-navbar.active { - background-color: #157ab5 \9; -} - -.navbar .btn-navbar .icon-bar { - display: block; - width: 18px; - height: 2px; - background-color: #f5f5f5; - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; - -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); -} - -.btn-navbar .icon-bar + .icon-bar { - margin-top: 3px; -} - -.navbar .nav > li > .dropdown-menu:before { - position: absolute; - top: -7px; - left: 9px; - display: inline-block; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-left: 7px solid transparent; - border-bottom-color: rgba(0, 0, 0, 0.2); - content: ''; -} - -.navbar .nav > li > .dropdown-menu:after { - position: absolute; - top: -6px; - left: 10px; - display: inline-block; - border-right: 6px solid transparent; - border-bottom: 6px solid #ffffff; - border-left: 6px solid transparent; - content: ''; -} - -.navbar-fixed-bottom .nav > li > .dropdown-menu:before { - top: auto; - bottom: -7px; - border-top: 7px solid #ccc; - border-bottom: 0; - border-top-color: rgba(0, 0, 0, 0.2); -} - -.navbar-fixed-bottom .nav > li > .dropdown-menu:after { - top: auto; - bottom: -6px; - border-top: 6px solid #ffffff; - border-bottom: 0; -} - -.navbar .nav li.dropdown > a:hover .caret, -.navbar .nav li.dropdown > a:focus .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar .nav li.dropdown.open > .dropdown-toggle, -.navbar .nav li.dropdown.active > .dropdown-toggle, -.navbar .nav li.dropdown.open.active > .dropdown-toggle { - color: #ffffff; - background-color: #1684c2; -} - -.navbar .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar .pull-right > li > .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu:before, -.navbar .nav > li > .dropdown-menu.pull-right:before { - right: 12px; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu:after, -.navbar .nav > li > .dropdown-menu.pull-right:after { - right: 13px; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu { - right: 100%; - left: auto; - margin-right: -1px; - margin-left: 0; - -webkit-border-radius: 6px 0 6px 6px; - -moz-border-radius: 6px 0 6px 6px; - border-radius: 6px 0 6px 6px; -} - -.navbar-inverse .navbar-inner { - background-color: #034482; - background-image: -moz-linear-gradient(top, #04498c, #033c73); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#04498c), to(#033c73)); - background-image: -webkit-linear-gradient(top, #04498c, #033c73); - background-image: -o-linear-gradient(top, #04498c, #033c73); - background-image: linear-gradient(to bottom, #04498c, #033c73); - background-repeat: repeat-x; - border-color: #033464; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04498c', endColorstr='#ff033c73', GradientType=0); -} - -.navbar-inverse .brand, -.navbar-inverse .nav > li > a { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} - -.navbar-inverse .brand:hover, -.navbar-inverse .nav > li > a:hover, -.navbar-inverse .brand:focus, -.navbar-inverse .nav > li > a:focus { - color: #ffffff; -} - -.navbar-inverse .brand { - color: #ffffff; -} - -.navbar-inverse .navbar-text { - color: #ffffff; -} - -.navbar-inverse .nav > li > a:focus, -.navbar-inverse .nav > li > a:hover { - color: #ffffff; - background-color: #022c55; -} - -.navbar-inverse .nav .active > a, -.navbar-inverse .nav .active > a:hover, -.navbar-inverse .nav .active > a:focus { - color: #ffffff; - background-color: #022c55; -} - -.navbar-inverse .navbar-link { - color: #ffffff; -} - -.navbar-inverse .navbar-link:hover, -.navbar-inverse .navbar-link:focus { - color: #ffffff; -} - -.navbar-inverse .divider-vertical { - border-right-color: #04498c; - border-left-color: #033c73; -} - -.navbar-inverse .nav li.dropdown.open > .dropdown-toggle, -.navbar-inverse .nav li.dropdown.active > .dropdown-toggle, -.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { - color: #ffffff; - background-color: #022c55; -} - -.navbar-inverse .nav li.dropdown > a:hover .caret, -.navbar-inverse .nav li.dropdown > a:focus .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .navbar-search .search-query { - color: #ffffff; - background-color: #ffffff; - border-color: #033c73; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - -webkit-transition: none; - -moz-transition: none; - -o-transition: none; - transition: none; -} - -.navbar-inverse .navbar-search .search-query:-moz-placeholder { - color: #999999; -} - -.navbar-inverse .navbar-search .search-query:-ms-input-placeholder { - color: #999999; -} - -.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder { - color: #999999; -} - -.navbar-inverse .navbar-search .search-query:focus, -.navbar-inverse .navbar-search .search-query.focused { - padding: 5px 15px; - color: #333333; - text-shadow: 0 1px 0 #ffffff; - background-color: #ffffff; - border: 0; - outline: 0; - -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); -} - -.navbar-inverse .btn-navbar { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #033769; - *background-color: #022f5a; - background-image: -moz-linear-gradient(top, #033c73, #022f5a); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#033c73), to(#022f5a)); - background-image: -webkit-linear-gradient(top, #033c73, #022f5a); - background-image: -o-linear-gradient(top, #033c73, #022f5a); - background-image: linear-gradient(to bottom, #033c73, #022f5a); - background-repeat: repeat-x; - border-color: #022f5a #022f5a #000810; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff033c73', endColorstr='#ff022f5a', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.navbar-inverse .btn-navbar:hover, -.navbar-inverse .btn-navbar:focus, -.navbar-inverse .btn-navbar:active, -.navbar-inverse .btn-navbar.active, -.navbar-inverse .btn-navbar.disabled, -.navbar-inverse .btn-navbar[disabled] { - color: #ffffff; - background-color: #022f5a; - *background-color: #022241; -} - -.navbar-inverse .btn-navbar:active, -.navbar-inverse .btn-navbar.active { - background-color: #011528 \9; -} - -.breadcrumb { - padding: 8px 15px; - margin: 0 0 20px; - list-style: none; - background-color: #f5f5f5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.breadcrumb > li { - display: inline-block; - *display: inline; - text-shadow: 0 1px 0 #ffffff; - *zoom: 1; -} - -.breadcrumb > li > .divider { - padding: 0 5px; - color: #ccc; -} - -.breadcrumb > .active { - color: #999999; -} - -.pagination { - margin: 20px 0; -} - -.pagination ul { - display: inline-block; - *display: inline; - margin-bottom: 0; - margin-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - *zoom: 1; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.pagination ul > li { - display: inline; -} - -.pagination ul > li > a, -.pagination ul > li > span { - float: left; - padding: 4px 12px; - line-height: 20px; - text-decoration: none; - background-color: #ffffff; - border: 1px solid #dddddd; - border-left-width: 0; -} - -.pagination ul > li > a:hover, -.pagination ul > li > a:focus, -.pagination ul > .active > a, -.pagination ul > .active > span { - background-color: #f5f5f5; -} - -.pagination ul > .active > a, -.pagination ul > .active > span { - color: #999999; - cursor: default; -} - -.pagination ul > .disabled > span, -.pagination ul > .disabled > a, -.pagination ul > .disabled > a:hover, -.pagination ul > .disabled > a:focus { - color: #999999; - cursor: default; - background-color: transparent; -} - -.pagination ul > li:first-child > a, -.pagination ul > li:first-child > span { - border-left-width: 1px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 4px; -} - -.pagination ul > li:last-child > a, -.pagination ul > li:last-child > span { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; -} - -.pagination-centered { - text-align: center; -} - -.pagination-right { - text-align: right; -} - -.pagination-large ul > li > a, -.pagination-large ul > li > span { - padding: 11px 19px; - font-size: 17.5px; -} - -.pagination-large ul > li:first-child > a, -.pagination-large ul > li:first-child > span { - -webkit-border-bottom-left-radius: 6px; - border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; -} - -.pagination-large ul > li:last-child > a, -.pagination-large ul > li:last-child > span { - -webkit-border-top-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-topright: 6px; - -moz-border-radius-bottomright: 6px; -} - -.pagination-mini ul > li:first-child > a, -.pagination-small ul > li:first-child > a, -.pagination-mini ul > li:first-child > span, -.pagination-small ul > li:first-child > span { - -webkit-border-bottom-left-radius: 3px; - border-bottom-left-radius: 3px; - -webkit-border-top-left-radius: 3px; - border-top-left-radius: 3px; - -moz-border-radius-bottomleft: 3px; - -moz-border-radius-topleft: 3px; -} - -.pagination-mini ul > li:last-child > a, -.pagination-small ul > li:last-child > a, -.pagination-mini ul > li:last-child > span, -.pagination-small ul > li:last-child > span { - -webkit-border-top-right-radius: 3px; - border-top-right-radius: 3px; - -webkit-border-bottom-right-radius: 3px; - border-bottom-right-radius: 3px; - -moz-border-radius-topright: 3px; - -moz-border-radius-bottomright: 3px; -} - -.pagination-small ul > li > a, -.pagination-small ul > li > span { - padding: 2px 10px; - font-size: 11.9px; -} - -.pagination-mini ul > li > a, -.pagination-mini ul > li > span { - padding: 0 6px; - font-size: 10.5px; -} - -.pager { - margin: 20px 0; - text-align: center; - list-style: none; - *zoom: 1; -} - -.pager:before, -.pager:after { - display: table; - line-height: 0; - content: ""; -} - -.pager:after { - clear: both; -} - -.pager li { - display: inline; -} - -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #f5f5f5; -} - -.pager .next > a, -.pager .next > span { - float: right; -} - -.pager .previous > a, -.pager .previous > span { - float: left; -} - -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #999999; - cursor: default; - background-color: #fff; -} - -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000000; -} - -.modal-backdrop.fade { - opacity: 0; -} - -.modal-backdrop, -.modal-backdrop.fade.in { - opacity: 0.8; - filter: alpha(opacity=80); -} - -.modal { - position: fixed; - top: 10%; - left: 50%; - z-index: 1050; - width: 560px; - margin-left: -280px; - background-color: #ffffff; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, 0.3); - *border: 1px solid #999; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - outline: none; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} - -.modal.fade { - top: -25%; - -webkit-transition: opacity 0.3s linear, top 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; - transition: opacity 0.3s linear, top 0.3s ease-out; -} - -.modal.fade.in { - top: 10%; -} - -.modal-header { - padding: 9px 15px; - border-bottom: 1px solid #eee; -} - -.modal-header .close { - margin-top: 2px; -} - -.modal-header h3 { - margin: 0; - line-height: 30px; -} - -.modal-body { - position: relative; - max-height: 400px; - padding: 15px; - overflow-y: auto; -} - -.modal-form { - margin-bottom: 0; -} - -.modal-footer { - padding: 14px 15px 15px; - margin-bottom: 0; - text-align: right; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - *zoom: 1; - -webkit-box-shadow: inset 0 1px 0 #ffffff; - -moz-box-shadow: inset 0 1px 0 #ffffff; - box-shadow: inset 0 1px 0 #ffffff; -} - -.modal-footer:before, -.modal-footer:after { - display: table; - line-height: 0; - content: ""; -} - -.modal-footer:after { - clear: both; -} - -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} - -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} - -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} - -.tooltip { - position: absolute; - z-index: 1020; - display: block; - font-size: 11px; - line-height: 1.4; - opacity: 0; - filter: alpha(opacity=0); - visibility: visible; -} - -.tooltip.in { - opacity: 0.8; - filter: alpha(opacity=80); -} - -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} - -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} - -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} - -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} - -.tooltip-inner { - max-width: 200px; - padding: 8px; - color: #ffffff; - text-align: center; - text-decoration: none; - background-color: #000000; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-color: #000000; - border-width: 5px 5px 0; -} - -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: #000000; - border-width: 5px 5px 5px 0; -} - -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: #000000; - border-width: 5px 0 5px 5px; -} - -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: #000000; - border-width: 0 5px 5px; -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1010; - display: none; - max-width: 276px; - padding: 1px; - text-align: left; - white-space: normal; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.popover.top { - margin-top: -10px; -} - -.popover.right { - margin-left: 10px; -} - -.popover.bottom { - margin-top: 10px; -} - -.popover.left { - margin-left: -10px; -} - -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - -webkit-border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - border-radius: 5px 5px 0 0; -} - -.popover-title:empty { - display: none; -} - -.popover-content { - padding: 9px 14px; -} - -.popover .arrow, -.popover .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.popover .arrow { - border-width: 11px; -} - -.popover .arrow:after { - border-width: 10px; - content: ""; -} - -.popover.top .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, 0.25); - border-bottom-width: 0; -} - -.popover.top .arrow:after { - bottom: 1px; - margin-left: -10px; - border-top-color: #ffffff; - border-bottom-width: 0; -} - -.popover.right .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, 0.25); - border-left-width: 0; -} - -.popover.right .arrow:after { - bottom: -10px; - left: 1px; - border-right-color: #ffffff; - border-left-width: 0; -} - -.popover.bottom .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, 0.25); - border-top-width: 0; -} - -.popover.bottom .arrow:after { - top: 1px; - margin-left: -10px; - border-bottom-color: #ffffff; - border-top-width: 0; -} - -.popover.left .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, 0.25); - border-right-width: 0; -} - -.popover.left .arrow:after { - right: 1px; - bottom: -10px; - border-left-color: #ffffff; - border-right-width: 0; -} - -.thumbnails { - margin-left: -20px; - list-style: none; - *zoom: 1; -} - -.thumbnails:before, -.thumbnails:after { - display: table; - line-height: 0; - content: ""; -} - -.thumbnails:after { - clear: both; -} - -.row-fluid .thumbnails { - margin-left: 0; -} - -.thumbnails > li { - float: left; - margin-bottom: 20px; - margin-left: 20px; -} - -.thumbnail { - display: block; - padding: 4px; - line-height: 20px; - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; -} - -a.thumbnail:hover, -a.thumbnail:focus { - border-color: #2fa4e7; - -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); -} - -.thumbnail > img { - display: block; - max-width: 100%; - margin-right: auto; - margin-left: auto; -} - -.thumbnail .caption { - padding: 9px; - color: #555555; -} - -.media, -.media-body { - overflow: hidden; - *overflow: visible; - zoom: 1; -} - -.media, -.media .media { - margin-top: 15px; -} - -.media:first-child { - margin-top: 0; -} - -.media-object { - display: block; -} - -.media-heading { - margin: 0 0 5px; -} - -.media > .pull-left { - margin-right: 10px; -} - -.media > .pull-right { - margin-left: 10px; -} - -.media-list { - margin-left: 0; - list-style: none; -} - -.label, -.badge { - display: inline-block; - padding: 2px 4px; - font-size: 11.844px; - font-weight: bold; - line-height: 14px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - white-space: nowrap; - vertical-align: baseline; - background-color: #999999; -} - -.label { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.badge { - padding-right: 9px; - padding-left: 9px; - -webkit-border-radius: 9px; - -moz-border-radius: 9px; - border-radius: 9px; -} - -.label:empty, -.badge:empty { - display: none; -} - -a.label:hover, -a.label:focus, -a.badge:hover, -a.badge:focus { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} - -.label-important, -.badge-important { - background-color: #bd4247; -} - -.label-important[href], -.badge-important[href] { - background-color: #983538; -} - -.label-warning, -.badge-warning { - background-color: #dd5600; -} - -.label-warning[href], -.badge-warning[href] { - background-color: #aa4200; -} - -.label-success, -.badge-success { - background-color: #669533; -} - -.label-success[href], -.badge-success[href] { - background-color: #4c6f26; -} - -.label-info, -.badge-info { - background-color: #178acc; -} - -.label-info[href], -.badge-info[href] { - background-color: #126b9e; -} - -.label-inverse, -.badge-inverse { - background-color: #333333; -} - -.label-inverse[href], -.badge-inverse[href] { - background-color: #1a1a1a; -} - -.btn .label, -.btn .badge { - position: relative; - top: -1px; -} - -.btn-mini .label, -.btn-mini .badge { - top: 0; -} - -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-moz-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-ms-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-o-keyframes progress-bar-stripes { - from { - background-position: 0 0; - } - to { - background-position: 40px 0; - } -} - -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f7f7f7; - background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); - background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); - background-repeat: repeat-x; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); -} - -.progress .bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - color: #ffffff; - text-align: center; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #0e90d2; - background-image: -moz-linear-gradient(top, #149bdf, #0480be); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); - background-image: -webkit-linear-gradient(top, #149bdf, #0480be); - background-image: -o-linear-gradient(top, #149bdf, #0480be); - background-image: linear-gradient(to bottom, #149bdf, #0480be); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-transition: width 0.6s ease; - -moz-transition: width 0.6s ease; - -o-transition: width 0.6s ease; - transition: width 0.6s ease; -} - -.progress .bar + .bar { - -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); -} - -.progress-striped .bar { - background-color: #149bdf; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - -moz-background-size: 40px 40px; - -o-background-size: 40px 40px; - background-size: 40px 40px; -} - -.progress.active .bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -moz-animation: progress-bar-stripes 2s linear infinite; - -ms-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} - -.progress-danger .bar, -.progress .bar-danger { - background-color: #dd514c; - background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); - background-image: linear-gradient(to bottom, #ee5f5b, #c43c35); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0); -} - -.progress-danger.progress-striped .bar, -.progress-striped .bar-danger { - background-color: #ee5f5b; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-success .bar, -.progress .bar-success { - background-color: #5eb95e; - background-image: -moz-linear-gradient(top, #62c462, #57a957); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957)); - background-image: -webkit-linear-gradient(top, #62c462, #57a957); - background-image: -o-linear-gradient(top, #62c462, #57a957); - background-image: linear-gradient(to bottom, #62c462, #57a957); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0); -} - -.progress-success.progress-striped .bar, -.progress-striped .bar-success { - background-color: #62c462; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-info .bar, -.progress .bar-info { - background-color: #4bb1cf; - background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9)); - background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); - background-image: -o-linear-gradient(top, #5bc0de, #339bb9); - background-image: linear-gradient(to bottom, #5bc0de, #339bb9); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0); -} - -.progress-info.progress-striped .bar, -.progress-striped .bar-info { - background-color: #5bc0de; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-warning .bar, -.progress .bar-warning { - background-color: #f16e1a; - background-image: -moz-linear-gradient(top, #ff7d2b, #dd5600); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff7d2b), to(#dd5600)); - background-image: -webkit-linear-gradient(top, #ff7d2b, #dd5600); - background-image: -o-linear-gradient(top, #ff7d2b, #dd5600); - background-image: linear-gradient(to bottom, #ff7d2b, #dd5600); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff7d2b', endColorstr='#ffdd5600', GradientType=0); -} - -.progress-warning.progress-striped .bar, -.progress-striped .bar-warning { - background-color: #ff7d2b; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.accordion { - margin-bottom: 20px; -} - -.accordion-group { - margin-bottom: 2px; - border: 1px solid #e5e5e5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.accordion-heading { - border-bottom: 0; -} - -.accordion-heading .accordion-toggle { - display: block; - padding: 8px 15px; -} - -.accordion-toggle { - cursor: pointer; -} - -.accordion-inner { - padding: 9px 15px; - border-top: 1px solid #e5e5e5; -} - -.carousel { - position: relative; - margin-bottom: 20px; - line-height: 1; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} - -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: 0.6s ease-in-out left; - -moz-transition: 0.6s ease-in-out left; - -o-transition: 0.6s ease-in-out left; - transition: 0.6s ease-in-out left; -} - -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - line-height: 1; -} - -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} - -.carousel-inner > .active { - left: 0; -} - -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} - -.carousel-inner > .next { - left: 100%; -} - -.carousel-inner > .prev { - left: -100%; -} - -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} - -.carousel-inner > .active.left { - left: -100%; -} - -.carousel-inner > .active.right { - left: 100%; -} - -.carousel-control { - position: absolute; - top: 40%; - left: 15px; - width: 40px; - height: 40px; - margin-top: -20px; - font-size: 60px; - font-weight: 100; - line-height: 30px; - color: #ffffff; - text-align: center; - background: #222222; - border: 3px solid #ffffff; - -webkit-border-radius: 23px; - -moz-border-radius: 23px; - border-radius: 23px; - opacity: 0.5; - filter: alpha(opacity=50); -} - -.carousel-control.right { - right: 15px; - left: auto; -} - -.carousel-control:hover, -.carousel-control:focus { - color: #ffffff; - text-decoration: none; - opacity: 0.9; - filter: alpha(opacity=90); -} - -.carousel-indicators { - position: absolute; - top: 15px; - right: 15px; - z-index: 5; - margin: 0; - list-style: none; -} - -.carousel-indicators li { - display: block; - float: left; - width: 10px; - height: 10px; - margin-left: 5px; - text-indent: -999px; - background-color: #ccc; - background-color: rgba(255, 255, 255, 0.25); - border-radius: 5px; -} - -.carousel-indicators .active { - background-color: #fff; -} - -.carousel-caption { - position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 15px; - background: #333333; - background: rgba(0, 0, 0, 0.75); -} - -.carousel-caption h4, -.carousel-caption p { - line-height: 20px; - color: #ffffff; -} - -.carousel-caption h4 { - margin: 0 0 5px; -} - -.carousel-caption p { - margin-bottom: 0; -} - -.hero-unit { - padding: 60px; - margin-bottom: 30px; - font-size: 18px; - font-weight: 200; - line-height: 30px; - color: inherit; - background-color: #f5f5f5; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.hero-unit h1 { - margin-bottom: 0; - font-size: 60px; - line-height: 1; - letter-spacing: -1px; - color: inherit; -} - -.hero-unit li { - line-height: 30px; -} - -.pull-right { - float: right; -} - -.pull-left { - float: left; -} - -.hide { - display: none; -} - -.show { - display: block; -} - -.invisible { - visibility: hidden; -} - -.affix { - position: fixed; -} - -.navbar .brand { - padding: 14px 20px 16px; - font-family: 'Nunito', sans-serif; - text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); -} - -.navbar li { - line-height: 20px; -} - -.navbar .nav > li > a { - padding: 16px 10px 14px; - font-family: 'Nunito', sans-serif; - text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); -} - -.navbar .search-query { - line-height: normal; - border: 1px solid #178acc; -} - -.navbar .navbar-text { - padding: 19px 10px 18px; - line-height: 13px; - color: rgba(0, 0, 0, 0.5); - text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.3); -} - -.navbar-inverse .navbar-search .search-query { - color: #555555; -} - -@media (max-width: 979px) { - .navbar .nav-collapse .nav li > a { - font-family: 'Nunito', sans-serif; - font-weight: normal; - color: #ffffff; - text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); - } - .navbar .nav-collapse .nav li > a:hover { - background-color: #2B7CAC; - } - .navbar .nav-collapse .nav .active > a { - background-color: #2B7CAC; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - } - .navbar .nav-collapse .dropdown-menu li > a:hover, - .navbar .nav-collapse .dropdown-menu li > a:focus, - .navbar .nav-collapse .dropdown-submenu:hover > a { - background-image: none; - } - .navbar .nav-collapse .navbar-form, - .navbar .nav-collapse .navbar-search { - border: none; - } - .navbar .nav-collapse .nav-header { - color: #2B7CAC; - } - .navbar-inverse .nav-collapse .nav li > a { - color: #ffffff; - } - .navbar-inverse .nav-collapse .nav li > a:hover { - background-color: rgba(0, 0, 0, 0.1); - } - .navbar-inverse .nav-collapse .nav .active > a, - .navbar-inverse .nav-collapse .nav > li > a:hover, - .navbar-inverse .nav-collapse .dropdown-menu a:hover { - background-color: rgba(0, 0, 0, 0.1) !important; - } -} - -div.subnav { - font-family: 'Nunito', sans-serif; - text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.2); -} - -div.subnav-fixed { - position: fixed; - top: 51px; - z-index: 10; - color: #999999; - background-color: whiteSmoke; - padding: 20px 2px 0px; - background-color: #f5f5f5; - border: 1px solid; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.btn { - background-color: #ffffff; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(5%, #ffffff), to(#ffffff)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 5%, #ffffff); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 5%, #ffffff); - background-image: -o-linear-gradient(#ffffff, #ffffff 5%, #ffffff); - background-image: linear-gradient(#ffffff, #ffffff 5%, #ffffff); - background-repeat: no-repeat; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffffffff', GradientType=0); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn:hover { - background-position: 0 0; -} - -.btn-primary { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #3daae9; - *background-color: #2fa4e7; - background-image: -moz-linear-gradient(top, #46aeea, #2fa4e7); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#46aeea), to(#2fa4e7)); - background-image: -webkit-linear-gradient(top, #46aeea, #2fa4e7); - background-image: -o-linear-gradient(top, #46aeea, #2fa4e7); - background-image: linear-gradient(to bottom, #46aeea, #2fa4e7); - background-repeat: repeat-x; - border-color: #2fa4e7 #2fa4e7 #157ab5; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff46aeea', endColorstr='#ff2fa4e7', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-primary:hover, -.btn-primary:focus, -.btn-primary:active, -.btn-primary.active, -.btn-primary.disabled, -.btn-primary[disabled] { - color: #ffffff; - background-color: #2fa4e7; - *background-color: #1a99e2; -} - -.btn-primary:active, -.btn-primary.active { - background-color: #178acc \9; -} - -.btn-info { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #9e6ab8; - *background-color: #9760b3; - background-image: -moz-linear-gradient(top, #a271bb, #9760b3); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#a271bb), to(#9760b3)); - background-image: -webkit-linear-gradient(top, #a271bb, #9760b3); - background-image: -o-linear-gradient(top, #a271bb, #9760b3); - background-image: linear-gradient(to bottom, #a271bb, #9760b3); - background-repeat: repeat-x; - border-color: #9760b3 #9760b3 #6f4086; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa271bb', endColorstr='#ff9760b3', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-info:hover, -.btn-info:focus, -.btn-info:active, -.btn-info.active, -.btn-info.disabled, -.btn-info[disabled] { - color: #ffffff; - background-color: #9760b3; - *background-color: #8b51a9; -} - -.btn-info:active, -.btn-info.active { - background-color: #7d4898 \9; -} - -.btn-success { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #7bb33d; - *background-color: #73a839; - background-image: -moz-linear-gradient(top, #80bb3f, #73a839); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#80bb3f), to(#73a839)); - background-image: -webkit-linear-gradient(top, #80bb3f, #73a839); - background-image: -o-linear-gradient(top, #80bb3f, #73a839); - background-image: linear-gradient(to bottom, #80bb3f, #73a839); - background-repeat: repeat-x; - border-color: #73a839 #73a839 #4c6f26; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff80bb3f', endColorstr='#ff73a839', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-success:hover, -.btn-success:focus, -.btn-success:active, -.btn-success.active, -.btn-success.disabled, -.btn-success[disabled] { - color: #ffffff; - background-color: #73a839; - *background-color: #669533; -} - -.btn-success:active, -.btn-success.active { - background-color: #59822c \9; -} - -.btn-warning { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #ec5c00; - *background-color: #dd5600; - background-image: -moz-linear-gradient(top, #f76000, #dd5600); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f76000), to(#dd5600)); - background-image: -webkit-linear-gradient(top, #f76000, #dd5600); - background-image: -o-linear-gradient(top, #f76000, #dd5600); - background-image: linear-gradient(to bottom, #f76000, #dd5600); - background-repeat: repeat-x; - border-color: #dd5600 #dd5600 #913800; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff76000', endColorstr='#ffdd5600', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-warning:hover, -.btn-warning:focus, -.btn-warning:active, -.btn-warning.active, -.btn-warning.disabled, -.btn-warning[disabled] { - color: #ffffff; - background-color: #dd5600; - *background-color: #c44c00; -} - -.btn-warning:active, -.btn-warning.active { - background-color: #aa4200 \9; -} - -.btn-danger { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #d41e24; - *background-color: #c71c22; - background-image: -moz-linear-gradient(top, #dd1f26, #c71c22); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#dd1f26), to(#c71c22)); - background-image: -webkit-linear-gradient(top, #dd1f26, #c71c22); - background-image: -o-linear-gradient(top, #dd1f26, #c71c22); - background-image: linear-gradient(to bottom, #dd1f26, #c71c22); - background-repeat: repeat-x; - border-color: #c71c22 #c71c22 #841317; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd1f26', endColorstr='#ffc71c22', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-danger:hover, -.btn-danger:focus, -.btn-danger:active, -.btn-danger.active, -.btn-danger.disabled, -.btn-danger[disabled] { - color: #ffffff; - background-color: #c71c22; - *background-color: #b1191e; -} - -.btn-danger:active, -.btn-danger.active { - background-color: #9a161a \9; -} - -.btn-inverse { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #034482; - *background-color: #033c73; - background-image: -moz-linear-gradient(top, #04498c, #033c73); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#04498c), to(#033c73)); - background-image: -webkit-linear-gradient(top, #04498c, #033c73); - background-image: -o-linear-gradient(top, #04498c, #033c73); - background-image: linear-gradient(to bottom, #04498c, #033c73); - background-repeat: repeat-x; - border-color: #033c73 #033c73 #011528; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04498c', endColorstr='#ff033c73', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-inverse:hover, -.btn-inverse:focus, -.btn-inverse:active, -.btn-inverse.active, -.btn-inverse.disabled, -.btn-inverse[disabled] { - color: #ffffff; - background-color: #033c73; - *background-color: #022f5a; -} - -.btn-inverse:active, -.btn-inverse.active { - background-color: #022241 \9; -} - -.pull-right { - float: right; -} - -.pull-left { - float: left; -} - -.hide { - display: none; -} - -.show { - display: block; -} - -.invisible { - visibility: hidden; -} - -.affix { - position: fixed; -} diff --git a/src/main/webapp/static/css/colorbox.css b/src/main/webapp/static/css/colorbox.css index 2a578881fb..a7e0db08c7 100644 --- a/src/main/webapp/static/css/colorbox.css +++ b/src/main/webapp/static/css/colorbox.css @@ -46,4 +46,4 @@ #colorbox { overflow: initial; -} \ No newline at end of file +} diff --git a/src/main/webapp/static/css/icons.css b/src/main/webapp/static/css/icons.css new file mode 100644 index 0000000000..950a4c8642 --- /dev/null +++ b/src/main/webapp/static/css/icons.css @@ -0,0 +1,72 @@ +@font-face { + font-family: 'icomoon'; + src:url('../fonts/icomoon.eot?f8i4y6'); + src:url('../fonts/icomoon.eot?#iefixf8i4y6') format('embedded-opentype'), + url('../fonts/icomoon.ttf?f8i4y6') format('truetype'), + url('../fonts/icomoon.woff?f8i4y6') format('woff'), + url('../fonts/icomoon.svg?f8i4y6#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} + +[class^="icomoon-"], [class*=" icomoon-"] { + font-family: 'icomoon'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icomoon-large:before { + vertical-align: -10%; + font-size: 2em; +} + +.icomoon-file-presentation:before { + content: "\e600"; +} +.icomoon-file-html:before { + content: "\e601"; +} +.icomoon-file-email:before { + content: "\e602"; +} +.icomoon-file-spreadsheet:before { + content: "\e603"; +} +.icomoon-file-powerpoint:before { + content: "\e604"; +} +.icomoon-file-general:before { + content: "\e924"; +} +.icomoon-file-text:before { + content: "\e926"; +} +.icomoon-file-image:before { + content: "\e927"; +} +.icomoon-file-audio:before { + content: "\e928"; +} +.icomoon-file-video:before { + content: "\e92a"; +} +.icomoon-file-spreadsheet2:before { + content: "\ea70"; +} +.icomoon-file-pdf:before { + content: "\eada"; +} +.icomoon-file-word:before { + content: "\eadc"; +} +.icomoon-file-excel:before { + content: "\eadd"; +} \ No newline at end of file diff --git a/src/main/webapp/static/favicon.ico b/src/main/webapp/static/favicon.ico index 6870d2a134..b206b1201e 100644 Binary files a/src/main/webapp/static/favicon.ico and b/src/main/webapp/static/favicon.ico differ diff --git a/src/main/webapp/static/fonts/icomoon.eot b/src/main/webapp/static/fonts/icomoon.eot new file mode 100644 index 0000000000..e28984974a Binary files /dev/null and b/src/main/webapp/static/fonts/icomoon.eot differ diff --git a/src/main/webapp/static/fonts/icomoon.svg b/src/main/webapp/static/fonts/icomoon.svg new file mode 100644 index 0000000000..2d90d5e2fa --- /dev/null +++ b/src/main/webapp/static/fonts/icomoon.svg @@ -0,0 +1,24 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/static/fonts/icomoon.ttf b/src/main/webapp/static/fonts/icomoon.ttf new file mode 100644 index 0000000000..f5ea36b506 Binary files /dev/null and b/src/main/webapp/static/fonts/icomoon.ttf differ diff --git a/src/main/webapp/static/fonts/icomoon.woff b/src/main/webapp/static/fonts/icomoon.woff new file mode 100644 index 0000000000..0da4e6a307 Binary files /dev/null and b/src/main/webapp/static/fonts/icomoon.woff differ diff --git a/src/main/webapp/static/img/Find_banner.png b/src/main/webapp/static/img/Find_banner.png new file mode 100644 index 0000000000..677d562f88 Binary files /dev/null and b/src/main/webapp/static/img/Find_banner.png differ diff --git a/src/main/webapp/static/img/Find_logo.png b/src/main/webapp/static/img/Find_logo.png new file mode 100644 index 0000000000..66d9524824 Binary files /dev/null and b/src/main/webapp/static/img/Find_logo.png differ diff --git a/src/main/webapp/static/img/HPE_header_logo.png b/src/main/webapp/static/img/HPE_header_logo.png new file mode 100644 index 0000000000..5fd17be499 Binary files /dev/null and b/src/main/webapp/static/img/HPE_header_logo.png differ diff --git a/src/main/webapp/static/img/HPE_logo.png b/src/main/webapp/static/img/HPE_logo.png new file mode 100644 index 0000000000..ef1deab784 Binary files /dev/null and b/src/main/webapp/static/img/HPE_logo.png differ diff --git a/src/main/webapp/static/js/config.js b/src/main/webapp/static/js/config.js index 254d14dc6b..fc78c3f419 100644 --- a/src/main/webapp/static/js/config.js +++ b/src/main/webapp/static/js/config.js @@ -7,4 +7,4 @@ require(['require-config'], function() { require(['find/config/app'], function(App) { new App(); }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/app.js b/src/main/webapp/static/js/find/app/app.js index c350f20a25..d957b97f1a 100644 --- a/src/main/webapp/static/js/find/app/app.js +++ b/src/main/webapp/static/js/find/app/app.js @@ -9,7 +9,6 @@ define([ 'find/app/navigation', 'text!find/templates/app/private-app.html' ], function(BaseApp, Pages, Navigation, template) { - return BaseApp.extend({ template: _.template(template), @@ -33,7 +32,5 @@ define([ this.$('.header').append(this.navigation.el); } - }); - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/base-app.js b/src/main/webapp/static/js/find/app/base-app.js index 43c93d27ce..dc04f4a6dc 100644 --- a/src/main/webapp/static/js/find/app/base-app.js +++ b/src/main/webapp/static/js/find/app/base-app.js @@ -27,12 +27,15 @@ define([ }, render: function() { - this.$el.html(this.template()); + this.$el.html(this.template(this.getTemplateParameters())); this.pages.render(); this.$('.content').append(this.pages.el); - } + }, + getTemplateParameters: function() { + return {}; + } }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/configuration.js b/src/main/webapp/static/js/find/app/configuration.js new file mode 100644 index 0000000000..cff953aebb --- /dev/null +++ b/src/main/webapp/static/js/find/app/configuration.js @@ -0,0 +1,19 @@ +define([ + 'jquery' +], function() { + + var config; + + return function() { + if (!config) { + var configString = $('#config-json').text(); + + if (configString) { + config = JSON.parse(configString); + } + } + + return config; + } + +}) \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/find-pages.js b/src/main/webapp/static/js/find/app/find-pages.js index 71f9ee3393..16d04bef49 100644 --- a/src/main/webapp/static/js/find/app/find-pages.js +++ b/src/main/webapp/static/js/find/app/find-pages.js @@ -8,7 +8,6 @@ define([ 'find/app/router', 'find/app/vent' ], function(AbstractPages, router, vent) { - return AbstractPages.extend({ routePrefix: 'find/', @@ -18,7 +17,5 @@ define([ vent: vent, router: router - - }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/model/backbone-query-model.js b/src/main/webapp/static/js/find/app/model/backbone-query-model.js new file mode 100644 index 0000000000..499ddb88b0 --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/backbone-query-model.js @@ -0,0 +1,39 @@ +define([ + 'backbone' +], function(Backbone) { + + var Sort = { + date: 'date', + relevance: 'relevance' + }; + + return Backbone.Model.extend({ + defaults: { + queryText: '', + indexes: [], + fieldText: null, + minDate: undefined, + maxDate: undefined, + sort: Sort.relevance + }, + + getIsoDate: function(type) { + var date = this.get(type); + if(date) { + return date.toISOString(); + } else { + return null; + } + }, + + refresh: function(queryText) { + if(this.get('queryText') === queryText) { + this.trigger('refresh'); + } else { + this.set('queryText', queryText) + } + } + }, { + Sort: Sort + }); +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/model/config.js b/src/main/webapp/static/js/find/app/model/config.js index 4969e597e4..cf386c65ff 100644 --- a/src/main/webapp/static/js/find/app/model/config.js +++ b/src/main/webapp/static/js/find/app/model/config.js @@ -18,4 +18,4 @@ define([ }); return new Config(); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/model/dates-filter-model.js b/src/main/webapp/static/js/find/app/model/dates-filter-model.js new file mode 100644 index 0000000000..e54a020e8a --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/dates-filter-model.js @@ -0,0 +1,89 @@ +define([ + 'backbone', + 'moment' +], function(Backbone, moment) { + + var dateRange = { + custom: 'custom', + year: 'year', + month: 'month', + week: 'week' + }; + + var dateRangeDescription = { + year: {maxDate: moment(), minDate: moment().subtract(1, 'years')}, + month: {maxDate: moment(), minDate: moment().subtract(1, 'months')}, + week: {maxDate: moment(), minDate: moment().subtract(1, 'weeks')} + }; + + return Backbone.Model.extend({ + defaults: { + minDate: null, + maxDate: null, + dateRange: null + }, + + initialize: function(attributes, options) { + this.queryModel = options.queryModel; + + this.listenTo(this, 'change', function() { + this.queryModel.set({ + minDate: this.get('minDate'), + maxDate: this.get('maxDate') + }) + }) + }, + + setDateRange: function(range) { + if(range === dateRange.custom) { + this.set({ + minDate: this.customMinDate, + maxDate: this.customMaxDate, + dateRange: dateRange.custom + }); + } else if(range) { + var dateRangeProperties = dateRangeDescription[range] || {}; + + this.set({ + minDate: dateRangeProperties.minDate, + maxDate: dateRangeProperties.maxDate, + dateRange: range + }); + } else { + this.set({ + minDate: null, + maxDate: null, + dateRange: null + }); + } + }, + + setMinDate: function(date) { + // library gives us false which would trigger a change event + date = date || undefined; + + this.customMinDate = date; + + this.set({ + dateRange: dateRange.custom, + minDate: date, + maxDate: this.customMaxDate + }); + }, + + setMaxDate: function(date) { + // library gives us false which would trigger a change event + date = date || undefined; + + this.customMaxDate = date; + + this.set({ + dateRange: dateRange.custom, + minDate: this.customMinDate, + maxDate: date + }); + } + }, { + dateRange: dateRange + }); +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/model/document-model.js b/src/main/webapp/static/js/find/app/model/document-model.js new file mode 100644 index 0000000000..a979910c13 --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/document-model.js @@ -0,0 +1,32 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'backbone' +], function(Backbone) { + + // Model representing a document in an HOD text index + return Backbone.Model.extend({ + parse: function(response) { + if (!response.title) { + // If there is no title, use the last part of the reference (assuming the reference is a file path) + // C:\Documents\file.txt -> file.txt + // /home/user/another-file.txt -> another-file.txt + var splitReference = response.reference.split(/\/|\\/); + var lastPart = _.last(splitReference); + + if (/\S/.test(lastPart)) { + // Use the "file name" if it contains a non whitespace character + response.title = lastPart; + } else { + response.title = response.reference; + } + } + + return response; + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/model/documents-collection.js b/src/main/webapp/static/js/find/app/model/documents-collection.js index 80c6f98de2..60f2de604b 100644 --- a/src/main/webapp/static/js/find/app/model/documents-collection.js +++ b/src/main/webapp/static/js/find/app/model/documents-collection.js @@ -4,13 +4,26 @@ */ define([ - 'backbone' -], function(Backbone) { + 'find/app/model/find-base-collection', + 'find/app/model/document-model', + 'underscore' +], function(FindBaseCollection, DocumentModel, _) { - return Backbone.Collection.extend({ + return FindBaseCollection.extend({ + model: DocumentModel, + url: '../api/public/search/query-text-index/results', - url: '../api/search/query-text-index' + initialize: function(models, options) { + this.indexesCollection = options.indexesCollection; + }, - }) + parse: function(response) { + return _.map(response.documents, function(document) { + document.index = this.indexesCollection.findWhere({name: document.index}); + + return document; + }, this); + } + }); }); diff --git a/src/main/webapp/static/js/find/app/model/entity-collection.js b/src/main/webapp/static/js/find/app/model/entity-collection.js index 9bd34ff068..6bd096b47a 100644 --- a/src/main/webapp/static/js/find/app/model/entity-collection.js +++ b/src/main/webapp/static/js/find/app/model/entity-collection.js @@ -4,19 +4,10 @@ */ define([ - 'backbone' -], function(Backbone) { - - return Backbone.Collection.extend({ - - url: '../api/search/find-related-concepts', - - fetch: function(options) { - return Backbone.Collection.prototype.fetch.call(this, _.defaults(options, { - reset: true - })); - } + 'find/app/model/find-base-collection' +], function(FindBaseCollection) { + return FindBaseCollection.extend({ + url: '../api/public/search/find-related-concepts' }) - }); diff --git a/src/main/webapp/static/js/find/app/model/find-base-collection.js b/src/main/webapp/static/js/find/app/model/find-base-collection.js new file mode 100644 index 0000000000..2eb2bae87b --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/find-base-collection.js @@ -0,0 +1,54 @@ +/* + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'backbone', + 'underscore' +], function(Backbone, _) { + + return Backbone.Collection.extend({ + + /** + * Fetch tracks in-flight requests and cancels them when a new one is run + * @param options + * @returns {*|null} + */ + fetch: function(options) { + if (this.currentRequest) { + this.currentRequest.abort(); + } + + var success = options.success; + + this.currentRequest = Backbone.Collection.prototype.fetch.call(this, _.extend(options || {}, { + reset: _.isUndefined(options.reset) ? true : options.reset, + success: _.bind(function() { + this.currentRequest = null; + + if (success) { + success.apply(options, arguments); + } + }, this) + })); + + return this.currentRequest; + }, + + /** + * Sync uses "traditional" options serialization + * @param method + * @param model + * @param options + * @returns {*} + */ + sync: function(method, model, options) { + options = options || {}; + options.traditional = true; // Force "traditional" serialization of query parameters, e.g. index=foo&index=bar, for IOD multi-index support. + + return Backbone.Collection.prototype.sync.call(this, method, model, options); + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/model/indexes-collection.js b/src/main/webapp/static/js/find/app/model/indexes-collection.js index f5d0299586..12189e2c2a 100644 --- a/src/main/webapp/static/js/find/app/model/indexes-collection.js +++ b/src/main/webapp/static/js/find/app/model/indexes-collection.js @@ -4,13 +4,19 @@ */ define([ - 'backbone' -], function(Backbone) { + 'databases-view/js/databases-collection', + 'underscore' +], function(DatabasesCollection, _) { - return Backbone.Collection.extend({ + return DatabasesCollection.extend({ + url: '../api/public/search/list-indexes', - url: '../api/search/list-indexes' + parse: function(response) { + return _.map(response, function(responseItem) { + responseItem.id = encodeURIComponent(responseItem.domain) + ':' + encodeURIComponent(responseItem.name); + return responseItem; + }); + } + }); - }) - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/model/parametric-collection.js b/src/main/webapp/static/js/find/app/model/parametric-collection.js new file mode 100644 index 0000000000..566aa8c7f7 --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/parametric-collection.js @@ -0,0 +1,17 @@ +define([ + 'backbone', + 'find/app/model/find-base-collection' +], function(Backbone, FindBaseCollection) { + + return FindBaseCollection.extend({ + url: '../api/public/parametric', + + model: Backbone.Model.extend({ + idAttribute: 'name', + + defaults: { + values: [] + } + }) + }); +}); diff --git a/src/main/webapp/static/js/find/app/model/promotions-collection.js b/src/main/webapp/static/js/find/app/model/promotions-collection.js new file mode 100644 index 0000000000..ef9179560d --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/promotions-collection.js @@ -0,0 +1,27 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'find/app/model/find-base-collection', + 'find/app/model/document-model' +], function(FindBaseCollection, DocumentModel) { + + return FindBaseCollection.extend({ + model: DocumentModel, + url: '../api/public/search/query-text-index/promotions', + + initialize: function(models, options) { + this.indexesCollection = options.indexesCollection; + }, + + parse: function(response) { + return _.map(response.documents, function(document) { + document.index = this.indexesCollection.findWhere({name: document.index}); + + return document; + }, this); + } + }) +}); diff --git a/src/main/webapp/static/js/find/app/model/query-model.js b/src/main/webapp/static/js/find/app/model/query-model.js new file mode 100644 index 0000000000..9b73dc97d9 --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/query-model.js @@ -0,0 +1,33 @@ +define([ + 'backbone' +], function(Backbone) { + + var EventsProxy = function(queryModel) { + this.model = queryModel; + + this.listenTo(this.model, 'all', function(event) { + if (event !== 'change' && event !== 'refresh' || (this.model.get('queryText') && !_.isEmpty(this.model.get('indexes')))) { + this.trigger.apply(this, arguments); + } + }); + }; + + _.extend(EventsProxy.prototype, Backbone.Events, { + + hasAnyChangedAttributes: function(attributes) { + return _.any(attributes, function (attr) { + return _.has(this.changedAttributes(), attr); + }, this); + } + + }); + + _.each(['get', 'set', 'unset', 'changedAttributes', 'getIsoDate', 'refresh'], function(methodName) { + EventsProxy.prototype[methodName] = function() { + return this.model[methodName].apply(this.model, arguments); + }; + }); + + return EventsProxy; +}); + diff --git a/src/main/webapp/static/js/find/app/model/search-filters-collection.js b/src/main/webapp/static/js/find/app/model/search-filters-collection.js new file mode 100644 index 0000000000..a394870936 --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/search-filters-collection.js @@ -0,0 +1,231 @@ +define([ + 'backbone', + 'underscore', + 'moment', + 'find/app/model/backbone-query-model', + 'find/app/model/dates-filter-model', + 'find/app/page/date/dates-filter-view', + 'i18n!find/nls/bundle' +], function(Backbone, _, moment, QueryModel, DatesFilterModel, datesFilterView, i18n) { + + var FilterTypes = { + indexes: 'indexes', + maxDate: 'maxDate', + minDate: 'minDate', + dateRange: 'dateRange', + PARAMETRIC: 'PARAMETRIC' + }; + + var metaFilterType = { + date: 'date' + }; + + function getDateFilterText(filterType, dateString) { + var textPrefixKey = filterType === FilterTypes.maxDate ? 'app.until' : 'app.from'; + return i18n[textPrefixKey] + ': ' + dateString; + } + + // Get the filter model id for a given parametric field name + function parametricFilterId(fieldName) { + return FilterTypes.PARAMETRIC + ':' + fieldName; + } + + // Get the display text for the given parametric field name and array of selected parametric values + function parametricFilterText(fieldName, values) { + return fieldName + ': ' + values.join(', '); + } + + // Get an array of filter model attributes from the selected parametric values collection + function extractParametricFilters(selectedParametricValues) { + return _.map(selectedParametricValues.toFieldsAndValues(), function(values, field) { + return { + id: parametricFilterId(field), + field: field, + text: parametricFilterText(field, values), + type: FilterTypes.PARAMETRIC + }; + }); + } + + // This collection backs the search filters display view. It monitors the query model and selected parmaetric values + // collection and creates/removes it's own models when they change. + // When a dates filter model is removed, it updates the appropriate request model attribute with a null value. However, + // this currently can't be done for the selected databases because the databases view isn't backed by a collection. + return Backbone.Collection.extend({ + initialize: function(models, options) { + this.queryModel = options.queryModel; + this.datesFilterModel = options.datesFilterModel; + this.indexesCollection = options.indexesCollection; + this.selectedIndexesCollection = options.selectedIndexesCollection; + this.selectedParametricValues = options.selectedParametricValues; + + this.listenTo(this.selectedParametricValues, 'add remove', this.updateParametricSelection); + this.listenTo(this.selectedParametricValues, 'reset', this.resetParametricSelection); + this.listenTo(this.selectedIndexesCollection, 'reset update', this.updateDatabases); + + this.listenTo(this.queryModel, 'change', function() { + if (this.queryModel.hasAnyChangedAttributes(['minDate', 'maxDate'])) { + var changed = this.queryModel.changedAttributes(); + var dateFilterTypes = _.intersection(['minDate', 'maxDate'], _.keys(changed)); + + var dateRange = this.datesFilterModel.get('dateRange'); + + if(!_.isEmpty(dateFilterTypes)) { + if(dateRange === DatesFilterModel.dateRange.custom) { + this.intervalDate(dateFilterTypes); + } else if(dateRange) { + this.humanDate(); + } else { + this.removeAllDateFilters(); + } + } + } + }); + + this.on('remove', function(model) { + var type = model.get('type'); + + if (type === FilterTypes.PARAMETRIC) { + var field = model.get('field'); + this.selectedParametricValues.remove(this.selectedParametricValues.where({field: field})); + } else if (type === FilterTypes.indexes) { + this.selectedIndexesCollection.set(this.indexesCollection.toResourceIdentifiers()); + } + }); + + if (this.queryModel.get('minDate')) { + models.push({ + id: FilterTypes.minDate, + type: FilterTypes.minDate, + metaType: metaFilterType.date, + text: getDateFilterText(FilterTypes.minDate, moment(this.queryModel.get('minDate')).format('LLL')) + }); + } + + if (this.queryModel.get('maxDate')) { + models.push({ + id: FilterTypes.maxDate, + type: FilterTypes.maxDate, + metaType: metaFilterType.date, + text: getDateFilterText(FilterTypes.maxDate, moment(this.queryModel.get('maxDate')).format('LLL')) + }); + } + + if (!this.allIndexesSelected()) { + models.push({ + id: FilterTypes.indexes, + type: FilterTypes.indexes, + text: this.getDatabasesFilterText() + }); + } + + Array.prototype.push.apply(models, extractParametricFilters(this.selectedParametricValues)); + }, + + getDatabasesFilterText: function() { + var selectedIndexNames = this.selectedIndexesCollection.pluck('name'); + return i18n['search.indexes'] + ': ' + selectedIndexNames.join(', '); + }, + + allIndexesSelected: function() { + return this.indexesCollection.length === this.selectedIndexesCollection.length; + }, + + updateDatabases: function() { + var filterModel = this.get(FilterTypes.indexes); + + if (!this.allIndexesSelected()) { + var filterText = this.getDatabasesFilterText(); + + if (filterModel) { + filterModel.set('text', filterText); + } else { + // The databases filter model has equal id and type since only one filter of this type can be present + this.add({id: FilterTypes.indexes, type: FilterTypes.indexes, text: filterText}); + } + } else if (filterModel) { + this.remove(filterModel); + } + }, + + removeAllDateFilters: function() { + this.remove(this.where({metaType: metaFilterType.date})); + }, + + humanDate: function() { + this.removeAllDateFilters(); + + var dateRange = this.datesFilterModel.get('dateRange'); + + if (dateRange) { + this.add({ + id: dateRange, + type: FilterTypes.dateRange, + metaType: metaFilterType.date, + text: i18n['search.dates.timeInterval.' + dateRange] + }); + } + }, + + intervalDate: function(filterTypes) { + this.remove(this.where({type: FilterTypes.dateRange})); + + _.each(filterTypes, function(filterType) { + var filterModel = this.get(filterType); + + var date = this.queryModel.get(filterType); + + if (date) { + var displayDate = date.format('LLL'); + var filterText = getDateFilterText(filterType, displayDate); + + if (filterModel) { + filterModel.set('text', filterText); + } else { + // Date filter models have equal id and type attributes since only one model of each type can be present + this.add({ + id: filterType, + type: filterType, + metaType: metaFilterType.date, + text: filterText + }); + } + } else if(filterModel) { + this.remove(filterModel); + } + }, this); + }, + + // Handles add and remove events from the selected parametric values collection + updateParametricSelection: function(selectionModel) { + var field = selectionModel.get('field'); + var fieldDisplayName = selectionModel.get('fieldDisplayName'); + var id = parametricFilterId(field); + var modelsForField = this.selectedParametricValues.where({field: field}); + + if (modelsForField.length) { + this.add({ + id: id, + field: field, + text: parametricFilterText(fieldDisplayName, _.invoke(modelsForField, 'get', 'value')), + type: FilterTypes.PARAMETRIC + }, { + // Merge true to overwrite the text for any existing model for this field name + merge: true + }); + } else { + // this.remove(id) doesn't work when this has been called in response to a different remove event + this.remove(this.where({id: id})); + } + }, + + resetParametricSelection: function() { + this.remove(this.where({type: FilterTypes.PARAMETRIC})); + this.add(extractParametricFilters(this.selectedParametricValues)); + } + }, { + FilterTypes: FilterTypes, + metaFilterTypes: metaFilterType + }); + +}); diff --git a/src/main/webapp/static/js/find/app/model/similar-documents-collection.js b/src/main/webapp/static/js/find/app/model/similar-documents-collection.js new file mode 100644 index 0000000000..d20e5fbf22 --- /dev/null +++ b/src/main/webapp/static/js/find/app/model/similar-documents-collection.js @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'underscore', + 'find/app/model/find-base-collection', + 'find/app/model/document-model' +], function(_, BaseCollection, DocumentModel) { + + return BaseCollection.extend({ + url: '../api/public/search/similar-documents', + model: DocumentModel, + + initialize: function(models, options) { + this.indexes = options.indexes; + this.reference = options.reference; + }, + + fetch: function(options) { + return BaseCollection.prototype.fetch.call(this, _.extend(options || {}, { + data: { + indexes: this.indexes, + reference: this.reference + } + })); + } + }); + +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/navigation.js b/src/main/webapp/static/js/find/app/navigation.js index b4ce4eb739..ac2842e2de 100644 --- a/src/main/webapp/static/js/find/app/navigation.js +++ b/src/main/webapp/static/js/find/app/navigation.js @@ -9,7 +9,6 @@ define([ 'i18n!find/nls/bundle', 'text!find/templates/app/navigation.html' ], function(Navigation, router, i18n, template) { - return Navigation.extend({ event: 'route:find', @@ -23,7 +22,5 @@ define([ i18n: i18n } } - }) - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/page/date/dates-filter-view.js b/src/main/webapp/static/js/find/app/page/date/dates-filter-view.js new file mode 100644 index 0000000000..628abe0118 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/date/dates-filter-view.js @@ -0,0 +1,141 @@ +define([ + 'backbone', + 'moment', + 'i18n!find/nls/bundle', + 'find/app/model/dates-filter-model', + 'js-whatever/js/list-view', + 'text!find/templates/app/page/date/dates-filter-view.html', + 'text!find/templates/app/page/date/custom-datepicker.html', + 'text!find/templates/app/page/date/date-item.html', + 'bootstrap-datetimepicker' +], function(Backbone, moment, i18n, DatesFilterModel, ListView, template, datepicker, dateItemTemplate) { + + var DATES_DISPLAY_FORMAT = 'YYYY/MM/DD HH:mm'; + + return Backbone.View.extend({ + template: _.template(template), + datepickerTemplate: _.template(datepicker), + itemTemplate: _.template(dateItemTemplate), + + events: { + 'click tr': function(e) { + var $targetRow = $(e.currentTarget); + var selected = $targetRow.find('[data-id]').data('id'); + var previous = this.datesFilterModel.get('dateRange'); + + if(selected === previous) { + this.datesFilterModel.setDateRange(null); + } else { + this.datesFilterModel.setDateRange(selected); + } + } + }, + + initialize: function(options) { + this.datesFilterModel = options.datesFilterModel; + this.queryModel = options.queryModel; + + this.dateFiltersCollection = new Backbone.Collection([ + { + id: DatesFilterModel.dateRange.week, + label: i18n['search.dates.timeInterval.' + DatesFilterModel.dateRange.week] + }, + { + id: DatesFilterModel.dateRange.month, + label: i18n['search.dates.timeInterval.' + DatesFilterModel.dateRange.month] + }, + { + id: DatesFilterModel.dateRange.year, + label: i18n['search.dates.timeInterval.' + DatesFilterModel.dateRange.year] + }, + { + id: DatesFilterModel.dateRange.custom, + label: i18n['search.dates.timeInterval.' + DatesFilterModel.dateRange.custom] + } + ]); + + this.listView = new ListView({ + collection: this.dateFiltersCollection, + itemOptions: { + tagName: 'tr', + className: 'clickable', + template: this.itemTemplate + } + }); + + _.each(['minDate', 'maxDate'], function(date) { + this.listenTo(this.queryModel, 'change:' + date, function(model, value) { + var display = ''; + + if(value) { + display = value.format(DATES_DISPLAY_FORMAT); + } + + this['$' + date].find('input').val(display); + }); + }, this); + + this.listenTo(this.datesFilterModel, 'change:dateRange', function(datesFilterModel, dateRange) { + // Clear all checkboxes, check selected + this.$('.date-filters-list i').addClass('hide'); + this.$("[data-id='" + dateRange + "'] i").removeClass('hide'); + + // If custom show custom options + this.$('.search-dates-wrapper').toggleClass('hide', dateRange !== DatesFilterModel.dateRange.custom); + }); + }, + + render: function() { + this.$el.html(this.template({ + i18n: i18n + })); + + this.listView.setElement(this.$('table')).render(); + + this.$el.append(this.datepickerTemplate({ + i18n:i18n + })); + + this.$minDate = this.$('.results-filter-min-date'); + this.$maxDate = this.$('.results-filter-max-date'); + + this.$minDate.datetimepicker({ + format: DATES_DISPLAY_FORMAT, + icons: { + time: 'icon-time', + date: 'icon-calendar', + up: 'icon-chevron-up', + down:'icon-chevron-down', + next: 'icon-chevron-right', + previous: 'icon-chevron-left' + } + }).on('dp.change', _.bind(function(ev) { + this.setMinDate(ev.date); + }, this)); + + this.$maxDate.datetimepicker({ + format: DATES_DISPLAY_FORMAT, + icons: { + time: 'icon-time', + date: 'icon-calendar', + up: 'icon-chevron-up', + down:'icon-chevron-down', + next: 'icon-chevron-right', + previous: 'icon-chevron-left' + } + }).on('dp.change', _.bind(function(ev) { + this.setMaxDate(ev.date); + }, this)); + + }, + + setMinDate: function(date) { + this.datesFilterModel.setMinDate(date); + }, + + setMaxDate: function(date) { + this.datesFilterModel.setMaxDate(date); + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/page/filter-display/filter-display-view.js b/src/main/webapp/static/js/find/app/page/filter-display/filter-display-view.js new file mode 100644 index 0000000000..819f75995e --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/filter-display/filter-display-view.js @@ -0,0 +1,92 @@ +define([ + 'backbone', + 'js-whatever/js/list-view', + 'js-whatever/js/list-item-view', + 'find/app/model/search-filters-collection', + 'i18n!find/nls/bundle', + 'text!find/templates/app/page/filter-display/filter-display.html', + 'text!find/templates/app/page/filter-display/filter-display-item.html' +], function(Backbone, ListView, ListItemView, SearchFiltersCollection, i18n, template, itemTemplate) { + + var FilterListItemView = ListItemView.extend({ + render: function() { + ListItemView.prototype.render.apply(this, arguments); + + this.$tooltip = this.$('[data-toggle="tooltip"]'); + + this.$tooltip.tooltip({ + container: 'body', + placement: 'bottom' + }); + }, + + remove: function() { + this.$tooltip.tooltip('destroy'); + + ListItemView.prototype.remove.apply(this, arguments); + } +}); + + // Each of the collection's models should have an id and a text attribute + return Backbone.View.extend({ + template: _.template(template), + itemTemplate: _.template(itemTemplate), + + events: { + 'click .filters-remove-icon': function(e) { + var id = $(e.currentTarget).closest('[data-id]').attr('data-id'); + var metaType = $(e.currentTarget).closest('[data-metatype]').attr('data-metatype'); + var type = $(e.currentTarget).closest('[data-type]').attr('data-type'); + + if(metaType && metaType === SearchFiltersCollection.metaFilterTypes.date) { + if(type === SearchFiltersCollection.FilterTypes.dateRange) { + this.datesFilterModel.setDateRange(null); + } + if(type === SearchFiltersCollection.FilterTypes.minDate) { + this.datesFilterModel.setMinDate(null); + } + if(type === SearchFiltersCollection.FilterTypes.maxDate) { + this.datesFilterModel.setMaxDate(null); + } + } else { + this.collection.remove(id); + } + + + } + }, + + initialize: function(options) { + this.datesFilterModel = options.datesFilterModel; + + this.listView = new ListView({ + collection: this.collection, + ItemView: FilterListItemView, + itemOptions: { + className: 'label filter-label border filters-margin inline-block m-b-xs', + template: this.itemTemplate + } + }); + + this.listenTo(this.collection, 'reset update', this.updateVisibility); + }, + + render: function() { + this.$el.html(this.template({ + i18n: i18n + })); + + this.updateVisibility(); + + this.listView.render(); + this.$el.append(this.listView.$el); + + return this; + }, + + updateVisibility: function() { + this.$el.toggleClass('hide', this.collection.isEmpty()); + } + }); + +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/page/find-search.js b/src/main/webapp/static/js/find/app/page/find-search.js index eb5a0d4878..cc33251097 100644 --- a/src/main/webapp/static/js/find/app/page/find-search.js +++ b/src/main/webapp/static/js/find/app/page/find-search.js @@ -5,318 +5,86 @@ define([ 'js-whatever/js/base-page', - 'find/app/model/entity-collection', - 'find/app/model/documents-collection', - 'find/app/model/indexes-collection', + 'find/app/model/backbone-query-model', + 'find/app/model/query-model', + 'find/app/page/input-view', + 'find/app/page/service-view', 'find/app/router', 'find/app/vent', - 'i18n!find/nls/bundle', - 'text!find/templates/app/page/find-search.html', - 'text!find/templates/app/page/results-container.html', - 'text!find/templates/app/page/suggestions-container.html', - 'text!find/templates/app/page/loading-spinner.html', - 'text!find/templates/app/page/colorbox-controls.html', - 'text!find/templates/app/page/index-popover.html', - 'text!find/templates/app/page/index-popover-contents.html', - 'text!find/templates/app/page/top-results-popover-contents.html', - 'colorbox' -], function(BasePage, EntityCollection, DocumentsCollection, IndexesCollection, router, vent, i18n, template, resultsTemplate, - suggestionsTemplate, loadingSpinnerTemplate, colorboxControlsTemplate, indexPopover, indexPopoverContents, topResultsPopoverContents) { + 'underscore', + 'text!find/templates/app/page/find-search.html' +], function(BasePage, BackboneQueryModel, QueryModel, InputView, ServiceView, router, vent, _, template) { return BasePage.extend({ - + className: 'search-page', template: _.template(template), - resultsTemplate: _.template(resultsTemplate), - noResultsTemplate: _.template('
<%- i18n["search.noResults"] %>
'), - suggestionsTemplate: _.template(suggestionsTemplate), - indexPopover: _.template(indexPopover), - indexPopoverContents: _.template(indexPopoverContents), - topResultsPopoverContents: _.template(topResultsPopoverContents), - events: { - 'keyup .find-input': 'keyupAnimation', - 'click .list-indexes': _.debounce(function(){ - this.$('.popover-content label').html(''); + initialize: function() { + var backboneQueryModel = new BackboneQueryModel(); + this.queryModel = new QueryModel(backboneQueryModel); - this.indexesCollection.fetch(); - }, 500, true), - 'change [name="indexRadios"]': function(e) { - this.index = $(e.currentTarget).val(); + // Because the queryModel doesn't fire with the empty string, we listen to the unadulterated model + // underlying the main model for the change in queryText. + this.listenTo(backboneQueryModel, 'change:queryText', function(model, text) { + this.$('.find-input').val(text); //when clicking one of the suggested search links - if(this.$('.find-input').val()){ - this.searchRequest(this.$('.find-input').val()); + if(text.length) { // input has at least one non whitespace character + this.expandedState(); } - }, - 'mouseover .suggestions-content a': _.debounce(function(e) { - this.$('.suggestions-content .popover-content').append(_.template(loadingSpinnerTemplate)); - this.topResultsCollection.fetch({ - data: { - text: $(e.currentTarget).html(), - max_results: 3, - summary: 'quick', - index: this.index - } - }); - }, 800), - 'mouseover .entity-to-summary': function(e) { - var title = $(e.currentTarget).find('a').html(); - this.$('[data-title="'+ title +'"]').addClass('label label-primary entity-to-summary').removeClass('label-info'); - }, - 'mouseleave .entity-to-summary': function() { - this.$('.suggestions-content li a').removeClass('label label-primary entity-to-summary'); - this.$('.main-results-content .entity-to-summary').removeClass('label-primary').addClass('label-info'); - } - }, + }); - initialize: function() { - this.entityCollection = new EntityCollection(); - this.documentsCollection = new DocumentsCollection(); - this.topResultsCollection = new DocumentsCollection(); - this.indexesCollection = new IndexesCollection(); + this.inputView = new InputView({ + queryModel: this.queryModel + }); - router.on('route:search', function(text) { - this.entityCollection.reset(); - this.documentsCollection.set([]); + this.serviceView = new ServiceView({ + queryModel: this.queryModel + }); + router.on('route:search', function(text) { if (text) { this.$('.find-input').val(text); //when clicking one of the suggested search links - this.keyupAnimation(); + this.queryModel.set('queryText', text); } else { - this.reverseAnimation(); //when clicking the small 'find' logo + this.queryModel.set('queryText', ''); } }, this); - - this.indexesCollection.once('sync', function() { - this.index = this.indexesCollection.at(0).get('index'); - }, this); - - this.indexesCollection.fetch(); }, render: function() { this.$el.html(this.template); - this.$('.find-form').submit(function(e){ //preventing input form submit and page reload - e.preventDefault(); - }); - - this.$('.list-indexes').popover({ - html: true, - content: this.indexPopover(), - placement: 'bottom' - }); - - /*indices popover*/ - this.listenTo(this.indexesCollection, 'request', function(){ - if(this.$('.find-form .popover-content').length === 1) { - this.$('.find-form .popover-content').append(_.template(loadingSpinnerTemplate)); - } - }); - - this.listenTo(this.indexesCollection, 'add', function(model){ - this.$('.find-form .popover-content .loading-spinner').remove(); - - this.$('.find-form .popover-content ul').append(this.indexPopoverContents({ - index: model.get('index') - })); - - if (model.get('index') === this.index) { - this.$('[name="indexRadios"]').val([this.index]); - } - }); - - /*top 3 results popover*/ - this.listenTo(this.topResultsCollection, 'add', function(model){ - this.$('.suggestions-content .popover-content .loading-spinner').remove(); - - this.$('.suggestions-content .popover-content').append(this.topResultsPopoverContents({ - title: model.get('title'), - summary: model.get('summary').trim().substring(0, 100) + "..." - })); - }); - - /*suggested links*/ - this.listenTo(this.entityCollection, 'request', function() { - if(!this.$('.suggestions-content ul').length) { - this.$('.suggestions-content').append(_.template(loadingSpinnerTemplate)); - } - - this.$('.suggested-links-header').removeClass('hide') - }); - - this.listenTo(this.entityCollection, 'reset', function() { - this.$('.suggestions-content').empty(); - - if (this.entityCollection.isEmpty()) { - this.$('.suggested-links-header').addClass('hide') - } - else { - var clusters = this.entityCollection.groupBy('cluster'); - - _.each(clusters, function(entities) { - this.$('.suggestions-content').append(this.suggestionsTemplate({ - entities: entities - })); - - this.$('.suggestions-content li a').popover({ - html: true, - content: '
Top Results
', - placement: 'right', - trigger: 'hover' - }) - }, this); - - this.documentsCollection.each(function(document) { - var summary = this.addLinksToSummary(document.get('summary')); + this.inputView.setElement(this.$('.input-view-container')).render(); + this.serviceView.setElement(this.$('.service-view-container')).render(); - this.$('[data-reference="' + document.get('reference') + '"] .result-summary').html(summary); - }, this); - } - }); - - /*main results content*/ - this.listenTo(this.documentsCollection, 'request', function() { - if(!this.$('.main-results-container').length) { - this.$('.main-results-content').append(_.template(loadingSpinnerTemplate)); - } - - this.$('.main-results-content .no-results').remove(); - }); - - this.listenTo(this.documentsCollection, 'add', function(model) { - var reference = model.get('reference'); - var summary = model.get('summary'); - - summary = this.addLinksToSummary(summary); - - this.$('.main-results-content .loading-spinner').remove(); - - var $newResult = $(_.template(resultsTemplate ,{ - title: model.get('title'), - reference: reference, - index: model.get('index'), - summary: summary - })); - - this.$('.main-results-content').append($newResult); - - $newResult.find('.result-header').colorbox({ - iframe: true, - width:'70%', - height:'70%', - href: reference, - rel: 'results', - current: '{current} of {total}', - onComplete: _.bind(function() { - $('#cboxPrevious, #cboxNext').remove(); //removing default colorbox nav buttons - }, this) - }); - - $newResult.find('.dots').click(function (e) { - e.preventDefault(); - $newResult.find('.result-header').trigger('click'); //dot-dot-dot triggers the colorbox event - }); - }); - - this.listenTo(this.documentsCollection, 'remove', function(model) { - var reference = model.get('reference'); - - this.$('[data-reference="' + reference + '"]').remove(); - }); - - this.listenTo(this.documentsCollection, 'sync', function() { - if(this.documentsCollection.isEmpty()) { - this.$('.main-results-content .loading-spinner').remove(); - this.$('.main-results-content').append(this.noResultsTemplate({i18n: i18n})); - } - }); - - /*colorbox fancy button override*/ - $('#colorbox').append(_.template(colorboxControlsTemplate)); - $('.nextBtn').on('click', this.handleNextResult); - $('.prevBtn').on('click', this.handlePrevResult); + this.reducedState(); }, - addLinksToSummary: function(summary) { - //creating an array of the entity titles, longest first - var entities = this.entityCollection.map(function(entity) { - return { - text: entity.get('text'), - id: _.uniqueId('Find-IOD-Entity-Placeholder') - } - }).sort(function(a,b) { - return b.text.length - a.text.length; - }); - - _.each(entities, function(entity) { - summary = summary.replace(new RegExp('(^|\\s|[,.-:;?\'"!\\(\\)\\[\\]{}])' + entity.text + '($|\\s|[,.-:;?\'"!\\(\\)\\[\\]{}])', 'gi'), '$1' + entity.id + '$2'); - }); - - _.each(entities, function(entity) { - summary = summary.replace(new RegExp(entity.id, 'g'), '' + entity.text + ''); - }); - - return summary; - }, - - keyupAnimation: _.debounce(function() { + expandedState: function() { /*fancy animation*/ - if($.trim(this.$('.find-input').val()).length) { // input has at least one non whitespace character - this.$('.find').addClass('animated-container').removeClass('reverse-animated-container'); - - this.$('.suggested-links-container.span2').show(); - this.searchRequest(this.$('.find-input').val()); - } else { - this.reverseAnimation(); - vent.navigate('find/search', {trigger: false}); - } - this.$('.popover').remove(); - }, 500), + this.$('.find').addClass('animated-container col-md-offset-2').removeClass('reverse-animated-container col-md-offset-3'); - handlePrevResult: function() { - $.colorbox.prev(); - }, + this.$('.service-view-container').show(); + this.$('.app-logo').hide(); + $('.find-navbar').addClass('visible'); + $('.find-banner').addClass('hidden'); + $('.hp-logo-footer').addClass('hidden'); - handleNextResult: function() { - $.colorbox.next(); + vent.navigate('find/search/' + encodeURIComponent(this.queryModel.get('queryText')), {trigger: false}); }, - reverseAnimation: function() { + reducedState: function() { /*fancy reverse animation*/ - this.$('.find').removeClass('animated-container').addClass('reverse-animated-container'); - - this.$('.main-results-content').empty(); - this.$('.suggested-links-container.span2').hide(); - this.$('.find-input').val(''); - this.$('.popover').remove(); - }, - - searchRequest: function(input) { - if (this.index) { - this.documentsCollection.fetch({ - data: { - text: input, - max_results: 30, - summary: 'quick', - index: this.index - } - }, this); + this.$('.find').removeClass('animated-container col-md-offset-2').addClass('reverse-animated-container col-md-offset-3'); - this.entityCollection.fetch({ - data: { - text: input, - index: this.index - } - }); + this.$('.service-view-container').hide(); + this.$('.app-logo').show(); + $('.find-navbar').removeClass('visible'); + $('.find-banner').removeClass('hidden'); + $('.hp-logo-footer').removeClass('hidden'); - vent.navigate('find/search/' + encodeURIComponent(input), {trigger: false}); - } - else { - this.indexesCollection.once('sync', function() { - this.searchRequest(input); - }, this); - } + vent.navigate('find/search', {trigger: false}); } }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/page/find-settings-page.js b/src/main/webapp/static/js/find/app/page/find-settings-page.js index 2e6fbbac8e..b376e6f6ae 100644 --- a/src/main/webapp/static/js/find/app/page/find-settings-page.js +++ b/src/main/webapp/static/js/find/app/page/find-settings-page.js @@ -10,8 +10,10 @@ define([ 'settings/js/settings-page', 'find/app/model/config', 'find/app/page/settings/iod-widget', - 'settings/js/widgets/single-user-widget' -], function(i18n, vent, router, SettingsPage, configModel, IodWidget, SingleUserWidget) { + 'settings/js/widgets/single-user-widget', + 'find/app/page/settings/validate-on-save-modal', + 'find/app/util/confirm-view' +], function(i18n, vent, router, SettingsPage, configModel, IodWidget, SingleUserWidget, ValidateOnSaveModal, Confirm) { var serverStrings = function() { return { @@ -31,12 +33,14 @@ define([ var urlRoot = /\bconfig\/[\/]*$/.test(window.location.pathname) ? '../api/config/config/' : '../api/useradmin/config/'; return SettingsPage.extend({ + SaveModalConstructor: ValidateOnSaveModal, configModel: configModel, router: router, routeRoot: 'find/settings', scrollSelector: '.body', vent: vent, validateUrl: urlRoot + 'config-validation', + groupClass: 'col-md-4', strings: { cancelButton: i18n['settings.cancel'], cancelCancel: i18n['app.cancel'], @@ -79,6 +83,9 @@ define([ isOpened: true, title: 'IoD settings', strings: _.extend(serverStrings(), { + application: i18n['settings.iod.application'], + apiKey: i18n['settings.iod.apiKey'], + domain: i18n['settings.iod.domain'], iconClass: 'key', validateFailed: 'Invalid API Key', validateSuccess: 'API Key OK', @@ -108,6 +115,26 @@ define([ }) ] ]; + }, + + handleCancelButton: function(e) { + e.preventDefault(); + + new Confirm({ + cancelClass: 'btn-default', + cancelIcon: 'icon-remove', + cancelText: this.strings.cancelCancel, + okText: this.strings.cancelOk, + okClass: 'btn-primary', + okIcon: 'icon-undo', + message: this.strings.cancelMessage, + title: this.strings.cancelTitle, + hiddenEvent: 'hidden.bs.modal', + okHandler: _.bind(function() { + this.loadFromConfig(); + }, this) + }); + } }); diff --git a/src/main/webapp/static/js/find/app/page/indexes/indexes-view.js b/src/main/webapp/static/js/find/app/page/indexes/indexes-view.js new file mode 100644 index 0000000000..bcfab34e03 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/indexes/indexes-view.js @@ -0,0 +1,108 @@ +define([ + 'backbone', + 'underscore', + 'jquery', + 'databases-view/js/databases-view', + 'i18n!find/nls/bundle', + 'text!find/templates/app/page/indexes/indexes-view.html', + 'text!find/templates/app/page/indexes/index-list.html', + 'text!find/templates/app/page/indexes/index-item.html' +], function(Backbone, _, $, DatabasesView, i18n, template, listTemplate, itemTemplate) { + + var CHECKED_CLASS = 'icon-ok'; + var INDETERMINATE_CLASS = 'icon-minus'; + var DISABLED_CLASS = 'disabled'; + + var ICON_SELECTOR = '> span > .database-icon'; + + return DatabasesView.extend({ + template: _.template(template), + categoryTemplate: _.template(listTemplate), + databaseTemplate: _.template(itemTemplate), + + events: { + 'click li[data-id]': function(e) { + e.stopPropagation(); + + var $target = $(e.currentTarget).find('.database-input'); + var index = $target.attr('data-name'); + var domain = $target.attr('data-domain'); + var checked = $target.find('i').hasClass('icon-ok'); + + this.selectDatabase(index, domain, !checked); + }, + 'click .category-input': function(e) { + // data-target means they've clicked a chevron, so we want to collapse stuff + if (!$(e.target).attr('data-target')) { + e.stopPropagation(); + + var $target = $(e.currentTarget); + var category = $target.attr('data-category-id'); + var checked = $target.find('i').hasClass('icon-ok'); + + this.selectCategory(category, !checked); + } + }, + 'show.bs.collapse': function(e) { + e.stopPropagation(); + + $(e.target).parent().find('> span > i[data-target]').removeClass('collapsed'); + }, + 'hide.bs.collapse': function(e) { + e.stopPropagation(); + + $(e.target).parent().find('> span > i[data-target]').addClass('collapsed'); + } + }, + + initialize: function (options) { + DatabasesView.prototype.initialize.call(this, { + databasesCollection: options.indexesCollection, + emptyMessage: i18n['search.indexes.empty'], + selectedDatabasesCollection: options.selectedDatabasesCollection, + topLevelDisplayName: i18n['search.indexes.all'], + childCategories: [ + { + name: 'public', + displayName: i18n['search.indexes.publicIndexes'], + className: 'list-unstyled', + filter: function(model) { + return model.get('domain') === 'PUBLIC_INDEXES'; + } + }, { + name: 'private', + displayName: i18n['search.indexes.privateIndexes'], + className: 'list-unstyled', + filter: function(model) { + return model.get('domain') !== 'PUBLIC_INDEXES'; + } + } + ] + }); + }, + + check: function($input) { + $input.find(ICON_SELECTOR).addClass(CHECKED_CLASS).removeClass(INDETERMINATE_CLASS); + }, + + uncheck: function($input) { + $input.find(ICON_SELECTOR).removeClass(CHECKED_CLASS).removeClass(INDETERMINATE_CLASS); + }, + + enable: function($input) { + $input.find(ICON_SELECTOR).removeClass(DISABLED_CLASS); + }, + + disable: function($input) { + $input.find(ICON_SELECTOR).addClass(DISABLED_CLASS); + }, + + determinate: function($input) { + $input.find(ICON_SELECTOR).removeClass(INDETERMINATE_CLASS); + }, + + indeterminate: function($input) { + $input.find(ICON_SELECTOR).addClass(INDETERMINATE_CLASS); + } + }); +}); diff --git a/src/main/webapp/static/js/find/app/page/input-view.js b/src/main/webapp/static/js/find/app/page/input-view.js new file mode 100644 index 0000000000..c4c4f92118 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/input-view.js @@ -0,0 +1,38 @@ +define([ + 'backbone', + 'jquery', + 'underscore', + 'text!find/templates/app/page/input-view.html' +], function(Backbone, $, _, template) { + + return Backbone.View.extend({ + template: _.template(template), + + events: { + 'keyup .find-input': _.debounce(function(e) { + var findInput = this.$('.find-input').val(); + + // Keycode 13 is Enter, so where the user wants to refresh the search + // we call refresh. In all other cases, if the chain of keyup events + // did not result in the query text changing we are protected from + // rerunning the search by backbone. + if (e.which === 13) { + this.queryModel.refresh(findInput); + } else { + this.queryModel.set('queryText', findInput); + } + }, 500), + 'submit .find-form': function(e) { + e.preventDefault(); + } + }, + + initialize: function(options) { + this.queryModel = options.queryModel; + }, + + render: function() { + this.$el.html(this.template); + } + }) +}); diff --git a/src/main/webapp/static/js/find/app/page/parametric/parametric-field-view.js b/src/main/webapp/static/js/find/app/page/parametric/parametric-field-view.js new file mode 100644 index 0000000000..628105045a --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/parametric/parametric-field-view.js @@ -0,0 +1,60 @@ +define([ + 'backbone', + 'underscore', + 'jquery', + 'js-whatever/js/list-view', + 'find/app/util/collapsible', + 'find/app/page/parametric/parametric-value-view' +], function(Backbone, _, $, ListView, Collapsible, ValueView) { + + var ValuesView = Backbone.View.extend({ + className: 'table', + tagName: 'table', + + initialize: function() { + this.listView = new ListView({ + collection: this.collection, + tagName: 'tbody', + ItemView: ValueView, + collectionChangeEvents: { + count: 'updateCount', + selected: 'updateSelected' + } + }); + }, + + render: function() { + this.$el.empty().append(this.listView.render().$el); + }, + + remove: function() { + this.listView.remove(); + Backbone.View.prototype.remove.call(this); + } + }); + + return Backbone.View.extend({ + className: 'animated fadeIn', + + initialize: function() { + this.$el.attr('data-field', this.model.id); + this.$el.attr('data-field-display-name', this.model.get('displayName')); + + this.collapsible = new Collapsible({ + title: this.model.get('displayName'), + view: new ValuesView({collection: this.model.fieldValues}), + collapsed: false + }); + }, + + render: function() { + this.$el.empty().append(this.collapsible.$el); + this.collapsible.render(); + }, + + remove: function() { + Backbone.View.prototype.remove.call(this); + this.collapsible.remove(); + } + }); +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/page/parametric/parametric-value-view.js b/src/main/webapp/static/js/find/app/page/parametric/parametric-value-view.js new file mode 100644 index 0000000000..a39030f567 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/parametric/parametric-value-view.js @@ -0,0 +1,44 @@ +define([ + 'backbone', + 'text!find/templates/app/page/parametric/parametric-value-view.html' +], function(Backbone, template) { + + return Backbone.View.extend({ + className: 'parametric-value-element selectable-table-item clickable', + tagName: 'tr', + + initialize: function() { + this.$el.attr('data-value', this.model.id); + }, + + render: function() { + this.$el.html(template); + + this.$text = this.$('.parametric-value-text'); + this.$check = this.$('.parametric-value-icon'); + + this.updateCount(); + this.updateSelected(); + }, + + updateCount: function() { + if (this.$text) { + var text = this.model.id; + var count = this.model.get('count'); + + if (count !== null) { + text += ' (' + count + ')'; + } + + this.$text.text(text); + } + }, + + updateSelected: function() { + if (this.$check) { + this.$check.toggleClass('hide', !this.model.get('selected')); + } + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/page/parametric/parametric-view.js b/src/main/webapp/static/js/find/app/page/parametric/parametric-view.js new file mode 100644 index 0000000000..282a1479d9 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/parametric/parametric-view.js @@ -0,0 +1,139 @@ +define([ + 'backbone', + 'underscore', + 'jquery', + 'js-whatever/js/list-view', + 'js-whatever/js/filtering-collection', + 'find/app/model/parametric-collection', + 'find/app/page/parametric/parametric-field-view', + 'fieldtext/js/field-text-parser', + 'parametric-refinement/display-collection', + 'i18n!find/nls/bundle', + 'text!find/templates/app/page/parametric/parametric-view.html' +], function(Backbone, _, $, ListView, FilteringCollection, ParametricCollection, FieldView, parser, DisplayCollection, i18n, template) { + + var DEBOUNCE_WAIT_MILLISECONDS = 500; + + return Backbone.View.extend({ + template: _.template(template)({i18n: i18n}), + + events: { + 'click [data-field] [data-value]': function(e) { + var $target = $(e.currentTarget); + var $field = $target.closest('[data-field]'); + + var attributes = { + field: $field.attr('data-field'), + fieldDisplayName: $field.attr('data-field-display-name'), + value: $target.attr('data-value') + }; + + if (this.selectedParametricValues.get(attributes)) { + this.selectedParametricValues.remove(attributes); + } else { + this.selectedParametricValues.add(attributes); + } + } + }, + + initialize: function(options) { + this.queryModel = options.queryModel; + this.selectedParametricValues = options.selectedParametricValues; + this.indexesCollection = options.indexesCollection; + this.selectedIndexesCollection = options.selectedIndexesCollection; + + this.parametricCollection = new ParametricCollection(); + + this.model = new Backbone.Model({processing: false, error: false}); + this.listenTo(this.model, 'change:processing', this.updateProcessing); + this.listenTo(this.model, 'change:error', this.updateError); + + this.displayCollection = new DisplayCollection([], { + parametricCollection: this.parametricCollection, + selectedParametricValues: this.selectedParametricValues + }); + + this.fieldNamesListView = new ListView({ + collection: this.displayCollection, + ItemView: FieldView + }); + + this.listenTo(this.selectedParametricValues, 'add remove reset', _.debounce(_.bind(function() { + var node = this.selectedParametricValues.toFieldTextNode(); + this.queryModel.set('fieldText', node && node.toString()); + }, this), DEBOUNCE_WAIT_MILLISECONDS)); + + this.listenTo(this.queryModel, 'change:indexes', function() { + this.selectedParametricValues.reset(); + }); + + function fetch() { + this.parametricCollection.reset(); + this.model.set({processing: true, error: false}); + + var fieldNames = this.selectedIndexesCollection.chain() + .map(function(database) { + return this.indexesCollection.findWhere(database.pick('name','domain')).get('fieldNames') + }, this) + .flatten() + .uniq() + .value(); + + if(_.isEmpty(fieldNames)) { + this.model.set('processing', false); + } else { + this.parametricCollection.fetch({ + data: { + databases: this.queryModel.get('indexes'), + fieldNames: fieldNames, + queryText: this.queryModel.get('queryText'), + fieldText: this.queryModel.get('fieldText') + }, + error: _.bind(function (collection, xhr) { + if (xhr.status !== 0) { + // The request was not aborted, so there isn't another request in flight + this.model.set({error: true, processing: false}); + } + }, this), + success: _.bind(function () { + this.model.set({processing: false}); + }, this) + }); + } + } + + this.listenTo(this.queryModel, 'refresh', fetch); + + this.listenTo(this.queryModel, 'change', function() { + if (this.queryModel.hasAnyChangedAttributes(['queryText', 'indexes', 'fieldText'])) { + fetch.call(this); + } + }, this); + }, + + render: function() { + this.$el.html(this.template).prepend(this.fieldNamesListView.render().$el); + + this.$errorMessage = this.$('.parametric-error'); + this.$processing = this.$('.parametric-processing-indicator'); + + this.updateError(); + this.updateProcessing(); + + return this; + }, + + updateProcessing: function() { + if (this.$processing) { + this.$processing.toggleClass('hide', !this.model.get('processing')); + } + }, + + updateError: function() { + if (this.$errorMessage) { + this.$errorMessage.toggleClass('hide', !this.model.get('error')); + } + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/page/related-concepts/related-concepts-view.js b/src/main/webapp/static/js/find/app/page/related-concepts/related-concepts-view.js new file mode 100644 index 0000000000..60aa4911e7 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/related-concepts/related-concepts-view.js @@ -0,0 +1,123 @@ +define([ + 'backbone', + 'jquery', + 'underscore', + 'i18n!find/nls/bundle', + 'find/app/model/documents-collection', + 'find/app/util/popover', + 'find/app/util/view-state-selector', + 'text!find/templates/app/page/related-concepts/related-concepts-view.html', + 'text!find/templates/app/page/related-concepts/related-concept-list-item.html', + 'text!find/templates/app/page/popover-message.html', + 'text!find/templates/app/page/results-popover.html', + 'text!find/templates/app/page/loading-spinner.html' +], function(Backbone, $, _, i18n, DocumentsCollection, popover, viewStateSelector, relatedConceptsView, relatedConceptListItemTemplate, + popoverMessageTemplate, popoverTemplate, loadingSpinnerTemplate) { + + function popoverHandler($content, $target) { + var queryText = $target.text(); + + var topResultsCollection = new DocumentsCollection([], { + indexesCollection: this.indexesCollection + }); + + topResultsCollection.fetch({ + reset: true, + data: { + text: queryText, + max_results: 3, + summary: 'context', + index: this.queryModel.get('indexes') + }, + error: _.bind(function() { + $content.html(this.popoverMessageTemplate({message: i18n['search.relatedConcepts.topResults.error']})); + }, this), + success: _.bind(function() { + if (topResultsCollection.isEmpty()) { + $content.html(this.popoverMessageTemplate({message: i18n['search.relatedConcepts.topResults.none']})); + } else { + $content.html(this.popoverTemplate({collection: topResultsCollection})); + } + }, this) + }); + } + + return Backbone.View.extend({ + className: 'suggestions-content', + + template: _.template(relatedConceptsView), + listItemTemplate: _.template(relatedConceptListItemTemplate), + popoverTemplate: _.template(popoverTemplate), + popoverMessageTemplate: _.template(popoverMessageTemplate), + loadingSpinnerTemplate: _.template(loadingSpinnerTemplate)({i18n: i18n, large: false}), + + events: { + 'click .query-text' : function(e) { + var $target = $(e.target); + var queryText = $target.attr('data-title'); + this.queryModel.set('queryText', queryText); + } + }, + + initialize: function(options) { + this.queryModel = options.queryModel; + this.entityCollection = options.entityCollection; + this.indexesCollection = options.indexesCollection; + + // Each instance of this view gets its own bound, de-bounced popover handler + var handlePopover = _.debounce(_.bind(popoverHandler, this), 500); + + this.listenTo(this.entityCollection, 'reset', function() { + this.$list.empty(); + + if (this.entityCollection.isEmpty()) { + this.selectViewState(['none']); + } else { + this.selectViewState(['list']); + + var clusters = this.entityCollection.groupBy('cluster'); + + _.each(clusters, function(entities) { + this.$list.append(this.listItemTemplate({entities: entities})); + }, this); + + popover(this.$list.find('.query-text'), handlePopover); + } + }); + + /*suggested links*/ + this.listenTo(this.entityCollection, 'request', function() { + this.selectViewState(['processing']); + }); + + this.listenTo(this.entityCollection, 'error', function() { + this.selectViewState(['error']); + + this.$error.text(i18n['search.error.relatedConcepts']); + }); + }, + + render: function() { + this.$el.html(this.template({i18n:i18n})); + + this.$list = this.$('.related-concepts-list'); + this.$error = this.$('.related-concepts-error'); + + this.$none = this.$('.related-concepts-none'); + + this.$notLoading = this.$('.not-loading'); + + this.$processing = this.$('.processing'); + this.$processing.append(this.loadingSpinnerTemplate); + + this.selectViewState = viewStateSelector({ + list: this.$list, + processing: this.$processing, + error: this.$error, + none: this.$none, + notLoading: this.$notLoading + }); + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/page/results/results-view.js b/src/main/webapp/static/js/find/app/page/results/results-view.js new file mode 100644 index 0000000000..1911c30ecc --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/results/results-view.js @@ -0,0 +1,425 @@ +define([ + 'backbone', + 'jquery', + 'find/app/model/documents-collection', + 'find/app/model/promotions-collection', + 'find/app/model/similar-documents-collection', + 'find/app/util/popover', + 'find/app/util/view-server-client', + 'find/app/util/document-mime-types', + 'js-whatever/js/escape-regex', + 'text!find/templates/app/page/results-popover.html', + 'text!find/templates/app/page/popover-message.html', + 'text!find/templates/app/page/results/results-view.html', + 'text!find/templates/app/page/results-container.html', + 'text!find/templates/app/page/colorbox-controls.html', + 'text!find/templates/app/page/loading-spinner.html', + 'text!find/templates/app/page/view/media-player.html', + 'text!find/templates/app/page/view/view-document.html', + 'text!find/templates/app/page/results/entity-label.html', + 'moment', + 'i18n!find/nls/bundle', + 'colorbox' +], function(Backbone, $, DocumentsCollection, PromotionsCollection, SimilarDocumentsCollection, popover, viewClient, documentMimeTypes, + escapeRegex, popoverTemplate, popoverMessageTemplate, template, resultsTemplate, colorboxControlsTemplate, + loadingSpinnerTemplate, mediaPlayerTemplate, viewDocumentTemplate, entityTemplate, moment, i18n) { + + /** Whitespace OR character in set bounded by [] */ + var boundaryChars = '\\s|[,.-:;?\'"!\\(\\)\\[\\]{}]'; + /** Start of input OR boundary chars */ + var startRegex = '(^|' + boundaryChars + ')'; + /** End of input OR boundary chars */ + var endRegex = '($|' + boundaryChars + ')'; + + var mediaTypes = ['audio', 'video']; + + var getContentTypeClass = function(model) { + var contentType = model.get('fields').content_type ? model.get('fields').content_type[0] : ''; + + var matchedType = _.find(documentMimeTypes, function(mimeType) { + return Boolean(_.find(mimeType.typeRegex, function(regex) { + return regex().test(contentType); + })); + }); + + return matchedType.className; + }; + + var $window = $(window); + var SIZE = '90%'; + + var onResize = function() { + $.colorbox.resize({width: SIZE, height: SIZE}); + }; + + return Backbone.View.extend({ + loadingTemplate: _.template(loadingSpinnerTemplate)({i18n: i18n, large: true}), + resultsTemplate: _.template(resultsTemplate), + popoverMessageTemplate: _.template(popoverMessageTemplate), + messageTemplate: _.template('
<%-message%>
'), + mediaPlayerTemplate: _.template(mediaPlayerTemplate), + popoverTemplate: _.template(popoverTemplate), + entityTemplate: _.template(entityTemplate), + viewDocumentTemplate: _.template(viewDocumentTemplate), + + events: { + 'click .query-text' : function(e) { + var $target = $(e.target); + var queryText = $target.attr('data-title'); + this.queryModel.set('queryText', queryText); + }, + 'mouseover .entity-to-summary': function(e) { + var title = $(e.currentTarget).find('a').html(); + this.$('[data-title="'+ title +'"]').addClass('label label-primary entity-to-summary').removeClass('label-info'); + }, + 'mouseleave .entity-to-summary': function() { + this.$('.suggestions-content li a').removeClass('label label-primary entity-to-summary'); + this.$('.main-results-content .entity-to-summary').removeClass('label-primary').addClass('label-info'); + } + }, + + initialize: function(options) { + _.bindAll(this, 'handlePopover'); + + this.queryModel = options.queryModel; + this.entityCollection = options.entityCollection; + this.indexesCollection = options.indexesCollection; + + this.documentsCollection = new DocumentsCollection([], { + indexesCollection: options.indexesCollection + }); + + this.promotionsCollection = new PromotionsCollection([], { + indexesCollection: options.indexesCollection + }); + + this.listenTo(this.queryModel, 'change refresh', function() { + if (!_.isEmpty(this.queryModel.get('indexes'))) { + this.documentsCollection.fetch({ + data: { + text: this.queryModel.get('queryText'), + max_results: 30, + summary: 'context', + index: this.queryModel.get('indexes'), + field_text: this.queryModel.get('fieldText'), + min_date: this.queryModel.getIsoDate('minDate'), + max_date: this.queryModel.getIsoDate('maxDate'), + sort: this.queryModel.get('sort') + }, + reset: false + }, this); + + // TODO: Move out of if statement when HOD allows fetching promotions without query text + this.promotionsCollection.fetch({ + data: { + text: this.queryModel.get('queryText'), + max_results: 30, // TODO maybe less? + summary: 'context', + index: this.queryModel.get('indexes'), + field_text: this.queryModel.get('fieldText'), + min_date: this.queryModel.getIsoDate('minDate'), + max_date: this.queryModel.getIsoDate('maxDate'), + sort: this.queryModel.get('sort') + }, + reset: false + }, this); + } + }); + }, + + clearLoadingSpinner: function() { + if(this.resultsFinished && this.promotionsFinished) { + this.$loadingSpinner.addClass('hide'); + } + }, + + render: function() { + this.$el.html(template); + + this.$loadingSpinner = $(this.loadingTemplate); + + this.$el.prepend(this.$loadingSpinner); + + /*promotions content content*/ + this.listenTo(this.promotionsCollection, 'add', function(model) { + this.formatResult(model, true); + }); + + this.listenTo(this.promotionsCollection, 'request', function () { + this.promotionsFinished = false; + this.$('.main-results-content .promotions').empty(); + }); + + this.listenTo(this.promotionsCollection, 'sync', function () { + this.promotionsFinished = true; + this.clearLoadingSpinner(); + }); + + this.listenTo(this.promotionsCollection, 'error', function (collection, xhr) { + this.promotionsFinished = true; + this.clearLoadingSpinner(); + + this.$('.main-results-content .promotions').append(this.errorMessage(i18n["search.error.promotions"], xhr)); + }); + + /*main results content*/ + this.listenTo(this.documentsCollection, 'request', function () { + this.resultsFinished = false; + this.$loadingSpinner.removeClass('hide'); + this.$('.main-results-content .results').empty(); + }); + + this.listenTo(this.documentsCollection, 'add', function (model) { + this.formatResult(model, false); + }); + + this.listenTo(this.documentsCollection, 'sync', function () { + this.resultsFinished = true; + this.clearLoadingSpinner(); + + if (this.documentsCollection.isEmpty()) { + this.$('.main-results-content .results').append(this.messageTemplate({message: i18n["search.noResults"]})); + } + }); + + this.listenTo(this.documentsCollection, 'error', function (collection, xhr) { + this.resultsFinished = true; + this.clearLoadingSpinner(); + + this.$('.main-results-content .results').append(this.errorMessage(i18n["search.error.results"], xhr)); + }); + + this.listenTo(this.entityCollection, 'reset', function() { + if (!this.entityCollection.isEmpty()) { + this.documentsCollection.each(function(document) { + var summary = this.addLinksToSummary(document.get('summary')); + + this.$('[data-reference="' + document.get('reference') + '"] .result-summary').html(summary); + }, this); + + this.promotionsCollection.each(function(document) { + var summary = this.addLinksToSummary(document.get('summary')); + + this.$('[data-reference="' + document.get('reference') + '"] .result-summary').html(summary); + }, this); + } + }); + + /*colorbox fancy button override*/ + $('#colorbox').append(_.template(colorboxControlsTemplate)); + $('.nextBtn').on('click', this.handleNextResult); + $('.prevBtn').on('click', this.handlePrevResult); + }, + + handlePrevResult: function() { + $.colorbox.prev(); + }, + + handleNextResult: function() { + $.colorbox.next(); + }, + + colorboxArguments: function(options) { + var args = { + current: '{current} of {total}', + height:'70%', + iframe: false, + rel: 'results', + width:'70%', + onClosed: function() { + $window.off('resize', onResize); + }, + onComplete: _.bind(function() { + $('#cboxPrevious, #cboxNext').remove(); //removing default colorbox nav buttons + + var $viewServerPage = $('.view-server-page'); + + $viewServerPage.on('load', function() { + $('.view-server-loading-indicator').addClass('hidden'); + $('.view-server-page').removeClass('hidden'); + }); + + $window.resize(onResize); + }, this) + }; + + if(options.media) { + args.html = this.mediaPlayerTemplate({ + media: options.media, + url: options.url, + offset: options.offset + }); + } else { + args.html = this.viewDocumentTemplate({ + src: options.href, + i18n: i18n, + model: options.model, + dateFields: ['date', 'dateCreated', 'dateModified'], + fields: ['reference', 'author', 'categories'] + }); + } + + return args; + }, + + formatResult: function(model, isPromotion) { + var reference = model.get('reference'); + var summary = model.get('summary'); + + var date = null; + var dateStamp = null; + + var dateArray = model.get('fields').date; + + if(dateArray) { + dateStamp = dateArray[0]; + + if(_.isFinite(dateStamp) && Math.floor(dateStamp) === dateStamp) { + date = moment(dateStamp * 1000).format("YYYY/MM/DD HH:mm:ss"); + } + } + + summary = this.addLinksToSummary(summary); + + var href = viewClient.getHref(reference, model.get('index')); + + var $newResult = $(this.resultsTemplate({ + i18n: i18n, + title: model.get('title'), + reference: reference, + href: href, + summary: summary, + promotion: isPromotion, + date: date, + contentType: getContentTypeClass(model) + })); + + if (isPromotion) { + this.$('.main-results-content .promotions').append($newResult); + } else { + this.$('.main-results-content .results').append($newResult); + } + + var fields = model.get('fields'); + var contentType = fields.content_type ? fields.content_type[0] : ''; + + var media = _.find(mediaTypes, function(mediaType) { + return contentType.indexOf(mediaType) === 0; + }); + + if (media && fields.url) { + var url = fields.url[0]; + var offset = fields.offset ? fields.offset[0] : 0; + + $newResult.find('.result-header').colorbox(this.colorboxArguments({model: model, media: media, url: url, offset: offset})); + } else { + // Use the standard Viewserver display + $newResult.find('.result-header').colorbox(this.colorboxArguments({model: model, href: href})); + } + + $newResult.find('.dots').click(function (e) { + e.preventDefault(); + $newResult.find('.result-header').trigger('click'); //dot-dot-dot triggers the colorbox event + }); + + popover($newResult.find('.similar-documents-trigger'), this.handlePopover); + }, + + addLinksToSummary: function(summary) { + // Protect us from XSS + summary = _.escape(summary); + + // Process the search text first + var searchText = this.queryModel.get("queryText"); + var searchTextID = _.uniqueId('Find-IOD-QueryText-Placeholder'); + summary = this.replaceBoundedText(summary, searchText, searchTextID); + + // Create an array of the entity titles, longest first + var entities = this.entityCollection.map(function(entity) { + return { + text: entity.get('text'), + id: _.uniqueId('Find-IOD-Entity-Placeholder') + }; + }).sort(function(a,b) { + return b.text.length - a.text.length; + }); + + // Loop through entities, replacing each with a unique id to prevent later replaces finding what we've + // changed here and messing things up badly + _.each(entities, function(entity) { + summary = this.replaceBoundedText(summary, entity.text, entity.id); + }, this); + + // Loop through entities again, replacing text with labels + _.each(entities, function(entity) { + summary = this.replaceTextWithLabel(summary, entity.id, entity.text, "entity-to-summary"); + }, this); + + // Add the search text label + summary = this.replaceTextWithLabel(summary, searchTextID, searchText, "entity-to-summary"); + + return summary; + }, + + /** + * Finds a string that's bounded by [some regex stuff] and replaces it with something else. + * Used as part 1 of highlighting text in result summaries. + * @param text The text to search in + * @param textToFind The text to search for + * @param replacement What to replace textToFind with + * @returns {string|XML|void} `text`, but with replacements made + */ + replaceBoundedText: function(text, textToFind, replacement) { + return text.replace(new RegExp(startRegex + escapeRegex(textToFind) + endRegex, 'gi'), '$1' + replacement + '$2'); + }, + + /** + * Finds a string and replaces it with an HTML label. + * Used as part 2 of highlighting text in results summaries. + * @param text The text to search in + * @param textToFind The text to replace with a label + * @param replacement The term or phrase to display in the label + * @returns {string|XML|*} `text`, but with replacements made + */ + replaceTextWithLabel: function(text, textToFind, replacement, labelClasses) { + var label = this.entityTemplate({ + replacement: replacement, + labelClasses: labelClasses + }); + + return text.replace(new RegExp(startRegex + textToFind + endRegex, 'g'), '$1' + label + '$2'); + }, + + handlePopover: function($content, $target) { + var collection = new SimilarDocumentsCollection([], { + indexes: this.queryModel.get('indexes'), + reference: $target.closest('[data-reference]').attr('data-reference') + }); + + collection.fetch({ + error: _.bind(function() { + $content.html(this.popoverMessageTemplate({message: i18n['search.similarDocuments.error']})); + }, this), + success: _.bind(function() { + if (collection.isEmpty()) { + $content.html(this.popoverMessageTemplate({message: i18n['search.similarDocuments.none']})); + } else { + $content.html(this.popoverTemplate({collection: collection})); + } + }, this) + }); + }, + + errorMessage: function(serviceErrorMessage, xhr) { + var messageTemplate; + + if (xhr.responseJSON && i18n["hod.error." + xhr.responseJSON.hodErrorCode]) { + messageTemplate = this.messageTemplate({message: serviceErrorMessage + ': ' + i18n["hod.error." + xhr.responseJSON.hodErrorCode]}); + } else { + messageTemplate = this.messageTemplate({message: serviceErrorMessage}); + } + + return messageTemplate; + } + }); +}); diff --git a/src/main/webapp/static/js/find/app/page/service-view.js b/src/main/webapp/static/js/find/app/page/service-view.js new file mode 100644 index 0000000000..a9bdf855ac --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/service-view.js @@ -0,0 +1,145 @@ +define([ + 'backbone', + 'jquery', + 'underscore', + 'find/app/model/dates-filter-model', + 'find/app/model/indexes-collection', + 'find/app/model/entity-collection', + 'find/app/model/search-filters-collection', + 'find/app/page/parametric/parametric-view', + 'find/app/page/filter-display/filter-display-view', + 'find/app/page/date/dates-filter-view', + 'find/app/page/results/results-view', + 'find/app/page/related-concepts/related-concepts-view', + 'find/app/page/sort/sort-view', + 'find/app/page/indexes/indexes-view', + 'find/app/util/collapsible', + 'parametric-refinement/selected-values-collection', + 'i18n!find/nls/bundle', + 'text!find/templates/app/page/service-view.html' +], function(Backbone, $, _, DatesFilterModel, IndexesCollection, EntityCollection, SearchFiltersCollection, + ParametricView, FilterDisplayView, DateView, ResultsView, RelatedConceptsView, SortView, + IndexesView, Collapsible, SelectedParametricValuesCollection, i18n, template) { + + var collapseView = function(titleKey, view) { + return new Collapsible({ + view: view, + collapsed: false, + title: i18n[titleKey] + }); + }; + + return Backbone.View.extend({ + template: _.template(template)({i18n: i18n}), + + initialize: function(options) { + this.queryModel = options.queryModel; + + this.datesFilterModel = new DatesFilterModel({}, {queryModel: this.queryModel}); + + this.indexesCollection = new IndexesCollection(); + this.entityCollection = new EntityCollection(); + this.selectedParametricValues = new SelectedParametricValuesCollection(); + this.selectedIndexesCollection = new IndexesCollection(); + + this.filtersCollection = new SearchFiltersCollection([], { + queryModel: this.queryModel, + datesFilterModel: this.datesFilterModel, + indexesCollection: this.indexesCollection, + selectedIndexesCollection: this.selectedIndexesCollection, + selectedParametricValues: this.selectedParametricValues + }); + + this.indexesCollection.fetch(); + + var fetchEntities = _.bind(function() { + this.entityCollection.fetch({ + data: { + text: this.queryModel.get('queryText'), + index: this.queryModel.get('indexes'), + field_text: this.queryModel.get('fieldText') + } + }); + }, this); + + this.listenTo(this.queryModel, 'refresh', fetchEntities); + + this.listenTo(this.queryModel, 'change', function() { + if (this.queryModel.hasAnyChangedAttributes(['queryText', 'indexes', 'fieldText'])) { + fetchEntities(); + } + }); + + this.listenTo(this.selectedIndexesCollection, 'update reset', _.debounce(_.bind(function() { + this.queryModel.set('indexes', this.selectedIndexesCollection.map(function(model) { + return encodeURIComponent(model.get('domain')) + ':' + encodeURIComponent(model.get('name')); + })); + }, this), 500)); + + this.resultsView = new ResultsView({ + entityCollection: this.entityCollection, + indexesCollection: this.indexesCollection, + queryModel: this.queryModel + }); + + // Left Views + this.filterDisplayView = new FilterDisplayView({ + collection: this.filtersCollection, + datesFilterModel: this.datesFilterModel + }); + + this.parametricView = new ParametricView({ + queryModel: this.queryModel, + selectedParametricValues: this.selectedParametricValues, + indexesCollection: this.indexesCollection, + selectedIndexesCollection: this.selectedIndexesCollection, + }); + + // Left Collapsed Views + this.indexesView = new IndexesView({ + queryModel: this.queryModel, + indexesCollection: this.indexesCollection, + selectedDatabasesCollection: this.selectedIndexesCollection + }); + + this.dateView = new DateView({ + queryModel: this.queryModel, + datesFilterModel: this.datesFilterModel + }); + + //Right Collapsed View + this.relatedConceptsView = new RelatedConceptsView({ + entityCollection: this.entityCollection, + indexesCollection: this.indexesCollection, + queryModel: this.queryModel + }); + + this.sortView = new SortView({ + queryModel: this.queryModel + }); + + // Collapse wrappers + this.indexesViewWrapper = collapseView('search.indexes', this.indexesView); + this.dateViewWrapper = collapseView('search.dates', this.dateView); + this.relatedConceptsViewWrapper = collapseView('search.relatedConcepts', this.relatedConceptsView); + }, + + render: function() { + this.$el.html(this.template); + + this.filterDisplayView.setElement(this.$('.filter-display-container')).render(); + this.indexesViewWrapper.setElement(this.$('.indexes-container')).render(); + this.parametricView.setElement(this.$('.parametric-container')).render(); + this.dateViewWrapper.setElement(this.$('.date-container')).render(); + + this.relatedConceptsViewWrapper.render(); + + this.sortView.setElement(this.$('.sort-container')).render(); + + this.$('.related-concepts-container').append(this.relatedConceptsViewWrapper.$el); + + this.resultsView.setElement(this.$('.results-container')).render(); + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/page/settings/iod-widget.js b/src/main/webapp/static/js/find/app/page/settings/iod-widget.js index d0a4f0bad0..fae3e69f33 100644 --- a/src/main/webapp/static/js/find/app/page/settings/iod-widget.js +++ b/src/main/webapp/static/js/find/app/page/settings/iod-widget.js @@ -1,131 +1,160 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -define([ - 'settings/js/server-widget', - 'find/app/model/indexes-collection', - 'text!find/templates/app/page/settings/iod-widget.html', - 'text!find/templates/app/page/settings/indexes-list.html' -], function(ServerWidget, IndexesCollection, template, indexesTemplate) { - - template = _.template(template); - indexesTemplate = _.template(indexesTemplate); - - return ServerWidget.extend({ - render: function() { - ServerWidget.prototype.render.call(this); - - this.$validateButtonParent = this.$('button[name="validate"]').parent(); - this.$validateButtonParent.before(template({strings: this.strings})); - - this.$apikey = this.$('input[name="apikey"]'); - this.$apiKeyControlGroup = this.$('.control-group').eq(0); - }, - - getConfig: function() { - var $indexCheckboxes = this.$('input[type="checkbox"]:checked'); - - var activeIndexes; - - if ($indexCheckboxes.length) { - var selectedIndexes = _.map($indexCheckboxes, function (input) { - return $(input).val(); - }); - - activeIndexes = _.filter(this.indexes, function (index) { - return _.contains(selectedIndexes, index.index); - }); - } - else if(this.$('input[type="checkbox"]').length) { - // no checkboxes ticked - activeIndexes = []; - } - else { - // checkboxes have not rendered, use previous value - activeIndexes = this.indexes; - } - - return { - apiKey: this.$apikey.val(), - activeIndexes: activeIndexes - }; - }, - - updateConfig: function(config) { - ServerWidget.prototype.updateConfig.apply(this, arguments); - - this.$apikey.val(config.apiKey); - this.indexes = config.activeIndexes; - }, - - validateInputs: function() { - var isValid = true; - - if (this.shouldValidate()) { - var config = this.getConfig(); - - if (config.apiKey === '') { - isValid = false; - this.updateInputValidation(this.$apikey); - } - } - - return isValid; - }, - - handleValidation: function(config, response) { - if (_.isEqual(config.iod, this.lastValidationConfig.iod)) { - this.lastValidation = response.valid; - - if(this.lastValidation && response.data && response.data.indexes) { - var privateIndexes = response.data.indexes.privateIndexes; - - _.each(privateIndexes, function(privateIndex) { - privateIndex.private = true; - }); - - this.indexes = response.data.indexes.publicIndexes.concat(privateIndexes); - } - else { - this.indexes = []; - } - - this.hideValidationInfo(); - - this.toggleIndexes(); - - this.displayValidationMessage(true, response); - - if(response.data) { - _.each(response.data.activeIndexes, function(activeIndex){ - this.$('[value="' + activeIndex.index + '"]').prop('checked', true); - }) - } - } - }, - - setValidationFormatting: function(state) { - if (state === 'clear') { - this.$apiKeyControlGroup.removeClass('success error'); - } else { - this.$apiKeyControlGroup.addClass(state) - .removeClass(state === 'success' ? 'error' : 'success'); - } - }, - - toggleIndexes: function() { - this.$('.indexes-list-group').remove(); - - if(this.indexes) { - if(this.indexes.length && this.lastValidation) { - this.$validateButtonParent.after(indexesTemplate({ - indexes: this.indexes - })); - } - } - } - }); - -}); +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'settings/js/server-widget', + 'find/app/model/indexes-collection', + 'text!find/templates/app/page/settings/server-widget.html', + 'text!find/templates/app/page/settings/iod-widget.html', + 'text!find/templates/app/page/settings/indexes-list.html' +], function(ServerWidget, IndexesCollection, serverWidget, template, indexesTemplate) { + + template = _.template(template); + indexesTemplate = _.template(indexesTemplate); + + return ServerWidget.extend({ + serverTemplate: _.template(serverWidget), + + render: function() { + ServerWidget.prototype.render.call(this); + + this.$validateButtonParent = this.$('button[name="validate"]').parent(); + this.$validateButtonParent.before(template({strings: this.strings})); + + this.$apikey = this.$('input[name="apikey"]'); + this.$application = this.$('input[name="application"]'); + this.$domain = this.$('input[name="domain"]'); + this.$apiKeyControlGroup = this.$('.form-group').eq(0); + }, + + getConfig: function() { + var $indexCheckboxes = this.$('input[type="checkbox"]:checked'); + + var activeIndexes; + + var domain = this.$domain.val(); + + if ($indexCheckboxes.length) { + var selectedIndexes = _.map($indexCheckboxes, function (input) { + return $(input).val(); + }); + + activeIndexes = _.chain(this.indexes) + .filter(function (index) { + return _.contains(selectedIndexes, index.resource); + }) + .map(function(index) { + return { + domain: index.private ? domain : 'PUBLIC_INDEXES', + name: index.resource + } + }) + .value(); + } + else if(this.$('input[type="checkbox"]').length) { + // no checkboxes ticked + activeIndexes = []; + } + else { + // checkboxes have not rendered, use previous value + activeIndexes = this.indexes; + } + + return { + apiKey: this.$apikey.val(), + application: this.$application.val(), + domain: domain, + activeIndexes: activeIndexes + }; + }, + + updateConfig: function(config) { + ServerWidget.prototype.updateConfig.apply(this, arguments); + + this.$apikey.val(config.apiKey); + this.$application.val(config.application); + this.$domain.val(config.domain); + this.indexes = config.activeIndexes; + }, + + validateInputs: function() { + var isValid = true; + + if (this.shouldValidate()) { + var config = this.getConfig(); + + if (config.apiKey === '') { + isValid = false; + this.updateInputValidation(this.$apikey); + } + + if (config.application === '') { + isValid = false; + this.updateInputValidation(this.$application); + } + + if (config.domain === '') { + isValid = false; + this.updateInputValidation(this.$domain); + } + } + + return isValid; + }, + + handleValidation: function(config, response) { + if (_.isEqual(config.iod, this.lastValidationConfig.iod)) { + this.lastValidation = response.valid; + + if(this.lastValidation && response.data && response.data.indexes) { + var privateIndexes = response.data.indexes.resources; + + _.each(privateIndexes, function(privateIndex) { + privateIndex.private = true; + }); + + this.indexes = response.data.indexes.publicResources.concat(privateIndexes); + } + else { + this.indexes = []; + } + + this.hideValidationInfo(); + + this.toggleIndexes(); + + this.displayValidationMessage(true, response); + + if(response.data) { + _.each(response.data.activeIndexes, function(activeIndex){ + this.$('[value="' + activeIndex.index + '"]').prop('checked', true); + }) + } + } + }, + + setValidationFormatting: function(state) { + if (state === 'clear') { + this.$apiKeyControlGroup.removeClass('success error'); + } else { + this.$apiKeyControlGroup.addClass(state) + .removeClass(state === 'success' ? 'error' : 'success'); + } + }, + + toggleIndexes: function() { + this.$('.indexes-list-group').remove(); + + if(this.indexes) { + if(this.indexes.length && this.lastValidation) { + this.$validateButtonParent.after(indexesTemplate({ + indexes: this.indexes + })); + } + } + } + }); + +}); diff --git a/src/main/webapp/static/js/find/app/page/settings/validate-on-save-modal.js b/src/main/webapp/static/js/find/app/page/settings/validate-on-save-modal.js new file mode 100644 index 0000000000..e1dce88413 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/settings/validate-on-save-modal.js @@ -0,0 +1,11 @@ +define([ + 'settings/js/validate-on-save-modal', + 'text!find/templates/app/page/settings/validate-on-save-modal.html' +], function(SaveModal, template) { + + return SaveModal.extend({ + className: 'modal fade', + + template: _.template(template) + }); +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/page/sort/sort-view.js b/src/main/webapp/static/js/find/app/page/sort/sort-view.js new file mode 100644 index 0000000000..415781fc95 --- /dev/null +++ b/src/main/webapp/static/js/find/app/page/sort/sort-view.js @@ -0,0 +1,40 @@ +define([ + 'backbone', + 'find/app/model/backbone-query-model', + 'text!find/templates/app/page/sort/sort-view.html', + 'i18n!find/nls/bundle' +], function(Backbone, QueryModel, template, i18n) { + + return Backbone.View.extend({ + template: _.template(template), + + events: { + 'click [data-sort]': function(e) { + var sortType = $(e.currentTarget).attr('data-sort'); + this.queryModel.set('sort', sortType); + } + }, + + initialize: function(options) { + this.queryModel = options.queryModel; + this.listenTo(this.queryModel, 'change:sort', this.updateCurrentSort); + }, + + render: function() { + this.$el.html(this.template({ + i18n: i18n, + sortTypes: QueryModel.Sort + })); + + this.$currentSort = this.$('.current-search-sort'); + this.updateCurrentSort(); + }, + + updateCurrentSort: function() { + if (this.$currentSort) { + this.$currentSort.text(i18n['search.resultsSort.' + this.queryModel.get('sort')]); + } + } + }); + +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/pages.js b/src/main/webapp/static/js/find/app/pages.js index 2e3dcabc38..6faa6df051 100644 --- a/src/main/webapp/static/js/find/app/pages.js +++ b/src/main/webapp/static/js/find/app/pages.js @@ -9,23 +9,16 @@ define([ 'find/app/page/find-settings-page', 'i18n!find/nls/bundle' ], function(FindPages, FindSearch, SettingsPage) { - return FindPages.extend({ initializePages: function() { this.pages = [ { - constructor: FindSearch - , pageName: 'search' - , classes: '' - }, { constructor: SettingsPage , pageName: 'settings' , classes: 'hide-from-non-useradmin' } ]; } - }); - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/router.js b/src/main/webapp/static/js/find/app/router.js index 7caed86f11..70275ac16f 100644 --- a/src/main/webapp/static/js/find/app/router.js +++ b/src/main/webapp/static/js/find/app/router.js @@ -22,9 +22,7 @@ define([ search: function() { this.trigger('route:find', 'search'); } - }); return new Router(); - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/util/collapsible.js b/src/main/webapp/static/js/find/app/util/collapsible.js new file mode 100644 index 0000000000..213bf50f03 --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/collapsible.js @@ -0,0 +1,49 @@ +define([ + 'backbone', + 'text!find/templates/app/util/collapsible.html' +], function(Backbone, collapsibleTemplate) { + + return Backbone.View.extend({ + template: _.template(collapsibleTemplate, {variable: 'data'}), + + events: { + 'show.bs.collapse .collapsible-header': function() { + this.collapsed = false; + this.updateHeaderState(); + }, + 'hide.bs.collapse .collapsible-header': function() { + this.collapsed = true; + this.updateHeaderState(); + } + }, + + initialize: function(options) { + this.view = options.view; + this.collapsed = options.collapsed || false; + this.title = options.title; + }, + + render: function() { + this.$el.html(this.template({ + contentState: this.collapsed ? '' : 'in', + title: this.title + })); + + this.$header = this.$('.collapsible-header'); + this.updateHeaderState(); + + this.view.render(); + this.$('.collapse').append(this.view.$el); + }, + + remove: function() { + this.view.remove(); + Backbone.View.prototype.remove.call(this); + }, + + updateHeaderState: function() { + // The "collapsed" class controls the icons with class "rotating-chevron" + this.$header.toggleClass('collapsed', this.collapsed); + } + }); +}); diff --git a/src/main/webapp/static/js/find/app/util/confirm-view.js b/src/main/webapp/static/js/find/app/util/confirm-view.js new file mode 100644 index 0000000000..8a79a0e335 --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/confirm-view.js @@ -0,0 +1,11 @@ +define([ + 'js-whatever/js/confirm-view', + 'text!find/templates/app/page/settings/confirm-modal.html' +], function(Confirm, confirmTemplate) { + + return Confirm.extend({ + className: 'modal fade', + template: _.template(confirmTemplate) + }); + +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/util/document-mime-types.js b/src/main/webapp/static/js/find/app/util/document-mime-types.js new file mode 100644 index 0000000000..d1b9b028f3 --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/document-mime-types.js @@ -0,0 +1,106 @@ +define( + function() { + return [ + { + typeRegex: [ + function() { + return /text\/html/; + } + ], + className: 'html' + }, + { + typeRegex: [ + function() { + return /text\/.*/; + } + ], + className: 'text' + }, + { + typeRegex: [ + function() { + return /video\/.*/; + } + ], + className: 'video' + }, + { + typeRegex: [ + function() { + return /image\/.*/; + } + ], + className: 'image' + }, + { + typeRegex: [ + function() { + return /audio\/.*/; + } + ], + className: 'audio' + }, + { + typeRegex: [ + function() { + return /message\/rtc822/; + } + ], + className: 'email' + }, + { + typeRegex: [ + function() { + return /application\/pdf/; + } + ], + className: 'pdf' + }, + { + typeRegex: [ + function() { + return /application\/x-ms-powerpoint\d{2}/; + } + ], + className: 'powerpoint' + }, + { + typeRegex: [ + function() { + return /application\/x-ms-word\d{2}/; + } + ], + className: 'word' + }, + { + typeRegex: [ + function() { + return /application\/x-ms-excel\d{2}/; + } + ], + className: 'excel' + }, + { + typeRegex: [ + // TODO figure out what this should be + ], + className: 'presentation' + }, + { + typeRegex: [ + // TODO figure out what this should be + ], + className: 'spreadsheet' + }, + { + typeRegex: [ + function() { + return /.*/; + } + ], + className: 'general' + } + ]; + } +); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/util/logout.js b/src/main/webapp/static/js/find/app/util/logout.js new file mode 100644 index 0000000000..fbcfdebc51 --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/logout.js @@ -0,0 +1,14 @@ +define([ + 'jquery' +], function($) { + + // Logout is a POST request. This cannot be done with an href, so append a magic form to the body and submit it. + return function(url) { + var $form = $(_.template('
')({ + url: url + })); + + $('body').append($form); + $form.submit(); + }; +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/util/popover.js b/src/main/webapp/static/js/find/app/util/popover.js new file mode 100644 index 0000000000..7b8bedc499 --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/popover.js @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'underscore', + 'jquery', + 'i18n!find/nls/bundle', + 'text!find/templates/app/page/loading-spinner.html', + 'bootstrap' +], function(_, $, i18n, loadingTemplate) { + + var smallLoadingTemplate = _.template(loadingTemplate)({i18n: i18n, large: false}); + var initialContent = '
' + smallLoadingTemplate + '
'; + + /** + * Add popovers to the given element(s). When the popover is inserted into the DOM, the callback will be called with + * the popover content element and the trigger element. A load indicator will be displayed in the content element + * until it is overwritten by the callback. + * @param {Jquery} $el + * @param {Function} callback + */ + return function($el, callback) { + $el.popover({ + content: initialContent, + html: true, + placement: 'bottom', + trigger: 'hover' + }).on('inserted.bs.popover', function(e) { + var $target = $(e.currentTarget); + + // Don't pass the bootstrap .popover-content element to the caller since this element can be preserved between + // popover openings. The .popover-content-inner element will be destroyed each time, so we don't have to track + // the state ourselves. + callback($target.siblings('.popover').find('.popover-content-inner'), $target); + }); + }; + +}); diff --git a/src/main/webapp/static/js/find/app/util/test-browser.js b/src/main/webapp/static/js/find/app/util/test-browser.js index 64aa9d6c52..910016ac47 100644 --- a/src/main/webapp/static/js/find/app/util/test-browser.js +++ b/src/main/webapp/static/js/find/app/util/test-browser.js @@ -24,4 +24,4 @@ define([ }); } } -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/app/util/view-server-client.js b/src/main/webapp/static/js/find/app/util/view-server-client.js new file mode 100644 index 0000000000..890654bede --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/view-server-client.js @@ -0,0 +1,19 @@ +define([ + +], function () { + + return { + getHref: function(reference, index) { + if (index) { + return '../api/public/view/viewDocument?' + $.param({ + indexes: index.id, + reference: reference + }); + } else { + return '../api/public/view/viewStaticContentPromotion?' + $.param({ + reference: reference + }); + } + } + } +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/util/view-state-selector.js b/src/main/webapp/static/js/find/app/util/view-state-selector.js new file mode 100644 index 0000000000..1d52a8f47c --- /dev/null +++ b/src/main/webapp/static/js/find/app/util/view-state-selector.js @@ -0,0 +1,17 @@ +define([ + 'underscore' +], function(_) { + return function(viewStates) { + return function(showStates) { + // Hide all the states + _.each(viewStates, function (value, key) { + value.addClass('hide'); + }, this); + + // Show the states asked for + _.each(showStates, function (stateKey) { + viewStates[stateKey].removeClass('hide'); + }, this) + } + } +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/find/app/vent.js b/src/main/webapp/static/js/find/app/vent.js index 55b2235a33..50093153d9 100644 --- a/src/main/webapp/static/js/find/app/vent.js +++ b/src/main/webapp/static/js/find/app/vent.js @@ -7,7 +7,5 @@ define([ 'js-whatever/js/vent-constructor', 'find/app/router' ], function(Vent, router) { - return new Vent(router); - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/config/app.js b/src/main/webapp/static/js/find/config/app.js index 64d435003f..7d03e41493 100644 --- a/src/main/webapp/static/js/find/config/app.js +++ b/src/main/webapp/static/js/find/config/app.js @@ -11,7 +11,6 @@ define([ 'text!find/templates/config/config.html', 'underscore' ], function(SettingsPage, EmptyNavbar, testBrowser, i18n, template, _) { - return function () { jQuery.ajaxSetup({ cache: false }); @@ -49,4 +48,4 @@ define([ testBrowser(); } -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/lib/backbone/backbone-extensions.js b/src/main/webapp/static/js/find/lib/backbone/backbone-extensions.js index b045113f5c..7762673841 100644 --- a/src/main/webapp/static/js/find/lib/backbone/backbone-extensions.js +++ b/src/main/webapp/static/js/find/lib/backbone/backbone-extensions.js @@ -14,7 +14,10 @@ define([ var error = options.error; options.error = function (jqXHR, status, message) { - if (jqXHR.status === 403) { + if (jqXHR.status === 401) { + window.location = "../sso" + } + else if (jqXHR.status === 403) { // refresh the page - the filters should then redirect to the login screen window.location.reload(); } @@ -27,4 +30,4 @@ define([ }); return Backbone; -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/nls/en-gb/bundle.js b/src/main/webapp/static/js/find/nls/en-gb/bundle.js index 82380ae0b1..40ffc87d77 100644 --- a/src/main/webapp/static/js/find/nls/en-gb/bundle.js +++ b/src/main/webapp/static/js/find/nls/en-gb/bundle.js @@ -8,4 +8,4 @@ define([ return substitution({ // en-GB translation goes here }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/nls/root/bundle.js b/src/main/webapp/static/js/find/nls/root/bundle.js index 5743c7e345..a0b8f1a7f8 100644 --- a/src/main/webapp/static/js/find/nls/root/bundle.js +++ b/src/main/webapp/static/js/find/nls/root/bundle.js @@ -7,6 +7,7 @@ define([ ], function(substitution) { return substitution({ 'app.cancel': 'Cancel', + 'app.loading': 'Loading...', 'app.logout': 'Logout', 'app.name': "Find", 'app.ok': 'OK', @@ -14,6 +15,11 @@ define([ 'app.settings': 'Settings', 'app.user': 'User', 'app.users': 'Users', + 'app.from': 'From', + 'app.until': 'Until', + 'datepicker.language': 'en', + 'hod.error.INVALID_QUERY_TEXT': 'All terms were invalid, through being stopwords, too short, or incorrectly formatted', + 'hod.error.NO_IGNORE_SPECIALS': 'Invalid use of special tokens', 'footer.click-to-hide': 'Collapse footer', 'footer.click-to-show': 'Show more\u2026', 'login.defaultLogin': 'Details for the default login can be found in your config.json file', @@ -33,7 +39,44 @@ define([ 'placeholder.hostname': 'hostname', 'placeholder.ip': 'IP', 'placeholder.port': 'port', + 'search.selected': 'Selected', 'search.noResults': 'No results found', + 'search.indexes': 'Indexes', + 'search.indexes.all': 'All', + 'search.indexes.publicIndexes': 'Public Indexes', + 'search.indexes.privateIndexes': 'Private Indexes', + 'search.indexes.empty': 'Waiting for Indexes...', + 'search.dates': 'Dates', + 'search.dates.timeInterval.custom': 'Custom', + 'search.dates.timeInterval.week': 'Last Week', + 'search.dates.timeInterval.month': 'Last Month', + 'search.dates.timeInterval.year': 'Last Year', + 'search.document.author': 'Author', + 'search.document.categories': 'Categories', + 'search.document.date': 'Date', + 'search.document.dateCreated': 'Date Created', + 'search.document.dateModified': 'Date Modified', + 'search.document.domain': 'Domain', + 'search.document.index': 'Index', + 'search.document.openInNewTab': 'Open in New Tab', + 'search.document.reference': 'Reference', + 'search.document.staticContent': 'Static Content', + 'search.document.weight': 'Weight', + 'search.relatedConcepts': 'Related Concepts', + 'search.relatedConcepts.topResults.error': 'An error occurred fetching top results', + 'search.relatedConcepts.topResults.none': 'No top results found', + 'search.relatedConcepts.notLoading': 'The list of indexes has not yet been retrieved', + 'search.relatedConcepts.none': 'There are no related concepts', + 'search.resultsSort': 'Sort', + 'search.resultsSort.date': 'by date', + 'search.resultsSort.relevance': 'by relevance', + 'search.similarDocuments': 'Similar documents', + 'search.similarDocuments.error': 'An error occurred fetching similar documents', + 'search.similarDocuments.none': 'No similar documents found', + 'search.error.results' : 'An error occurred retrieving results', + 'search.error.promotions' : 'An error occurred retrieving promotions', + 'search.error.relatedConcepts' : 'An error occurred retrieving related concepts', + 'search.error.parametric' : 'An error occurred retrieving additional filters', 'settings.cancel': 'Cancel', 'settings.cancel.message': 'All unsaved changes will be lost.', 'settings.cancel.title': 'Revert settings?', @@ -50,6 +93,9 @@ define([ 'settings.database': 'Database', 'settings.databaseCheckbox': 'Use username for database name', 'settings.description': "This page is for editing the Find config file. The config file location is stored in the Java system property {0}. The current location is {1}.", + 'settings.iod.apiKey': 'API key', + 'settings.iod.application': 'Application', + 'settings.iod.domain': 'Domain', 'settings.locale.title': 'Locale', 'settings.locale.default': 'Default locale', 'settings.password': 'Password', diff --git a/src/main/webapp/static/js/find/public/app.js b/src/main/webapp/static/js/find/public/app.js index 1c3c1bb7c4..85195ef8b8 100644 --- a/src/main/webapp/static/js/find/public/app.js +++ b/src/main/webapp/static/js/find/public/app.js @@ -5,22 +5,33 @@ define([ 'find/app/base-app', + 'find/app/util/logout', + 'find/app/configuration', 'find/public/pages', 'text!find/templates/app/app.html' -], function(BaseApp, Pages, template) { - +], function(BaseApp, logout, configuration, Pages, template) { return BaseApp.extend({ template: _.template(template), defaultRoute: 'find/search', + events: { + 'click .navigation-logout': function() { + logout('../logout'); + } + }, + initialize: function() { this.pages = new Pages(); BaseApp.prototype.initialize.apply(this, arguments); - } + }, + getTemplateParameters: function() { + return { + username: configuration().username + }; + } }); - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/public/pages.js b/src/main/webapp/static/js/find/public/pages.js index 28b31be622..3befcc915e 100644 --- a/src/main/webapp/static/js/find/public/pages.js +++ b/src/main/webapp/static/js/find/public/pages.js @@ -8,7 +8,6 @@ define([ 'find/app/page/find-search', 'i18n!find/nls/bundle' ], function(FindPages, FindSearch) { - return FindPages.extend({ initializePages: function() { @@ -19,7 +18,5 @@ define([ } ]; } - }); - -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/find/templates/app/app.html b/src/main/webapp/static/js/find/templates/app/app.html index 4e41fdc23a..eda681bc56 100644 --- a/src/main/webapp/static/js/find/templates/app/app.html +++ b/src/main/webapp/static/js/find/templates/app/app.html @@ -1,16 +1,30 @@
+
+
-
-
+
+
+ +
-
\ No newline at end of file +
diff --git a/src/main/webapp/static/js/find/templates/app/navigation.html b/src/main/webapp/static/js/find/templates/app/navigation.html index 909d13bbd7..c448a635b1 100644 --- a/src/main/webapp/static/js/find/templates/app/navigation.html +++ b/src/main/webapp/static/js/find/templates/app/navigation.html @@ -1,17 +1,15 @@ \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/colorbox-controls.html b/src/main/webapp/static/js/find/templates/app/page/colorbox-controls.html index 808070e351..d646df84be 100644 --- a/src/main/webapp/static/js/find/templates/app/page/colorbox-controls.html +++ b/src/main/webapp/static/js/find/templates/app/page/colorbox-controls.html @@ -1,2 +1,2 @@ - \ No newline at end of file + diff --git a/src/main/webapp/static/js/find/templates/app/page/date/custom-datepicker.html b/src/main/webapp/static/js/find/templates/app/page/date/custom-datepicker.html new file mode 100644 index 0000000000..8d087279d9 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/date/custom-datepicker.html @@ -0,0 +1,28 @@ +
+
+
+
<%-i18n['app.from']%>
+
+
+ + + + +
+
+
+ +
+
<%-i18n['app.until']%>
+
+
+ + + + +
+
+
+ +
+
\ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/date/date-item.html b/src/main/webapp/static/js/find/templates/app/page/date/date-item.html new file mode 100644 index 0000000000..7f7a6b0d37 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/date/date-item.html @@ -0,0 +1,2 @@ + +<%-data.label%> diff --git a/src/main/webapp/static/js/find/templates/app/page/date/dates-filter-view.html b/src/main/webapp/static/js/find/templates/app/page/date/dates-filter-view.html new file mode 100644 index 0000000000..92cbbf4292 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/date/dates-filter-view.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/filter-display/filter-display-item.html b/src/main/webapp/static/js/find/templates/app/page/filter-display/filter-display-item.html new file mode 100644 index 0000000000..2fc676e38b --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/filter-display/filter-display-item.html @@ -0,0 +1,2 @@ +<%- data.text %> + \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/filter-display/filter-display.html b/src/main/webapp/static/js/find/templates/app/page/filter-display/filter-display.html new file mode 100644 index 0000000000..750556af30 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/filter-display/filter-display.html @@ -0,0 +1 @@ +

<%-i18n['search.selected']%>

\ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/find-search.html b/src/main/webapp/static/js/find/templates/app/page/find-search.html index 0b3cc0db1d..045d30dd97 100644 --- a/src/main/webapp/static/js/find/templates/app/page/find-search.html +++ b/src/main/webapp/static/js/find/templates/app/page/find-search.html @@ -1,18 +1,7 @@ -
- -
- -
- -
-
+
+ -
\ No newline at end of file +
+
+
\ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/index-popover-contents.html b/src/main/webapp/static/js/find/templates/app/page/index-popover-contents.html deleted file mode 100644 index 33cb9de4ee..0000000000 --- a/src/main/webapp/static/js/find/templates/app/page/index-popover-contents.html +++ /dev/null @@ -1,5 +0,0 @@ -
  • - -
  • \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/index-popover.html b/src/main/webapp/static/js/find/templates/app/page/index-popover.html deleted file mode 100644 index b62b81b100..0000000000 --- a/src/main/webapp/static/js/find/templates/app/page/index-popover.html +++ /dev/null @@ -1,2 +0,0 @@ -
    Public Indexes
    -
      \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/indexes/index-item.html b/src/main/webapp/static/js/find/templates/app/page/indexes/index-item.html new file mode 100644 index 0000000000..9882c3ccad --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/indexes/index-item.html @@ -0,0 +1,4 @@ + + + <%-data.name%> + \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/indexes/index-list.html b/src/main/webapp/static/js/find/templates/app/page/indexes/index-list.html new file mode 100644 index 0000000000..0688b7572d --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/indexes/index-list.html @@ -0,0 +1,9 @@ +<% var categoryId = _.uniqueId('database-category-'); %> +
        +
      • + + <%-data.node.displayName%> + +
        +
      • +
      \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/indexes/indexes-view.html b/src/main/webapp/static/js/find/templates/app/page/indexes/indexes-view.html new file mode 100644 index 0000000000..284bd504b5 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/indexes/indexes-view.html @@ -0,0 +1,2 @@ +
      +
      \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/input-view.html b/src/main/webapp/static/js/find/templates/app/page/input-view.html new file mode 100644 index 0000000000..cfde8c5be3 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/input-view.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/loading-spinner.html b/src/main/webapp/static/js/find/templates/app/page/loading-spinner.html index 52106e87e4..bf7b579a20 100644 --- a/src/main/webapp/static/js/find/templates/app/page/loading-spinner.html +++ b/src/main/webapp/static/js/find/templates/app/page/loading-spinner.html @@ -1,3 +1,3 @@ -
      - Loading... +
      + <%-i18n['app.loading']%>
      diff --git a/src/main/webapp/static/js/find/templates/app/page/parametric/parametric-value-view.html b/src/main/webapp/static/js/find/templates/app/page/parametric/parametric-value-view.html new file mode 100644 index 0000000000..6db270ad81 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/parametric/parametric-value-view.html @@ -0,0 +1,2 @@ + + diff --git a/src/main/webapp/static/js/find/templates/app/page/parametric/parametric-view.html b/src/main/webapp/static/js/find/templates/app/page/parametric/parametric-view.html new file mode 100644 index 0000000000..0b59d37e41 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/parametric/parametric-view.html @@ -0,0 +1,5 @@ +
      +
      + <%-i18n['app.loading']%> +
      +

      <%-i18n['search.error.parametric']%>

      diff --git a/src/main/webapp/static/js/find/templates/app/page/popover-message.html b/src/main/webapp/static/js/find/templates/app/page/popover-message.html new file mode 100644 index 0000000000..3ce5106d75 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/popover-message.html @@ -0,0 +1 @@ +

      <%-message%>

      diff --git a/src/main/webapp/static/js/find/templates/app/page/related-concepts/related-concept-list-item.html b/src/main/webapp/static/js/find/templates/app/page/related-concepts/related-concept-list-item.html new file mode 100644 index 0000000000..8a4d963194 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/related-concepts/related-concept-list-item.html @@ -0,0 +1,10 @@ +<% var headText = _.head(entities).get('text') %> +<%- headText %> + + + <% _.each(_.tail(entities), function(entity) { %> + + + + <% }); %> +
      <%- entity.get('text') %>
      diff --git a/src/main/webapp/static/js/find/templates/app/page/related-concepts/related-concepts-view.html b/src/main/webapp/static/js/find/templates/app/page/related-concepts/related-concepts-view.html new file mode 100644 index 0000000000..43bc541e5b --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/related-concepts/related-concepts-view.html @@ -0,0 +1,5 @@ + +
      + + +
      <%- i18n['search.relatedConcepts.notLoading'] %>
      \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/results-container.html b/src/main/webapp/static/js/find/templates/app/page/results-container.html index c6e99c5c08..b6270b4be9 100644 --- a/src/main/webapp/static/js/find/templates/app/page/results-container.html +++ b/src/main/webapp/static/js/find/templates/app/page/results-container.html @@ -1,11 +1,25 @@ - diff --git a/src/main/webapp/static/js/find/templates/app/page/results-popover.html b/src/main/webapp/static/js/find/templates/app/page/results-popover.html new file mode 100644 index 0000000000..0e0f3a2a3a --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/results-popover.html @@ -0,0 +1,8 @@ +
        + <% collection.map(function(model) { %> +
      • +
        <%- model.get('title') %>
        +

        <%- model.get('summary').trim().substring(0, 100) + '...' %>

        +
      • + <% }); %> +
      diff --git a/src/main/webapp/static/js/find/templates/app/page/results/entity-label.html b/src/main/webapp/static/js/find/templates/app/page/results/entity-label.html new file mode 100644 index 0000000000..971e68b9c5 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/results/entity-label.html @@ -0,0 +1,5 @@ + + + <%- replacement %> + + \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/results/results-view.html b/src/main/webapp/static/js/find/templates/app/page/results/results-view.html new file mode 100644 index 0000000000..79104d67fa --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/results/results-view.html @@ -0,0 +1,4 @@ +
      +
      +
      +
      diff --git a/src/main/webapp/static/js/find/templates/app/page/service-view.html b/src/main/webapp/static/js/find/templates/app/page/service-view.html new file mode 100644 index 0000000000..21919fdf8c --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/service-view.html @@ -0,0 +1,15 @@ +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/settings/confirm-modal.html b/src/main/webapp/static/js/find/templates/app/page/settings/confirm-modal.html new file mode 100644 index 0000000000..d611102159 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/settings/confirm-modal.html @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/settings/indexes-list.html b/src/main/webapp/static/js/find/templates/app/page/settings/indexes-list.html index bbea438e6a..56b9100612 100644 --- a/src/main/webapp/static/js/find/templates/app/page/settings/indexes-list.html +++ b/src/main/webapp/static/js/find/templates/app/page/settings/indexes-list.html @@ -1,12 +1,10 @@ -
      - <% if(indexes) { %> - -
      - <% _.each(indexes, function(index) { %> - - <% }) %> -
      - <% } %> -
      \ No newline at end of file +
      + <% if(indexes) { %> + + <% _.each(indexes, function(index) { %> + + <% }) %> + <% } %> +
      diff --git a/src/main/webapp/static/js/find/templates/app/page/settings/iod-widget.html b/src/main/webapp/static/js/find/templates/app/page/settings/iod-widget.html index 2d7ec7d08e..cd982f7015 100644 --- a/src/main/webapp/static/js/find/templates/app/page/settings/iod-widget.html +++ b/src/main/webapp/static/js/find/templates/app/page/settings/iod-widget.html @@ -1,4 +1,12 @@ -
      - - -
      \ No newline at end of file +
      + + +
      +
      + + +
      +
      + + +
      diff --git a/src/main/webapp/static/js/find/templates/app/page/settings/server-widget.html b/src/main/webapp/static/js/find/templates/app/page/settings/server-widget.html new file mode 100644 index 0000000000..3f542320b8 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/settings/server-widget.html @@ -0,0 +1,3 @@ +
      + +
      diff --git a/src/main/webapp/static/js/find/templates/app/page/settings/validate-on-save-modal.html b/src/main/webapp/static/js/find/templates/app/page/settings/validate-on-save-modal.html new file mode 100644 index 0000000000..d997a1ae98 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/settings/validate-on-save-modal.html @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/sort/sort-view.html b/src/main/webapp/static/js/find/templates/app/page/sort/sort-view.html new file mode 100644 index 0000000000..f615efef3d --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/sort/sort-view.html @@ -0,0 +1,14 @@ +
      + <%-i18n['search.resultsSort']%> + + <%- i18n['search.resultsSort.relevance'] %> + + + +
      \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/suggestions-container.html b/src/main/webapp/static/js/find/templates/app/page/suggestions-container.html deleted file mode 100644 index 653a37b864..0000000000 --- a/src/main/webapp/static/js/find/templates/app/page/suggestions-container.html +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/src/main/webapp/static/js/find/templates/app/page/top-results-popover-contents.html b/src/main/webapp/static/js/find/templates/app/page/top-results-popover-contents.html deleted file mode 100644 index 13d245cad1..0000000000 --- a/src/main/webapp/static/js/find/templates/app/page/top-results-popover-contents.html +++ /dev/null @@ -1,4 +0,0 @@ -
    • -
      <%- title %>
      -

      <%- summary %>

      -
    • diff --git a/src/main/webapp/static/js/find/templates/app/page/view/media-player.html b/src/main/webapp/static/js/find/templates/app/page/view/media-player.html new file mode 100644 index 0000000000..f11c6ad7a6 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/view/media-player.html @@ -0,0 +1,7 @@ +
      + <<%= media %> src="<%- url %>#t=<%- offset %>" preload="auto" controls> +

      + Your browser does not support HTML5 <%= media %>. To view this content, please use a different browser. +

      + > +
      diff --git a/src/main/webapp/static/js/find/templates/app/page/view/view-document.html b/src/main/webapp/static/js/find/templates/app/page/view/view-document.html new file mode 100644 index 0000000000..8a4e75a539 --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/page/view/view-document.html @@ -0,0 +1,61 @@ +<% + var index = model.get('index'); +%> + +
      +
      +
      +

      + <%-i18n["app.loading"]%> +

      + +
      +
      + <%-i18n['search.document.openInNewTab']%> + + + <% if (index) { %> + <% if (index.get('domain')) { %> + + + + + <% } %> + + + + + <% } %> + + <% _.each(fields, function(field) { %> + <% if(model.has(field)) { %> + + + <% if(_.isArray(model.get(field))) { %> + + <% } else { %> + + <% } %> + + <% } %> + <% }); %> + + <% _.each(dateFields, function(field) { %> + <% if(model.has(field)) { %> + + + + + <% } %> + <% });%> + +
      <%-i18n['search.document.domain']%><%-index.get('domain')%>
      <%-i18n['search.document.index']%><%-index.get('name')%>
      <%-i18n['search.document.' + field]%> +
        + <% _.each(model.get(field), function(value) { %> +
      • <%-value%>
      • + <% }); %> +
      +
      <%-model.get(field)%>
      <%-i18n['search.document.' + field]%><%-model.get(field).format('LLLL')%>
      +
      +
      +
      diff --git a/src/main/webapp/static/js/find/templates/app/private-app.html b/src/main/webapp/static/js/find/templates/app/private-app.html index 948969e011..05d582eed7 100644 --- a/src/main/webapp/static/js/find/templates/app/private-app.html +++ b/src/main/webapp/static/js/find/templates/app/private-app.html @@ -1,8 +1,8 @@
      -
      -
      +
      +
      -
      \ No newline at end of file +
      diff --git a/src/main/webapp/static/js/find/templates/app/util/collapsible.html b/src/main/webapp/static/js/find/templates/app/util/collapsible.html new file mode 100644 index 0000000000..5c5faba1bd --- /dev/null +++ b/src/main/webapp/static/js/find/templates/app/util/collapsible.html @@ -0,0 +1,5 @@ +<% var collapseId = _.uniqueId('collapse-'); %> +
      +

      <%- data.title %>

      +
      +
      diff --git a/src/main/webapp/static/js/find/templates/config/config.html b/src/main/webapp/static/js/find/templates/config/config.html index 9f4ac2860c..54cbb62fba 100644 --- a/src/main/webapp/static/js/find/templates/config/config.html +++ b/src/main/webapp/static/js/find/templates/config/config.html @@ -1,8 +1,8 @@
      -
      -
      +
      +
      diff --git a/src/main/webapp/static/js/login.js b/src/main/webapp/static/js/login.js index 505b4c1e90..7a08cd1f83 100644 --- a/src/main/webapp/static/js/login.js +++ b/src/main/webapp/static/js/login.js @@ -31,4 +31,4 @@ require(['require-config'], function() { } }); }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/main.js b/src/main/webapp/static/js/main.js index 5758af3110..2c81318abb 100644 --- a/src/main/webapp/static/js/main.js +++ b/src/main/webapp/static/js/main.js @@ -7,4 +7,4 @@ require(['require-config'], function() { require(['find/app/app'], function(App) { new App(); }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/public.js b/src/main/webapp/static/js/public.js index c21a7187d3..5ba8a184ec 100644 --- a/src/main/webapp/static/js/public.js +++ b/src/main/webapp/static/js/public.js @@ -7,4 +7,4 @@ require(['require-config'], function() { require(['find/public/app'], function(App) { new App(); }); -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/js/require-config.js b/src/main/webapp/static/js/require-config.js index f888c9b606..59486975a2 100644 --- a/src/main/webapp/static/js/require-config.js +++ b/src/main/webapp/static/js/require-config.js @@ -6,17 +6,24 @@ require.config({ paths: { backbone: 'find/lib/backbone/backbone-extensions', - 'backbone-base': '../lib/backbone/backbone', - bootstrap: '../lib/hp-autonomy-bootstrap-2/bootstrap/js/bootstrap', - colorbox: '../lib/colorbox/jquery.colorbox', - i18n: '../lib/requirejs-i18n/i18n', - jquery: '../lib/jquery/jquery', - 'js-whatever': '../lib/hp-autonomy-js-whatever/src', - json2: '../lib/json/json2', - 'login-page': '../lib/hp-autonomy-login-page/src', - settings: '../lib/hp-autonomy-settings-page/src', - text: '../lib/requirejs-text/text', - underscore: '../lib/underscore/underscore' + 'backbone-base': '../bower_components/backbone/backbone', + bootstrap: '../lib/bootstrap/js/bootstrap', + colorbox: '../bower_components/colorbox/jquery.colorbox', + 'bootstrap-datetimepicker': '../bower_components/eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker', + 'databases-view': '../bower_components/hp-autonomy-js-databases-view/src', + i18n: '../bower_components/requirejs-i18n/i18n', + iCheck: '../bower_components/icheck/icheck', + 'peg': '../bower_components/pegjs/peg-0.8.0', + 'fieldtext': '../bower_components/hp-autonomy-fieldtext-js/src', + 'parametric-refinement': '../bower_components/hp-autonomy-js-parametric-refinement/src', + jquery: '../bower_components/jquery/jquery', + 'js-whatever': '../bower_components/hp-autonomy-js-whatever/src', + json2: '../bower_components/json/json2', + 'login-page': '../bower_components/hp-autonomy-login-page/src', + moment: '../bower_components/moment/moment', + settings: '../bower_components/hp-autonomy-settings-page/src', + text: '../bower_components/requirejs-text/text', + underscore: '../bower_components/underscore/underscore' }, shim: { 'backbone-base': { @@ -24,9 +31,14 @@ require.config({ exports: 'Backbone' }, bootstrap: ['jquery'], + 'bootstrap-datetimepicker': ['jquery'], colorbox: ['jquery'], + iCheck: ['jquery'], + peg: { + exports: 'PEG' + }, underscore: { exports: '_' } } -}); \ No newline at end of file +}); diff --git a/src/main/webapp/static/lib/bootstrap/README.txt b/src/main/webapp/static/lib/bootstrap/README.txt new file mode 100644 index 0000000000..775a5253c5 --- /dev/null +++ b/src/main/webapp/static/lib/bootstrap/README.txt @@ -0,0 +1,2 @@ +Customised bootstrap 3 generated using: +http://getbootstrap.com/customize/ diff --git a/src/main/webapp/static/lib/bootstrap/css/bootstrap-theme.css b/src/main/webapp/static/lib/bootstrap/css/bootstrap-theme.css new file mode 100644 index 0000000000..824d2f40c4 --- /dev/null +++ b/src/main/webapp/static/lib/bootstrap/css/bootstrap-theme.css @@ -0,0 +1,596 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! + * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=c57f77e06a6bfbf29e21) + * Config saved to config.json and https://gist.github.com/c57f77e06a6bfbf29e21 + */ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); +} +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-default.disabled, +.btn-primary.disabled, +.btn-success.disabled, +.btn-info.disabled, +.btn-warning.disabled, +.btn-danger.disabled, +.btn-default[disabled], +.btn-primary[disabled], +.btn-success[disabled], +.btn-info[disabled], +.btn-warning[disabled], +.btn-danger[disabled], +fieldset[disabled] .btn-default, +fieldset[disabled] .btn-primary, +fieldset[disabled] .btn-success, +fieldset[disabled] .btn-info, +fieldset[disabled] .btn-warning, +fieldset[disabled] .btn-danger { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-default .badge, +.btn-primary .badge, +.btn-success .badge, +.btn-info .badge, +.btn-warning .badge, +.btn-danger .badge { + text-shadow: none; +} +.btn:active, +.btn.active { + background-image: none; +} +.btn-default { + background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); + background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#e0e0e0)); + background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #dbdbdb; + text-shadow: 0 1px 0 #fff; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; +} +.btn-default:active, +.btn-default.active { + background-color: #e0e0e0; + border-color: #dbdbdb; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #e0e0e0; + background-image: none; +} +.btn-primary { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); + background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #245580; +} +.btn-primary:hover, +.btn-primary:focus { + background-color: #265a88; + background-position: 0 -15px; +} +.btn-primary:active, +.btn-primary.active { + background-color: #265a88; + border-color: #245580; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #265a88; + background-image: none; +} +.btn-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #3e8f3e; +} +.btn-success:hover, +.btn-success:focus { + background-color: #419641; + background-position: 0 -15px; +} +.btn-success:active, +.btn-success.active { + background-color: #419641; + border-color: #3e8f3e; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #419641; + background-image: none; +} +.btn-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #28a4c9; +} +.btn-info:hover, +.btn-info:focus { + background-color: #2aabd2; + background-position: 0 -15px; +} +.btn-info:active, +.btn-info.active { + background-color: #2aabd2; + border-color: #28a4c9; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #2aabd2; + background-image: none; +} +.btn-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #e38d13; +} +.btn-warning:hover, +.btn-warning:focus { + background-color: #eb9316; + background-position: 0 -15px; +} +.btn-warning:active, +.btn-warning.active { + background-color: #eb9316; + border-color: #e38d13; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #eb9316; + background-image: none; +} +.btn-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #b92c28; +} +.btn-danger:hover, +.btn-danger:focus { + background-color: #c12e2a; + background-position: 0 -15px; +} +.btn-danger:active, +.btn-danger.active { + background-color: #c12e2a; + border-color: #b92c28; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #c12e2a; + background-image: none; +} +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-color: #e8e8e8; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-color: #2e6da4; +} +.navbar-default { + background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); + background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8)); + background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); + background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); + background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222222)); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + border-radius: 4px; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); + background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); +} +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +@media (max-width: 767px) { + .navbar .navbar-nav .open .dropdown-menu > .active > a, + .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + } +} +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); +} +.alert-success { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + border-color: #b2dba1; +} +.alert-info { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); + border-color: #9acfea; +} +.alert-warning { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); + border-color: #f5e79e; +} +.alert-danger { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); + border-color: #dca7a7; +} +.progress { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); +} +.progress-bar { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); + background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); +} +.progress-bar-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); +} +.progress-bar-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); +} +.progress-bar-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); +} +.progress-bar-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); +} +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #286090; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); + border-color: #2b669a; +} +.list-group-item.active .badge, +.list-group-item.active:hover .badge, +.list-group-item.active:focus .badge { + text-shadow: none; +} +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} +.panel-default > .panel-heading { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); +} +.panel-primary > .panel-heading { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); +} +.panel-success > .panel-heading { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); +} +.panel-info > .panel-heading { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); +} +.panel-warning > .panel-heading { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); +} +.panel-danger > .panel-heading { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); +} +.well { + background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + border-color: #dcdcdc; + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); +} diff --git a/src/main/webapp/static/lib/bootstrap/css/bootstrap.css b/src/main/webapp/static/lib/bootstrap/css/bootstrap.css new file mode 100644 index 0000000000..8d8d0deee7 --- /dev/null +++ b/src/main/webapp/static/lib/bootstrap/css/bootstrap.css @@ -0,0 +1,5999 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! + * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=c57f77e06a6bfbf29e21) + * Config saved to config.json and https://gist.github.com/c57f77e06a6bfbf29e21 + */ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + text-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333333; + background-color: #ffffff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + background-color: #fcf8e3; + padding: .2em; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; + margin-left: -5px; +} +.list-inline > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eeeeee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; + text-align: right; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #ffffff; + background-color: #333333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #333333; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #dddddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #dddddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #dddddd; +} +.table .table { + background-color: #ffffff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #dddddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #dddddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + overflow-x: auto; + min-height: 0.01%; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #dddddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + padding: 0; + margin: 0; + border: 0; + min-width: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; + background-color: #ffffff; + background-image: none; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control::-moz-placeholder { + color: #999999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999999; +} +.form-control::-webkit-input-placeholder { + color: #999999; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eeeeee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; + min-height: 34px; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-left: 0; + padding-right: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + border-color: #3c763d; + background-color: #dff0d8; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + border-color: #8a6d3b; + background-color: #fcf8e3; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + border-color: #a94442; + background-color: #f2dede; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 7px; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + margin-bottom: 0; + padding-top: 7px; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 14.333333px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333333; + text-decoration: none; +} +.btn:active, +.btn.active { + outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333333; + background-color: #ffffff; + border-color: #cccccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #ffffff; + border-color: #cccccc; +} +.btn-default .badge { + color: #ffffff; + background-color: #333333; +} +.btn-primary { + color: #ffffff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #ffffff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #ffffff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #ffffff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #ffffff; +} +.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #ffffff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #ffffff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #ffffff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #ffffff; +} +.btn-info { + color: #ffffff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #ffffff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #ffffff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #ffffff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #ffffff; +} +.btn-warning { + color: #ffffff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #ffffff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #ffffff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #ffffff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #ffffff; +} +.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #ffffff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #ffffff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #ffffff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #ffffff; +} +.btn-link { + color: #337ab7; + font-weight: normal; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; + -webkit-transition-duration: 0.35s; + -o-transition-duration: 0.35s; + transition-duration: 0.35s; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + -webkit-background-clip: padding-box; + background-clip: padding-box; +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + text-decoration: none; + color: #262626; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #337ab7; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + left: auto; + right: 0; +} +.dropdown-menu-left { + left: 0; + right: auto; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; + content: ""; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + left: auto; + right: 0; + } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-bottom-left-radius: 4px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + float: none; + display: table-cell; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555555; + text-align: center; + background-color: #eeeeee; + border: 1px solid #cccccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.nav > li.disabled > a { + color: #777777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777777; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #dddddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; + cursor: default; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #dddddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #dddddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #ffffff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #dddddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #dddddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #ffffff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + overflow-x: visible; + padding-right: 15px; + padding-left: 15px; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-left: 0; + padding-right: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; + height: 50px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 8px; + margin-bottom: 8px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-left: 15px; + margin-right: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777777; +} +.navbar-default .navbar-nav > li > a { + color: #777777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #dddddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #dddddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + background-color: #e7e7e7; + color: #555555; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777777; +} +.navbar-default .navbar-link:hover { + color: #333333; +} +.navbar-default .btn-link { + color: #777777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #cccccc; +} +.navbar-inverse { + background-color: #222222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + background-color: #080808; + color: #ffffff; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #ffffff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #cccccc; +} +.breadcrumb > .active { + color: #777777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + line-height: 1.42857143; + text-decoration: none; + color: #337ab7; + background-color: #ffffff; + border: 1px solid #dddddd; + margin-left: -1px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 3; + color: #23527c; + background-color: #eeeeee; + border-color: #dddddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #ffffff; + background-color: #337ab7; + border-color: #337ab7; + cursor: default; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777777; + background-color: #ffffff; + border-color: #dddddd; + cursor: not-allowed; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + list-style: none; + text-align: center; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777777; + background-color: #ffffff; + cursor: not-allowed; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + color: #ffffff; + line-height: 1; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: #777777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #ffffff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eeeeee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-left: 60px; + padding-right: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: border 0.2s ease-in-out; + -o-transition: border 0.2s ease-in-out; + transition: border 0.2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-left: auto; + margin-right: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + background-color: #d9edf7; + border-color: #bce8f1; + color: #31708f; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + background-color: #f2dede; + border-color: #ebccd1; + color: #a94442; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + overflow: hidden; + height: 20px; + margin-bottom: 20px; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #ffffff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + zoom: 1; + overflow: hidden; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + margin-bottom: 20px; + padding-left: 0; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + text-decoration: none; + color: #555555; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + background-color: #eeeeee; + color: #777777; + cursor: not-allowed; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-left: 15px; + padding-right: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #dddddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + border: 0; + margin-bottom: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #dddddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} +.panel-default { + border-color: #dddddd; +} +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #dddddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #dddddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #dddddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #ffffff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + -webkit-background-clip: padding-box; + background-clip: padding-box; + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; + min-height: 16.42857143px; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-left: 5px; + margin-bottom: 0; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 12px; + opacity: 0; + filter: alpha(opacity=0); +} +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} +.tooltip.top { + margin-top: -3px; + padding: 5px 0; +} +.tooltip.right { + margin-left: 3px; + padding: 0 5px; +} +.tooltip.bottom { + margin-top: 3px; + padding: 5px 0; +} +.tooltip.left { + margin-left: -3px; + padding: 0 5px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + background-color: #000000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + right: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 14px; + background-color: #ffffff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + border-width: 10px; + content: ""; +} +.popover.top > .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + bottom: -11px; +} +.popover.top > .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #ffffff; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); +} +.popover.right > .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #ffffff; +} +.popover.bottom > .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.popover.bottom > .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #ffffff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left > .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #ffffff; + bottom: -10px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; +} +.carousel-inner > .item { + display: none; + position: relative; + -webkit-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform 0.6s ease-in-out; + -o-transition: -o-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + left: 0; + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + left: 0; + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + left: 0; + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 15%; + opacity: 0.5; + filter: alpha(opacity=50); + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} +.carousel-control.right { + left: auto; + right: 0; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} +.carousel-control:hover, +.carousel-control:focus { + outline: 0; + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + margin-top: -10px; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + line-height: 1; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid #ffffff; + border-radius: 10px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); +} +.carousel-indicators .active { + margin: 0; + width: 12px; + height: 12px; + background-color: #ffffff; +} +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + content: " "; + display: table; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} diff --git a/src/main/webapp/static/lib/bootstrap/js/bootstrap.js b/src/main/webapp/static/lib/bootstrap/js/bootstrap.js new file mode 100644 index 0000000000..9fc4f2ff16 --- /dev/null +++ b/src/main/webapp/static/lib/bootstrap/js/bootstrap.js @@ -0,0 +1,2366 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! + * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=c57f77e06a6bfbf29e21) + * Config saved to config.json and https://gist.github.com/c57f77e06a6bfbf29e21 + */ +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.5 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.5' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.5 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.5' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.5 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.5' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.5 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.5' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger('shown.bs.dropdown', relatedTarget) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.5 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.5' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.5 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.5' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.5 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.5' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.5 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.5' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.5 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.5' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.5 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.5' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.5 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.5' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.5 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); diff --git a/src/test/java/com/hp/autonomy/frontend/find/search/PrivateIndexTest.java b/src/test/java/com/hp/autonomy/frontend/find/search/PrivateIndexTest.java deleted file mode 100644 index eb1da86178..0000000000 --- a/src/test/java/com/hp/autonomy/frontend/find/search/PrivateIndexTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - */ - -package com.hp.autonomy.frontend.find.search; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -public class PrivateIndexTest { - - @Test - public void testPrivateIndexConvertsFromJson() throws IOException { - final ObjectMapper mapper = new ObjectMapper(); - - final PrivateIndex privateIndex = mapper.readValue(getClass().getResourceAsStream("/com/hp/autonomy/frontend/find/search/privateIndex.json"), PrivateIndex.class); - - assertThat(privateIndex.getFlavor(), is("standard")); - assertThat(privateIndex.getIndex(), is("myindex")); - assertThat(privateIndex.getType(), is("content")); - assertThat(privateIndex.getNumComponents(), is(1)); - assertThat(privateIndex.getDateCreated(), is("Mon Mar 10 2014 19:29:47 GMT+0000 (UTC)")); - assertThat(privateIndex.getDescription(), is(nullValue())); - assertThat(privateIndex.getSubType(), is(nullValue())); - } -} diff --git a/src/test/js/mock/find/placeholder.txt b/src/test/js/mock/find/placeholder.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/com/hp/autonomy/frontend/find/ApiKeyService.java b/src/test/js/mock/model/documents-collection.js similarity index 57% rename from src/main/java/com/hp/autonomy/frontend/find/ApiKeyService.java rename to src/test/js/mock/model/documents-collection.js index 36acea32c4..ff89d6f72d 100644 --- a/src/main/java/com/hp/autonomy/frontend/find/ApiKeyService.java +++ b/src/test/js/mock/model/documents-collection.js @@ -3,10 +3,10 @@ * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. */ -package com.hp.autonomy.frontend.find; +define([ + 'js-testing/backbone-mock-factory' +], function(backboneMockFactory) { -public interface ApiKeyService { + return backboneMockFactory.getCollection(['fetch']); - String getApiKey(); - -} +}); \ No newline at end of file diff --git a/src/test/js/mock/model/indexes-collection.js b/src/test/js/mock/model/indexes-collection.js new file mode 100644 index 0000000000..48c100f6f1 --- /dev/null +++ b/src/test/js/mock/model/indexes-collection.js @@ -0,0 +1,7 @@ +define([ + 'js-testing/backbone-mock-factory' +], function(backboneMockFactory) { + + return backboneMockFactory.getCollection(['fetch', 'getResourceIdentifiers']); + +}); \ No newline at end of file diff --git a/src/test/js/spec/app/model/dates-filter-model.js b/src/test/js/spec/app/model/dates-filter-model.js new file mode 100644 index 0000000000..40e8a693b6 --- /dev/null +++ b/src/test/js/spec/app/model/dates-filter-model.js @@ -0,0 +1,78 @@ +define([ + 'backbone', + 'find/app/model/dates-filter-model', + 'moment' +], function(Backbone, DatesFilterModel, moment) { + + describe('Dates Filter Model', function() { + beforeEach(function() { + this.dateOne = 'dateOne'; + + this.queryModel = new Backbone.Model(); + + this.datesFilterModel = new DatesFilterModel({}, {queryModel: this.queryModel}); + }); + + describe('calling setDateRange with weeks', function() { + beforeEach(function() { + this.datesFilterModel.setDateRange(DatesFilterModel.dateRange.week); + }); + + it('should set maxDate and minDate on the queryModel to an interval spanning a week', function() { + var minDate = this.queryModel.get('minDate'); + var maxDate = this.queryModel.get('maxDate'); + + expect(moment(minDate).isBetween( + moment().subtract(1, 'weeks').subtract(1, 'minutes'), + moment().subtract(1, 'weeks').add(1, 'minutes') + )).toBe(true); + + expect(moment(maxDate).isBetween( + moment().subtract(1, 'minutes'), + moment().add(1, 'minutes') + )).toBe(true); + }); + + describe('calling setDateRange with custom', function() { + beforeEach(function() { + this.datesFilterModel.setDateRange(DatesFilterModel.dateRange.custom); + }); + + it('should set maxDate and minDate to falsy values', function() { + expect(this.queryModel.get('minDate')).toBeFalsy(); + expect(this.queryModel.get('maxDate')).toBeFalsy(); + }); + }); + }); + + describe('calling setMaxDate', function() { + beforeEach(function() { + this.datesFilterModel.setMaxDate(this.dateOne); + }); + + it('should set the same value on the queryModel', function() { + expect(this.queryModel.get('maxDate')).toBe(this.dateOne); + }); + + it('should set the value of its dateRange attribute to custom', function() { + expect(this.datesFilterModel.get('dateRange')).toBe(DatesFilterModel.dateRange.custom); + }) + }); + + describe('calling setMinDate', function() { + beforeEach(function() { + this.datesFilterModel.setMaxDate(this.dateOne); + }); + + it('should set the same value on the queryModel', function() { + expect(this.queryModel.get('maxDate')).toBe(this.dateOne); + }); + + it('should set the value of its dateRange attribute to custom', function() { + expect(this.datesFilterModel.get('dateRange')).toBe(DatesFilterModel.dateRange.custom); + }) + }); + + }); + +}); \ No newline at end of file diff --git a/src/test/js/spec/app/model/document-model.js b/src/test/js/spec/app/model/document-model.js new file mode 100644 index 0000000000..f9d89cccb6 --- /dev/null +++ b/src/test/js/spec/app/model/document-model.js @@ -0,0 +1,36 @@ +define([ + 'find/app/model/document-model' +], function(DocumentModel) { + + describe('Document model', function() { + describe('parse method', function() { + beforeEach(function() { + this.parse = DocumentModel.prototype.parse; + }); + + it('uses the title from the response if present', function() { + var title = 'My Document'; + expect(this.parse({reference: 'my-document', title: title}).title).toBe(title); + }); + + it('uses the last part of a windows file path as the title if there is no title', function() { + expect(this.parse({reference: 'C:\\Documents\\file.txt'}).title).toBe('file.txt'); + }); + + it('uses the last part of a unix file path as the title if there is no title', function() { + expect(this.parse({reference: '/home/user/another-file.txt'}).title).toBe('another-file.txt'); + }); + + it('uses the whole reference if the reference finishes with a slash', function() { + var reference = 'http://example.com/main/'; + expect(this.parse({reference: reference}).title).toBe(reference); + }); + + it('uses the whole reference if the reference finishes with a slash followed by whitespace', function() { + var reference = 'foo/ \n '; + expect(this.parse({reference: reference}).title).toBe(reference); + }); + }); + }); + +}); diff --git a/src/test/js/spec/app/model/query-model.js b/src/test/js/spec/app/model/query-model.js new file mode 100644 index 0000000000..c7487f831c --- /dev/null +++ b/src/test/js/spec/app/model/query-model.js @@ -0,0 +1,53 @@ +define([ + 'find/app/model/backbone-query-model', + 'find/app/model/query-model' +], function(BackboneQueryModel, QueryModel) { + + describe('Query Model', function() { + beforeEach(function() { + var backboneQueryModel = new BackboneQueryModel(); + + this.queryModel = new QueryModel(backboneQueryModel); + + this.changed = false; + + this.queryModel.on('change', function() { + this.changed = true; + }, this); + }); + + it("should not change with just query text set", function() { + this.queryModel.set('queryText', 'text'); + + expect(this.changed).toBe(false); + }); + + it("should not change with just indexes set", function() { + this.queryModel.set('indexes', ['index1','index2']); + + expect(this.changed).toBe(false); + }); + + it("should change with both queryText and indexes set", function() { + this.queryModel.set('queryText', 'text'); + + expect(this.changed).toBe(false); + + this.queryModel.set('indexes', ['index1','index2']); + + expect(this.changed).toBe(true); + }); + + it("should proxy set events down to the underlying model", function() { + this.queryModel.set('queryText', 'text'); + + expect(this.queryModel.model.get('queryText')).toEqual('text'); + }); + + it("should proxy get events down to the underlying model", function() { + this.queryModel.model.set('queryText', 'text'); + + expect(this.queryModel.get('queryText')).toEqual('text'); + }); + }); +}); \ No newline at end of file diff --git a/src/test/js/spec/app/model/search-filters-collection.js b/src/test/js/spec/app/model/search-filters-collection.js new file mode 100644 index 0000000000..579dc3e346 --- /dev/null +++ b/src/test/js/spec/app/model/search-filters-collection.js @@ -0,0 +1,287 @@ +define([ + 'js-testing/backbone-mock-factory', + 'find/app/model/dates-filter-model', + 'find/app/model/search-filters-collection', + 'parametric-refinement/selected-values-collection', + 'databases-view/js/databases-collection', + 'find/app/model/backbone-query-model', + 'i18n!find/nls/bundle', + 'fieldtext/js/field-text-parser', + 'backbone', + 'moment' +], function(mockFactory, DatesFilterModel, FiltersCollection, SelectedParametricValues, DatabasesCollection, QueryModel, i18n, fieldTextParser, Backbone, moment) { + + var WOOKIEPEDIA = { + id: 'TESTDOMAIN:wookiepedia', + domain: 'TESTDOMAIN', + name: 'wookiepedia' + }; + + var WIKI_ENG = { + id: 'TESTDOMAIN:wiki_eng', + domain: 'TESTDOMAIN', + name: 'wiki_eng' + }; + + var INITIAL_MIN_DATE = moment(); + var DATE_FORMAT = 'LLL'; + + describe('Search filters collection initialised with an indexes filter, a DatesFilterModel with a min date set and a selected parametric value on the AGE field', function() { + beforeEach(function() { + this.indexesCollection = new DatabasesCollection([WOOKIEPEDIA, WIKI_ENG]); + this.selectedIndexesCollection = new DatabasesCollection([WIKI_ENG]); + + this.queryModel = new Backbone.Model({ + minDate: INITIAL_MIN_DATE + }); + + this.queryModel.hasAnyChangedAttributes = function() { + return true; + }; + + this.datesFilterModel = new Backbone.Model({ + dateRange: DatesFilterModel.dateRange.custom, + minDate: INITIAL_MIN_DATE + }); + + this.selectedParametricValues = new SelectedParametricValues([ + {field: 'AGE', value: '4'} + ]); + + this.collection = new FiltersCollection([], { + queryModel: this.queryModel, + datesFilterModel: this.datesFilterModel, + indexesCollection: this.indexesCollection, + selectedIndexesCollection: this.selectedIndexesCollection, + selectedParametricValues: this.selectedParametricValues + }); + }); + + it('contains three models', function() { + expect(this.collection.length).toBe(3); + }); + + it('contains a min date filter model', function() { + var model = this.collection.get(FiltersCollection.FilterTypes.minDate); + expect(model).toBeDefined(); + expect(model.get('text')).toContain(INITIAL_MIN_DATE.format(DATE_FORMAT)); + }); + + it('contains a databases filter model', function() { + var model = this.collection.get(FiltersCollection.FilterTypes.indexes); + expect(model).toBeDefined(); + expect(model.get('text')).toContain(WIKI_ENG.name); + expect(model.get('text')).not.toContain(WOOKIEPEDIA.name); + }); + + it('contains an AGE parametric field filter model', function() { + var model = this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC}); + expect(model).toBeDefined(); + expect(model.get('field')).toBe('AGE'); + expect(model.get('text')).toContain('AGE'); + expect(model.get('text')).toContain('4'); + }); + + describe('after datesFilterModel has a maxDate set', function() { + beforeEach(function() { + this.maxDate = moment(INITIAL_MIN_DATE).add(2, 'days'); + + this.queryModel.set('maxDate', this.maxDate); + + this.datesFilterModel.set({ + dateRange: DatesFilterModel.dateRange.custom, + maxDate: this.maxDate + }); + }); + + it('contains four models', function() { + expect(this.collection.length).toBe(4); + }); + + it('contains a max date filter model with the correct date', function() { + var model = this.collection.get(FiltersCollection.FilterTypes.maxDate); + expect(model).toBeDefined(); + expect(model.get('text')).toContain(i18n['app.until']); + expect(model.get('text')).toContain(moment(this.maxDate).format(DATE_FORMAT)); + }); + }); + + describe('after datesFilterModel has a minDate set', function() { + beforeEach(function() { + this.minDate = moment(INITIAL_MIN_DATE).subtract(2, 'days'); + + this.datesFilterModel.set({ + dateRange: DatesFilterModel.dateRange.custom, + minDate: this.minDate + }); + + this.queryModel.set('minDate', this.minDate); + }); + + it('contains three models', function() { + expect(this.collection.length).toBe(3); + }); + + it('updates the min date filter model', function() { + var model = this.collection.get(FiltersCollection.FilterTypes.minDate); + expect(model.get('text')).toContain(i18n['app.from']); + expect(model.get('text')).toContain(moment(this.minDate).format(DATE_FORMAT)); + }); + }); + + describe('after all databases are selected', function() { + beforeEach(function() { + this.selectedIndexesCollection.set([WOOKIEPEDIA, WIKI_ENG]); + }); + + it('contains two models', function() { + expect(this.collection.length).toBe(2); + }); + + it('removes the databases filter model', function() { + expect(this.collection.get(FiltersCollection.FilterTypes.indexes)).toBeUndefined(); + }); + + describe('then a database is deselected', function() { + beforeEach(function() { + this.selectedIndexesCollection.set([WOOKIEPEDIA]); + }); + + it('contains three models', function() { + expect(this.collection.length).toBe(3); + }); + + it('adds a databases filter model', function() { + var model = this.collection.get(FiltersCollection.FilterTypes.indexes); + expect(model).toBeDefined(); + expect(model.get('text')).toContain(WOOKIEPEDIA.name); + expect(model.get('text')).not.toContain(WIKI_ENG.name); + }); + }); + }); + + describe('after datesFilterModel has a minDate set', function() { + beforeEach(function() { + this.datesFilterModel.set({ + dateRange: null, + minDate: null + }); + + this.queryModel.set('minDate', null); + }); + + it('sets the request model minDate attribute to null', function() { + expect(this.queryModel.get('minDate')).toBeNull(); + }); + }); + + describe('after the parametric filter model is removed', function() { + beforeEach(function() { + this.collection.remove(this.collection.where({type: FiltersCollection.FilterTypes.PARAMETRIC})); + }); + + it('removes the associated model from the selected parametric values collection', function() { + expect(this.selectedParametricValues.length).toBe(0); + }); + }); + + describe('after the indexes filter model is removed', function() { + beforeEach(function() { + this.collection.remove(this.collection.where({type: FiltersCollection.FilterTypes.indexes})); + }); + + it('selects all of the indexes', function() { + expect(this.selectedIndexesCollection.length).toBe(2); + }); + }); + + describe('after two more parametric values are selected from the NAME field', function() { + beforeEach(function() { + this.selectedParametricValues.add([ + {field: 'NAME', fieldDisplayName: 'Name', value: 'bobby'}, + {field: 'NAME', fieldDisplayName: 'Name', value: 'penny'} + ]); + }); + + it('contains four models', function() { + expect(this.collection.length).toBe(4); + }); + + it('contains a NAME parametric filter model', function() { + var model = this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'NAME'}); + expect(model).toBeDefined(); + expect(model.get('text')).toContain('Name'); + expect(model.get('text')).toContain('bobby'); + expect(model.get('text')).toContain('penny'); + }); + + describe('then one of the NAME values is deselected', function() { + beforeEach(function() { + this.selectedParametricValues.remove(this.selectedParametricValues.findWhere({field: 'NAME', value: 'penny'})); + }); + + it('contains four models', function() { + expect(this.collection.length).toBe(4); + }); + + it('removes the deselected field value from the NAME parametric filter model', function() { + var model = this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'NAME'}); + expect(model.get('text')).toContain('Name'); + expect(model.get('text')).toContain('bobby'); + expect(model.get('text')).not.toContain('penny'); + }); + }); + + describe('then the AGE parametric value is deselected', function() { + beforeEach(function() { + this.selectedParametricValues.remove(this.selectedParametricValues.findWhere({field: 'AGE', value: '4'})); + }); + + it('contains three models', function() { + expect(this.collection.length).toBe(3); + }); + + it('removes the AGE filter model', function() { + expect(this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'AGE'})).toBeUndefined(); + }); + }); + + describe('then the selected parametric values collection is reset with a new selected field', function() { + beforeEach(function() { + this.selectedParametricValues.reset([ + {field: 'VEHICLE', value: 'car'} + ]); + }); + + it('contains three models', function() { + expect(this.collection.length).toBe(3); + }); + + it('replaces all parametric filter models with one representing the new field', function() { + expect(this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'AGE'})).toBeUndefined(); + expect(this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'NAME'})).toBeUndefined(); + + var vehicleModel = this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'VEHICLE'}); + expect(vehicleModel).toBeDefined(); + expect(vehicleModel.get('text')).toContain('VEHICLE'); + expect(vehicleModel.get('text')).toContain('car'); + }); + }); + + describe('then the AGE filter model is removed', function() { + beforeEach(function() { + this.collection.remove(this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'AGE'})); + }); + + it('contains three models', function() { + expect(this.collection.length).toBe(3); + }); + + it('still contains the NAME parametric filter model', function() { + expect(this.collection.findWhere({type: FiltersCollection.FilterTypes.PARAMETRIC, field: 'NAME'})).toBeDefined(); + }); + }); + }); + }); + +}); diff --git a/src/test/js/spec/app/page/date/dates-filter-view.js b/src/test/js/spec/app/page/date/dates-filter-view.js new file mode 100644 index 0000000000..e0c4c28222 --- /dev/null +++ b/src/test/js/spec/app/page/date/dates-filter-view.js @@ -0,0 +1,132 @@ +define([ + 'backbone', + 'moment', + 'find/app/page/date/dates-filter-view', + 'find/app/model/dates-filter-model' +], function(Backbone, moment, DatesFilterView, DatesFilterModel) { + + describe('Dates Filter View', function() { + beforeEach(function() { + this.now = moment.utc(1000000000000); + this.twoMonthsAgo = moment(this.now).subtract(2, 'months'); + + this.queryModel = new Backbone.Model(); + this.datesFilterModel = new Backbone.Model(); + + this.datesFilterView = new DatesFilterView({ + queryModel: this.queryModel, + datesFilterModel: this.datesFilterModel + }); + + this.datesFilterView.render(); + }); + + describe('after initialization', function() { + + it('should not tick any date range', function() { + var checkboxes = this.datesFilterView.$('[data-id] i:not(.checked)'); + + var anyTicked = _.find(checkboxes, function(checkbox) { + return !$(checkbox).hasClass('hide'); + }); + + expect(anyTicked).not.toBeDefined(); + }); + + }); + + describe('after this.datesFilterModel.setDateRange is called with weeks', function() { + beforeEach(function() { + this.datesFilterModel.set({ + minDate: moment(), + maxDate: moment(), + dateRange: DatesFilterModel.dateRange.week + }); + }); + + it('should tick the weeks checkbox', function() { + expect(this.datesFilterView.$("[data-id='" + DatesFilterModel.dateRange.week + "'] i").hasClass('hide')).toBe(false); + }); + + it('should change the dateRange on the queryModel to weeks', function() { + expect(this.datesFilterModel.get('dateRange')).toBe(DatesFilterModel.dateRange.week); + }); + + describe('after this.datesFilterModel.setDateRange is called with null', function() { + beforeEach(function() { + this.datesFilterModel.set('dateRange', null); + }); + + it('should not tick any date range', function() { + var checkboxes = this.datesFilterView.$("[data-id] i"); + + var anyTicked = _.some(checkboxes, function(checkbox) { + return !$(checkbox).hasClass('hide'); + }); + + expect(anyTicked).toBe(false); + }); + }); + }); + + describe('after this.datesFilterModel.setDateRange is called with custom', function() { + beforeEach(function() { + this.datesFilterModel.set('dateRange', DatesFilterModel.dateRange.custom); + }); + + it('should tick the custom checkbox', function() { + expect(this.datesFilterView.$("[data-id='" + DatesFilterModel.dateRange.custom + "'] i").hasClass('hide')).toBe(false); + }); + + it('should change the dateRange on the datesFilterModel to custom', function() { + expect(this.datesFilterModel.get('dateRange')).toBe(DatesFilterModel.dateRange.custom); + }); + + describe('then this.datesFilterModel.setDateRange is called with month', function() { + beforeEach(function() { + this.datesFilterModel.set('dateRange', DatesFilterModel.dateRange.month); + }); + + it('should tick the months checkbox', function() { + expect(this.datesFilterView.$("[data-id='" + DatesFilterModel.dateRange.month + "'] i").hasClass('hide')).toBe(false); + }); + + describe('then this.datesFilterModel.setDateRange is called with custom', function() { + beforeEach(function() { + this.datesFilterModel.set('dateRange', DatesFilterModel.dateRange.custom); + }); + + it('should tick the custom checkbox', function() { + expect(this.datesFilterView.$("[data-id='" + DatesFilterModel.dateRange.custom + "'] i").hasClass('hide')).toBe(false); + }); + }); + }); + }); + + describe('after custom is set on the datesFilterModel', function() { + beforeEach(function() { + this.datesFilterModel.set({ + dateRange: 'custom' + }); + }); + + it('should show the datepickers', function() { + expect($('.search-dates-wrapper').hasClass('hide')).toBe(false); + }); + + describe('after setting min and max dates on the datesFilterModel the new values should be reflected in the input', function() { + beforeEach(function() { + this.queryModel.set({ + maxDate: this.now, + minDate: this.twoMonthsAgo + }); + }); + + it('should clear the text from the display box', function() { + expect(this.datesFilterView.$('.results-filter-min-date input').val()).toBe("2001/07/09 01:46"); + expect(this.datesFilterView.$('.results-filter-max-date input').val()).toBe("2001/09/09 01:46"); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/test/js/spec/app/page/find-search.js b/src/test/js/spec/app/page/find-search.js new file mode 100644 index 0000000000..85912686f3 --- /dev/null +++ b/src/test/js/spec/app/page/find-search.js @@ -0,0 +1,51 @@ +define([ + 'find/app/page/find-search' +], function(FindSearch) { + + describe('Find Search', function() { + beforeEach(function() { + this.findSearch = new FindSearch(); + }); + + it("should show the service view when the queryText is not the empty string", function() { + this.findSearch.render(); + + this.findSearch.queryModel.set('queryText', 'text'); + + var $serviceViewContainer = this.findSearch.$el.find('.service-view-container'); + + expect($serviceViewContainer.css('display')).not.toEqual('none'); + }); + + it("should hide the service view when the queryText is the empty string", function() { + this.findSearch.render(); + + this.findSearch.queryModel.set('queryText', ''); + + var $serviceViewContainer = this.findSearch.$el.find('.service-view-container'); + + expect($serviceViewContainer.css('display')).toEqual('none'); + }); + + it("should animate the input view when the queryText is not the empty string", function() { + this.findSearch.render(); + + this.findSearch.queryModel.set('queryText', 'text'); + + var $animatedContainer = this.findSearch.$el.find('.animated-container'); + + expect($animatedContainer).not.toEqual([]); + }); + + it("should reverse-animate the input view when the queryText is the empty string", function() { + this.findSearch.render(); + + this.findSearch.queryModel.set('queryText', ''); + + var $animatedContainer = this.findSearch.$el.find('.reverse-animated-container'); + + expect($animatedContainer).not.toEqual([]); + }); + }); + +}); \ No newline at end of file diff --git a/src/test/js/spec/app/page/indexes/indexes-view.js b/src/test/js/spec/app/page/indexes/indexes-view.js new file mode 100644 index 0000000000..e8c9ae74ab --- /dev/null +++ b/src/test/js/spec/app/page/indexes/indexes-view.js @@ -0,0 +1,142 @@ +define([ + 'backbone', + 'underscore', + 'jquery', + 'find/app/page/indexes/indexes-view', + 'databases-view/js/databases-collection' +], function(Backbone, _, $, IndexesView, DatabasesCollection) { + + describe('Indexes View', function() { + var DOMAIN = 'TEST'; + + var indexId = function(name) { + return DOMAIN + ':' + name; + }; + + var INDEXES = _.map(['a','b','c'], function(name) { + return {name: name, domain: DOMAIN, id: indexId(name)}; + }); + + // Convert index collection model attributes to a resource identifier object + var toResourceIdentifier = _.partial(_.pick, _, 'domain', 'name'); + + beforeEach(function() { + this.indexesCollection = new DatabasesCollection(); + this.selectedIndexesCollection = new DatabasesCollection(); + + this.queryModel = new Backbone.Model(); + + this.indexesView = new IndexesView({ + queryModel: this.queryModel, + indexesCollection: this.indexesCollection, + selectedDatabasesCollection: this.selectedIndexesCollection + }); + + this.idElement = function(indexAttributes) { + return this.indexesView.$('li[data-id="' + indexAttributes.id + '"]'); + }; + + this.indexesView.render(); + + this.indexesCollection.reset(INDEXES); + this.queryModel.set('indexes', _.pluck(INDEXES, 'id')); + }); + + describe('after initialization', function() { + it('should display indexes in the IndexesCollection', function() { + var elements = this.indexesView.$el.find('[data-id]'); + + var dataIds = _.map(elements, function (element) { + return $(element).attr('data-id'); + }); + + expect(dataIds).toContain(INDEXES[0].id); + expect(dataIds).toContain(INDEXES[1].id); + expect(dataIds).toContain(INDEXES[2].id); + }); + + it('sets all indexes on the selected indexes collection', function() { + expect(this.selectedIndexesCollection.length).toBe(3); + }); + + it('does not select indexes in the UI', function() { + expect(this.indexesView.$('i.icon-ok')).toHaveLength(0); + }); + + describe('clicking an index once', function() { + beforeEach(function() { + this.idElement(INDEXES[0]).click(); + }); + + it('updates the selected indexes collection', function() { + expect(this.selectedIndexesCollection.toResourceIdentifiers()).toEqual([toResourceIdentifier(INDEXES[0])]); + }); + + it('should check the clicked index', function() { + var checkedCheckbox = this.idElement(INDEXES[0]).find('i'); + var uncheckedCheckboxOne = this.idElement(INDEXES[1]).find('i'); + var uncheckedCheckboxTwo = this.idElement(INDEXES[2]).find('i'); + + expect(checkedCheckbox).toHaveClass('icon-ok'); + expect(uncheckedCheckboxOne).not.toHaveClass('icon-ok'); + expect(uncheckedCheckboxTwo).not.toHaveClass('icon-ok'); + }); + }); + + describe('clicking an index twice', function() { + beforeEach(function() { + this.idElement(INDEXES[0]).click().click(); + }); + + it('updates the selected indexes collection with all of the indexes', function() { + expect(this.selectedIndexesCollection.length).toBe(3); + + _.each(INDEXES, function(index) { + expect(this.selectedIndexesCollection.findWhere({name: index.name})).toBeDefined(); + }, this); + }); + + it('should leave the indexes selected in the ui unchanged', function() { + expect(this.indexesView.$('i.icon-ok')).toHaveLength(0); + }); + }); + + }); + + describe('when selected indexes collection', function() { + describe('is set to contain all but the first index', function() { + beforeEach(function() { + this.selectedIndexesCollection.set(_.tail(INDEXES)); + }); + + it('should select the right indexes', function() { + var uncheckedCheckbox = this.idElement(INDEXES[0]).find('i'); + var checkedCheckboxOne = this.idElement(INDEXES[1]).find('i'); + var checkedCheckboxTwo = this.idElement(INDEXES[2]).find('i'); + + expect(uncheckedCheckbox).not.toHaveClass('icon-ok'); + expect(checkedCheckboxOne).toHaveClass('icon-ok'); + expect(checkedCheckboxTwo).toHaveClass('icon-ok'); + }); + }); + + describe('is set to contain only first index', function() { + beforeEach(function() { + this.selectedIndexesCollection.set(_.head(INDEXES)); + }); + + it('should select only the first index', function() { + var checkedCheckbox = this.idElement(INDEXES[0]).find('i'); + var uncheckedCheckboxOne = this.idElement(INDEXES[1]).find('i'); + var uncheckedCheckboxTwo = this.idElement(INDEXES[2]).find('i'); + + expect(checkedCheckbox).toHaveClass('icon-ok'); + expect(uncheckedCheckboxOne).not.toHaveClass('icon-ok'); + expect(uncheckedCheckboxTwo).not.toHaveClass('icon-ok'); + }); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/src/test/js/spec/app/page/parametric/parametric-field-view.js b/src/test/js/spec/app/page/parametric/parametric-field-view.js new file mode 100644 index 0000000000..3b75b684d8 --- /dev/null +++ b/src/test/js/spec/app/page/parametric/parametric-field-view.js @@ -0,0 +1,38 @@ +define([ + 'find/app/page/parametric/parametric-field-view', + 'backbone' +], function(FieldView, Backbone) { + + describe('Parametric field view', function() { + beforeEach(function() { + this.model = new Backbone.Model({ + displayName: 'Primary Author', + id: 'primary_author' + }); + + this.model.fieldValues = new Backbone.Collection([ + {id: 'bob', count: 100, selected: true}, + {id: 'penny', count: 96, selected: true}, + {id: 'fred', count: 25, selected: false} + ]); + + this.fieldView = new FieldView({model: this.model}); + this.fieldView.render(); + }); + + it('sets a data-field attribute', function() { + expect(this.fieldView.$el).toHaveAttr('data-field', 'primary_author'); + }); + + it('displays the display name', function() { + expect(this.fieldView.$el).toContainText('Primary Author'); + }); + + it('displays the field values', function() { + expect(this.fieldView.$('[data-value="bob"]')).toHaveLength(1); + expect(this.fieldView.$('[data-value="penny"]')).toHaveLength(1); + expect(this.fieldView.$('[data-value="fred"]')).toHaveLength(1); + }); + }); + +}); \ No newline at end of file diff --git a/src/test/js/spec/app/page/parametric/parametric-value-view.js b/src/test/js/spec/app/page/parametric/parametric-value-view.js new file mode 100644 index 0000000000..ce8300d21b --- /dev/null +++ b/src/test/js/spec/app/page/parametric/parametric-value-view.js @@ -0,0 +1,93 @@ +define([ + 'find/app/page/parametric/parametric-value-view', + 'backbone' +], function(ValueView, Backbone) { + + describe('Parametric value view', function() { + beforeEach(function() { + this.model = new Backbone.Model({ + id: 'cat', + count: 3, + selected: false + }); + + this.view = new ValueView({model: this.model}); + this.view.render(); + + this.$check = this.view.$('.parametric-value-icon'); + this.$text = this.view.$('.parametric-value-text'); + }); + + it('sets a data-value attribute', function() { + expect(this.view.$el).toHaveAttr('data-value', 'cat'); + }); + + it('displays the value name', function() { + expect(this.$text).toContainText('cat'); + }); + + it('displays the count', function() { + expect(this.$text).toContainText('(3)'); + }); + + it('hides the check icon if the value is not selected', function() { + expect(this.$check).toHaveClass('hide'); + }); + + describe('after the count is set to null', function() { + beforeEach(function() { + this.model.set('count', null); + this.view.updateCount(); + }); + + it('displays the value name', function() { + expect(this.$text).toContainText('cat'); + }); + + it('does not display the count', function() { + expect(this.$text).not.toContainText('(3)'); + expect(this.$text).not.toContainText('(0)'); + expect(this.$text).not.toContainText('()'); + expect(this.$text).not.toContainText('null'); + }); + + describe('then the count is set to a number', function() { + beforeEach(function() { + this.model.set('count', 50); + this.view.updateCount(); + }); + + it('displays the value name', function() { + expect(this.$text).toContainText('cat'); + }); + + it('displays the count', function() { + expect(this.$text).toContainText('(50)'); + }); + }); + }); + + describe('after the value is selected', function() { + beforeEach(function() { + this.model.set('selected', true); + this.view.updateSelected(); + }); + + it('shows the check icon', function() { + expect(this.$check).not.toHaveClass('hide'); + }); + + describe('then the value is deselected', function() { + beforeEach(function() { + this.model.set('selected', false); + this.view.updateSelected(); + }); + + it('hides the check icon', function() { + expect(this.$check).toHaveClass('hide'); + }); + }); + }); + }); + +}); diff --git a/src/test/js/spec/app/page/related-concepts/related-concepts-view.js b/src/test/js/spec/app/page/related-concepts/related-concepts-view.js new file mode 100644 index 0000000000..2ec2fdbd1f --- /dev/null +++ b/src/test/js/spec/app/page/related-concepts/related-concepts-view.js @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2015 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'backbone', + 'find/app/page/related-concepts/related-concepts-view', + 'mock/model/indexes-collection', + 'jasmine-jquery' +], function(Backbone, RelatedConceptsView, IndexesCollection) { + + describe('Related Concepts view', function() { + beforeEach(function() { + this.indexesCollection = new IndexesCollection(); + this.queryModel = new Backbone.Model(); + this.entityCollection = new Backbone.Collection(); + + this.relatedConceptsView = new RelatedConceptsView({ + entityCollection: this.entityCollection, + indexesCollection: this.indexesCollection, + queryModel: this.queryModel + }); + + this.relatedConceptsView.render(); + }); + + afterEach(function() { + IndexesCollection.reset(); + }); + + it('should only render one loading spinner when multiple request events are triggered', function() { + this.entityCollection.trigger('request'); + this.entityCollection.trigger('request'); + + expect(this.relatedConceptsView.$('.loading-spinner')).toHaveLength(1); + }); + }) + +}); diff --git a/src/test/js/test-config.json b/src/test/js/test-config.json index 1fb0c0f7f2..d05243c4bd 100644 --- a/src/test/js/test-config.json +++ b/src/test/js/test-config.json @@ -1,3 +1,3 @@ [ "/test/test-require-config.js" -] \ No newline at end of file +] diff --git a/src/test/js/test-require-config.js b/src/test/js/test-require-config.js index 34ed88c47a..5fccdac9f9 100644 --- a/src/test/js/test-require-config.js +++ b/src/test/js/test-require-config.js @@ -3,17 +3,25 @@ * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. */ -define(['/src/js/require-config.js'], function() { - require.config({ - baseUrl: '/src/static/js', - paths: { - /* Directories */ - mock: '/test/mock', - real: '/src/static/js', - resources: '/test/resources' - - /* Mocks */ - // replace this comment with your mocks +require.config({ + baseUrl: 'src/main/webapp/static/js', + paths: { + 'jasmine-jquery': '../bower_components/jasmine-jquery/lib/jasmine-jquery', + 'js-testing': '../bower_components/hp-autonomy-js-testing-utils/src/js', + 'mock': '../../../../test/js/mock' + }, + shim: { + 'jasmine-jquery': ['jquery'] + }, + map: { + '*': { + 'find/lib/backbone/backbone-extensions': 'backbone' + }, + 'find/app/page/service-view': { + 'find/app/model/indexes-collection': 'mock/model/indexes-collection' + }, + 'find/app/page/related-concepts/related-concepts-view': { + 'find/app/model/documents-collection': 'mock/model/documents-collection' } - }); -}); \ No newline at end of file + } +}); diff --git a/src/test/resources/com/hp/autonomy/frontend/find/search/privateIndex.json b/src/test/resources/com/hp/autonomy/frontend/find/search/privateIndex.json deleted file mode 100644 index 09050ff159..0000000000 --- a/src/test/resources/com/hp/autonomy/frontend/find/search/privateIndex.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "index": "myindex", - "flavor": "standard", - "type": "content", - "date_created": "Mon Mar 10 2014 19:29:47 GMT+0000 (UTC)", - "num_components": 1, - "description": null, - "subtype": null -} diff --git a/vagrant/roles/redis-server.rb b/vagrant/roles/redis-server.rb new file mode 100644 index 0000000000..55546abf93 --- /dev/null +++ b/vagrant/roles/redis-server.rb @@ -0,0 +1,10 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +name 'redis-server' +description 'Redis datastore server' +run_list 'recipe[redis]', 'recipe[redis::server]' + +override_attributes 'redis' => { + :bind => '0.0.0.0' +} diff --git a/web-override.xml b/web-override.xml index 99b7efc205..9174e1dbd8 100644 --- a/web-override.xml +++ b/web-override.xml @@ -41,4 +41,4 @@ - \ No newline at end of file +