diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 00000000..8a6391c6 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,5 @@ +[ + import_deps: [:ecto, :phoenix], + inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"], + subdirectories: ["priv/*/migrations"] +] diff --git a/.gitignore b/.gitignore index 076d05cd..4d1577b2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,23 @@ /deps /*.ez +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# Ignore package tarball (built via "mix hex.build"). +app-*.tar + # Generated on crash by the VM erl_crash.dump # Static artifacts -/node_modules +node_modules +node_modules/* +npm-debug.log +assets/package-lock.json # Since we are building assets from web/static, # we ignore priv/static. You may want to comment @@ -21,7 +33,7 @@ erl_crash.dump # Alternatively, you may comment the line below and commit the # secrets file as long as you replace its contents by environment # variables. -/config/prod.secret.exs +# /config/prod.secret.exs lib-cov *.seed @@ -36,9 +48,8 @@ pids logs results -npm-debug.log -node_modules .DS_Store .vagrant test/coverage.html coverage/ +cover/ diff --git a/.travis.yml b/.travis.yml index 136dcb9b..b472f501 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,22 @@ -language: node_js -node_js: - - 0.12 +language: elixir +elixir: + - 1.10.3 +otp_release: + - 22.1.8 services: - - elasticsearch + - postgresql +cache: + directories: + - _build + - deps +script: + - mix do setup, compile --warnings-as-errors, coveralls.json +after_success: +- bash <(curl -s https://codecov.io/bash) env: - - ES_HOST="127.0.0.1" ES_PORT=9200 ES_INDEX=travis JWT_SECRET="EverythingIsAwesome!" + global: + - MIX_ENV=test +cache: + directories: + - _build + - deps diff --git a/Procfile b/Procfile index 489b2700..2271dc84 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: node server.js +web: MIX_ENV=prod mix do ecto.migrate, phx.server diff --git a/README.md b/README.md index bcb2abe4..19731595 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,2112 @@ -# Phoenix Todo List Tutorial! +
+# Phoenix Todo List Tutorial +A complete beginners step-by-step tutorial +for building a Todo List in Phoenix.
+100% functional. 0% JavaScript. Just `HTML`, `CSS` and `Elixir`. +Fast and maintainable. -![perfection-intro-image](https://cloud.githubusercontent.com/assets/194400/8255483/2fc78e6c-1698-11e5-8c27-d1b9db99f020.png) +[![Build Status](https://img.shields.io/travis/dwyl/phoenix-todo-list-tutorial/master.svg?style=flat-square)](https://travis-ci.org/dwyl/phoenix-todo-list-tutorial) +[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/phoenix-todo-list-tutorial/master.svg?style=flat-square)](http://codecov.io/github/dwyl/phoenix-todo-list-tutorial?branch=master) +[![HitCount](http://hits.dwyl.com/dwyl/phoenix-todo-list-tutorial.svg)](http://hits.dwyl.com/dwyl/phoenix-todo-list-tutorial) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/phoenix-todo-list-tutorial/issues) +
+
-Comming Soon!! +## Why? 🤷‍ + +Todo lists are familiar to most people; +we make lists all the time. +_Building_ a Todo list from scratch is a great way to learn Elixir/Phoenix +because the UI/UX is simple, +so we can focus on implementation. + +For the team +[**`@dwyl`**](https://github.com/dwyl) +this app/tutorial +is a showcase of how server side rendering +(_with client side progressive enhancement_) +can provide a excellent balance between +developer effectiveness (_shipping features fast_), +UX and _accessibility_. +The server rendered pages take less than 5ms to respond +so the UX is _fast_. +On Heroku (_after the "Free" App wakes up!_), +round-trip response times are sub 100ms for all interactions, +so it _feels_ like a client-side rendered App. + + +
+ +## What? 💭 + +A Todo list tutorial +that shows a complete beginner +how to build an app in Elixir/Phoenix +from scratch. + + +### Try it on Heroku: [phxtodo.herokuapp.com](https://phxtodo.herokuapp.com) + +Try the Heroku version. +Add a few items to the list and test the functionality. + +![todo-app-heroku-version](https://user-images.githubusercontent.com/194400/83279718-6e5a4a00-a1cd-11ea-90b3-f0b29a898b3d.gif) + +Even with a full HTTP round-trip for each interaction, +the response time is _fast_. +Pay attention to how Chrome|Firefox|Safari +waits for the response from the server before re-rendering the page. +The old full page refresh of yesteryear is _gone_. +Modern browsers intelligently render just the changes! +So the UX approximates "native"! +Seriously, try the Heroku app on your Phone and see! + + +### TodoMVC + +In this tutorial +we are using the +[TodoMVC](https://github.com/dwyl/javascript-todo-list-tutorial#todomvc) +CSS to simplify our UI. +This has several advantages +the biggest being _minimising_ how much CSS we have to write! +It also means we have a guide to which _features_ +need to be implemented to achieve full functionality. + +> **Note**: we _love_ `CSS` for its incredible power/flexibility, +but we know that not everyone like it. +see: [learn-tachyons#why](https://github.com/dwyl/learn-tachyons#why) +The _last_ thing we want is to waste tons of time +with `CSS` in a `Phoenix` tutorial! + + +
+ +## Who? 👤 + +This tutorial is for +anyone who is learning to Elixir/Phoenix. +No prior experience with Phoenix is assumed/expected. +We have included _all_ the steps required to build the app. + +> If you get stuck on any step, +please open an +[issue](https://github.com/dwyl/phoenix-todo-list-tutorial/issues) +on GitHub where we are happy to help you get unstuck! +If you feel that any line of code can use a bit more explanation/clarity, +please don't hesitate to _inform_ us! +We _know_ what it's like to be a beginner, +it can be _frustrating_ when something does not make sense! +If you're stuck, don't suffer in silence, +asking questions on GitHub +helps _everyone_ to learn! + + +
+ +## _How_? 👩‍💻 + + +### Before You Start! 💡 + +_Before_ you attempt to _build_ the Todo List, +make sure you have everything you need installed on you computer. +See: +[prerequisites](https://github.com/dwyl/phoenix-chat-example#0-pre-requisites-before-you-start) + +Once you have confirmed that you have Phoenix & PostgreSQL installed, +try running the _finished_ App. + + +### 0. Run The _Finished_ App on Your `localhost` 💻 + +_Before_ you start building your own version of the Todo List App, +run the _finished_ version on your `localhost` +to confirm that it works. + +Clone the project from GitHub: + +```sh +git clone git@github.com:dwyl/phoenix-todo-list-tutorial.git && cd phoenix-todo-list-tutorial +``` + +Install dependencies and setup the database: + +```sh +mix setup +``` + +Start the Phoenix server: + +```sh +mix phx.server +``` + +Visit +[`localhost:4000`](http://localhost:4000) +in your web browser. + + +You should see: + +![phoenix-todo-list-on-localhost](https://user-images.githubusercontent.com/194400/83285190-bed5a580-a1d5-11ea-9154-80684cf9cc0e.gif) + +Now that you have the _finished_ example app +running on your `localhost`,
+let's build it from scratch +and understand all the steps. + +If you ran the finished app on your `localhost` +(_and you really should!_),
+you will need to change up a directory before starting the tutorial: + +``` +cd .. +``` + +Now you are ready to _build_! + +
+ +### 1. Create a New Phoenix Project 🆕 + +In your terminal, +create a new Phoenix app +using the following +[`mix`](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) +command: + +```sh +mix phx.new app +``` + +When prompted to install dependencies, +type Y followed by Enter. + +Change into the newly created `app` directory (`cd app`) +and +ensure you have everything you need: + +```sh +mix setup +``` + +Start the Phoenix server: + +```sh +mix phx.server +``` + +Now you can visit +[`localhost:4000`](http://localhost:4000) +in your web browser. +You should see something similar to: + +![welcome-to-phoenix](https://user-images.githubusercontent.com/194400/82494801-11caa100-9ae2-11ea-821d-8181580201cb.png) + +Shut down the Phoenix server ctrl+C. + +Run the tests to ensure everything works as expected: + +```sh +mix test +``` + +You should see: + +```sh +Compiling 16 files (.ex) +Generated app app + +17:49:40.111 [info] Already up +... + +Finished in 0.04 seconds +3 tests, 0 failures +``` + +Having established that the Phoenix App works as expected, +let's move on to creating some files! + +
+ +### 2. Create `items` Schema + +In creating a basic Todo List we only need one schema: `items`. +Later we can add separate lists and tags to organise/categorise +our `items` but for now this is all we need. + +Run the following [generator](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Html.html) command to create the items table: + +```sh +mix phx.gen.html Todo Item items text:string person_id:integer status:integer +``` + +Strictly speaking we only _need_ the `text` and `status` fields, +but since we know we want to associate items with people +(_later in the tutorial), +we are adding the field _now_. + + +You will see the following output: + +``` +* creating lib/app_web/controllers/item_controller.ex +* creating lib/app_web/templates/item/edit.html.eex +* creating lib/app_web/templates/item/form.html.eex +* creating lib/app_web/templates/item/index.html.eex +* creating lib/app_web/templates/item/new.html.eex +* creating lib/app_web/templates/item/show.html.eex +* creating lib/app_web/views/item_view.ex +* creating test/app_web/controllers/item_controller_test.exs +* creating lib/app/todo/item.ex +* creating priv/repo/migrations/20200521145424_create_items.exs +* creating lib/app/todo.ex +* injecting lib/app/todo.ex +* creating test/app/todo_test.exs +* injecting test/app/todo_test.exs + +Add the resource to your browser scope in lib/app_web/router.ex: + + resources "/items", ItemController + + +Remember to update your repository by running migrations: + + $ mix ecto.migrate +``` + +That created a _bunch_ of files! +Some of which we don't strictly _need_.
+We could _manually_ create _only_ the files we _need_, +but this is the "official" way of creating a CRUD App in Phoenix, +so we are using it for speed. + + +> **Note**: Phoenix +[Contexts](https://hexdocs.pm/phoenix/contexts.html) +denoted in this example as `Todo`, +are "_dedicated modules that expose and group related functionality_." +We feel they _unnecessarily complicate_ basic Phoenix Apps +with layers of "interface" and we _really_ wish we could +[avoid](https://github.com/phoenixframework/phoenix/issues/3832) them. +But given that they are baked into the generators, +and the _creator_ of the framework +[_likes_](https://youtu.be/tMO28ar0lW8?t=376) them, +we have a choice: either get on board with Contexts +or _manually_ create all the files in our Phoenix projects. +Generators are a _much_ faster way to build! +_Embrace_ them, +even if you end up having to `delete` a few +unused files along the way! + +We are _not_ going to explain each of these files +at this stage in the tutorial because +it's _easier_ to understand the files +as you are _building_ the App! +The purpose of each file will become clear +as you progress through editing them. + + + +
+ +### 2.1 Add the `/items` Resources to `router.ex` + +Follow the instructions noted by the generator to +add the `resources "/items", ItemController` to the `router.ex`. + +Open the `lib/app_web/router.ex` file +and locate the line: `scope "/", AppWeb do`
+Add the line to the end of the block. +e.g: + +```elixir +scope "/", AppWeb do + pipe_through :browser + + get "/", PageController, :index + resources "/items", ItemController # this is the new line +end +``` + +Your `router.ex` file should look like this: +[`router.ex#L20`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/f66184b58b7dd1ef593680e7a1a446247909cae7/lib/app_web/router.ex#L20) + +
+ +### 2.2 _Run_ The App! + +At this point we _already_ have a functional Todo List +(_if we were willing to use the default Phoenix UI_).
+Try running the app on your `localhost`: +Run the generated migrations with `mix ecto.migrate` then the server with: +``` +mix phx.server +``` + +Visit: http://localhost:4000/items/new +and input some data. + +![todo-list-phoenix-default-ui](https://user-images.githubusercontent.com/194400/82673112-3edd9780-9c39-11ea-84d3-f62c3e456a6b.png) + +Click the "Save" button +and you will be redirected to the "show" page: +[/items/1](http://localhost:4000/items/1) + +![todo-list-phoenix-default-ui-show-item](https://user-images.githubusercontent.com/194400/82674191-d7c0e280-9c3a-11ea-9c52-9a37525d0626.png) + +This is not an attractive User Experience (UX), +but it _works_! +Here is a _list_ of items; a "Todo List": + +![todo-list-phoenix-default-ui-show-items-list](https://user-images.githubusercontent.com/194400/82674676-967d0280-9c3b-11ea-999f-bf5fbe1ab2d4.png) + + +Let's improve the UX by using the TodoMVC `HTML` and `CSS`! + +
+ +### 3. Create the TodoMVC UI/UX + +To recreate the TodoMVC UI/UX, +let's borrow the `HTML` code directly from the example. + +Visit: http://todomvc.com/examples/vanillajs +add a couple of items to the list,
+then inspect the source +using your browser's +[Dev Tools](https://developers.google.com/web/tools/chrome-devtools/open). +e.g: + +![todomvc-view-source](https://user-images.githubusercontent.com/194400/82698634-0b176780-9c63-11ea-9e1d-7aa6e2328766.png) + +Right-click on the source you want +(e.g: `
`) + and select "Edit as HTML": + +![edit-as-html](https://user-images.githubusercontent.com/194400/82721624-b8679b00-9cb6-11ea-8d3f-2405f2bd301f.png) + + +Once the `HTML` for the `
` is editable, +select it and copy it. + +![todomvc-html-editable-copy](https://user-images.githubusercontent.com/194400/82721711-b05c2b00-9cb7-11ea-85c2-083d2981440d.png) + + +The `HTML` code is: + + +```html +
+
+

todos

+ +
+
+ + +
    +
  • +
    + + + +
    +
  • +
  • +
    + + + +
    +
  • +
+
+ +
+``` + +Let's convert this `HTML` to an Embedded Elixir +([`EEx`](https://hexdocs.pm/eex/EEx.html)) template. + + +> **Note**: the _reason_ that we are copying this `HTML` +from the browser's Elements inspector +instead of _directly_ from the source +on GitHub: +[`examples/vanillajs/index.html`](https://github.com/tastejs/todomvc/blob/c50cc922495fd76cb44844e3b1cd77e35a5d6be1/examples/vanillajs/index.html#L18) +is that this is a "single page app", +so the `` +only gets populated in the browser. +Copying it from the browser Dev Tools +is the easiest way to get the _complete_ `HTML`. + +
+ +### 3.1 Paste the HTML into `index.html.eex` + +Open the `lib/app_web/templates/item/index.html.eex` file +and scroll to the bottom. + +Then (_without removing the code that is already there_) +paste the `HTML` code we sourced from TodoMVC. + +> e.g: +[`/lib/app_web/templates/item/index.html.eex#L33-L73`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/bddacda93ecd892fe0907210bab335e6b6e5e489/lib/app_web/templates/item/index.html.eex#L33-L73) + +If you attempt to run the app now +and visit +[http://localhost:4000/items/](http://localhost:4000/items/)
+You will see this (_without the TodoMVC `CSS`_): + +![before-adding-css](https://user-images.githubusercontent.com/194400/82725401-af85c200-9cd4-11ea-8717-714477fc3157.png) + +That's obviously not what we want, +so let's get the TodoMVC `CSS` +and save it in our project! + +
+ +### 3.2 Save the TodoMVC CSS to `/assets/css` + +Visit +http://todomvc.com/examples/vanillajs/node_modules/todomvc-app-css/index.css
+and save the file to `/assets/css/todomvc-app.css`. + +e.g: +[`/assets/css/todomvc-app.css`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/65bec23b92307527a414f77b667b29ea10619e5a/assets/css/todomvc-app.css) + +
+ +### 3.3 Import the `todomvc-app.css` in `app.scss` + +Open the `assets/css/app.scss` file and replace it with the following: + +```css +/* This file is for your main application css. */ +/* @import "./phoenix.css"; */ +@import "./todomvc-app.css"; +``` + +e.g: +[`/assets/css/app.scss#L3`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/5fd673089be616c9bb783277ae2d4f0e310b8413/assets/css/app.scss#L3) + +We commented out the +`@import "./phoenix.css";` +because we don't want the Phoenix (Milligram) styles +_conflicting_ with the TodoMVC ones. + +
+ +### 3.4 _Simplify_ The Layout Template + +Open your `lib/app_web/templates/layout/app.html.eex` file +and replace the contents with the following code: + +```html + + + + + + + Phoenix Todo List + "/> + + +
+ <%= @inner_content %> +
+ + + +``` + + +> Before: +[`/lib/app_web/templates/layout/app.html.eex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/bddacda93ecd892fe0907210bab335e6b6e5e489/lib/app_web/templates/layout/app.html.eex)
+> After: +[`/lib/app_web/templates/layout/app.html.eex#L12`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/4d9e2031687d07494f98fad407dc5cc2be795b24/lib/app_web/templates/layout/app.html.eex#L12) + +`<%= @inner_content %>` is where the Todo App will be rendered. + +> **Note**: the ` // -// You will need to verify the user token in the "connect/2" function -// in "web/channels/user_socket.ex": +// You will need to verify the user token in the "connect/3" function +// in "lib/web/channels/user_socket.ex": // -// def connect(%{"token" => token}, socket) do +// def connect(%{"token" => token}, socket, _connect_info) do // # max_age: 1209600 is equivalent to two weeks in seconds // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do // {:ok, user_id} -> @@ -48,9 +51,7 @@ let socket = new Socket("/socket", {params: {token: window.userToken}}) // end // end // -// Finally, pass the token on connect as below. Or remove it -// from connect if you don't care about authentication. - +// Finally, connect to the socket: socket.connect() // Now that you are connected, you can join channels with a topic: diff --git a/assets/package.json b/assets/package.json new file mode 100644 index 00000000..53c5abc2 --- /dev/null +++ b/assets/package.json @@ -0,0 +1,33 @@ +{ + "repository": { + "type": "git", + "url": "https://github.com/dwyl/phoenix-todo-list-tutorial.git" + }, + "engines": { + "node": ">=12" + }, + "description": "A Phoenix Implementation of TodoMVC", + "license": "MIT", + "scripts": { + "deploy": "webpack --mode production", + "watch": "webpack --mode development --watch" + }, + "dependencies": { + "phoenix": "file:../deps/phoenix", + "phoenix_html": "file:../deps/phoenix_html" + }, + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "babel-loader": "^8.0.0", + "copy-webpack-plugin": "^5.1.1", + "css-loader": "^3.4.2", + "sass-loader": "^8.0.2", + "node-sass": "^4.13.1", + "mini-css-extract-plugin": "^0.9.0", + "optimize-css-assets-webpack-plugin": "^5.0.1", + "terser-webpack-plugin": "^2.3.2", + "webpack": "4.41.5", + "webpack-cli": "^3.3.2" + } +} diff --git a/web/static/assets/favicon.ico b/assets/static/favicon.ico similarity index 100% rename from web/static/assets/favicon.ico rename to assets/static/favicon.ico diff --git a/web/static/assets/images/phoenix.png b/assets/static/images/phoenix.png similarity index 100% rename from web/static/assets/images/phoenix.png rename to assets/static/images/phoenix.png diff --git a/web/static/assets/robots.txt b/assets/static/robots.txt similarity index 100% rename from web/static/assets/robots.txt rename to assets/static/robots.txt diff --git a/assets/webpack.config.js b/assets/webpack.config.js new file mode 100644 index 00000000..dd77c3dd --- /dev/null +++ b/assets/webpack.config.js @@ -0,0 +1,51 @@ +const path = require('path'); +const glob = require('glob'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = (env, options) => { + const devMode = options.mode !== 'production'; + + return { + optimization: { + minimizer: [ + new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }), + new OptimizeCSSAssetsPlugin({}) + ] + }, + entry: { + 'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js']) + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, '../priv/static/js'), + publicPath: '/js/' + }, + devtool: devMode ? 'source-map' : undefined, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader' + } + }, + { + test: /\.[s]?css$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader', + ], + } + ] + }, + plugins: [ + new MiniCssExtractPlugin({ filename: '../css/app.css' }), + new CopyWebpackPlugin([{ from: 'static/', to: '../' }]) + ] + } +}; diff --git a/brunch-config.js b/brunch-config.js deleted file mode 100644 index d2fe679c..00000000 --- a/brunch-config.js +++ /dev/null @@ -1,69 +0,0 @@ -exports.config = { - // See http://brunch.io/#documentation for docs. - files: { - javascripts: { - joinTo: "js/app.js" - - // To use a separate vendor.js bundle, specify two files path - // http://brunch.io/docs/config#-files- - // joinTo: { - // "js/app.js": /^(web\/static\/js)/, - // "js/vendor.js": /^(web\/static\/vendor)|(deps)/ - // } - // - // To change the order of concatenation of files, explicitly mention here - // order: { - // before: [ - // "web/static/vendor/js/jquery-2.1.1.js", - // "web/static/vendor/js/bootstrap.min.js" - // ] - // } - }, - stylesheets: { - joinTo: "css/app.css", - order: { - after: ["web/static/css/app.css"] // concat app.css last - } - }, - templates: { - joinTo: "js/app.js" - } - }, - - conventions: { - // This option sets where we should place non-css and non-js assets in. - // By default, we set this to "/web/static/assets". Files in this directory - // will be copied to `paths.public`, which is "priv/static" by default. - assets: /^(web\/static\/assets)/ - }, - - // Phoenix paths configuration - paths: { - // Dependencies and current project directories to watch - watched: [ - "web/static", - "test/static" - ], - - // Where to compile files to - public: "priv/static" - }, - - // Configure your plugins - plugins: { - babel: { - // Do not use ES6 compiler in vendor code - ignore: [/web\/static\/vendor/] - } - }, - - modules: { - autoRequire: { - "js/app.js": ["web/static/js/app"] - } - }, - - npm: { - enabled: true - } -}; diff --git a/config/config.exs b/config/config.exs index e6956bbf..84643cb3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -3,25 +3,29 @@ # # This configuration file is loaded before any dependency and # is restricted to this project. -use Mix.Config # General application configuration -config :api, - ecto_repos: [Api.Repo] +use Mix.Config + +config :app, + ecto_repos: [App.Repo] # Configures the endpoint -config :api, Api.Endpoint, +config :app, AppWeb.Endpoint, url: [host: "localhost"], - secret_key_base: "20vNsXrlE/ySWLOy2TocheXQVcOh3n3KfPndZTdTy+KKUYkmJZOUcBt4NaV2WcvD", - render_errors: [view: Api.ErrorView, accepts: ~w(html json)], - pubsub: [name: Api.PubSub, - adapter: Phoenix.PubSub.PG2] + secret_key_base: "/dFtq9nub4hRMOK5Bbaqvrw6yglP5P5qS3aZXqUv+rizrkA4lSItOpIwGOboSeOt", + render_errors: [view: AppWeb.ErrorView, accepts: ~w(html json), layout: false], + pubsub_server: App.PubSub, + live_view: [signing_salt: "x2TlvXSe"] # Configures Elixir's Logger config :logger, :console, format: "$time $metadata[$level] $message\n", metadata: [:request_id] +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{Mix.env}.exs" +import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 44a853fd..fe4d0d6f 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,28 +1,67 @@ use Mix.Config +# Configure your database +config :app, App.Repo, + username: "postgres", + password: "postgres", + database: "app_dev", + hostname: "localhost", + show_sensitive_data_on_connection_error: true, + pool_size: 10 + # For development, we disable any cache and enable # debugging and code reloading. # # The watchers configuration can be used to run external # watchers to your application. For example, we use it -# with brunch.io to recompile .js and .css sources. -config :api, Api.Endpoint, +# with webpack to recompile .js and .css sources. +config :app, AppWeb.Endpoint, http: [port: 4000], debug_errors: true, code_reloader: true, check_origin: false, - watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin", - cd: Path.expand("../", __DIR__)]] + watchers: [ + node: [ + "node_modules/webpack/bin/webpack.js", + "--mode", + "development", + "--watch-stdin", + cd: Path.expand("../assets", __DIR__) + ] + ] +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Note that this task requires Erlang/OTP 20 or later. +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. # Watch static and templates for browser reloading. -config :api, Api.Endpoint, +config :app, AppWeb.Endpoint, live_reload: [ patterns: [ - ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, - ~r{priv/gettext/.*(po)$}, - ~r{web/views/.*(ex)$}, - ~r{web/templates/.*(eex)$} + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/app_web/(live|views)/.*(ex)$", + ~r"lib/app_web/templates/.*(eex)$" ] ] @@ -33,11 +72,5 @@ config :logger, :console, format: "[$level] $message\n" # in production as building large stacktraces may be expensive. config :phoenix, :stacktrace_depth, 20 -# Configure your database -config :api, Api.Repo, - adapter: Ecto.Adapters.Postgres, - username: "postgres", - password: "postgres", - database: "api_dev", - hostname: "localhost", - pool_size: 10 +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/config/prod.exs b/config/prod.exs index 90349256..2abc3503 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -1,20 +1,17 @@ use Mix.Config -# For production, we configure the host to read the PORT -# from the system environment. Therefore, you will need -# to set PORT=80 before running your server. +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. # -# You should also configure the url host to something -# meaningful, we use this information when generating URLs. -# -# Finally, we also include the path to a manifest +# Note we also include the path to a cache manifest # containing the digested version of static files. This -# manifest is generated by the mix phoenix.digest task -# which you typically run after static files are built. -config :api, Api.Endpoint, - http: [port: {:system, "PORT"}], +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :app, AppWeb.Endpoint, url: [host: "example.com", port: 80], - cache_static_manifest: "priv/static/manifest.json" + cache_static_manifest: "priv/static/cache_manifest.json" # Do not print debug messages in production config :logger, level: :info @@ -24,38 +21,35 @@ config :logger, level: :info # To get SSL working, you will need to add the `https` key # to the previous section and set your `:url` port to 443: # -# config :api, Api.Endpoint, +# config :app, AppWeb.Endpoint, # ... # url: [host: "example.com", port: 443], -# https: [port: 443, -# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), -# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] -# -# Where those two env variables return an absolute path to -# the key and cert in disk or a relative path inside priv, -# for example "priv/ssl/server.key". -# -# We also recommend setting `force_ssl`, ensuring no data is -# ever sent via http, always redirecting to https: -# -# config :api, Api.Endpoint, +# https: [ +# port: 443, +# cipher_suite: :strong, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH"), +# transport_options: [socket_opts: [:inet6]] +# ] +# +# The `cipher_suite` is set to `:strong` to support only the +# latest and more secure SSL ciphers. This means old browsers +# and clients may not be supported. You can set it to +# `:compatible` for wider support. +# +# `:keyfile` and `:certfile` expect an absolute path to the key +# and cert in disk or a relative path inside priv, for example +# "priv/ssl/server.key". For all supported SSL configuration +# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 +# +# We also recommend setting `force_ssl` in your endpoint, ensuring +# no data is ever sent via http, always redirecting to https: +# +# config :app, AppWeb.Endpoint, # force_ssl: [hsts: true] # # Check `Plug.SSL` for all available options in `force_ssl`. -# ## Using releases -# -# If you are doing OTP releases, you need to instruct Phoenix -# to start the server for all endpoints: -# -# config :phoenix, :serve_endpoints, true -# -# Alternatively, you can configure exactly which server to -# start per endpoint: -# -# config :api, Api.Endpoint, server: true -# - -# Finally import the config/prod.secret.exs -# which should be versioned separately. +# Finally import the config/prod.secret.exs which loads secrets +# and configuration from environment variables. import_config "prod.secret.exs" diff --git a/config/prod.secret.exs b/config/prod.secret.exs new file mode 100644 index 00000000..595dc2a6 --- /dev/null +++ b/config/prod.secret.exs @@ -0,0 +1,41 @@ +# In this file, we load production configuration and secrets +# from environment variables. You can also hardcode secrets, +# although such is generally not recommended and you have to +# remember to add this file to your .gitignore. +use Mix.Config + +database_url = + System.get_env("DATABASE_URL") || + raise """ + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + """ + +config :app, App.Repo, + # ssl: true, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + +secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + +config :app, AppWeb.Endpoint, + http: [ + port: String.to_integer(System.get_env("PORT") || "4000"), + transport_options: [socket_opts: [:inet6]] + ], + secret_key_base: secret_key_base + +# ## Using releases (Elixir v1.9+) +# +# If you are doing OTP releases, you need to instruct Phoenix +# to start each relevant endpoint: +# +# config :app, AppWeb.Endpoint, server: true +# +# Then you can assemble a release by calling `mix release`. +# See `mix help release` for more information. diff --git a/config/test.exs b/config/test.exs index 5ece7c95..7068d024 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,19 +1,22 @@ use Mix.Config +# Configure your database +# +# The MIX_TEST_PARTITION environment variable can be used +# to provide built-in test partitioning in CI environment. +# Run `mix help test` for more information. +config :app, App.Repo, + username: "postgres", + password: "postgres", + database: "app_test#{System.get_env("MIX_TEST_PARTITION")}", + hostname: "localhost", + pool: Ecto.Adapters.SQL.Sandbox + # We don't run a server during test. If one is required, # you can enable the server option below. -config :api, Api.Endpoint, - http: [port: 4001], +config :app, AppWeb.Endpoint, + http: [port: 4002], server: false # Print only warnings and errors during test config :logger, level: :warn - -# Configure your database -config :api, Api.Repo, - adapter: Ecto.Adapters.Postgres, - username: "postgres", - password: "postgres", - database: "api_test", - hostname: "localhost", - pool: Ecto.Adapters.SQL.Sandbox diff --git a/coveralls.json b/coveralls.json new file mode 100644 index 00000000..cca3ffa9 --- /dev/null +++ b/coveralls.json @@ -0,0 +1,13 @@ +{ + "coverage_options": { + "minimum_coverage": 100 + }, + "skip_files": [ + "lib/app/application.ex", + "lib/app_web.ex", + "lib/app_web/router.ex", + "lib/app_web/views/error_helpers.ex", + "test/", + "lib/app_web/telemetry.ex" + ] +} diff --git a/elixir_buildpack.config b/elixir_buildpack.config new file mode 100644 index 00000000..e650d238 --- /dev/null +++ b/elixir_buildpack.config @@ -0,0 +1,8 @@ +# Elixir version +elixir_version=1.10 + +# Erlang version +# available versions https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions +erlang_version=22.2.7 + +# always_rebuild=true diff --git a/email_templates/welcome_html.html b/email_templates/welcome_html.html deleted file mode 100644 index 4b212940..00000000 --- a/email_templates/welcome_html.html +++ /dev/null @@ -1,12 +0,0 @@ -

Hi!

- -

Welcome to dwyl! Thanks so much for your interest, we're excited to have you!

- -

At dwyl we work to help you do what you love (whether you know what that is yet or not). We do this through technology and we hold certain beliefs strongly, including that your data belongs to you and no one else.

- -

As we develop dwyl over the summer, you'll be the first to hear of the progress and the first to use the app. - -

If you have any questions or suggestions, please reply to this email or add an issue to github (if that's your kind of thing), we really appreciate it!

- -

Happy dwyling!

-Ines & Nelson \ No newline at end of file diff --git a/email_templates/welcome_text.txt b/email_templates/welcome_text.txt deleted file mode 100644 index 43e78c43..00000000 --- a/email_templates/welcome_text.txt +++ /dev/null @@ -1,13 +0,0 @@ -Hi! - -Welcome to dwyl! Thanks so much for your interest, we're excited to have you! - -At dwyl we work to help you do what you love (whether you know what that is yet or not). We do this through technology and we hold certain beliefs strongly, including that *your data belongs to you* and no one else. You can see those here: http://git.io/vtYgs - -As we develop dwyl, you'll be the first to hear of the progress and the first to use the app. - -If you have any questions or suggestions, please reply to this email or add an issue to github here (if that's your kind of thing): http://git.io/vtYgz -We really appreciate it! - -Happy dwyling, -Ines & Nelson diff --git a/lib/api.ex b/lib/api.ex deleted file mode 100644 index 27ddd603..00000000 --- a/lib/api.ex +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Api do - use Application - - # See http://elixir-lang.org/docs/stable/elixir/Application.html - # for more information on OTP Applications - def start(_type, _args) do - import Supervisor.Spec - - # Define workers and child supervisors to be supervised - children = [ - # Start the Ecto repository - supervisor(Api.Repo, []), - # Start the endpoint when the application starts - supervisor(Api.Endpoint, []), - # Start your own worker by calling: Api.Worker.start_link(arg1, arg2, arg3) - # worker(Api.Worker, [arg1, arg2, arg3]), - ] - - # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html - # for other strategies and supported options - opts = [strategy: :one_for_one, name: Api.Supervisor] - Supervisor.start_link(children, opts) - end - - # Tell Phoenix to update the endpoint configuration - # whenever the application is updated. - def config_change(changed, _new, removed) do - Api.Endpoint.config_change(changed, removed) - :ok - end -end diff --git a/lib/api/repo.ex b/lib/api/repo.ex deleted file mode 100644 index 2f94f3b5..00000000 --- a/lib/api/repo.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Api.Repo do - use Ecto.Repo, otp_app: :api -end diff --git a/lib/app.ex b/lib/app.ex new file mode 100644 index 00000000..a10dc060 --- /dev/null +++ b/lib/app.ex @@ -0,0 +1,9 @@ +defmodule App do + @moduledoc """ + App keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/lib/app/application.ex b/lib/app/application.ex new file mode 100644 index 00000000..cd46ff43 --- /dev/null +++ b/lib/app/application.ex @@ -0,0 +1,34 @@ +defmodule App.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + def start(_type, _args) do + children = [ + # Start the Ecto repository + App.Repo, + # Start the Telemetry supervisor + AppWeb.Telemetry, + # Start the PubSub system + {Phoenix.PubSub, name: App.PubSub}, + # Start the Endpoint (http/https) + AppWeb.Endpoint + # Start a worker by calling: App.Worker.start_link(arg) + # {App.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: App.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + def config_change(changed, _new, removed) do + AppWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/lib/app/repo.ex b/lib/app/repo.ex new file mode 100644 index 00000000..857bd3f9 --- /dev/null +++ b/lib/app/repo.ex @@ -0,0 +1,5 @@ +defmodule App.Repo do + use Ecto.Repo, + otp_app: :app, + adapter: Ecto.Adapters.Postgres +end diff --git a/lib/app/todo.ex b/lib/app/todo.ex new file mode 100644 index 00000000..0c410b1f --- /dev/null +++ b/lib/app/todo.ex @@ -0,0 +1,104 @@ +defmodule App.Todo do + @moduledoc """ + The Todo context. + """ + + import Ecto.Query, warn: false + alias App.Repo + + alias App.Todo.Item + + @doc """ + Returns the list of items. + + ## Examples + + iex> list_items() + [%Item{}, ...] + + """ + def list_items do + Item |> order_by(asc: :id) |> Repo.all() + end + + @doc """ + Gets a single item. + + Raises `Ecto.NoResultsError` if the Item does not exist. + + ## Examples + + iex> get_item!(123) + %Item{} + + iex> get_item!(456) + ** (Ecto.NoResultsError) + + """ + def get_item!(id), do: Repo.get!(Item, id) + + @doc """ + Creates a item. + + ## Examples + + iex> create_item(%{field: value}) + {:ok, %Item{}} + + iex> create_item(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_item(attrs \\ %{}) do + %Item{} + |> Item.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a item. + + ## Examples + + iex> update_item(item, %{field: new_value}) + {:ok, %Item{}} + + iex> update_item(item, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_item(%Item{} = item, attrs) do + item + |> Item.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a item. + + ## Examples + + iex> delete_item(item) + {:ok, %Item{}} + + iex> delete_item(item) + {:error, %Ecto.Changeset{}} + + """ + def delete_item(%Item{} = item) do + Repo.delete(item) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking item changes. + + ## Examples + + iex> change_item(item) + %Ecto.Changeset{data: %Item{}} + + """ + def change_item(%Item{} = item, attrs \\ %{}) do + Item.changeset(item, attrs) + end +end diff --git a/lib/app/todo/item.ex b/lib/app/todo/item.ex new file mode 100644 index 00000000..af917734 --- /dev/null +++ b/lib/app/todo/item.ex @@ -0,0 +1,19 @@ +defmodule App.Todo.Item do + use Ecto.Schema + import Ecto.Changeset + + schema "items" do + field :person_id, :integer, default: 0 + field :status, :integer, default: 0 + field :text, :string + + timestamps() + end + + @doc false + def changeset(item, attrs) do + item + |> cast(attrs, [:text, :person_id, :status]) + |> validate_required([:text]) + end +end diff --git a/lib/app_web.ex b/lib/app_web.ex new file mode 100644 index 00000000..bceb6b0b --- /dev/null +++ b/lib/app_web.ex @@ -0,0 +1,80 @@ +defmodule AppWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use AppWeb, :controller + use AppWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: AppWeb + + import Plug.Conn + import AppWeb.Gettext + alias AppWeb.Router.Helpers, as: Routes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/app_web/templates", + namespace: AppWeb + + # Import convenience functions from controllers + import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1] + + # Include shared imports and aliases for views + unquote(view_helpers()) + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + use Phoenix.Channel + import AppWeb.Gettext + end + end + + defp view_helpers do + quote do + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import AppWeb.ErrorHelpers + import AppWeb.Gettext + alias AppWeb.Router.Helpers, as: Routes + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/web/channels/user_socket.ex b/lib/app_web/channels/user_socket.ex similarity index 68% rename from web/channels/user_socket.ex rename to lib/app_web/channels/user_socket.ex index 661b2d45..e2bef5dd 100644 --- a/web/channels/user_socket.ex +++ b/lib/app_web/channels/user_socket.ex @@ -1,12 +1,8 @@ -defmodule Api.UserSocket do +defmodule AppWeb.UserSocket do use Phoenix.Socket ## Channels - # channel "room:*", Api.RoomChannel - - ## Transports - transport :websocket, Phoenix.Transports.WebSocket - # transport :longpoll, Phoenix.Transports.LongPoll + # channel "room:*", AppWeb.RoomChannel # Socket params are passed from the client and can # be used to verify and authenticate a user. After @@ -19,19 +15,21 @@ defmodule Api.UserSocket do # # See `Phoenix.Token` documentation for examples in # performing token verification on connect. - def connect(_params, socket) do + @impl true + def connect(_params, socket, _connect_info) do {:ok, socket} end # Socket id's are topics that allow you to identify all sockets for a given user: # - # def id(socket), do: "users_socket:#{socket.assigns.user_id}" + # def id(socket), do: "user_socket:#{socket.assigns.user_id}" # # Would allow you to broadcast a "disconnect" event and terminate # all active sockets and channels for a given user: # - # Api.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{}) + # AppWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) # # Returning `nil` makes this socket anonymous. + @impl true def id(_socket), do: nil end diff --git a/lib/app_web/controllers/item_controller.ex b/lib/app_web/controllers/item_controller.ex new file mode 100644 index 00000000..3dbe40d9 --- /dev/null +++ b/lib/app_web/controllers/item_controller.ex @@ -0,0 +1,95 @@ +defmodule AppWeb.ItemController do + use AppWeb, :controller + + alias App.Todo + alias App.Todo.Item + + def index(conn, params) do + item = + if not is_nil(params) and Map.has_key?(params, "id") do + Todo.get_item!(params["id"]) + else + %Item{} + end + + items = Todo.list_items() + changeset = Todo.change_item(item) + + render(conn, "index.html", + items: items, + changeset: changeset, + editing: item, + filter: Map.get(params, "filter", "all") + ) + end + + def new(conn, _params) do + changeset = Todo.change_item(%Item{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"item" => item_params}) do + case Todo.create_item(item_params) do + {:ok, _item} -> + conn + |> put_flash(:info, "Item created successfully.") + |> redirect(to: Routes.item_path(conn, :index)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "new.html", changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + item = Todo.get_item!(id) + render(conn, "show.html", item: item) + end + + def edit(conn, params) do + index(conn, params) + end + + def update(conn, %{"id" => id, "item" => item_params}) do + item = Todo.get_item!(id) + + case Todo.update_item(item, item_params) do + {:ok, _item} -> + conn + # |> put_flash(:info, "Item updated successfully.") + |> redirect(to: Routes.item_path(conn, :index)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "edit.html", item: item, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + item = Todo.get_item!(id) + Todo.update_item(item, %{status: 2}) + index(conn, %{}) + end + + def toggle_status(item) do + case item.status do + 1 -> 0 + 0 -> 1 + end + end + + def toggle(conn, %{"id" => id}) do + item = Todo.get_item!(id) + Todo.update_item(item, %{status: toggle_status(item)}) + redirect(conn, to: Routes.item_path(conn, :index)) + end + + import Ecto.Query + alias App.Repo + + def clear_completed(conn, _param) do + person_id = 0 + query = from(i in Item, where: i.person_id == ^person_id, where: i.status == 1) + Repo.update_all(query, set: [status: 2]) + # render the main template: + index(conn, %{filter: "all"}) + end +end diff --git a/lib/api/endpoint.ex b/lib/app_web/endpoint.ex similarity index 52% rename from lib/api/endpoint.ex rename to lib/app_web/endpoint.ex index 12465bf2..661564c3 100644 --- a/lib/api/endpoint.ex +++ b/lib/app_web/endpoint.ex @@ -1,14 +1,29 @@ -defmodule Api.Endpoint do - use Phoenix.Endpoint, otp_app: :api +defmodule AppWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :app - socket "/socket", Api.UserSocket + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_app_key", + signing_salt: "EaSsAcnS" + ] + + socket "/socket", AppWeb.UserSocket, + websocket: true, + longpoll: false + + socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] # Serve at "/" the static files from "priv/static" directory. # - # You should set gzip to true if you are running phoenix.digest + # You should set gzip to true if you are running phx.digest # when deploying your static files in production. plug Plug.Static, - at: "/", from: :api, gzip: false, + at: "/", + from: :app, + gzip: false, only: ~w(css fonts images js favicon.ico robots.txt) # Code reloading can be explicitly enabled under the @@ -17,26 +32,23 @@ defmodule Api.Endpoint do socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket plug Phoenix.LiveReloader plug Phoenix.CodeReloader + plug Phoenix.Ecto.CheckRepoStatus, otp_app: :app end + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + plug Plug.RequestId - plug Plug.Logger + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], - json_decoder: Poison + json_decoder: Phoenix.json_library() plug Plug.MethodOverride plug Plug.Head - - # The session will be stored in the cookie and signed, - # this means its contents can be read but not tampered with. - # Set :encryption_salt if you would also like to encrypt it. - plug Plug.Session, - store: :cookie, - key: "_api_key", - signing_salt: "BjlUvgQk" - - plug Api.Router + plug Plug.Session, @session_options + plug AppWeb.Router end diff --git a/web/gettext.ex b/lib/app_web/gettext.ex similarity index 61% rename from web/gettext.ex rename to lib/app_web/gettext.ex index e378f805..19bc04ee 100644 --- a/web/gettext.ex +++ b/lib/app_web/gettext.ex @@ -1,24 +1,24 @@ -defmodule Api.Gettext do +defmodule AppWeb.Gettext do @moduledoc """ A module providing Internationalization with a gettext-based API. By using [Gettext](https://hexdocs.pm/gettext), your module gains a set of macros for translations, for example: - import Api.Gettext + import AppWeb.Gettext # Simple translation - gettext "Here is the string to translate" + gettext("Here is the string to translate") # Plural translation - ngettext "Here is the string to translate", + ngettext("Here is the string to translate", "Here are the strings to translate", - 3 + 3) # Domain-based translation - dgettext "errors", "Here is the error message to translate" + dgettext("errors", "Here is the error message to translate") See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ - use Gettext, otp_app: :api + use Gettext, otp_app: :app end diff --git a/lib/app_web/router.ex b/lib/app_web/router.ex new file mode 100644 index 00000000..6983e82a --- /dev/null +++ b/lib/app_web/router.ex @@ -0,0 +1,46 @@ +defmodule AppWeb.Router do + use AppWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_flash + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", AppWeb do + pipe_through :browser + + get "/", ItemController, :index + resources "/items", ItemController + get "/items/toggle/:id", ItemController, :toggle + get "/clear", ItemController, :clear_completed + get "/:filter", ItemController, :index + end + + # Other scopes may use custom stacks. + # scope "/api", AppWeb do + # pipe_through :api + # end + + # Enables LiveDashboard only for development + # + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + if Mix.env() in [:dev, :test] do + import Phoenix.LiveDashboard.Router + + scope "/" do + pipe_through :browser + live_dashboard "/dashboard", metrics: AppWeb.Telemetry + end + end +end diff --git a/lib/app_web/telemetry.ex b/lib/app_web/telemetry.ex new file mode 100644 index 00000000..20839ca4 --- /dev/null +++ b/lib/app_web/telemetry.ex @@ -0,0 +1,53 @@ +defmodule AppWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + + # Database Metrics + summary("app.repo.query.total_time", unit: {:native, :millisecond}), + summary("app.repo.query.decode_time", unit: {:native, :millisecond}), + summary("app.repo.query.query_time", unit: {:native, :millisecond}), + summary("app.repo.query.queue_time", unit: {:native, :millisecond}), + summary("app.repo.query.idle_time", unit: {:native, :millisecond}), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {AppWeb, :count_users, []} + ] + end +end diff --git a/lib/app_web/templates/item/edit.html.eex b/lib/app_web/templates/item/edit.html.eex new file mode 100644 index 00000000..8f5718ed --- /dev/null +++ b/lib/app_web/templates/item/edit.html.eex @@ -0,0 +1,6 @@ +

Edit Item

+ +<%= render "form.html", + Map.put(assigns, :action, Routes.item_path(@conn, :update, @item)) %> + +<%= link "Back", to: Routes.item_path(@conn, :index) %> diff --git a/lib/app_web/templates/item/form.html.eex b/lib/app_web/templates/item/form.html.eex new file mode 100644 index 00000000..21e081bd --- /dev/null +++ b/lib/app_web/templates/item/form.html.eex @@ -0,0 +1,6 @@ +<%= form_for @changeset, @action, fn f -> %> + <%= text_input f, :text, placeholder: "what needs to be done?", + class: "new-todo", autofocus: "" %> +
<%= submit "Save" %>
+ +<% end %> diff --git a/lib/app_web/templates/item/index.html.eex b/lib/app_web/templates/item/index.html.eex new file mode 100644 index 00000000..c11752b4 --- /dev/null +++ b/lib/app_web/templates/item/index.html.eex @@ -0,0 +1,75 @@ +
+
+

todos

+ <%= if @editing.id do %> + + Click here to create a new item! + + <% else %> + <%= render "form.html", Map.put(assigns, :action, Routes.item_path(@conn, :create)) %> + <% end %> +
+
+ + +
    + + <%= for item <- filter(@items, @filter) do %> +
  • + + + + +
    + <%= if item.id == @editing.id do %> + <%= render "form.html", Map.put(assigns, :action, + Routes.item_path(@conn, :update, item)) %> + <% else %> + + + + + <% end %> + + <%= link "", class: "destroy", method: :delete, + to: Routes.item_path(@conn, :delete, item), + data: [confirm: "Are you sure you want to delete it?"] %> +
    +
  • + <% end %> + +
+
+ +<%= if got_items?(@items) do %> + +<% end %> +
diff --git a/lib/app_web/templates/item/new.html.eex b/lib/app_web/templates/item/new.html.eex new file mode 100644 index 00000000..fe5c45b9 --- /dev/null +++ b/lib/app_web/templates/item/new.html.eex @@ -0,0 +1,5 @@ +

New Item

+ +<%= render "form.html", Map.put(assigns, :action, Routes.item_path(@conn, :create)) %> + +<%= link "Back", to: Routes.item_path(@conn, :index) %> diff --git a/lib/app_web/templates/item/show.html.eex b/lib/app_web/templates/item/show.html.eex new file mode 100644 index 00000000..fedb5c8f --- /dev/null +++ b/lib/app_web/templates/item/show.html.eex @@ -0,0 +1,23 @@ +

Show Item

+ + + +<%= link "Edit", to: Routes.item_path(@conn, :edit, @item) %> +<%= link "Back", to: Routes.item_path(@conn, :index) %> diff --git a/lib/app_web/templates/layout/app.html.eex b/lib/app_web/templates/layout/app.html.eex new file mode 100644 index 00000000..dd619f06 --- /dev/null +++ b/lib/app_web/templates/layout/app.html.eex @@ -0,0 +1,16 @@ + + + + + + + Phoenix Todo List + "/> + + +
+ <%= @inner_content %> +
+ + + diff --git a/lib/app_web/views/error_helpers.ex b/lib/app_web/views/error_helpers.ex new file mode 100644 index 00000000..2e891a28 --- /dev/null +++ b/lib/app_web/views/error_helpers.ex @@ -0,0 +1,47 @@ +defmodule AppWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_id(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(AppWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(AppWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/lib/app_web/views/error_view.ex b/lib/app_web/views/error_view.ex new file mode 100644 index 00000000..a6651a5c --- /dev/null +++ b/lib/app_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule AppWeb.ErrorView do + use AppWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/lib/app_web/views/item_view.ex b/lib/app_web/views/item_view.ex new file mode 100644 index 00000000..f7f388d8 --- /dev/null +++ b/lib/app_web/views/item_view.ex @@ -0,0 +1,56 @@ +defmodule AppWeb.ItemView do + use AppWeb, :view + + # add class "completed" to a list item if item.status=1 + def complete(item) do + case item.status do + 1 -> "completed" + # empty string means empty class so no style applied + _ -> "" + end + end + + #  add "checked" to input if item.status=1 + def checked(item) do + case item.status do + 1 -> "checked" + # empty string means empty class so no style applied + _ -> "" + end + end + + # returns integer value of items where item.status == 0 (not "done") + def remaining_items(items) do + Enum.filter(items, fn i -> i.status < 1 end) |> Enum.count() + end + + # filter the items based on the filter string + def filter(items, str) do + case str do + "all" -> Enum.filter(items, fn i -> i.status == 0 || i.status == 1 end) + "active" -> Enum.filter(items, fn i -> i.status == 0 end) + "completed" -> Enum.filter(items, fn i -> i.status == 1 end) + end + end + + def selected(filter, str) do + case filter == str do + true -> "selected" + false -> "" + end + end + + # pluralise the word item when the number of items is greather/less than 1 + def pluralise(items) do + # items where status < 1 is equal to Zero or Greater than One: + case remaining_items(items) == 0 || remaining_items(items) > 1 do + true -> "items" + false -> "item" + end + end + + # check if there are items with status 0 or 1, used to hide/show footer + def got_items?(items) do + Enum.filter(items, fn i -> i.status < 2 end) |> Enum.count > 0 + end +end diff --git a/lib/app_web/views/layout_view.ex b/lib/app_web/views/layout_view.ex new file mode 100644 index 00000000..80c818d5 --- /dev/null +++ b/lib/app_web/views/layout_view.ex @@ -0,0 +1,3 @@ +defmodule AppWeb.LayoutView do + use AppWeb, :view +end diff --git a/mix.exs b/mix.exs index 1e79e564..93329b5c 100644 --- a/mix.exs +++ b/mix.exs @@ -1,43 +1,61 @@ -defmodule Api.Mixfile do +defmodule App.MixProject do use Mix.Project def project do - [app: :api, - version: "0.0.1", - elixir: "~> 1.2", - elixirc_paths: elixirc_paths(Mix.env), - compilers: [:phoenix, :gettext] ++ Mix.compilers, - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - aliases: aliases(), - deps: deps()] + [ + app: :app, + version: "1.5.3", + elixir: "~> 1.7", + elixirc_paths: elixirc_paths(Mix.env()), + compilers: [:phoenix, :gettext] ++ Mix.compilers(), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps(), + test_coverage: [tool: ExCoveralls], + preferred_cli_env: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.post": :test, + "coveralls.html": :test + ] + ] end # Configuration for the OTP application. # # Type `mix help compile.app` for more information. def application do - [mod: {Api, []}, - applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, - :phoenix_ecto, :postgrex]] + [ + mod: {App.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] end # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "web", "test/support"] - defp elixirc_paths(_), do: ["lib", "web"] + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] # Specifies your project dependencies. # # Type `mix help deps` for examples and options. defp deps do - [{:phoenix, "~> 1.2.1"}, - {:phoenix_pubsub, "~> 1.0"}, - {:phoenix_ecto, "~> 3.0"}, - {:postgrex, ">= 0.0.0"}, - {:phoenix_html, "~> 2.6"}, - {:phoenix_live_reload, "~> 1.0", only: :dev}, - {:gettext, "~> 0.11"}, - {:cowboy, "~> 1.0"}] + [ + {:phoenix, "~> 1.5.3"}, + {:phoenix_ecto, "~> 4.1"}, + {:ecto_sql, "~> 3.4"}, + {:postgrex, ">= 0.0.0"}, + {:phoenix_html, "~> 2.11"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_dashboard, "~> 0.2.0"}, + {:telemetry_metrics, "~> 0.4"}, + {:telemetry_poller, "~> 0.4"}, + {:gettext, "~> 0.11"}, + {:jason, "~> 1.0"}, + {:plug_cowboy, "~> 2.0"}, + + # Test Code Coverage: + {:excoveralls, "~> 0.12.2", only: :test} + ] end # Aliases are shortcuts or tasks specific to the current project. @@ -47,8 +65,11 @@ defmodule Api.Mixfile do # # See the documentation for `Mix` for more info on aliases. defp aliases do - ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], - "ecto.reset": ["ecto.drop", "ecto.setup"], - "test": ["ecto.create --quiet", "ecto.migrate", "test"]] + [ + setup: ["deps.get", "ecto.setup", "cmd npm install --prefix assets"], + "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], + "ecto.reset": ["ecto.drop", "ecto.setup"], + test: ["ecto.create --quiet", "ecto.migrate", "test"] + ] end end diff --git a/mix.lock b/mix.lock index b7fab05d..75da7a65 100644 --- a/mix.lock +++ b/mix.lock @@ -1,19 +1,37 @@ -%{"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []}, - "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, optional: false]}]}, - "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, - "db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, - "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], []}, - "ecto": {:hex, :ecto, "2.1.4", "d1ba932813ec0e0d9db481ef2c17777f1cefb11fc90fa7c142ff354972dfba7e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, - "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, - "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []}, - "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []}, - "phoenix": {:hex, :phoenix, "1.2.4", "4172479b5e21806a5e4175b54820c239e0d4effb0b07912e631aa31213a05bae", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.4 or ~> 1.3.3 or ~> 1.2.4 or ~> 1.1.8 or ~> 1.0.5", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, - "phoenix_ecto": {:hex, :phoenix_ecto, "3.2.3", "450c749876ff1de4a78fdb305a142a76817c77a1cd79aeca29e5fc9a6c630b26", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]}, - "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []}, - "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, - "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, - "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, - "postgrex": {:hex, :postgrex, "0.13.3", "c277cfb2a9c5034d445a722494c13359e361d344ef6f25d604c2353185682bfc", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]}, - "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []}} +%{ + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, + "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, + "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, + "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, + "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, + "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"}, + "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"}, + "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"}, + "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, + "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, + "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, + "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, + "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.3", "aba80790b5358877c39e89d4e9a28e65817c22f01601c2eea64749e18e5c9b6e", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.12.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3871e507e2811022d5af80b45aa61ef2c20d3e7b0465b3a64d1669fffb92fd67"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.12.1", "42f591c781edbf9fab921319076b7ac635d43aa23e6748d2644563326236d7e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.16 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "585321e98df1cd5943e370b9784e950a37ca073744eb534660c9048967c52ab6"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.2.2", "7a09aa5d10e79b92d332a288f21cc49406b1b994cbda0fde76160e7f4cc890ea", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82364b29311dbad3753d588febd7e5ef05062cd6697d8c231e0e007adab3727"}, + "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"}, + "telemetry_poller": {:hex, :telemetry_poller, "0.5.0", "4770888ef85599ead39c7f51d6b4b62306e602d96c69b2625d54dea3d9a5204b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69e4e8e65b0ae077c9e14cd5f42c7cc486de0e07ac6e3409e6f0e52699a7872c"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, +} diff --git a/package.json b/package.json deleted file mode 100644 index 13de8581..00000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "repository": {}, - "license": "MIT", - "scripts": { - "deploy": "brunch build --production", - "watch": "brunch watch --stdin" - }, - "dependencies": { - "phoenix": "file:deps/phoenix", - "phoenix_html": "file:deps/phoenix_html" - }, - "devDependencies": { - "babel-brunch": "~6.0.0", - "brunch": "2.7.4", - "clean-css-brunch": "~2.0.0", - "css-brunch": "~2.0.0", - "javascript-brunch": "~2.0.0", - "uglify-js-brunch": "~2.0.1" - } -} diff --git a/phoenix_static_buildpack.config b/phoenix_static_buildpack.config new file mode 100644 index 00000000..4a263a26 --- /dev/null +++ b/phoenix_static_buildpack.config @@ -0,0 +1,2 @@ +node_version=12.17.0 +npm_version=6.14.4 diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po index 087d374e..a589998c 100644 --- a/priv/gettext/en/LC_MESSAGES/errors.po +++ b/priv/gettext/en/LC_MESSAGES/errors.po @@ -22,6 +22,10 @@ msgstr "" msgid "is invalid" msgstr "" +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + ## From Ecto.Changeset.validate_format/3 msgid "has invalid format" msgstr "" @@ -39,10 +43,10 @@ msgid "does not match confirmation" msgstr "" ## From Ecto.Changeset.no_assoc_constraint/3 -msgid "is still associated to this entry" +msgid "is still associated with this entry" msgstr "" -msgid "are still associated to this entry" +msgid "are still associated with this entry" msgstr "" ## From Ecto.Changeset.validate_length/3 diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index a2289579..39a220be 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -1,11 +1,11 @@ -## This file is a PO Template file. +## This is a PO Template file. ## ## `msgid`s here are often extracted from source code. ## Add new translations manually only if they're dynamic ## translations that can't be statically extracted. ## ## Run `mix gettext.extract` to bring this file up to -## date. Leave `msgstr`s empty as changing them here as no +## date. Leave `msgstr`s empty as changing them here has no ## effect: edit them in PO (`.po`) files instead. ## From Ecto.Changeset.cast/4 @@ -20,6 +20,10 @@ msgstr "" msgid "is invalid" msgstr "" +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + ## From Ecto.Changeset.validate_format/3 msgid "has invalid format" msgstr "" @@ -37,10 +41,10 @@ msgid "does not match confirmation" msgstr "" ## From Ecto.Changeset.no_assoc_constraint/3 -msgid "is still associated to this entry" +msgid "is still associated with this entry" msgstr "" -msgid "are still associated to this entry" +msgid "are still associated with this entry" msgstr "" ## From Ecto.Changeset.validate_length/3 diff --git a/priv/repo/migrations/.formatter.exs b/priv/repo/migrations/.formatter.exs new file mode 100644 index 00000000..49f9151e --- /dev/null +++ b/priv/repo/migrations/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:ecto_sql], + inputs: ["*.exs"] +] diff --git a/priv/repo/migrations/20200521145424_create_items.exs b/priv/repo/migrations/20200521145424_create_items.exs new file mode 100644 index 00000000..0e61b442 --- /dev/null +++ b/priv/repo/migrations/20200521145424_create_items.exs @@ -0,0 +1,13 @@ +defmodule App.Repo.Migrations.CreateItems do + use Ecto.Migration + + def change do + create table(:items) do + add :text, :string + add :person_id, :integer + add :status, :integer + + timestamps() + end + end +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index a70a91dc..fe3037bf 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -5,7 +5,7 @@ # Inside the script, you can read and write to any of your # repositories directly: # -# Api.Repo.insert!(%Api.SomeModel{}) +# App.Repo.insert!(%App.SomeSchema{}) # # We recommend using the bang functions (`insert!`, `update!` # and so on) as they will fail if something goes wrong. diff --git a/test/app/todo_test.exs b/test/app/todo_test.exs new file mode 100644 index 00000000..85cbc31d --- /dev/null +++ b/test/app/todo_test.exs @@ -0,0 +1,68 @@ +defmodule App.TodoTest do + use App.DataCase + + alias App.Todo + + describe "items" do + alias App.Todo.Item + + @valid_attrs %{person_id: 42, status: 42, text: "some text"} + @update_attrs %{person_id: 43, status: 43, text: "some updated text"} + @invalid_attrs %{person_id: nil, status: nil, text: nil} + + def item_fixture(attrs \\ %{}) do + {:ok, item} = + attrs + |> Enum.into(@valid_attrs) + |> Todo.create_item() + + item + end + + test "list_items/0 returns all items" do + item = item_fixture() + assert Todo.list_items() == [item] + end + + test "get_item!/1 returns the item with given id" do + item = item_fixture() + assert Todo.get_item!(item.id) == item + end + + test "create_item/1 with valid data creates a item" do + assert {:ok, %Item{} = item} = Todo.create_item(@valid_attrs) + assert item.person_id == 42 + assert item.status == 42 + assert item.text == "some text" + end + + test "create_item/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Todo.create_item(@invalid_attrs) + end + + test "update_item/2 with valid data updates the item" do + item = item_fixture() + assert {:ok, %Item{} = item} = Todo.update_item(item, @update_attrs) + assert item.person_id == 43 + assert item.status == 43 + assert item.text == "some updated text" + end + + test "update_item/2 with invalid data returns error changeset" do + item = item_fixture() + assert {:error, %Ecto.Changeset{}} = Todo.update_item(item, @invalid_attrs) + assert item == Todo.get_item!(item.id) + end + + test "delete_item/1 deletes the item" do + item = item_fixture() + assert {:ok, %Item{}} = Todo.delete_item(item) + assert_raise Ecto.NoResultsError, fn -> Todo.get_item!(item.id) end + end + + test "change_item/1 returns a item changeset" do + item = item_fixture() + assert %Ecto.Changeset{} = Todo.change_item(item) + end + end +end diff --git a/test/app_web/controllers/item_controller_test.exs b/test/app_web/controllers/item_controller_test.exs new file mode 100644 index 00000000..0bee10eb --- /dev/null +++ b/test/app_web/controllers/item_controller_test.exs @@ -0,0 +1,106 @@ +defmodule AppWeb.ItemControllerTest do + use AppWeb.ConnCase + + alias App.Todo + + @create_attrs %{person_id: 0, status: 0, text: "some text"} + @update_attrs %{person_id: 0, status: 1, text: "some updated text"} + @invalid_attrs %{person_id: nil, status: nil, text: nil} + + def fixture(:item) do + {:ok, item} = Todo.create_item(@create_attrs) + item + end + + describe "index" do + test "lists all items", %{conn: conn} do + conn = get(conn, Routes.item_path(conn, :index)) + assert html_response(conn, 200) =~ "todos" + end + end + + describe "new item" do + test "renders form", %{conn: conn} do + conn = get(conn, Routes.item_path(conn, :new)) + assert html_response(conn, 200) =~ "New Item" + end + end + + describe "create item" do + test "redirects to :index page when item data is valid", %{conn: conn} do + conn = post(conn, Routes.item_path(conn, :create), item: @create_attrs) + + assert redirected_to(conn) == Routes.item_path(conn, :index) + assert html_response(conn, 302) =~ "redirected" + + conn = get(conn, Routes.item_path(conn, :index)) + assert html_response(conn, 200) =~ @create_attrs.text + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, Routes.item_path(conn, :create), item: @invalid_attrs) + assert html_response(conn, 200) =~ "New Item" + end + end + + describe "edit item" do + setup [:create_item] + + test "renders form for editing chosen item", %{conn: conn, item: item} do + conn = get(conn, Routes.item_path(conn, :edit, item)) + assert html_response(conn, 200) =~ item.text + end + end + + describe "update item" do + setup [:create_item] + + test "redirects when data is valid", %{conn: conn, item: item} do + conn = put(conn, Routes.item_path(conn, :update, item), item: @update_attrs) + assert redirected_to(conn) == Routes.item_path(conn, :index) + + conn = get(conn, Routes.item_path(conn, :show, item)) + assert html_response(conn, 200) =~ "some updated text" + end + + test "renders errors when data is invalid", %{conn: conn, item: item} do + conn = put(conn, Routes.item_path(conn, :update, item), item: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Item" + end + end + + describe "delete item" do + setup [:create_item] + + test "deletes chosen item", %{conn: conn, item: item} do + conn = delete(conn, Routes.item_path(conn, :delete, item)) + conn = get(conn, Routes.item_path(conn, :show, item)) + assert html_response(conn, 200) =~ "2" # Status: 2 + end + end + + describe "toggle updates the status of an item 0 > 1 | 1 > 0" do + setup [:create_item] + + test "toggle_status/1 item.status 1 > 0", %{item: item} do + assert item.status == 0 + # first toggle + toggled_item = %{item | status: AppWeb.ItemController.toggle_status(item)} + assert toggled_item.status == 1 + # second toggle sets status back to 0 + assert AppWeb.ItemController.toggle_status(toggled_item) == 0 + end + + test "toggle/2 updates an item.status 0 > 1", %{conn: conn, item: item} do + assert item.status == 0 + get(conn, Routes.item_path(conn, :toggle, item.id)) + toggled_item = Todo.get_item!(item.id) + assert toggled_item.status == 1 + end + end + + defp create_item(_) do + item = fixture(:item) + %{item: item} + end +end diff --git a/test/app_web/views/error_view_test.exs b/test/app_web/views/error_view_test.exs new file mode 100644 index 00000000..85a326cd --- /dev/null +++ b/test/app_web/views/error_view_test.exs @@ -0,0 +1,14 @@ +defmodule AppWeb.ErrorViewTest do + use AppWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.html" do + assert render_to_string(AppWeb.ErrorView, "404.html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(AppWeb.ErrorView, "500.html", []) == "Internal Server Error" + end +end diff --git a/test/app_web/views/item_view_test.exs b/test/app_web/views/item_view_test.exs new file mode 100644 index 00000000..75d629e7 --- /dev/null +++ b/test/app_web/views/item_view_test.exs @@ -0,0 +1,44 @@ +defmodule AppWeb.ItemViewTest do + use AppWeb.ConnCase, async: true + alias AppWeb.ItemView + + test "complete/1 returns completed if item.status == 1" do + assert ItemView.complete(%{status: 1}) == "completed" + end + + test "complete/1 returns empty string if item.status == 0" do + assert ItemView.complete(%{status: 0}) == "" + end + + test "checked/1 returns checked if item.status == 1" do + assert ItemView.checked(%{status: 1}) == "checked" + end + + test "checked/1 returns empty string if item.status == 0" do + assert ItemView.checked(%{status: 0}) == "" + end + + test "remaining_items/1 returns count of items where item.status==0" do + items = [ + %{text: "one", status: 0}, + %{text: "two", status: 0}, + %{text: "done", status: 1} + ] + + assert ItemView.remaining_items(items) == 2 + end + + test "remaining_items/1 returns 0 (zero) when no items are status==0" do + items = [] + assert ItemView.remaining_items(items) == 0 + end + + test "pluralise/1 returns item for 1 item and items for < 1 <" do + assert ItemView.pluralise([%{text: "one", status: 0}]) == "item" + assert ItemView.pluralise([ + %{text: "one", status: 0}, + %{text: "two", status: 0} + ]) == "items" + assert ItemView.pluralise([%{text: "one", status: 1}]) == "items" + end +end diff --git a/test/app_web/views/layout_view_test.exs b/test/app_web/views/layout_view_test.exs new file mode 100644 index 00000000..1ba98bc8 --- /dev/null +++ b/test/app_web/views/layout_view_test.exs @@ -0,0 +1,8 @@ +defmodule AppWeb.LayoutViewTest do + use AppWeb.ConnCase, async: true + + # When testing helpers, you may want to import Phoenix.HTML and + # use functions such as safe_to_string() to convert the helper + # result into an HTML string. + # import Phoenix.HTML +end diff --git a/test/app_web/views/page_view_test.exs b/test/app_web/views/page_view_test.exs new file mode 100644 index 00000000..75067cee --- /dev/null +++ b/test/app_web/views/page_view_test.exs @@ -0,0 +1,3 @@ +defmodule AppWeb.PageViewTest do + use AppWeb.ConnCase, async: true +end diff --git a/test/controllers/page_controller_test.exs b/test/controllers/page_controller_test.exs deleted file mode 100644 index c2623a7d..00000000 --- a/test/controllers/page_controller_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Api.PageControllerTest do - use Api.ConnCase - - test "GET /", %{conn: conn} do - conn = get conn, "/" - assert html_response(conn, 200) =~ "Welcome to Phoenix!" - end -end diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex index d116b4fb..dc7f79f3 100644 --- a/test/support/channel_case.ex +++ b/test/support/channel_case.ex @@ -1,16 +1,18 @@ -defmodule Api.ChannelCase do +defmodule AppWeb.ChannelCase do @moduledoc """ This module defines the test case to be used by channel tests. Such tests rely on `Phoenix.ChannelTest` and also import other functionality to make it easier - to build and query models. + to build common data structures and query the data layer. Finally, if the test case interacts with the database, - it cannot be async. For this reason, every test runs - inside a transaction which is reset at the beginning - of the test unless the test case is marked as async. + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use AppWeb.ChannelCase, async: true`, although + this option is not recommended for other databases. """ use ExUnit.CaseTemplate @@ -18,24 +20,19 @@ defmodule Api.ChannelCase do using do quote do # Import conveniences for testing with channels - use Phoenix.ChannelTest - - alias Api.Repo - import Ecto - import Ecto.Changeset - import Ecto.Query - + import Phoenix.ChannelTest + import AppWeb.ChannelCase # The default endpoint for testing - @endpoint Api.Endpoint + @endpoint AppWeb.Endpoint end end setup tags do - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Api.Repo) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo) unless tags[:async] do - Ecto.Adapters.SQL.Sandbox.mode(Api.Repo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()}) end :ok diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 4a693454..05be8b3f 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -1,16 +1,18 @@ -defmodule Api.ConnCase do +defmodule AppWeb.ConnCase do @moduledoc """ This module defines the test case to be used by tests that require setting up a connection. Such tests rely on `Phoenix.ConnTest` and also import other functionality to make it easier - to build and query models. + to build common data structures and query the data layer. Finally, if the test case interacts with the database, - it cannot be async. For this reason, every test runs - inside a transaction which is reset at the beginning - of the test unless the test case is marked as async. + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use AppWeb.ConnCase, async: true`, although + this option is not recommended for other databases. """ use ExUnit.CaseTemplate @@ -18,25 +20,22 @@ defmodule Api.ConnCase do using do quote do # Import conveniences for testing with connections - use Phoenix.ConnTest + import Plug.Conn + import Phoenix.ConnTest + import AppWeb.ConnCase - alias Api.Repo - import Ecto - import Ecto.Changeset - import Ecto.Query - - import Api.Router.Helpers + alias AppWeb.Router.Helpers, as: Routes # The default endpoint for testing - @endpoint Api.Endpoint + @endpoint AppWeb.Endpoint end end setup tags do - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Api.Repo) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo) unless tags[:async] do - Ecto.Adapters.SQL.Sandbox.mode(Api.Repo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()}) end {:ok, conn: Phoenix.ConnTest.build_conn()} diff --git a/test/support/data_case.ex b/test/support/data_case.ex new file mode 100644 index 00000000..eb71dd1d --- /dev/null +++ b/test/support/data_case.ex @@ -0,0 +1,55 @@ +defmodule App.DataCase do + @moduledoc """ + This module defines the setup for tests requiring + access to the application's data layer. + + You may define functions here to be used as helpers in + your tests. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use App.DataCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + alias App.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import App.DataCase + end + end + + setup tags do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo) + + unless tags[:async] do + Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()}) + end + + :ok + end + + @doc """ + A helper that transforms changeset errors into a map of messages. + + assert {:error, changeset} = Accounts.create_user(%{password: "short"}) + assert "password is too short" in errors_on(changeset).password + assert %{password: ["password is too short"]} = errors_on(changeset) + + """ + def errors_on(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> + Regex.replace(~r"%{(\w+)}", message, fn _, key -> + opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() + end) + end) + end +end diff --git a/test/support/model_case.ex b/test/support/model_case.ex deleted file mode 100644 index 64748be9..00000000 --- a/test/support/model_case.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Api.ModelCase do - @moduledoc """ - This module defines the test case to be used by - model tests. - - You may define functions here to be used as helpers in - your model tests. See `errors_on/2`'s definition as reference. - - Finally, if the test case interacts with the database, - it cannot be async. For this reason, every test runs - inside a transaction which is reset at the beginning - of the test unless the test case is marked as async. - """ - - use ExUnit.CaseTemplate - - using do - quote do - alias Api.Repo - - import Ecto - import Ecto.Changeset - import Ecto.Query - import Api.ModelCase - end - end - - setup tags do - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Api.Repo) - - unless tags[:async] do - Ecto.Adapters.SQL.Sandbox.mode(Api.Repo, {:shared, self()}) - end - - :ok - end - - @doc """ - Helper for returning list of errors in a struct when given certain data. - - ## Examples - - Given a User schema that lists `:name` as a required field and validates - `:password` to be safe, it would return: - - iex> errors_on(%User{}, %{password: "password"}) - [password: "is unsafe", name: "is blank"] - - You could then write your assertion like: - - assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"}) - - You can also create the changeset manually and retrieve the errors - field directly: - - iex> changeset = User.changeset(%User{}, password: "password") - iex> {:password, "is unsafe"} in changeset.errors - true - """ - def errors_on(struct, data) do - struct.__struct__.changeset(struct, data) - |> Ecto.Changeset.traverse_errors(&Api.ErrorHelpers.translate_error/1) - |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end) - end -end diff --git a/test/test_helper.exs b/test/test_helper.exs index 32552430..4fe7a40f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,2 @@ -ExUnit.start - -Ecto.Adapters.SQL.Sandbox.mode(Api.Repo, :manual) - +ExUnit.start() +Ecto.Adapters.SQL.Sandbox.mode(App.Repo, :manual) diff --git a/test/views/error_view_test.exs b/test/views/error_view_test.exs deleted file mode 100644 index 73469013..00000000 --- a/test/views/error_view_test.exs +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Api.ErrorViewTest do - use Api.ConnCase, async: true - - # Bring render/3 and render_to_string/3 for testing custom views - import Phoenix.View - - test "renders 404.html" do - assert render_to_string(Api.ErrorView, "404.html", []) == - "Page not found" - end - - test "render 500.html" do - assert render_to_string(Api.ErrorView, "500.html", []) == - "Internal server error" - end - - test "render any other" do - assert render_to_string(Api.ErrorView, "505.html", []) == - "Internal server error" - end -end diff --git a/test/views/layout_view_test.exs b/test/views/layout_view_test.exs deleted file mode 100644 index 9e23568a..00000000 --- a/test/views/layout_view_test.exs +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Api.LayoutViewTest do - use Api.ConnCase, async: true -end diff --git a/test/views/page_view_test.exs b/test/views/page_view_test.exs deleted file mode 100644 index 9f786fcc..00000000 --- a/test/views/page_view_test.exs +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Api.PageViewTest do - use Api.ConnCase, async: true -end diff --git a/web/controllers/page_controller.ex b/web/controllers/page_controller.ex deleted file mode 100644 index 953f5648..00000000 --- a/web/controllers/page_controller.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule Api.PageController do - use Api.Web, :controller - - def index(conn, _params) do - render conn, "index.html" - end -end diff --git a/web/router.ex b/web/router.ex deleted file mode 100644 index e20d1077..00000000 --- a/web/router.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Api.Router do - use Api.Web, :router - - pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_flash - plug :protect_from_forgery - plug :put_secure_browser_headers - end - - pipeline :api do - plug :accepts, ["json"] - end - - scope "/", Api do - pipe_through :browser # Use the default browser stack - - get "/", PageController, :index - end - - # Other scopes may use custom stacks. - # scope "/api", Api do - # pipe_through :api - # end -end diff --git a/web/static/css/app.css b/web/static/css/app.css deleted file mode 100644 index 5314c34d..00000000 --- a/web/static/css/app.css +++ /dev/null @@ -1 +0,0 @@ -/* This file is for your main application css. */ \ No newline at end of file diff --git a/web/static/css/phoenix.css b/web/static/css/phoenix.css deleted file mode 100644 index df69d80a..00000000 --- a/web/static/css/phoenix.css +++ /dev/null @@ -1,82 +0,0 @@ -/* Includes Bootstrap as well as some default style for the starter - * application. This can be safely deleted to start fresh. - */ - -/*! - * Bootstrap v3.3.5 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}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{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!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:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.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;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,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,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.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 ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;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{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.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-md-1,.col-md-10,.col-md-11,.col-md-12,.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-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.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-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{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>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border: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;max-width:100%;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=checkbox],input[type=radio]{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=checkbox]:focus,input[type=radio]: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,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.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{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{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;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{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;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{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;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-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;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{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.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{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.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{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.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{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;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;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)}.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:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left: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-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.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-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.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}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn: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=checkbox],.input-group-addon input[type=radio]{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-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-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:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-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:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #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:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.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:focus,.nav-tabs.nav-justified>.active>a:hover{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:focus,.nav-tabs.nav-justified>.active>a:hover{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:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.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:focus,.nav-tabs-justified>.active>a:hover{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:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-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{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{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-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{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;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-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)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{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:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{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:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{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:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.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{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.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;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.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:focus,.pager li>a:hover{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:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.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}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left: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:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.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,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;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:#337ab7;-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;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.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:-o-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);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-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:-o-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:-o-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:-o-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{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.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-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{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-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.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-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.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;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.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:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;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{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;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);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;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{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";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{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.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%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;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-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} - -/* Space out content a bit */ -body, form, ul, table { - margin-top: 20px; - margin-bottom: 20px; -} - -/* Phoenix flash messages */ -.alert:empty { display: none; } - -/* Phoenix inline forms in links and buttons */ -form.link, form.button { - display: inline; -} - -/* Custom page header */ -.header { - border-bottom: 1px solid #e5e5e5; -} -.logo { - width: 519px; - height: 71px; - display: inline-block; - margin-bottom: 1em; - background-image: url("/images/phoenix.png"); - background-size: 519px 71px; -} - -/* Everything but the jumbotron gets side spacing for mobile first views */ -.header, -.marketing { - padding-right: 15px; - padding-left: 15px; -} - -/* Customize container */ -@media (min-width: 768px) { - .container { - max-width: 730px; - } -} -.container-narrow > hr { - margin: 30px 0; -} - -/* Main marketing message */ -.jumbotron { - text-align: center; - border-bottom: 1px solid #e5e5e5; -} - -/* Supporting marketing content */ -.marketing { - margin: 35px 0; -} - -/* Responsive: Portrait tablets and up */ -@media screen and (min-width: 768px) { - /* Remove the padding we set earlier */ - .header, - .marketing { - padding-right: 0; - padding-left: 0; - } - /* Space out the masthead */ - .header { - margin-bottom: 30px; - } - /* Remove the bottom border on the jumbotron for visual effect */ - .jumbotron { - border-bottom: 0; - } -} \ No newline at end of file diff --git a/web/static/js/app.js b/web/static/js/app.js deleted file mode 100644 index e7549b9d..00000000 --- a/web/static/js/app.js +++ /dev/null @@ -1,21 +0,0 @@ -// Brunch automatically concatenates all files in your -// watched paths. Those paths can be configured at -// config.paths.watched in "brunch-config.js". -// -// However, those files will only be executed if -// explicitly imported. The only exception are files -// in vendor, which are never wrapped in imports and -// therefore are always executed. - -// Import dependencies -// -// If you no longer want to use a dependency, remember -// to also remove its path from "config.paths.watched". -import "phoenix_html" - -// Import local files -// -// Local files can be imported directly using relative -// paths "./socket" or full ones "web/static/js/socket". - -// import socket from "./socket" diff --git a/web/templates/layout/app.html.eex b/web/templates/layout/app.html.eex deleted file mode 100644 index 83a8e818..00000000 --- a/web/templates/layout/app.html.eex +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - Hello Api! - "> - - - -
-
- - -
- - - - -
- <%= render @view_module, @view_template, assigns %> -
- -
- - - diff --git a/web/templates/page/index.html.eex b/web/templates/page/index.html.eex deleted file mode 100644 index 8ff4b818..00000000 --- a/web/templates/page/index.html.eex +++ /dev/null @@ -1,36 +0,0 @@ -
-

<%= gettext "Welcome to %{name}", name: "Phoenix!" %>

-

A productive web framework that
does not compromise speed and maintainability.

-
- -
-
-

Resources

- -
- - -
diff --git a/web/views/error_helpers.ex b/web/views/error_helpers.ex deleted file mode 100644 index 107e98fe..00000000 --- a/web/views/error_helpers.ex +++ /dev/null @@ -1,40 +0,0 @@ -defmodule Api.ErrorHelpers do - @moduledoc """ - Conveniences for translating and building error messages. - """ - - use Phoenix.HTML - - @doc """ - Generates tag for inlined form input errors. - """ - def error_tag(form, field) do - if error = form.errors[field] do - content_tag :span, translate_error(error), class: "help-block" - end - end - - @doc """ - Translates an error message using gettext. - """ - def translate_error({msg, opts}) do - # Because error messages were defined within Ecto, we must - # call the Gettext module passing our Gettext backend. We - # also use the "errors" domain as translations are placed - # in the errors.po file. - # Ecto will pass the :count keyword if the error message is - # meant to be pluralized. - # On your own code and templates, depending on whether you - # need the message to be pluralized or not, this could be - # written simply as: - # - # dngettext "errors", "1 file", "%{count} files", count - # dgettext "errors", "is invalid" - # - if count = opts[:count] do - Gettext.dngettext(Api.Gettext, "errors", msg, msg, count, opts) - else - Gettext.dgettext(Api.Gettext, "errors", msg, opts) - end - end -end diff --git a/web/views/error_view.ex b/web/views/error_view.ex deleted file mode 100644 index b1725e39..00000000 --- a/web/views/error_view.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Api.ErrorView do - use Api.Web, :view - - def render("404.html", _assigns) do - "Page not found" - end - - def render("500.html", _assigns) do - "Internal server error" - end - - # In case no render clause matches or no - # template is found, let's render it as 500 - def template_not_found(_template, assigns) do - render "500.html", assigns - end -end diff --git a/web/views/layout_view.ex b/web/views/layout_view.ex deleted file mode 100644 index 0e0f4944..00000000 --- a/web/views/layout_view.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Api.LayoutView do - use Api.Web, :view -end diff --git a/web/views/page_view.ex b/web/views/page_view.ex deleted file mode 100644 index 69e35a99..00000000 --- a/web/views/page_view.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Api.PageView do - use Api.Web, :view -end diff --git a/web/web.ex b/web/web.ex deleted file mode 100644 index 8f1c3b68..00000000 --- a/web/web.ex +++ /dev/null @@ -1,81 +0,0 @@ -defmodule Api.Web do - @moduledoc """ - A module that keeps using definitions for controllers, - views and so on. - - This can be used in your application as: - - use Api.Web, :controller - use Api.Web, :view - - The definitions below will be executed for every view, - controller, etc, so keep them short and clean, focused - on imports, uses and aliases. - - Do NOT define functions inside the quoted expressions - below. - """ - - def model do - quote do - use Ecto.Schema - - import Ecto - import Ecto.Changeset - import Ecto.Query - end - end - - def controller do - quote do - use Phoenix.Controller - - alias Api.Repo - import Ecto - import Ecto.Query - - import Api.Router.Helpers - import Api.Gettext - end - end - - def view do - quote do - use Phoenix.View, root: "web/templates" - - # Import convenience functions from controllers - import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] - - # Use all HTML functionality (forms, tags, etc) - use Phoenix.HTML - - import Api.Router.Helpers - import Api.ErrorHelpers - import Api.Gettext - end - end - - def router do - quote do - use Phoenix.Router - end - end - - def channel do - quote do - use Phoenix.Channel - - alias Api.Repo - import Ecto - import Ecto.Query - import Api.Gettext - end - end - - @doc """ - When used, dispatch to the appropriate controller/view/etc. - """ - defmacro __using__(which) when is_atom(which) do - apply(__MODULE__, which, []) - end -end