├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── sbt └── src ├── main ├── java │ └── gr │ │ └── gnostix │ │ └── freeswitch │ │ ├── ConectionStatus.java │ │ ├── EslConnection.java │ │ └── MyEslEventListener.java ├── resources │ ├── dialcodes.csv │ ├── dialcodesErrors.csv │ └── logback.xml ├── scala │ ├── ScalatraBootstrap.scala │ └── gr │ │ └── gnostix │ │ └── freeswitch │ │ ├── FreeswitchopStack.scala │ │ ├── actors │ │ ├── ActorsJsonProtocol.scala │ │ ├── ActorsProtocol.scala │ │ ├── BasicStatsActor.scala │ │ ├── CallActor.scala │ │ ├── CallRouter.scala │ │ ├── CentralMessageRouter.scala │ │ ├── ChannelActor.scala │ │ ├── CompletedCallsActor.scala │ │ ├── DialCodesActor.scala │ │ ├── EslConnectionDispatcherActor.scala │ │ ├── EslEventRouter.scala │ │ ├── EventType.scala │ │ ├── FailedCallsActor.scala │ │ ├── HeartBeatActor.scala │ │ └── WSLiveEventsActor.scala │ │ ├── auth │ │ ├── AuthenticationSupport.scala │ │ └── strategies │ │ │ ├── TheBasicAuthStrategy.scala │ │ │ └── UserPasswordStrategy.scala │ │ ├── model │ │ ├── PlainModels.scala │ │ ├── SecureClient.scala │ │ └── User.scala │ │ ├── servlets │ │ ├── ChatController.scala │ │ ├── ConfigurationServlet.scala │ │ ├── EslActorApp.scala │ │ ├── LoginServlet.scala │ │ └── WSEslServlet.scala │ │ └── utilities │ │ ├── DateUtils.scala │ │ ├── EmailUtils.java │ │ ├── FileUtilities.scala │ │ └── HelperFunctions.scala └── webapp │ ├── WEB-INF │ └── web.xml │ ├── configuration.html │ ├── css │ ├── bootstrap-responsive.min.css │ ├── bootstrap-rtl.css │ ├── bootstrap-rtl.min.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── bootstrap.min.css.ol │ ├── growl │ │ └── jquery.gritter.css │ ├── plugins │ │ └── morris.css │ ├── sb-admin-rtl.css │ ├── sb-admin.css │ ├── style.css │ └── upload │ │ └── jquery.fileupload.css │ ├── dashboard.html │ ├── dialog │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ ├── chosen.css │ │ ├── elfinder.min.css │ │ ├── elfinder.theme.css │ │ ├── font-awesome-ie7.min.css │ │ ├── font-awesome.min.css │ │ ├── fullcalendar.css │ │ ├── glyphicons.css │ │ ├── halflings.css │ │ ├── ie.css │ │ ├── ie9.css │ │ ├── jquery-ui-1.8.21.custom.css │ │ ├── jquery.cleditor.css │ │ ├── jquery.gritter.css │ │ ├── jquery.iphone.toggle.css │ │ ├── jquery.noty.css │ │ ├── noty_theme_default.css │ │ ├── style-forms.css │ │ ├── style-responsive.css │ │ ├── style.css │ │ ├── styleMetro.css │ │ ├── uniform.default.css │ │ └── uploadify.css │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── counter.js │ │ ├── custom.js │ │ ├── excanvas.js │ │ ├── fullcalendar.min.js │ │ ├── jquery-1.9.1.min.js │ │ ├── jquery-migrate-1.0.0.min.js │ │ ├── jquery-ui-1.10.0.custom.min.js │ │ ├── jquery.chosen.min.js │ │ ├── jquery.cleditor.min.js │ │ ├── jquery.cookie.js │ │ ├── jquery.dataTables.min.js │ │ ├── jquery.elfinder.min.js │ │ ├── jquery.flot.js │ │ ├── jquery.flot.pie.js │ │ ├── jquery.flot.resize.min.js │ │ ├── jquery.flot.stack.js │ │ ├── jquery.gritter.min.js │ │ ├── jquery.imagesloaded.js │ │ ├── jquery.iphone.toggle.js │ │ ├── jquery.knob.modified.js │ │ ├── jquery.masonry.min.js │ │ ├── jquery.noty.js │ │ ├── jquery.raty.min.js │ │ ├── jquery.sparkline.min.js │ │ ├── jquery.ui.touch-punch.js │ │ ├── jquery.uniform.min.js │ │ ├── jquery.uploadify-3.1.min.js │ │ ├── modernizr.js │ │ └── retina.js │ ├── font-awesome │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── less │ │ ├── bordered-pulled.less │ │ ├── core.less │ │ ├── fixed-width.less │ │ ├── font-awesome.less │ │ ├── icons.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── mixins.less │ │ ├── path.less │ │ ├── rotated-flipped.less │ │ ├── spinning.less │ │ ├── stacked.less │ │ └── variables.less │ └── scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _spinning.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── font-awesome.scss │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── images │ ├── Feedback_Button.png │ ├── background.jpg │ ├── favicon.ico │ ├── gnostix.png │ ├── gnostixWhite.png │ └── growl │ │ ├── confirm.png │ │ ├── error.png │ │ ├── gritter-light.png │ │ ├── gritter-long.png │ │ ├── gritter.png │ │ ├── ie-spacer.gif │ │ └── trees.jpg │ ├── index.html │ ├── indexDialog.html │ ├── indexGrid.html │ ├── js │ ├── alx-configuration.js │ ├── application.js │ ├── arcGauge.js │ ├── atmosphere.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── charts.js │ ├── createGauge.js │ ├── custom.js │ ├── export-csv.js │ ├── init.js │ ├── jquery-1.9.0.js │ ├── jquery-1.9.1.min.js │ ├── jquery-migrate-1.0.0.min.js │ ├── jquery-ui-1.10.0.custom.min.js │ ├── jquery.cookie.js │ ├── jquery.dataTables.min.js │ ├── jquery.js │ ├── lazyloading.js │ ├── plugins │ │ ├── flot │ │ │ ├── excanvas.min.js │ │ │ ├── flot-data.js │ │ │ ├── jquery.flot.js │ │ │ ├── jquery.flot.pie.js │ │ │ ├── jquery.flot.resize.js │ │ │ └── jquery.flot.tooltip.min.js │ │ ├── growl │ │ │ └── jquery.gritter.js │ │ ├── morris │ │ │ ├── morris-data.js │ │ │ ├── morris.js │ │ │ ├── morris.min.js │ │ │ └── raphael.min.js │ │ └── upload │ │ │ ├── jquery.fileupload-process.js │ │ │ ├── jquery.fileupload-validate.js │ │ │ ├── jquery.fileupload.js │ │ │ ├── jquery.iframe-transport.js │ │ │ ├── main.js │ │ │ └── vendor │ │ │ └── jquery.ui.widget.js │ ├── setupProfile.js │ └── themes │ │ ├── customTheme.js │ │ └── darkTheme.js │ └── setupProfile.html └── test └── scala └── gr └── gnostix └── freeswitch ├── CentralServletSpec.scala └── actors ├── TestAllSystemActor.scala └── TestFailedCallsActor.scala /.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | 9 | # java specific 10 | *.class 11 | 12 | # akka specific 13 | logs/* 14 | 15 | # sbt specific 16 | target/ 17 | project/boot 18 | lib_managed/* 19 | project/build/target 20 | project/build/lib_managed 21 | project/build/src_managed 22 | project/plugins/lib_managed 23 | project/plugins/target 24 | project/plugins/src_managed 25 | project/plugins/project 26 | 27 | core/lib_managed 28 | core/target 29 | pubsub/lib_managed 30 | pubsub/target 31 | 32 | # eclipse specific 33 | .metadata 34 | jrebel.lic 35 | .settings 36 | .classpath 37 | .project 38 | 39 | .ensime* 40 | *.sublime-* 41 | .cache 42 | 43 | # intellij 44 | *.eml 45 | *.iml 46 | *.ipr 47 | *.iws 48 | .*.sw? 49 | .idea 50 | 51 | # paulp script 52 | /.lib/ 53 | # Alex files 54 | src/main/scala/gr/gnostix/freeswitch/tmp/* 55 | hs_err* 56 | *.orig 57 | testFile.log 58 | *.log 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Freeswitch-monitoring 2 | Project to monitor FreeSwitch status and health in Real-Time. 3 | 4 | 5 | ## Description 6 | 7 | Freeswitch-monitoring is a distributed application based in the actor-based paradigm. As events are comming from the Freeswitch ESL connection, these messages are routed and processed by the Actor system, keeping in this way the main statistics data ready in memory for fast access. These data are also served in the front client through a WS or/and an REST API. 8 | The Scala, Scalatra ,Atmosphere and Akka.io are the tools used to achive the goal of this project. 9 | 10 | 11 | 12 | # Design stack 13 | 14 | ### Application stack 15 | 16 | ![image](http://vieras.eu/wp-content/uploads/2015/09/Application-Diagram.png) 17 | 18 | 19 | 20 | ### Actor System 21 | 22 | ![image](http://vieras.eu/wp-content/uploads/2015/09/Actor-System.png) 23 | 24 | ### Dashboard pic 25 | 26 | ![image](http://vieras.eu/wp-content/uploads/2016/04/fsmoni-dashboard.png) 27 | ![image](http://vieras.eu/wp-content/uploads/2017/01/live_calls.png) 28 | 29 | ## How to run 30 | 31 | First have a Freeswitch instance. You can install the war file in two ways: 32 | 33 | a) Clone the project and from the application git folder, run bellow commands. 34 | 35 | * ./sbt 36 | * compile 37 | * jetty:start 38 | * jetty:stop 39 | 40 | After the last command the container will start on localhost:8080 41 | 42 | b) 43 | You can also [download](http://fs-moni.cloudapp.net/freeswitchop_2.11-0.1.0-SNAPSHOT.war) a ready build war file. 44 | 45 | * download file from location 46 | * have installed in a server java oracle 7 or 8 47 | * have installed a jetty server 9 between versions 9.2.1.v20140609 or 9.2.10.v20150310 48 | * add war file as root app in the Jetty webapps folder. We do that by copying the freeswitch-monitoring.war file to root.war file inside the webapps folder. e.g. in linux `cp /war/location/freeswitch-monitoring.war /path/to/jetty/webapps/root.war` 49 | * start jetty and connect your browser to url jetty-ip:8080 50 | 51 | After the installation is complete then: 52 | 53 | The application will try to connect to default hostname fs-instance.com and password ClueCon. You can add to your machine in /etc/hosts file the correct entry for fs-instance.com OR you can just navigate through the interface and go to the configuration and add there your Freeswitch credentials. 54 | 55 | 56 | Login use credentials **user: admin** and **password: admin** 57 | 58 | ### Basic events arriving in the web socket 59 | 60 | - New Call 61 | - End Call 62 | - Failed Call 63 | - Freeswitch Heartbeat 64 | - Basic Stats (ACD, ASR, Live Calls..) 65 | 66 | 67 | 68 | ### HTTP/WS routes 69 | 70 | 1. ws://localhost:8080/fs-moni/live/events 71 | 2. http://localhost:8080/actors/GetCompletedCalls 72 | 3. http://localhost:8080/actors/GetConcurrentCalls 73 | 4. http://localhost:8080/actors/GetTotalConcurrentCalls 74 | 5. http://localhost:8080/actors/GetFailedCalls 75 | 6. http://localhost:8080/actors/GetTotalFailedCalls 76 | 7. http://localhost:8080/actors/call/:callid 77 | 7. http://localhost:8080/actors/call/:callUuid/channel/:channelUuid 78 | 8. http://localhost:8080/actors/lastHeartbeat 79 | 9. http://localhost:8080/actors/allHeartbeats 80 | 10. http://localhost:8080/actors/stats/GetBasicStatsTimeSeries 81 | 82 | ## Contact: p_alx at hotmail dot com 83 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import org.scalatra.sbt._ 2 | import org.scalatra.sbt.PluginKeys._ 3 | import com.mojolly.scalate.ScalatePlugin._ 4 | import ScalateKeys._ 5 | 6 | 7 | 8 | ScalatraPlugin.scalatraSettings 9 | 10 | scalateSettings 11 | 12 | scalacOptions := Seq("-unchecked", "-feature", "-deprecation", "-encoding", "utf8") 13 | 14 | organization := "gr.gnostix" 15 | 16 | name := "FreeswitchOP" 17 | 18 | version := "0.1.0-SNAPSHOT" 19 | 20 | scalaVersion := "2.11.8" 21 | 22 | resolvers += Classpaths.typesafeReleases 23 | 24 | val ScalatraVersion = "2.5.0" 25 | 26 | val json4sversion = "3.3.0" 27 | 28 | val jettyVersion = "9.2.10.v20150310" 29 | 30 | val akkaVersion = "2.4.16" 31 | 32 | libraryDependencies ++= Seq( 33 | "org.scalatra" %% "scalatra" % ScalatraVersion, 34 | "org.scalatra" %% "scalatra-auth" % ScalatraVersion, 35 | "org.scalatra" %% "scalatra-scalate" % ScalatraVersion, 36 | "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test", 37 | "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", 38 | "org.scalatra" %% "scalatra-atmosphere" % ScalatraVersion, // exclude("org.atmosphere", "atmosphere-compat-tomcat"), 39 | "org.json4s" %% "json4s-jackson" % json4sversion, 40 | "org.eclipse.jetty" % "jetty-webapp" % jettyVersion % "container;compile", 41 | "org.eclipse.jetty" % "jetty-plus" % jettyVersion % "container;provided", 42 | "org.eclipse.jetty" % "jetty-servlets" % jettyVersion, 43 | "org.eclipse.jetty.websocket" % "websocket-server" % jettyVersion % "container;provided", 44 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "container;provided;test" artifacts Artifact("javax.servlet-api", "jar", "jar"), 45 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 46 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 47 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 48 | "org.scalatest" % "scalatest_2.11" % "2.2.5" % "test", 49 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.3", 50 | "org.freeswitch.esl.client" % "org.freeswitch.esl.client" % "0.9.2", 51 | "ch.qos.logback" % "logback-classic" % "1.1.2" % "runtime", 52 | "javax.mail" % "mail" % "1.4.1", 53 | "joda-time" % "joda-time" % "2.9.7", 54 | "org.joda" % "joda-convert" % "1.8.1" 55 | ) 56 | 57 | scalateTemplateConfig in Compile := { 58 | val base = (sourceDirectory in Compile).value 59 | Seq( 60 | TemplateConfig( 61 | base / "webapp" / "WEB-INF" / "templates", 62 | Seq.empty, /* default imports should be added here */ 63 | Seq( 64 | Binding("context", "_root_.org.scalatra.scalate.ScalatraRenderContext", importMembers = true, isImplicit = true) 65 | ), /* add extra bindings here */ 66 | Some("templates") 67 | ) 68 | ) 69 | } 70 | 71 | enablePlugins(JettyPlugin) -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.13 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /src/main/java/gr/gnostix/freeswitch/ConectionStatus.java: -------------------------------------------------------------------------------- 1 | package gr.gnostix.freeswitch; 2 | 3 | /** 4 | * Created by rebel on 3/9/15. 5 | */ 6 | public class ConectionStatus { 7 | 8 | public ConectionStatus(boolean connected, String message) { 9 | this.connected = connected; 10 | this.message = message; 11 | } 12 | 13 | public boolean isConnected() { 14 | return connected; 15 | } 16 | 17 | public String getMessage() { 18 | return message; 19 | } 20 | 21 | private boolean connected; 22 | private String message; 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/gr/gnostix/freeswitch/EslConnection.java: -------------------------------------------------------------------------------- 1 | package gr.gnostix.freeswitch; 2 | 3 | import akka.actor.ActorRef; 4 | import org.freeswitch.esl.client.inbound.Client; 5 | import org.freeswitch.esl.client.inbound.InboundConnectionFailure; 6 | 7 | /** 8 | * Created by rebel on 27/8/15. 9 | */ 10 | public class EslConnection { 11 | 12 | private Client conn = null; 13 | private ActorRef eslMessageRouter; 14 | private String ip; 15 | private int port; 16 | private String password; 17 | 18 | public EslConnection(ActorRef eslMessageRouter, String ip, int port, String password) { 19 | this.conn = new Client(); 20 | this.eslMessageRouter = eslMessageRouter; 21 | this.ip = ip; 22 | this.port = port; 23 | this.password = password; 24 | } 25 | 26 | public ActorRef getActor(){ 27 | return this.eslMessageRouter; 28 | } 29 | 30 | public ConectionStatus connectEsl() { 31 | try { 32 | conn.connect(ip, 8021, password, 10); 33 | 34 | if (conn.canSend() == true) 35 | { 36 | System.out.println("conn.canSend() connected"); 37 | //conn.setEventSubscriptions("plain", "all"); 38 | conn.setEventSubscriptions( "plain", "CHANNEL_HANGUP_COMPLETE CHANNEL_ANSWER HEARTBEAT" ); 39 | conn.addEventListener(new MyEslEventListener(eslMessageRouter)); 40 | } 41 | return new ConectionStatus(true, "all good"); 42 | //conn.setEventSubscriptions( "plain", "CHANNEL_HANGUP_COMPLETE CHANNEL_CALLSTATE CHANNEL_CREATE CHANNEL_EXECUTE CHANNEL_EXECUTE_COMPLETE CHANNEL_DESTROY" ); 43 | } catch (InboundConnectionFailure e) { 44 | System.out.println("------- ESL connection failed. !! " + e.getMessage()); 45 | //e.printStackTrace(); 46 | return new ConectionStatus(false, e.getMessage()); 47 | } 48 | } 49 | 50 | public ConectionStatus checkConnection(){ 51 | ConectionStatus status; 52 | if (conn.canSend() == true) { 53 | //System.out.println("connected"); 54 | status = new ConectionStatus(true, "all good"); 55 | } else { 56 | status = connectEsl(); 57 | } 58 | return status; 59 | } 60 | 61 | public void deinitConnection() { 62 | conn.close(); 63 | conn = null; 64 | } 65 | 66 | public String getIP(){ 67 | return this.ip; 68 | } 69 | 70 | public int getPort(){ 71 | return this.port; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/gr/gnostix/freeswitch/MyEslEventListener.java: -------------------------------------------------------------------------------- 1 | package gr.gnostix.freeswitch; 2 | 3 | import akka.actor.ActorRef; 4 | import gr.gnostix.freeswitch.actors.ActorsProtocol; 5 | import gr.gnostix.freeswitch.actors.EslEventRouter; 6 | import org.freeswitch.esl.client.IEslEventListener; 7 | import org.freeswitch.esl.client.transport.event.EslEvent; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import gr.gnostix.freeswitch.actors.CallRouter; 16 | 17 | public class MyEslEventListener implements IEslEventListener { 18 | 19 | public MyEslEventListener(ActorRef callRouter){ 20 | this.callRouter = callRouter; 21 | } 22 | private ActorRef callRouter = null; 23 | // Create the 'helloakka' actor system 24 | //final ActorSystem system = ActorSystem.create("HelloFS"); 25 | 26 | // Create the event receiver actor 27 | //final ActorRef eventAct = system.actorOf(Props.create(EslEventRouter.class), "eventAct"); 28 | 29 | 30 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 31 | 32 | public void eventReceived(EslEvent event) { 33 | // System.out.println(" --- event received ---" + event.getEventName()); 34 | // System.out.println(getEventToLog(event)); 35 | // System.out.println(" ---------------------------- event received END ------------------------------"); 36 | 37 | // eventAct should be the router where the events will be dispatched!!!!!! 38 | callRouter.tell(ActorsProtocol.mkEvent(event), ActorRef.noSender()); 39 | 40 | //System.out.println(getEventBodyLinesToLog(event)); 41 | //log.info("eventReceived [{}]\n[{}]\n", event, getEventToLog(event)); 42 | } 43 | 44 | public void backgroundJobResultReceived(EslEvent event) { 45 | System.out.println(" --- event backgroundJobResultReceived ---" + event.getEventName()); 46 | log.info("backgroundJobResultReceived [{}]\n[{}]\n", event, 47 | getEventToLog(event)); 48 | } 49 | 50 | private String getEventToLog(EslEvent event) { 51 | StringBuffer buf = new StringBuffer(); 52 | Map map = event.getEventHeaders(); 53 | Set set = map.keySet(); 54 | for (String name : set) { 55 | buf.append(name + " " + map.get(name) + "\n"); 56 | } 57 | return buf.toString(); 58 | } 59 | 60 | public String getEventBodyLinesToLog(EslEvent event) { 61 | StringBuffer buf = new StringBuffer(); 62 | List li = event.getEventBodyLines(); 63 | for (String name : li) { 64 | buf.append(name + "\n"); 65 | } 66 | return buf.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | import javax.servlet.ServletContext 20 | 21 | import _root_.akka.actor.{ActorSystem, Props} 22 | import gr.gnostix.freeswitch.actors.CentralMessageRouter 23 | import gr.gnostix.freeswitch.actors.ServletProtocol.{ApiReplyError, ApiReplyData} 24 | import gr.gnostix.freeswitch.servlets.{LoginServlet, ConfigurationServlet, EslActorApp, WSEslServlet} 25 | import gr.gnostix.freeswitch.utilities.FileUtilities 26 | import org.scalatra._ 27 | import org.scalatra.example.atmosphere.ChatController 28 | import org.slf4j.LoggerFactory 29 | 30 | import scala.collection.SortedMap 31 | 32 | class ScalatraBootstrap extends LifeCycle { 33 | val system = ActorSystem("esl-sys") 34 | val logger = LoggerFactory.getLogger(getClass) 35 | 36 | val dialCodes: Map[String, SortedMap[String, String]] = FileUtilities.processResourcesCsvFile() match { 37 | case x: ApiReplyData => 38 | val dialC = x.payload.asInstanceOf[Map[String, SortedMap[String, String]]] 39 | //logger info s"----> ${x.message} and head list e.g. ${dialC.head} " 40 | dialC 41 | 42 | case x: ApiReplyError => 43 | logger error x.message 44 | Map("default" -> scala.collection.SortedMap.empty[String, String]) 45 | case _ => 46 | logger.debug("we don't understand the reply back from dial codes actor when trying to upload a file") 47 | Map("default" -> scala.collection.SortedMap.empty[String, String]) 48 | } 49 | 50 | val myRouter = system.actorOf(Props(classOf[CentralMessageRouter], dialCodes), "centralMessageRouter") 51 | 52 | override def init(context: ServletContext) { 53 | context.mount(new ConfigurationServlet(system, myRouter) 54 | , "/configuration/*") 55 | context.mount(new ChatController, "/the-chat") 56 | context.mount(new LoginServlet, "/user/*") 57 | context.mount(new WSEslServlet(system, myRouter), "/fs-moni/live/*") 58 | context.mount(new EslActorApp(system, myRouter), "/actors/*") 59 | } 60 | 61 | override def destroy(context:ServletContext) { 62 | //myConn.deinitConnection() 63 | system.terminate() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/FreeswitchopStack.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch 20 | 21 | import org.scalatra._ 22 | import org.scalatra.scalate.ScalateSupport 23 | import org.slf4j.LoggerFactory 24 | 25 | trait FreeswitchopStack extends ScalatraServlet with ScalateSupport { 26 | 27 | implicit val logger = LoggerFactory.getLogger(getClass) 28 | 29 | notFound { 30 | // remove content type in case it was set through an action 31 | contentType = null 32 | // Try to render a ScalateTemplate if no route matched 33 | findTemplate(requestPath) map { path => 34 | contentType = "text/html" 35 | layoutTemplate(path) 36 | } orElse serveStaticResource() getOrElse resourceNotFound() 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/ActorsJsonProtocol.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import gr.gnostix.freeswitch.actors.ActorsProtocol.RouterProtocol 22 | import org.json4s.{Extraction, NoTypeHints} 23 | import org.json4s.jackson.Serialization 24 | import org.json4s.jackson.JsonMethods._ 25 | import org.json4s.jackson.Serialization.{read, write} 26 | import scala.language.implicitConversions 27 | import org.scalatra.atmosphere.JsonMessage 28 | 29 | 30 | /** 31 | * Created by rebel on 12/8/15. 32 | */ 33 | object ActorsJsonProtocol { 34 | implicit val formats = Serialization.formats(NoTypeHints) 35 | 36 | 37 | def caseClassToJsonMessage(message: Any): JsonMessage = 38 | JsonMessage(Extraction.decompose(message)) 39 | 40 | def callsTimeSeriesToJson(callsTimeSeries: BasicStatsCalls): JsonMessage = 41 | JsonMessage(Extraction.decompose(callsTimeSeries)) 42 | 43 | def heartbeatToJson(heartBeat: HeartBeat): JsonMessage = 44 | JsonMessage(Extraction.decompose(heartBeat)) 45 | 46 | implicit def newCallToJson(newCall: CallNew): JsonMessage = 47 | JsonMessage(Extraction.decompose(newCall)) 48 | 49 | implicit def endCallToJson(endCall: CallEnd): JsonMessage = 50 | // JsonMessage(parse(write(endCall))) 51 | JsonMessage(Extraction.decompose(endCall)) 52 | 53 | implicit def failedCallToJson(failedCall: FailedCall): JsonMessage = 54 | JsonMessage(Extraction.decompose(failedCall)) 55 | 56 | implicit def heartbeatToText(heartBeat: HeartBeat): String = 57 | //pretty(render(parse(write(heartBeat)))) 58 | pretty(render(Extraction.decompose(heartBeat))) 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/ActorsProtocol.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import java.sql.Timestamp 22 | import akka.actor.ActorRef 23 | 24 | import scala.collection.JavaConverters._ 25 | 26 | import org.freeswitch.esl.client.transport.event.EslEvent 27 | 28 | import scala.collection.SortedMap 29 | 30 | /** 31 | * Created by rebel on 23/8/15. 32 | */ 33 | 34 | 35 | object ActorsProtocol { 36 | 37 | sealed trait RouterProtocol 38 | 39 | sealed trait RouterRequest extends RouterProtocol 40 | 41 | sealed trait RouterResponse extends RouterProtocol 42 | 43 | case class Event(headers: scala.collection.Map[String, String]) extends RouterRequest 44 | 45 | case object InitializeDashboardHeartBeat extends RouterRequest 46 | 47 | case object InitializeDashboardBasicStats extends RouterRequest 48 | 49 | case object GetConcurrentCalls extends RouterRequest 50 | 51 | case object GetCompletedCalls extends RouterRequest 52 | 53 | case object GetTotalConcurrentCalls extends RouterRequest 54 | 55 | case class ConcurrentCallsNum(calls: Int) extends RouterResponse 56 | 57 | case class ConcurrentCallsChannels(calls: List[CallNew]) extends RouterResponse 58 | 59 | case object GetTotalFailedCalls extends RouterRequest 60 | 61 | case object GetFailedCalls extends RouterRequest 62 | 63 | case class GetFailedCallsAnalysis(fromNumberOfDigits: Int, toNumberOfDigits: Int) extends RouterRequest 64 | 65 | case class GetFailedCallsByDate(from: Timestamp, to: Timestamp) extends RouterRequest 66 | 67 | case class GetCallsResponse(totalCalls: Int, activeCallsUUID: List[String]) extends RouterResponse 68 | 69 | case class GetCallInfo(uuid: String) extends RouterRequest 70 | 71 | case class GetChannelInfo(callUuid: String, channelUuid: String) extends RouterRequest 72 | 73 | case object GetConcurrentCallsChannel extends RouterRequest 74 | 75 | case class GetConcurrentCallsChannel(uuid: String) extends RouterRequest 76 | 77 | case class GetConcurrentCallsChannelByIpPrefix(ip: Option[String], prefix: Option[String]) extends RouterRequest 78 | 79 | case object GetFailedCallsChannel extends RouterRequest 80 | 81 | case class GetFailedCallsChannelByTime(time: Timestamp) extends RouterRequest 82 | 83 | case class GetFailedCallsChannelByIp(ip: String) extends RouterRequest 84 | 85 | case object GetCompletedCallsChannel extends RouterRequest 86 | 87 | 88 | case object GetLastHeartBeat extends RouterRequest 89 | 90 | case object GetAllHeartBeat extends RouterRequest 91 | 92 | //case object GetFailedCallsTimeSeries extends RouterRequest 93 | 94 | case object GetBasicStatsTimeSeries extends RouterRequest 95 | 96 | //case object GetConcurrentCallsTimeSeries extends RouterRequest 97 | 98 | //case object GetBasicAcdTimeSeries extends RouterRequest 99 | 100 | case object GetCompletedCallMinutes extends RouterRequest 101 | 102 | case object GetEslConnections extends RouterRequest 103 | 104 | case class EslConnectionData(ip: String, port: Int, password: String) extends RouterRequest 105 | 106 | case class DelEslConnection(ip: String) extends RouterRequest 107 | 108 | case class ShutdownEslConnection(ip: String) extends RouterRequest 109 | 110 | case class CompletedCall(uuid: String, hangupTime: Timestamp, callActor: ActorRef) extends RouterProtocol 111 | 112 | case class CallTerminated(callEnd: CallEnd) extends RouterProtocol 113 | 114 | case object GetACDAndRTP extends RouterRequest 115 | 116 | case object GetBillSecAndRTPByCountry extends RouterRequest 117 | 118 | case class GetACDAndRTPByTime(lastCheck: Timestamp) extends RouterRequest 119 | 120 | case class AcdData(acd: Double) extends RouterResponse 121 | 122 | case class GetNumberDialCode(number: String) extends RouterRequest 123 | 124 | case class NumberDialCodeCountry(toNumber: String, prefix: Option[String], country: Option[String]) extends RouterResponse 125 | 126 | case class AddDialCodeList(fileName: String, dialCodes: SortedMap[String, String]) extends RouterRequest 127 | 128 | case class DelDialCodeList(fileName: String) extends RouterRequest 129 | 130 | case class GetDialCodeList(fileName: String) extends RouterRequest 131 | 132 | case object GetAllDialCodeList extends RouterRequest 133 | 134 | case class AllDialCodes(fileName: String, totalCodes: Int) 135 | 136 | case class AddAtmoClientUuid(uuid: String) 137 | 138 | case class RemoveAtmoClientUuid(uuid: String) 139 | 140 | object Event { 141 | def apply(event: EslEvent): Event = Event(event.getEventHeaders.asScala) 142 | 143 | def apply(): Event = Event(scala.collection.Map.empty[String, String]) 144 | } 145 | 146 | def mkEvent(event: EslEvent): Event = Event(event) 147 | } 148 | 149 | object ServletProtocol { 150 | sealed trait ApiProtocol 151 | sealed trait ApiRequest extends ApiProtocol 152 | sealed trait ApiResponse extends ApiProtocol 153 | 154 | case class ApiReply(status: Int, message: String) extends ApiResponse 155 | case class ApiReplyError(status: Int, message: String) extends ApiResponse 156 | case class ApiReplyData(status: Int, message: String, payload: Any) extends ApiResponse 157 | 158 | } -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/ChannelActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import akka.actor._ 22 | import gr.gnostix.freeswitch.actors.ActorsProtocol._ 23 | 24 | /** 25 | * Created by rebel on 7/8/15. 26 | */ 27 | 28 | 29 | object ChannelActor { 30 | def props(channelStates: List[CallEventType]): Props = Props(new ChannelActor(channelStates)) 31 | } 32 | 33 | 34 | class ChannelActor(channelStates: List[CallEventType]) extends Actor with ActorLogging { 35 | //var channelStates: List[CallEventType] = List() 36 | 37 | def idle(channelStates: List[CallEventType]): Receive = { 38 | 39 | case x@CallNew(uuid, eventName, fromUser, toUser, readCodec, writeCodec, fromUserIP, toUserIP, callUUID, 40 | callerChannelCreatedTime, callerChannelAnsweredTime, freeSWITCHHostname, freeSWITCHIPv4, callDirection, 41 | pdd, ringTimeSec, dialCode, country) => 42 | //channelState = channelState updated(uuid, x) 43 | val newList = channelStates ::: List(x) 44 | context become idle(newList) 45 | 46 | //log info s" channel actor state Map on CallNew $channelState" 47 | 48 | case x@CallEnd(uuid, eventName, fromUser, toUser, readCodec, writeCodec, fromUserIP, toUserIP, callUUID, 49 | callerChannelCreatedTime, callerChannelAnsweredTime, callerChannelHangupTime, freeSWITCHHostname, 50 | freeSWITCHIPv4, hangupCause, billSec, rtpQualityPerc, otherLegUniqueId, hangupDisposition, callDirection, mos, 51 | pdd, ringTimeSec, dialCode, country) => 52 | //context stop self 53 | // send message to parrent tha the channel is terminated 54 | val newList = channelStates ::: List(x) 55 | context become idle(newList) 56 | 57 | context.parent ! CallTerminated(x) 58 | //log info s" channel actor state List on CallEnd $channelState" 59 | 60 | case x@NumberDialCodeCountry(toNumber, prefix, country) => 61 | val newList = channelStates.map { 62 | s => s match { 63 | case c: CallNew => 64 | if (c.country == None) { 65 | CallNew(c.uuid, c.eventName, c.fromUser, c.toUser, 66 | c.readCodec, c.writeCodec, c.fromUserIP, c.toUserIP, 67 | c.callUUID, c.callerChannelCreatedTime, c.callerChannelAnsweredTime, 68 | c.freeSWITCHHostname, c.freeSWITCHIPv4, c.callDirection, 69 | c.pdd, c.ringingSec, x.prefix, x.country) 70 | } else c 71 | 72 | case c: CallEnd => 73 | if (c.country == None) { 74 | CallEnd(c.uuid, c.eventName, c.fromUser, c.toUser, c.readCodec, c.writeCodec, c.fromUserIP, c.toUserIP, 75 | c.callUUID, c.callerChannelCreatedTime, c.callerChannelAnsweredTime, c.callerChannelHangupTime, 76 | c.freeSWITCHHostname, c.freeSWITCHIPv4, c.hangupCause, c.billSec, c.rtpQualityPerc, c.otherLegUniqueId, 77 | c.hangupDisposition, c.callDirection, c.mos, c.pdd, c.ringingSec, x.prefix, x.country) 78 | } else c 79 | 80 | case ev => ev 81 | } 82 | } 83 | context become idle(newList) 84 | 85 | 86 | case GetChannelInfo(callUuid, channeluuid) => 87 | val response = channelStates 88 | //log info s"channel response $response" 89 | sender ! response 90 | 91 | case x@GetCallInfo(callUUID) => 92 | //log info "channel actor sending his state" 93 | sender ! channelStates.map { 94 | case x: CallEnd => x 95 | case _ => 96 | } 97 | 98 | case x@GetConcurrentCallsChannel(uuid) => 99 | //log info s"---------------> channel states ${channelStates}" 100 | //log info "channel actor got event GetConcurrentCallsChannel" 101 | (channelStates.head.asInstanceOf[CallNew].uuid == x.uuid) match { 102 | case true => 103 | //log info "call router got event GetConcurrentCallsChannel TRUE" 104 | sender ! Some(channelStates.head) 105 | case false => sender ! None 106 | } 107 | 108 | 109 | case x@GetConcurrentCallsChannelByIpPrefix(ip, prefix) => 110 | ip match { 111 | case None => prefix match { 112 | case None => sender ! Some(channelStates.head) 113 | case Some(pr) => 114 | if (channelStates.head.toUser.startsWith(pr)) sender ! Some(channelStates.head) else sender ! None 115 | } 116 | 117 | case Some(ipAddr) => prefix match { 118 | case None => if (channelStates.head.freeSWITCHIPv4 == ipAddr) { 119 | sender ! Some(channelStates.head) 120 | } else { 121 | sender ! None 122 | } 123 | case Some(pr) => 124 | if (channelStates.head.toUser.startsWith(pr) && channelStates.head.freeSWITCHIPv4 == ipAddr) { 125 | sender ! Some(channelStates.head) 126 | } else sender ! None 127 | } 128 | 129 | } 130 | 131 | 132 | /* 133 | case x@GetCompletedCallsChannel => 134 | //log info s"channel actor channels $channelState and sending ${channelState.head}" 135 | sender ! channelStates.last 136 | */ 137 | 138 | case _ => log info s"message not understood on channelActor" 139 | } 140 | 141 | def receive: Receive = 142 | idle(channelStates) 143 | 144 | } -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/DialCodesActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import akka.actor.{Props, ActorRef, Actor, ActorLogging} 22 | import gr.gnostix.freeswitch.actors.ActorsProtocol._ 23 | import gr.gnostix.freeswitch.actors.ServletProtocol.{ApiReplyError, ApiReplyData, ApiReply} 24 | 25 | import scala.collection.SortedMap 26 | 27 | /** 28 | * Created by rebel on 10/10/15. 29 | */ 30 | 31 | object DialCodesActor { 32 | def props(dialCodes: Map[String, SortedMap[String, String]]): Props = 33 | Props(new DialCodesActor(dialCodes: Map[String, SortedMap[String, String]])) 34 | } 35 | 36 | class DialCodesActor(dialCodes: Map[String, SortedMap[String, String]]) extends Actor with ActorLogging { 37 | 38 | def idle(dialCodes: Map[String, SortedMap[String, String]]): Receive = { 39 | 40 | case x@AddDialCodeList(filename, dialCodesS) => 41 | val newDialCodes = dialCodes + (x.fileName -> x.dialCodes) 42 | //sender ! ApiReply(200, "DialCodes added successfully") 43 | context become idle(newDialCodes) 44 | 45 | case GetNumberDialCode(number) => 46 | val dialCodeCountry = dialCodes.last._2.par.filter(d => number.startsWith(d._1)) 47 | .toList.sortBy(_._1.length).lastOption 48 | 49 | dialCodeCountry match { 50 | case Some(dt) => sender ! NumberDialCodeCountry(number, Some(dt._1), Some(dt._2)) 51 | case None => sender ! NumberDialCodeCountry(number, None, None) 52 | } 53 | 54 | case x@DelDialCodeList(fileName) => 55 | dialCodes.size match { 56 | case 1 => sender ! ApiReply(400, "We cannot remove the default list of DialCodes ") 57 | case _ => 58 | val newMap = dialCodes.filterNot(_._1 == fileName) 59 | context become idle(newMap) 60 | sender ! ApiReply(200, s"DialCodes with filename $fileName, removed successfully") 61 | } 62 | 63 | case x@GetDialCodeList(fileName) => 64 | val dialC = dialCodes.get(fileName) 65 | //log info s"-------> $dialC" 66 | dialC match { 67 | case Some(map) => sender ! dialC 68 | case None => sender ! None 69 | } 70 | 71 | case x@GetAllDialCodeList => 72 | sender ! dialCodes.map(d => AllDialCodes(d._1, d._2.size)) 73 | } 74 | 75 | def receive: Receive = 76 | idle(dialCodes) 77 | } 78 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/EslConnectionDispatcherActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import akka.actor._ 22 | import gr.gnostix.freeswitch.actors.ServletProtocol.ApiReply 23 | import gr.gnostix.freeswitch.{EslConnection} 24 | import gr.gnostix.freeswitch.actors.ActorsProtocol.{GetEslConnections, ShutdownEslConnection, EslConnectionData, DelEslConnection} 25 | import scala.concurrent.duration._ 26 | import scala.language.postfixOps 27 | import scala.concurrent.ExecutionContext.Implicits.global 28 | 29 | /** 30 | * Created by rebel on 27/8/15. 31 | */ 32 | 33 | object EslConnectionDispatcherActor { 34 | def props(wsLiveEventsActor: ActorRef): Props = Props(new EslConnectionDispatcherActor(wsLiveEventsActor)) 35 | } 36 | 37 | class EslConnectionDispatcherActor(wSLiveEventsActor: ActorRef) extends Actor with ActorLogging { 38 | 39 | val Tick = "tick" 40 | var actorConnections: scala.collection.Map[String, EslConnection] = Map() 41 | 42 | def idle(connections: scala.collection.Map[String, EslConnection]): Receive = { 43 | 44 | case x @ GetEslConnections => 45 | sender ! connections.map{ 46 | case (ip,conn) => EslConnectionData(conn.getIP, conn.getPort,"the-secret") 47 | }.toList 48 | 49 | case x @ DelEslConnection(ip) => 50 | connections get x.ip match { 51 | case Some(eslConnection) => 52 | // close connection 53 | log info s"----> shutdown connection with ip: ${x.ip} and connections: $connections" 54 | eslConnection.deinitConnection() 55 | val newMap = connections.filter(c => c._1 != x.ip) 56 | // stop the esl event actor for this connection 57 | context stop eslConnection.getActor() 58 | log info s"----> shutdown connection with connections: $newMap" 59 | actorConnections = newMap 60 | context become idle(newMap) 61 | sender ! ApiReply(200,"connection terminated") 62 | 63 | 64 | case None => sender ! ApiReply(400, "this ip doesn't exists") 65 | } 66 | 67 | 68 | case x @ EslConnectionData(ip, port, password) => 69 | log info s"esl connections: $connections" 70 | connections get x.ip match { 71 | case None => 72 | val eslEventRouter = context.actorOf(Props[EslEventRouter], x.ip) 73 | val eslConn = new EslConnection(eslEventRouter, x.ip, x.port, x.password) 74 | 75 | val connStatus = eslConn.connectEsl() 76 | connStatus.isConnected match { 77 | case true => 78 | log info "----> connection succeded " 79 | val resp = ApiReply(200, "Connection is up") 80 | //wSLiveEventsActor ! resp 81 | //wSLiveEventsActor ! ActorsJsonProtocol.caseClassToJsonMessage(resp) 82 | sender ! resp 83 | 84 | val newMap = connections updated(ip, eslConn) 85 | actorConnections = newMap 86 | context become idle(newMap) 87 | case false => 88 | log info "Connection failed for ip " + x.ip 89 | val resp = ApiReply(400, connStatus.getMessage) 90 | context stop eslEventRouter 91 | wSLiveEventsActor ! resp 92 | //wSLiveEventsActor ! ActorsJsonProtocol.caseClassToJsonMessage(resp) 93 | sender ! resp 94 | } 95 | 96 | case Some(eslConnection) => 97 | log error s"we already have this ip connection ${x.ip} and connections: ${connections}" 98 | sender ! ApiReply(400, s"we already have this ip connection ${x.ip}") 99 | } 100 | 101 | 102 | case Tick => 103 | connections.map{ 104 | case (a,y) => y.checkConnection() match { 105 | case x if x.isConnected => "all good" 106 | case x if !x.isConnected => 107 | sender ! ApiReply(400, x.getMessage) 108 | log warning(s"---> EslConnectionDispatcherActor | the connection with ip $a is down!!") 109 | } 110 | case _ => log info "---> EslConnectionDispatcherActor | empty connections Map" 111 | } 112 | 113 | case Terminated => 114 | log warning "------> Actor EslConnectionDispatcherActor is terminated" 115 | 116 | } 117 | 118 | override def postStop() = { 119 | log warning "------> Actor EslConnectionDispatcherActor is terminated postStop" 120 | actorConnections.map{ 121 | case (x,y) => y.deinitConnection() 122 | } 123 | } 124 | 125 | context.system.scheduler.schedule(60000 milliseconds, 126 | 60000 milliseconds, 127 | self, 128 | Tick) 129 | 130 | def receive: Receive = 131 | idle(scala.collection.Map.empty[String, EslConnection]) 132 | } 133 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/EventType.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import java.sql.Timestamp 22 | 23 | /** 24 | * Created by rebel on 23/8/15. 25 | */ 26 | 27 | sealed trait EventType { 28 | def eventName: String 29 | } 30 | 31 | case class HeartBeat(eventName: String, eventInfo: String, uptimeMsec: Long, concurrentCalls: Int, 32 | sessionPerSecond: Int, eventDateTimestamp: Timestamp, cpuUsage: Double, callsPeakMax: Int, 33 | sessionPeakMaxFiveMin: Int, freeSWITCHHostname: String, freeSWITCHIPv4: String, upTime: String, 34 | maxAllowedCalls: Int) 35 | extends EventType 36 | 37 | case class AvgHeartBeat(eventName: String, avgUptimeMsec: Double, totalConcurrentCalls: Int, avgSessionPerSecond: Double, 38 | eventDateTimestamp: Timestamp, avgCpuUsage: Double, callsPeakMax: Int, maxSessionPeakMaxFiveMin: Int, 39 | totalMaxAllowedCalls: Int) 40 | extends EventType 41 | 42 | 43 | sealed trait CallEventType extends EventType { 44 | def fromUser: String 45 | 46 | def freeSWITCHIPv4: String 47 | 48 | def toUser: String 49 | } 50 | 51 | case class CallNew(uuid: String, eventName: String, fromUser: String, toUser: String, readCodec: String, writeCodec: String, 52 | fromUserIP: String, toUserIP: String, callUUID: String, callerChannelCreatedTime: Option[Timestamp], 53 | callerChannelAnsweredTime: Option[Timestamp], freeSWITCHHostname: String, freeSWITCHIPv4: String, 54 | callDirection: String, pdd: Float, ringingSec: Float, dialCode: Option[String], country: Option[String]) 55 | extends CallEventType 56 | 57 | 58 | case class CallEnd(uuid: String, eventName: String, fromUser: String, toUser: String, readCodec: String, writeCodec: String, 59 | fromUserIP: String, toUserIP: String, callUUID: String, callerChannelCreatedTime: Option[Timestamp], 60 | callerChannelAnsweredTime: Option[Timestamp], callerChannelHangupTime: Timestamp, 61 | freeSWITCHHostname: String, freeSWITCHIPv4: String, hangupCause: String, billSec: Int, 62 | rtpQualityPerc: Double, otherLegUniqueId: String, hangupDisposition: String, callDirection: String, 63 | mos: Double, pdd: Float, ringingSec: Float, dialCode: Option[String], country: Option[String]) 64 | extends CallEventType 65 | 66 | case class FailedCall(eventName: String, fromUser: String, toUser: String, callUUID: String, freeSWITCHIPv4: String) 67 | extends CallEventType 68 | 69 | case class OtherEvent(eventName: String, uuid: String) extends EventType 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/FailedCallsActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import akka.actor.{Props, ActorRef, Actor, ActorLogging} 22 | import gr.gnostix.freeswitch.actors.ActorsProtocol._ 23 | import org.scalatra.atmosphere.AtmosphereClient 24 | import scala.collection.SortedMap 25 | import scala.concurrent.ExecutionContext.Implicits.global 26 | import scala.concurrent.Future 27 | import scala.concurrent.duration._ 28 | import scala.language.postfixOps 29 | 30 | /** 31 | * Created by rebel on 17/8/15. 32 | */ 33 | 34 | case class TotalFailedCalls(failedCalls: Int) 35 | 36 | object FailedCallsActor { 37 | def props(wsLiveEventsActor: ActorRef): Props = 38 | Props(new FailedCallsActor(wsLiveEventsActor: ActorRef)) 39 | } 40 | 41 | class FailedCallsActor(wsLiveEventsActor: ActorRef) extends Actor with ActorLogging { 42 | 43 | var failedCalls: List[CallEnd] = List() 44 | val Tick = "tick" 45 | val dialCodesActor = context.actorSelection("/user/centralMessageRouter/dialCodesActor") 46 | 47 | 48 | def receive: Receive = { 49 | case x@CallEnd(uuid, eventName, fromUser, toUser, readCodec, writeCodec, fromUserIP, toUserIP, callUUID, 50 | callerChannelCreatedTime, callerChannelAnsweredTime, callerChannelHangupTime, freeSWITCHHostname, 51 | freeSWITCHIPv4, hangupCause, billSec, rtpQualityPerc, otherLegUniqueId, hangupDisposition, callDirection, mos, 52 | pdd, ringTimeSec, None, None) => 53 | 54 | // the case where we get two FAILED call leg for the same call 55 | //{"eventName":"FAILED_CALL","fromUser":"0000000000","toUser":"19189898989","callUUID":"8315d80c-c404-4bcb-8612-94edb9863765","freeSWITCHIPv4":"10.143.0.54"} 56 | //{"eventName":"FAILED_CALL","fromUser":"1000","toUser":"9898989","callUUID":"8315d80c-c404-4bcb-8612-94edb9863765","freeSWITCHIPv4":"10.143.0.54"} 57 | 58 | if (x.toUser != "_UNKNOWN") { 59 | dialCodesActor ! GetNumberDialCode(toUser) 60 | } 61 | 62 | failedCalls.find(a => a.callUUID == x.callUUID) match { 63 | case Some(c) => 64 | log info "we have this failed call so now we get the second system leg" 65 | c.fromUser == "0000000000" match { 66 | case true => 67 | failedCalls = failedCalls.filter(ca => ca.callUUID != x.callUUID) 68 | failedCalls ::= x 69 | case false => 70 | } 71 | case None => 72 | log info "-------> add an extra failed call" 73 | failedCalls ::= x 74 | val fCall = FailedCall("FAILED_CALL", x.fromUser, x.toUser, x.callUUID, x.freeSWITCHIPv4) 75 | wsLiveEventsActor ! fCall 76 | //wsLiveEventsActor ! ActorsJsonProtocol.failedCallToJson(fCall) 77 | } 78 | 79 | case x@GetFailedCallsAnalysis(fromNumberOfDigits, toNumberOfDigits) => 80 | sender ! failedCalls.groupBy(x => x.fromUserIP).map { 81 | case (ip, call) => call.groupBy(pr => pr.fromUser.substring(0, fromNumberOfDigits)).map { 82 | case (fromUser, call2) => call2.groupBy(_.toUser.substring(0, toNumberOfDigits)).map { 83 | case (toUser, call3) => (ip, fromUser, toUser, call3.size) 84 | } 85 | } 86 | } 87 | 88 | case x@GetFailedCallsChannel => 89 | sender ! failedCalls 90 | 91 | 92 | case x@GetFailedCalls => 93 | //log info "returning the failed calls " + failedCalls 94 | sender ! failedCalls 95 | 96 | case x@GetTotalFailedCalls => 97 | //log info "returning the failed calls size " + failedCalls.size 98 | sender ! TotalFailedCalls(failedCalls.size) 99 | 100 | case x@GetFailedCallsByDate(fromDate, toDate) => 101 | sender ! failedCalls.filter(a => a.callerChannelHangupTime.after(fromDate) 102 | && a.callerChannelHangupTime.before(toDate)) 103 | 104 | case x@NumberDialCodeCountry(toNumber, prefix, countr) => 105 | prefix match { 106 | case Some(dt) => 107 | failedCalls = failedCalls.map { 108 | f => 109 | if (f.toUser == toNumber) { 110 | CallEnd(f.uuid, f.eventName, f.fromUser, f.toUser, f.readCodec, f.writeCodec, f.fromUserIP, f.toUserIP, f.callUUID, 111 | f.callerChannelCreatedTime, f.callerChannelAnsweredTime, f.callerChannelHangupTime, f.freeSWITCHHostname, 112 | f.freeSWITCHIPv4, f.hangupCause, f.billSec, f.rtpQualityPerc, f.otherLegUniqueId, f.hangupDisposition, 113 | f.callDirection, f.mos, f.pdd, f.ringingSec, x.prefix, x.country) 114 | } else f 115 | } 116 | 117 | case None => //do nothing 118 | } 119 | 120 | case x @ GetFailedCallsChannelByTime(t) => 121 | failedCalls.isEmpty match { 122 | case true => 123 | sender ! List() 124 | case false => 125 | val act = failedCalls.filter(s => s.callerChannelHangupTime.after(t)) 126 | 127 | sender ! act 128 | 129 | } 130 | 131 | 132 | case Tick => 133 | failedCalls = getLastsFailedCalls 134 | 135 | } 136 | 137 | context.system.scheduler.schedule(10000 milliseconds, 138 | 1200000 milliseconds, 139 | self, 140 | Tick) 141 | 142 | def getLastsFailedCalls = { 143 | failedCalls.take(5000) 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/HeartBeatActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import java.sql.Timestamp 22 | 23 | import akka.actor.{Props, Actor, ActorLogging, ActorRef} 24 | import gr.gnostix.freeswitch.actors.ActorsProtocol.{GetAllHeartBeat, GetLastHeartBeat, InitializeDashboardHeartBeat} 25 | import gr.gnostix.freeswitch.utilities.HelperFunctions 26 | 27 | import scala.concurrent.ExecutionContext.Implicits.global 28 | import scala.concurrent.duration._ 29 | import scala.language.postfixOps 30 | 31 | /** 32 | * Created by rebel on 19/8/15. 33 | */ 34 | 35 | object HeartBeatActor { 36 | def props(wsLiveEventsActor: ActorRef): Props = Props(new HeartBeatActor(wsLiveEventsActor)) 37 | } 38 | 39 | class HeartBeatActor(wsLiveEventsActor: ActorRef) extends Actor with ActorLogging { 40 | 41 | var heartBeats: List[HeartBeat] = List() 42 | 43 | val Tick = "tick" 44 | val AvgHeartbeat = "avgHeartbeat" 45 | 46 | def receive: Receive = { 47 | case x@HeartBeat(eventType, eventInfo, uptimeMsec, concurrentCalls, sessionPerSecond, eventDateTimestamp, 48 | idleCPU, callsPeakMax, sessionPeakMaxFiveMin, freeSWITCHHostname, freeSWITCHIPv4, upTime, maxAllowedCalls) => 49 | heartBeats ::= x 50 | //AtmosphereClient.broadcast("/fs-moni/live/events", ActorsJsonProtocol.heartbeatToJson(x)) 51 | wsLiveEventsActor ! x 52 | //wsLiveEventsActor ! ActorsJsonProtocol.heartbeatToJson(x) 53 | //log info "broadcasted HeartBeat to WS" 54 | 55 | case x@InitializeDashboardHeartBeat => 56 | sender ! heartBeats.take(30) 57 | 58 | case x@GetLastHeartBeat => 59 | log info "-----------> ask for last heartbeat" 60 | sender ! heartBeats.headOption.getOrElse(None) 61 | 62 | case x@GetAllHeartBeat => 63 | sender ! heartBeats 64 | 65 | case Tick => 66 | heartBeats = getLastsHeartBeats 67 | //log info "Tick coming .. " + heartBeats.size 68 | 69 | /*AvgHeartBeat(eventName: String, uptimeMsec: Long, concurrentCalls: Int, sessionPerSecond: Int, 70 | eventDateTimestamp: Timestamp, cpuUsage: Double, callsPeakMax: Int, sessionPeakMaxFiveMin: Int, 71 | maxAllowedCalls: Int) 72 | */ 73 | case AvgHeartbeat => 74 | val averageHeartBeatStats = heartBeats.take(10).groupBy(_.freeSWITCHHostname).map { 75 | case (x, y) => y.sortWith((leftD, rightD) => leftD.eventDateTimestamp.after(rightD.eventDateTimestamp)).head 76 | }.toList 77 | 78 | averageHeartBeatStats.size match { 79 | case x if (x <= 1) => // do nothing becase we already send the HeartBeat for this FS node 80 | case x if (x > 1) => 81 | val avgHeartBeat = AvgHeartBeat("AVG_HEARTBEAT", 82 | HelperFunctions.roundDouble(averageHeartBeatStats.map(_.uptimeMsec).sum / averageHeartBeatStats.size.toDouble), 83 | averageHeartBeatStats.map(_.concurrentCalls).sum, 84 | HelperFunctions.roundDouble(averageHeartBeatStats.map(_.sessionPerSecond).sum / averageHeartBeatStats.size.toDouble), 85 | new Timestamp(System.currentTimeMillis), 86 | HelperFunctions.roundDouble(averageHeartBeatStats.map(_.cpuUsage).sum / averageHeartBeatStats.size.toDouble), 87 | averageHeartBeatStats.sortBy(_.callsPeakMax).last.callsPeakMax, 88 | averageHeartBeatStats.sortBy(_.sessionPeakMaxFiveMin).last.sessionPeakMaxFiveMin, 89 | averageHeartBeatStats.map(_.maxAllowedCalls).sum) 90 | 91 | log info s"-----> $avgHeartBeat" 92 | 93 | wsLiveEventsActor ! avgHeartBeat 94 | } 95 | } 96 | 97 | context.system.scheduler.schedule(10000 milliseconds, 98 | 1200000 milliseconds, 99 | self, 100 | Tick) 101 | 102 | context.system.scheduler.schedule(10000 milliseconds, 103 | 60000 milliseconds, 104 | self, 105 | AvgHeartbeat) 106 | 107 | 108 | def getLastsHeartBeats = { 109 | heartBeats.take(1000) 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/actors/WSLiveEventsActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import akka.actor.{Actor, ActorLogging} 22 | import akka.pattern.ask 23 | import akka.util.Timeout 24 | import gr.gnostix.freeswitch.actors.ActorsProtocol._ 25 | import org.atmosphere.cpr.{AtmosphereResource, AtmosphereResourceImpl} 26 | import org.json4s.NoTypeHints 27 | import org.json4s.jackson.Serialization 28 | import org.json4s.jackson.Serialization.write 29 | import org.scalatra.atmosphere._ 30 | 31 | import scala.collection.JavaConverters._ 32 | import scala.concurrent.ExecutionContext.Implicits.global 33 | import scala.concurrent.Future 34 | import scala.concurrent.duration._ 35 | import scala.language.postfixOps 36 | import scala.util.{Failure, Success} 37 | 38 | /** 39 | * Created by rebel on 29/8/15. 40 | */ 41 | 42 | sealed trait Kokoko { 43 | def eventName: String 44 | } 45 | 46 | case class Lala(eventName: String, vava: String) extends Kokoko 47 | 48 | class WSLiveEventsActor extends Actor with ActorLogging { 49 | implicit val timeout = Timeout(1 seconds) 50 | 51 | implicit val formats = Serialization.formats(NoTypeHints) 52 | 53 | var atmoClientsUuid: List[String] = List() 54 | val Tick = "tick" 55 | 56 | def receive: Receive = { 57 | 58 | case x@AddAtmoClientUuid(uuid) => 59 | log info s"adding new client with uuid ${x.uuid}" 60 | atmoClientsUuid ::= x.uuid 61 | 62 | case x@RemoveAtmoClientUuid(uuid) => 63 | atmoClientsUuid = atmoClientsUuid.filter(_ != uuid) 64 | log info s"removing client with uuid ${x.uuid} and client id list $atmoClientsUuid" 65 | 66 | case x: BasicStatsCalls => 67 | log info "OutboundMessage coming .. " + x.eventName 68 | atmoClientsUuid.size match { 69 | case 0 => log info "do nothing.. no connected clients.." 70 | case _ => 71 | AtmosphereClient.lookup("/fs-moni/live/events").map { a => 72 | a.broadcast(caseClassToJson(x)) 73 | } 74 | 75 | } 76 | 77 | //case x: OutboundMessage => 78 | case x: EventType => 79 | log info "OutboundMessage coming .. " + x.eventName 80 | atmoClientsUuid.size match { 81 | case 0 => log info "do nothing.. no connected clients.." 82 | case _ => 83 | // log info s"the atmoClients list $atmoClientsUuid" 84 | AtmosphereClient.lookup("/fs-moni/live/events").foreach((broadcaster: ScalatraBroadcaster) => { 85 | val myResources = broadcaster.getAtmosphereResources.asScala filter { r => atmoClientsUuid.contains(r.uuid()) } 86 | myResources foreach ((resource: AtmosphereResource) => { 87 | val session = resource.getRequest.getSession(false) 88 | 89 | if (session == null) { 90 | // log warning (s"-------> Encountered atmosphere resource ${resource.uuid()} associated with invalid session") 91 | resource.asInstanceOf[AtmosphereResourceImpl]._destroy() 92 | } else { 93 | // log info s"Atmo: this client belongs to this channel and is logged in, id: ${resource.uuid()}" 94 | resource.getBroadcaster.broadcast(caseClassToJson(x)) 95 | } 96 | }) 97 | }) 98 | 99 | } 100 | 101 | 102 | case Tick => 103 | //to extend the session lifetime, use bellow code - https://github.com/scalatra/scalatra/issues/387 104 | AtmosphereClient.lookup("/fs-moni/live/events").foreach((broadcaster: ScalatraBroadcaster) => { 105 | val myResources = broadcaster.getAtmosphereResources.asScala filter { r => atmoClientsUuid.contains(r.uuid()) } 106 | myResources foreach ((resource: AtmosphereResource) => { 107 | val session = resource.getRequest.getSession(false) 108 | session.setMaxInactiveInterval(session.getMaxInactiveInterval + 900) 109 | log info (s"Extended life of session ${session.getId} for another 15 minutes " + 110 | s"maximum inactivity period in seconds of ${session.getMaxInactiveInterval}") 111 | }) 112 | }) 113 | case _ => log warning "WSLiveEventsActor | Unknown Message .." 114 | } 115 | 116 | def caseClassToJson(event: AnyRef) = { 117 | write(event) 118 | } 119 | 120 | def broadcastByUuid(uuid: String, events: List[AnyRef]) = { 121 | AtmosphereClient.lookup("/fs-moni/live/events").foreach((broadcaster: ScalatraBroadcaster) => { 122 | val myResources = broadcaster.getAtmosphereResources.asScala filter { r => uuid == r.uuid() } 123 | myResources foreach ((resource: AtmosphereResource) => { 124 | val session = resource.getRequest.getSession(false) 125 | 126 | if (session == null) { 127 | // log warning (s"-------> Encountered atmosphere resource ${resource.uuid()} associated with invalid session") 128 | resource.asInstanceOf[AtmosphereResourceImpl]._destroy() 129 | } else { 130 | // log info s"Atmo: this client belongs to this channel and is logged in, id: ${resource.uuid()}" 131 | events.map(event => resource.getBroadcaster.broadcast(caseClassToJson(event))) 132 | } 133 | }) 134 | }) 135 | } 136 | 137 | context.system.scheduler.schedule(3000 milliseconds, 138 | 600000 milliseconds, 139 | self, 140 | Tick) 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/auth/AuthenticationSupport.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.api.auth 20 | 21 | 22 | import gr.gnostix.api.auth.strategies.{TheBasicAuthStrategy, UserPasswordStrategy} 23 | import gr.gnostix.freeswitch.model.{User, UserDao} 24 | import org.scalatra.ScalatraBase 25 | import org.scalatra.auth.{ScentryConfig, ScentrySupport} 26 | 27 | //import com.constructiveproof.hackertracker.auth.strategies.RememberMeStrategy 28 | 29 | import org.slf4j.LoggerFactory 30 | 31 | trait AuthenticationSupport extends ScalatraBase with ScentrySupport[User] { 32 | self: ScalatraBase => 33 | 34 | val realm = "FS-moni Basic Auth" 35 | val log = LoggerFactory.getLogger(getClass) 36 | 37 | 38 | protected val scentryConfig = (new ScentryConfig {}).asInstanceOf[ScentryConfiguration] 39 | 40 | 41 | protected def fromSession = { 42 | case userId: String => { 43 | log.info("----> get from SessionStore") 44 | //User(name,98) 45 | UserDao.getUserById(userId.toInt).get 46 | } 47 | } 48 | 49 | protected def toSession = { 50 | case user: User => { 51 | log.info("-----> store to SessionStore") 52 | user.userId.toString 53 | } 54 | } 55 | 56 | protected def requireLogin() = { 57 | if (!isAuthenticated) { 58 | log.info("------------------> trait:requiredLogin1: not authenticated") 59 | //halt(401) 60 | //redirect("/login") 61 | scentry.authenticate() 62 | if (!isAuthenticated) { 63 | log.info("------------------> trait:requiredLogin2: not authenticated") 64 | halt(401) 65 | } 66 | } 67 | } 68 | 69 | override protected def configureScentry = { 70 | scentry.unauthenticated { 71 | scentry.strategies("UserPassword").unauthenticated() 72 | } 73 | } 74 | 75 | override protected def registerAuthStrategies = { 76 | scentry.register("TheBasicAuth", app => new TheBasicAuthStrategy(app, realm)) 77 | scentry.register("UserPassword", app => new UserPasswordStrategy(app)) 78 | //scentry.register("RememberMe", app => new RememberMeStrategy(app)) 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/auth/strategies/TheBasicAuthStrategy.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.api.auth.strategies 20 | 21 | 22 | import javax.servlet.http.{HttpServletRequest, HttpServletResponse} 23 | 24 | import gr.gnostix.freeswitch.model.{UserDao, User} 25 | import org.scalatra.ScalatraBase 26 | import org.scalatra.auth.strategy.BasicAuthStrategy 27 | import org.slf4j.LoggerFactory 28 | 29 | 30 | class TheBasicAuthStrategy(protected override val app: ScalatraBase, realm: String) 31 | extends BasicAuthStrategy[User](app, realm) { 32 | 33 | override def name: String = "TheBasicAuth" 34 | 35 | val logger = LoggerFactory.getLogger(getClass) 36 | 37 | override protected def getUserId(user: User)(implicit request: HttpServletRequest, response: HttpServletResponse): String = user.userId.toString 38 | 39 | override def isValid(implicit request: HttpServletRequest) = { 40 | logger.info("-----------> TheBasicAuthStrategy: isValid " + app.request.isBasicAuth +" " + app.request.providesAuth) 41 | app.request.isBasicAuth && app.request.providesAuth 42 | } 43 | 44 | override protected def validate(userName: String, password: String)(implicit request: HttpServletRequest, response: HttpServletResponse): Option[User] = { 45 | logger.info("TheBasicAuthStrategy: found the username in DB. userName: " + userName) 46 | UserDao.getUserByUsername(userName) match { 47 | case Some(user) => { 48 | logger.info("TheBasicAuthStrategy: found the username in DB") 49 | if (true) { 50 | Some(user) 51 | } else { 52 | logger.info("-----------> TheBasicAuthStrategy: login failed --> user and pass did not match!!"); 53 | None 54 | } 55 | } 56 | case None => { 57 | logger.info("-----------> TheBasicAuthStrategy: login failed") 58 | None 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * What should happen if the user is currently not authenticated? 65 | */ 66 | override def unauthenticated()(implicit request: HttpServletRequest, response: HttpServletResponse) { 67 | //app.redirect("/sessions/new") 68 | logger.info("---------> TheBasicAuthStrategy: login unauthenticated, was redirected") 69 | //app.redirect("/login") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/auth/strategies/UserPasswordStrategy.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.api.auth.strategies 20 | 21 | import javax.servlet.http.{HttpServletRequest, HttpServletResponse} 22 | 23 | import gr.gnostix.freeswitch.model.{User, UserDao} 24 | import gr.gnostix.freeswitch.utilities.HelperFunctions 25 | import org.scalatra.ScalatraBase 26 | import org.scalatra.auth.ScentryStrategy 27 | import org.slf4j.LoggerFactory 28 | 29 | class UserPasswordStrategy(protected val app: ScalatraBase) 30 | (implicit request: HttpServletRequest, response: HttpServletResponse) 31 | extends ScentryStrategy[User] { 32 | 33 | //val db: Database 34 | override def name: String = "UserPassword" 35 | 36 | val logger = LoggerFactory.getLogger(getClass) 37 | 38 | 39 | private def username = app.params.getOrElse("username", "") 40 | 41 | private def password = app.params.getOrElse("password", "") 42 | 43 | /** * 44 | * Determine whether the strategy should be run for the current request. 45 | */ 46 | override def isValid(implicit request: HttpServletRequest) = { 47 | logger.info("----------> UserPasswordStrategy: determining isValid: " + (username != "" && password != "").toString()) 48 | username != "" && password != "" 49 | } 50 | 51 | 52 | /** 53 | * In real life, this is where we'd consult our data store, asking it whether the user credentials matched 54 | * any existing user. Here, we'll just check for a known login/password combination and return a user if 55 | * it's found. 56 | */ 57 | def authenticate()(implicit request: HttpServletRequest, response: HttpServletResponse): Option[User] = { 58 | logger.info("UserPasswordStrategy: attempting authentication") 59 | 60 | 61 | UserDao.getUserByUsername(username) match { 62 | case Some(user) => { 63 | logger.info("UserPasswordStrategy: found the username in DB") 64 | /* 65 | if (checkUserPassword(username, password, user.password)) { 66 | Some(user) 67 | } else { 68 | logger.info("-----------> UserPasswordStrategy: login failed --> user and pass did not match!!"); 69 | None 70 | }*/ 71 | Some(user) 72 | } 73 | case None => { 74 | logger.info("-----------> UserPasswordStrategy: login failed"); 75 | None 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * What should happen if the user is currently not authenticated? 82 | */ 83 | override def unauthenticated()(implicit request: HttpServletRequest, response: HttpServletResponse) { 84 | //app.redirect("/sessions/new") 85 | logger.info("---------> UserPasswordStrategy: login unauthenticated, was redirected") 86 | //app.redirect("/login") 87 | } 88 | 89 | private def checkUserPassword(username: String, password: String, userDbPassword: String): Boolean = { 90 | // logger.info ("---------> UserPasswordStrategy checkUserPassword :" + username + ":" + password + ":" ) 91 | // logger.info (s"---------> UserPasswordStrategy userDbPassword : $userDbPassword") 92 | // 93 | // logger.info (s"---------> UserPasswordStrategy checkUserPassword : " + (username.concat(password) ) ) 94 | // logger.info (s"---------> UserPasswordStrategy Password : ${HelperFunctions.sha1Hash(username.concat(password)) }") 95 | 96 | 97 | if (HelperFunctions.sha1Hash(username + password) == userDbPassword) true else false 98 | } 99 | 100 | 101 | } -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/model/PlainModels.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.model 20 | 21 | import java.sql.Timestamp 22 | 23 | /** 24 | * Created by rebel on 13/10/15. 25 | */ 26 | case class CompletedCallStatsByIP(acd: Int, rtpQuality: Double, callerChannelHangupTime: Timestamp, ipAddress: String, hostname: String) 27 | case class CompletedCallStatsByCountryByIP(prefix: Option[String], country: Option[String], billSec: Int, rtpQuality: Double, callerChannelHangupTime: Timestamp, ipAddress: String, hostname: String) 28 | case class CompletedCallStatsByCountryAcdRtpQualityByIP(prefix: String, country: String, acd: Double, rtpQuality: Double, callsNum: Int, ipAddress: String, hostname: String) 29 | case class CompletedCallStatsByCountryAsrByIP(prefix: String, country: String, completedCallsNum: Int, failedCallsNum: Int, asr: Double, ipAddress: String, hostname: String) 30 | case class IpPrefix(ip: String, prefix: String) 31 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/model/SecureClient.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | import org.scalatra.atmosphere.{OutboundMessage, ClientFilter, AtmosphereClient} 20 | import scala.concurrent.ExecutionContext.Implicits.global 21 | import org.atmosphere.cpr.AtmosphereResource 22 | 23 | /* 24 | class SecureClient extends AtmosphereClient { 25 | 26 | 27 | var adminUuids: List[String] = List() 28 | // adminUuids is a collection of uuids for admin users. You'd need to 29 | // add each admin user's uuid to the list at connection time. 30 | final protected def OnlyAdmins: ClientFilter = adminUuids.contains(_.uuid) 31 | 32 | /** 33 | * Broadcast a message to admin users only. 34 | */ 35 | def adminBroadcast(msg: OutboundMessage) { 36 | broadcast(msg, OnlyAdmins) 37 | } 38 | }*/ 39 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/model/User.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.model 20 | 21 | import org.slf4j.LoggerFactory 22 | 23 | /** 24 | * Created by rebel on 6/9/15. 25 | */ 26 | case class User(userId: Int, username: String, var password: String, firstName: String, lastName: String, company: String, role: String) 27 | 28 | object UserDao { 29 | 30 | val logger = LoggerFactory.getLogger(getClass) 31 | 32 | def getUserByUsername(username: String): Option[User] = { 33 | if (username == "admin"){ 34 | Some(User(1, "admin", "admin", "Alex", "Kapas", "Fs-Moni", "admin")) 35 | } else { 36 | None 37 | } 38 | } 39 | 40 | def getUserById(userId: Int): Option[User] = { 41 | if (userId == 1){ 42 | Some(User(1, "admin", "admin", "Alex", "Kapas", "Fs-Moni", "admin")) 43 | } else { 44 | None 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/servlets/ChatController.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package org.scalatra.example.atmosphere 20 | 21 | import java.util.Date 22 | 23 | import gr.gnostix.api.auth.AuthenticationSupport 24 | import org.json4s.JsonDSL._ 25 | import org.json4s.{JValue, DefaultFormats, Formats} 26 | import org.scalatra.atmosphere._ 27 | import org.scalatra.json._ 28 | import org.scalatra._ 29 | import org.scalatra.json.JacksonJsonSupport 30 | import scalate.ScalateSupport 31 | 32 | import scala.concurrent.ExecutionContext.Implicits.global 33 | 34 | class ChatController extends ScalatraServlet 35 | with ScalateSupport with JValueResult 36 | with JacksonJsonSupport with SessionSupport 37 | with AtmosphereSupport with AuthenticationSupport { 38 | 39 | implicit protected val jsonFormats: Formats = DefaultFormats 40 | // protected implicit lazy val jsonFormats: Formats = DefaultFormats 41 | 42 | before(){ 43 | contentType = formats("json") 44 | requireLogin() 45 | } 46 | 47 | 48 | atmosphere("/") { 49 | new AtmosphereClient { 50 | def receive: AtmoReceive = { 51 | case Connected => 52 | println("Client %s is connected" format uuid) 53 | broadcast(("author" -> "Someone") ~ ("message" -> "joined the room") ~ ("time" -> (new Date().getTime.toString )), Everyone) 54 | 55 | case Disconnected(ClientDisconnected, _) => 56 | println("Client %s is disconnected" format uuid) 57 | broadcast(("author" -> "Someone") ~ ("message" -> "has left the room") ~ ("time" -> (new Date().getTime.toString )), Everyone) 58 | 59 | case Disconnected(ServerDisconnected, _) => 60 | println("Server disconnected the client %s" format uuid) 61 | 62 | case x @ TextMessage(_) => 63 | println("text message received %s" format uuid) 64 | broadcast(x.toString()) 65 | //send(("author" -> "system") ~ ("message" -> "Only json is allowed") ~ ("time" -> (new Date().getTime.toString ))) 66 | 67 | case JsonMessage(json) => 68 | println("------ json " + json.toString) 69 | println("Got message %s from %s".format((json \ "message").extract[String], (json \ "author").extract[String])) 70 | val msg = json merge (("time" -> (new Date().getTime().toString)): JValue) 71 | broadcast(msg) // by default a broadcast is to everyone but self 72 | //send(msg) // also send to the sender 73 | } 74 | } 75 | } 76 | 77 | /* error { 78 | case t: Throwable => t.printStackTrace() 79 | }*/ 80 | 81 | /* notFound { 82 | // remove content type in case it was set through an action 83 | contentType = null 84 | // Try to render a ScalateTemplate if no route matched 85 | findTemplate(requestPath) map { path => 86 | contentType = "text/html" 87 | layoutTemplate(path) 88 | } orElse serveStaticResource() getOrElse resourceNotFound() 89 | }*/ 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/servlets/LoginServlet.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.servlets 20 | 21 | import gr.gnostix.api.auth.AuthenticationSupport 22 | import gr.gnostix.freeswitch.FreeswitchopStack 23 | import org.json4s.{JValue, Formats, DefaultFormats} 24 | import org.scalatra.{ScalatraServlet, CorsSupport} 25 | import org.scalatra.json.JacksonJsonSupport 26 | 27 | class LoginServlet 28 | extends ScalatraServlet 29 | with JacksonJsonSupport 30 | with AuthenticationSupport 31 | with CorsSupport 32 | with FreeswitchopStack { 33 | 34 | options("/*") { 35 | response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")) 36 | } 37 | 38 | // Sets up automatic case class to JSON output serialization, required by 39 | // the JValueResult trait. 40 | protected implicit val jsonFormats: Formats = DefaultFormats 41 | 42 | before() { 43 | contentType = formats("json") 44 | } 45 | 46 | post("/login") { 47 | logger.error("--------------> /login: wewewewewewewe: ") 48 | 49 | scentry.authenticate() 50 | if (isAuthenticated) { 51 | logger.info("--------------> /login: successful Id: " + user.userId) 52 | 53 | logger.info("--------------> /login: request.getRemoteAddr : " + request.getRemoteAddr) 54 | logger.info("--------------> /login: username : " + user.username) 55 | logger.info("--------------> /login: session.getid : " + session.getId) 56 | 57 | 58 | user.password = "" 59 | user 60 | } else { 61 | logger.info("-----------------------> /login: NOT successful") 62 | halt(401, "bad username or password") 63 | } 64 | } 65 | 66 | 67 | post("/logout") { 68 | //SqlUtils.logUserLogout(user.username, session.getId) 69 | requireLogin() //?? 70 | scentry.logout() 71 | } 72 | 73 | notFound { 74 | // remove content type in case it was set through an action 75 | contentType = null 76 | // Try to render a ScalateTemplate if no route matched 77 | findTemplate(requestPath) map { path => 78 | contentType = "text/html" 79 | layoutTemplate(path) 80 | } orElse serveStaticResource() getOrElse resourceNotFound() 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/servlets/WSEslServlet.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.servlets 20 | 21 | import java.util.Date 22 | 23 | import _root_.akka.actor.{ActorRef, ActorSystem} 24 | import gr.gnostix.api.auth.AuthenticationSupport 25 | import gr.gnostix.freeswitch.FreeswitchopStack 26 | import gr.gnostix.freeswitch.actors.ActorsProtocol.{RemoveAtmoClientUuid, AddAtmoClientUuid} 27 | import org.atmosphere.cpr.{AtmosphereResource, AtmosphereResourceFactory} 28 | import org.json4s.JsonDSL._ 29 | import org.json4s.{DefaultFormats, Formats, _} 30 | import org.scalatra._ 31 | import org.scalatra.atmosphere._ 32 | import org.scalatra.json.{JValueResult, JacksonJsonSupport} 33 | import org.scalatra.scalate.ScalateSupport 34 | 35 | import scala.concurrent.ExecutionContext.Implicits.global 36 | import scala.concurrent._ 37 | 38 | /** 39 | * Created by rebel on 18/7/15. 40 | */ 41 | 42 | class WSEslServlet(system:ActorSystem, myActor:ActorRef) extends ScalatraServlet 43 | with ScalateSupport 44 | with JValueResult 45 | with JacksonJsonSupport 46 | with SessionSupport 47 | with AuthenticationSupport 48 | with AtmosphereSupport 49 | with FreeswitchopStack 50 | with CorsSupport { 51 | 52 | protected implicit val jsonFormats: Formats = DefaultFormats 53 | 54 | before() { 55 | contentType = formats("json") 56 | //requireLogin() 57 | } 58 | 59 | options("/*") { 60 | response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")) 61 | } 62 | 63 | get("/test"){ 64 | "test works" 65 | } 66 | 67 | 68 | get("/") { 69 | contentType="text/html" 70 | ssp("/login") 71 | } 72 | 73 | atmosphere("/events") { 74 | new AtmosphereClient { 75 | def receive: AtmoReceive = { 76 | case Connected => 77 | //println("params :" + params("events")) 78 | myActor ! AddAtmoClientUuid(uuid) 79 | println("Client %s is connected" format uuid) 80 | //broadcast(("author" -> "Someone") ~ ("message" -> "joined the room") ~ ("time" -> (new Date().getTime.toString )), Everyone) 81 | 82 | case Disconnected(ClientDisconnected, _) => 83 | myActor ! RemoveAtmoClientUuid(uuid) 84 | println("Client %s is disconnected" format uuid) 85 | //broadcast(("author" -> "Someone") ~ ("message" -> "has left the room") ~ ("time" -> (new Date().getTime.toString )), Everyone) 86 | 87 | case Disconnected(ServerDisconnected, _) => 88 | myActor ! RemoveAtmoClientUuid(uuid) 89 | println("Server disconnected the client %s" format uuid) 90 | 91 | case x @ TextMessage(_) => 92 | println("text message received %s" format uuid) 93 | //broadcast(x.toString()) 94 | //send(("author" -> "system") ~ ("message" -> "Only json is allowed") ~ ("time" -> (new Date().getTime.toString ))) 95 | 96 | case JsonMessage(json) => 97 | println("------ json " + json.toString) 98 | println("Got message %s from %s".format((json \ "message").extract[String], (json \ "author").extract[String])) 99 | val msg = json merge (("time" -> (new Date().getTime().toString)): JValue) 100 | broadcast(msg) // by default a broadcast is to everyone but self 101 | //send(msg) // also send to the sender 102 | } 103 | } 104 | } 105 | 106 | 107 | error { 108 | case t: Throwable => t.printStackTrace() 109 | } 110 | } -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/utilities/DateUtils.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.utilities 20 | 21 | import java.sql.Timestamp 22 | import java.util.Date 23 | 24 | import org.joda.time.{DateTime, Days} 25 | import org.slf4j.LoggerFactory 26 | 27 | 28 | object DateUtils { 29 | val logger = LoggerFactory.getLogger(getClass) 30 | 31 | def findNumberOfDays(fromDate: DateTime, toDate: DateTime): Int = { 32 | try { 33 | val days = Days.daysBetween(fromDate, toDate).getDays 34 | logger.info("-----------------------> number of days between the two dates " + fromDate + " " + toDate) 35 | logger.info("-----------------------> number of days between the two dates " + days) 36 | days 37 | } catch { 38 | case e: Exception => println("-------------- exception in findNumberOfDays") 39 | 0 40 | } 41 | } 42 | 43 | def sqlGrouByDateOra(numDays: Int): String = { 44 | numDays match { 45 | case 0 => "HH" 46 | case x if 0 until 31 contains x => "DD" 47 | case x if 31 until 91 contains x => "ww" 48 | case x if x >= 91 => "month" 49 | case _ => "month" 50 | } 51 | } 52 | 53 | def sqlGrouByDatePg(numDays: Int): String = { 54 | numDays match { 55 | case 0 => "hour" 56 | case x if 0 until 31 contains x => "day" 57 | case x if 31 until 91 contains x => "week" 58 | case x if x >= 91 => "month" 59 | case _ => "month" 60 | } 61 | } 62 | 63 | 64 | def checkExpirationDate(expiration: Timestamp): Boolean = { 65 | if (expiration.after(new Date())) { 66 | true 67 | } else { 68 | false 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/utilities/EmailUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.utilities; 20 | 21 | import javax.mail.*; 22 | import javax.mail.internet.InternetAddress; 23 | import javax.mail.internet.MimeBodyPart; 24 | import javax.mail.internet.MimeMessage; 25 | import javax.mail.internet.MimeMultipart; 26 | import java.io.IOException; 27 | import java.util.Date; 28 | import java.util.Properties; 29 | 30 | /** 31 | * Created by rebel on 10/2/15. 32 | */ 33 | public class EmailUtils { 34 | 35 | public static void sendMailOneRecipient(String toEmail, String msg, String subject) throws IOException { 36 | final String username = "username"; 37 | final String password = "password"; 38 | final String fromEmailAddress = "info@domain.com"; 39 | 40 | Properties props = new Properties(); 41 | props.put("mail.smtp.host", "smtp.gmail.com"); 42 | props.put("mail.smtp.socketFactory.port", "465"); 43 | props.put("mail.smtp.socketFactory.class", 44 | "javax.net.ssl.SSLSocketFactory"); 45 | props.put("mail.smtp.auth", "true"); 46 | props.put("mail.smtp.port", "465"); 47 | 48 | Session session = Session.getInstance(props, 49 | new javax.mail.Authenticator() { 50 | protected PasswordAuthentication getPasswordAuthentication() { 51 | return new PasswordAuthentication(username,password); 52 | } 53 | }); 54 | 55 | try { 56 | 57 | Message message = new MimeMessage(session); 58 | message.setFrom(new InternetAddress(fromEmailAddress, "FS-Moni Support")); 59 | 60 | Address[] toAddr = new InternetAddress[1]; 61 | toAddr[0] = new InternetAddress(toEmail); 62 | message.setRecipients(Message.RecipientType.TO, toAddr); 63 | 64 | 65 | message.setSubject(subject); 66 | 67 | MimeBodyPart messagePart = new MimeBodyPart(); 68 | // messagePart.se 69 | messagePart.setText(msg, "utf-8"); 70 | messagePart.setHeader("Content-Type", 71 | "text/html; charset=\"utf-8\""); 72 | messagePart.setHeader("Content-Transfer-Encoding", 73 | "quoted-printable"); 74 | MimeMultipart multipart = new MimeMultipart(); 75 | multipart.addBodyPart(messagePart); // adding message part 76 | 77 | message.setContent(multipart); 78 | message.setSentDate(new Date()); 79 | 80 | Transport.send(message); 81 | 82 | System.out.println("Email send"); 83 | 84 | } catch (MessagingException e) { 85 | throw new RuntimeException(e); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/utilities/FileUtilities.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.utilities 20 | 21 | import java.io.File 22 | 23 | import gr.gnostix.freeswitch.actors.ServletProtocol.{ApiReplyError, ApiResponse, ApiReplyData, ApiReply} 24 | import org.scalatra.servlet.FileItem 25 | import org.slf4j.LoggerFactory 26 | 27 | /** 28 | * Created by rebel on 10/10/15. 29 | */ 30 | object FileUtilities { 31 | 32 | val log = LoggerFactory.getLogger(getClass) 33 | 34 | def processCsvFileItem(fileName: String, file: FileItem): ApiResponse = { 35 | var dialCodesMap = scala.collection.SortedMap.empty[String, String] 36 | val csv = io.Source.createBufferedSource(file.getInputStream) 37 | val (itr1, itr2) = csv.getLines().duplicate 38 | val chkFile = checkFile(itr1) 39 | 40 | chkFile.size match { 41 | case 0 => 42 | // without header ,proccess file country,code 43 | itr2.toList.map { 44 | l => val cols = if (l.contains(";")) l.split(";").map(_.trim) else l.split(",").map(_.trim) 45 | dialCodesMap += (cols(1) -> cols(0)) 46 | } 47 | csv.close() 48 | ApiReplyData(200, "File uploaded successfully", Map(fileName -> dialCodesMap)) 49 | 50 | case 1 if(chkFile.head == 1) => 51 | // with header ,proccess file country,code 52 | itr2.toList.tail.map { 53 | l => val cols = if (l.contains(";")) l.split(";").map(_.trim) else l.split(",").map(_.trim) 54 | dialCodesMap += (cols(1) -> cols(0)) 55 | } 56 | csv.close() 57 | ApiReplyData(200, "File uploaded successfully", Map(fileName -> dialCodesMap)) 58 | 59 | case _ => // errors in file 60 | csv.close() 61 | chkFile.head match { 62 | case 1 => ApiReplyError(400, s"Error in lines: ${chkFile.tail.mkString(",")}") 63 | case _ => ApiReplyError(400, s"Error in lines: ${chkFile.mkString(",")}") 64 | } 65 | 66 | } 67 | 68 | } 69 | 70 | def processResourcesCsvFile(): ApiResponse = { 71 | val csv = io.Source.fromURL(getClass.getResource("/dialcodes.csv")) //io.Source.fromFile("resources/dialcodes.csv") 72 | var dialCodesMap = scala.collection.SortedMap.empty[String, String] 73 | val (itr1, itr2) = csv.getLines().duplicate 74 | val chkFile = checkFile(itr1) //log.debug("----------> " + csv.getLines().size) 75 | 76 | chkFile.size match { 77 | case 0 => 78 | // proccess file country,code 79 | itr2.toList.map { 80 | l => val cols = if (l.contains(";")) l.split(";").map(_.trim) else l.split(",").map(_.trim) 81 | dialCodesMap += (cols(1) -> cols(0)) 82 | } 83 | csv.close() 84 | ApiReplyData(200, "File resources/dialcodes.csv uploaded successfully", Map("default" -> dialCodesMap)) 85 | case _ => // errors in file 86 | csv.close() 87 | ApiReplyError(400, s"Error uploading the default dialcodes.csv file from resources in lines: ${chkFile.mkString}") 88 | } 89 | 90 | } 91 | 92 | private def checkFile(itr: Iterator[String]) = { 93 | // country,code 94 | var errorLines: List[Int] = List() 95 | itr.toList.zipWithIndex.map { 96 | case (l, i) => val cols = if (l.contains(";")) l.split(";").map(_.trim) else l.split(",").map(_.trim) 97 | try { 98 | cols(1).toLong 99 | } catch { 100 | case e: Exception => errorLines ::= i + 1 101 | } 102 | } 103 | 104 | errorLines.reverse 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/scala/gr/gnostix/freeswitch/utilities/HelperFunctions.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.utilities 20 | 21 | import gr.gnostix.freeswitch.actors.CallEnd 22 | import gr.gnostix.freeswitch.model.{CompletedCallStatsByCountryAsrByIP, CompletedCallStatsByCountryAcdRtpQualityByIP, CompletedCallStatsByCountryByIP} 23 | 24 | import scala.util.Random 25 | 26 | /** 27 | * Created by rebel on 13/1/15. 28 | */ 29 | object HelperFunctions { 30 | 31 | def roundDouble(number: Double): Double = { 32 | BigDecimal(number).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble 33 | } 34 | 35 | def sortAcdByCountry(li: List[Option[CompletedCallStatsByCountryByIP]]) ={ 36 | li.flatten.groupBy(_.country).map{ 37 | case (c,v) => 38 | CompletedCallStatsByCountryAcdRtpQualityByIP(v.head.prefix.get, c.get, 39 | BigDecimal(v.map(_.billSec).sum.toDouble / v.size ).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble, 40 | BigDecimal(v.map(_.rtpQuality).sum.toDouble / v.size ).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble, 41 | v.size, v.head.ipAddress, v.head.hostname) 42 | } 43 | }.toList //BigDecimal( ).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble 44 | 45 | def getAsrByCountry(compCallsStats: List[Option[CompletedCallStatsByCountryByIP]], failedCalls: List[CallEnd]) = { 46 | compCallsStats.flatten.groupBy(_.country).map{ 47 | case (x,y) => 48 | val fCallsSizeByCountry = failedCalls.filter(_.country == x).size 49 | 50 | fCallsSizeByCountry match { 51 | case 0 => 52 | // if the failed calls to this destination, is 0 then the ASR is 100% 53 | CompletedCallStatsByCountryAsrByIP(y.head.prefix.get, x.get, y.size, fCallsSizeByCountry,100,y.head.ipAddress, y.head.hostname) 54 | 55 | case _ => CompletedCallStatsByCountryAsrByIP(y.head.prefix.get, x.get, y.size, fCallsSizeByCountry, 56 | BigDecimal(y.size.toDouble / (fCallsSizeByCountry + y.size) * 100).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble, 57 | y.head.ipAddress, y.head.hostname) 58 | } 59 | 60 | } 61 | } 62 | 63 | def randomAlphaNumericString(length: Int): String = { 64 | val chars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 65 | randomStringFromCharList(length, chars) 66 | } 67 | 68 | private def randomStringFromCharList(length: Int, chars: Seq[Char]): String = { 69 | val sb = new StringBuilder 70 | for (i <- 1 to length) { 71 | val randomNum = new Random().nextInt(chars.length) 72 | sb.append(chars(randomNum)) 73 | } 74 | sb.toString 75 | } 76 | 77 | // create a md5 hash from a string 78 | def sha1Hash(text: String): String = java.security.MessageDigest.getInstance("SHA").digest(text.getBytes()).map(0xFF & _).map { 79 | "%02x".format(_) 80 | }.foldLeft("") { 81 | _ + _ 82 | } 83 | 84 | 85 | def doublePrecision1(num: Double): Double = { 86 | BigDecimal(num).setScale(1, BigDecimal.RoundingMode.HALF_UP).toDouble 87 | } 88 | 89 | def isStrEmpty(x: String) = x != null && x.trim.nonEmpty 90 | } 91 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/webapp/css/growl/jquery.gritter.css: -------------------------------------------------------------------------------- 1 | /* the norm */ 2 | #gritter-notice-wrapper { 3 | position:fixed; 4 | top:20px; 5 | right:20px; 6 | width:301px; 7 | z-index:9999; 8 | } 9 | #gritter-notice-wrapper.top-left { 10 | left: 20px; 11 | right: auto; 12 | } 13 | #gritter-notice-wrapper.bottom-right { 14 | top: auto; 15 | left: auto; 16 | bottom: 20px; 17 | right: 20px; 18 | } 19 | #gritter-notice-wrapper.bottom-left { 20 | top: auto; 21 | right: auto; 22 | bottom: 20px; 23 | left: 20px; 24 | } 25 | .gritter-item-wrapper { 26 | position:relative; 27 | margin:0 0 10px 0; 28 | background:url('../../images/growl/ie-spacer.gif'); /* ie7/8 fix */ 29 | } 30 | .gritter-top { 31 | background:url(../../images/growl/gritter.png) no-repeat left -30px; 32 | height:10px; 33 | } 34 | .hover .gritter-top { 35 | background-position:right -30px; 36 | } 37 | .gritter-bottom { 38 | background:url(../../images/growl/gritter.png) no-repeat left bottom; 39 | height:8px; 40 | margin:0; 41 | } 42 | .hover .gritter-bottom { 43 | background-position: bottom right; 44 | } 45 | .gritter-item { 46 | display:block; 47 | background:url(../../images/growl/gritter.png) no-repeat left -40px; 48 | color:#eee; 49 | padding:2px 11px 8px 11px; 50 | font-size: 11px; 51 | font-family:verdana; 52 | } 53 | .hover .gritter-item { 54 | background-position:right -40px; 55 | } 56 | .gritter-item p { 57 | padding:0; 58 | margin:0; 59 | word-wrap:break-word; 60 | } 61 | .gritter-close { 62 | display:none; 63 | position:absolute; 64 | top:5px; 65 | left:3px; 66 | background:url(../../images/growl/gritter.png) no-repeat left top; 67 | cursor:pointer; 68 | width:30px; 69 | height:30px; 70 | text-indent:-9999em; 71 | } 72 | .gritter-title { 73 | font-size:14px; 74 | font-weight:bold; 75 | padding:0 0 7px 0; 76 | display:block; 77 | text-shadow:1px 1px 0 #000; /* Not supported by IE :( */ 78 | } 79 | .gritter-image { 80 | width:48px; 81 | height:48px; 82 | float:left; 83 | } 84 | .gritter-with-image, 85 | .gritter-without-image { 86 | padding:0; 87 | } 88 | .gritter-with-image { 89 | width:220px; 90 | float:right; 91 | } 92 | /* for the light (white) version of the gritter notice */ 93 | .gritter-light .gritter-item, 94 | .gritter-light .gritter-bottom, 95 | .gritter-light .gritter-top, 96 | .gritter-light .gritter-close { 97 | background-image: url(../../images/growl/gritter-light.png); 98 | color: #222; 99 | } 100 | .gritter-light .gritter-title { 101 | text-shadow: none; 102 | } 103 | -------------------------------------------------------------------------------- /src/main/webapp/css/plugins/morris.css: -------------------------------------------------------------------------------- 1 | .morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} 2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} 3 | -------------------------------------------------------------------------------- /src/main/webapp/css/sb-admin-rtl.css: -------------------------------------------------------------------------------- 1 | 2 | @media (min-width: 768px){ 3 | #wrapper {padding-right: 225px; padding-left: 0;} 4 | .side-nav{right: 0;left: auto;} 5 | } -------------------------------------------------------------------------------- /src/main/webapp/css/upload/jquery.fileupload.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button { 14 | position: relative; 15 | overflow: hidden; 16 | display: inline-block; 17 | } 18 | .fileinput-button input { 19 | position: absolute; 20 | top: 0; 21 | right: 0; 22 | margin: 0; 23 | opacity: 0; 24 | -ms-filter: 'alpha(opacity=0)'; 25 | font-size: 200px; 26 | direction: ltr; 27 | cursor: pointer; 28 | } 29 | 30 | /* Fixes for IE < 8 */ 31 | @media screen\9 { 32 | .fileinput-button input { 33 | filter: alpha(opacity=0); 34 | font-size: 100%; 35 | height: 100%; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/elfinder.theme.css: -------------------------------------------------------------------------------- 1 | /** 2 | * MacOS X like theme for elFinder. 3 | * Required jquery ui "smoothness" theme. 4 | * 5 | * @author Dmitry (dio) Levashov 6 | **/ 7 | 8 | /* dialogs */ 9 | .std42-dialog, .std42-dialog .ui-widget-content { background-color:#ededed; background-image:none; background-clip: content-box; } 10 | 11 | /* navbar */ 12 | .elfinder .elfinder-navbar { background:#dde4eb; } 13 | .elfinder-navbar .ui-state-hover { background:transparent; border-color:transparent; } 14 | .elfinder-navbar .ui-state-active { background: #3875d7; border-color:#3875d7; color:#fff; } 15 | /* disabled elfinder */ 16 | .elfinder-disabled .elfinder-navbar .ui-state-active { background: #dadada; border-color:#aaa; color:#fff; } 17 | 18 | 19 | /* current directory */ 20 | /* selected file in "icons" view */ 21 | .elfinder-cwd-view-icons .elfinder-cwd-file .ui-state-hover { background:#ccc; } 22 | /* list view*/ 23 | .elfinder-cwd table tr:nth-child(odd) { background-color:#edf3fe; } 24 | .elfinder-cwd table tr { border-top:1px solid #fff; } 25 | 26 | /* common selected background/color */ 27 | .elfinder-cwd-view-icons .elfinder-cwd-file .elfinder-cwd-filename.ui-state-hover, 28 | .elfinder-cwd table td.ui-state-hover, 29 | .elfinder-button-menu .ui-state-hover { background: #3875d7; color:#fff;} 30 | /* disabled elfinder */ 31 | .elfinder-disabled .elfinder-cwd-view-icons .elfinder-cwd-file .elfinder-cwd-filename.ui-state-hover, 32 | .elfinder-disabled .elfinder-cwd table td.ui-state-hover { background:#dadada;} 33 | 34 | /* statusbar */ 35 | .elfinder .elfinder-statusbar { color:#555; } 36 | .elfinder .elfinder-statusbar a { text-decoration:none; color:#555;} 37 | 38 | 39 | .std42-dialog .elfinder-help, .std42-dialog .elfinder-help .ui-widget-content { background:#fff;} 40 | 41 | /* contextmenu */ 42 | .elfinder-contextmenu .ui-state-hover { background: #3875d7; color:#fff; } 43 | .elfinder-contextmenu .ui-state-hover .elfinder-contextmenu-arrow { background-image:url('../img/arrows-active.png'); } 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/ie.css: -------------------------------------------------------------------------------- 1 | .navbar-inner a i { 2 | margin-top: 4px; 3 | } 4 | 5 | .box-header h2 i { 6 | margin-top: 1px; 7 | } 8 | 9 | .todo-actions i { 10 | margin: 2px 5px 0px 5px; 11 | } 12 | 13 | .message .header i { 14 | margin-top: 3px; 15 | } 16 | 17 | .container-fluid-full { 18 | overflow: hidden; 19 | position: relative; 20 | height: 100%; 21 | } 22 | 23 | #content { 24 | width: 85.578%; 25 | padding: 28px; 26 | margin: 0px 0px; 27 | margin-left: 14.422% !important; 28 | } 29 | 30 | #sidebar-left { 31 | background: #3D3D3D; 32 | margin-left: 0px !important; 33 | position: absolute; 34 | height: 100%; 35 | } 36 | 37 | hr { 38 | height: 2px; 39 | border: none; 40 | background: #f9f9f9; 41 | } 42 | 43 | .dark { 44 | right: -12px; 45 | } 46 | 47 | footer { 48 | margin: 0px 0px 0px 0px; 49 | padding: 10px 20px; 50 | } 51 | 52 | .hideInIE8 { 53 | display: none; 54 | } 55 | 56 | .task .time .date { 57 | font-size: 14px; 58 | margin-bottom: 5px; 59 | } 60 | 61 | .statbox .footer { 62 | background:none; 63 | -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#19FFFFFF,endColorstr=#19FFFFFF); 64 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#19FFFFFF,endColorstr=#19FFFFFF); 65 | zoom: 1; 66 | } 67 | 68 | .statbox .footer:hover { 69 | background:none; 70 | -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#33FFFFFF,endColorstr=#33FFFFFF); 71 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#33FFFFFF,endColorstr=#33FFFFFF); 72 | zoom: 1; 73 | } 74 | 75 | .sidebar-nav > ul > li > ul { 76 | background:none; 77 | -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3F000000,endColorstr=#3F000000); 78 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3F000000,endColorstr=#3F000000); 79 | zoom: 1; 80 | } 81 | 82 | .verticalChart .singleBar .bar .value span{ 83 | color: #000; 84 | } 85 | 86 | .verticalChart .singleBar .bar { 87 | background:none; 88 | -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#33FFFFFF,endColorstr=#33FFFFFF); 89 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#33FFFFFF,endColorstr=#33FFFFFF); 90 | zoom: 1; 91 | } 92 | 93 | ul.chat.metro li .message { 94 | background:none; 95 | -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#44000000,endColorstr=#44000000); 96 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#44000000,endColorstr=#44000000); 97 | zoom: 1; 98 | border: 0px; 99 | } 100 | 101 | ul.chat.metro li.left .message .arrow { 102 | left: -20px; 103 | } 104 | 105 | ul.chat.metro li.right .message .arrow { 106 | right: -20px; 107 | } -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/ie9.css: -------------------------------------------------------------------------------- 1 | .dark { 2 | right: -25px; 3 | } 4 | 5 | .btn-overlay { 6 | filter: none !important; 7 | } 8 | 9 | hr, 10 | .sliderOverlay, 11 | .progressBarOverlay, 12 | .slider, 13 | .progress, 14 | .progressSlim, 15 | .ui-progressbar-value, 16 | .ui-slider-range, 17 | .sliderVertical { 18 | filter: none !important; 19 | } 20 | 21 | .verticalChart .singleBar .bar .value span{ 22 | color: #3b3b41; 23 | } -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/jquery.cleditor.css: -------------------------------------------------------------------------------- 1 | .cleditorMain {border:1px solid #ddd; padding:0 1px 1px; background-color:white} 2 | .cleditorMain iframe {border:none; margin:0; padding:0} 3 | .cleditorMain textarea {border:none; margin:0; padding:0; overflow-y:scroll; font:10pt Arial,Verdana; resize:none; outline:none /* webkit grip focus */} 4 | .cleditorToolbar {background: url('../img/toolbar.gif') repeat} 5 | .cleditorGroup {float:left; height:26px} 6 | .cleditorButton {float:left; width:24px; height:24px; margin:1px 0 1px 0; background: url('../img/buttons.gif')} 7 | .cleditorDisabled {opacity:0.3; filter:alpha(opacity=30)} 8 | .cleditorDivider {float:left; width:1px; height:23px; margin:1px 0 1px 0; background:#CCC} 9 | .cleditorPopup {border:solid 1px #999; background-color:white; position:absolute; font:10pt Arial,Verdana; cursor:default; z-index:10000} 10 | .cleditorList div {padding:2px 4px 2px 4px} 11 | .cleditorList p, 12 | .cleditorList h1, 13 | .cleditorList h2, 14 | .cleditorList h3, 15 | .cleditorList h4, 16 | .cleditorList h5, 17 | .cleditorList h6, 18 | .cleditorList font {padding:0; margin:0; background-color:Transparent} 19 | .cleditorColor {width:150px; padding:1px 0 0 1px} 20 | .cleditorColor div {float:left; width:14px; height:14px; margin:0 1px 1px 0} 21 | .cleditorPrompt {background-color:#F6F7F9; padding:4px; font-size:8.5pt} 22 | .cleditorPrompt input, 23 | .cleditorPrompt textarea {font:8.5pt Arial,Verdana;} 24 | .cleditorMsg {background-color:#FDFCEE; width:150px; padding:4px; font-size:8.5pt} 25 | -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/jquery.gritter.css: -------------------------------------------------------------------------------- 1 | /* the norm */ 2 | #gritter-notice-wrapper { 3 | position:fixed; 4 | top:20px; 5 | right:20px; 6 | width:301px; 7 | z-index:9999; 8 | } 9 | 10 | #gritter-notice-wrapper.top-left { 11 | left: 20px; 12 | right: auto; 13 | } 14 | 15 | #gritter-notice-wrapper.bottom-right { 16 | top: auto; 17 | left: auto; 18 | bottom: 20px; 19 | right: 20px; 20 | } 21 | 22 | #gritter-notice-wrapper.bottom-left { 23 | top: auto; 24 | right: auto; 25 | bottom: 20px; 26 | left: 20px; 27 | } 28 | 29 | .gritter-item-wrapper { 30 | position:relative; 31 | margin:0 0 10px 0; 32 | } 33 | 34 | .gritter-item { 35 | background-color: rgba(0,0,0,0.8); 36 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc000000', endColorstr='#cc000000',GradientType=0 ); /* IE6-8 */ 37 | color:#fff; 38 | padding:15px; 39 | font-size: 11px; 40 | -webkit-border-radius: 4px !important; 41 | -moz-border-radius: 4px !important; 42 | border-radius: 4px !important; 43 | -webkit-box-shadow: 0px 1px 1px rgba(0,0,0,0.25); 44 | -moz-box-shadow: 0px 1px 1px rgba(0,0,0,0.25); 45 | box-shadow: 0px 1px 1px rgba(0,0,0,0.25); 46 | } 47 | 48 | .hover .gritter-item {} 49 | 50 | .gritter-item p { 51 | padding:0; 52 | margin:0; 53 | word-wrap:break-word; 54 | } 55 | 56 | .gritter-close { 57 | display:none; 58 | position:absolute; 59 | top:5px; 60 | right:5px; 61 | cursor:pointer; 62 | width:12px; 63 | height:12px; 64 | background: url(../img/close-button-white.png); 65 | opacity: .6; 66 | 67 | } 68 | 69 | .gritter-title { 70 | font-size:14px; 71 | font-weight:bold; 72 | padding:0 0 7px 0; 73 | display:block; 74 | text-shadow:1px 1px 0 #000; /* Not supported by IE :( */ 75 | } 76 | 77 | .gritter-image { 78 | width:48px; 79 | height:48px; 80 | float:left; 81 | margin: -5px 5px 5px -5px; 82 | } 83 | 84 | .gritter-with-image, 85 | .gritter-without-image { 86 | padding:0; 87 | } 88 | 89 | .gritter-with-image { 90 | width:220px; 91 | float:right; 92 | } 93 | 94 | /* for the light (white) version of the gritter notice */ 95 | .gritter-light .gritter-item { 96 | background-color: rgba(255,255,255,0.8); 97 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccFFFFFF', endColorstr='#ccFFFFFF',GradientType=0 ); /* IE6-8 */ 98 | color: #646464 !important; 99 | -webkit-box-shadow: 0px 1px 1px rgba(0,0,0,0.25); 100 | -moz-box-shadow: 0px 1px 1px rgba(0,0,0,0.25); 101 | box-shadow: 0px 1px 1px rgba(0,0,0,0.25); 102 | } 103 | 104 | .gritter-light .gritter-close { 105 | background: url(../img/close-button.png); 106 | } 107 | 108 | 109 | .gritter-light .gritter-title { 110 | color: #646464 !important; 111 | text-shadow: none !important; 112 | } -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/jquery.iphone.toggle.css: -------------------------------------------------------------------------------- 1 | .iPhoneCheckContainer { 2 | position: relative; 3 | height: 27px; 4 | cursor: pointer; 5 | overflow: hidden; } 6 | .iPhoneCheckContainer input { 7 | position: absolute; 8 | top: 5px; 9 | left: 30px; 10 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 11 | opacity: 0; } 12 | .iPhoneCheckContainer label { 13 | white-space: nowrap; 14 | font-size: 17px; 15 | line-height: 17px; 16 | font-weight: bold; 17 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; 18 | cursor: pointer; 19 | display: block; 20 | height: 27px; 21 | position: absolute; 22 | width: auto; 23 | top: 0; 24 | padding-top: 5px; 25 | overflow: hidden; } 26 | .iPhoneCheckContainer, .iPhoneCheckContainer label { 27 | user-select: none; 28 | -moz-user-select: none; 29 | -khtml-user-select: none; } 30 | 31 | .iPhoneCheckDisabled { 32 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); 33 | opacity: 0.5; } 34 | 35 | label.iPhoneCheckLabelOn { 36 | color: white; 37 | background: url('../img/iphone-style-checkboxes/on-63584.png') no-repeat; 38 | text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.6); 39 | left: 0; 40 | padding-top: 5px; } 41 | label.iPhoneCheckLabelOn span { 42 | padding-left: 8px; } 43 | label.iPhoneCheckLabelOff { 44 | color: #8b8b8b; 45 | background: url('../img/iphone-style-checkboxes/off-63584.png') no-repeat right 0; 46 | text-shadow: 0px 0px 2px rgba(255, 255, 255, 0.6); 47 | text-align: right; 48 | right: 0; } 49 | label.iPhoneCheckLabelOff span { 50 | padding-right: 8px; } 51 | 52 | .iPhoneCheckHandle { 53 | display: block; 54 | height: 27px; 55 | cursor: pointer; 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | width: 0; 60 | background: url('../img/iphone-style-checkboxes/slider_left-63584.png') no-repeat; 61 | padding-left: 3px; } 62 | 63 | .iPhoneCheckHandleRight { 64 | height: 100%; 65 | width: 100%; 66 | padding-right: 3px; 67 | background: url('../img/iphone-style-checkboxes/slider_right-63584.png') no-repeat right 0; } 68 | 69 | .iPhoneCheckHandleCenter { 70 | height: 100%; 71 | width: 100%; 72 | background: url('../img/iphone-style-checkboxes/slider_center-63584.png'); } 73 | 74 | .iOSCheckContainer { 75 | position: relative; 76 | height: 27px; 77 | cursor: pointer; 78 | overflow: hidden; } 79 | .iOSCheckContainer input { 80 | position: absolute; 81 | top: 5px; 82 | left: 30px; 83 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 84 | opacity: 0; } 85 | .iOSCheckContainer label { 86 | white-space: nowrap; 87 | font-size: 17px; 88 | line-height: 17px; 89 | font-weight: bold; 90 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; 91 | cursor: pointer; 92 | display: block; 93 | height: 27px; 94 | position: absolute; 95 | width: auto; 96 | top: 0; 97 | padding-top: 5px; 98 | overflow: hidden; } 99 | .iOSCheckContainer, .iOSCheckContainer label { 100 | user-select: none; 101 | -moz-user-select: none; 102 | -khtml-user-select: none; } 103 | 104 | .iOSCheckDisabled { 105 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); 106 | opacity: 0.5; } 107 | 108 | label.iOSCheckLabelOn { 109 | color: white; 110 | background: url('../img/ios-style-checkboxes/on-63584.png') no-repeat; 111 | text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.6); 112 | left: 0; 113 | padding-top: 5px; } 114 | label.iOSCheckLabelOn span { 115 | padding-left: 8px; } 116 | label.iOSCheckLabelOff { 117 | color: #8b8b8b; 118 | background: url('../img/ios-style-checkboxes/off-63584.png') no-repeat right 0; 119 | text-shadow: 0px 0px 2px rgba(255, 255, 255, 0.6); 120 | text-align: right; 121 | right: 0; } 122 | label.iOSCheckLabelOff span { 123 | padding-right: 8px; } 124 | 125 | .iOSCheckHandle { 126 | display: block; 127 | height: 27px; 128 | cursor: pointer; 129 | position: absolute; 130 | top: 0; 131 | left: 0; 132 | width: 0; 133 | background: url('../img/ios-style-checkboxes/slider_left-63584.png') no-repeat; 134 | padding-left: 3px; } 135 | 136 | .iOSCheckHandleRight { 137 | height: 100%; 138 | width: 100%; 139 | padding-right: 3px; 140 | background: url('../img/ios-style-checkboxes/slider_right-63584.png') no-repeat right 0; } 141 | 142 | .iOSCheckHandleCenter { 143 | height: 100%; 144 | width: 100%; 145 | background: url('../img/ios-style-checkboxes/slider_center-63584.png'); } 146 | 147 | /* Localized */ -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/jquery.noty.css: -------------------------------------------------------------------------------- 1 | 2 | /* CORE STYLES */ 3 | 4 | /* noty bar */ 5 | .noty_bar { 6 | position: fixed; 7 | display: none; 8 | z-index: 9999999; 9 | } 10 | 11 | /* noty_message */ 12 | .noty_bar .noty_message { 13 | text-align: center; 14 | } 15 | 16 | /* noty close button */ 17 | .noty_bar .noty_close { 18 | cursor: pointer; 19 | } 20 | 21 | /* noty modal */ 22 | .noty_modal { 23 | position: fixed; 24 | width: 100%; 25 | height: 100%; 26 | background-color: #000; 27 | z-index: 10000; 28 | opacity: 0.6; 29 | display: none; 30 | left: 0; 31 | top: 0; 32 | } 33 | 34 | /* noty container for noty_layout_topLeft & noty_layout_topRight */ 35 | ul.noty_cont { 36 | position: fixed; 37 | z-index: 10000000; 38 | margin: 0px; 39 | padding: 0px; 40 | list-style: none; 41 | width: 300px; 42 | } 43 | ul.noty_cont li { 44 | position: relative; 45 | float: left; 46 | clear: both; 47 | list-style: none; 48 | padding: 0px; 49 | margin: 10px 0 0 0; 50 | width: 300px; /* Fix for: http://bugs.jquery.com/ticket/2278 */ 51 | } 52 | ul.noty_cont.noty_layout_topLeft {left:20px; top:20px;} 53 | ul.noty_cont.noty_layout_topRight {right:40px; top:20px;} 54 | ul.noty_cont.noty_layout_bottomLeft {left:20px; bottom:20px} 55 | ul.noty_cont.noty_layout_bottomRight {right:40px; bottom:20px} 56 | ul.noty_cont.noty_layout_topRight li {float:right} 57 | 58 | /* LAYOUTS */ 59 | 60 | /* noty_layout_top */ 61 | .noty_bar.noty_layout_top { 62 | top: 0; 63 | left: 0; 64 | width: 100%; 65 | -webkit-border-radius: 0px; 66 | -moz-border-radius: 0px; 67 | border-radius: 0px; 68 | } 69 | 70 | /* noty_layout_bottom */ 71 | .noty_bar.noty_layout_bottom { 72 | bottom: 0; 73 | left: 0; 74 | width: 100%; 75 | -webkit-border-radius: 0px; 76 | -moz-border-radius: 0px; 77 | border-radius: 0px; 78 | } 79 | 80 | /* noty_layout_center */ 81 | .noty_bar.noty_layout_center { 82 | top: 40%; 83 | } 84 | 85 | /* noty_layout_topLeft & noty_layout_topRight */ 86 | .noty_bar.noty_layout_topLeft, 87 | .noty_bar.noty_layout_topRight, 88 | .noty_bar.noty_layout_bottomLeft, 89 | .noty_bar.noty_layout_bottomRight { 90 | width: 100%; 91 | clear: both; 92 | position: relative; 93 | } 94 | 95 | .noty_bar.noty_layout_topLeft .noty_message, 96 | .noty_bar.noty_layout_topRight .noty_message, 97 | .noty_bar.noty_layout_bottomLeft .noty_message, 98 | .noty_bar.noty_layout_bottomRight .noty_message { 99 | text-align: left; 100 | } 101 | 102 | /* noty_layout_topCenter */ 103 | .noty_bar.noty_layout_topCenter { 104 | top: 20px; 105 | } -------------------------------------------------------------------------------- /src/main/webapp/dialog/css/uploadify.css: -------------------------------------------------------------------------------- 1 | /* 2 | Uploadify 3 | Copyright (c) 2012 Reactive Apps, Ronnie Garcia 4 | Released under the MIT License 5 | */ 6 | 7 | .uploadify { 8 | position: relative; 9 | margin-bottom: 1em; 10 | } 11 | .uploadify-button { 12 | background-color: #505050; 13 | background-image: linear-gradient(bottom, #505050 0%, #707070 100%); 14 | background-image: -o-linear-gradient(bottom, #505050 0%, #707070 100%); 15 | background-image: -moz-linear-gradient(bottom, #505050 0%, #707070 100%); 16 | background-image: -webkit-linear-gradient(bottom, #505050 0%, #707070 100%); 17 | background-image: -ms-linear-gradient(bottom, #505050 0%, #707070 100%); 18 | background-image: -webkit-gradient( 19 | linear, 20 | left bottom, 21 | left top, 22 | color-stop(0, #505050), 23 | color-stop(1, #707070) 24 | ); 25 | background-position: center top; 26 | background-repeat: no-repeat; 27 | -webkit-border-radius: 30px; 28 | -moz-border-radius: 30px; 29 | border-radius: 30px; 30 | border: 2px solid #808080; 31 | color: #FFF; 32 | font: bold 12px Arial, Helvetica, sans-serif; 33 | text-align: center; 34 | text-shadow: 0 -1px 0 rgba(0,0,0,0.25); 35 | width: 100%; 36 | } 37 | .uploadify:hover .uploadify-button { 38 | background-color: #606060; 39 | background-image: linear-gradient(top, #606060 0%, #808080 100%); 40 | background-image: -o-linear-gradient(top, #606060 0%, #808080 100%); 41 | background-image: -moz-linear-gradient(top, #606060 0%, #808080 100%); 42 | background-image: -webkit-linear-gradient(top, #606060 0%, #808080 100%); 43 | background-image: -ms-linear-gradient(top, #606060 0%, #808080 100%); 44 | background-image: -webkit-gradient( 45 | linear, 46 | left bottom, 47 | left top, 48 | color-stop(0, #606060), 49 | color-stop(1, #808080) 50 | ); 51 | background-position: center bottom; 52 | } 53 | .uploadify-button.disabled { 54 | background-color: #D0D0D0; 55 | color: #808080; 56 | } 57 | .uploadify-queue { 58 | margin-bottom: 1em; 59 | } 60 | .uploadify-queue-item { 61 | background-color: #F5F5F5; 62 | -webkit-border-radius: 3px; 63 | -moz-border-radius: 3px; 64 | border-radius: 3px; 65 | font: 11px Verdana, Geneva, sans-serif; 66 | margin-top: 5px; 67 | max-width: 350px; 68 | padding: 10px; 69 | } 70 | .uploadify-error { 71 | background-color: #FDE5DD !important; 72 | } 73 | .uploadify-queue-item .cancel a { 74 | background: url('../img/uploadify-cancel.png') 0 0 no-repeat; 75 | float: right; 76 | height: 16px; 77 | text-indent: -9999px; 78 | width: 16px; 79 | } 80 | .uploadify-queue-item.completed { 81 | background-color: #E5E5E5; 82 | } 83 | .uploadify-progress { 84 | background-color: #E5E5E5; 85 | margin-top: 10px; 86 | width: 100%; 87 | } 88 | .uploadify-progress-bar { 89 | background-color: #0099FF; 90 | height: 3px; 91 | width: 1px; 92 | } -------------------------------------------------------------------------------- /src/main/webapp/dialog/js/counter.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.countTo = function(options) { 3 | // merge the default plugin settings with the custom options 4 | options = $.extend({}, $.fn.countTo.defaults, options || {}); 5 | 6 | // how many times to update the value, and how much to increment the value on each update 7 | var loops = Math.ceil(options.speed / options.refreshInterval), 8 | increment = (options.to - options.from) / loops; 9 | 10 | return $(this).each(function() { 11 | var _this = this, 12 | loopCount = 0, 13 | value = options.from, 14 | interval = setInterval(updateTimer, options.refreshInterval); 15 | 16 | function updateTimer() { 17 | value += increment; 18 | loopCount++; 19 | $(_this).html(value.toFixed(options.decimals)); 20 | 21 | if (typeof(options.onUpdate) == 'function') { 22 | options.onUpdate.call(_this, value); 23 | } 24 | 25 | if (loopCount >= loops) { 26 | clearInterval(interval); 27 | value = options.to; 28 | 29 | if (typeof(options.onComplete) == 'function') { 30 | options.onComplete.call(_this, value); 31 | } 32 | } 33 | } 34 | }); 35 | }; 36 | 37 | $.fn.countTo.defaults = { 38 | from: 0, // the number the element should start at 39 | to: 100, // the number the element should end at 40 | speed: 1000, // how long it should take to count between the target numbers 41 | refreshInterval: 100, // how often the element should be updated 42 | decimals: 0, // the number of decimal places to show 43 | onUpdate: null, // callback method for every time the element is updated, 44 | onComplete: null, // callback method for when the element finishes updating 45 | }; 46 | })(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/dialog/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2011, Klaus Hartl 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.opensource.org/licenses/GPL-2.0 9 | */ 10 | (function($) { 11 | $.cookie = function(key, value, options) { 12 | 13 | // key and at least value given, set cookie... 14 | if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) { 15 | options = $.extend({}, options); 16 | 17 | if (value === null || value === undefined) { 18 | options.expires = -1; 19 | } 20 | 21 | if (typeof options.expires === 'number') { 22 | var days = options.expires, t = options.expires = new Date(); 23 | t.setDate(t.getDate() + days); 24 | } 25 | 26 | value = String(value); 27 | 28 | return (document.cookie = [ 29 | encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value), 30 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 31 | options.path ? '; path=' + options.path : '', 32 | options.domain ? '; domain=' + options.domain : '', 33 | options.secure ? '; secure' : '' 34 | ].join('')); 35 | } 36 | 37 | // key and possibly options given, get cookie... 38 | options = value || {}; 39 | var decode = options.raw ? function(s) { return s; } : decodeURIComponent; 40 | 41 | var pairs = document.cookie.split('; '); 42 | for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) { 43 | if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined 44 | } 45 | return null; 46 | }; 47 | })(jQuery); 48 | -------------------------------------------------------------------------------- /src/main/webapp/dialog/js/jquery.flot.resize.min.js: -------------------------------------------------------------------------------- 1 | (function(n,p,u){var w=n([]),s=n.resize=n.extend(n.resize,{}),o,l="setTimeout",m="resize",t=m+"-special-event",v="delay",r="throttleWindow";s[v]=250;s[r]=true;n.event.special[m]={setup:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.add(a);n.data(this,t,{w:a.width(),h:a.height()});if(w.length===1){q()}},teardown:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.not(a);a.removeData(t);if(!w.length){clearTimeout(o)}},add:function(b){if(!s[r]&&this[l]){return false}var c;function a(d,h,g){var f=n(this),e=n.data(this,t);e.w=h!==u?h:f.width();e.h=g!==u?g:f.height();c.apply(this,arguments)}if(n.isFunction(b)){c=b;return a}else{c=b.handler;b.handler=a}}};function q(){o=p[l](function(){w.each(function(){var d=n(this),a=d.width(),b=d.height(),c=n.data(this,t);if(a!==c.w||b!==c.h){d.trigger(m,[c.w=a,c.h=b])}});q()},s[v])}})(jQuery,this);(function(b){var a={};function c(f){function e(){var h=f.getPlaceholder();if(h.width()==0||h.height()==0){return}f.resize();f.setupGrid();f.draw()}function g(i,h){i.getPlaceholder().resize(e)}function d(i,h){i.getPlaceholder().unbind("resize",e)}f.hooks.bindEvents.push(g);f.hooks.shutdown.push(d)}b.plot.plugins.push({init:c,options:a,name:"resize",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/dialog/js/jquery.gritter.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.gritter={};b.gritter.options={position:"",class_name:"",fade_in_speed:"medium",fade_out_speed:1000,time:6000};b.gritter.add=function(f){try{return a.add(f||{})}catch(d){var c="Gritter Error: "+d;(typeof(console)!="undefined"&&console.error)?console.error(c,f):alert(c)}};b.gritter.remove=function(d,c){a.removeSpecific(d,c||{})};b.gritter.removeAll=function(c){a.stop(c||{})};var a={position:"",fade_in_speed:"",fade_out_speed:"",time:"",_custom_timer:0,_item_count:0,_is_setup:0,_tpl_close:'
',_tpl_title:'[[title]]',_tpl_item:'',_tpl_wrap:'
',add:function(g){if(typeof(g)=="string"){g={text:g}}if(!g.text){throw'You must supply "text" parameter.'}if(!this._is_setup){this._runSetup()}var k=g.title,n=g.text,e=g.image||"",l=g.sticky||false,m=g.class_name||b.gritter.options.class_name,j=b.gritter.options.position,d=g.time||"";this._verifyWrapper();this._item_count++;var f=this._item_count,i=this._tpl_item;b(["before_open","after_open","before_close","after_close"]).each(function(p,q){a["_"+q+"_"+f]=(b.isFunction(g[q]))?g[q]:function(){}});this._custom_timer=0;if(d){this._custom_timer=d}var c=(e!="")?'':"",h=(e!="")?"gritter-with-image":"gritter-without-image";if(k){k=this._str_replace("[[title]]",k,this._tpl_title)}else{k=""}i=this._str_replace(["[[title]]","[[text]]","[[close]]","[[image]]","[[number]]","[[class_name]]","[[item_class]]"],[k,n,this._tpl_close,c,this._item_count,h,m],i);if(this["_before_open_"+f]()===false){return false}b("#gritter-notice-wrapper").addClass(j).append(i);var o=b("#gritter-item-"+this._item_count);o.fadeIn(this.fade_in_speed,function(){a["_after_open_"+f](b(this))});if(!l){this._setFadeTimer(o,f)}b(o).bind("mouseenter mouseleave",function(p){if(p.type=="mouseenter"){if(!l){a._restoreItemIfFading(b(this),f)}}else{if(!l){a._setFadeTimer(b(this),f)}}a._hoverState(b(this),p.type)});b(o).find(".gritter-close").click(function(){a.removeSpecific(f,{},null,true)});return f},_countRemoveWrapper:function(c,d,f){d.remove();this["_after_close_"+c](d,f);if(b(".gritter-item-wrapper").length==0){b("#gritter-notice-wrapper").remove()}},_fade:function(g,d,j,f){var j=j||{},i=(typeof(j.fade)!="undefined")?j.fade:true,c=j.speed||this.fade_out_speed,h=f;this["_before_close_"+d](g,h);if(f){g.unbind("mouseenter mouseleave")}if(i){g.animate({opacity:0},c,function(){g.animate({height:0},300,function(){a._countRemoveWrapper(d,g,h)})})}else{this._countRemoveWrapper(d,g)}},_hoverState:function(d,c){if(c=="mouseenter"){d.addClass("hover");d.find(".gritter-close").show()}else{d.removeClass("hover");d.find(".gritter-close").hide()}},removeSpecific:function(c,g,f,d){if(!f){var f=b("#gritter-item-"+c)}this._fade(f,c,g||{},d)},_restoreItemIfFading:function(d,c){clearTimeout(this["_int_id_"+c]);d.stop().css({opacity:"",height:""})},_runSetup:function(){for(opt in b.gritter.options){this[opt]=b.gritter.options[opt]}this._is_setup=1},_setFadeTimer:function(f,d){var c=(this._custom_timer)?this._custom_timer:this.time;this["_int_id_"+d]=setTimeout(function(){a._fade(f,d)},c)},stop:function(e){var c=(b.isFunction(e.before_close))?e.before_close:function(){};var f=(b.isFunction(e.after_close))?e.after_close:function(){};var d=b("#gritter-notice-wrapper");c(d);d.fadeOut(function(){b(this).remove();f()})},_str_replace:function(v,e,o,n){var k=0,h=0,t="",m="",g=0,q=0,l=[].concat(v),c=[].concat(e),u=o,d=c instanceof Array,p=u instanceof Array;u=[].concat(u);if(n){this.window[n]=0}for(k=0,g=u.length;k 1) { 34 | return; 35 | } 36 | 37 | event.preventDefault(); 38 | 39 | var touch = event.originalEvent.changedTouches[0], 40 | simulatedEvent = document.createEvent('MouseEvents'); 41 | 42 | // Initialize the simulated mouse event using the touch event's coordinates 43 | simulatedEvent.initMouseEvent( 44 | simulatedType, // type 45 | true, // bubbles 46 | true, // cancelable 47 | window, // view 48 | 1, // detail 49 | touch.screenX, // screenX 50 | touch.screenY, // screenY 51 | touch.clientX, // clientX 52 | touch.clientY, // clientY 53 | false, // ctrlKey 54 | false, // altKey 55 | false, // shiftKey 56 | false, // metaKey 57 | 0, // button 58 | null // relatedTarget 59 | ); 60 | 61 | // Dispatch the simulated event to the target element 62 | event.target.dispatchEvent(simulatedEvent); 63 | } 64 | 65 | /** 66 | * Handle the jQuery UI widget's touchstart events 67 | * @param {Object} event The widget element's touchstart event 68 | */ 69 | mouseProto._touchStart = function (event) { 70 | 71 | var self = this; 72 | 73 | // Ignore the event if another widget is already being handled 74 | if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) { 75 | return; 76 | } 77 | 78 | // Set the flag to prevent other widgets from inheriting the touch event 79 | touchHandled = true; 80 | 81 | // Track movement to determine if interaction was a click 82 | self._touchMoved = false; 83 | 84 | // Simulate the mouseover event 85 | simulateMouseEvent(event, 'mouseover'); 86 | 87 | // Simulate the mousemove event 88 | simulateMouseEvent(event, 'mousemove'); 89 | 90 | // Simulate the mousedown event 91 | simulateMouseEvent(event, 'mousedown'); 92 | }; 93 | 94 | /** 95 | * Handle the jQuery UI widget's touchmove events 96 | * @param {Object} event The document's touchmove event 97 | */ 98 | mouseProto._touchMove = function (event) { 99 | 100 | // Ignore event if not handled 101 | if (!touchHandled) { 102 | return; 103 | } 104 | 105 | // Interaction was not a click 106 | this._touchMoved = true; 107 | 108 | // Simulate the mousemove event 109 | simulateMouseEvent(event, 'mousemove'); 110 | }; 111 | 112 | /** 113 | * Handle the jQuery UI widget's touchend events 114 | * @param {Object} event The document's touchend event 115 | */ 116 | mouseProto._touchEnd = function (event) { 117 | 118 | // Ignore event if not handled 119 | if (!touchHandled) { 120 | return; 121 | } 122 | 123 | // Simulate the mouseup event 124 | simulateMouseEvent(event, 'mouseup'); 125 | 126 | // Simulate the mouseout event 127 | simulateMouseEvent(event, 'mouseout'); 128 | 129 | // If the touch interaction did not move, it should trigger a click 130 | if (!this._touchMoved) { 131 | 132 | // Simulate the click event 133 | simulateMouseEvent(event, 'click'); 134 | } 135 | 136 | // Unset the flag to allow other widgets to inherit the touch event 137 | touchHandled = false; 138 | }; 139 | 140 | /** 141 | * A duck punch of the $.ui.mouse _mouseInit method to support touch events. 142 | * This method extends the widget with bound touch event handlers that 143 | * translate touch events to mouse events and pass them to the widget's 144 | * original mouse event handling methods. 145 | */ 146 | mouseProto._mouseInit = function () { 147 | 148 | var self = this; 149 | 150 | // Delegate the touch handlers to the widget's element 151 | self.element 152 | .bind('touchstart', $.proxy(self, '_touchStart')) 153 | .bind('touchmove', $.proxy(self, '_touchMove')) 154 | .bind('touchend', $.proxy(self, '_touchEnd')); 155 | 156 | // Call the original $.ui.mouse init method 157 | _mouseInit.call(self); 158 | }; 159 | 160 | })(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/dialog/js/modernizr.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.6.1 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-inlinesvg-shiv-cssclasses-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes 3 | */ 4 | ;window.Modernizr=function(a,b,c){function B(a){j.cssText=a}function C(a,b){return B(m.join(a+";")+(b||""))}function D(a,b){return typeof a===b}function E(a,b){return!!~(""+a).indexOf(b)}function F(a,b){for(var d in a){var e=a[d];if(!E(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function G(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:D(f,"function")?f.bind(d||b):f}return!1}function H(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return D(b,"string")||D(b,"undefined")?F(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),G(e,b,c))}var d="2.6.1",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={svg:"http://www.w3.org/2000/svg"},r={},s={},t={},u=[],v=u.slice,w,x=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["­",'"].join(""),k.id=h,(l?k:m).innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},y=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=D(e[d],"function"),D(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),z={}.hasOwnProperty,A;!D(z,"undefined")&&!D(z.call,"undefined")?A=function(a,b){return z.call(a,b)}:A=function(a,b){return b in a&&D(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=v.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(v.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(v.call(arguments)))};return e}),r.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==q.svg};for(var I in r)A(r,I)&&(w=I.toLowerCase(),e[w]=r[I](),u.push((e[w]?"":"no-")+w));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)A(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},B(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.hasEvent=y,e.testProp=function(a){return F([a])},e.testAllProps=H,e.testStyles=x,e.prefixed=function(a,b,c){return b?H(a,b,c):H(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+u.join(" "):""),e}(this,this.document); -------------------------------------------------------------------------------- /src/main/webapp/dialog/js/retina.js: -------------------------------------------------------------------------------- 1 | // retina.js, a high-resolution image swapper (http://retinajs.com), v0.0.2 2 | 3 | (function(){function t(e){this.path=e;var t=this.path.split("."),n=t.slice(0,t.length-1).join("."),r=t[t.length-1];this.at_2x_path=n+"@2x."+r}function n(e){this.el=e,this.path=new t(this.el.getAttribute("src"));var n=this;this.path.check_2x_variant(function(e){e&&n.swap()})}var e=typeof exports=="undefined"?window:exports;e.RetinaImagePath=t,t.confirmed_paths=[],t.prototype.is_external=function(){return!!this.path.match(/^https?\:/i)&&!this.path.match("//"+document.domain)},t.prototype.check_2x_variant=function(e){var n,r=this;if(this.is_external())return e(!1);if(this.at_2x_path in t.confirmed_paths)return e(!0);n=new XMLHttpRequest,n.open("HEAD",this.at_2x_path),n.onreadystatechange=function(){return n.readyState!=4?e(!1):n.status>=200&&n.status<=399?(t.confirmed_paths.push(r.at_2x_path),e(!0)):e(!1)},n.send()},e.RetinaImage=n,n.prototype.swap=function(e){function n(){t.el.complete?(t.el.setAttribute("width",t.el.offsetWidth),t.el.setAttribute("height",t.el.offsetHeight),t.el.setAttribute("src",e)):setTimeout(n,5)}typeof e=="undefined"&&(e=this.path.at_2x_path);var t=this;n()},e.devicePixelRatio>1&&(window.onload=function(){var e=document.getElementsByTagName("img"),t=[],r,i;for(r=0;r li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | .fa-icon-rotate(@degrees, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 15 | -webkit-transform: rotate(@degrees); 16 | -ms-transform: rotate(@degrees); 17 | transform: rotate(@degrees); 18 | } 19 | 20 | .fa-icon-flip(@horiz, @vert, @rotation) { 21 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 22 | -webkit-transform: scale(@horiz, @vert); 23 | -ms-transform: scale(@horiz, @vert); 24 | transform: scale(@horiz, @vert); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 9 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 10 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/less/spinning.less: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | @-webkit-keyframes fa-spin { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | -webkit-transform: rotate(359deg); 16 | transform: rotate(359deg); 17 | } 18 | } 19 | 20 | @keyframes fa-spin { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | -webkit-transform: rotate(359deg); 27 | transform: rotate(359deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | @mixin fa-icon-rotate($degrees, $rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 15 | -webkit-transform: rotate($degrees); 16 | -ms-transform: rotate($degrees); 17 | transform: rotate($degrees); 18 | } 19 | 20 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 21 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 22 | -webkit-transform: scale($horiz, $vert); 23 | -ms-transform: scale($horiz, $vert); 24 | transform: scale($horiz, $vert); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_spinning.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | @-webkit-keyframes fa-spin { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | -webkit-transform: rotate(359deg); 16 | transform: rotate(359deg); 17 | } 18 | } 19 | 20 | @keyframes fa-spin { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | -webkit-transform: rotate(359deg); 27 | transform: rotate(359deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/main/webapp/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /src/main/webapp/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/webapp/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/webapp/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/webapp/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/webapp/images/Feedback_Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/Feedback_Button.png -------------------------------------------------------------------------------- /src/main/webapp/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/background.jpg -------------------------------------------------------------------------------- /src/main/webapp/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/images/gnostix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/gnostix.png -------------------------------------------------------------------------------- /src/main/webapp/images/gnostixWhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/gnostixWhite.png -------------------------------------------------------------------------------- /src/main/webapp/images/growl/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/confirm.png -------------------------------------------------------------------------------- /src/main/webapp/images/growl/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/error.png -------------------------------------------------------------------------------- /src/main/webapp/images/growl/gritter-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/gritter-light.png -------------------------------------------------------------------------------- /src/main/webapp/images/growl/gritter-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/gritter-long.png -------------------------------------------------------------------------------- /src/main/webapp/images/growl/gritter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/gritter.png -------------------------------------------------------------------------------- /src/main/webapp/images/growl/ie-spacer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/ie-spacer.gif -------------------------------------------------------------------------------- /src/main/webapp/images/growl/trees.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnostix/freeswitch-monitoring/302b92c9e872e1260289026f01d73a841523af41/src/main/webapp/images/growl/trees.jpg -------------------------------------------------------------------------------- /src/main/webapp/js/arcGauge.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.arcGauge = function (config) { 3 | this.each(function () { 4 | var gauge = {}; 5 | // set container 6 | gauge.container = $(this); 7 | // get size 8 | var size = Math.max(gauge.container.outerWidth(), gauge.container.outerHeight()) 9 | // settings 10 | gauge.settings = $.extend({}, { 11 | class: 'arc-gauge', 12 | width: size, 13 | height: size, 14 | startAngle: -120, 15 | endAngle: +120, 16 | thickness: 8, 17 | value: 0, 18 | minValue: 0, 19 | maxValue: 100, 20 | transition: 1000, 21 | colors: '#08c', 22 | bgColor: '#eee', 23 | onchange: function(value){} 24 | }, gauge.container.data(), config); 25 | // convert degrees to radians 26 | gauge.degToRad = function (degree) { 27 | return degree * Math.PI / 180; 28 | }; 29 | // setup radius 30 | gauge.settings.radius = Math.min(gauge.settings.width, gauge.settings.height) / 2; 31 | // convenience method to map data to start/end angles 32 | gauge.pie = d3.layout.pie() 33 | .startAngle(gauge.degToRad(gauge.settings.startAngle)) 34 | .endAngle(gauge.degToRad(gauge.settings.endAngle)) 35 | // setup the arc 36 | gauge.arc = d3.svg.arc() 37 | .innerRadius(gauge.settings.radius - gauge.settings.thickness) 38 | .outerRadius(gauge.settings.radius) 39 | .cornerRadius(gauge.settings.thickness); 40 | // get data to draw in the right format 41 | gauge.getData = function (data) { 42 | var value = Math.max(Math.min(data, gauge.settings.maxValue), gauge.settings.minValue); 43 | // compute the start angle 44 | var start = gauge.degToRad(gauge.settings.startAngle); 45 | // compute the end angle 46 | var end = start + value * (gauge.degToRad(gauge.settings.endAngle) - start) / gauge.settings.maxValue; 47 | // return data 48 | return [{ 49 | startAngle: start, 50 | endAngle: end, 51 | value: value 52 | }]; 53 | }; 54 | // get the value of the gauge 55 | gauge.get = function () { 56 | return gauge.svg.selectAll('path.arc-value').data()[0].value; 57 | }; 58 | // set a new value of the gauge 59 | gauge.set = function (data) { 60 | // animate chart 61 | gauge.svg.selectAll('path.arc-value') 62 | .data(gauge.getData(data)) 63 | .transition() 64 | .duration(gauge.settings.transition) 65 | .attrTween('d', gauge.arcTween) 66 | .style('fill', gauge.getColor); 67 | // trigger change 68 | if (gauge.settings.onchange) 69 | gauge.settings.onchange.call(this, data); 70 | }; 71 | // draw 72 | gauge.arcTween = function (d, i) { 73 | var isOuter = this == gauge.svg.select('path.arc-background')[0][0]; 74 | // compute the start angle 75 | var start = gauge.degToRad(gauge.settings.startAngle); 76 | // compute the end angle 77 | var end = gauge.degToRad(gauge.settings.endAngle); 78 | // interpolation 79 | var interpolate = d3.interpolate(this.previous || gauge.getData(gauge.settings[isOuter ? 'maxValue' : 'minValue'])[0], d); 80 | // cache value 81 | if (!isOuter) this.previous = d; 82 | // return 83 | return function (t) { 84 | return gauge.arc(interpolate(t)); 85 | }; 86 | }; 87 | // gradient color based 88 | gauge.getColor = function (d, i) { 89 | if (!$.isPlainObject(gauge.settings.colors)) 90 | return gauge.settings.colors; 91 | var color = gauge.settings.bgColor, percent = d.value / gauge.settings.maxValue; 92 | // looking for the color with the highest value in range 93 | for (var i in gauge.settings.colors) { 94 | if (percent < parseFloat(i)) break; 95 | color = gauge.settings.colors[i]; 96 | } 97 | return color; 98 | }; 99 | // initialization 100 | gauge.init = function () { 101 | // create the svg 102 | gauge.svg = d3.select(gauge.container[0]) 103 | .append('svg') 104 | .attr('class', gauge.settings.class) 105 | .attr('width', gauge.settings.width) 106 | .attr('height', gauge.settings.height) 107 | .append('g') 108 | .attr('transform', 'translate(' + gauge.settings.width / 2 + ',' + gauge.settings.height / 2 + ')'); 109 | // append the outer arc 110 | gauge.svg.append('path') 111 | .data(gauge.getData(gauge.settings.maxValue)) 112 | .attr('class', 'arc-background') 113 | .attr('fill', gauge.settings.bgColor) 114 | .transition() 115 | .attrTween('d', gauge.arcTween); 116 | // append the inner arc 117 | gauge.svg.append('path') 118 | .data(gauge.getData(gauge.settings.minValue)) 119 | .attr('class', 'arc-value') 120 | .attr('fill', gauge.settings.bgColor); 121 | // set first value 122 | gauge.set(gauge.settings.value); 123 | // export get function 124 | gauge.container[0].get = gauge.get; 125 | // export set function 126 | gauge.container[0].set = gauge.set; 127 | }; 128 | // start 129 | gauge.init(); 130 | }); 131 | // return 132 | return this; 133 | }; 134 | }(jQuery)); -------------------------------------------------------------------------------- /src/main/webapp/js/charts.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $('#heartbeat').highcharts({ 3 | chart: { 4 | type: "areaspline", 5 | //backgroundColor: "rgba(255, 255, 255, 0.65)", 6 | backgroundColor: "rgba(24, 11, 81, 0.85)", 7 | borderRadius: 5, 8 | borderColor: "#ffffff", 9 | borderWidth: 2 10 | }, 11 | title: { 12 | //text: "Live Monitoring" 13 | text: '', 14 | style: { 15 | display: 'none' 16 | } 17 | }, 18 | credits: { 19 | enabled: false 20 | }, 21 | plotOptions: { 22 | // series: { 23 | // stacking: 'normal' 24 | // } 25 | }, 26 | yAxis: { 27 | title: { 28 | text: null 29 | }, 30 | // type: 'logarithmic', 31 | // minorTickInterval: 0.1, //solution for error with zero values display 32 | //minorTickInterval: "auto", 33 | gridLineWidth: 0, 34 | minorGridLineWidth: 0 35 | }, 36 | colors: [ 37 | "#67a8e6", 38 | "#434348", 39 | "#4ac831", 40 | "#f7a35c", 41 | "#8085e9", 42 | "#f15c80", 43 | "#e4d354", 44 | "#2b908f", 45 | "#f45b5b", 46 | "#91e8e1" 47 | ], 48 | xAxis: { 49 | type: "datetime", 50 | tickPixelInterval: 100 51 | }, 52 | series: [ 53 | { 54 | index: 0, 55 | id:'rtp', 56 | dashStyle: "Solid", 57 | marker: { 58 | "enabled": false 59 | }, 60 | name: "RTP Quality", 61 | data: [ 62 | //[ 63 | //(new Date()).getTime(), 64 | //0 65 | //] 66 | ] 67 | }, 68 | { 69 | index: 1, 70 | id: 'asr', 71 | dashStyle: "Solid", 72 | marker: { 73 | "enabled": false 74 | }, 75 | name: "ASR", 76 | data: [ 77 | //[ 78 | //(new Date()).getTime(), 79 | //0 80 | //] 81 | ] 82 | }, 83 | { 84 | index: 2, 85 | id:'acd', 86 | dashStyle: "Solid", 87 | marker: { 88 | "enabled": false 89 | }, 90 | name: "ACD", 91 | data: [ 92 | //[ 93 | //(new Date()).getTime(), 94 | //0 95 | //] 96 | ] 97 | } 98 | 99 | ] 100 | }); 101 | 102 | $('#basicstats').highcharts({ 103 | chart: { 104 | type: "areaspline", 105 | //backgroundColor: "rgba(255, 255, 255, 0.65)", 106 | //backgroundColor: "rgba(0, 0, 52, 0.85)", 107 | backgroundColor: "rgba(24, 11, 81, 0.85)", 108 | borderRadius: 5, 109 | borderColor: "#ffffff", 110 | borderWidth: 2 111 | }, 112 | title: { 113 | //text: "Live Monitoring" 114 | text: '', 115 | style: { 116 | display: 'none' 117 | } 118 | }, 119 | credits: { 120 | enabled: false 121 | }, 122 | plotOptions: { 123 | // series: { 124 | // stacking: 'normal' 125 | // } 126 | }, 127 | yAxis: { 128 | title: { 129 | text: null 130 | }, 131 | //type: "logarithmic", 132 | // minorTickInterval: 0.1, 133 | // minorTickInterval: "auto", 134 | gridLineWidth: 0, 135 | minorGridLineWidth: 0 136 | }, 137 | colors: [ 138 | "#67a8e6", 139 | "#434348", 140 | "#4ac831", 141 | "#f7a35c", 142 | "#8085e9", 143 | "#f15c80", 144 | "#e4d354", 145 | "#2b908f", 146 | "#f45b5b", 147 | "#91e8e1" 148 | ], 149 | xAxis: { 150 | type: "datetime", 151 | tickPixelInterval: 100 152 | }, 153 | series: [ 154 | { 155 | index: 0, 156 | id: 'concurrentCalls', 157 | dashStyle: "Solid", 158 | marker: { 159 | "enabled": false 160 | }, 161 | name: "Concurrent Calls", 162 | data: [ 163 | //[ 164 | //(new Date()).getTime(), 165 | //0 166 | //] 167 | ] 168 | }, 169 | { 170 | index: 1, 171 | id: 'failedCalls', 172 | dashStyle: "Solid", 173 | marker: { 174 | "enabled": false 175 | }, 176 | name: "Failed Calls", 177 | data: [ 178 | //[ 179 | //(new Date()).getTime(), 180 | //0 181 | //] 182 | ] 183 | } 184 | ] 185 | }); 186 | 187 | $('#cpu').highcharts({ 188 | chart: { 189 | type: "area", 190 | //backgroundColor: "rgba(255, 255, 255, 0.65)", 191 | //backgroundColor: "rgba(0, 0, 52, 0.85)", 192 | backgroundColor: "rgba(24, 11, 81, 0.85)", 193 | borderRadius: 5, 194 | borderColor: "#ffffff", 195 | borderWidth: 2 196 | }, 197 | title: { 198 | //text: "Live Monitoring" 199 | text: '', 200 | style: { 201 | display: 'none' 202 | } 203 | }, 204 | credits: { 205 | enabled: false 206 | }, 207 | yAxis: { 208 | title: { 209 | text: null 210 | }, 211 | //type: "logarithmic", 212 | minorTickInterval: "auto", 213 | gridLineWidth: 0, 214 | minorGridLineWidth: 0, 215 | max:100 216 | }, 217 | colors: [ 218 | "#67a8e6", 219 | "#434348", 220 | "#4ac831", 221 | "#f7a35c", 222 | "#8085e9", 223 | "#f15c80", 224 | "#e4d354", 225 | "#2b908f", 226 | "#f45b5b", 227 | "#91e8e1" 228 | ], 229 | xAxis: { 230 | type: "datetime", 231 | tickPixelInterval: 100 232 | }, 233 | series: [ 234 | { 235 | index: 0, 236 | id: 'cpuUsage', 237 | dashStyle: "Solid", 238 | marker: { 239 | "enabled": false 240 | }, 241 | name: "CPU Usage (%)", 242 | data: [ 243 | //[ 244 | //(new Date()).getTime(), 245 | //0 246 | //] 247 | ] 248 | } 249 | ] 250 | }); 251 | }); 252 | -------------------------------------------------------------------------------- /src/main/webapp/js/export-csv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A small plugin for getting the CSV of a categorized chart 3 | */ 4 | (function (Highcharts) { 5 | 6 | 7 | var each = Highcharts.each; 8 | Highcharts.Chart.prototype.getCSV = function () { 9 | var columns = [], 10 | line, 11 | tempLine, 12 | csv = "", 13 | row, 14 | col, 15 | maxRows, 16 | options = (this.options.exporting || {}).csv || {}, 17 | 18 | // Options 19 | dateFormat = options.dateFormat || '%Y-%m-%d %H:%M:%S', 20 | itemDelimiter = options.itemDelimiter || ',', // use ';' for direct import to Excel 21 | lineDelimiter = options.lineDelimiter || '\n'; 22 | 23 | each (this.series, function (series) { 24 | if (series.options.includeInCSVExport !== false) { 25 | if (series.xAxis) { 26 | var xData = series.xData.slice(), 27 | xTitle = 'X values'; 28 | if (series.xAxis.isDatetimeAxis) { 29 | xData = Highcharts.map(xData, function (x) { 30 | return Highcharts.dateFormat(dateFormat, x) 31 | }); 32 | xTitle = 'DateTime'; 33 | } else if (series.xAxis.categories) { 34 | xData = Highcharts.map(xData, function (x) { 35 | return Highcharts.pick(series.xAxis.categories[x], x); 36 | }); 37 | xTitle = 'Category'; 38 | } 39 | columns.push(xData); 40 | columns[columns.length - 1].unshift(xTitle); 41 | } 42 | columns.push(series.yData.slice()); 43 | columns[columns.length - 1].unshift(series.name); 44 | } 45 | }); 46 | 47 | // Transform the columns to CSV 48 | maxRows = Math.max.apply(this, Highcharts.map(columns, function (col) { return col.length; })); 49 | for (row = 0; row < maxRows; row++) { 50 | line = []; 51 | for (col = 0; col < columns.length; col++) { 52 | line.push(columns[col][row]); 53 | } 54 | csv += line.join(itemDelimiter) + lineDelimiter; 55 | } 56 | 57 | return csv; 58 | }; 59 | 60 | // Now we want to add "Download CSV" to the exporting menu. We post the CSV 61 | // to a simple PHP script that returns it with a content-type header as a 62 | // downloadable file. 63 | // The source code for the PHP script can be viewed at 64 | // https://raw.github.com/highslide-software/highcharts.com/master/studies/csv-export/csv.php 65 | if (Highcharts.getOptions().exporting) { 66 | Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({ 67 | text: Highcharts.getOptions().lang.downloadCSV || "Download CSV", 68 | onclick: function () { 69 | Highcharts.post('http://www.highcharts.com/studies/csv-export/csv.php', { 70 | csv: this.getCSV() 71 | }); 72 | } 73 | }); 74 | } 75 | }(Highcharts)); -------------------------------------------------------------------------------- /src/main/webapp/js/init.js: -------------------------------------------------------------------------------- 1 | //initialise global vars. 2 | 3 | //var path='http://fs-moni.cloudapp.net:8080'; 4 | var path=''; 5 | //var pathP='http://fs-moni.cloudapp.net:8080'; 6 | 7 | function logout() { 8 | console.log("logout username::"); 9 | 10 | var logoutdata = { 11 | 12 | } 13 | $.ajax({ 14 | url: path+'/user/logout', 15 | type: 'POST', 16 | dataType: 'json', 17 | encode: true 18 | }) 19 | //contentType: "application/json", 20 | .done(function (data) { 21 | console.log("Logout success:" + JSON.stringify(data)); 22 | //save to localstorage (maybe check to save on sessionstorage 23 | // Store to local storage 24 | localStorage.removeItem("username"); 25 | localStorage.removeItem("uid"); 26 | localStorage.removeItem("name"); 27 | localStorage.removeItem("company"); 28 | localStorage.removeItem("role"); 29 | 30 | //ajaxComplete(result, un); 31 | }) 32 | .fail(function (data) { 33 | console.log("error:" + data.responseText); 34 | //te(data, un); 35 | }) 36 | } -------------------------------------------------------------------------------- /src/main/webapp/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2011, Klaus Hartl 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.opensource.org/licenses/GPL-2.0 9 | */ 10 | (function($) { 11 | $.cookie = function(key, value, options) { 12 | 13 | // key and at least value given, set cookie... 14 | if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) { 15 | options = $.extend({}, options); 16 | 17 | if (value === null || value === undefined) { 18 | options.expires = -1; 19 | } 20 | 21 | if (typeof options.expires === 'number') { 22 | var days = options.expires, t = options.expires = new Date(); 23 | t.setDate(t.getDate() + days); 24 | } 25 | 26 | value = String(value); 27 | 28 | return (document.cookie = [ 29 | encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value), 30 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 31 | options.path ? '; path=' + options.path : '', 32 | options.domain ? '; domain=' + options.domain : '', 33 | options.secure ? '; secure' : '' 34 | ].join('')); 35 | } 36 | 37 | // key and possibly options given, get cookie... 38 | options = value || {}; 39 | var decode = options.raw ? function(s) { return s; } : decodeURIComponent; 40 | 41 | var pairs = document.cookie.split('; '); 42 | for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) { 43 | if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined 44 | } 45 | return null; 46 | }; 47 | })(jQuery); 48 | -------------------------------------------------------------------------------- /src/main/webapp/js/plugins/flot/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | 23 | (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); 24 | 25 | (function ($) { 26 | var options = { }; // no options 27 | 28 | function init(plot) { 29 | function onResize() { 30 | var placeholder = plot.getPlaceholder(); 31 | 32 | // somebody might have hidden us and we can't plot 33 | // when we don't have the dimensions 34 | if (placeholder.width() == 0 || placeholder.height() == 0) 35 | return; 36 | 37 | plot.resize(); 38 | plot.setupGrid(); 39 | plot.draw(); 40 | } 41 | 42 | function bindEvents(plot, eventHolder) { 43 | plot.getPlaceholder().resize(onResize); 44 | } 45 | 46 | function shutdown(plot, eventHolder) { 47 | plot.getPlaceholder().unbind("resize", onResize); 48 | } 49 | 50 | plot.hooks.bindEvents.push(bindEvents); 51 | plot.hooks.shutdown.push(shutdown); 52 | } 53 | 54 | $.plot.plugins.push({ 55 | init: init, 56 | options: options, 57 | name: 'resize', 58 | version: '1.0' 59 | }); 60 | })(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/js/plugins/flot/jquery.flot.tooltip.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.flot.tooltip 3 | * 4 | * description: easy-to-use tooltips for Flot charts 5 | * version: 0.6.2 6 | * author: Krzysztof Urbas @krzysu [myviews.pl] 7 | * website: https://github.com/krzysu/flot.tooltip 8 | * 9 | * build on 2013-09-30 10 | * released under MIT License, 2012 11 | */ 12 | (function(t){var o={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},i=function(t){this.tipPosition={x:0,y:0},this.init(t)};i.prototype.init=function(o){function i(t){var o={};o.x=t.pageX,o.y=t.pageY,s.updateTooltipPosition(o)}function e(t,o,i){var e=s.getDomElement();if(i){var n;n=s.stringFormat(s.tooltipOptions.content,i),e.html(n),s.updateTooltipPosition({x:o.pageX,y:o.pageY}),e.css({left:s.tipPosition.x+s.tooltipOptions.shifts.x,top:s.tipPosition.y+s.tooltipOptions.shifts.y}).show(),"function"==typeof s.tooltipOptions.onHover&&s.tooltipOptions.onHover(i,e)}else e.hide().html("")}var s=this;o.hooks.bindEvents.push(function(o,n){s.plotOptions=o.getOptions(),s.plotOptions.tooltip!==!1&&void 0!==s.plotOptions.tooltip&&(s.tooltipOptions=s.plotOptions.tooltipOpts,s.getDomElement(),t(o.getPlaceholder()).bind("plothover",e),t(n).bind("mousemove",i))}),o.hooks.shutdown.push(function(o,s){t(o.getPlaceholder()).unbind("plothover",e),t(s).unbind("mousemove",i)})},i.prototype.getDomElement=function(){var o;return t("#flotTip").length>0?o=t("#flotTip"):(o=t("
").attr("id","flotTip"),o.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&o.css({background:"#fff","z-index":"100",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),o},i.prototype.updateTooltipPosition=function(o){var i=t("#flotTip").outerWidth()+this.tooltipOptions.shifts.x,e=t("#flotTip").outerHeight()+this.tooltipOptions.shifts.y;o.x-t(window).scrollLeft()>t(window).innerWidth()-i&&(o.x-=i),o.y-t(window).scrollTop()>t(window).innerHeight()-e&&(o.y-=e),this.tipPosition.x=o.x,this.tipPosition.y=o.y},i.prototype.stringFormat=function(t,o){var i=/%p\.{0,1}(\d{0,})/,e=/%s/,s=/%x\.{0,1}(?:\d{0,})/,n=/%y\.{0,1}(?:\d{0,})/;return"function"==typeof t&&(t=t(o.series.label,o.series.data[o.dataIndex][0],o.series.data[o.dataIndex][1],o)),o.series.percent!==void 0&&(t=this.adjustValPrecision(i,t,o.series.percent)),o.series.label!==void 0&&(t=t.replace(e,o.series.label)),this.isTimeMode("xaxis",o)&&this.isXDateFormat(o)&&(t=t.replace(s,this.timestampToDate(o.series.data[o.dataIndex][0],this.tooltipOptions.xDateFormat))),this.isTimeMode("yaxis",o)&&this.isYDateFormat(o)&&(t=t.replace(n,this.timestampToDate(o.series.data[o.dataIndex][1],this.tooltipOptions.yDateFormat))),"number"==typeof o.series.data[o.dataIndex][0]&&(t=this.adjustValPrecision(s,t,o.series.data[o.dataIndex][0])),"number"==typeof o.series.data[o.dataIndex][1]&&(t=this.adjustValPrecision(n,t,o.series.data[o.dataIndex][1])),o.series.xaxis.tickFormatter!==void 0&&(t=t.replace(s,o.series.xaxis.tickFormatter(o.series.data[o.dataIndex][0],o.series.xaxis))),o.series.yaxis.tickFormatter!==void 0&&(t=t.replace(n,o.series.yaxis.tickFormatter(o.series.data[o.dataIndex][1],o.series.yaxis))),t},i.prototype.isTimeMode=function(t,o){return o.series[t].options.mode!==void 0&&"time"===o.series[t].options.mode},i.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},i.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},i.prototype.timestampToDate=function(o,i){var e=new Date(o);return t.plot.formatDate(e,i)},i.prototype.adjustValPrecision=function(t,o,i){var e,s=o.match(t);return null!==s&&""!==RegExp.$1&&(e=RegExp.$1,i=i.toFixed(e),o=o.replace(t,i)),o};var e=function(t){new i(t)};t.plot.plugins.push({init:e,options:o,name:"tooltip",version:"0.6.1"})})(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/js/plugins/upload/jquery.fileupload-validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Validation Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define([ 19 | 'jquery', 20 | './jquery.fileupload-process' 21 | ], factory); 22 | } else if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory( 28 | window.jQuery 29 | ); 30 | } 31 | }(function ($) { 32 | 'use strict'; 33 | 34 | // Append to the default processQueue: 35 | $.blueimp.fileupload.prototype.options.processQueue.push( 36 | { 37 | action: 'validate', 38 | // Always trigger this action, 39 | // even if the previous action was rejected: 40 | always: true, 41 | // Options taken from the global options map: 42 | acceptFileTypes: '@', 43 | maxFileSize: '@', 44 | minFileSize: '@', 45 | maxNumberOfFiles: '@', 46 | disabled: '@disableValidation' 47 | } 48 | ); 49 | 50 | // The File Upload Validation plugin extends the fileupload widget 51 | // with file validation functionality: 52 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 53 | 54 | options: { 55 | /* 56 | // The regular expression for allowed file types, matches 57 | // against either file type or file name: 58 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 59 | // The maximum allowed file size in bytes: 60 | maxFileSize: 10000000, // 10 MB 61 | // The minimum allowed file size in bytes: 62 | minFileSize: undefined, // No minimal file size 63 | // The limit of files to be uploaded: 64 | maxNumberOfFiles: 10, 65 | */ 66 | 67 | // Function returning the current number of files, 68 | // has to be overriden for maxNumberOfFiles validation: 69 | getNumberOfFiles: $.noop, 70 | 71 | // Error and info messages: 72 | messages: { 73 | maxNumberOfFiles: 'Maximum number of files exceeded', 74 | acceptFileTypes: 'File type not allowed', 75 | maxFileSize: 'File is too large', 76 | minFileSize: 'File is too small' 77 | } 78 | }, 79 | 80 | processActions: { 81 | 82 | validate: function (data, options) { 83 | if (options.disabled) { 84 | return data; 85 | } 86 | var dfd = $.Deferred(), 87 | settings = this.options, 88 | file = data.files[data.index], 89 | fileSize; 90 | if (options.minFileSize || options.maxFileSize) { 91 | fileSize = file.size; 92 | } 93 | if ($.type(options.maxNumberOfFiles) === 'number' && 94 | (settings.getNumberOfFiles() || 0) + data.files.length > 95 | options.maxNumberOfFiles) { 96 | file.error = settings.i18n('maxNumberOfFiles'); 97 | } else if (options.acceptFileTypes && 98 | !(options.acceptFileTypes.test(file.type) || 99 | options.acceptFileTypes.test(file.name))) { 100 | file.error = settings.i18n('acceptFileTypes'); 101 | } else if (fileSize > options.maxFileSize) { 102 | file.error = settings.i18n('maxFileSize'); 103 | } else if ($.type(fileSize) === 'number' && 104 | fileSize < options.minFileSize) { 105 | file.error = settings.i18n('minFileSize'); 106 | } else { 107 | delete file.error; 108 | } 109 | if (file.error || data.files.error) { 110 | data.files.error = true; 111 | dfd.rejectWith(this, [data]); 112 | } else { 113 | dfd.resolveWith(this, [data]); 114 | } 115 | return dfd.promise(); 116 | } 117 | 118 | } 119 | 120 | }); 121 | 122 | })); -------------------------------------------------------------------------------- /src/main/webapp/js/plugins/upload/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin JS Example 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global $, window */ 13 | 14 | $(function () { 15 | 'use strict'; 16 | 17 | // Initialize the jQuery File Upload widget: 18 | $('#fileupload').fileupload({ 19 | // Uncomment the following to send cross-domain cookies: 20 | //xhrFields: {withCredentials: true}, 21 | url: 'server/php/' 22 | }); 23 | 24 | // Enable iframe cross-domain access via redirect option: 25 | $('#fileupload').fileupload( 26 | 'option', 27 | 'redirect', 28 | window.location.href.replace( 29 | /\/[^\/]*$/, 30 | '/cors/result.html?%s' 31 | ) 32 | ); 33 | 34 | if (window.location.hostname === 'blueimp.github.io') { 35 | // Demo settings: 36 | $('#fileupload').fileupload('option', { 37 | url: '//jquery-file-upload.appspot.com/', 38 | // Enable image resizing, except for Android and Opera, 39 | // which actually support image resizing, but fail to 40 | // send Blob objects via XHR requests: 41 | disableImageResize: /Android(?!.*Chrome)|Opera/ 42 | .test(window.navigator.userAgent), 43 | maxFileSize: 999000, 44 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i 45 | }); 46 | // Upload server status check for browsers with CORS support: 47 | if ($.support.cors) { 48 | $.ajax({ 49 | url: '//jquery-file-upload.appspot.com/', 50 | type: 'HEAD' 51 | }).fail(function () { 52 | $('
') 53 | .text('Upload server currently unavailable - ' + 54 | new Date()) 55 | .appendTo('#fileupload'); 56 | }); 57 | } 58 | } else { 59 | // Load existing files: 60 | $('#fileupload').addClass('fileupload-processing'); 61 | $.ajax({ 62 | // Uncomment the following to send cross-domain cookies: 63 | //xhrFields: {withCredentials: true}, 64 | url: $('#fileupload').fileupload('option', 'url'), 65 | dataType: 'json', 66 | context: $('#fileupload')[0] 67 | }).always(function () { 68 | $(this).removeClass('fileupload-processing'); 69 | }).done(function (result) { 70 | $(this).fileupload('option', 'done') 71 | .call(this, $.Event('done'), {result: result}); 72 | }); 73 | } 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /src/main/webapp/js/themes/customTheme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dark theme for Highcharts JS 3 | * @author Torstein Honsi 4 | */ 5 | 6 | // Load the fonts 7 | Highcharts.createElement('link', { 8 | // href: '//fonts.googleapis.com/css?family=Unica+One', 9 | rel: 'stylesheet', 10 | type: 'text/css' 11 | }, null, document.getElementsByTagName('head')[0]); 12 | 13 | Highcharts.theme = { 14 | colors: ["#2b908f", "#90ee7e", "#f45b5b", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", 15 | "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"], 16 | chart: { 17 | backgroundColor: { 18 | linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 }, 19 | stops: [ 20 | [0, '#2a2a2b'], 21 | [1, '#3e3e40'] 22 | ] 23 | }, 24 | style: { 25 | fontFamily: "'Unica One', sans-serif" 26 | }, 27 | plotBorderColor: '#606063' 28 | }, 29 | title: { 30 | style: { 31 | color: '#E0E0E3', 32 | textTransform: 'uppercase', 33 | fontSize: '20px' 34 | } 35 | }, 36 | subtitle: { 37 | style: { 38 | color: '#E0E0E3', 39 | textTransform: 'uppercase' 40 | } 41 | }, 42 | xAxis: { 43 | gridLineColor: '#707073', 44 | labels: { 45 | style: { 46 | color: '#E0E0E3' 47 | } 48 | }, 49 | lineColor: '#707073', 50 | minorGridLineColor: '#505053', 51 | tickColor: '#707073', 52 | title: { 53 | style: { 54 | color: '#A0A0A3' 55 | 56 | } 57 | } 58 | }, 59 | yAxis: { 60 | gridLineColor: '#707073', 61 | labels: { 62 | style: { 63 | color: '#E0E0E3' 64 | } 65 | }, 66 | lineColor: '#707073', 67 | minorGridLineColor: '#505053', 68 | tickColor: '#707073', 69 | tickWidth: 1, 70 | title: { 71 | style: { 72 | color: '#A0A0A3' 73 | } 74 | } 75 | }, 76 | tooltip: { 77 | backgroundColor: 'rgba(0, 0, 0, 0.85)', 78 | style: { 79 | color: '#F0F0F0' 80 | } 81 | }, 82 | plotOptions: { 83 | series: { 84 | borderWidth: 0, 85 | dataLabels: { 86 | color: '#B0B0B3' 87 | }, 88 | marker: { 89 | lineColor: '#333' 90 | } 91 | }, 92 | boxplot: { 93 | fillColor: '#505053' 94 | }, 95 | candlestick: { 96 | lineColor: 'white' 97 | }, 98 | errorbar: { 99 | color: 'white' 100 | } 101 | }, 102 | legend: { 103 | itemStyle: { 104 | color: '#E0E0E3' 105 | }, 106 | itemHoverStyle: { 107 | color: '#FFF' 108 | }, 109 | itemHiddenStyle: { 110 | color: '#606063' 111 | } 112 | }, 113 | credits: { 114 | style: { 115 | color: '#666' 116 | } 117 | }, 118 | labels: { 119 | style: { 120 | color: '#707073' 121 | } 122 | }, 123 | 124 | drilldown: { 125 | activeAxisLabelStyle: { 126 | color: '#F0F0F3' 127 | }, 128 | activeDataLabelStyle: { 129 | color: '#F0F0F3' 130 | } 131 | }, 132 | 133 | navigation: { 134 | buttonOptions: { 135 | symbolStroke: '#DDDDDD', 136 | theme: { 137 | fill: '#505053' 138 | } 139 | } 140 | }, 141 | 142 | // scroll charts 143 | rangeSelector: { 144 | buttonTheme: { 145 | fill: '#505053', 146 | stroke: '#000000', 147 | style: { 148 | color: '#CCC' 149 | }, 150 | states: { 151 | hover: { 152 | fill: '#707073', 153 | stroke: '#000000', 154 | style: { 155 | color: 'white' 156 | } 157 | }, 158 | select: { 159 | fill: '#000003', 160 | stroke: '#000000', 161 | style: { 162 | color: 'white' 163 | } 164 | } 165 | } 166 | }, 167 | inputBoxBorderColor: '#505053', 168 | inputStyle: { 169 | backgroundColor: '#333', 170 | color: 'silver' 171 | }, 172 | labelStyle: { 173 | color: 'silver' 174 | } 175 | }, 176 | 177 | navigator: { 178 | handles: { 179 | backgroundColor: '#666', 180 | borderColor: '#AAA' 181 | }, 182 | outlineColor: '#CCC', 183 | maskFill: 'rgba(255,255,255,0.1)', 184 | series: { 185 | color: '#7798BF', 186 | lineColor: '#A6C7ED' 187 | }, 188 | xAxis: { 189 | gridLineColor: '#505053' 190 | } 191 | }, 192 | 193 | scrollbar: { 194 | barBackgroundColor: '#808083', 195 | barBorderColor: '#808083', 196 | buttonArrowColor: '#CCC', 197 | buttonBackgroundColor: '#606063', 198 | buttonBorderColor: '#606063', 199 | rifleColor: '#FFF', 200 | trackBackgroundColor: '#404043', 201 | trackBorderColor: '#404043' 202 | }, 203 | 204 | // special colors for some of the 205 | legendBackgroundColor: 'rgba(0, 0, 0, 0.5)', 206 | background2: '#505053', 207 | dataLabelsColor: '#B0B0B3', 208 | textColor: '#C0C0C0', 209 | contrastTextColor: '#F0F0F3', 210 | maskColor: 'rgba(255,255,255,0.3)' 211 | }; 212 | 213 | // Apply the theme 214 | Highcharts.setOptions(Highcharts.theme); 215 | Highcharts.setOptions({ 216 | global: { 217 | useUTC: false 218 | } 219 | }); -------------------------------------------------------------------------------- /src/main/webapp/js/themes/darkTheme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dark theme for Highcharts JS 3 | * @author Torstein Honsi 4 | */ 5 | 6 | // Load the fonts 7 | Highcharts.createElement('link', { 8 | href: '//fonts.googleapis.com/css?family=Unica+One', 9 | rel: 'stylesheet', 10 | type: 'text/css' 11 | }, null, document.getElementsByTagName('head')[0]); 12 | 13 | Highcharts.theme = { 14 | colors: ["#2b908f", "#90ee7e", "#f45b5b", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", 15 | "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"], 16 | chart: { 17 | backgroundColor: { 18 | linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 }, 19 | stops: [ 20 | [0, '#2a2a2b'], 21 | [1, '#3e3e40'] 22 | ] 23 | }, 24 | style: { 25 | fontFamily: "'Unica One', sans-serif" 26 | }, 27 | plotBorderColor: '#606063' 28 | }, 29 | title: { 30 | style: { 31 | color: '#E0E0E3', 32 | textTransform: 'uppercase', 33 | fontSize: '20px' 34 | } 35 | }, 36 | subtitle: { 37 | style: { 38 | color: '#E0E0E3', 39 | textTransform: 'uppercase' 40 | } 41 | }, 42 | xAxis: { 43 | gridLineColor: '#707073', 44 | labels: { 45 | style: { 46 | color: '#E0E0E3' 47 | } 48 | }, 49 | lineColor: '#707073', 50 | minorGridLineColor: '#505053', 51 | tickColor: '#707073', 52 | title: { 53 | style: { 54 | color: '#A0A0A3' 55 | 56 | } 57 | } 58 | }, 59 | yAxis: { 60 | gridLineColor: '#707073', 61 | labels: { 62 | style: { 63 | color: '#E0E0E3' 64 | } 65 | }, 66 | lineColor: '#707073', 67 | minorGridLineColor: '#505053', 68 | tickColor: '#707073', 69 | tickWidth: 1, 70 | title: { 71 | style: { 72 | color: '#A0A0A3' 73 | } 74 | } 75 | }, 76 | tooltip: { 77 | backgroundColor: 'rgba(0, 0, 0, 0.85)', 78 | style: { 79 | color: '#F0F0F0' 80 | } 81 | }, 82 | plotOptions: { 83 | series: { 84 | dataLabels: { 85 | color: '#B0B0B3' 86 | }, 87 | marker: { 88 | lineColor: '#333' 89 | } 90 | }, 91 | boxplot: { 92 | fillColor: '#505053' 93 | }, 94 | candlestick: { 95 | lineColor: 'white' 96 | }, 97 | errorbar: { 98 | color: 'white' 99 | } 100 | }, 101 | legend: { 102 | itemStyle: { 103 | color: '#E0E0E3' 104 | }, 105 | itemHoverStyle: { 106 | color: '#FFF' 107 | }, 108 | itemHiddenStyle: { 109 | color: '#606063' 110 | } 111 | }, 112 | credits: { 113 | style: { 114 | color: '#666' 115 | } 116 | }, 117 | labels: { 118 | style: { 119 | color: '#707073' 120 | } 121 | }, 122 | 123 | drilldown: { 124 | activeAxisLabelStyle: { 125 | color: '#F0F0F3' 126 | }, 127 | activeDataLabelStyle: { 128 | color: '#F0F0F3' 129 | } 130 | }, 131 | 132 | navigation: { 133 | buttonOptions: { 134 | symbolStroke: '#DDDDDD', 135 | theme: { 136 | fill: '#505053' 137 | } 138 | } 139 | }, 140 | 141 | // scroll charts 142 | rangeSelector: { 143 | buttonTheme: { 144 | fill: '#505053', 145 | stroke: '#000000', 146 | style: { 147 | color: '#CCC' 148 | }, 149 | states: { 150 | hover: { 151 | fill: '#707073', 152 | stroke: '#000000', 153 | style: { 154 | color: 'white' 155 | } 156 | }, 157 | select: { 158 | fill: '#000003', 159 | stroke: '#000000', 160 | style: { 161 | color: 'white' 162 | } 163 | } 164 | } 165 | }, 166 | inputBoxBorderColor: '#505053', 167 | inputStyle: { 168 | backgroundColor: '#333', 169 | color: 'silver' 170 | }, 171 | labelStyle: { 172 | color: 'silver' 173 | } 174 | }, 175 | 176 | navigator: { 177 | handles: { 178 | backgroundColor: '#666', 179 | borderColor: '#AAA' 180 | }, 181 | outlineColor: '#CCC', 182 | maskFill: 'rgba(255,255,255,0.1)', 183 | series: { 184 | color: '#7798BF', 185 | lineColor: '#A6C7ED' 186 | }, 187 | xAxis: { 188 | gridLineColor: '#505053' 189 | } 190 | }, 191 | 192 | scrollbar: { 193 | barBackgroundColor: '#808083', 194 | barBorderColor: '#808083', 195 | buttonArrowColor: '#CCC', 196 | buttonBackgroundColor: '#606063', 197 | buttonBorderColor: '#606063', 198 | rifleColor: '#FFF', 199 | trackBackgroundColor: '#404043', 200 | trackBorderColor: '#404043' 201 | }, 202 | 203 | // special colors for some of the 204 | legendBackgroundColor: 'rgba(0, 0, 0, 0.5)', 205 | background2: '#505053', 206 | dataLabelsColor: '#B0B0B3', 207 | textColor: '#C0C0C0', 208 | contrastTextColor: '#F0F0F3', 209 | maskColor: 'rgba(255,255,255,0.3)' 210 | }; 211 | 212 | // Apply the theme 213 | Highcharts.setOptions(Highcharts.theme); -------------------------------------------------------------------------------- /src/test/scala/gr/gnostix/freeswitch/CentralServletSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch 20 | 21 | import gr.gnostix.freeswitch.servlets.LoginServlet 22 | import org.scalatra.test.specs2._ 23 | 24 | // For more on Specs2, see http://etorreborre.github.com/specs2/guide/org.specs2.guide.QuickStart.html 25 | class CentralServletSpec extends ScalatraSpec { def is = 26 | "GET / on CentralServlet" ^ 27 | "should return status 200" ! root200^ 28 | end 29 | 30 | addServlet(classOf[LoginServlet], "/*") 31 | 32 | def root200 = get("/") { 33 | status must_== 200 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/scala/gr/gnostix/freeswitch/actors/TestFailedCallsActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Alexandros Pappas p_alx hotmail com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 17 | */ 18 | 19 | package gr.gnostix.freeswitch.actors 20 | 21 | import gr.gnostix.freeswitch.actors.ActorsProtocol.{GetTotalFailedCalls, GetFailedCalls} 22 | import org.scalatest.BeforeAndAfterAll 23 | import org.scalatest.WordSpecLike 24 | import org.scalatest.Matchers 25 | import akka.actor.{Props, ActorSystem} 26 | import akka.testkit.{TestActors, DefaultTimeout, ImplicitSender, TestKit} 27 | import scala.concurrent.duration._ 28 | import scala.language.postfixOps 29 | 30 | /** 31 | * Created by rebel on 24/8/15. 32 | */ 33 | class TestFailedCallsActor 34 | extends TestKit(ActorSystem("TestKitUsageSpec")) 35 | with DefaultTimeout with ImplicitSender 36 | with WordSpecLike with Matchers with BeforeAndAfterAll { 37 | 38 | override def afterAll: Unit = { 39 | TestKit.shutdownActorSystem(system) 40 | } 41 | 42 | /* val failedCallsActor = system.actorOf(Props(classOf[FailedCallsActor])) 43 | 44 | "this test should" should { 45 | " return the list of Calls " in { 46 | within(5000 millis) { 47 | failedCallsActor ! GetFailedCalls 48 | expectMsg(List()) 49 | } 50 | } 51 | } 52 | 53 | 54 | "this is for number of failed calls" should { 55 | " return an Int of calls " in { 56 | within(5000 millis) { 57 | failedCallsActor ! GetTotalFailedCalls 58 | expectMsg(TotalFailedCalls(0)) 59 | } 60 | } 61 | }*/ 62 | } 63 | 64 | --------------------------------------------------------------------------------