├── tmp └── projects │ └── .gitkeep ├── public ├── images │ └── favicon.png └── bootstrap │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.svg │ ├── css │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.css.map │ └── bootstrap-theme.css │ └── js │ └── bootstrap.min.js ├── activator-launch-1.1.2.jar ├── project ├── build.properties └── plugins.sbt ├── app ├── views │ ├── index.scala.html │ ├── project.scala.html │ ├── sidebar │ │ ├── projects.scala.html │ │ └── lock.scala.html │ ├── welcome.scala.html │ ├── layout.scala.html │ ├── commits.scala.html │ └── main.scala.html ├── assets │ ├── stylesheets │ │ ├── main.less │ │ └── commits.less │ └── javascripts │ │ └── main.coffee ├── models │ ├── ProcessEnumerator.scala │ ├── Lock.scala │ ├── UnbufferedCommand.scala │ ├── User.scala │ ├── LdapSearcher.scala │ ├── WorkingDir.scala │ ├── Repo.scala │ ├── Hook.scala │ ├── Commit.scala │ └── Project.scala ├── Global.scala └── controllers │ └── Application.scala ├── .gitignore ├── .travis.yml ├── test ├── IntegrationSpec.scala └── ApplicationSpec.scala ├── Dockerfile ├── LICENSE ├── conf ├── messages.ja ├── messages ├── routes └── application.conf ├── README.md └── activator /tmp/projects/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edvakf/pploy/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /activator-launch-1.1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edvakf/pploy/HEAD/activator-launch-1.1.2.jar -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edvakf/pploy/HEAD/public/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edvakf/pploy/HEAD/public/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edvakf/pploy/HEAD/public/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | #Activator-generated Properties 2 | #Sun Aug 03 21:56:29 JST 2014 3 | template.uuid=688014f0-0585-43e3-a9fa-d62f7d12f1ab 4 | sbt.version=0.13.15 5 | -------------------------------------------------------------------------------- /app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @()(implicit request: Request[AnyContent], messages: Messages, flash: Flash) 2 | 3 | @layout{ 4 | @welcome() 5 | }{ 6 | @sidebar.projects(None) 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/main.less: -------------------------------------------------------------------------------- 1 | .sidebar-section { 2 | margin-top: 1em; 3 | margin-bottom: 1em; 4 | padding: 1em; 5 | } 6 | .box { 7 | background-color: #eee; 8 | } 9 | .log-frame { 10 | border: solid black 2px !important; //override twitter bootstrap's embed-responsive iframe 11 | } 12 | .log-frame.loading { 13 | transition-duration: 0.4s; 14 | transition-property: border; 15 | border: solid red 2px !important; 16 | } 17 | -------------------------------------------------------------------------------- /app/views/project.scala.html: -------------------------------------------------------------------------------- 1 | @import models.Project 2 | @import models.User 3 | 4 | @(currentProject: Project, currentUserOption: Option[User], useWebSocket: Boolean)(implicit request: Request[AnyContent], messages: Messages, flash: Flash) 5 | 6 | @layout{ 7 | @main(currentProject, currentUserOption, useWebSocket) 8 | }{ 9 | @sidebar.lock(currentProject, currentUserOption) 10 | @sidebar.projects(Some(currentProject)) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 2.11.7 3 | sudo: false 4 | jdk: oraclejdk8 5 | script: sbt dist 6 | deploy: 7 | provider: releases 8 | api_key: 9 | secure: HqzrciYL691qxmq8/1gv7j+Ogm9MHVC/tsfW+7rO0LvJn8NbWH6/0gXSkt4CtP0PimmQt+vh98x+tPALwPJCSoV38KEfdtTTD9yBdbaDK17YBEXsbwwr5TRPX7K/Kv5+nhtAtWxXhln+12S7sa7i0q4Va4Ssh2MN5I0A8vFJSL4= 10 | file: target/universal/pploy.zip 11 | on: 12 | repo: edvakf/pploy 13 | all_branches: true 14 | tags: true 15 | -------------------------------------------------------------------------------- /test/IntegrationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{ FunSuite, Matchers } 2 | import play.api.test.TestServer 3 | import play.api.test.Helpers._ 4 | 5 | class IntegrationSpec extends FunSuite with Matchers { 6 | 7 | test("work from within a browser") { 8 | val port = 3333 9 | running(TestServer(port), HTMLUNIT) { browser => 10 | browser.goTo("http://localhost:" + port) 11 | browser.$("h2").getTexts.get(0) should be("Welcome to pploy") 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /test/ApplicationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatestplus.play.PlaySpec 2 | import play.api.test.Helpers._ 3 | import play.api.test._ 4 | 5 | class ApplicationSpec extends PlaySpec { 6 | 7 | "Application" must { 8 | "should render the index page" in { 9 | running(FakeApplication()) { 10 | val home = route(FakeRequest(GET, "/")).get 11 | status(home) must be(OK) 12 | contentType(home) must be(Some("text/html")) 13 | contentAsString(home) must include("Welcome to pploy") 14 | } 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/models/ProcessEnumerator.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import play.api.libs.iteratee.Enumerator 4 | import scala.sys.process._ 5 | import scala.concurrent.ExecutionContext.Implicits.global 6 | 7 | object ProcessEnumerator { 8 | // wraps a ProcessBuilder with Play's Enumerator 9 | // and executes the process in Future 10 | // so that the process' output can be streamed 11 | def apply(process: ProcessBuilder): Enumerator[String] = { 12 | Enumerator.enumerate[String]( 13 | process.lineStream_!(ProcessLogger(line => ())).map { line => line + "\n" } 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ### how to build in docker 2 | # 3 | # TAG=$(git log --oneline -n 1 | cut -f 1 -d ' ') 4 | # NAME=pploy-$TAG 5 | # 6 | # docker build -t pploy-build . 7 | # mkdir -p $PWD/.ivy2 $PWD/.sbt 8 | # docker rm -f $NAME || true 9 | # docker run --name=$NAME -v $PWD/.ivy2:/root/.ivy2 -v $PWD/.sbt:/root/.sbt \ 10 | # pploy-build \ 11 | # bash -c './activator dist' 12 | # 13 | # docker cp $NAME:/pploy/target/universal/pploy-1.0-SNAPSHOT.zip ./ 14 | # docker rm -f $NAME || true 15 | 16 | FROM java:8-jdk 17 | 18 | COPY . /pploy 19 | WORKDIR /pploy 20 | 21 | VOLUME ["/root/.ivy2", "/root/.sbt"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the Apache 2 license, quoted below. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with 4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 5 | 6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 8 | language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 2 | 3 | // The Play plugin 4 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.11") 5 | 6 | // web plugins 7 | 8 | addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0") 9 | 10 | addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.0") 11 | 12 | addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.0") 13 | 14 | addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.1") 15 | 16 | addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0") 17 | 18 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.6.0") 19 | -------------------------------------------------------------------------------- /app/views/sidebar/projects.scala.html: -------------------------------------------------------------------------------- 1 | @import models.Project 2 | 3 | @(currentProjectOption: Option[Project])(implicit messages: Messages) 4 | 5 | 21 | -------------------------------------------------------------------------------- /app/models/Lock.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import play.api.Play.current 4 | import play.api.cache.Cache 5 | 6 | import org.joda.time._ 7 | 8 | case class Lock(project: Project, user: User, endTime: DateTime) { 9 | def save() = Cache.set("lock_" + project.name, this) 10 | def delete() = Cache.remove("lock_" + project.name) 11 | 12 | def secondsLeft = Seconds.secondsBetween(new DateTime(), endTime).getSeconds 13 | def minutesAndSecondsLeft = f"${secondsLeft / 60}%02d:${secondsLeft % 60}%02d" 14 | } 15 | 16 | object Lock { 17 | def fetch(project: Project) = { 18 | Cache.getAs[Lock]("lock_" + project.name) 19 | } 20 | 21 | def save(project: Project, user: User, endTime: DateTime) = { 22 | new Lock(project, user, endTime).save() 23 | } 24 | } -------------------------------------------------------------------------------- /app/models/UnbufferedCommand.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import scala.sys.process._ 4 | 5 | object UnbufferedCommand { 6 | // libc does not line buffer if output is not a terminal, instead use full buffering. 7 | // `script` runs a given command in a pseudo terminal. 8 | // it also redirects stderr to stdout which fits our usage 9 | 10 | // see: http://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe 11 | 12 | private lazy val hasStdbufCommand = { 13 | 0 == Process("which stdbuf").! 14 | } 15 | 16 | def apply(command: String): Seq[String] = { 17 | if (hasStdbufCommand) { 18 | Seq("bash", "-c", "stdbuf -oL -eL " + command + " 2>&1") // Linux 19 | } else { 20 | Seq("script", "-q", "/dev/null", "bash", "-c", command) // Mac OS X 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /conf/messages.ja: -------------------------------------------------------------------------------- 1 | welcome=pployへようこそ 2 | welcome.click_sidebar=サイドバーのプロジェクト名をクリックしてください 3 | welcome.add_project=プロジェクトを追加する 4 | welcome.remove_project=プロジェクトを削除する 5 | welcome.remove_project_confirmation=プロジェクト"{0}"を本当に削除しますか? 6 | main.recent_commits=最近のコミット 7 | main.checkout=チェックアウト 8 | main.deploy={0}環境にデプロイ 9 | main.previous_log=前回実行ログ 10 | sidebar.header.projects=プロジェクト 11 | sidebar.user.noselect=選択してください 12 | sidebar.lock.user=デプロイ中 13 | sidebar.lock.extend={0}分延長する 14 | sidebar.lock.release=デプロイを終了する 15 | sidebar.lock.gain=デプロイを開始する 16 | sidebar.lock.time_left=残り時間 17 | lock.operation.expired=デプロイ時間切れです 18 | lock.operation.taken=他の人がデプロイ中です 19 | hook.lock.gained=[{0}] {1}さんがデプロイ中になりました 20 | hook.lock.released=[{0}] {1}さんがデプロイを終了しました 21 | hook.lock.extended=[{0}] {1}さんがデプロイを延長しました 22 | hook.deployed=[{0}] {1}さんが{2}環境にデプロイしました 23 | -------------------------------------------------------------------------------- /conf/messages: -------------------------------------------------------------------------------- 1 | welcome=Welcome to pploy 2 | welcome.click_sidebar=Click on a project in the sidebar. 3 | welcome.add_project=Add a project 4 | welcome.remove_project=Remove a project 5 | welcome.remove_project_confirmation=Are you sure to remove {0}? 6 | main.recent_commits=Recent Commits 7 | main.checkout=Checkout 8 | main.deploy=Deploy to {0} environment 9 | main.deploy.staging=Deploy to staging 10 | main.previous_log=Previous log 11 | sidebar.header.projects=Projects 12 | sidebar.user.noselect=Please Select 13 | sidebar.lock.user=Working 14 | sidebar.lock.extend=Extend {0} minutes 15 | sidebar.lock.release=Finish deploying 16 | sidebar.lock.gain=Start deploying 17 | sidebar.lock.time_left=Time left 18 | lock.operation.expired=Your lock is expired 19 | lock.operation.taken=Lock to this project is held by someone else 20 | hook.lock.gained=[{0}] {1} has started a deploy session 21 | hook.lock.released=[{0}] {1} has finished a deploy session 22 | hook.lock.extended=[{0}] {1} has extended the deploy session 23 | hook.deployed=[{0}] {1} has deployed to {2} environment 24 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Home page 6 | GET / controllers.Application.index() 7 | POST /_/create controllers.Application.create() 8 | GET /:project controllers.Application.project(project) 9 | GET /:project/lock controllers.Application.lockUser(project) 10 | POST /:project/lock controllers.Application.lock(project) 11 | POST /:project/checkout controllers.Application.checkout(project) 12 | GET /:project/checkout controllers.Application.checkoutWS(project, ref) 13 | GET /:project/commits controllers.Application.commits(project) 14 | POST /:project/deploy controllers.Application.deploy(project) 15 | GET /:project/deploy controllers.Application.deployWS(project, target) 16 | GET /:project/logs controllers.Application.logs(project, full: Long ?= 0) 17 | POST /:project/remove controllers.Application.remove(project) 18 | 19 | # Map static resources from the /public folder to the /assets URL path 20 | GET /assets/*file controllers.Assets.at(path="/public", file) 21 | -------------------------------------------------------------------------------- /app/views/welcome.scala.html: -------------------------------------------------------------------------------- 1 | @import helper._ 2 | 3 | @()(implicit request: Request[AnyContent], messages: Messages) 4 | 5 |

@Messages("welcome")

6 | 7 |

@Messages("welcome.click_sidebar")

8 | 9 |

@Messages("welcome.add_project")

10 | 11 |
12 | @CSRF.formField 13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | @if(!Project.allNames.isEmpty) { 22 |

@Messages("welcome.remove_project")

23 | 24 | 25 | @Project.allNames.map { name => 26 | 27 | 34 | 35 | } 36 |
28 |
29 | 30 | @CSRF.formField 31 | @name 32 |
33 |
37 | } 38 | -------------------------------------------------------------------------------- /app/models/User.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import play.api.Logger 4 | import play.api.Play.current 5 | import play.api.cache.Cache 6 | 7 | case class User(name: String) { 8 | if (!User.allNames.contains(name)) { 9 | throw new IllegalArgumentException("bad user name") 10 | } 11 | } 12 | 13 | object User { 14 | def allNames: Seq[String] = { 15 | current.configuration.getString("pploy.ldap.url").map { url => 16 | // if url is present, assume others are present too 17 | 18 | val login = current.configuration.getString("pploy.ldap.login").get 19 | val password = current.configuration.getString("pploy.ldap.password").get 20 | val search = current.configuration.getString("pploy.ldap.search").get 21 | 22 | def fetchLdap = { 23 | Logger.info("fetching users from LDAP: " + search) 24 | new LdapSearcher(url, login, password).search(search) 25 | } 26 | 27 | current.configuration.getInt("pploy.ldap.cachettl").fold(fetchLdap) { ttl => 28 | Cache.getAs[Seq[String]]("ldap_users").getOrElse { 29 | val names = fetchLdap 30 | Cache.set("ldap_users", names, ttl) 31 | names 32 | } 33 | } 34 | 35 | }.getOrElse( 36 | // otherwise get users from pploy.users 37 | current.configuration.getStringSeq("pploy.users").get 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/models/LdapSearcher.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.util 4 | import javax.naming.Context 5 | import javax.naming.directory.SearchControls 6 | import javax.naming.ldap.InitialLdapContext 7 | 8 | import scala.collection.mutable.ListBuffer 9 | 10 | class LdapSearcher(url: String, user: String, pass: String) { 11 | val env = new util.Hashtable[String, String]() 12 | env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") 13 | env.put(Context.PROVIDER_URL, url) 14 | env.put(Context.SECURITY_PRINCIPAL, user) 15 | env.put(Context.SECURITY_CREDENTIALS, pass) 16 | 17 | // http://stackoverflow.com/questions/2172831/how-do-a-ldap-search-authenticate-against-this-ldap-in-java 18 | def search(where: String) = { 19 | val ctx = new InitialLdapContext(env, null) 20 | ctx.setRequestControls(null) 21 | val namingEnum = ctx.search(where, "cn=*", getSimpleSearchControls) 22 | val users = new ListBuffer[String] 23 | while (namingEnum.hasMore) { 24 | val attr = namingEnum.next().getAttributes.get("cn") 25 | if (attr.size > 0) users += attr.get(0).toString 26 | } 27 | namingEnum.close() 28 | users.toSeq 29 | } 30 | 31 | private def getSimpleSearchControls = { 32 | val searchControls = new SearchControls() 33 | searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE) 34 | searchControls.setTimeLimit(30000) 35 | searchControls 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/models/WorkingDir.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.io.File 4 | 5 | import play.api.Logger 6 | import play.api.Play.current 7 | import scala.collection.JavaConversions._ 8 | 9 | object WorkingDir { 10 | 11 | val dirname = current.configuration.getString("pploy.dir").getOrElse(sys.error("pploy.dir is not set")) 12 | 13 | val projectsDir = new File(dirname, "projects") 14 | 15 | val logsDir = new File(dirname, "logs") 16 | 17 | def setup(): Unit = { 18 | mkdir(new File(dirname)) 19 | mkdir(projectsDir) 20 | mkdir(logsDir) 21 | } 22 | 23 | def projectDir(project: String) = new File(projectsDir, project) 24 | 25 | def logFile(project: String) = new File(logsDir, project + ".log") 26 | 27 | private def mkdir(dir: File): Unit = { 28 | if (!dir.exists()) { 29 | Logger.info("making directory: " + dir.toString) 30 | dir.mkdirs() 31 | } else if (!dir.isDirectory) { 32 | throw new RuntimeException("working directory is taken!") 33 | } 34 | } 35 | 36 | def projects = { 37 | // FIXME: handle irregular files in projects dir 38 | projectsDir.listFiles().map { _.getName }.sorted 39 | } 40 | 41 | def removeProjectFiles(project: String) = { 42 | delete(projectDir(project)) 43 | delete(logFile(project)) 44 | } 45 | 46 | private def delete(f: File): Unit = { 47 | if (f.exists) { 48 | if (f.isDirectory) { 49 | f.listFiles.map(delete) 50 | } 51 | if (!f.delete()) 52 | throw new RuntimeException("Failed to delete file: " + f) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/views/layout.scala.html: -------------------------------------------------------------------------------- 1 | @(mainContent: Html)(sidebarContent: Html)(implicit lang: Lang, flash: Flash) 2 | 3 | 4 | 5 | 6 | 7 | pploy 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 30 |
31 |
32 |
33 | @mainContent 34 |
35 | 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /app/Global.scala: -------------------------------------------------------------------------------- 1 | import java.net.URL 2 | 3 | import play.api.Play.current 4 | import play.api._ 5 | import play.api.mvc._ 6 | import play.filters.csrf._ 7 | import play.api.mvc.Results._ 8 | import scala.concurrent.Future 9 | import org.apache.commons.lang3.exception.ExceptionUtils 10 | import models._ 11 | 12 | object Global extends WithFilters(CSRFFilter()) with GlobalSettings { 13 | 14 | lazy val isDev = Play.isDev(current) 15 | 16 | override def onStart(app: Application) { 17 | WorkingDir.setup() 18 | } 19 | 20 | private def backUrl(request: RequestHeader) = { 21 | request.headers.get("referer") match { 22 | case Some(url) => new URL(url).getFile 23 | case None => controllers.routes.Application.index().toString() 24 | } 25 | } 26 | 27 | override def onError(request: RequestHeader, ex: Throwable) = { 28 | if (isDev) super.onError(request, ex) 29 | else { 30 | Logger.info(ex.getMessage) 31 | Logger.info(ExceptionUtils.getStackTrace(ex)) 32 | Future.successful( 33 | Redirect(backUrl(request)) 34 | .flashing("message" -> ex.getMessage) 35 | ) 36 | } 37 | } 38 | 39 | override def onHandlerNotFound(request: RequestHeader) = { 40 | if (isDev) super.onHandlerNotFound(request) 41 | else { 42 | Future.successful( 43 | Redirect(backUrl(request)) 44 | ) 45 | } 46 | } 47 | 48 | override def onBadRequest(request: RequestHeader, error: String) = { 49 | if (isDev) super.onBadRequest(request, error) 50 | else { 51 | Future.successful( 52 | Redirect(backUrl(request)) 53 | .flashing("message" -> error) 54 | ) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/assets/stylesheets/commits.less: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | .commits { 6 | table { 7 | width: 100%; 8 | border-collapse: collapse; 9 | box-sizing: border-box; 10 | } 11 | 12 | th, td { 13 | padding: 3px; 14 | border: solid gray 1px; 15 | box-sizing: border-box; 16 | 17 | // http://stackoverflow.com/questions/19068909/why-is-box-sizing-acting-different-on-table-vs-div?answertab=active#tab-top 18 | &:first-child { 19 | border-left: none; 20 | } 21 | &:last-child { 22 | border-right: none; 23 | } 24 | } 25 | 26 | th { 27 | border-bottom: solid black 2px; 28 | background: #eee; 29 | font-size: 14px; 30 | } 31 | 32 | td { 33 | font-size: 14px; 34 | font-family: "Consolas", "Courier New", "Courier", "monospace"; 35 | } 36 | 37 | .header { 38 | background: #eee; 39 | } 40 | 41 | .subject { 42 | background: #eee; 43 | font-weight: bold; 44 | } 45 | 46 | .code { 47 | padding: 10px 6px 20px; 48 | 49 | pre { 50 | margin: 0; 51 | font-family: "Consolas", "Courier New", "Courier", "monospace"; 52 | } 53 | } 54 | } 55 | 56 | .hash, .hash:link, .hash:visited { 57 | color: #2fa0bb; 58 | font-weight: bold; 59 | text-decoration: none; 60 | } 61 | 62 | .ref { 63 | display: inline-block; 64 | margin: 2px 0; 65 | padding: 2px 4px; 66 | border-radius: 4px; 67 | background: #999; 68 | color: #fff; 69 | font-size: 12px; 70 | 71 | &.tag { 72 | background: #418be5; 73 | color: #fff; 74 | } 75 | 76 | &.master { 77 | background: #00c53d; 78 | color: #fff; 79 | } 80 | 81 | &.head { 82 | background: #e53d5f; 83 | color: #fff; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/views/sidebar/lock.scala.html: -------------------------------------------------------------------------------- 1 | @import helper._ 2 | @import models.Project 3 | @import models.User 4 | 5 | @(currentProject: Project, currentUserOption: Option[User])(implicit request: Request[AnyContent], messages: Messages) 6 | 7 | 38 | 39 | -------------------------------------------------------------------------------- /app/models/Repo.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.io._ 4 | import org.eclipse.jgit.api._ 5 | import org.eclipse.jgit.transport.URIish 6 | import play.api.Play._ 7 | import scala.collection.JavaConversions._ 8 | import scala.sys.process.Process 9 | 10 | case class Repo(name: String) { 11 | lazy val dir = WorkingDir.projectDir(name) 12 | lazy val git = Git.open(dir) 13 | 14 | def readmeFile: Option[File] = { 15 | val file = new File(dir, ".deploy/config/readme.html") 16 | if (file.isFile) { Some(file) } else { None } 17 | } 18 | 19 | def deployEnvsFile: Option[File] = { 20 | val file = new File(dir, ".deploy/config/deploy_envs") 21 | if (file.isFile) { Some(file) } else { None } 22 | } 23 | 24 | val deployCommand = ".deploy/bin/deploy" 25 | 26 | def checkoutCommand = { 27 | val checkoutScript = ".deploy/bin/checkout_overwrite" 28 | // if checkout_overwrite script exists 29 | if (new File(dir, checkoutScript).isFile) { 30 | checkoutScript 31 | } else { 32 | "bash -x -c '" + Seq( 33 | "git fetch --prune", 34 | "git checkout -f $DEPLOY_COMMIT", 35 | "git reset --hard $DEPLOY_COMMIT", 36 | "git clean -fdx", 37 | "git submodule sync", 38 | "git submodule init", 39 | "git submodule update --recursive" 40 | ).mkString(" && ") + "'" 41 | } 42 | } 43 | 44 | def commits = { 45 | val logs = Process(Seq("git", "log", "-n", Repo.commitLength.toString) ++ Commit.gitLogOption, dir).!! 46 | Commit.parse(logs) 47 | } 48 | } 49 | 50 | object Repo { 51 | val commitLength = current.configuration.getInt("pploy.commits.length").getOrElse(20) 52 | 53 | def clone(url: String): Repo = { 54 | val uriish = new URIish(url) 55 | Process(Seq("git", "clone", url, "--depth", Repo.commitLength.toString), WorkingDir.projectsDir).! 56 | Repo(uriish.getHumanishName) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/views/commits.scala.html: -------------------------------------------------------------------------------- 1 | @import models.Commit 2 | 3 | @(commits: Iterable[Commit]) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | @commits.map { commit => 16 | 17 | 20 | 21 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | } 54 | 55 |
18 | @commit.hash.take(7) 19 | @commit.author 22 | @commit.otherRefs.map { ref => 23 | @ref match { 24 | case "HEAD" => { 25 | HEAD 26 | } 27 | case _ if ref.startsWith("refs/remotes/origin/master") => { 28 | origin/master 29 | } 30 | case _ if ref.startsWith("refs/remotes/") => { 31 | @ref.stripPrefix("refs/remotes/") 32 | } 33 | case _ if ref.startsWith("refs/heads/") => {} 34 | case _ if ref.startsWith("refs/tags/") => { 35 | @ref.stripPrefix("refs/tags/") 36 | } 37 | case _ => { 38 | @ref 39 | } 40 | } 41 | } 42 | @commit.dateTimeString
@commit.subject
50 |
@commit.nameStatus
51 |
56 |
57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/models/Hook.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import play.api.Play._ 4 | import play.api.i18n.Messages.Implicits._ 5 | import play.api.i18n.{ Lang, Messages } 6 | import play.api.libs.json._ 7 | import play.api.libs.ws.WS 8 | 9 | object Hook { 10 | val idobata = current.configuration.getString("pploy.idobata.endpoint").map { 11 | url => new IdobataGenericHook(url) 12 | } 13 | val slack = current.configuration.getString("pploy.slack.endpoint").map { 14 | url => new SlackIncomingWebHook(url) 15 | } 16 | implicit val lang = Lang.availables.head 17 | 18 | def lockGained(projectName: String, userName: String) = { 19 | idobata.foreach { _.post(Messages("hook.lock.gained", projectName, userName)) } 20 | slack.foreach { _.post(Messages("hook.lock.gained", projectName, userName)) } 21 | } 22 | 23 | def lockReleased(projectName: String, userName: String) = { 24 | idobata.foreach { _.post(Messages("hook.lock.released", projectName, userName)) } 25 | slack.foreach { _.post(Messages("hook.lock.released", projectName, userName)) } 26 | } 27 | 28 | def lockExtended(projectName: String, userName: String) = { 29 | idobata.foreach { _.post(Messages("hook.lock.extended", projectName, userName)) } 30 | slack.foreach { _.post(Messages("hook.lock.extended", projectName, userName)) } 31 | } 32 | 33 | def deployed(projectName: String, userName: String, target: String) = { 34 | idobata.foreach { _.post(Messages("hook.deployed", projectName, userName, target)) } 35 | slack.foreach { _.post(Messages("hook.deployed", projectName, userName, target)) } 36 | } 37 | 38 | } 39 | 40 | class IdobataGenericHook(endpoint: String) { 41 | def post(html: String): Unit = { 42 | WS.url(endpoint).post(Map("format" -> Seq("html"), "source" -> Seq(html))) 43 | } 44 | } 45 | 46 | class SlackIncomingWebHook(endpoint: String) { 47 | def post(text: String): Unit = { 48 | val payload = Json.obj( 49 | "text" -> text 50 | ) 51 | WS.url(endpoint).post(payload) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/models/Commit.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import org.joda.time.DateTime 4 | import org.joda.time.format.{ ISODateTimeFormat, DateTimeFormat } 5 | 6 | case class Commit(hash: String, dateTime: DateTime, author: String, otherRefs: Seq[String], subject: String, body: String, nameStatus: String) { 7 | def dateTimeString = dateTime.toString(DateTimeFormat.longDateTime()) 8 | } 9 | 10 | object Commit { 11 | private val delim1 = "1PPLOY1YOLPP1" 12 | private val delim2 = "2PPLOY2YOLPP2" 13 | 14 | val gitLogOption = Seq( 15 | "--decorate=full", // prefix refs with refs/heads/, refs/remotes/origin/ and so on 16 | "--name-status", // show list of file diffs 17 | "-m", // show file diffs for merge commit 18 | "--first-parent", // -m shows file diffs from each parent. --first-parent make it from the first parent 19 | s"--pretty=format:${delim1}%H${delim2}%ci${delim2}%an${delim2}%d${delim2}%s${delim2}%b${delim2}" 20 | ) 21 | 22 | def parse(logs: String): Seq[Commit] = { 23 | logs.split(delim1).drop(1).map { log => 24 | log.split(delim2).toSeq match { 25 | case Seq(hash, isoLikeDate, author, refs, subject, body, nameStatus) => 26 | val re = """^ \((.*?)\)$""".r // git 1.7 doesn't have `%D` format, which is the same as `%d` without the parens 27 | val otherRefs: Seq[String] = refs match { 28 | case re(foo) => 29 | foo.split(", ") 30 | .flatMap(_.split(" -> ")) // master becomes "HEAD -> refs/head/master" 31 | .filterNot(_ == "") // filter out the case when refs is an empty string 32 | case _ => 33 | Seq() 34 | } 35 | 36 | Commit( 37 | hash, 38 | // newer version of git has `%cI` format which can be parsed with `ISODateTimeFormat.dateTimeNoMillis`, but git 1.7 doesn't... 39 | DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss Z").parseDateTime(isoLikeDate), 40 | author, 41 | otherRefs, 42 | subject, 43 | body.trim, 44 | nameStatus.trim 45 | ) 46 | case _ => 47 | throw new RuntimeException(s"failed to parse commit log: ${log}") 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @import helper._ 2 | @import models.Project 3 | @import models.User 4 | 5 | @(currentProject: Project, currentUserOption: Option[User], useWebSocket: Boolean)(implicit request: Request[AnyContent], messages: Messages) 6 | 7 | @if(useWebSocket) { 8 | 9 | } 10 | 11 |
12 |
13 |

@currentProject.name

14 |
15 | 16 | @if(currentProject.isLockedBy(currentUserOption)) { 17 |
18 |

@Messages("main.checkout")

19 |
20 | @CSRF.formField 21 | 22 | 23 |
24 |
25 | @CSRF.formField 26 | @currentProject.deployEnvs.map { env => 27 |

@Messages("main.deploy", env)

28 | 29 | } 30 |
31 |
32 | } 33 | 34 |
35 | @currentProject.readme.map { readme => 36 |
@Html(readme)
37 | } 38 | 39 | 42 | 43 |

@Messages("main.recent_commits")

44 |
45 | 46 |
47 | 48 |

@Messages("main.previous_log")

49 |
50 | 51 |
52 |
53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # 8 | # This must be changed for production, but we recommend not changing it in this file. 9 | # 10 | # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. 11 | application.secret="3U=sXah8dWOyDVf_er/DInCyBG]3aF1oyTjY_vKX[`mm/sr86BZ]uWB4o>Z]9e11" 12 | 13 | # The application languages 14 | # ~~~~~ 15 | application.langs="en,ja" 16 | 17 | # Global object class 18 | # ~~~~~ 19 | # Define the Global object class for this application. 20 | # Default to Global in the root package. 21 | # application.global=Global 22 | 23 | # Router 24 | # ~~~~~ 25 | # Define the Router object to use for this application. 26 | # This router will be looked up first when the application is starting up, 27 | # so make sure this is the entry point. 28 | # Furthermore, it's assumed your route file is named properly. 29 | # So for an application router like `my.application.Router`, 30 | # you may need to define a router file `conf/my.application.routes`. 31 | # Default to Routes in the root package (and conf/routes) 32 | # application.router=my.application.Routes 33 | 34 | # Database configuration 35 | # ~~~~~ 36 | # You can declare as many datasources as you want. 37 | # By convention, the default datasource is named `default` 38 | # 39 | # db.default.driver=org.h2.Driver 40 | # db.default.url="jdbc:h2:mem:play" 41 | # db.default.user=sa 42 | # db.default.password="" 43 | 44 | # Evolutions 45 | # ~~~~~ 46 | # You can disable evolutions if needed 47 | # evolutionplugin=disabled 48 | 49 | # Logger 50 | # ~~~~~ 51 | # You can also configure logback (http://logback.qos.ch/), 52 | # by providing an application-logger.xml file in the conf directory. 53 | 54 | # Root logger: 55 | logger.root=ERROR 56 | 57 | # Logger used by the framework: 58 | logger.play=INFO 59 | 60 | # Logger provided to your application: 61 | logger.application=DEBUG 62 | 63 | # pploy specific 64 | pploy.dir="/tmp/pploy" 65 | 66 | pploy.users=["foo", "bar"] 67 | 68 | #pploy.ldap.url="ldap://ldap.example.com:389" 69 | #pploy.ldap.login="cn=someone,dc=example,dc=com" 70 | #pploy.ldap.password="SomeonesPassword" 71 | #pploy.ldap.search="dc=deployers,dc=example,dc=com" 72 | #pploy.ldap.cachettl=3600 73 | 74 | pploy.lock.gainMinutes=20 75 | pploy.lock.extendMinutes=10 76 | 77 | # number of commit logs to show 78 | pploy.commits.length=20 79 | 80 | pploy.commitlogs.lines=1000 81 | 82 | # prefer websocket for streaming logs 83 | # useful when HTTP 1.1 `Content-Encoding: chunked` doesn't go through the proxy 84 | #pploy.preference.websocket=true 85 | 86 | #pploy.idobata.endpoint="https://idobata.io/hook/generic/11112222-3333-4444-5555-666677778888" 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **THIS PROJECT IS NO LONGER MAINTAINED IN FAVOR OF https://github.com/edvakf/go-pploy** 2 | 3 | # pploy 4 | 5 | The deploy lock manager for [pixiv](http://www.pixiv.net/). 6 | 7 | With pploy you can; 8 | 9 | * deploy any git repository with a deploy script placed at .deploy/bin/deploy 10 | * add a git repository for deploy list easily 11 | * let your project members lock a project between staging check and production deploy 12 | * configure LDAP info to fetch staffs' names 13 | * notify deployments to [Slack](https://slack.com/) and [idobata](https://idobata.io/) 14 | 15 | ## Requirements to a repository to be deployed 16 | 17 | It must be a git repo. 18 | 19 | It must have an executable file `.deploy/bin/deploy` at the top level of the repo. 20 | 21 | It can optionally have `.deploy/config/readme.html` which will be displayed at it's deploy page. 22 | 23 | The deploy script is run as; 24 | 25 | ``` 26 | DEPLOY_USER=foo DEPLOY_ENV=staging .deploy/bin/deploy 27 | ``` 28 | 29 | So you can do pretty much anything there, eg. call a Capistrano command. 30 | 31 | ### Changing the `DEPLOY_ENV`s 32 | 33 | By default, `staging` and `production` are the only deploy envs supported. If you want to change them, add a file named `.deploy/config/deploy_envs` in the repo with one line per env. 34 | 35 | ``` 36 | preview 37 | staging 38 | production 39 | ``` 40 | 41 | ## Config values 42 | 43 | ### Working directory 44 | 45 | ``` 46 | pploy.dir="/tmp/pploy" 47 | ``` 48 | 49 | The working directory in which repos are cloned and logs are written. 50 | 51 | ### Deploy user names 52 | 53 | ``` 54 | pploy.users=["foo", "bar"] 55 | ``` 56 | 57 | Names of users who can gain lock of projects. 58 | 59 | ### LDAP 60 | 61 | ``` 62 | pploy.ldap.url="ldap://ldap.example.com:389" 63 | pploy.ldap.login="cn=someone,dc=example,dc=com" 64 | pploy.ldap.password="SomeonesPassword" 65 | pploy.ldap.search="dc=deployers,dc=example,dc=com" 66 | pploy.ldap.cachettl=3600 67 | ``` 68 | 69 | You can optionally set an LDAP configuration from which to fetch member names. 70 | 71 | LDAP's `cn` (Common Name) values are used. 72 | 73 | If `pploy.ldap.cachettl` is omitted, it caches the names for an hour. 74 | 75 | ### Lock minutes 76 | 77 | ``` 78 | pploy.lock.gainMinutes=20 79 | pploy.lock.extendMinutes=10 80 | ``` 81 | 82 | These are for how long a project is locked once someone gained and extended a lock. 83 | 84 | ### Slack 85 | 86 | ``` 87 | pploy.slack.endpoint="https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/1234567890abcdefghijklmn" 88 | ``` 89 | 90 | If enabled, deploy logs are posted to Slack through an Incoming WebHook integration. 91 | 92 | ### Idobata 93 | 94 | ``` 95 | pploy.idobata.endpoint="https://idobata.io/hook/generic/11112222-3333-4444-5555-666677778888" 96 | ``` 97 | 98 | If enabled, deploy logs are posted to idobata. 99 | -------------------------------------------------------------------------------- /app/models/Project.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.nio.charset.CodingErrorAction 4 | 5 | import org.joda.time.DateTime 6 | import play.api.Logger 7 | import play.api.Play.current 8 | import play.api.i18n.{ Messages, Lang } 9 | import scala.io.{ Source, Codec } 10 | import scala.sys.process._ 11 | 12 | case class Project(name: String) { 13 | if (!Project.allNames.contains(name)) { 14 | throw new IllegalArgumentException("bad project name") 15 | } 16 | 17 | lazy val repo = Repo(name) 18 | 19 | private lazy val lock_ = Lock.fetch(this) match { 20 | case Some(l) if l.secondsLeft <= 0 => 21 | l.delete() 22 | None 23 | case other => other 24 | } 25 | def isLocked = lock_.isDefined 26 | 27 | // This method must be called after `isLocked`. 28 | // Plus, it's not updated during gainLock, extendLock or releaseLock 29 | def lock = lock_.get 30 | 31 | def isLockedBy(user: User): Boolean = { 32 | isLocked && lock.user == user 33 | } 34 | def isLockedBy(user: Option[User]): Boolean = { 35 | user.isDefined && isLockedBy(user.get) 36 | } 37 | def assertLockedByUser(user: User) = { 38 | if (!isLockedBy(user)) new LockStatusException(this, user) 39 | } 40 | 41 | def gainLock(user: User) = { 42 | lock_ match { 43 | case None => 44 | Lock.save(this, user, new DateTime().plusMinutes(Project.gainMinutes)) 45 | case _ => 46 | throw new LockStatusException(this, user) 47 | } 48 | Hook.lockGained(name, user.name) 49 | } 50 | 51 | def extendLock(user: User) = { 52 | lock_ match { 53 | case Some(lock) if lock.user == user => 54 | Lock.save(this, user, lock.endTime.plusMinutes(Project.extendMinutes)) 55 | case _ => 56 | throw new LockStatusException(this, user) 57 | } 58 | Hook.lockExtended(name, user.name) 59 | } 60 | 61 | def releaseLock(user: User) = { 62 | lock_ match { 63 | case Some(lock) if lock.user == user => 64 | lock.delete() 65 | case _ => 66 | throw new LockStatusException(this, user) 67 | } 68 | Hook.lockReleased(name, user.name) 69 | } 70 | 71 | def checkoutProcess(ref: String) = { 72 | Logger.info(repo.checkoutCommand) 73 | 74 | Process( 75 | UnbufferedCommand(repo.checkoutCommand), 76 | repo.dir, 77 | "DEPLOY_COMMIT" -> ref 78 | ) 79 | } 80 | 81 | def deployProcess(user: User, target: String) = { 82 | Logger.info(repo.deployCommand) 83 | Hook.deployed(name, user.name, target) 84 | 85 | Process( 86 | UnbufferedCommand(repo.deployCommand), 87 | repo.dir, 88 | "DEPLOY_ENV" -> target, 89 | "DEPLOY_USER" -> user.name 90 | ) #| Process(Seq("tee", WorkingDir.logFile(name).toString)) 91 | } 92 | 93 | def readme: Option[String] = { 94 | repo.readmeFile map { file => 95 | val codec = Codec.UTF8 96 | codec.onMalformedInput(CodingErrorAction.IGNORE) 97 | 98 | val source = Source.fromFile(file)(codec) 99 | try source.mkString 100 | finally source.close() 101 | } 102 | } 103 | 104 | def deployEnvs: Seq[String] = { 105 | repo.deployEnvsFile match { 106 | case Some(file) => 107 | val codec = Codec.UTF8 108 | codec.onMalformedInput(CodingErrorAction.IGNORE) 109 | 110 | val source = Source.fromFile(file)(codec) 111 | try { 112 | source.mkString.split("\n").toSeq 113 | } finally { 114 | source.close() 115 | } 116 | case None => 117 | Seq("staging", "production") 118 | } 119 | } 120 | 121 | def remove() = { 122 | WorkingDir.removeProjectFiles(this.name) 123 | } 124 | } 125 | 126 | object Project { 127 | def allNames = WorkingDir.projects.toSeq 128 | 129 | lazy val gainMinutes = current.configuration.getInt("pploy.lock.gainMinutes").get 130 | lazy val extendMinutes = current.configuration.getInt("pploy.lock.extendMinutes").get 131 | 132 | def apply(repo: Repo): Project = Project(repo.name) 133 | } 134 | 135 | class LockStatusException(message: String = null, cause: Throwable = null) 136 | extends RuntimeException(message, cause) { 137 | 138 | def this(project: Project, user: User) = this( 139 | if (project.isLocked) "lock.operation.taken" else "lock.operation.expired" 140 | ) 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/assets/javascripts/main.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | jsSetting() 3 | countDown() 4 | checkLocked() 5 | commandForm() 6 | submitButtonHack() 7 | checkoutBeforeDeploy() 8 | 9 | $ '.confirm' 10 | .on 'click', -> 11 | return confirm $(this).attr('data-confirm-text') 12 | 13 | iframeFollowScroll = (frame) -> 14 | frame.addClass('loading') 15 | 16 | timer = setInterval -> 17 | contents = frame.contents() 18 | contents.scrollTop contents.height() 19 | clearTimeout(timer) if not frame.hasClass('loading') 20 | return 21 | , 100 22 | return 23 | 24 | iframeStopFollowingScroll = (frame) -> 25 | frame.removeClass('loading') 26 | return 27 | 28 | countDown = -> 29 | elem = $ '.time-left' 30 | return if not elem.length 31 | maxSeconds = + elem.attr 'data-seconds' 32 | startTime = Date.now() 33 | setInterval -> 34 | leftSeconds = maxSeconds - (Date.now() - startTime) / 1000 35 | location.reload() if leftSeconds <= 0 36 | elem.html secondsToString(leftSeconds) 37 | return 38 | , 1000 39 | return 40 | 41 | pad02 = (num) -> 42 | ('0' + num).substr(-2) 43 | 44 | secondsToString = (seconds) -> 45 | pad02(Math.floor(seconds/60)) + ':' + pad02(Math.floor(seconds%60)) 46 | 47 | checkLocked = -> 48 | elem = $('#lock-form') 49 | return if not elem.length 50 | checkUrl = elem.attr('action') 51 | lockUser = elem.attr('data-lock-user') or "" 52 | setInterval -> 53 | $.ajax checkUrl 54 | .success (res) -> 55 | if lockUser isnt res 56 | location.reload() 57 | return 58 | return 59 | , 10000 60 | return 61 | 62 | commandForm = -> 63 | $ '.command-form' 64 | .on 'submit', (ev) -> 65 | $ '#command-log' 66 | .removeClass 'hidden' 67 | 68 | commandLog = $ '#command-log iframe' 69 | iframeFollowScroll(commandLog) 70 | setTimeout disableAllButtons, 10 71 | 72 | if window.setting['use_web_socket'] 73 | postWebSocket this, commandLog, -> 74 | reloadCommitLog() 75 | iframeStopFollowingScroll(commandLog) 76 | enableAllButtons() 77 | return 78 | ev.preventDefault() 79 | else 80 | commandLog.on 'load', onload = -> 81 | commandLog.off 'load', onload 82 | reloadCommitLog() 83 | iframeStopFollowingScroll(commandLog) 84 | enableAllButtons() 85 | return 86 | 87 | return 88 | return 89 | 90 | reloadCommitLog = -> 91 | commitLog = $ '#commit-log iframe' 92 | commitLog.attr 'src', commitLog.attr 'src' 93 | return 94 | 95 | disableAllButtons = -> 96 | $ 'button' 97 | .each (i, e) -> 98 | $(e).prop 'disabled', true 99 | return 100 | return 101 | 102 | enableAllButtons = -> 103 | $ 'button' 104 | .each (i, e) -> 105 | $(e).prop 'disabled', false 106 | return 107 | return 108 | 109 | postWebSocket = (form, frame, oncomplete) -> 110 | $(frame).contents().find('body').html('
')
111 |   pre = $(frame).contents().find('pre')
112 | 
113 |   action = $(form).prop 'action'
114 |   query = $(form).serialize()
115 |   ws = new WebSocket action.replace(/^http(s)?:\/\//, "ws$1:") + '?' + query
116 |   ws.onopen = (event) ->
117 |     return
118 |   ws.onmessage = (event) ->
119 |     pre.append($('').text(event.data))
120 |     return
121 |   ws.onerror = (event) ->
122 |     oncomplete()
123 |     return
124 |   ws.onclose = (event) ->
125 |     oncomplete()
126 |     return
127 |   return
128 | 
129 | # for form.serialize()
130 | # http://stackoverflow.com/a/11271850
131 | submitButtonHack = ->
132 |   $('form :submit').on 'click', ->
133 |     if $(this).attr 'name'
134 |       $form = $(this).closest('form')
135 |       $hidden = $('').attr({
136 |         name: $(this).attr('name'),
137 |         value: $(this).attr('value')
138 |       })
139 |       $form.append($hidden)
140 |       setTimeout ->
141 |         $hidden.remove()
142 |         return
143 |       , 10
144 |     return
145 |   return
146 | 
147 | # pass Application settings to JavaScript via DOM elements with classname "js-setting"
148 | jsSetting = ->
149 |   window.setting = {}
150 |   $('.js-setting').each ->
151 |     window.setting[$(this).attr('data-name')] = $(this).attr('data-value')
152 |     return
153 |   return
154 | 
155 | checkoutBeforeDeploy = ->
156 |   $('.checkout-button').on 'click', ->
157 |     $('.deploy-button').prop('disabled', false)


--------------------------------------------------------------------------------
/app/controllers/Application.scala:
--------------------------------------------------------------------------------
  1 | package controllers
  2 | 
  3 | import java.io.File
  4 | 
  5 | import models._
  6 | import play.api.Play.current
  7 | import play.api.data.Form
  8 | import play.api.data.Forms._
  9 | import play.api.i18n.Messages.Implicits._
 10 | import play.api.libs.iteratee.Iteratee
 11 | import play.api.mvc._
 12 | 
 13 | import scala.io.Source
 14 | 
 15 | object Application extends Controller {
 16 |   def getCurrentUser(request: RequestHeader) = {
 17 |     request.cookies.get("user").map {
 18 |       cookie => User(cookie.value)
 19 |     }
 20 |   }
 21 | 
 22 |   val createProjectForm = Form("url" -> text)
 23 | 
 24 |   def index() = Action { implicit request =>
 25 |     Ok(views.html.index())
 26 |   }
 27 | 
 28 |   def create() = Action { implicit request =>
 29 |     val url = createProjectForm.bindFromRequest.get
 30 |     val proj = Project(Repo.clone(url))
 31 |     Redirect(routes.Application.project(proj.name))
 32 |   }
 33 | 
 34 |   def project(project: String) = Action { implicit request =>
 35 |     val proj = Project(project)
 36 |     val user = getCurrentUser(request)
 37 |     val useWebSocket = current.configuration.getBoolean("pploy.preference.websocket").getOrElse(false)
 38 | 
 39 |     Ok(views.html.project(proj, user, useWebSocket))
 40 |   }
 41 | 
 42 |   def lockUser(project: String) = Action { implicit request =>
 43 |     val proj = Project(project)
 44 |     if (proj.isLocked) Ok(proj.lock.user.name)
 45 |     else Ok("")
 46 |   }
 47 | 
 48 |   val lockOperationForm = Form(tuple("user" -> nonEmptyText, "operation" -> text))
 49 | 
 50 |   def lock(project: String) = Action { implicit request =>
 51 |     val proj = Project(project)
 52 | 
 53 |     lockOperationForm.bindFromRequest.fold(
 54 |       errors => Redirect(routes.Application.project(proj.name)),
 55 |       f => {
 56 |         val (userName, operation) = f
 57 |         val user = User(userName)
 58 |         operation match {
 59 |           case "gain" => proj.gainLock(user)
 60 |           case "release" => proj.releaseLock(user)
 61 |           case "extend" => proj.extendLock(user)
 62 |           case _ =>
 63 |             throw new RuntimeException("operation not specified")
 64 |         }
 65 |         Redirect(routes.Application.project(proj.name))
 66 |           .withCookies(Cookie("user", userName, Some(3600 * 24 * 7)))
 67 |       }
 68 |     )
 69 |   }
 70 | 
 71 |   val checkoutForm = Form("ref" -> nonEmptyText)
 72 | 
 73 |   def checkout(project: String) = Action { implicit request =>
 74 |     val proj = Project(project)
 75 |     val user = getCurrentUser(request)
 76 |       .getOrElse(throw new RuntimeException("user not selected"))
 77 |     proj.assertLockedByUser(user)
 78 | 
 79 |     checkoutForm.bindFromRequest.fold(
 80 |       errors => BadRequest,
 81 |       ref => {
 82 |         Ok.chunked(ProcessEnumerator(proj.checkoutProcess(ref)))
 83 |           .withHeaders("Content-Type" -> "text/plain; charset=utf-8", "X-Content-Type-Options" -> "nosniff")
 84 |       }
 85 |     )
 86 |   }
 87 | 
 88 |   def checkoutWS(project: String, ref: String) = WebSocket.using[String] { request =>
 89 |     val proj = Project(project)
 90 |     val user = getCurrentUser(request)
 91 |       .getOrElse(throw new RuntimeException("user not selected"))
 92 | 
 93 |     val in = Iteratee.ignore[String]
 94 |     val out = ProcessEnumerator(proj.checkoutProcess(ref))
 95 |     (in, out)
 96 |   }
 97 | 
 98 |   val deployForm = Form("target" -> text)
 99 | 
100 |   def deploy(project: String) = Action { implicit request =>
101 |     val proj = Project(project)
102 |     val user = getCurrentUser(request)
103 |       .getOrElse(throw new RuntimeException("user not selected"))
104 |     proj.assertLockedByUser(user)
105 | 
106 |     deployForm.bindFromRequest.fold(
107 |       errors => BadRequest,
108 |       target => {
109 |         Ok.chunked(ProcessEnumerator(proj.deployProcess(user, target)))
110 |           .withHeaders("Content-Type" -> "text/plain; charset=utf-8", "X-Content-Type-Options" -> "nosniff")
111 |       }
112 |     )
113 |   }
114 | 
115 |   def deployWS(project: String, target: String) = WebSocket.using[String] { request =>
116 |     val proj = Project(project)
117 |     val user = getCurrentUser(request)
118 |       .getOrElse(throw new RuntimeException("user not selected"))
119 | 
120 |     val in = Iteratee.ignore[String]
121 |     val out = ProcessEnumerator(proj.deployProcess(user, target))
122 |     (in, out)
123 |   }
124 | 
125 |   def commits(project: String) = Action { implicit request =>
126 |     val proj = Project(project)
127 |     Ok(views.html.commits(proj.repo.commits))
128 |   }
129 | 
130 |   def logs(project: String, full: Long) = Action { implicit request =>
131 |     val proj = Project(project)
132 |     val file = new File(WorkingDir.logFile(proj.name).toString)
133 |     if (file.isFile) {
134 |       if (full != 1) {
135 |         val limit = current.configuration.getInt("pploy.commitlogs.lines").getOrElse(1000)
136 | 
137 |         val lines = Source.fromFile(file.getCanonicalPath).getLines().take(limit).toList
138 |         if (lines.length >= limit) {
139 |           Ok(lines.mkString("\n") + "\n*** LOG FILE TOO LONG. PLEASE SEE THE FULL LOG. ***")
140 |             .withHeaders("Content-Type" -> "text/plain; charset=utf-8", "X-Content-Type-Options" -> "nosniff")
141 |         } else {
142 |           Ok(lines.mkString("\n"))
143 |             .withHeaders("Content-Type" -> "text/plain; charset=utf-8", "X-Content-Type-Options" -> "nosniff")
144 |         }
145 |       } else {
146 |         Ok.sendFile(content = file, inline = true)
147 |           .withHeaders("Content-Type" -> "text/plain; charset=utf-8", "X-Content-Type-Options" -> "nosniff")
148 |       }
149 |     } else {
150 |       NotFound
151 |     }
152 |   }
153 | 
154 |   def remove(project: String) = Action { implicit request =>
155 |     Project(project).remove()
156 |     Redirect(routes.Application.index())
157 |   }
158 | }


--------------------------------------------------------------------------------
/activator:
--------------------------------------------------------------------------------
  1 | #!/bin/bash
  2 | 
  3 | ###  ------------------------------- ###
  4 | ###  Helper methods for BASH scripts ###
  5 | ###  ------------------------------- ###
  6 | 
  7 | realpath () {
  8 | (
  9 |   TARGET_FILE="$1"
 10 | 
 11 |   cd $(dirname "$TARGET_FILE")
 12 |   TARGET_FILE=$(basename "$TARGET_FILE")
 13 | 
 14 |   COUNT=0
 15 |   while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ]
 16 |   do
 17 |       TARGET_FILE=$(readlink "$TARGET_FILE")
 18 |       cd $(dirname "$TARGET_FILE")
 19 |       TARGET_FILE=$(basename "$TARGET_FILE")
 20 |       COUNT=$(($COUNT + 1))
 21 |   done
 22 | 
 23 |   if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then
 24 |     cd "$TARGET_FILE"
 25 |     TARGET_FILEPATH=
 26 |   else
 27 |     TARGET_FILEPATH=/$TARGET_FILE
 28 |   fi
 29 | 
 30 |   # make sure we grab the actual windows path, instead of cygwin's path.
 31 |   if ! is_cygwin; then
 32 |     echo "$(pwd -P)/$TARGET_FILE"
 33 |   else
 34 |     echo $(cygwinpath "$(pwd -P)/$TARGET_FILE")
 35 |   fi
 36 | )
 37 | }
 38 | 
 39 | # TODO - Do we need to detect msys?
 40 | 
 41 | # Uses uname to detect if we're in the odd cygwin environment.
 42 | is_cygwin() {
 43 |   local os=$(uname -s)
 44 |   case "$os" in
 45 |     CYGWIN*) return 0 ;;
 46 |     *)  return 1 ;;
 47 |   esac
 48 | }
 49 | 
 50 | # This can fix cygwin style /cygdrive paths so we get the
 51 | # windows style paths.
 52 | cygwinpath() {
 53 |   local file="$1"
 54 |   if is_cygwin; then
 55 |     echo $(cygpath -w $file)
 56 |   else
 57 |     echo $file
 58 |   fi
 59 | }
 60 | 
 61 | # Make something URI friendly
 62 | make_url() {
 63 |   url="$1"
 64 |   local nospaces=${url// /%20}
 65 |   if is_cygwin; then
 66 |     echo "/${nospaces//\\//}"
 67 |   else
 68 |     echo "$nospaces"
 69 |   fi
 70 | }
 71 | 
 72 | # Detect if we should use JAVA_HOME or just try PATH.
 73 | get_java_cmd() {
 74 |   if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]];  then
 75 |     echo "$JAVA_HOME/bin/java"
 76 |   else
 77 |     echo "java"
 78 |   fi
 79 | }
 80 | 
 81 | echoerr () {
 82 |   echo 1>&2 "$@"
 83 | }
 84 | vlog () {
 85 |   [[ $verbose || $debug ]] && echoerr "$@"
 86 | }
 87 | dlog () {
 88 |   [[ $debug ]] && echoerr "$@"
 89 | }
 90 | execRunner () {
 91 |   # print the arguments one to a line, quoting any containing spaces
 92 |   [[ $verbose || $debug ]] && echo "# Executing command line:" && {
 93 |     for arg; do
 94 |       if printf "%s\n" "$arg" | grep -q ' '; then
 95 |         printf "\"%s\"\n" "$arg"
 96 |       else
 97 |         printf "%s\n" "$arg"
 98 |       fi
 99 |     done
100 |     echo ""
101 |   }
102 | 
103 |   exec "$@"
104 | }
105 | addJava () {
106 |   dlog "[addJava] arg = '$1'"
107 |   java_args=( "${java_args[@]}" "$1" )
108 | }
109 | addApp () {
110 |   dlog "[addApp] arg = '$1'"
111 |   sbt_commands=( "${app_commands[@]}" "$1" )
112 | }
113 | addResidual () {
114 |   dlog "[residual] arg = '$1'"
115 |   residual_args=( "${residual_args[@]}" "$1" )
116 | }
117 | addDebugger () {
118 |   addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"
119 | }
120 | # a ham-fisted attempt to move some memory settings in concert
121 | # so they need not be messed around with individually.
122 | get_mem_opts () {
123 |   local mem=${1:-1024}
124 |   local perm=$(( $mem / 4 ))
125 |   (( $perm > 256 )) || perm=256
126 |   (( $perm < 1024 )) || perm=1024
127 | 
128 |   # default is to set memory options but this can be overridden by code section below
129 |   memopts="-Xms${mem}m -Xmx${mem}m"
130 |   extmemopts="-XX:PermSize=64m -XX:MaxPermSize=${perm}m"
131 | 
132 |   if [[ "${java_opts}" == *-Xmx* ]] || [[ "${java_opts}" == *-Xms* ]] || [[ "${java_opts}" == *-XX:MaxPermSize* ]] || [[ "${java_opts}" == *-XX:ReservedCodeCacheSize* ]]; then
133 |     # if we detect any of these settings in ${java_opts} we need to NOT output our settings.
134 |     # The reason is the Xms/Xmx, if they don't line up, cause errors.
135 |      memopts=""
136 |      extmemopts=""
137 |   elif [[ "${java_version}" > "1.8" ]]; then
138 |     extmemopts=""
139 |   fi
140 | 
141 |   echo "${memopts} ${extmemopts}"
142 | }
143 | require_arg () {
144 |   local type="$1"
145 |   local opt="$2"
146 |   local arg="$3"
147 |   if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
148 |     die "$opt requires <$type> argument"
149 |   fi
150 | }
151 | require_arg () {
152 |   local type="$1"
153 |   local opt="$2"
154 |   local arg="$3"
155 |   if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
156 |     die "$opt requires <$type> argument"
157 |   fi
158 | }
159 | is_function_defined() {
160 |   declare -f "$1" > /dev/null
161 | }
162 | 
163 | # If we're *not* running in a terminal, and we don't have any arguments, then we need to add the 'ui' parameter
164 | detect_terminal_for_ui() {
165 |   [[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && {
166 |     addResidual "ui"
167 |   }
168 |   # SPECIAL TEST FOR MAC
169 |   [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && {
170 |     echo "Detected MAC OSX launched script...."
171 |     echo "Swapping to UI"
172 |     addResidual "ui"
173 |   }
174 | }
175 | 
176 | # Processes incoming arguments and places them in appropriate global variables.  called by the run method.
177 | process_args () {
178 |   while [[ $# -gt 0 ]]; do
179 |     case "$1" in
180 |        -h|-help) usage; exit 1 ;;
181 |     -v|-verbose) verbose=1 && shift ;;
182 |       -d|-debug) debug=1 && shift ;;
183 |            -mem) require_arg integer "$1" "$2" && app_mem="$2" && shift 2 ;;
184 |      -jvm-debug) [[ "$2" =~ ^[0-9]+$ ]] && addDebugger "$2" && shift || addDebugger 9999 && shift ;;
185 |      -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;;
186 |             -D*) addJava "$1" && shift ;;
187 |             -J*) addJava "${1:2}" && shift ;;
188 |               *) addResidual "$1" && shift ;;
189 |     esac
190 |   done
191 | 
192 |   is_function_defined process_my_args && {
193 |     myargs=("${residual_args[@]}")
194 |     residual_args=()
195 |     process_my_args "${myargs[@]}"
196 |   }
197 | }
198 | 
199 | # Actually runs the script.
200 | run() {
201 |   # TODO - check for sane environment
202 | 
203 |   # process the combined args, then reset "$@" to the residuals
204 |   process_args "$@"
205 |   detect_terminal_for_ui
206 |   set -- "${residual_args[@]}"
207 |   argumentCount=$#
208 | 
209 |   #check for jline terminal fixes on cygwin
210 |   if is_cygwin; then
211 |     stty -icanon min 1 -echo > /dev/null 2>&1
212 |     addJava "-Djline.terminal=jline.UnixTerminal"
213 |     addJava "-Dsbt.cygwin=true"
214 |   fi
215 | 
216 |   # run sbt
217 |   execRunner "$java_cmd" \
218 |     "-Dactivator.home=$(make_url "$activator_home")" \
219 |     $(get_mem_opts $app_mem) \
220 |     ${java_opts[@]} \
221 |     ${java_args[@]} \
222 |     -jar "$app_launcher" \
223 |     "${app_commands[@]}" \
224 |     "${residual_args[@]}"
225 |     
226 |   local exit_code=$?
227 |   if is_cygwin; then
228 |     stty icanon echo > /dev/null 2>&1
229 |   fi
230 |   exit $exit_code
231 | }
232 | 
233 | # Loads a configuration file full of default command line options for this script.
234 | loadConfigFile() {
235 |   cat "$1" | sed '/^\#/d'
236 | }
237 | 
238 | ###  ------------------------------- ###
239 | ###  Start of customized settings    ###
240 | ###  ------------------------------- ###
241 | usage() {
242 |  cat < [options]
244 | 
245 |   Command:
246 |   ui                 Start the Activator UI
247 |   new [name] [template-id]  Create a new project with [name] using template [template-id]
248 |   list-templates     Print all available template names
249 |   -h | -help         Print this message
250 | 
251 |   Options:
252 |   -v | -verbose      Make this runner chattier
253 |   -d | -debug        Set sbt log level to debug
254 |   -mem      Set memory options (default: $sbt_mem, which is $(get_mem_opts $sbt_mem))
255 |   -jvm-debug   Turn on JVM debugging, open at the given port.
256 | 
257 |   # java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
258 |   -java-home   Alternate JAVA_HOME
259 | 
260 |   # jvm options and output control
261 |   -Dkey=val          Pass -Dkey=val directly to the java runtime
262 |   -J-X               Pass option -X directly to the java runtime
263 |                      (-J is stripped)
264 | 
265 |   # environment variables (read from context)
266 |   JAVA_OPTS          Environment variable, if unset uses ""
267 |   SBT_OPTS           Environment variable, if unset uses ""
268 |   ACTIVATOR_OPTS     Environment variable, if unset uses ""
269 | 
270 | In the case of duplicated or conflicting options, the order above
271 | shows precedence: environment variables lowest, command line options highest.
272 | EOM
273 | }
274 | 
275 | ###  ------------------------------- ###
276 | ###  Main script                     ###
277 | ###  ------------------------------- ###
278 | 
279 | declare -a residual_args
280 | declare -a java_args
281 | declare -a app_commands
282 | declare -r real_script_path="$(realpath "$0")"
283 | declare -r activator_home="$(realpath "$(dirname "$real_script_path")")"
284 | declare -r app_version="1.1.2"
285 | 
286 | declare -r app_launcher="${activator_home}/activator-launch-${app_version}.jar"
287 | declare -r script_name=activator
288 | declare -r java_cmd=$(get_java_cmd)
289 | declare -r java_opts=( "${ACTIVATOR_OPTS[@]}" "${SBT_OPTS[@]}" "${JAVA_OPTS[@]}" "${java_opts[@]}" )
290 | userhome="$HOME"
291 | if is_cygwin; then
292 |   # cygwin sets home to something f-d up, set to real windows homedir
293 |   userhome="$USERPROFILE"
294 | fi
295 | declare -r activator_user_home_dir="${userhome}/.activator"
296 | declare -r java_opts_config="${activator_user_home_dir}/activatorconfig.txt"
297 | 
298 | # Now check to see if it's a good enough version
299 | declare -r java_version=$("$java_cmd" -version 2>&1 | awk -F '"' '/version/ {print $2}')
300 | if [[ "$java_version" == "" ]]; then
301 |   echo
302 |   echo No java installations was detected.
303 |   echo Please go to http://www.java.com/getjava/ and download
304 |   echo
305 |   exit 1
306 | elif [[ ! "$java_version" > "1.6" ]]; then
307 |   echo
308 |   echo The java installation you have is not up to date
309 |   echo Activator requires at least version 1.6+, you have
310 |   echo version $java_version
311 |   echo
312 |   echo Please go to http://www.java.com/getjava/ and download
313 |   echo a valid Java Runtime and install before running Activator.
314 |   echo
315 |   exit 1
316 | fi
317 | 
318 | # if configuration files exist, prepend their contents to the java args so it can be processed by this runner
319 | if [[ -f "$java_opts_config" ]]; then
320 |   config_opts=$(loadConfigFile "$java_opts_config")
321 |   for item in $config_opts
322 |   do
323 |     addJava "$item"
324 |   done
325 | fi
326 | 
327 | run "$@"
328 | 


--------------------------------------------------------------------------------
/public/bootstrap/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 |  * Bootstrap v3.2.0 (http://getbootstrap.com)
3 |  * Copyright 2011-2014 Twitter, Inc.
4 |  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 |  */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-o-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#2d6ca2));background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-primary:disabled,.btn-primary[disabled]{background-color:#2d6ca2;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f3f3f3));background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-o-linear-gradient(top,#222 0,#282828 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#222),to(#282828));background-image:linear-gradient(to bottom,#222 0,#282828 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-o-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3071a9));background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{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)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-o-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3278b3));background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);background-repeat:repeat-x;border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}


--------------------------------------------------------------------------------
/public/bootstrap/css/bootstrap-theme.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"bootstrap-theme.css","sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAeA;;;;;;EAME,0CAAA;EC+CA,6FAAA;EACQ,qFAAA;EC5DT;AFiBC;;;;;;;;;;;;EC0CA,0DAAA;EACQ,kDAAA;EC7CT;AFqCC;;EAEE,wBAAA;EEnCH;AFwCD;EG/CI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EA+B2C,2BAAA;EAA2B,oBAAA;EE7BvE;AFAC;;EAEE,2BAAA;EACA,8BAAA;EEEH;AFCC;;EAEE,2BAAA;EACA,uBAAA;EECH;AFEC;;EAEE,2BAAA;EACA,wBAAA;EEAH;AFeD;EGhDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0BD;AFxBC;;EAEE,2BAAA;EACA,8BAAA;EE0BH;AFvBC;;EAEE,2BAAA;EACA,uBAAA;EEyBH;AFtBC;;EAEE,2BAAA;EACA,wBAAA;EEwBH;AFRD;EGjDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EEkDD;AFhDC;;EAEE,2BAAA;EACA,8BAAA;EEkDH;AF/CC;;EAEE,2BAAA;EACA,uBAAA;EEiDH;AF9CC;;EAEE,2BAAA;EACA,wBAAA;EEgDH;AF/BD;EGlDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0ED;AFxEC;;EAEE,2BAAA;EACA,8BAAA;EE0EH;AFvEC;;EAEE,2BAAA;EACA,uBAAA;EEyEH;AFtEC;;EAEE,2BAAA;EACA,wBAAA;EEwEH;AFtDD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EEkGD;AFhGC;;EAEE,2BAAA;EACA,8BAAA;EEkGH;AF/FC;;EAEE,2BAAA;EACA,uBAAA;EEiGH;AF9FC;;EAEE,2BAAA;EACA,wBAAA;EEgGH;AF7ED;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0HD;AFxHC;;EAEE,2BAAA;EACA,8BAAA;EE0HH;AFvHC;;EAEE,2BAAA;EACA,uBAAA;EEyHH;AFtHC;;EAEE,2BAAA;EACA,wBAAA;EEwHH;AF7FD;;ECbE,oDAAA;EACQ,4CAAA;EC8GT;AFvFD;;EGvEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHsEF,2BAAA;EE6FD;AF3FD;;;EG5EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4EF,2BAAA;EEiGD;AFvFD;EG1FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ4GA,oBAAA;EC9CA,6FAAA;EACQ,qFAAA;EC4IT;AFlGD;EG1FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECqJT;AF/FD;;EAEE,gDAAA;EEiGD;AF7FD;EG5GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EFgOD;AFrGD;EG5GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0KT;AF9GD;;EAWI,2CAAA;EEuGH;AFlGD;;;EAGE,kBAAA;EEoGD;AF1FD;EACE,+CAAA;EC3FA,4FAAA;EACQ,oFAAA;ECwLT;AFlFD;EGtJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EE8FD;AFzFD;EGvJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EEsGD;AFhGD;EGxJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EE8GD;AFvGD;EGzJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EEsHD;AFtGD;EGlKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2QH;AFnGD;EG5KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkRH;AFzGD;EG7KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDyRH;AF/GD;EG9KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDgSH;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AF3HD;EGhLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AF9HD;EGnJI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDoRH;AF1HD;EACE,oBAAA;EC/IA,oDAAA;EACQ,4CAAA;EC4QT;AF3HD;;;EAGE,+BAAA;EGpME,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHkMF,uBAAA;EEiID;AFvHD;ECjKE,mDAAA;EACQ,2CAAA;EC2RT;AFjHD;EG1NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8UH;AFvHD;EG3NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqVH;AF7HD;EG5NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4VH;AFnID;EG7NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmWH;AFzID;EG9NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0WH;AF/ID;EG/NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDiXH;AF9ID;EGvOI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHqOF,uBAAA;EC1LA,2FAAA;EACQ,mFAAA;EC+UT","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n  text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n  @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n  .box-shadow(@shadow);\n\n  // Reset the shadow\n  &:active,\n  &.active {\n    .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n  }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n  #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n  .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners\n  background-repeat: repeat-x;\n  border-color: darken(@btn-color, 14%);\n\n  &:hover,\n  &:focus  {\n    background-color: darken(@btn-color, 12%);\n    background-position: 0 -15px;\n  }\n\n  &:active,\n  &.active {\n    background-color: darken(@btn-color, 12%);\n    border-color: darken(@btn-color, 14%);\n  }\n\n  &:disabled,\n  &[disabled] {\n    background-color: darken(@btn-color, 12%);\n    background-image: none;\n  }\n}\n\n// Common styles\n.btn {\n  // Remove the gradient for the pressed/active state\n  &:active,\n  &.active {\n    background-image: none;\n  }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info    { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger  { .btn-styles(@btn-danger-bg); }\n\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n  .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n  background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n  background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n  #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n  .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n  border-radius: @navbar-border-radius;\n  @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n  .box-shadow(@shadow);\n\n  .navbar-nav > .active > a {\n    #gradient > .vertical(@start-color: darken(@navbar-default-bg, 5%); @end-color: darken(@navbar-default-bg, 2%));\n    .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n  }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n  text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n  #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n  .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n\n  .navbar-nav > .active > a {\n    #gradient > .vertical(@start-color: @navbar-inverse-bg; @end-color: lighten(@navbar-inverse-bg, 2.5%));\n    .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n  }\n\n  .navbar-brand,\n  .navbar-nav > li > a {\n    text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n  }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  border-radius: 0;\n}\n\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n  text-shadow: 0 1px 0 rgba(255,255,255,.2);\n  @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n  .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n  border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success    { .alert-styles(@alert-success-bg); }\n.alert-info       { .alert-styles(@alert-info-bg); }\n.alert-warning    { .alert-styles(@alert-warning-bg); }\n.alert-danger     { .alert-styles(@alert-danger-bg); }\n\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n  #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar            { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success    { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info       { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning    { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger     { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n  #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n  border-radius: @border-radius-base;\n  .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n  #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n  border-color: darken(@list-group-active-border, 7.5%);\n}\n\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n  .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading   { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading   { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading   { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading      { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading   { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading    { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n  #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n  border-color: darken(@well-bg, 10%);\n  @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n  .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n  -webkit-animation: @animation;\n       -o-animation: @animation;\n          animation: @animation;\n}\n.animation-name(@name) {\n  -webkit-animation-name: @name;\n          animation-name: @name;\n}\n.animation-duration(@duration) {\n  -webkit-animation-duration: @duration;\n          animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n  -webkit-animation-timing-function: @timing-function;\n          animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n  -webkit-animation-delay: @delay;\n          animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n  -webkit-animation-iteration-count: @iteration-count;\n          animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n  -webkit-animation-direction: @direction;\n          animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n  -webkit-animation-fill-mode: @fill-mode;\n          animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n  -webkit-backface-visibility: @visibility;\n     -moz-backface-visibility: @visibility;\n          backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n  -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n          box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n  -webkit-box-sizing: @boxmodel;\n     -moz-box-sizing: @boxmodel;\n          box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n  -webkit-column-count: @column-count;\n     -moz-column-count: @column-count;\n          column-count: @column-count;\n  -webkit-column-gap: @column-gap;\n     -moz-column-gap: @column-gap;\n          column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n  word-wrap: break-word;\n  -webkit-hyphens: @mode;\n     -moz-hyphens: @mode;\n      -ms-hyphens: @mode; // IE10+\n       -o-hyphens: @mode;\n          hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n  &::-moz-placeholder           { color: @color;   // Firefox\n                                  opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n  &:-ms-input-placeholder       { color: @color; } // Internet Explorer 10+\n  &::-webkit-input-placeholder  { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n  -webkit-transform: scale(@ratio);\n      -ms-transform: scale(@ratio); // IE9 only\n       -o-transform: scale(@ratio);\n          transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n  -webkit-transform: scale(@ratioX, @ratioY);\n      -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n       -o-transform: scale(@ratioX, @ratioY);\n          transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n  -webkit-transform: scaleX(@ratio);\n      -ms-transform: scaleX(@ratio); // IE9 only\n       -o-transform: scaleX(@ratio);\n          transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n  -webkit-transform: scaleY(@ratio);\n      -ms-transform: scaleY(@ratio); // IE9 only\n       -o-transform: scaleY(@ratio);\n          transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n  -webkit-transform: skewX(@x) skewY(@y);\n      -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n       -o-transform: skewX(@x) skewY(@y);\n          transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n  -webkit-transform: translate(@x, @y);\n      -ms-transform: translate(@x, @y); // IE9 only\n       -o-transform: translate(@x, @y);\n          transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n  -webkit-transform: translate3d(@x, @y, @z);\n          transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n  -webkit-transform: rotate(@degrees);\n      -ms-transform: rotate(@degrees); // IE9 only\n       -o-transform: rotate(@degrees);\n          transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n  -webkit-transform: rotateX(@degrees);\n      -ms-transform: rotateX(@degrees); // IE9 only\n       -o-transform: rotateX(@degrees);\n          transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n  -webkit-transform: rotateY(@degrees);\n      -ms-transform: rotateY(@degrees); // IE9 only\n       -o-transform: rotateY(@degrees);\n          transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n  -webkit-perspective: @perspective;\n     -moz-perspective: @perspective;\n          perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n  -webkit-perspective-origin: @perspective;\n     -moz-perspective-origin: @perspective;\n          perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n  -webkit-transform-origin: @origin;\n     -moz-transform-origin: @origin;\n      -ms-transform-origin: @origin; // IE9 only\n          transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n  -webkit-transition: @transition;\n       -o-transition: @transition;\n          transition: @transition;\n}\n.transition-property(@transition-property) {\n  -webkit-transition-property: @transition-property;\n          transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n  -webkit-transition-delay: @transition-delay;\n          transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n  -webkit-transition-duration: @transition-duration;\n          transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n  -webkit-transition-timing-function: @timing-function;\n          transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n  -webkit-transition: -webkit-transform @transition;\n     -moz-transition: -moz-transform @transition;\n       -o-transition: -o-transform @transition;\n          transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n  -webkit-user-select: @select;\n     -moz-user-select: @select;\n      -ms-user-select: @select; // IE10+\n          user-select: @select;\n}\n",null,"// Gradients\n\n#gradient {\n\n  // Horizontal gradient, from left to right\n  //\n  // Creates two color stops, start and end, by specifying a color and position for each color stop.\n  // Color stops are not available in IE9 and below.\n  .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n    background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n    background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n    background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n    background-repeat: repeat-x;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n  }\n\n  // Vertical gradient, from top to bottom\n  //\n  // Creates two color stops, start and end, by specifying a color and position for each color stop.\n  // Color stops are not available in IE9 and below.\n  .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n    background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent);  // Safari 5.1-6, Chrome 10+\n    background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent);  // Opera 12\n    background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n    background-repeat: repeat-x;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n  }\n\n  .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n    background-repeat: repeat-x;\n    background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n    background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n    background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n  }\n  .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n    background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n    background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n    background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n    background-repeat: no-repeat;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n  }\n  .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n    background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n    background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n    background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n    background-repeat: no-repeat;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n  }\n  .radial(@inner-color: #555; @outer-color: #333) {\n    background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n    background-image: radial-gradient(circle, @inner-color, @outer-color);\n    background-repeat: no-repeat;\n  }\n  .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n    background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n    background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n    background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n  }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n  filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]}


--------------------------------------------------------------------------------
/public/bootstrap/css/bootstrap-theme.css:
--------------------------------------------------------------------------------
  1 | /*!
  2 |  * Bootstrap v3.2.0 (http://getbootstrap.com)
  3 |  * Copyright 2011-2014 Twitter, Inc.
  4 |  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  5 |  */
  6 | 
  7 | .btn-default,
  8 | .btn-primary,
  9 | .btn-success,
 10 | .btn-info,
 11 | .btn-warning,
 12 | .btn-danger {
 13 |   text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
 14 |   -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
 15 |           box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
 16 | }
 17 | .btn-default:active,
 18 | .btn-primary:active,
 19 | .btn-success:active,
 20 | .btn-info:active,
 21 | .btn-warning:active,
 22 | .btn-danger:active,
 23 | .btn-default.active,
 24 | .btn-primary.active,
 25 | .btn-success.active,
 26 | .btn-info.active,
 27 | .btn-warning.active,
 28 | .btn-danger.active {
 29 |   -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
 30 |           box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
 31 | }
 32 | .btn:active,
 33 | .btn.active {
 34 |   background-image: none;
 35 | }
 36 | .btn-default {
 37 |   text-shadow: 0 1px 0 #fff;
 38 |   background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
 39 |   background-image:      -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
 40 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
 41 |   background-image:         linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
 42 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
 43 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
 44 |   background-repeat: repeat-x;
 45 |   border-color: #dbdbdb;
 46 |   border-color: #ccc;
 47 | }
 48 | .btn-default:hover,
 49 | .btn-default:focus {
 50 |   background-color: #e0e0e0;
 51 |   background-position: 0 -15px;
 52 | }
 53 | .btn-default:active,
 54 | .btn-default.active {
 55 |   background-color: #e0e0e0;
 56 |   border-color: #dbdbdb;
 57 | }
 58 | .btn-default:disabled,
 59 | .btn-default[disabled] {
 60 |   background-color: #e0e0e0;
 61 |   background-image: none;
 62 | }
 63 | .btn-primary {
 64 |   background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
 65 |   background-image:      -o-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
 66 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#2d6ca2));
 67 |   background-image:         linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
 68 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
 69 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
 70 |   background-repeat: repeat-x;
 71 |   border-color: #2b669a;
 72 | }
 73 | .btn-primary:hover,
 74 | .btn-primary:focus {
 75 |   background-color: #2d6ca2;
 76 |   background-position: 0 -15px;
 77 | }
 78 | .btn-primary:active,
 79 | .btn-primary.active {
 80 |   background-color: #2d6ca2;
 81 |   border-color: #2b669a;
 82 | }
 83 | .btn-primary:disabled,
 84 | .btn-primary[disabled] {
 85 |   background-color: #2d6ca2;
 86 |   background-image: none;
 87 | }
 88 | .btn-success {
 89 |   background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
 90 |   background-image:      -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
 91 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
 92 |   background-image:         linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
 93 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
 94 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
 95 |   background-repeat: repeat-x;
 96 |   border-color: #3e8f3e;
 97 | }
 98 | .btn-success:hover,
 99 | .btn-success:focus {
100 |   background-color: #419641;
101 |   background-position: 0 -15px;
102 | }
103 | .btn-success:active,
104 | .btn-success.active {
105 |   background-color: #419641;
106 |   border-color: #3e8f3e;
107 | }
108 | .btn-success:disabled,
109 | .btn-success[disabled] {
110 |   background-color: #419641;
111 |   background-image: none;
112 | }
113 | .btn-info {
114 |   background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
115 |   background-image:      -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
116 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
117 |   background-image:         linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
118 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
119 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
120 |   background-repeat: repeat-x;
121 |   border-color: #28a4c9;
122 | }
123 | .btn-info:hover,
124 | .btn-info:focus {
125 |   background-color: #2aabd2;
126 |   background-position: 0 -15px;
127 | }
128 | .btn-info:active,
129 | .btn-info.active {
130 |   background-color: #2aabd2;
131 |   border-color: #28a4c9;
132 | }
133 | .btn-info:disabled,
134 | .btn-info[disabled] {
135 |   background-color: #2aabd2;
136 |   background-image: none;
137 | }
138 | .btn-warning {
139 |   background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
140 |   background-image:      -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
141 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
142 |   background-image:         linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
143 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
144 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
145 |   background-repeat: repeat-x;
146 |   border-color: #e38d13;
147 | }
148 | .btn-warning:hover,
149 | .btn-warning:focus {
150 |   background-color: #eb9316;
151 |   background-position: 0 -15px;
152 | }
153 | .btn-warning:active,
154 | .btn-warning.active {
155 |   background-color: #eb9316;
156 |   border-color: #e38d13;
157 | }
158 | .btn-warning:disabled,
159 | .btn-warning[disabled] {
160 |   background-color: #eb9316;
161 |   background-image: none;
162 | }
163 | .btn-danger {
164 |   background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
165 |   background-image:      -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
166 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
167 |   background-image:         linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
168 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
169 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
170 |   background-repeat: repeat-x;
171 |   border-color: #b92c28;
172 | }
173 | .btn-danger:hover,
174 | .btn-danger:focus {
175 |   background-color: #c12e2a;
176 |   background-position: 0 -15px;
177 | }
178 | .btn-danger:active,
179 | .btn-danger.active {
180 |   background-color: #c12e2a;
181 |   border-color: #b92c28;
182 | }
183 | .btn-danger:disabled,
184 | .btn-danger[disabled] {
185 |   background-color: #c12e2a;
186 |   background-image: none;
187 | }
188 | .thumbnail,
189 | .img-thumbnail {
190 |   -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
191 |           box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
192 | }
193 | .dropdown-menu > li > a:hover,
194 | .dropdown-menu > li > a:focus {
195 |   background-color: #e8e8e8;
196 |   background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
197 |   background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
198 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
199 |   background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
200 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
201 |   background-repeat: repeat-x;
202 | }
203 | .dropdown-menu > .active > a,
204 | .dropdown-menu > .active > a:hover,
205 | .dropdown-menu > .active > a:focus {
206 |   background-color: #357ebd;
207 |   background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
208 |   background-image:      -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
209 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
210 |   background-image:         linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
211 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
212 |   background-repeat: repeat-x;
213 | }
214 | .navbar-default {
215 |   background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
216 |   background-image:      -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
217 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
218 |   background-image:         linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
219 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
220 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
221 |   background-repeat: repeat-x;
222 |   border-radius: 4px;
223 |   -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
224 |           box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
225 | }
226 | .navbar-default .navbar-nav > .active > a {
227 |   background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
228 |   background-image:      -o-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
229 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f3f3f3));
230 |   background-image:         linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
231 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
232 |   background-repeat: repeat-x;
233 |   -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
234 |           box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
235 | }
236 | .navbar-brand,
237 | .navbar-nav > li > a {
238 |   text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
239 | }
240 | .navbar-inverse {
241 |   background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
242 |   background-image:      -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
243 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
244 |   background-image:         linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
245 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
246 |   filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
247 |   background-repeat: repeat-x;
248 | }
249 | .navbar-inverse .navbar-nav > .active > a {
250 |   background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
251 |   background-image:      -o-linear-gradient(top, #222 0%, #282828 100%);
252 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#222), to(#282828));
253 |   background-image:         linear-gradient(to bottom, #222 0%, #282828 100%);
254 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
255 |   background-repeat: repeat-x;
256 |   -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
257 |           box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
258 | }
259 | .navbar-inverse .navbar-brand,
260 | .navbar-inverse .navbar-nav > li > a {
261 |   text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
262 | }
263 | .navbar-static-top,
264 | .navbar-fixed-top,
265 | .navbar-fixed-bottom {
266 |   border-radius: 0;
267 | }
268 | .alert {
269 |   text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
270 |   -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
271 |           box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
272 | }
273 | .alert-success {
274 |   background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
275 |   background-image:      -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
276 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
277 |   background-image:         linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
278 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
279 |   background-repeat: repeat-x;
280 |   border-color: #b2dba1;
281 | }
282 | .alert-info {
283 |   background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
284 |   background-image:      -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
285 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
286 |   background-image:         linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
287 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
288 |   background-repeat: repeat-x;
289 |   border-color: #9acfea;
290 | }
291 | .alert-warning {
292 |   background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
293 |   background-image:      -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
294 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
295 |   background-image:         linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
296 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
297 |   background-repeat: repeat-x;
298 |   border-color: #f5e79e;
299 | }
300 | .alert-danger {
301 |   background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
302 |   background-image:      -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
303 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
304 |   background-image:         linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
305 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
306 |   background-repeat: repeat-x;
307 |   border-color: #dca7a7;
308 | }
309 | .progress {
310 |   background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
311 |   background-image:      -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
312 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
313 |   background-image:         linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
314 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
315 |   background-repeat: repeat-x;
316 | }
317 | .progress-bar {
318 |   background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
319 |   background-image:      -o-linear-gradient(top, #428bca 0%, #3071a9 100%);
320 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3071a9));
321 |   background-image:         linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
322 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
323 |   background-repeat: repeat-x;
324 | }
325 | .progress-bar-success {
326 |   background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
327 |   background-image:      -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
328 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
329 |   background-image:         linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
330 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
331 |   background-repeat: repeat-x;
332 | }
333 | .progress-bar-info {
334 |   background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
335 |   background-image:      -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
336 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
337 |   background-image:         linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
338 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
339 |   background-repeat: repeat-x;
340 | }
341 | .progress-bar-warning {
342 |   background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
343 |   background-image:      -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
344 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
345 |   background-image:         linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
346 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
347 |   background-repeat: repeat-x;
348 | }
349 | .progress-bar-danger {
350 |   background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
351 |   background-image:      -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
352 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
353 |   background-image:         linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
354 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
355 |   background-repeat: repeat-x;
356 | }
357 | .progress-bar-striped {
358 |   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);
359 |   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);
360 |   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);
361 | }
362 | .list-group {
363 |   border-radius: 4px;
364 |   -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
365 |           box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
366 | }
367 | .list-group-item.active,
368 | .list-group-item.active:hover,
369 | .list-group-item.active:focus {
370 |   text-shadow: 0 -1px 0 #3071a9;
371 |   background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
372 |   background-image:      -o-linear-gradient(top, #428bca 0%, #3278b3 100%);
373 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3278b3));
374 |   background-image:         linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
375 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
376 |   background-repeat: repeat-x;
377 |   border-color: #3278b3;
378 | }
379 | .panel {
380 |   -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
381 |           box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
382 | }
383 | .panel-default > .panel-heading {
384 |   background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
385 |   background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
386 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
387 |   background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
388 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
389 |   background-repeat: repeat-x;
390 | }
391 | .panel-primary > .panel-heading {
392 |   background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
393 |   background-image:      -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
394 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
395 |   background-image:         linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
396 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
397 |   background-repeat: repeat-x;
398 | }
399 | .panel-success > .panel-heading {
400 |   background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
401 |   background-image:      -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
402 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
403 |   background-image:         linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
404 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
405 |   background-repeat: repeat-x;
406 | }
407 | .panel-info > .panel-heading {
408 |   background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
409 |   background-image:      -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
410 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
411 |   background-image:         linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
412 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
413 |   background-repeat: repeat-x;
414 | }
415 | .panel-warning > .panel-heading {
416 |   background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
417 |   background-image:      -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
418 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
419 |   background-image:         linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
420 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
421 |   background-repeat: repeat-x;
422 | }
423 | .panel-danger > .panel-heading {
424 |   background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
425 |   background-image:      -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
426 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
427 |   background-image:         linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
428 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
429 |   background-repeat: repeat-x;
430 | }
431 | .well {
432 |   background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
433 |   background-image:      -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
434 |   background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
435 |   background-image:         linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
436 |   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
437 |   background-repeat: repeat-x;
438 |   border-color: #dcdcdc;
439 |   -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
440 |           box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
441 | }
442 | /*# sourceMappingURL=bootstrap-theme.css.map */
443 | 


--------------------------------------------------------------------------------
/public/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 |  * Bootstrap v3.2.0 (http://getbootstrap.com)
3 |  * Copyright 2011-2014 Twitter, Inc.
4 |  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 |  */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('