Skip to content

Commit

Permalink
Bug Fixing for Electron version.
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix committed Nov 20, 2016
1 parent e627dc1 commit 4f1a38b
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 51 deletions.
2 changes: 2 additions & 0 deletions chromeapp/src/main/scala/util/ChromePlatformService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ object ChromePlatformService extends PlatformService {
case "linux" | "openbsd" => ConfigStorage.DefaultLinuxUrl
case _ => ""
}

override def checkIsLatestVersion(callback: (String) => Unit): Unit = {} // Auto Chrome update
}


Expand Down
1 change: 1 addition & 0 deletions electron/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
4 changes: 3 additions & 1 deletion electron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ npm start
### Package the native app
```
npm install electron-packager
npm install electron-installer-dmg
nom install electron-installer
npm run-script package
npm run-script dmg
```
Binary file added electron/img/logo_small.png.ico
Binary file not shown.
12 changes: 8 additions & 4 deletions electron/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "electron-dockerui",
"version": "1.0.0",
"version": "0.5.1",
"description": "A minimal Electron application",
"main": "main.js",
"scripts": {
"start": "electron .",
"debug": "cd .. && sbt \"project electron\" \"fastOptJS\" && cd electron && electron .",
"test": "sbt test",
"package": "electron-packager . SimpleDockerUI --platform=darwin --arch=x64 --overwrite --icon=img/logo_small.icns --ignore='src' --ignore='target'"
"package": "electron-packager . SimpleDockerUI --platform=darwin --arch=x64 --overwrite --icon=img/logo_small.icns --ignore='src' --ignore='target' --asar",
"dmg": "electron-installer-dmg SimpleDockerUI-darwin-x64/SimpleDockerUI.app SimpleDockerUI --icon=img/logo_small.icns --overwrite"
},
"repository": {
"type": "git",
Expand All @@ -27,9 +28,12 @@
},
"homepage": "https://github.com/felixgborrego/docker-ui-chrome-app",
"devDependencies": {
"electron": "^1.4.0"
"electron": "^1.4.6",
"electron-installer-dmg": "^0.1.2"
},
"dependencies": {
"docker-modem" : "^0.3.1"
"docker-modem": "^0.3.1",
"split-ca": "^1.0.0",
"universal-analytics": "0.4.6"
}
}
32 changes: 29 additions & 3 deletions electron/src/main/scala/util/DockerModem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,21 @@ import scala.scalajs.js.annotation.ScalaJSDefined
class ModemOptions(val socketPath: String,
val host: String,
val protocol: String,
val port: String
val port: String,
val ca: js.UndefOr[InputData] = js.undefined,
val cert: js.UndefOr[InputData] = js.undefined,
val key: js.UndefOr[InputData] = js.undefined
) extends js.Object


@ScalaJSDefined
class DialOptions(val path: String, val method: String, val options: js.UndefOr[InputData], val statusCodes: js.Dictionary[Boolean], val isStream: Boolean = false, val hijack: Boolean = false, val openStdin: Boolean = false) extends js.Object
class DialOptions(val path: String,
val method: String,
val options: js.UndefOr[InputData],
val statusCodes: js.Dictionary[Boolean],
val isStream: Boolean = false,
val hijack: Boolean = false,
val openStdin: Boolean = false) extends js.Object

@ScalaJSDefined
class DockerMessage(val json: String, val reason: String, val statusCode: String) extends js.Object
Expand Down Expand Up @@ -46,7 +55,24 @@ object DockerModem {
log.debug(s"Url: protocol: $protocol, host: $host")
val port = connection.url.drop(protocol.size).drop("://".size).dropWhile(_ != ':').drop(1)
log.debug(s"Url: port: $port")
new ModemOptions(socketPath = null, host = host, protocol = protocol, port = port)

val pathOpt = Option(g.process.env.DOCKER_CERT_PATH.asInstanceOf[String])
val (envCa, envCert, envKey) =
if (protocol == "https" && pathOpt.isDefined) {
val path = pathOpt.get

log.info(s"Using Cert Path $path")
val fs = g.require("fs")
val splitCa = g.require("split-ca")
val ca: js.UndefOr[InputData] = splitCa.apply(s"$path/ca.pem").asInstanceOf[InputData]
val cert: js.UndefOr[InputData] = fs.readFileSync(s"$path/cert.pem").asInstanceOf[InputData]
val key: js.UndefOr[InputData] = fs.readFileSync(s"$path/key.pem").asInstanceOf[InputData]
(ca, cert, key)
} else {
(js.undefined, js.undefined, js.undefined)
}

new ModemOptions(socketPath = null, host = host, protocol = protocol, port = port, ca = envCa, cert = envCert, key = envKey)
}
}
log.debug(s"modemOptions: protocol: ${modemOptions.protocol} path: ${modemOptions.socketPath}")
Expand Down
38 changes: 38 additions & 0 deletions electron/src/main/scala/util/ElectronAnalytics.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package util

import util.logger.log

import scala.scalajs.js

// Analytics using https://www.npmjs.com/package/universal-analytics
object ElectronAnalytics {

import js.Dynamic.{global => g}

val ua = g.require("universal-analytics")

def sendEvent(category: String, action: String, label: String): Unit = {
val visitor = ua(GoogleUA, userId())
visitor.event(category, action, label).send()
log.info(s"sendEvent: $category, $action, $label")
}

def sendPageView(name: String): Unit = {
val visitor = ua(GoogleUA, userId())
visitor.pageview(name).send()
log.info(s"sendAppView: $name")
}

def sendException(ex: String): Unit = {
val visitor = ua(GoogleUA, userId())
log.info(s"sendException: $ex")
visitor.exception(s"sendException: $ex").send()
}

val GoogleUA = "UA-61270183-1"

// TODO Use custom user id
// https://www.npmjs.com/package/electron-machine-id
def userId(): String = "dev-1"

}
23 changes: 15 additions & 8 deletions electron/src/main/scala/util/ElectronPlatformService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ import scala.scalajs.js
import scala.scalajs.js.{Dynamic, URIUtils}

object ElectronPlatformService extends PlatformService {
override def appVersion: String = "Electron DockerUI beta"

override def appVersion: String = {
import js.Dynamic.{global => g}
val electron = g.require("electron")
electron.remote.app.getVersion().asInstanceOf[String]
}

val keyStoragePrefix = "v1_"

override def osName: Future[String] = Future.successful("Electron Platform")
Expand All @@ -36,15 +42,15 @@ object ElectronPlatformService extends PlatformService {
}

override def sendAppView(name: String): Unit = {
log.info(s"sendAppView: $name")
ElectronAnalytics.sendPageView(name)
}

override def sendEvent(category: String, action: String, label: String): Unit = {
log.info(s"sendEvent: $category, $action, $label")
ElectronAnalytics.sendEvent(category, action, label)
}

override def sendException(ex: String): Unit = {
log.info(s"sendException: $ex")
ElectronAnalytics.sendException(ex)
}

override def buildDockerClient(con: Connection): DockerClient = try {
Expand All @@ -62,10 +68,13 @@ object ElectronPlatformService extends PlatformService {
log.debug(s"Default docker url $DefaultDockerURL")
DefaultDockerURL
}

override def checkIsLatestVersion(callback: (String) => Unit): Unit = CheckIsLatestVersion.check(callback)
}

class ElectronDockerConnection(val connection: Connection) extends DockerConnection {
import DockerClientConfig._

import js.JSConverters._

val info = connection.url
Expand All @@ -82,14 +91,14 @@ class ElectronDockerConnection(val connection: Connection) extends DockerConnect
val callback: js.Function2[js.Any, js.Dynamic, Unit] =
(msg: js.Any, response: js.Dynamic) => {
if (msg == null) {
log.debug(s"Processing dail response...")
//log.debug(s"Processing dail response...")
if (dialOptions.hijack) {
processHijackResponse(onWebSocketCreated, response)
} else if (dialOptions.isStream) {
processStreamingResponse(onStreamingData, shouldAbort, response)
} else {
val responseText = js.Dynamic.global.JSON.stringify(response).asInstanceOf[String]
log.debug(s"dial response: ${responseText.take(1000)}...")
//log.debug(s"dial response: ${responseText.take(1000)}...")
p.success(Response(responseText, 200))
}
} else {
Expand Down Expand Up @@ -203,7 +212,6 @@ class ElectronDockerConnection(val connection: Connection) extends DockerConnect

// TODO refactor
def events(update: Seq[DockerEvent] => Unit): ConnectedStream = {
log.info("[dockerClient.events] start")
val since = {
val t = new js.Date()
t.setDate(t.getDate() - 3) // pull 3 days
Expand All @@ -225,7 +233,6 @@ class ElectronDockerConnection(val connection: Connection) extends DockerConnect
update(currentStream.events)
}

log.info(s"[dockerClient.events] start: $eventsUrl")
val options = new DialOptions(path = eventsUrl, method = "GET", options = js.undefined, Map(("200", true)).toJSDictionary, isStream = true)
dial(options, onStreamingData, (Unit) => isAborted)

Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.12
sbt.version=0.13.13
19 changes: 0 additions & 19 deletions src/main/scala/demo/Main.scala_

This file was deleted.

29 changes: 19 additions & 10 deletions src/main/scala/ui/pages/HomePage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ case object HomePage extends Page {

val id = "HomePage"

case class State(info: Option[DockerMetadata] = None, events: Seq[DockerEvent] = Seq.empty, error: Option[String] = None, stream: Option[ConnectedStream] = None)
case class State(info: Option[DockerMetadata] = None,
events: Seq[DockerEvent] = Seq.empty,
error: Option[String] = None,
stream: Option[ConnectedStream] = None,
message: Option[String] = None)

case class Props(ref: WorkbenchRef) {
def url = ref.connection.map(_.url).getOrElse("''")
Expand Down Expand Up @@ -60,6 +64,11 @@ case object HomePage extends Page {

def willUnMount(): Unit = t.state.stream.map(_.abort())

def didMount(): Unit = {
PlatformService.current.checkIsLatestVersion { msg =>
t.modState(s => s.copy(message = Some(msg)))
}
}
def refresh() = willMount()
}

Expand All @@ -79,37 +88,37 @@ object HomePageRender {
.backend(new Backend(_))
.render((P, S, B) => vdom(S, P, B))
.componentWillMount(_.backend.willMount)
.componentDidMount(_.backend.didMount)
.componentWillUnmount(_.backend.willUnMount)
.build

def vdom(S: State, P: Props, B: Backend) = <.div(
S.error.map(Alert(_, Some(P.ref.link(SettingsPage)))),
S.info.map(vdomInfo(_, P.ref, B)),
S.info.map(vdomInfo(_, P.ref, B, S)),
vdomEvents(S.events)
)

def vdomInfo(docker: DockerMetadata, ref: WorkbenchRef, B: Backend) = {
def vdomInfo(docker: DockerMetadata, ref: WorkbenchRef, B: Backend, S: State) = {
val info = Map(
"Connected to" -> docker.connectionInfo,
"Version" -> s"${docker.version.Version} (api: ${docker.version.ApiVersion})",
"Docker UI" -> PlatformService.current.appVersion
)
<.div(
ContainersCard(docker, ref)(() => B.refresh()),
InfoCard(info, InfoCard.SMALL, Some("System"), footer = infoFooter),
InfoCard(info, InfoCard.SMALL, Some("System"), footer = infoFooter(S.message)),
!docker.info.swarmMasterInfo.isEmpty ?= InfoCard(docker.info.swarmMasterInfo, InfoCard.SMALL, Some("Swarm Info")),
!docker.info.swarmNodesDescription.isEmpty ?= docker.info.swarmNodesDescription.map { nodeInfo =>
InfoCard(nodeInfo, InfoCard.SMALL, nodeInfo.keys.headOption)
}
)
}

val infoFooter = Some(<.div(^.className := "panel-footer alert-warning",
""" Google has deprecated Chrome Apps.
| This app will no longer be available on the Web Store to users on Windows, Mac or Linux.
| For new updates, you can migrate to """.stripMargin,
<.a(^.href := "https://github.com/felixgborrego/docker-ui-chrome-app/wiki/Install-Simple-Docker-UI-for-Desktop", ^.target := "blank", " the Desktop version of this app!"
)))
def infoFooter(message: Option[String]) = message.map { msg =>
<.div(^.className := "panel-footer alert-warning",
msg
)
}


def vdomEvents(events: Seq[DockerEvent]) = {
Expand Down
10 changes: 5 additions & 5 deletions src/main/scala/ui/pages/SettingsPage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object SettingsPage extends Page {
def verifyConnection(connection: Connection): Future[Unit] =
connection.checkConnection().map { c =>
t.modState { s =>
s.copy(info = s.info.replace(connection, c))
s.copy(info = s.info.replace(connection, c), error = c.error)
}
}

Expand Down Expand Up @@ -244,7 +244,7 @@ object SettingsPageModel {
object ConnectionUnableToConnect extends ConnectionState
object ConnectionInvalidApi extends ConnectionState

case class Connection(urlText: String, state: ConnectionState, id: UUID = UUID.randomUUID) {
case class Connection(urlText: String, state: ConnectionState, error: Option[String] = None, id: UUID = UUID.randomUUID) {
def isValid = state == ConnectionValid
val url = urlText.toLowerCase

Expand All @@ -258,14 +258,14 @@ object SettingsPageModel {
case false => Connection(url, ConnectionInvalidApi)
}.recover {
case ex: Exception =>
log.info(s"Unable to connected to $url")
Connection(url, ConnectionUnableToConnect)
log.info(s"Unable to connected to $url - ${ex.getMessage}")
Connection(url, ConnectionUnableToConnect, Some(ex.getMessage))
}
}

def stateMessage = state match {
case ConnectionVerifying | ConnectionNoVerified => ""
case ConnectionUnableToConnect => "Unable to connect"
case ConnectionUnableToConnect => s"Unable to connect"
case ConnectionInvalidApi => s"Invalid API version, minimum API supported is ${DockerClientConfig.DockerVersion}"
case ConnectionValid => "Valid connection"
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/scala/util/CheckIsLatestVersion.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package util

import org.scalajs.dom.ext.{Ajax, AjaxException}
import upickle.default._

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import logger.log

object CheckIsLatestVersion {
def installedVersion(): String = PlatformService.current.appVersion

case class Release(name: String)

val Url = "https://api.github.com/repos/felixgborrego/docker-ui-chrome-app/releases/latest"

def latestVersion(): Future[String] = {
Ajax.get(Url, timeout = 500).map { xhr =>
read[Release](xhr.responseText)
}.map(_.name)
}

var alreadyChecked = false

def check(callback: (String => Unit)): Unit = if (!alreadyChecked) {
alreadyChecked = true
val installed = installedVersion()
latestVersion().map {
case `installed` => log.info(s"Installed version $installed is the latest version")
case latest => callback(s"There is a new version available $latest")
}.onFailure {
case ex: AjaxException => log.info(s"Unable to fetch latest version - ${ex.xhr.responseText}")
}
}


}
Loading

0 comments on commit 4f1a38b

Please sign in to comment.