From a83fbeb6d4e20d391148a4f753de7d71b4a50965 Mon Sep 17 00:00:00 2001 From: Sean Zhong Date: Fri, 8 Jan 2016 16:07:42 +0800 Subject: [PATCH] fix #1247, add authentication for UI --- .travis.yml | 3 + conf/gear.conf | 35 +++ core/src/main/resources/reference.conf | 54 +++- .../io/gearpump/security/Authenticator.scala | 45 ++++ .../ConfigFileBasedAuthenticator.scala | 94 +++++++ .../io/gearpump/security/PasswordUtil.scala | 93 +++++++ .../scala/io/gearpump/util/Constants.scala | 3 + docs/dev-ide-setup.md | 9 + .../akka/stream/gearpump/AttributesSpec.scala | 23 +- project/Build.scala | 3 +- project/build.properties | 2 +- project/plugins.sbt | 2 - services/dashboard/dashboard.js | 4 +- services/dashboard/index.html | 10 +- services/dashboard/login/login.html | 111 ++++++++ services/dashboard/login/login.js | 41 +++ services/dashboard/services/login_check.js | 31 +++ .../dashboard/services/streamingservice.js | 2 +- services/dashboard/views/apps/apps.html | 12 +- services/dashboard/views/landing/header.html | 15 ++ services/dashboard/views/landing/header.js | 7 +- .../io/gearpump/services/AdminService.scala | 62 +++++ .../gearpump/services/AppMasterService.scala | 133 +++++----- .../io/gearpump/services/BasicService.scala | 68 +++++ .../io/gearpump/services/MasterService.scala | 241 +++++++++--------- .../io/gearpump/services/RestServices.scala | 100 +++----- .../gearpump/services/SecurityService.scala | 179 +++++++++++++ .../io/gearpump/services/StaticService.scala | 21 +- .../io/gearpump/services/WorkerService.scala | 67 ++--- .../io/gearpump/services/main/Services.scala | 36 ++- .../services/AppMasterServiceSpec.scala | 12 +- .../gearpump/services/MasterServiceSpec.scala | 11 +- .../services/SecurityServiceSpec.scala | 23 ++ .../gearpump/services/WorkerServiceSpec.scala | 10 +- .../streaming/appmaster/AppMasterSpec.scala | 2 +- 35 files changed, 1214 insertions(+), 350 deletions(-) create mode 100644 core/src/main/scala/io/gearpump/security/Authenticator.scala create mode 100644 core/src/main/scala/io/gearpump/security/ConfigFileBasedAuthenticator.scala create mode 100644 core/src/main/scala/io/gearpump/security/PasswordUtil.scala create mode 100644 services/dashboard/login/login.html create mode 100644 services/dashboard/login/login.js create mode 100644 services/dashboard/services/login_check.js create mode 100644 services/jvm/src/main/scala/io/gearpump/services/AdminService.scala create mode 100644 services/jvm/src/main/scala/io/gearpump/services/BasicService.scala create mode 100644 services/jvm/src/main/scala/io/gearpump/services/SecurityService.scala create mode 100644 services/jvm/src/test/scala/io/gearpump/services/SecurityServiceSpec.scala diff --git a/.travis.yml b/.travis.yml index baf1501238..771f6522a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: - java - scala sudo: false +before_script: +- mkdir -p $HOME/.sbt/launchers/0.13.9/ +- curl -L -o $HOME/.sbt/launchers/0.13.9/sbt-launch.jar http://dl.bintray.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.9/sbt-launch.jar script: - echo "TRAVIS_PULL_REQUEST" $TRAVIS_PULL_REQUEST - echo "TRAVIS_BRANCH" $TRAVIS_BRANCH diff --git a/conf/gear.conf b/conf/gear.conf index f61346dfe0..605a8facd1 100644 --- a/conf/gear.conf +++ b/conf/gear.conf @@ -259,4 +259,39 @@ gearpump { enabled = true } } + + ## Security related settings + security { + + ## Whether enable authentication for UI Server + ui-authentication-enabled = true + + ## What authenticator to use. The class must implement interface + ## io.gearpump.security.Authenticator + ui-authenticator = "io.gearpump.security.ConfigFileBasedAuthenticator" + + ## Configuration options for authenticator io.gearpump.security.ConfigFileBasedAuthenticator + config-file-based-authenticator = { + ## Admin users have permission to submit/change/kill a running application. + users = { + ## Default Admin. Username: admin, password: admin + ## Format: username = "password_hash_value" + ## password_hash_value can be generated by running shell tool: + ## bin/gear io.gearpump.security.PasswordUtil -password + ## !!! Please replace this builtin account for production cluster for security reason. !!! + "admin" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA==" + } + + ## Guest user can only view the UI with minimum permission. With no permission to submit/change/kill + ## a running application. + guests = { + ## Default guest. Username: guest, Password: guest + ## Format: username = "password_hash_value" + ## password_hash_value can be generated by running shell tool: + ## bin/gear io.gearpump.security.PasswordUtil -password + ## !!! Please replace this builtin account for production cluster for security reason. !!! + "guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew==" + } + } + } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 5b12ab9348..0377712243 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -244,6 +244,38 @@ gearpump { single-thread-dispatcher { type = PinnedDispatcher } + + ## Security related settings + security { + + ## Whether enable authentication for UI Server + ui-authentication-enabled = false + + ## What authenticator to use. The class must implement interface + ## io.gearpump.security.Authenticator + ui-authenticator = "io.gearpump.security.ConfigFileBasedAuthenticator" + + ## Configuration options for authenticator io.gearpump.security.ConfigFileBasedAuthenticator + config-file-based-authenticator = { + ## Admin users have permission to submit/change/kill a running application. + users = { + ## Default Admin. Username: admin, password: admin + ## !!! Please remove this builtin account for production cluster for security reason. !!! + ## Format: username = "password_hash_value" + ## password_hash_value can be generated by running + ## gear io.gearpump.security.PasswordUtil -password + "admin" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA==" + } + + ## Guest user can only view the UI with minimum permission. With no permission to submit/change/kill + ## a running application. + guests = { + ## Default guest. Username: guest, Password: guest + ## !!! Please remove this builtin account for production cluster for security reason. !!! + "guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew==" + } + } + } } ### Akka system configuration for master nodes @@ -319,7 +351,6 @@ base { akka.scheduler.tick-duration = 1 akka { - http { client { parsing { @@ -327,11 +358,30 @@ base { } } server { + remote-address-header = on parsing { max-content-length = 2048m illegal-header-warnings = off } } + + ## Akka-http session related settings + session { + clientSession { + cookie { + name = "gearpump_token" + ## domain = "..." + path = "/" + ## maxAge = 0 + secure = false + httpOnly = true + } + + ## Session lifetime. + maxAgeSeconds = 3600 + encryptData = true + } + } } test { @@ -411,4 +461,4 @@ windows { ### On windows, the value must be larger than 10ms, check ### https://github.com/akka/akka/blob/master/akka-actor/src/main/scala/akka/actor/Scheduler.scala#L204 akka.scheduler.tick-duration = 10 -} +} \ No newline at end of file diff --git a/core/src/main/scala/io/gearpump/security/Authenticator.scala b/core/src/main/scala/io/gearpump/security/Authenticator.scala new file mode 100644 index 0000000000..0a9385b4ca --- /dev/null +++ b/core/src/main/scala/io/gearpump/security/Authenticator.scala @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gearpump.security + +import scala.concurrent.{ExecutionContext, Future} + + +/** + * Authenticator for UI dashboard. + * + * Sub Class must implement a constructor with signature like this: + * this(config: Config) + * + */ +trait Authenticator { + + // TODO: Change the signature to return more attributes of user + // credentials... + def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult] +} + +trait AuthenticationResult { + + // Whether current user is a valid user. + def authenticated: Boolean + + // Admin user have unlimited permission + def isAdministrator: Boolean +} \ No newline at end of file diff --git a/core/src/main/scala/io/gearpump/security/ConfigFileBasedAuthenticator.scala b/core/src/main/scala/io/gearpump/security/ConfigFileBasedAuthenticator.scala new file mode 100644 index 0000000000..68950f3cf1 --- /dev/null +++ b/core/src/main/scala/io/gearpump/security/ConfigFileBasedAuthenticator.scala @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gearpump.security + +import io.gearpump.security.ConfigFileBasedAuthenticator._ +import com.typesafe.config.Config +import scala.concurrent.{ExecutionContext, Future} + +object ConfigFileBasedAuthenticator { + + private val ROOT = "gearpump.security.config-file-based-authenticator" + private val USERS = ROOT + "." + "users" + private val GUESTS = ROOT + "." + "guests" + + private class Result(override val authenticated: Boolean, override val isAdministrator: Boolean) + extends AuthenticationResult + + private case class Credentials(users: Map[String, String], guests: Map[String, String]) { + def verify(user: String, password: String): AuthenticationResult = { + if (users.contains(user)) { + new Result(verify(user, password, users), isAdministrator = true) + } else if (guests.contains(user)) { + new Result(verify(user, password, guests), isAdministrator = false) + } else { + new Result(authenticated = false, isAdministrator = false) + } + } + + private def verify(user: String, password: String, map: Map[String, String]): Boolean = { + val storedPass = map(user) + PasswordUtil.verify(password, storedPass) + } + } +} + +/** + * UI dashboard authenticator based on configuration file. + * + * It has two categories of users: users, and guests. + * users have unlimited permission on the UI, while guests can not submit/kill applications. + * + * see conf/gear.conf section gearpump.security.config-file-based-authenticator to find information + * about how to configure this authenticator. + * + * [Security consideration] + * It will keep one-way sha1 digest of password instead of password itself. The original password is NOT + * kept in any way, so generally it is safe. + * + * digesting flow (from original password to digest): + * random salt byte array of length 8 -> byte array of (salt + sha1(salt, password)) -> base64Encode + * + * verification user input password with stored digest: + * base64Decode -> extract salt -> do sha1(salt, password) -> generate digest: salt + sha1 -> + * compare the generated digest with the stored digest. + * + */ +class ConfigFileBasedAuthenticator(config: Config) extends Authenticator { + + private val credentials = loadCredentials(config) + + override def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult] = { + implicit val ctx = ec + Future { + credentials.verify(user, password) + } + } + + private def loadCredentials(config: Config): Credentials = { + val admins = configToMap(config, USERS) + val guests = configToMap(config, GUESTS) + new Credentials(admins, guests) + } + + private def configToMap(config : Config, path: String) = { + import scala.collection.JavaConverters._ + config.getConfig(path).root.unwrapped.asScala.toMap map { case (k, v) => k -> v.toString } + } +} \ No newline at end of file diff --git a/core/src/main/scala/io/gearpump/security/PasswordUtil.scala b/core/src/main/scala/io/gearpump/security/PasswordUtil.scala new file mode 100644 index 0000000000..f8eafddc69 --- /dev/null +++ b/core/src/main/scala/io/gearpump/security/PasswordUtil.scala @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gearpump.security + +import java.security.MessageDigest +import sun.misc.{BASE64Decoder, BASE64Encoder} +import scala.util.Try + +/** + * Util to verify whether user input password is valid or not. + * It use sha1 to do the digesting. + */ +object PasswordUtil { + private val SALT_LENGTH = 8 + + /** + * verification user input password with stored digest: + * base64Decode -> extract salt -> do sha1(salt, password) -> + * generate digest: salt + sha1 -> compare the generated digest with the stored digest. + */ + def verify(password: String, stored: String): Boolean = { + Try { + val decoded = base64Decode(stored) + val salt = new Array[Byte](SALT_LENGTH) + Array.copy(decoded, 0, salt, 0, SALT_LENGTH) + + hash(password, salt) == stored + }.getOrElse(false) + } + /** + * digesting flow (from original password to digest): + * random salt byte array of length 8 -> byte array of (salt + sha1(salt, password)) -> base64Encode + */ + def hash(password: String): String = { + // Salt generation 64 bits long + val salt = new Array[Byte](SALT_LENGTH) + new java.util.Random().nextBytes(salt) + hash(password, salt) + } + + private def hash(password: String, salt: Array[Byte]): String = { + val digest = MessageDigest.getInstance("SHA-1") + digest.reset() + digest.update(salt) + var input = digest.digest(password.getBytes("UTF-8")) + digest.reset() + input = digest.digest(input) + val withSalt = salt ++ input + base64Encode(withSalt) + } + + private def base64Encode(data: Array[Byte]): String = { + val endecoder = new BASE64Encoder() + endecoder.encode(data) + } + + private def base64Decode(data: String): Array[Byte] = { + val decoder = new BASE64Decoder() + decoder.decodeBuffer(data) + } + + private def help = { + Console.println("usage: gear io.gearpump.security.PasswordUtil -password ") + } + + def main(args: Array[String]): Unit = { + if (args.length != 2 || args(0) != "-password") { + help + } else { + val pass = args(1) + val result = hash(pass) + Console.println("Here is the hashed password") + Console.println("==============================") + Console.println(result) + } + } +} \ No newline at end of file diff --git a/core/src/main/scala/io/gearpump/util/Constants.scala b/core/src/main/scala/io/gearpump/util/Constants.scala index a800f87561..98efc1d534 100644 --- a/core/src/main/scala/io/gearpump/util/Constants.scala +++ b/core/src/main/scala/io/gearpump/util/Constants.scala @@ -140,4 +140,7 @@ object Constants { val GEARPUMP_METRICS_MAX_LIMIT = "gearpump.metrics.akka.max-limit-on-query" val GEARPUMP_METRICS_AGGREGATORS = "gearpump.metrics.akka.metrics-aggregator-class" + + val GEARPUMP_UI_SECURITY_ENABLED = "gearpump.security.ui-authentication-enabled" + val GEARPUMP_UI_AUTHENTICATOR_CLASS = "gearpump.security.ui-authenticator" } diff --git a/docs/dev-ide-setup.md b/docs/dev-ide-setup.md index b82c7b630c..fed97e9845 100644 --- a/docs/dev-ide-setup.md +++ b/docs/dev-ide-setup.md @@ -8,6 +8,15 @@ title: IDE Preparation for Gearpump Development 2. Open menu "File->Open" to open Gearpump root project, then choose the Gearpump source folder. 3. All set. +**NOTE:** Intellij Scala plugin is already bundled with sbt. If you have Scala plugin installed, please don't install additional sbt plugin. Check your settings at "Settings -> Plugins" +**NOTE:** If you are behind a proxy, to speed up the build, please set the proxy for sbt in "Settings -> Build Tools > SBT". in input field "VM parameters", add +``` +-Dhttp.proxyHost= +-Dhttp.proxyPort= +-Dhttps.proxyHost= +-Dhttps.proxyPort= +``` + ### Eclipse IDE Setup I will show how to do this in eclipse LUNA. diff --git a/experiments/akkastream/src/test/scala/akka/stream/gearpump/AttributesSpec.scala b/experiments/akkastream/src/test/scala/akka/stream/gearpump/AttributesSpec.scala index 975c343a34..97a52bf196 100644 --- a/experiments/akkastream/src/test/scala/akka/stream/gearpump/AttributesSpec.scala +++ b/experiments/akkastream/src/test/scala/akka/stream/gearpump/AttributesSpec.scala @@ -1,11 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package akka.stream.gearpump import akka.stream.Attributes import org.scalatest.{FlatSpec, Matchers, WordSpec} -/** - * Created by xzhong10 on 2015/11/6. - */ class AttributesSpec extends FlatSpec with Matchers { it should "merge the attributes together" in { val a = Attributes.name("aa") @@ -13,7 +28,7 @@ class AttributesSpec extends FlatSpec with Matchers { val c = a and b - println("DD:" + c.nameOrDefault()) + assert("aa-bb" == c.nameOrDefault()) } } diff --git a/project/Build.scala b/project/Build.scala index 30c40ba1b0..6c2dae94c2 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -287,7 +287,8 @@ object Build extends sbt.Build { "org.webjars.bower" % "vis" % "4.7.0", "org.webjars.bower" % "clipboard.js" % "0.1.1", "org.webjars.npm" % "dashing-deps" % "0.1.2", - "org.webjars.npm" % "dashing" % "0.4.0" + "org.webjars.npm" % "dashing" % "0.4.0", + "com.softwaremill" %% "akka-http-session" % "0.1.4" ).map(_.exclude("org.scalamacros", "quasiquotes_2.10")).map(_.exclude("org.scalamacros", "quasiquotes_2.10.3"))) lazy val serviceJSSettings = Seq( diff --git a/project/build.properties b/project/build.properties index 4b29a5b2a5..a4820ce974 100644 --- a/project/build.properties +++ b/project/build.properties @@ -16,4 +16,4 @@ # limitations under the License. # -sbt.version=0.13.7 +sbt.version=0.13.9 diff --git a/project/plugins.sbt b/project/plugins.sbt index 78c8bd038e..58b0aa6e82 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,5 @@ resolvers += Resolver.url("fvunicorn", url("http://dl.bintray.com/fvunicorn/sbt-plugins"))(Resolver.ivyStylePatterns) -resolvers += Resolver.url("sbt-plugin", url("http://dl.bintray.com/sbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns) - resolvers += Classpaths.sbtPluginReleases addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.4") diff --git a/services/dashboard/dashboard.js b/services/dashboard/dashboard.js index 559e6b6b84..2afe8b165a 100644 --- a/services/dashboard/dashboard.js +++ b/services/dashboard/dashboard.js @@ -14,7 +14,6 @@ angular.module('dashboard', [ 'dashing', 'io.gearpump.models' ]) - // configure routes .config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { @@ -67,6 +66,7 @@ angular.module('dashboard', [ restapiRoot: location.origin + location.pathname, restapiQueryInterval: 3 * 1000, // in milliseconds restapiQueryTimeout: 30 * 1000, // in milliseconds - restapiTaskLevelMetricsQueryLimit: 100 + restapiTaskLevelMetricsQueryLimit: 100, + loginUrl: location.origin + location.pathname + 'login/login.html' }) ; \ No newline at end of file diff --git a/services/dashboard/index.html b/services/dashboard/index.html index dfb369bb65..99d7083adc 100644 --- a/services/dashboard/index.html +++ b/services/dashboard/index.html @@ -25,6 +25,10 @@ + + + + @@ -43,7 +47,8 @@ - + + @@ -59,8 +64,10 @@ + + @@ -72,6 +79,7 @@ + diff --git a/services/dashboard/login/login.html b/services/dashboard/login/login.html new file mode 100644 index 0000000000..3d024eba1c --- /dev/null +++ b/services/dashboard/login/login.html @@ -0,0 +1,111 @@ + + + + + + + + + + Login + + + + + + Gearpump + + + + +

+

Sign in to Gearpump

+ + + + + + +
+
+
+ + +
+
+
+ + + + + + \ No newline at end of file diff --git a/services/dashboard/login/login.js b/services/dashboard/login/login.js new file mode 100644 index 0000000000..e33c5accbd --- /dev/null +++ b/services/dashboard/login/login.js @@ -0,0 +1,41 @@ +/* + * Licensed under the Apache License, Version 2.0 + * See accompanying LICENSE file. + */ + +/** + * call rest service /login to setup login session tokens. + * If login succeeds, it will redirect to dashboard home page. + */ +function login() { + + var loginUrl = $("#loginUrl").attr('href'); + var index = $("#index").attr('href'); + + $.post(loginUrl, $("#loginForm").serialize() ).done( + function(msg) { + var user = $.parseJSON(msg); + $.cookie("username", user.user, { expires: 365, path: '/' }); + // clear the errors + $("#error").text(""); + // redirect to index.html + $(location).attr('href', index); + } + ) + .fail( function(xhr, textStatus, errorThrown) { + $("#error").text(textStatus + "(" + xhr.status + "): " + xhr.responseText); + }); +} + +/** + * call rest service /logout to clear the session tokens. + */ +function logout() { + var logoutUrl = $("#logoutUrl").attr('href'); + $.post(logoutUrl) +} + +$(document).ready(function() { + // Send a initial logout to clear the sessions. + logout(); +}); \ No newline at end of file diff --git a/services/dashboard/services/login_check.js b/services/dashboard/services/login_check.js new file mode 100644 index 0000000000..1d15dc4f22 --- /dev/null +++ b/services/dashboard/services/login_check.js @@ -0,0 +1,31 @@ +/* + * Licensed under the Apache License, Version 2.0 + * See accompanying LICENSE file. + */ + +angular.module('dashboard') +// Authentication Angular interceptor for http methods. +// If server respond 401 (unauthenticated), will redirect to login page. +.factory('authInterceptor', ['$q', 'conf', function ($q, conf) { + + // Defer the error response to caller after this timeout to avoid browser hang issue + // See https://github.com/gearpump/gearpump/issues/1855 + var deferErrorResponseMs = 3000; + return { + 'responseError': function(response) { + if (response.status == 401) { + window.location.href = conf.loginUrl; + } + + var deferred = $q.defer(); + setTimeout(function() { + deferred.reject(response); + }, 3000); + return deferred.promise; + + } + }; +}]) +.config(['$httpProvider', function ($httpProvider) { + $httpProvider.interceptors.push('authInterceptor'); +}]); \ No newline at end of file diff --git a/services/dashboard/services/streamingservice.js b/services/dashboard/services/streamingservice.js index 05b6b36654..b2352edb46 100644 --- a/services/dashboard/services/streamingservice.js +++ b/services/dashboard/services/streamingservice.js @@ -9,7 +9,7 @@ angular.module('dashboard.streamingservice', []) .factory('StreamingService', ['$http', '$timeout', 'conf', function ($http, $timeout, conf) { return { subscribe: function (request, scope, onMessage) { - $http.get(conf.restapiRoot + '/websocket/url') + $http.get(conf.restapiRoot + 'websocket/url') .success(function (data) { var ws = new WebSocket(data.url); ws.onmessage = onMessage; diff --git a/services/dashboard/views/apps/apps.html b/services/dashboard/views/apps/apps.html index 7e8bda8b13..d6032c4b3d 100644 --- a/services/dashboard/views/apps/apps.html +++ b/services/dashboard/views/apps/apps.html @@ -1,7 +1,7 @@
-
+
Applications
@@ -14,10 +14,9 @@
-
- +
- @@ -30,10 +29,5 @@ records-bind="appsTable.rows" search-bind="search" pagination="10"> -
-

-

No application is running

-

Please submit an user application first.

-
\ No newline at end of file diff --git a/services/dashboard/views/landing/header.html b/services/dashboard/views/landing/header.html index fc67135aa9..9d2d037f05 100644 --- a/services/dashboard/views/landing/header.html +++ b/services/dashboard/views/landing/header.html @@ -22,6 +22,21 @@ Applications + + + + +