├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bower.json ├── brunch-config.js ├── config ├── config.exs ├── dev.exs ├── prod.exs ├── template.secret.exs └── test.exs ├── deploy.sh ├── files └── .gitignore ├── lib ├── eientei.ex ├── eientei │ ├── archiver.ex │ ├── endpoint.ex │ ├── rate_limit.ex │ └── repo.ex └── mix │ └── archive_files.ex ├── mix.exs ├── mix.lock ├── package.json ├── priv └── repo │ ├── migrations │ └── 20151021063000_create_uploads.exs │ └── seeds.exs ├── test ├── controllers │ └── page_controller_test.exs ├── models │ └── uploads_test.exs ├── support │ ├── channel_case.ex │ ├── conn_case.ex │ └── model_case.ex ├── test_helper.exs └── views │ ├── error_view_test.exs │ ├── layout_view_test.exs │ └── page_view_test.exs └── web ├── channels └── user_socket.ex ├── controllers ├── page_controller.ex └── upload_controller.ex ├── models └── uploads.ex ├── router.ex ├── static ├── assets │ ├── favicon.ico │ └── robots.txt ├── css │ ├── app.css │ └── bootstrap.css └── js │ ├── FileSelector.js │ ├── FileUpload.js │ ├── UploadButton.js │ ├── app.js │ └── socket.js ├── templates ├── layout │ └── app.html.eex ├── page │ ├── contact.html.eex │ ├── faq.html.eex │ ├── index.html.eex │ ├── info.html.eex │ └── tools.html.eex └── shared │ └── nav.html.eex ├── views ├── error_view.ex ├── layout_view.ex ├── page_view.ex └── shared_view.ex └── web.ex /.gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | /bower_components/* 7 | 8 | # Generate on crash by the VM 9 | erl_crash.dump 10 | 11 | # Static artifacts 12 | /node_modules 13 | 14 | # Since we are building assets from web/static, 15 | # we ignore priv/static. You may want to comment 16 | # this depending on your deployment strategy. 17 | /priv/static 18 | 19 | # The config/prod.secret.exs file by default contains sensitive 20 | # data and you should not commit it into version control. 21 | # 22 | # Alternatively, you may comment the line below and commit the 23 | # secrets file as long as you replace its contents by environment 24 | # variables. 25 | /config/prod.secret.exs 26 | /config/dev.secret.exs 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Luminarys 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | Portions of code utilized in the CSS, Javascript and HTML are licensed under these terms: 16 | 17 | Copyright (c) 2013, 2014, 2015 Eric Johansson 18 | Copyright (c) 2013, 2014 Peter Lejeck 19 | Copyright (c) 2015 cenci0 20 | Copyright (c) 2015 the Pantsu.cat developers 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining a copy of 23 | this software and associated documentation files (the "Software"), to deal in 24 | the Software without restriction, including without limitation the rights to 25 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 26 | the Software, and to permit persons to whom the Software is furnished to do so, 27 | subject to the following conditions: 28 | 29 | The above copyright notice and this permission notice shall be included in all 30 | copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 34 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 35 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 36 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 37 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 38 | 39 | Copyright (c) 2015 Drew DeVault 40 | 41 | Permission is hereby granted, free of charge, to any person obtaining a copy of 42 | this software and associated documentation files (the "Software"), to deal in 43 | the Software without restriction, including without limitation the rights to 44 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 45 | of the Software, and to permit persons to whom the Software is furnished to do 46 | so, subject to the following conditions: 47 | 48 | The above copyright notice and this permission notice shall be included in all 49 | copies or substantial portions of the Software. 50 | 51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 57 | SOFTWARE. 58 | 59 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mix deps.get 3 | mix compile 4 | ./node_modules/brunch/bin/brunch build --production 5 | mix phoenix.digest 6 | 7 | setup: 8 | npm install 9 | bower install 10 | mix deps.get 11 | mix compile 12 | ./node_modules/brunch/bin/brunch build --production 13 | mix phoenix.digest 14 | mix ecto.create 15 | mix ecto.migrate 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eientei 2 | 3 | A simple file uploading and sharing service. 4 | 5 | ## Features 6 | * Simple uploading, no registration required 7 | * Automated archival of files to the Internet Archive 8 | * Fallback service that can be used if migrating from another Pomf clone 9 | * A built in LRU-like cache which enables fast file access 10 | 11 | ## Installation 12 | ### Prerequisites 13 | * Elixir 14 | * NPM 15 | * Bower 16 | * PostgreSQL 17 | 18 | ### Configuration 19 | 1. In `config/` copy file `template.secret.exs` to `dev.secret.exs` and `prod.secret.exs`. 20 | 2. In the main directory run `mix phoenix.gen.secret` to generate a new secret. 21 | 3. In the prod secret config files, set the `secret_key_base` parameter using this generated string. Note that if you want to run Eientei in the development environment you should generate another secret key, and apply the following instructions to the dev.secret.exs with appropriately modified settings. 22 | 4. In prod secret config, look through the settings and set them accordingly: 23 | * Set the `use_ia_archive` option to true if you would like automatic archivals. If enabled fill out the fields accordingly. 24 | * Set the `fallback_service` to true if you're migrating from a previous service. If you enable it, set the url parameter, making sure not to add on a trailing / and look at the alert params. 25 | * Configure all settings in the general configuration and database configuration sections. 26 | 27 | ### Setup 28 | * Run `EXPORT MIX_ENV=prod`, or use dev instead of prod if you'd like to use the development environment. 29 | * For first time setup, or if the db needs updates run `make setup` 30 | * If you just want to recompile assets after an update, run `make` 31 | 32 | ### Running 33 | 1. Run `MIX_ENV=prod mix phoenix.server` to start the server. If you'd like to use a custom port, run `PORT=your_port MIX_ENV=prod mix phoenix.server`. 34 | Alternatively you can run `./deploy.sh` which will start your server on either port 21111 or 21112 depending on which one is occupied. You can use this to do upgrades production, setting your nginx to try the 21111 server then 21112 with an upstream block and then just starting a new instance after an upgrade and shutting off the old one. 35 | 2. Success! You have succesfully started Eientei and it will be running on port 21111 or the one you defined. You should now setup nginx or some other web service to reverse proxy connections to the service. 36 | 37 | ### Archiving 38 | * For privacy reasons I don't have immediate auto-archiving currently enabled. 39 | * You may manually archive all currently unarchived files by running `MIX_ENV=prod mix archive.files` 40 | * I suggest you run this on a semi-regular basis 41 | 42 | ## TODO 43 | 1. Bug fixes/general code checks 44 | 2. Custom styling 45 | 3. Tests 46 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eientei", 3 | "description": "Simple file uploader with an advanced feature set", 4 | "main": "", 5 | "authors": [ 6 | "Luminarys " 7 | ], 8 | "license": "MIT", 9 | "homepage": "https://github.com/Luminarys/Eientei", 10 | "moduleType": [], 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "react": "~0.14.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /brunch-config.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | // See http://brunch.io/#documentation for docs. 3 | files: { 4 | javascripts: { 5 | joinTo: 'js/app.js', 6 | order: { 7 | before: [ 8 | /^bower_components/, 9 | /^web\/static\/vendor/ 10 | ] 11 | } 12 | }, 13 | stylesheets: { 14 | joinTo: 'css/app.css' 15 | }, 16 | templates: { 17 | joinTo: 'js/app.js' 18 | } 19 | }, 20 | 21 | conventions: { 22 | // This option sets where we should place non-css and non-js assets in. 23 | // By default, we set this to '/web/static/assets'. Files in this directory 24 | // will be copied to `paths.public`, which is "priv/static" by default. 25 | assets: /^(web\/static\/assets)/ 26 | }, 27 | 28 | // Phoenix paths configuration 29 | paths: { 30 | // Which directories to watch 31 | watched: [ 32 | "deps/phoenix/web/static", 33 | "deps/phoenix_html/web/static", 34 | "web/static", 35 | "test/static" 36 | ], 37 | 38 | // Where to compile files to 39 | public: "priv/static" 40 | }, 41 | 42 | // Configure your plugins 43 | plugins: { 44 | babel: { 45 | // Do not use ES6 compiler in vendor code 46 | //ignore: [/^(web\/static\/vendor)/] 47 | ignore: [/^(web\/static\/vendor)|(bower_components)/] 48 | } 49 | }, 50 | 51 | modules: { 52 | autoRequire: { 53 | "js/app.js": ["web/static/js/app"] 54 | } 55 | }, 56 | 57 | npm: { 58 | enabled: true 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | use Mix.Config 7 | 8 | # Configures the endpoint 9 | config :eientei, Eientei.Endpoint, 10 | url: [host: "localhost"], 11 | root: Path.dirname(__DIR__), 12 | secret_key_base: "Nixw4hRwgf7ZC3i+k6FmUGgdlgr10+K6FRRIZSY7Ig83aeUWX1kGh4ZIUkJBrCjG", 13 | render_errors: [accepts: ~w(html json)], 14 | pubsub: [name: Eientei.PubSub, 15 | adapter: Phoenix.PubSub.PG2] 16 | 17 | # Set port based on env_var, otherwise use default of 21111 18 | case System.get_env("PORT") do 19 | nil -> 20 | config :eientei, Eientei.Endpoint, 21 | http: [port: 21111] 22 | port_num -> 23 | config :eientei, Eientei.Endpoint, 24 | http: [port: port_num] 25 | end 26 | 27 | # Configures Elixir's Logger 28 | config :logger, :console, 29 | format: "$time $metadata[$level] $message\n", 30 | metadata: [:request_id] 31 | 32 | # Link to git repo 33 | config :eientei, 34 | git_repo_url: "https://github.com/luminarys/eientei" 35 | 36 | # Import environment specific config. This must remain at the bottom 37 | # of this file so it overrides the configuration defined above. 38 | import_config "#{Mix.env}.exs" 39 | 40 | # Configure phoenix generators 41 | config :phoenix, :generators, 42 | migration: true, 43 | binary_id: false 44 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with brunch.io to recompile .js and .css sources. 9 | config :eientei, Eientei.Endpoint, 10 | debug_errors: true, 11 | code_reloader: true, 12 | cache_static_lookup: false, 13 | check_origin: false, 14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin"]] 15 | 16 | # Watch static and templates for browser reloading. 17 | config :eientei, Eientei.Endpoint, 18 | live_reload: [ 19 | patterns: [ 20 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, 21 | ~r{web/views/.*(ex)$}, 22 | ~r{web/templates/.*(eex)$} 23 | ] 24 | ] 25 | 26 | # Do not include metadata nor timestamps in development logs 27 | config :logger, :console, format: "[$level] $message\n" 28 | 29 | # Set a higher stacktrace during development. 30 | # Do not configure such in production as keeping 31 | # and calculating stacktraces is usually expensive. 32 | config :phoenix, :stacktrace_depth, 20 33 | 34 | # Configure your database 35 | config :eientei, Eientei.Repo, 36 | adapter: Ecto.Adapters.Postgres, 37 | username: "postgres", 38 | password: "postgres", 39 | database: "eientei_dev", 40 | hostname: "localhost", 41 | pool_size: 10 42 | 43 | import_config "dev.secret.exs" 44 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, we configure the host to read the PORT 4 | # from the system environment. Therefore, you will need 5 | # to set PORT=80 before running your server. 6 | # 7 | # You should also configure the url host to something 8 | # meaningful, we use this information when generating URLs. 9 | # 10 | # Finally, we also include the path to a manifest 11 | # containing the digested version of static files. This 12 | # manifest is generated by the mix phoenix.digest task 13 | # which you typically run after static files are built. 14 | config :eientei, Eientei.Endpoint, 15 | cache_static_manifest: "priv/static/manifest.json" 16 | 17 | # Do not print debug messages in production 18 | config :logger, level: :info 19 | 20 | # ## SSL Support 21 | # 22 | # To get SSL working, you will need to add the `https` key 23 | # to the previous section and set your `:url` port to 443: 24 | # 25 | # config :eientei, Eientei.Endpoint, 26 | # ... 27 | # url: [host: "example.com", port: 443], 28 | # https: [port: 443, 29 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 30 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] 31 | # 32 | # Where those two env variables return an absolute path to 33 | # the key and cert in disk or a relative path inside priv, 34 | # for example "priv/ssl/server.key". 35 | # 36 | # We also recommend setting `force_ssl`, ensuring no data is 37 | # ever sent via http, always redirecting to https: 38 | # 39 | # config :eientei, Eientei.Endpoint, 40 | # force_ssl: [hsts: true] 41 | # 42 | # Check `Plug.SSL` for all available options in `force_ssl`. 43 | 44 | # ## Using releases 45 | # 46 | # If you are doing OTP releases, you need to instruct Phoenix 47 | # to start the server for all endpoints: 48 | # 49 | # config :phoenix, :serve_endpoints, true 50 | # 51 | # Alternatively, you can configure exactly which server to 52 | # start per endpoint: 53 | # 54 | # config :eientei, Eientei.Endpoint, server: true 55 | # 56 | 57 | # Finally import the config/prod.secret.exs 58 | # which should be versioned separately. 59 | 60 | import_config "prod.secret.exs" 61 | -------------------------------------------------------------------------------- /config/template.secret.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # SECRET KEY CONFIGURATION 4 | config :eientei, Eientei.Endpoint, 5 | # Generate this by running mix phoenix.gen.secret 6 | secret_key_base: "Insert generated secret key here!" 7 | 8 | # AUTO ARCHIVE CONFIGURATION 9 | # Setup auto archiving settings here. 10 | config :eientei, 11 | # Set this to true if you do want autoarchiving 12 | use_ia_archive: false, 13 | ia_access: "YOUR ACCESS KEY", 14 | ia_secret: "YOUR SECRET KEY", 15 | ia_service_name: "YOUR SERVICE NAME", 16 | ia_sponsor: "YOUR REAL NAME OR HANDLE" 17 | 18 | # FALLBACK SERVICE CONFIGURATION 19 | # If you are migrating from an old service, you can have it check 20 | # if the queried file is there, so as to not confuse users. 21 | # Set this to true to enable it. 22 | config :eientei, 23 | fallback_service: false, 24 | # Don't put a trailing / on this - e.g. for the original Pomf, this would be a.pomf.se 25 | fallback_service_url: "https://previous-file-hosting-domain.tld", 26 | # Set these two options to create an alert on the front page telling users how to find your fallback/old service 27 | fallback_service_alert: false, 28 | # Don't put a http(s) on this, just domain.tld 29 | fallback_service_home_page: "previous-homepage-url.tld" 30 | 31 | # GENERAL CONFIGURATION 32 | # Note that it is assumed that you have an abuse@ email 33 | config :eientei, 34 | service_name: "Service Name", 35 | service_domain: "service.tld", 36 | service_url: "https://service.tld", 37 | # This should be used to specify a response url. Make sure to set http/https properly 38 | # If you want to serve files at f.service.tld set that here. Please don't add a trailing / 39 | contact_email: "mycontactaddress@email.com", 40 | # Max individual file size in megabytes 41 | max_file_size: 32, 42 | # Max total UL size in megabytes 43 | max_upload_size: 100, 44 | # Maximum number of cache entries. 45 | # For safety purposes do not let 46 | # max_cache_size * max_upload_size/1000 47 | # exceed your RAM amount(in gigabytes)) 48 | max_cache_size: 100, 49 | use_cloudflare: false, 50 | # If enabled this blocks embedded javascript execution on files. 51 | use_csrf_protection: false 52 | 53 | # RATE LIMITING 54 | # This should be used to block spam. 55 | config :eientei, 56 | # Interval in seconds. 57 | rate_interval: 60, 58 | # NUmber of files which can be uploaded during the interval 59 | rate_access_usage: 20, 60 | # Amount of data(in megabytes) which can be uploaded during the interval 61 | rate_data_usage: 200 62 | 63 | # DATABSE CONFIGURATION 64 | config :eientei, Eientei.Repo, 65 | adapter: Ecto.Adapters.Postgres, 66 | username: "postgres", 67 | password: "postgres", 68 | database: "eientei_#{Mix.env}", 69 | pool_size: 20 70 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :eientei, Eientei.Endpoint, 6 | http: [port: 4001], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | 12 | # Set a higher stacktrace during test 13 | config :phoenix, :stacktrace_depth, 20 14 | 15 | # Configure your database 16 | config :eientei, Eientei.Repo, 17 | adapter: Ecto.Adapters.Postgres, 18 | username: "postgres", 19 | password: "postgres", 20 | database: "eientei_test", 21 | hostname: "localhost", 22 | pool: Ecto.Adapters.SQL.Sandbox 23 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | s1=$(netstat -aon | grep 21111 | wc -l) 4 | s2=$(netstat -aon | grep 21112 | wc -l) 5 | 6 | if [[ $s1 -eq 0 ]] 7 | then 8 | MIX_ENV=prod PORT=21111 mix phoenix.server 9 | elif [[ $s2 -eq 0 ]] 10 | then 11 | MIX_ENV=prod PORT=21112 mix phoenix.server 12 | else 13 | echo "Please stop at least one server, or make sure port 21111 or 21112 is free!" 14 | fi 15 | -------------------------------------------------------------------------------- /files/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /lib/eientei.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei do 2 | use Application 3 | 4 | # See http://elixir-lang.org/docs/stable/elixir/Application.html 5 | # for more information on OTP Applications 6 | def start(_type, _args) do 7 | import Supervisor.Spec, warn: false 8 | 9 | children = [ 10 | # Start the endpoint when the application starts 11 | supervisor(Eientei.Endpoint, []), 12 | # Start the Ecto repository 13 | worker(Eientei.Repo, []), 14 | worker(Eientei.Archiver, []), 15 | worker(ConCache, [ 16 | [ 17 | ttl_check: :timer.seconds(30), 18 | ttl: :timer.minutes(30), 19 | touch_on_read: true 20 | ], 21 | [name: :file_cache]]) 22 | # Here you could define other workers and supervisors as children 23 | # worker(Eientei.Worker, [arg1, arg2, arg3]), 24 | ] 25 | 26 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 27 | # for other strategies and supported options 28 | opts = [strategy: :one_for_one, name: Eientei.Supervisor] 29 | Supervisor.start_link(children, opts) 30 | end 31 | 32 | # Tell Phoenix to update the endpoint configuration 33 | # whenever the application is updated. 34 | def config_change(changed, _new, removed) do 35 | Eientei.Endpoint.config_change(changed, removed) 36 | :ok 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/eientei/archiver.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Archiver do 2 | use GenServer 3 | 4 | @make_bucket {"x-amz-auto-make-bucket", "1"} 5 | @collection {"x-archive-meta01-collection", "opensource_media"} 6 | @media_type {"x-archive-meta-mediatype", "web"} 7 | @ia_sponsor {"x-archive-meta-sponsor", Application.get_env(:einetei, :ia_sponsor)} 8 | 9 | @ia_access Application.get_env(:eientei, :ia_access) 10 | @ia_secret Application.get_env(:eientei, :ia_secret) 11 | @ia_auth {"authorization", "LOW #{@ia_access}:#{@ia_secret}"} 12 | 13 | @service Application.get_env(:eientei, :ia_service_name) 14 | @bucket_name "#{@service}_archive" 15 | 16 | def start_link() do 17 | GenServer.start_link(__MODULE__, [], name: Archiver) 18 | end 19 | 20 | def handle_call({:archive, name, location}, _from, state) do 21 | require Logger 22 | case archive_file(name, location) do 23 | :error -> 24 | Logger.log :warn, "File #{location} could not be uploaded to the IA archive!" 25 | {:reply, :failed, state} 26 | :ok -> 27 | update_model(name) 28 | {:reply, :ok, state} 29 | end 30 | end 31 | 32 | def handle_cast({:archive, name, location}, state) do 33 | require Logger 34 | case archive_file(name, location) do 35 | :error -> 36 | Logger.log :warn, "File #{location} could not be uploaded to the IA archive!" 37 | {:noreply, state} 38 | :ok -> 39 | update_model(name) 40 | {:noreply, state} 41 | end 42 | end 43 | 44 | defp update_model(name) do 45 | import Ecto.Query 46 | 47 | query = from u in Eientei.Upload, 48 | where: u.name == ^name 49 | file = Eientei.Repo.one query 50 | {:ok, _file} = Eientei.Repo.update(%{file | archived_url: "https://archive.org/download/#{@bucket_name}/#{name}"}) 51 | end 52 | 53 | defp archive_file(name, location) do 54 | check_rate_limit 55 | access = Application.get_env(:eientei, :ia_access) 56 | secret = Application.get_env(:eientei, :ia_secret) 57 | headers = [ 58 | @make_bucket, 59 | @collection, 60 | @media_type, 61 | @ia_sponsor, 62 | @ia_auth, 63 | ] 64 | try do 65 | {:ok, resp} = HTTPoison.put("http://s3.us.archive.org/#{@bucket_name}/#{name}", 66 | {:file, "#{location}"}, 67 | headers) 68 | case resp do 69 | %{status_code: 200} -> :ok 70 | %{status_code: 400} -> 71 | # Try sending with no extension 72 | # If it fails then log an error 73 | new_name = Path.basename(name, Path.extname(name)) 74 | {:ok, new_resp} = HTTPoison.put("http://s3.us.archive.org/#{@bucket_name}/#{new_name}", 75 | {:file, "#{location}"}, 76 | headers) 77 | case new_resp do 78 | %{status_code: 200} -> :ok 79 | %{status_code: 400} -> :error 80 | end 81 | end 82 | rescue 83 | MatchError -> 84 | # Catches timeout errors 85 | GenServer.cast(Archiver, {:archive, name, location}) 86 | :ok 87 | end 88 | end 89 | 90 | defp check_rate_limit() do 91 | {:ok, %{:body => body}} = HTTPoison.get("http://s3.us.archive.org/?check_limit=1&bucket=#{@bucket_name}&accesskey=#{@ia_access}") 92 | %{"over_limit" => over_limit} = body |> Poison.decode! 93 | case over_limit do 94 | 0 -> :ok 95 | 1 -> 96 | :timer.sleep(10000) 97 | check_rate_limit 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/eientei/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :eientei 3 | 4 | socket "/socket", Eientei.UserSocket 5 | 6 | # Serve at "/" the static files from "priv/static" directory. 7 | # 8 | # You should set gzip to true if you are running phoenix.digest 9 | # when deploying your static files in production. 10 | plug Plug.Static, 11 | at: "/", from: :eientei, gzip: false, 12 | only: ~w(css fonts images js favicon.ico robots.txt) 13 | 14 | # Code reloading can be explicitly enabled under the 15 | # :code_reloader configuration of your endpoint. 16 | if code_reloading? do 17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 18 | plug Phoenix.LiveReloader 19 | plug Phoenix.CodeReloader 20 | end 21 | 22 | plug Plug.RequestId 23 | plug Plug.Logger 24 | 25 | @max_upload_size Application.get_env(:eientei, :max_upload_size) * 1000 * 1000 26 | 27 | @use_cloudflare Application.get_env(:eientei, :use_cloudflare) 28 | 29 | if @use_cloudflare do 30 | plug Plug.CloudFlare 31 | end 32 | 33 | plug Plug.Parsers, 34 | parsers: [:urlencoded, :multipart, :json], 35 | pass: ["*/*"], 36 | json_decoder: Poison, 37 | length: @max_upload_size 38 | 39 | plug Plug.MethodOverride 40 | plug Plug.Head 41 | 42 | 43 | plug Plug.Session, 44 | store: :cookie, 45 | key: "_eientei_key", 46 | signing_salt: "6ot8uYMj" 47 | 48 | plug Eientei.Router 49 | end 50 | -------------------------------------------------------------------------------- /lib/eientei/rate_limit.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.RateLimit do 2 | import Phoenix.Controller, only: [json: 2] 3 | import Plug.Conn, only: [put_status: 2, halt: 1] 4 | 5 | @interval Application.get_env(:eientei, :rate_interval) * 1000 6 | @max_requests Application.get_env(:eientei, :rate_access_max_requests) 7 | @max_data Application.get_env(:eientei, :rate_data_usage) 8 | 9 | def rate_limit(conn, options \\ []) do 10 | case check_amount_rate(conn) do 11 | {:ok, _count} -> conn 12 | _ -> render_error(conn) 13 | end 14 | end 15 | 16 | defp check_amount_rate(conn) do 17 | ExRated.check_rate(bucket_name(conn), @interval, @max_requests) 18 | end 19 | 20 | def check_data_rate(conn, 0) do 21 | case ExRated.check_rate(data_bucket_name(conn), @interval, @max_data) do 22 | {:ok, _count} -> :ok 23 | _ -> :rate_exceeded 24 | end 25 | end 26 | def check_data_rate(conn, size) do 27 | case ExRated.check_rate(data_bucket_name(conn), @interval, @max_data) do 28 | {:ok, count} -> 29 | check_data_rate(conn, size-1) 30 | _ -> :rate_exceeded 31 | end 32 | end 33 | 34 | # Bucket name should be a combination of ip address and request path 35 | defp bucket_name(conn) do 36 | path = Enum.join(conn.path_info, "/") 37 | ip = conn.remote_ip |> Tuple.to_list |> Enum.join(".") 38 | "#{ip}:#{path}_amount" 39 | end 40 | 41 | defp data_bucket_name(conn) do 42 | path = Enum.join(conn.path_info, "/") 43 | ip = conn.remote_ip |> Tuple.to_list |> Enum.join(".") 44 | "#{ip}:#{path}_size" 45 | end 46 | 47 | defp render_error(conn) do 48 | conn 49 | |> put_status(429) 50 | |> json(%{"success" => false, "reason" => "Maximum upload rate exceeded!"}) 51 | |> halt 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/eientei/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Repo do 2 | use Ecto.Repo, otp_app: :eientei 3 | end 4 | 5 | defmodule Upload do 6 | @moduledoc """ 7 | Represents a file in the database. 8 | """ 9 | use Ecto.Model 10 | schema "uploads" do 11 | field :name, :string 12 | field :location, :string 13 | field :hash, :string 14 | field :filename, :string 15 | field :size, :integer 16 | field :archived_url, :string 17 | timestamps 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/mix/archive_files.ex: -------------------------------------------------------------------------------- 1 | 2 | defmodule Mix.Tasks.Archive.Files do 3 | use Mix.Task 4 | 5 | def run(_) do 6 | import Ecto.Query 7 | 8 | Mix.shell.info "Uploading all unarchived files!" 9 | Mix.Tasks.App.Start.run([]) 10 | # start 11 | query = from u in Eientei.Upload, 12 | select: {u.name, u.location}, 13 | where: is_nil u.archived_url 14 | res = Eientei.Repo.all query 15 | for {name, location} <- res, do: GenServer.call(Archiver, {:archive, name, location}, 500000) 16 | Mix.shell.info "Done!" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :eientei, 6 | version: "0.3.2", 7 | elixir: "~> 1.0", 8 | elixirc_paths: elixirc_paths(Mix.env), 9 | compilers: [:phoenix] ++ Mix.compilers, 10 | build_embedded: Mix.env == :prod, 11 | start_permanent: Mix.env == :prod, 12 | deps: deps] 13 | end 14 | 15 | # Configuration for the OTP application 16 | # 17 | # Type `mix help compile.app` for more information 18 | def application do 19 | [mod: {Eientei, []}, 20 | applications: [:phoenix, :phoenix_html, :cowboy, :logger, :ex_rated, 21 | :phoenix_ecto, :postgrex, :httpoison, :con_cache]] 22 | end 23 | 24 | # Specifies which paths to compile per environment 25 | defp elixirc_paths(:test), do: ["lib", "web", "test/support"] 26 | defp elixirc_paths(_), do: ["lib", "web"] 27 | 28 | # Specifies your project dependencies 29 | # 30 | # Type `mix help deps` for examples and options 31 | defp deps do 32 | [{:phoenix, "~> 1.2.0"}, 33 | {:phoenix_ecto, "~> 1.1"}, 34 | {:postgrex, ">= 0.0.0"}, 35 | {:phoenix_html, "~> 2.6.0"}, 36 | {:phoenix_live_reload, "~> 1.0", only: :dev}, 37 | {:mimerl, "~> 1.0"}, 38 | {:httpoison, "~> 0.7.4"}, 39 | {:pipe, "~> 0.0.2"}, 40 | {:con_cache, "~> 0.9.0"}, 41 | {:ex_rated, "~> 0.0.6"}, 42 | {:plug_cloudflare, ">= 1.2.0"}, 43 | {:cowboy, "~> 1.0"}] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"cidr": {:hex, :cidr, "0.3.0", "1744addc441b8f0641308a93a40abfee4d865828e2de5b553eb6de2f6e08421f", [:mix], [], "hexpm"}, 2 | "con_cache": {:hex, :con_cache, "0.9.0", "a0b8240e1c0b18ac084bf6d4e2ed2e351a8eff5c65e3118e9d3da0cbb4995def", [:mix], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, 5 | "decimal": {:hex, :decimal, "1.1.0", "3333732f17a90ff3057d7ab8c65f0930ca2d67e15cca812a91ead5633ed472fe", [:mix], [], "hexpm"}, 6 | "ecto": {:hex, :ecto, "1.0.6", "c1831d44a9f4124f12306a3bf88c65fdd41cb64be69d389774630983e831304a", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.4.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 1.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.4", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.9.1", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 0.7", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, 7 | "ex2ms": {:hex, :ex2ms, "1.3.0", "8e25696e0891f22c43615428a340aa3785322129ea2cff4b1bc6e0df78bbdc0f", [:mix], [], "hexpm"}, 8 | "ex_rated": {:hex, :ex_rated, "0.0.6", "857b261e0e55c86ead1449fc21dd93507c33a035e72cda32338cbe695664a84e", [:mix], [{:ex2ms, "~> 1.3.0", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"}, 9 | "exactor": {:hex, :exactor, "2.2.0", "2a7418b82d974fe8276edd62c1facf4a9dc06339cdf11b5dcd10359e107fe5c3", [:mix], [], "hexpm"}, 10 | "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"}, 11 | "hackney": {:hex, :hackney, "1.3.2", "43bd07ab88753f5e136e38fddd2a09124bee25733b03361eeb459d0173fc17ab", [:make, :rebar], [{:idna, "~> 1.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:ssl_verify_hostname, "~> 1.0.5", [hex: :ssl_verify_hostname, repo: "hexpm", optional: false]}], "hexpm"}, 12 | "httpoison": {:hex, :httpoison, "0.7.4", "053fa5420c9a2f7792ab49c9963ce67ede8b81dd9a1d0a7123cce54028deeb05", [:mix], [{:hackney, "~> 1.3.1", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, 13 | "idna": {:hex, :idna, "1.0.2", "397e3d001c002319da75759b0a81156bf11849c71d565162436d50020cb7265e", [:make], [], "hexpm"}, 14 | "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"}, 15 | "mimerl": {:hex, :mimerl, "1.0.0", "b9813e0fa1019420da8c8001748d7c34791fd49464cb28762bc22638b1ff6198", [:rebar3], [], "hexpm"}, 16 | "phoenix": {:hex, :phoenix, "1.2.4", "4172479b5e21806a5e4175b54820c239e0d4effb0b07912e631aa31213a05bae", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.4 or ~> 1.3.3 or ~> 1.2.4 or ~> 1.1.8 or ~> 1.0.5", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, 17 | "phoenix_ecto": {:hex, :phoenix_ecto, "1.2.0", "3f261f1822d8fba109c23fa584fe921a7a12f69e4d45bf29aea58b04238e7a19", [:mix], [{:ecto, "~> 1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:poison, "~> 1.3", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, 18 | "phoenix_html": {:hex, :phoenix_html, "2.6.2", "944a5e581b0d899e4f4c838a69503ebd05300fe35ba228a74439e6253e10e0c0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 19 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.1", "5ee31224971f360de89d886c6b0c826092939988d99cb66142cb6758473b567c", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}, {:phoenix, "~> 0.16 or ~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, 20 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], [], "hexpm"}, 21 | "pipe": {:hex, :pipe, "0.0.2", "eff98a868b426745acef103081581093ff5c1b88100f8ff5949b4a30e81d0d9f", [:mix], [], "hexpm"}, 22 | "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, 23 | "plug_cloudflare": {:hex, :plug_cloudflare, "1.2.1", "9c634b0c32eab18b57953eb09fd270415bcc31d3441da8d30b113dd971cc043d", [:mix], [{:cidr, ">= 0.3.0", [hex: :cidr, repo: "hexpm", optional: false]}, {:plug, ">= 0.11.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 24 | "poison": {:hex, :poison, "1.5.2", "560bdfb7449e3ddd23a096929fb9fc2122f709bcc758b2d5d5a5c7d0ea848910", [:mix], [], "hexpm"}, 25 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, 26 | "postgrex": {:hex, :postgrex, "0.9.1", "a7c9d4b3cc70b4b8c73190e0d462e057e22e1597954deb422fc45d5183463d61", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, 27 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, 28 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5", "2e73e068cd6393526f9fa6d399353d7c9477d6886ba005f323b592d389fb47be", [:make], [], "hexpm"}} 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": {}, 3 | "dependencies": { 4 | "babel-brunch": "^5.1.1", 5 | "brunch": "^1.8.5", 6 | "clean-css-brunch": ">= 1.0 < 1.8", 7 | "css-brunch": ">= 1.0 < 1.8", 8 | "javascript-brunch": ">= 1.0 < 1.8", 9 | "uglify-js-brunch": ">= 1.0 < 1.8" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20151021063000_create_uploads.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Repo.Migrations.CreateUploads do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:uploads) do 6 | add :name, :string 7 | add :location, :string 8 | add :hash, :string 9 | add :filename, :string 10 | add :size, :integer 11 | add :archived_url, :string 12 | 13 | timestamps 14 | end 15 | create index(:uploads, [:name], unique: true) 16 | 17 | end 18 | 19 | def down do 20 | drop index(:uploads, [:name], unique: true) 21 | drop table(:uploads) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # Eientei.Repo.insert!(%SomeModel{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /test/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.PageControllerTest do 2 | use Eientei.ConnCase 3 | 4 | test "GET /" do 5 | conn = get conn(), "/" 6 | assert html_response(conn, 200) =~ "Fuwa~" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/models/uploads_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.UploadsTest do 2 | use Eientei.ModelCase 3 | 4 | alias Eientei.Upload 5 | 6 | @valid_attrs %{archived_url: "some content", filename: "some content", hash: "some content", location: "some content", name: "some content", size: 42} 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = Upload.changeset(%Upload{}, @valid_attrs) 11 | assert changeset.valid? 12 | end 13 | 14 | test "changeset with invalid attributes" do 15 | changeset = Upload.changeset(%Upload{}, @invalid_attrs) 16 | refute changeset.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.ChannelCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | channel tests. 5 | 6 | Such tests rely on `Phoenix.ChannelTest` and also 7 | imports other functionality to make it easier 8 | to build and query models. 9 | 10 | Finally, if the test case interacts with the database, 11 | it cannot be async. For this reason, every test runs 12 | inside a transaction which is reset at the beginning 13 | of the test unless the test case is marked as async. 14 | """ 15 | 16 | use ExUnit.CaseTemplate 17 | 18 | using do 19 | quote do 20 | # Import conveniences for testing with channels 21 | use Phoenix.ChannelTest 22 | 23 | alias Eientei.Repo 24 | import Ecto.Model 25 | import Ecto.Query, only: [from: 2] 26 | 27 | 28 | # The default endpoint for testing 29 | @endpoint Eientei.Endpoint 30 | end 31 | end 32 | 33 | setup tags do 34 | unless tags[:async] do 35 | Ecto.Adapters.SQL.restart_test_transaction(Eientei.Repo, []) 36 | end 37 | 38 | :ok 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | imports other functionality to make it easier 8 | to build and query models. 9 | 10 | Finally, if the test case interacts with the database, 11 | it cannot be async. For this reason, every test runs 12 | inside a transaction which is reset at the beginning 13 | of the test unless the test case is marked as async. 14 | """ 15 | 16 | use ExUnit.CaseTemplate 17 | 18 | using do 19 | quote do 20 | # Import conveniences for testing with connections 21 | use Phoenix.ConnTest 22 | 23 | alias Eientei.Repo 24 | import Ecto.Model 25 | import Ecto.Query, only: [from: 2] 26 | 27 | import Eientei.Router.Helpers 28 | 29 | # The default endpoint for testing 30 | @endpoint Eientei.Endpoint 31 | end 32 | end 33 | 34 | setup tags do 35 | unless tags[:async] do 36 | Ecto.Adapters.SQL.restart_test_transaction(Eientei.Repo, []) 37 | end 38 | 39 | :ok 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/support/model_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.ModelCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | model tests. 5 | 6 | You may define functions here to be used as helpers in 7 | your model tests. See `errors_on/2`'s definition as reference. 8 | 9 | Finally, if the test case interacts with the database, 10 | it cannot be async. For this reason, every test runs 11 | inside a transaction which is reset at the beginning 12 | of the test unless the test case is marked as async. 13 | """ 14 | 15 | use ExUnit.CaseTemplate 16 | 17 | using do 18 | quote do 19 | alias Eientei.Repo 20 | import Ecto.Model 21 | import Ecto.Query, only: [from: 2] 22 | import Eientei.ModelCase 23 | end 24 | end 25 | 26 | setup tags do 27 | unless tags[:async] do 28 | Ecto.Adapters.SQL.restart_test_transaction(Eientei.Repo, []) 29 | end 30 | 31 | :ok 32 | end 33 | 34 | @doc """ 35 | Helper for returning list of errors in model when passed certain data. 36 | 37 | ## Examples 38 | 39 | Given a User model that lists `:name` as a required field and validates 40 | `:password` to be safe, it would return: 41 | 42 | iex> errors_on(%User{}, password: "password") 43 | [password: "is unsafe", name: "is blank"] 44 | 45 | You could then write your assertion like: 46 | 47 | assert {:password, "is unsafe"} in errors_on(%User{}, password: "password") 48 | 49 | You can also create the changeset manually and retrieve the errors 50 | field directly: 51 | 52 | iex> changeset = User.changeset(%User{}, password: "password") 53 | iex> {:password, "is unsafe"} in changeset.errors 54 | true 55 | """ 56 | def errors_on(model, data) do 57 | model.__struct__.changeset(model, data).errors 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | 3 | Mix.Task.run "ecto.create", ["--quiet"] 4 | Mix.Task.run "ecto.migrate", ["--quiet"] 5 | Ecto.Adapters.SQL.begin_test_transaction(Eientei.Repo) 6 | 7 | -------------------------------------------------------------------------------- /test/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.ErrorViewTest do 2 | use Eientei.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(Eientei.ErrorView, "404.html", []) == 9 | "Page not found" 10 | end 11 | 12 | test "render 500.html" do 13 | assert render_to_string(Eientei.ErrorView, "500.html", []) == 14 | "Server internal error" 15 | end 16 | 17 | test "render any other" do 18 | assert render_to_string(Eientei.ErrorView, "505.html", []) == 19 | "Server internal error" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.LayoutViewTest do 2 | use Eientei.ConnCase, async: true 3 | end -------------------------------------------------------------------------------- /test/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Eientei.PageViewTest do 2 | use Eientei.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "rooms:*", Eientei.RoomChannel 6 | 7 | ## Transports 8 | transport :websocket, Phoenix.Transports.WebSocket 9 | # transport :longpoll, Phoenix.Transports.LongPoll 10 | 11 | # Socket params are passed from the client and can 12 | # be used to verify and authenticate a user. After 13 | # verification, you can put default assigns into 14 | # the socket that will be set for all channels, ie 15 | # 16 | # {:ok, assign(socket, :user_id, verified_user_id)} 17 | # 18 | # To deny connection, return `:error`. 19 | # 20 | # See `Phoenix.Token` documentation for examples in 21 | # performing token verification on connect. 22 | def connect(_params, socket) do 23 | {:ok, socket} 24 | end 25 | 26 | # Socket id's are topics that allow you to identify all sockets for a given user: 27 | # 28 | # def id(socket), do: "users_socket:#{socket.assigns.user_id}" 29 | # 30 | # Would allow you to broadcast a "disconnect" event and terminate 31 | # all active sockets and channels for a given user: 32 | # 33 | # Eientei.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{}) 34 | # 35 | # Returning `nil` makes this socket anonymous. 36 | def id(_socket), do: nil 37 | end 38 | -------------------------------------------------------------------------------- /web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.PageController do 2 | use Eientei.Web, :controller 3 | 4 | @service_domain Application.get_env(:eientei, :service_domain) 5 | 6 | @use_fallback Application.get_env(:eientei, :fallback_service) 7 | @fallback_url Application.get_env(:eientei, :fallback_service_url) 8 | @max_cache_size Application.get_env(:eientei, :max_cache_size) 9 | 10 | def index(conn, _params) do 11 | render conn, "index.html" 12 | end 13 | 14 | def faq(conn, _params) do 15 | render conn, "faq.html" 16 | end 17 | 18 | def info(conn, _params) do 19 | render conn, "info.html" 20 | end 21 | 22 | def contact(conn, _params) do 23 | render conn, "contact.html" 24 | end 25 | 26 | def tools(conn, _params) do 27 | render conn, "tools.html" 28 | end 29 | 30 | def file(conn, %{"file" => filename}) do 31 | require Pipe 32 | resp =Pipe.pipe_matching val, {:ok, val}, 33 | {:ok, filename} 34 | |> check_cache 35 | |> check_file_loc 36 | |> check_db 37 | |> check_fallback 38 | 39 | send_file_response(resp, conn, filename) 40 | end 41 | 42 | defp send_file_response(:redirect, conn, filename) do 43 | redirect conn, external: "#{@fallback_url}/#{filename}" 44 | end 45 | defp send_file_response({:cached, data}, conn, filename) do 46 | conn 47 | |> put_resp_content_type(Plug.MIME.path(filename)) 48 | |> send_resp(200, data) 49 | end 50 | defp send_file_response({:file, filename, path}, conn, filename) do 51 | Task.start(fn -> cache_file(filename, path) end) 52 | conn 53 | |> put_resp_content_type(Plug.MIME.path(path)) 54 | |> send_file(200, path) 55 | end 56 | defp send_file_response(:no_file, conn, _file) do 57 | conn 58 | |> put_status(404) 59 | |> put_flash(:error, "The file you tried to view does not exist!") 60 | |> render "index.html" 61 | end 62 | 63 | defp cache_file(filename, path) do 64 | ets = ConCache.ets(:file_cache) 65 | size = :ets.info(ets) |> Keyword.get(:size) 66 | if size >= @max_cache_size do 67 | :random.seed(:os.timestamp) 68 | {item, _} = Enum.random(ets |> :ets.tab2list) 69 | ConCache.delete(:file_cache, item) 70 | end 71 | ConCache.put(:file_cache, filename, File.read! path) 72 | end 73 | 74 | defp check_cache(filename) do 75 | case ConCache.get(:file_cache, filename) do 76 | nil -> {:ok, filename} 77 | val -> {:cached, val} 78 | end 79 | end 80 | 81 | defp check_file_loc(filename) do 82 | case File.exists?("files/" <> filename) do 83 | true -> {:file, filename, "files/" <> filename} 84 | false -> {:ok, filename} 85 | end 86 | end 87 | 88 | defp check_db(filename) do 89 | case Eientei.Repo.get_by(Eientei.Upload, name: filename) do 90 | nil -> {:ok, filename} 91 | %{location: path} -> {:file, filename, path} 92 | end 93 | end 94 | 95 | defp check_fallback(filename) when @use_fallback do 96 | {:ok, fallback_resp} = HTTPoison.get("#{@fallback_url}/#{filename}") 97 | case fallback_resp do 98 | %{status_code: 200} -> :redirect 99 | %{status_code: 301} -> :redirect 100 | %{status_code: 302} -> :redirect 101 | %{status_code: _status} -> :no_file 102 | end 103 | end 104 | defp check_fallback(_file) do 105 | :no_file 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /web/controllers/upload_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.UploadController do 2 | use Eientei.Web, :controller 3 | 4 | @max_file_size Application.get_env(:eientei, :max_file_size) 5 | @max_cache_size Application.get_env(:eientei, :max_cache_size) 6 | @use_ia Application.get_env(:eientei, :use_ia_archive) 7 | 8 | @service_url Application.get_env(:eientei, :service_url) 9 | 10 | @illegal_exts [".ade", ".adp", ".bat", ".chm", ".cmd", ".com", ".cpl", ".exe", ".hta", ".ins", ".isp", ".jse", ".lib", ".lnk", ".mde", ".msc", ".msp", ".mst", ".pif", ".scr", ".sct", ".shb", ".sys", ".vb", ".vbe", ".vbs", ".vxd", ".wsc", ".wsf", ".wsh"] 11 | @name_len 6 12 | 13 | def upload(conn, %{"file" => file}) when is_list(file) do 14 | json conn, failure("You can only send individual files using the file param!") 15 | end 16 | def upload(conn, %{"file" => file}) do 17 | json conn, %{"file" => upload_file({file, conn})} 18 | end 19 | 20 | def upload(conn, %{"files" => files}) when is_list(files) do 21 | cfiles = Enum.map(files, &({&1, conn})) 22 | json conn, %{"files" => Enum.map(cfiles, &upload_file/1)} 23 | end 24 | def upload(conn, %{"files" => _files}) do 25 | json conn, failure("You can only send a list of files with the files param!") 26 | end 27 | 28 | def upload(conn, _params) do 29 | put_status conn, 400 30 | json conn, failure("Please send files using the file or files key!") 31 | end 32 | 33 | defp failure(reason), do: %{"success" => false, "reason" => reason} 34 | defp file_success(name, old_name), do: %{"url" => "#{@service_url}/f/#{name}", "name" => old_name, "success" => true} 35 | defp file_failure(name, reason), do: %{"name" => name, "success" => false, "reason" => reason} 36 | 37 | @doc """ 38 | Perform size, rate, and file type checks, on a file, 39 | then generates an unused file name, moves the file, 40 | and commits the transaction to the database. 41 | """ 42 | defp upload_file({file, conn}) do 43 | res = check_file({file, conn}) 44 | case res do 45 | %{"success" => false} -> res 46 | file -> 47 | file 48 | |> gen_name 49 | |> move_file 50 | |> generate_db_entry 51 | end 52 | end 53 | 54 | defp check_file({file, conn}) do 55 | use Pipe 56 | Pipe.pipe_matching val, {:ok, val}, 57 | {:ok, file} 58 | |> check_file_size 59 | |> check_magic_number 60 | |> check_data_rate(conn) 61 | end 62 | 63 | defp check_file_size(file) do 64 | %{size: size} = File.stat! file.path 65 | case size > @max_file_size * 1000 * 1000 do 66 | false -> file 67 | true -> file_failure file.filename, "File too big!" 68 | end 69 | end 70 | 71 | defp check_magic_number(file) do 72 | b = get_init_bytes(file.path) 73 | case is_exe(b) do 74 | false -> {:ok, file} 75 | true -> file_failure file.filename, "Exe's are not allowed!" 76 | end 77 | end 78 | 79 | defp get_init_bytes(path) do 80 | path 81 | |> File.stream! 82 | |> Enum.take(1) 83 | |> hd 84 | end 85 | 86 | defp is_exe(<<77::8, 90::8, _rest::binary>>), do: true 87 | defp is_exe(_data), do: false 88 | 89 | defp check_data_rate(file, conn) do 90 | %{size: size} = File.stat! file.path 91 | case Eientei.RateLimit.check_data_rate(conn, div(size, (1000 * 1000))) do 92 | :ok -> {:ok, file} 93 | :rate_exceeded -> file_failure file.filename, "You have exceeded the maximum rate of data uploaded" 94 | end 95 | end 96 | 97 | defp gen_name(file) do 98 | name = get_rand_chars(@name_len) 99 | loc = "files/" <> name 100 | case File.exists?(loc) do 101 | true -> gen_name(file) 102 | false -> check_db_names(file, name) 103 | end 104 | end 105 | 106 | defp check_db_names(file, name) do 107 | case Eientei.Repo.get_by(Eientei.Upload, name: name) do 108 | nil -> {file, name} 109 | _ -> gen_name(file) 110 | end 111 | end 112 | 113 | defp get_rand_chars(length) do 114 | :crypto.strong_rand_bytes(length) 115 | |> Base.url_encode64 116 | |> binary_part(0, length) 117 | end 118 | 119 | defp move_file({file, new_name}) do 120 | path = file.path 121 | old_name = file.filename 122 | name = new_name <> Path.extname(old_name) 123 | new_path = "files/" <> name 124 | # Move file, since Elixir seems to not provide an equivalent 125 | System.cmd("mv", [path, new_path]) 126 | {name, new_path, old_name} 127 | end 128 | 129 | defp generate_db_entry({name, loc, orig_name}) do 130 | r = File.read! loc 131 | hash = md5(r) 132 | Task.start(fn -> update_cache(name, r) end) 133 | %{size: size} = File.stat! loc 134 | new_loc = remove_duplicate_file(hash, loc) 135 | add_db_entry(name, new_loc, hash, orig_name, size) 136 | end 137 | 138 | defp md5(data) do 139 | :erlang.md5(data) 140 | |> :erlang.bitstring_to_list 141 | |> Enum.map(&(:io_lib.format("~2.16.0b", [&1]))) 142 | |> List.flatten 143 | |> :erlang.list_to_bitstring 144 | end 145 | 146 | defp update_cache(file, name) do 147 | ets = ConCache.ets(:file_cache) 148 | size = :ets.info(ets) |> Keyword.get(:size) 149 | if size >= @max_cache_size do 150 | # High quality algorithms right here 151 | :random.seed(:os.timestamp) 152 | {item, _} = Enum.random(ets |> :ets.tab2list) 153 | ConCache.delete(:file_cache, item) 154 | end 155 | ConCache.put(:file_cache, name, file) 156 | end 157 | 158 | defp remove_duplicate_file(hash, location) do 159 | import Ecto.Query, only: [from: 2] 160 | query = from u in Eientei.Upload, 161 | where: u.hash == ^hash, 162 | select: u.location, 163 | limit: 1 164 | case Eientei.Repo.one(query) do 165 | nil -> location 166 | existing_location -> 167 | File.rm! location 168 | existing_location 169 | end 170 | end 171 | 172 | defp add_db_entry(full_name, location, hash, orig_name, size) do 173 | changeset = Eientei.Upload.changeset(%Eientei.Upload{}, %{:name => full_name, :location => location, :hash => hash, :filename => orig_name, :size => size}) 174 | {:ok, _user} = Eientei.Repo.insert(changeset) 175 | file_success full_name, orig_name 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /web/models/uploads.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Upload do 2 | use Eientei.Web, :model 3 | 4 | schema "uploads" do 5 | field :name, :string 6 | field :location, :string 7 | field :hash, :string 8 | field :filename, :string 9 | field :size, :integer 10 | field :archived_url, :string 11 | 12 | timestamps 13 | end 14 | 15 | @required_fields ~w(name location hash filename size) 16 | @optional_fields ~w(archived_url) 17 | 18 | @doc """ 19 | Creates a changeset based on the `model` and `params`. 20 | 21 | If no params are provided, an invalid changeset is returned 22 | with no validation performed. 23 | """ 24 | def changeset(model, params \\ :empty) do 25 | model 26 | |> cast(params, @required_fields, @optional_fields) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Router do 2 | use Eientei.Web, :router 3 | import Eientei.RateLimit 4 | 5 | pipeline :browser do 6 | plug :accepts, ["html"] 7 | plug :fetch_session 8 | plug :fetch_flash 9 | plug :put_secure_browser_headers 10 | end 11 | 12 | pipeline :csrf do 13 | plug :protect_from_forgery 14 | end 15 | 16 | pipeline :api do 17 | plug :rate_limit 18 | plug :accepts, ["json"] 19 | end 20 | 21 | scope "/", Eientei do 22 | pipe_through [:browser, :csrf] 23 | 24 | get "/", PageController, :index 25 | get "/faq", PageController, :faq 26 | get "/contact", PageController, :contact 27 | get "/tools", PageController, :tools 28 | end 29 | 30 | @use_csrf_protection Application.get_env(:eientei, :use_csrf_protection) 31 | scope "/", Eientei do 32 | if @use_csrf_protection do 33 | pipe_through [:browser, :csrf] 34 | else 35 | pipe_through [:browser] 36 | end 37 | 38 | # Let's not break compat quite yet 39 | get "/:file", PageController, :file 40 | get "/f/:file", PageController, :file 41 | get "/:file/:rn", PageController, :file 42 | end 43 | 44 | scope "/api/", Eientei do 45 | pipe_through :api 46 | post "upload", UploadController, :upload 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /web/static/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luminarys/Eientei/db10dbc73f524a680e9cb44cde98e976ef5c9df4/web/static/assets/favicon.ico -------------------------------------------------------------------------------- /web/static/assets/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /web/static/css/app.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit 2 | body, form, ul, table { 3 | margin-top: 20px; 4 | margin-bottom: 20px; 5 | } 6 | /**/ 7 | 8 | *:before, 9 | *:after { 10 | -webkit-box-sizing: border-box; 11 | -moz-box-sizing: border-box; 12 | box-sizing: border-box; 13 | } 14 | /* Phoenix flash messages */ 15 | .alert:empty { display: none; } 16 | 17 | /* Customize container */ 18 | @media (min-width: 768px) { 19 | .container { 20 | max-width: 700px; 21 | } 22 | } 23 | 24 | body { 25 | background-color: #F7F7F7; 26 | color: #333; 27 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 28 | font-size: 14px; 29 | height: 100%; 30 | line-height: 20px; 31 | padding-top: 20px; 32 | padding-bottom: 40px; 33 | margin: 0; 34 | } 35 | 36 | .container { 37 | margin: 0 auto; 38 | } 39 | 40 | /* links */ 41 | a { 42 | color: #0078B4; 43 | text-decoration: none !important; 44 | transition: color 0.25s; 45 | } 46 | a:hover, 47 | a:focus, 48 | a:active { 49 | color: #005580; 50 | } 51 | a:focus { 52 | outline: thin dotted #333; 53 | /* outline-offset: -2px;*/ 54 | } 55 | 56 | /* Main marketing message */ 57 | #jumbotron { 58 | text-align: center; 59 | margin: 30px 0; 60 | } 61 | 62 | #jumbotron h1 { 63 | color: inherit; 64 | font-family: inherit; 65 | font-size: 72px; 66 | font-weight: bold; 67 | line-height: 1; 68 | margin: 10px 0; 69 | cursor: default; 70 | text-rendering: optimizelegibility; 71 | } 72 | 73 | #jumbotron .target { 74 | background: rgba(202, 230, 190, 0.75); 75 | border: 1px solid #B7D1A0; 76 | border-radius: 4px; 77 | color: #468847; 78 | cursor: pointer; 79 | display: inline-block; 80 | font-size: 24px; 81 | padding: 28px 48px; 82 | text-shadow: 0 1px rgba(255, 255, 255, 0.5); 83 | transition: background-color 0.25s, width 0.5s, height 0.5s; 84 | } 85 | #jumbotron .target:hover, 86 | #jumbotron .target:active, 87 | #jumbotron .target:focus, 88 | #jumbotron .target.drop { 89 | background-color: rgb(188, 228, 170); 90 | text-decoration: none; 91 | } 92 | 93 | .alert { 94 | padding: 10px !important; 95 | } 96 | 97 | 98 | /** 99 | * NAVIGATION LINKS 100 | */ 101 | nav > ul, 102 | nav a { 103 | color: #33799B; 104 | list-style: none; 105 | margin: 0; 106 | padding: 0; 107 | text-align: center; 108 | } 109 | nav > ul > li { 110 | display: inline-block; 111 | margin: 0; 112 | padding: 0; 113 | cursor: default; 114 | } 115 | nav > ul > li:after { 116 | content: "|"; 117 | margin: 0 8px; 118 | opacity: 0.3; 119 | } 120 | nav > ul > li:last-child:after { 121 | content: ""; 122 | margin: 0; 123 | } 124 | -------------------------------------------------------------------------------- /web/static/css/bootstrap.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | /*! normalize.css v3.0.0 | MIT License | git.io/normalize */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,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:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.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:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-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]{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;-moz-box-sizing:content-box;-webkit-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 silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!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) ")"}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}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!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:62.5%;-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:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;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:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .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 #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}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:400;line-height:1;color:#999}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:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}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:700}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 #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}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:#999}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 #eee;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'}blockquote:before,blockquote:after{content:""}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;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}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:#333;background-color:#f5f5f5;border:1px solid #ccc;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:0}.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:0}.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:0}.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:0}.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:0}.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:0}.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:0}.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:0}.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{max-width:100%;background-color:transparent}th{text-align:left}.table{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 #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.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 #ddd}.table .table{background-color:#fff}.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 #ddd}.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 #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.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 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.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.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.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.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.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.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:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}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:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-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,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.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}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(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{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(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{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(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}.form-control-static{margin-bottom: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 .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;padding-left:0;vertical-align:middle}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.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}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;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{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.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: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:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.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: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:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.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: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:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.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: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:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.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: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:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.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: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:#fff}.btn-link{color:#428bca;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.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:#2a6496;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:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;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%;padding-left:0;padding-right:0}.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 .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.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;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);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:400;line-height:1.42857143;color:#333;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:#fff;text-decoration:none;outline:0;background-color:#428bca}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.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:#999}.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 solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@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:focus,.btn-group-vertical>.btn:focus{outline:0}.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-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>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-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,.125);box-shadow:inset 0 3px 5px rgba(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%}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{display: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.33;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:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;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{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:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.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 #ddd}.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:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;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 #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;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:#fff}}.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:#fff;background-color:#428bca}.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 #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;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:#fff}}.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{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;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}}.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;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@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;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-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.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,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.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 .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;padding-left:0;vertical-align:middle}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@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-form.navbar-right:last-child{margin-right:-15px}}.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{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}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.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:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;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:#555;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:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.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:#fff}@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:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;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:#fff;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:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.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:#ccc}.breadcrumb>.active{color:#999}.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:#428bca;background-color:#fff;border:1px solid #ddd;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{color:#2a6496;background-color:#eee;border-color:#ddd}.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:#fff;background-color:#428bca;border-color:#428bca;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:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.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}.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:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.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:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:gray}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.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:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .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:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .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:#428bca}.thumbnail .caption{padding:9px;color:#333}.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:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .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}}@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,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-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,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.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,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.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,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.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,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;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{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:#fff;border:1px solid #ddd}.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}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.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{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.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{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.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{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.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:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(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{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.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{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.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 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 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{border-top:1px solid #ddd}.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;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.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-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-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-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-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.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,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(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:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;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%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.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:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;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}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.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:#fff;text-align:center;text-decoration:none;background-color:#000;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:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.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;font-weight:400;line-height:18px;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:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{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:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(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,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(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:#fff;text-decoration:none;opacity:.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%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;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 #fff;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:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(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;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix: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,.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;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!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}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}@media print{.hidden-print{display:none!important}} 8 | -------------------------------------------------------------------------------- /web/static/js/FileSelector.js: -------------------------------------------------------------------------------- 1 | import FileUpload from "./FileUpload"; 2 | import UploadButton from "./UploadButton"; 3 | 4 | export default React.createClass({ 5 | getInitialState: function() { 6 | return {files: []}; 7 | }, 8 | getFileArray: function(files) { 9 | let fileArr = []; 10 | const len = files.length; 11 | for (let i = 0; i < len; i++) { 12 | fileArr[i] = files[i]; 13 | } 14 | return fileArr; 15 | }, 16 | handleFilesSelected: function(event) { 17 | const files = this.getFileArray(event.target.files); 18 | this.setState({files: files}); 19 | }, 20 | handleDrop: function(event) { 21 | const files = this.getFileArray(event.dataTransfer.files); 22 | this.setState({files: files}); 23 | }, 24 | render: function() { 25 | return ( 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | { 38 | this.state.files.map(function(file, ind) { 39 | return ( 40 | 41 | ); 42 | }, this) 43 | } 44 | 45 |
46 |
47 | ); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /web/static/js/FileUpload.js: -------------------------------------------------------------------------------- 1 | export default React.createClass({ 2 | getInitialState: function() { 3 | return {percent: 0, complete: false}; 4 | }, 5 | handleUploadProgress: function (event) { 6 | if (event.lengthComputable) { 7 | const progressPercent = Math.floor((event.loaded / event.total) * 100); 8 | this.setState({percent: progressPercent}); 9 | } 10 | }, 11 | handleUploadComplete: function(event) { 12 | const xhr = event.target; 13 | switch (xhr.status) { 14 | case 200: 15 | const resp = JSON.parse(xhr.responseText).file; 16 | if (resp.success) { 17 | this.setState({complete: true, message: resp.url, url: resp.url}); 18 | } else { 19 | this.setState({complete: true, error: true, message: resp.reason}); 20 | } 21 | break; 22 | case 413: 23 | this.setState({complete: true, error: true, message: "I-it's too big Onii-chan! Use a smaller file!"}); 24 | break; 25 | case 429: 26 | this.setState({complete: true, error: true, message: "I-it's too much Onii-chan! Stop uploading so much!"}); 27 | break; 28 | default: 29 | this.setState({complete: true, error: true, message: "S-something went wrong!!"}); 30 | break; 31 | } 32 | }, 33 | componentDidMount: function() { 34 | const xhr = new XMLHttpRequest(); 35 | xhr.open('POST', '/api/upload'); 36 | 37 | xhr.addEventListener('load', this.handleUploadComplete, false); 38 | xhr.upload.onprogress = this.handleUploadProgress; 39 | 40 | const form = new FormData(); 41 | form.append('file', this.props.file); 42 | xhr.send(form); 43 | }, 44 | render: function() { 45 | let bar = null; 46 | // This is a bit ugly, might want to make it its own component 47 | if (!this.state.complete) { 48 | bar =
49 |
50 | {this.state.percent + '%'} 51 |
52 |
; 53 | } else { 54 | if (this.state.error) { 55 | bar = this.state.message; 56 | } else { 57 | bar = {this.state.message}; 58 | } 59 | } 60 | return ( 61 | 62 | 63 | {this.props.name} 64 | 65 | 66 | {bar} 67 | 68 | 69 | ); 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /web/static/js/UploadButton.js: -------------------------------------------------------------------------------- 1 | export default React.createClass({ 2 | getInitialState: function() { 3 | return {message: 'Select or drop file(s)'}; 4 | }, 5 | stopEvent: function(event) { 6 | event.stopPropagation(); 7 | event.preventDefault(); 8 | }, 9 | handleClick: function(event) { 10 | this.stopEvent(event); 11 | document.getElementById('browse').click(); 12 | }, 13 | handleDragEnter: function(event) { 14 | this.stopEvent(event); 15 | this.setState({message: 'Drop it here!'}); 16 | }, 17 | handleDragLeave: function(event) { 18 | this.stopEvent(event); 19 | this.setState({message: 'Select or drop file(s)!'}); 20 | }, 21 | handleDrop: function(event) { 22 | this.stopEvent(event); 23 | this.props.onDrop(event); 24 | }, 25 | handleDragOver: function(event) { 26 | this.stopEvent(event); 27 | }, 28 | render: function() { 29 | return ( 30 |
36 | {this.state.message} 37 |
38 | ); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /web/static/js/app.js: -------------------------------------------------------------------------------- 1 | import 'deps/phoenix_html/web/static/js/phoenix_html'; 2 | import FileSelector from "./FileSelector"; 3 | 4 | ReactDOM.render( 5 | , 6 | document.getElementById('uploadForm') 7 | ); 8 | 9 | /* 10 | paste.addEventListener('keydown', function() { 11 | pasteBtn.style.display = 'block'; 12 | }); 13 | pasteBtn.addEventListener('click', function(e) { 14 | e.preventDefault(); 15 | var blob = new Blob([paste.value], {type: 'text/plain'}); 16 | var file = new File([blob], 'paste.txt'); 17 | var progress = addRow(file); 18 | uploadFile(file, progress); 19 | }); 20 | /**/ 21 | -------------------------------------------------------------------------------- /web/static/js/socket.js: -------------------------------------------------------------------------------- 1 | // NOTE: The contents of this file will only be executed if 2 | // you uncomment its entry in "web/static/js/app.js". 3 | 4 | // To use Phoenix channels, the first step is to import Socket 5 | // and connect at the socket path in "lib/my_app/endpoint.ex": 6 | import {Socket} from "deps/phoenix/web/static/js/phoenix" 7 | 8 | let socket = new Socket("/socket") 9 | 10 | // When you connect, you'll often need to authenticate the client. 11 | // For example, imagine you have an authentication plug, `MyAuth`, 12 | // which authenticates the session and assigns a `:current_user`. 13 | // If the current user exists you can assign the user's token in 14 | // the connection for use in the layout. 15 | // 16 | // In your "web/router.ex": 17 | // 18 | // pipeline :browser do 19 | // ... 20 | // plug MyAuth 21 | // plug :put_user_token 22 | // end 23 | // 24 | // defp put_user_token(conn, _) do 25 | // if current_user = conn.assigns[:current_user] do 26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id) 27 | // assign(conn, :user_token, token) 28 | // else 29 | // conn 30 | // end 31 | // end 32 | // 33 | // Now you need to pass this token to JavaScript. You can do so 34 | // inside a script tag in "web/templates/layout/app.html.eex": 35 | // 36 | // 37 | // 38 | // You will need to verify the user token in the "connect/2" function 39 | // in "web/channels/user_socket.ex": 40 | // 41 | // def connect(%{"token" => token}, socket) do 42 | // # max_age: 1209600 is equivalent to two weeks in seconds 43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do 44 | // {:ok, user_id} -> 45 | // {:ok, assign(socket, :user, user_id)} 46 | // {:error, reason} -> 47 | // :error 48 | // end 49 | // end 50 | // 51 | // Finally, pass the token on connect as below. Or remove it 52 | // from connect if you don't care about authentication. 53 | 54 | socket.connect({token: window.userToken}) 55 | 56 | // Now that you are connected, you can join channels with a topic: 57 | let channel = socket.channel("topic:subtopic", {}) 58 | channel.join() 59 | .receive("ok", resp => { console.log("Joined successfully", resp) }) 60 | .receive("error", resp => { console.log("Unable to join", resp) }) 61 | 62 | export default socket 63 | -------------------------------------------------------------------------------- /web/templates/layout/app.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Fuwa Fuwa~ 11 | "> 12 | 13 | 14 | 15 |
16 | <%= render @view_module, @view_template, assigns %> 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web/templates/page/contact.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Info

4 | <%= render Eientei.SharedView, "nav.html" %> 5 |
6 |
    7 |
  • Please contact me at <%= contact_email %> if you have any questions or concerns.
  • 8 |
  • If you wish to send me any DMCA requests or report any otherwise illegal material please contact me at abuse@<%= service_domain %>
  • 9 |
  • If you want to chat or discuss development of Fuwa, you can join #fuwa on Rizon. If you're interested in the development of pomf clones and general file hosting, join #pomfret on Rizon.
  • 10 |
11 |
12 | -------------------------------------------------------------------------------- /web/templates/page/faq.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 |

FAQ

4 | <%= render Eientei.SharedView, "nav.html" %> 5 |
6 |

What is <%= service_name %>?

7 |
    8 |
  • <%= service_name %> is a free file hosting service powered by Eientei.
  • 9 |
  • <%= service_name %> does NOT support Internet Explorer and older versions of Firefox, Chrome, Opera and Safari.
  • 10 |
  • We currently are hosting <%= get_file_count %> uploads totalling <%= get_total_file_size %> megabytes in size.
  • 11 |
12 |

How much can I upload?

13 |
    14 |
  • The max file size is <%= max_file_size %>MB, and you can upload up to <%= max_upload_size %> MB at a time using the API.
  • 15 |
16 |

What can I upload?

17 |
    18 |
  • No child porn, malware, or anything illegal under French laws.
  • 19 |
  • No .exe files.
  • 20 |
21 |

What data is logged by <%= service_name %>?

22 |
    23 |
  • No data is logged by us.
  • 24 |
  • IPs are held temporarily in application memory to allow for rate-limiting of uploads.
  • 25 |
  • If any changes to these policies happen you will be informed about them.
  • 26 |
27 |

What are <%= service_name %>'s transparency policies?

28 |
    29 |
  • This service will be kept as transparent as possible.
  • 30 |
  • All removed files will be publically listed as well as a copy of the DMCA notice.
  • 31 |
  • For more information please visit the transparency information page.
  • 32 |
33 |
34 | -------------------------------------------------------------------------------- /web/templates/page/index.html.eex: -------------------------------------------------------------------------------- 1 |
2 |

Fuwa~

3 |

Max file size is <%= max_file_size %> MB.

4 |
5 |
Select or drop file(s)
6 |
7 |
8 | 9 | 10 | 11 |

Virus scans are a daily routine — if a file gets detected it will be removed without further notice.

12 | <%= if use_ia do %> 13 |

Files are routinely archived — this happens on a monthly basis, and you can find files here.

14 | <%= end %> 15 | <%= if fallback_alert do %> 16 |

We've changed sites — you can find the old one at <%= fallback_homepage %>.

17 | <%= end %> 18 | <%= render Eientei.SharedView, "nav.html" %> 19 |
20 | 21 | -------------------------------------------------------------------------------- /web/templates/page/info.html.eex: -------------------------------------------------------------------------------- 1 |
2 |

Info

3 | <%= render Eientei.SharedView, "nav.html" %> 4 |
5 |
    6 |
  • <%= service_name %> is a free file hosting service powered by Eientei.
  • 7 |
  • We currently are hosting <%= get_file_count %> uploads totalling <%= get_total_file_size %> megabytes in size.
  • 8 |
9 | -------------------------------------------------------------------------------- /web/templates/page/tools.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Tools

4 | <%= render Eientei.SharedView, "nav.html" %> 5 |
6 |
7 |

fuwa-ul

8 |
9 |
Download
10 |
https://github.com/Luminarys/fuwa-tools
11 |
Contact
12 |
https://github.com/Luminarys
13 |
14 |
15 |
16 |

desktop-capture

17 |
18 |
Download
19 |
https://github.com/Luminarys/fuwa-tools
20 |
Contact
21 |
https://github.com/Luminarys
22 |
23 |
24 |
25 |

window-capture

26 |
27 |
Download
28 |
https://github.com/Luminarys/fuwa-tools
29 |
Contact
30 |
https://github.com/Luminarys
31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /web/templates/shared/nav.html.eex: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.ErrorView do 2 | use Eientei.Web, :view 3 | 4 | def render("404.html", _assigns) do 5 | "File not found!" 6 | end 7 | 8 | def render("500.html", _assigns) do 9 | "Server internal error" 10 | end 11 | 12 | # In case no render clause matches or no 13 | # template is found, let's render it as 500 14 | def template_not_found(_template, assigns) do 15 | render "500.html", assigns 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.LayoutView do 2 | use Eientei.Web, :view 3 | end 4 | -------------------------------------------------------------------------------- /web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.PageView do 2 | use Eientei.Web, :view 3 | 4 | defp service_name, do: Application.get_env(:eientei, :service_name) 5 | defp service_domain, do: Application.get_env(:eientei, :service_domain) 6 | defp contact_email, do: Application.get_env(:eientei, :contact_email) 7 | defp max_upload_size, do: Application.get_env(:eientei, :max_upload_size) 8 | defp max_file_size, do: Application.get_env(:eientei, :max_file_size) 9 | 10 | defp use_ia, do: Application.get_env(:eientei, :use_ia_archive) 11 | defp ia_service_name, do: Application.get_env(:eientei, :ia_service_name) 12 | 13 | defp fallback_alert, do: Application.get_env(:eientei, :fallback_service_alert) 14 | defp fallback_homepage, do: Application.get_env(:eientei, :fallback_service_home_page) 15 | 16 | def get_total_file_size do 17 | import Ecto.Query, only: [from: 2] 18 | size_query = from u in Eientei.Upload, 19 | distinct: u.location, 20 | select: u.size 21 | sizes = Eientei.Repo.all(size_query) 22 | Float.floor(Enum.reduce(sizes, 0, &(&1/(1000*1000) + &2)), 1) 23 | end 24 | 25 | def get_file_count do 26 | import Ecto.Query, only: [from: 2] 27 | count_query = from u in Eientei.Upload, 28 | select: count(u.id) 29 | Eientei.Repo.one(count_query) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /web/views/shared_view.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.SharedView do 2 | use Eientei.Web, :view 3 | 4 | defp git_repo_url, do: Application.get_env(:eientei, :git_repo_url) 5 | end 6 | -------------------------------------------------------------------------------- /web/web.ex: -------------------------------------------------------------------------------- 1 | defmodule Eientei.Web do 2 | @moduledoc """ 3 | A module that keeps using definitions for controllers, 4 | views and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use Eientei.Web, :controller 9 | use Eientei.Web, :view 10 | 11 | The definitions below will be executed for every view, 12 | controller, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. 17 | """ 18 | 19 | def model do 20 | quote do 21 | use Ecto.Model 22 | 23 | end 24 | end 25 | 26 | def controller do 27 | quote do 28 | use Phoenix.Controller 29 | 30 | alias Eientei.Repo 31 | import Ecto.Model 32 | import Ecto.Query, only: [from: 1, from: 2] 33 | 34 | import Eientei.Router.Helpers 35 | end 36 | end 37 | 38 | def view do 39 | quote do 40 | use Phoenix.View, root: "web/templates" 41 | 42 | # Import convenience functions from controllers 43 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] 44 | 45 | # Use all HTML functionality (forms, tags, etc) 46 | use Phoenix.HTML 47 | 48 | import Eientei.Router.Helpers 49 | end 50 | end 51 | 52 | def router do 53 | quote do 54 | use Phoenix.Router 55 | end 56 | end 57 | 58 | def channel do 59 | quote do 60 | use Phoenix.Channel 61 | 62 | alias Eientei.Repo 63 | import Ecto.Model 64 | import Ecto.Query, only: [from: 1, from: 2] 65 | 66 | end 67 | end 68 | 69 | @doc """ 70 | When used, dispatch to the appropriate controller/view/etc. 71 | """ 72 | defmacro __using__(which) when is_atom(which) do 73 | apply(__MODULE__, which, []) 74 | end 75 | end 76 | --------------------------------------------------------------------------------