Commit 2c768900 authored by Holger Bergunde's avatar Holger Bergunde Committed by holger.bergunde

OF-490 added various features. set to version 1.1.0 alpha

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@12947 b35dd754-fafc-0310-a699-88a17e54d16e
parent 635a188b
......@@ -2,7 +2,7 @@
<html>
<head>
<title>Remote Roster Plugin Changelog</title>
<title>GoJara Plugin Changelog</title>
<style type="text/css">
BODY {
font-size : 100%;
......@@ -41,9 +41,19 @@
<body>
<h1>
Remote Roster Plugin Changelog
GoJara Plugin Changelog
</h1>
<p><b>1.1 Alpha</b> -- Jan 6, 2012</p>
<ul>
<li>Added feature to limit gateways to specific user group</li>
<li>Capture packets to create statistics</li>
<li>Added live logging</li>
<li>Renamed to GoJara</li>
</ul>
<p><b>1.0 Alpha</b> -- Dec 12, 2011</p>
<ul>
......
......@@ -4,11 +4,13 @@
<class>org.jivesoftware.openfire.plugin.RemoteRosterPlugin</class>
<name>Remote Roster</name>
<name>GoJara</name>
<description>ProtoXEP-xxxx: Remote Roster Management support</description>
<author>Holger Bergunde and Daniel Henninger</author>
<version>1.0.0 Alpha</version>
<date>12/06/2011</date>
<version>1.1.0 Alpha</version>
<date>01/06/2012</date>
<databaseKey>gojara</databaseKey>
<databaseVersion>0</databaseVersion>
<minServerVersion>3.7.0</minServerVersion>
<!-- Admin console entries -->
......
......@@ -2,7 +2,7 @@
<html>
<head>
<title>Remote Roster Plugin Readme</title>
<title>GoJara Plugin Readme</title>
<style type="text/css">
BODY {
font-size : 100%;
......@@ -52,7 +52,7 @@
<h1>
Remote Roster Plugin Readme
GoJara Plugin Readme
</h1>
<h2>Overview</h2>
......@@ -78,7 +78,7 @@ remoteRoster.jar file over the existing file.</p>
<h2>Configuration</h2>
<p>The Remote Roster plugin can be configured under "Server"-"Server Settings"-"Remote Roster".</p>
<p>The GoJara plugin can be configured under "Server"-"Server Settings"-"GoJara".</p>
</body>
</html>
\ No newline at end of file
CREATE TABLE ofGojaraStatistics (
logID bigint(20) NOT NULL AUTO_INCREMENT,
messageDate bigint(20) NOT NULL,
messageType tinytext NOT NULL,
fromJID text NOT NULL,
toJID text NOT NULL,
component text NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
CREATE TABLE ofGojaraStatistics (
logID Integer Identity NOT NULL,
messageDate bigint(20) NOT NULL,
messageType VARCHAR(255) NOT NULL,
fromJID VARCHAR(255) NOT NULL,
toJID VARCHAR(255) NOT NULL,
component VARCHAR(255) NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
CREATE TABLE ofGojaraStatistics (
logID bigint(20) NOT NULL AUTO_INCREMENT,
messageDate bigint(20) NOT NULL,
messageType tinytext NOT NULL,
fromJID text NOT NULL,
toJID text NOT NULL,
component text NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
CREATE TABLE ofGojaraStatistics (
logID bigint(20) NOT NULL AUTO_INCREMENT,
messageDate bigint(20) NOT NULL,
messageType tinytext NOT NULL,
fromJID text NOT NULL,
toJID text NOT NULL,
component text NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
CREATE TABLE ofGojaraStatistics (
logID bigint(20) NOT NULL AUTO_INCREMENT,
messageDate bigint(20) NOT NULL,
messageType tinytext NOT NULL,
fromJID text NOT NULL,
toJID text NOT NULL,
component text NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
CREATE TABLE ofGojaraStatistics (
logID bigint(20) NOT NULL AUTO_INCREMENT,
messageDate bigint(20) NOT NULL,
messageType tinytext NOT NULL,
fromJID text NOT NULL,
toJID text NOT NULL,
component text NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
CREATE TABLE ofGojaraStatistics (
logID bigint(20) NOT NULL AUTO_INCREMENT,
messageDate bigint(20) NOT NULL,
messageType tinytext NOT NULL,
fromJID text NOT NULL,
toJID text NOT NULL,
component text NOT NULL,
PRIMARY KEY (logID)
);
INSERT INTO ofVersion (name, version) VALUES ('gojara', 0);
rr.summary.title=Remote Roster
\ No newline at end of file
rr.summary.title=GoJara
\ No newline at end of file
......@@ -46,6 +46,8 @@ public class RemoteRosterPlugin implements Plugin {
pluginManager = manager;
manageExternalComponents();
listenToSettings();
}
private void manageExternalComponents()
......
package org.jivesoftware.openfire.plugin.database;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.log4j.Logger;
import org.jivesoftware.database.DbConnectionManager;
public class DatabaseManager {
private static Logger Log = Logger.getLogger(DatabaseManager.class);
private static volatile DatabaseManager _myself;
private static final String COUNT_LOG_ENTRIES = "SELECT count(*) FROM ofGojaraStatistics";
private static final String COUNT_PACKAGES_ODLER = "SELECT count(*) FROM `ofGojaraStatistics` WHERE messageType like ? AND component = ? AND messageDate > ?";
private static final String GET_ALL_LOGS = "SELECT * FROM ofGojaraStatistics ORDER BY logID desc";
// private static final String MOST_ACTIVE = "SELECT toJID, count(logID) AS counter FROM `ofGojaraStatistics` GROUP by toJID ORDER BY counter DESC";
private static final String ADD_NEW_LOG = "INSERT INTO ofGojaraStatistics(messageDate, messageType, fromJID, toJId, component) VALUES(?,?,?,?,?)";
private static final String CLEAN_OLD_DATA = "DELETE FROM `ofGojaraStatistics` WHERE messageDate < ?";
private static final String GET_LOGS_DATE_LIMIT_COMPONENT = "SELECT * FROM `ofGojaraStatistics` WHERE messageDate > ? AND component LIKE ? ORDER BY messageDate DESC LIMIT ?";
private DatabaseManager() {
TimerTask task = new TimerTask() {
@Override
public void run() {
cleanOldLogEntries();
}
};
Timer timer = new Timer();
timer.schedule(task, 2 * 60 * 1000, 2 * 60 * 1000);
}
/**
* Singleton for Databasemanager, because we only need one.
*
* @return the Databasemanager
*/
public static DatabaseManager getInstance() {
if (_myself == null) {
synchronized (DatabaseManager.class) {
if (_myself == null)
_myself = new DatabaseManager();
}
}
return _myself;
}
/**
* Returns a list of LogEntry's ordered by date desc
*
* @param olderThan
* unix timestamp in ms
* @param limit
* num of rows max
* @param component
* the specified subdomain of the logged component
* @return Collection of LogEntry
*/
public Collection<LogEntry> getLogsByDateAndLimit(long olderThan, int limit, String component) {
List<LogEntry> result = new ArrayList<LogEntry>();
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(GET_LOGS_DATE_LIMIT_COMPONENT);
pstmt.setLong(1, olderThan);
pstmt.setString(2, component);
pstmt.setInt(3, limit);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String from = rs.getString(4);
String to = rs.getString(5);
String type = rs.getString(3);
long date = rs.getLong(2);
LogEntry res = new LogEntry(from, to, type, date);
result.add(res);
}
pstmt.close();
} catch (SQLException sqle) {
Log.error(sqle);
} finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return result;
}
public void cleanOldLogEntries() {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(CLEAN_OLD_DATA);
pstmt.setLong(1, System.currentTimeMillis() - 60 * 60 * 1000);
int rows = pstmt.executeUpdate();
Log.debug("Cleaned statistic database. Affected rows: " + rows);
pstmt.close();
} catch (SQLException sqle) {
Log.error(sqle);
} finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
public void addNewLogEntry(String component, String type, String from, String to) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ADD_NEW_LOG);
pstmt.setLong(1, System.currentTimeMillis());
pstmt.setString(2, type);
pstmt.setString(3, from);
pstmt.setString(4, to);
pstmt.setString(5, component);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException sqle) {
Log.error(sqle);
} finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
public List<String> getAllLogs() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<String> _result = new ArrayList<String>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(GET_ALL_LOGS);
rs = pstmt.executeQuery();
while (rs.next()) {
String from = rs.getString(4);
String to = rs.getString(5);
String type = rs.getString(3);
String component = rs.getString(6);
Timestamp date = rs.getTimestamp(2);
String res = "From: " + from + " To: " + to + " Type: " + type + " Timestamp: " + date.toString()
+ "Component: " + component;
_result.add(res);
}
}
catch (SQLException sqle) {
Log.error(sqle);
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return _result;
}
public int getLogSize() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(COUNT_LOG_ENTRIES);
rs = pstmt.executeQuery();
rs.next();
return rs.getInt(1);
} catch (SQLException sqle) {
Log.error(sqle);
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return 0;
}
public int getPacketCount(String component, Class packetClass) {
return getPacketCountOlderThan(component, packetClass, 60);
}
public int getPacketCountOlderThan(String component, Class packetClass, int minutes) {
String classname = packetClass.getName();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(COUNT_PACKAGES_ODLER);
pstmt.setString(1, "%" + classname + "");
pstmt.setString(2, component);
pstmt.setLong(3, System.currentTimeMillis() - minutes * 60 * 1000);
rs = pstmt.executeQuery();
rs.next();
return rs.getInt(1);
} catch (SQLException sqle) {
Log.error(sqle);
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return 0;
}
}
package org.jivesoftware.openfire.plugin.database;
/**
*
* This class represents a log entry for the gojara plugin
*
*
* @author holger.bergunde
*
*/
public class LogEntry {
private String _from;
private String _to;
private String _type;
private long _date;
public LogEntry(String from, String to, String type, long date) {
_from = from;
_to = to;
_type = type;
_date = date;
}
public String getFrom() {
return _from;
}
public String getTo() {
return _to;
}
public String getType() {
return _type;
}
/**
* Date of logentry
*
* @return date in unixtimestamp milliseconds
*/
public long getDate() {
return _date;
}
}
......@@ -3,11 +3,13 @@ package org.jivesoftware.openfire.plugin.interceptor;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketInterceptor;
public abstract class AbstractInterceptorHandler {
private static Logger Log = Logger.getLogger(AbstractInterceptorHandler.class);
private String _subdomain;
private boolean _isRunning = false;
private Set<PacketInterceptor> _interceptors = new HashSet<PacketInterceptor>();
......@@ -36,7 +38,7 @@ public abstract class AbstractInterceptorHandler {
public void start()
{
System.out.println("Start handling message interceptors for gateway " + _subdomain);
Log.debug("Start handling message interceptors for gateway " + _subdomain);
_isRunning = true;
for (PacketInterceptor interceptor : _interceptors) {
_iManager.addInterceptor(interceptor);
......@@ -45,7 +47,7 @@ public abstract class AbstractInterceptorHandler {
public void stop()
{
System.out.println("Stop handling message interceptors for gateway " + _subdomain);
Log.debug("Stop handling message interceptors for gateway " + _subdomain);
if (!_isRunning)
return;
_isRunning = false;
......
......@@ -13,13 +13,13 @@ import org.jivesoftware.openfire.session.Session;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Packet;
public class DiscoPackageInterceptor implements PacketInterceptor {
public class DiscoPackageInterceptorHandler implements PacketInterceptor {
private PermissionManager _permissions;
private String _subDomain;
private String _host;
public DiscoPackageInterceptor(String subdomain) {
public DiscoPackageInterceptorHandler(String subdomain) {
_permissions = new PermissionManager();
_subDomain = subdomain;
XMPPServer server = XMPPServer.getInstance();
......
......@@ -3,9 +3,11 @@ package org.jivesoftware.openfire.plugin.interceptor;
public class GatewayInterceptorHandler extends AbstractInterceptorHandler {
public GatewayInterceptorHandler(String subdomain) {
super(subdomain);
DiscoPackageInterceptor discoInterceptor = new DiscoPackageInterceptor(subdomain);
DiscoPackageInterceptorHandler discoInterceptor = new DiscoPackageInterceptorHandler(subdomain);
RemotePackageInterceptor remoteRosterInterceptor = new RemotePackageInterceptor(subdomain);
StatisticPackageInterceptor statisticInterceptor = new StatisticPackageInterceptor(subdomain);
addInterceptor(remoteRosterInterceptor);
addInterceptor(discoInterceptor);
addInterceptor(statisticInterceptor);
}
}
package org.jivesoftware.openfire.plugin.interceptor;
import org.jivesoftware.openfire.interceptor.PacketInterceptor;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.plugin.database.DatabaseManager;
import org.jivesoftware.openfire.session.Session;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
public class StatisticPackageInterceptor implements PacketInterceptor {
private String _subdomain;
private DatabaseManager _db;
public StatisticPackageInterceptor(String subdomain) {
_subdomain = subdomain;
_db = DatabaseManager.getInstance();
}
@Override
public void interceptPacket(Packet packet, Session session, boolean incoming, boolean processed)
throws PacketRejectedException {
JID from = packet.getFrom();
JID to = packet.getTo();
if (from != null && to != null && processed && incoming) {
if (from.toString().contains(_subdomain) || to.toString().contains(_subdomain)) {
/*
* Spectrum sends a Ping to itself through the server to check
* if the server is alive. We ignore that for statistics
*/
if (to.toString().equals(from.toString()) && to.toString().equals(_subdomain))
return;
String type = packet.getClass().getName();
_db.addNewLogEntry(_subdomain, type, from.toString(), to.toString());
}
}
}
}
package org.jivesoftware.openfire.plugin.servlet;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.group.GroupManager;
public class SearchGroupServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String param = req.getParameter("search");
Element root = new DefaultElement("result");
if (param != null && param.length() > 0) {
GroupManager manager = GroupManager.getInstance();
Collection<Group> groups = manager.getGroups();
for (Group gr : groups) {
if (gr.getName().startsWith(param)) {
root.addElement("item").addText(gr.getName());
}
}
}
resp.getOutputStream().write(root.asXML().getBytes());
resp.getOutputStream().close();
}
}
package org.jivesoftware.openfire.plugin.servlet;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jivesoftware.openfire.plugin.database.DatabaseManager;
import org.jivesoftware.openfire.plugin.database.LogEntry;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class StatisticsServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = -6872070494892162304L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
DatabaseManager db = DatabaseManager.getInstance();
String component = req.getParameter("component");
String fromString = req.getParameter("date");
/*
* { "packets": [ { "type": "IQ", "from": "holger" }, { "type":
* "Message", "from": "babett", "to": "holger", "date": "1243235344" } ]
* }
*/
int msgCnt = 0;
int iqCnt = 0;
int presenceCnt = 0;
int rosterCnt = 0;
JSONObject root = new JSONObject();
if (component != null && fromString != null) {
JSONArray packetArray = new JSONArray();
try {
root.put("packets", packetArray);
int limit = 40;
long from = Long.valueOf(fromString);
Collection<LogEntry> queryResult = db.getLogsByDateAndLimit(from, limit, component);
for (LogEntry entry : queryResult) {
JSONObject packet = new JSONObject();
packet.put("type", entry.getType()).put("to", entry.getTo()).put("from", entry.getFrom())
.put("date", entry.getDate());
packetArray.put(packet);
if (entry.getType().contains("IQ")) {
iqCnt++;
} else if (entry.getType().contains("Message")) {
msgCnt++;
} else if (entry.getType().contains("Roster")) {
rosterCnt++;
} else if (entry.getType().contains("Presence")) {
presenceCnt++;
}
}
JSONObject numbers = new JSONObject();
numbers.put("msg", msgCnt);
numbers.put("iq", iqCnt);
numbers.put("presence", presenceCnt);
numbers.put("roster", rosterCnt);
root.put("numbers", numbers);
} catch (JSONException e1) {
e1.printStackTrace();
}
}
resp.getOutputStream().write(root.toString().getBytes());
resp.getOutputStream().close();
}
}
<?xml version='1.0' encoding='ISO-8859-1'?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<servlet-name>stats</servlet-name>
<servlet-class>org.jivesoftware.openfire.plugin.servlet.StatisticsServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>searchGroup</servlet-name>
<servlet-class>org.jivesoftware.openfire.plugin.servlet.SearchGroupServlet</servlet-class>
</servlet>
<!-- Servlet mappings -->
<servlet-mapping>
<servlet-name>stats</servlet-name>
<url-pattern>/stats</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>searchGroup</servlet-name>
<url-pattern>/groups</url-pattern>
</servlet-mapping>
</web-app>
@CHARSET "ISO-8859-1";
.header {
width: 100%;
background-color: gray;
color: white;
padding: 3px;
font-weight: bold;
font-size: 14;
}
.graph {
margin-top: 10px;
width: 100%;
height: 300px;
margin-bottom: 25px;
}
.div-main {
background: #F5F5F5 url(../../../images/jive-body-contentbox-bg.gif) repeat-x
scroll center top;
border: 1px solid #DCDCDC;
margin: 5px;
padding: 10px 10px;
float: left;
}
body table {
font-family: arial, helvetica, sans-serif;
font-size: 13;
}
#logTable {
background-color: #B3B3B3;
width: 100%;
border-spacing: 1px;
border: 1px;
border-style: solid;
border-color: #383838;
}
#logTable thead th {
border-bottom: 1px;
border-style: solid;
text-align: center;
width: 25%;
}
#logTable tfoot tr {
font-weight: bold;
text-align: center;
}
\ No newline at end of file
@CHARSET "ISO-8859-1";
.suggest_link {
background-color: #FFFFFF;
padding: 2px 6px 2px 6px;
......@@ -11,52 +10,100 @@
padding: 2px 6px 2px 6px;
}
.grouptable {
border-spacing:0px;
}
#search_suggest {
position: absolute;
background-color: #FFFFFF;
text-align: left;
border: 1px solid #000000;
display:none;
display: none;
}
.slider {
margin-left:1%;
padding-top:0px;
background-color:rgb(230,230,230);
border-color:#CCCCCC rgb(204, 204, 204);
color:white;
width:58%;
margin-left: 48px;
padding-top: 0px;
background-color: rgb(230, 230, 230);
border-color: #CCCCCC rgb(204, 204, 204);
color: white;
width: 550px;
border-style: solid;
border-width: 1px;
border-top:none;
border-top: none;
display: none;
}
}
.sildeHeader {
background-color: #D4D2D2;
color: #000000;
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
border-bottom-color: gray;
border-bottom-width: 1px;
border-bottom-style: solid;
font-weight: bold;
}
.logtable {
border-left:6px;
width: 550px;
height: 100%;
border-spacing: 0px;
}
#loghead{
background-color:#DEDCDC;
font-weight: bold;
color:#404040;
}
#logodd {
background-color:#F0F0F0;
}
#logeven {
background-color:#E9E9E9;
}
#logfoot {
background-color:#DEDCDC;
}
div.graph {
width: 250px;
height: 150px;
float: right;
}
.gatewayHeader {
width:60%;
background-color:#EEEEEE;
border-color:#CCCCCC rgb(204, 204, 204);
border-style:solid solid solid;
border-width:1px 1px 1px;
width: 600px;
background-color: #EEEEEE;
border-color: #CCCCCC rgb(204, 204, 204);
border-style: solid solid solid;
border-width: 1px 1px 1px;
}
.gatewayName {
padding-left: 10px;
}
.gatewayIcon {
.gatewayIcon {
border-left: 1px;
}
}
.gatewayCheckbox {
width: 20px;
}
.configTable {
width: 80%;
}
width: 100%;
border-spacing:0px;
}
.configTable1Column {
width: 50%;
......@@ -68,32 +115,44 @@
}
#permissions {
margin-left:1%;
padding-top:0px;
background-color:rgb(230,230,230);
border-color:#CCCCCC rgb(204, 204, 204);
color:white;
width:58%;
margin-left: 1%;
padding-top: 0px;
background-color: rgb(230, 230, 230);
border-color: #CCCCCC rgb(204, 204, 204);
color: white;
width: 58%;
border-style: solid;
border-width: 1px;
border-top:none;
border-top: none;
display: none;
}
.permissionTableColumn
{
.permissionTableColumn {
width: 50%;
}
.ajaxloading {
padding-left: 4px;
padding-top: 3px;
}
}
.permissionTitle {
color:black;
size:+2;
color: black;
size: +2;
}
dt {
width: 50px;
}
.hbg-bar {
padding-left: 5px;
background-color: #369;
color: #fff;
font-weight: bold;
}
.hbg-title {
text-align: center;
font-weight: bold;
}
\ No newline at end of file
if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!i.namespaces.g_o_){i.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!i.styleSheets.ex_canvas_){var Z=i.createStyleSheet();Z.owningElement.id="ex_canvas_";Z.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}r(document);var E={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=Z||document;i.createElement("canvas");i.attachEvent("onreadystatechange",G(this.init_,this,i))}},init_:function(m){var j=m.getElementsByTagName("canvas");for(var Z=0;Z<j.length;Z++){this.initElement(j[Z])}},initElement:function(i){if(!i.getContext){i.getContext=T;r(i.ownerDocument);i.innerHTML="";i.attachEvent("onpropertychange",S);i.attachEvent("onresize",w);var Z=i.attributes;if(Z.width&&Z.width.specified){i.style.width=Z.width.nodeValue+"px"}else{i.width=i.clientWidth}if(Z.height&&Z.height.specified){i.style.height=Z.height.nodeValue+"px"}else{i.height=i.clientHeight}}return i}};function S(i){var Z=i.srcElement;switch(i.propertyName){case"width":Z.getContext().clearRect();Z.style.width=Z.attributes.width.nodeValue+"px";Z.firstChild.style.width=Z.clientWidth+"px";break;case"height":Z.getContext().clearRect();Z.style.height=Z.attributes.height.nodeValue+"px";Z.firstChild.style.height=Z.clientHeight+"px";break}}function w(i){var Z=i.srcElement;if(Z.firstChild){Z.firstChild.style.width=Z.clientWidth+"px";Z.firstChild.style.height=Z.clientHeight+"px"}}E.init();var I=[];for(var AC=0;AC<16;AC++){for(var AB=0;AB<16;AB++){I[AC*16+AB]=AC.toString(16)+AB.toString(16)}}function V(){return[[1,0,0],[0,1,0],[0,0,1]]}function d(m,j){var i=V();for(var Z=0;Z<3;Z++){for(var AF=0;AF<3;AF++){var p=0;for(var AE=0;AE<3;AE++){p+=m[Z][AE]*j[AE][AF]}i[Z][AF]=p}}return i}function Q(i,Z){Z.fillStyle=i.fillStyle;Z.lineCap=i.lineCap;Z.lineJoin=i.lineJoin;Z.lineWidth=i.lineWidth;Z.miterLimit=i.miterLimit;Z.shadowBlur=i.shadowBlur;Z.shadowColor=i.shadowColor;Z.shadowOffsetX=i.shadowOffsetX;Z.shadowOffsetY=i.shadowOffsetY;Z.strokeStyle=i.strokeStyle;Z.globalAlpha=i.globalAlpha;Z.font=i.font;Z.textAlign=i.textAlign;Z.textBaseline=i.textBaseline;Z.arcScaleX_=i.arcScaleX_;Z.arcScaleY_=i.arcScaleY_;Z.lineScale_=i.lineScale_}var B={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function g(i){var m=i.indexOf("(",3);var Z=i.indexOf(")",m+1);var j=i.substring(m+1,Z).split(",");if(j.length==4&&i.substr(3,1)=="a"){alpha=Number(j[3])}else{j[3]=1}return j}function C(Z){return parseFloat(Z)/100}function N(i,j,Z){return Math.min(Z,Math.max(j,i))}function c(AF){var j,i,Z;h=parseFloat(AF[0])/360%360;if(h<0){h++}s=N(C(AF[1]),0,1);l=N(C(AF[2]),0,1);if(s==0){j=i=Z=l}else{var m=l<0.5?l*(1+s):l+s-l*s;var AE=2*l-m;j=A(AE,m,h+1/3);i=A(AE,m,h);Z=A(AE,m,h-1/3)}return"#"+I[Math.floor(j*255)]+I[Math.floor(i*255)]+I[Math.floor(Z*255)]}function A(i,Z,j){if(j<0){j++}if(j>1){j--}if(6*j<1){return i+(Z-i)*6*j}else{if(2*j<1){return Z}else{if(3*j<2){return i+(Z-i)*(2/3-j)*6}else{return i}}}}function Y(Z){var AE,p=1;Z=String(Z);if(Z.charAt(0)=="#"){AE=Z}else{if(/^rgb/.test(Z)){var m=g(Z);var AE="#",AF;for(var j=0;j<3;j++){if(m[j].indexOf("%")!=-1){AF=Math.floor(C(m[j])*255)}else{AF=Number(m[j])}AE+=I[N(AF,0,255)]}p=m[3]}else{if(/^hsl/.test(Z)){var m=g(Z);AE=c(m);p=m[3]}else{AE=B[Z]||Z}}}return{color:AE,alpha:p}}var L={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var f={};function X(Z){if(f[Z]){return f[Z]}var m=document.createElement("div");var j=m.style;try{j.font=Z}catch(i){}return f[Z]={style:j.fontStyle||L.style,variant:j.fontVariant||L.variant,weight:j.fontWeight||L.weight,size:j.fontSize||L.size,family:j.fontFamily||L.family}}function P(j,i){var Z={};for(var AF in j){Z[AF]=j[AF]}var AE=parseFloat(i.currentStyle.fontSize),m=parseFloat(j.size);if(typeof j.size=="number"){Z.size=j.size}else{if(j.size.indexOf("px")!=-1){Z.size=m}else{if(j.size.indexOf("em")!=-1){Z.size=AE*m}else{if(j.size.indexOf("%")!=-1){Z.size=(AE/100)*m}else{if(j.size.indexOf("pt")!=-1){Z.size=m/0.75}else{Z.size=AE}}}}}Z.size*=0.981;return Z}function AA(Z){return Z.style+" "+Z.variant+" "+Z.weight+" "+Z.size+"px "+Z.family}function t(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function W(i){this.m_=V();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=D*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var Z=i.ownerDocument.createElement("div");Z.style.width=i.clientWidth+"px";Z.style.height=i.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";i.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var M=W.prototype;M.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};M.beginPath=function(){this.currentPath_=[]};M.moveTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"moveTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.lineTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"lineTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.bezierCurveTo=function(j,i,AI,AH,AG,AE){var Z=this.getCoords_(AG,AE);var AF=this.getCoords_(j,i);var m=this.getCoords_(AI,AH);e(this,AF,m,Z)};function e(Z,m,j,i){Z.currentPath_.push({type:"bezierCurveTo",cp1x:m.x,cp1y:m.y,cp2x:j.x,cp2y:j.y,x:i.x,y:i.y});Z.currentX_=i.x;Z.currentY_=i.y}M.quadraticCurveTo=function(AG,j,i,Z){var AF=this.getCoords_(AG,j);var AE=this.getCoords_(i,Z);var AH={x:this.currentX_+2/3*(AF.x-this.currentX_),y:this.currentY_+2/3*(AF.y-this.currentY_)};var m={x:AH.x+(AE.x-this.currentX_)/3,y:AH.y+(AE.y-this.currentY_)/3};e(this,AH,m,AE)};M.arc=function(AJ,AH,AI,AE,i,j){AI*=D;var AN=j?"at":"wa";var AK=AJ+U(AE)*AI-F;var AM=AH+J(AE)*AI-F;var Z=AJ+U(i)*AI-F;var AL=AH+J(i)*AI-F;if(AK==Z&&!j){AK+=0.125}var m=this.getCoords_(AJ,AH);var AG=this.getCoords_(AK,AM);var AF=this.getCoords_(Z,AL);this.currentPath_.push({type:AN,x:m.x,y:m.y,radius:AI,xStart:AG.x,yStart:AG.y,xEnd:AF.x,yEnd:AF.y})};M.rect=function(j,i,Z,m){this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath()};M.strokeRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.stroke();this.currentPath_=p};M.fillRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.fill();this.currentPath_=p};M.createLinearGradient=function(i,m,Z,j){var p=new v("gradient");p.x0_=i;p.y0_=m;p.x1_=Z;p.y1_=j;return p};M.createRadialGradient=function(m,AE,j,i,p,Z){var AF=new v("gradientradial");AF.x0_=m;AF.y0_=AE;AF.r0_=j;AF.x1_=i;AF.y1_=p;AF.r1_=Z;return AF};M.drawImage=function(AO,j){var AH,AF,AJ,AV,AM,AK,AQ,AX;var AI=AO.runtimeStyle.width;var AN=AO.runtimeStyle.height;AO.runtimeStyle.width="auto";AO.runtimeStyle.height="auto";var AG=AO.width;var AT=AO.height;AO.runtimeStyle.width=AI;AO.runtimeStyle.height=AN;if(arguments.length==3){AH=arguments[1];AF=arguments[2];AM=AK=0;AQ=AJ=AG;AX=AV=AT}else{if(arguments.length==5){AH=arguments[1];AF=arguments[2];AJ=arguments[3];AV=arguments[4];AM=AK=0;AQ=AG;AX=AT}else{if(arguments.length==9){AM=arguments[1];AK=arguments[2];AQ=arguments[3];AX=arguments[4];AH=arguments[5];AF=arguments[6];AJ=arguments[7];AV=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AW=this.getCoords_(AH,AF);var m=AQ/2;var i=AX/2;var AU=[];var Z=10;var AE=10;AU.push(" <g_vml_:group",' coordsize="',D*Z,",",D*AE,'"',' coordorigin="0,0"',' style="width:',Z,"px;height:",AE,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var p=[];p.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",K(AW.x/D),",","Dy=",K(AW.y/D),"");var AS=AW;var AR=this.getCoords_(AH+AJ,AF);var AP=this.getCoords_(AH,AF+AV);var AL=this.getCoords_(AH+AJ,AF+AV);AS.x=z.max(AS.x,AR.x,AP.x,AL.x);AS.y=z.max(AS.y,AR.y,AP.y,AL.y);AU.push("padding:0 ",K(AS.x/D),"px ",K(AS.y/D),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",p.join(""),", sizingmethod='clip');")}else{AU.push("top:",K(AW.y/D),"px;left:",K(AW.x/D),"px;")}AU.push(' ">','<g_vml_:image src="',AO.src,'"',' style="width:',D*AJ,"px;"," height:",D*AV,'px"',' cropleft="',AM/AG,'"',' croptop="',AK/AT,'"',' cropright="',(AG-AM-AQ)/AG,'"',' cropbottom="',(AT-AK-AX)/AT,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",AU.join(""))};M.stroke=function(AM){var m=10;var AN=10;var AE=5000;var AG={x:null,y:null};var AL={x:null,y:null};for(var AH=0;AH<this.currentPath_.length;AH+=AE){var AK=[];var AF=false;AK.push("<g_vml_:shape",' filled="',!!AM,'"',' style="position:absolute;width:',m,"px;height:",AN,'px;"',' coordorigin="0,0"',' coordsize="',D*m,",",D*AN,'"',' stroked="',!AM,'"',' path="');var AO=false;for(var AI=AH;AI<Math.min(AH+AE,this.currentPath_.length);AI++){if(AI%AE==0&&AI>0){AK.push(" m ",K(this.currentPath_[AI-1].x),",",K(this.currentPath_[AI-1].y))}var Z=this.currentPath_[AI];var AJ;switch(Z.type){case"moveTo":AJ=Z;AK.push(" m ",K(Z.x),",",K(Z.y));break;case"lineTo":AK.push(" l ",K(Z.x),",",K(Z.y));break;case"close":AK.push(" x ");Z=null;break;case"bezierCurveTo":AK.push(" c ",K(Z.cp1x),",",K(Z.cp1y),",",K(Z.cp2x),",",K(Z.cp2y),",",K(Z.x),",",K(Z.y));break;case"at":case"wa":AK.push(" ",Z.type," ",K(Z.x-this.arcScaleX_*Z.radius),",",K(Z.y-this.arcScaleY_*Z.radius)," ",K(Z.x+this.arcScaleX_*Z.radius),",",K(Z.y+this.arcScaleY_*Z.radius)," ",K(Z.xStart),",",K(Z.yStart)," ",K(Z.xEnd),",",K(Z.yEnd));break}if(Z){if(AG.x==null||Z.x<AG.x){AG.x=Z.x}if(AL.x==null||Z.x>AL.x){AL.x=Z.x}if(AG.y==null||Z.y<AG.y){AG.y=Z.y}if(AL.y==null||Z.y>AL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",AK.join(""))}};function R(j,AE){var i=Y(j.strokeStyle);var m=i.color;var p=i.alpha*j.globalAlpha;var Z=j.lineScale_*j.lineWidth;if(Z<1){p*=Z}AE.push("<g_vml_:stroke",' opacity="',p,'"',' joinstyle="',j.lineJoin,'"',' miterlimit="',j.miterLimit,'"',' endcap="',t(j.lineCap),'"',' weight="',Z,'px"',' color="',m,'" />')}function a(AO,AG,Ah,AP){var AH=AO.fillStyle;var AY=AO.arcScaleX_;var AX=AO.arcScaleY_;var Z=AP.x-Ah.x;var m=AP.y-Ah.y;if(AH instanceof v){var AL=0;var Ac={x:0,y:0};var AU=0;var AK=1;if(AH.type_=="gradient"){var AJ=AH.x0_/AY;var j=AH.y0_/AX;var AI=AH.x1_/AY;var Aj=AH.y1_/AX;var Ag=AO.getCoords_(AJ,j);var Af=AO.getCoords_(AI,Aj);var AE=Af.x-Ag.x;var p=Af.y-Ag.y;AL=Math.atan2(AE,p)*180/Math.PI;if(AL<0){AL+=360}if(AL<0.000001){AL=0}}else{var Ag=AO.getCoords_(AH.x0_,AH.y0_);Ac={x:(Ag.x-Ah.x)/Z,y:(Ag.y-Ah.y)/m};Z/=AY*D;m/=AX*D;var Aa=z.max(Z,m);AU=2*AH.r0_/Aa;AK=2*AH.r1_/Aa-AU}var AS=AH.colors_;AS.sort(function(Ak,i){return Ak.offset-i.offset});var AN=AS.length;var AR=AS[0].color;var AQ=AS[AN-1].color;var AW=AS[0].alpha*AO.globalAlpha;var AV=AS[AN-1].alpha*AO.globalAlpha;var Ab=[];for(var Ae=0;Ae<AN;Ae++){var AM=AS[Ae];Ab.push(AM.offset*AK+AU+" "+AM.color)}AG.push('<g_vml_:fill type="',AH.type_,'"',' method="none" focus="100%"',' color="',AR,'"',' color2="',AQ,'"',' colors="',Ab.join(","),'"',' opacity="',AV,'"',' g_o_:opacity2="',AW,'"',' angle="',AL,'"',' focusposition="',Ac.x,",",Ac.y,'" />')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("<g_vml_:fill",' position="',AF/Z*AY*AY,",",AZ/m*AX*AX,'"',' type="tile"',' src="',AH.src_,'" />')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('<g_vml_:fill color="',AT,'" opacity="',Ad,'" />')}}}M.fill=function(){this.stroke(true)};M.closePath=function(){this.currentPath_.push({type:"close"})};M.getCoords_=function(j,i){var Z=this.m_;return{x:D*(j*Z[0][0]+i*Z[1][0]+Z[2][0])-F,y:D*(j*Z[0][1]+i*Z[1][1]+Z[2][1])-F}};M.save=function(){var Z={};Q(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=d(V(),this.m_)};M.restore=function(){if(this.aStack_.length){Q(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function H(Z){return isFinite(Z[0][0])&&isFinite(Z[0][1])&&isFinite(Z[1][0])&&isFinite(Z[1][1])&&isFinite(Z[2][0])&&isFinite(Z[2][1])}function y(i,Z,j){if(!H(Z)){return }i.m_=Z;if(j){var p=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];i.lineScale_=k(b(p))}}M.translate=function(j,i){var Z=[[1,0,0],[0,1,0],[j,i,1]];y(this,d(Z,this.m_),false)};M.rotate=function(i){var m=U(i);var j=J(i);var Z=[[m,j,0],[-j,m,0],[0,0,1]];y(this,d(Z,this.m_),false)};M.scale=function(j,i){this.arcScaleX_*=j;this.arcScaleY_*=i;var Z=[[j,0,0],[0,i,0],[0,0,1]];y(this,d(Z,this.m_),true)};M.transform=function(p,m,AF,AE,i,Z){var j=[[p,m,0],[AF,AE,0],[i,Z,1]];y(this,d(j,this.m_),true)};M.setTransform=function(AE,p,AG,AF,j,i){var Z=[[AE,p,0],[AG,AF,0],[j,i,1]];y(this,Z,true)};M.drawText_=function(AK,AI,AH,AN,AG){var AM=this.m_,AQ=1000,i=0,AP=AQ,AF={x:0,y:0},AE=[];var Z=P(X(this.font),this.element_);var j=AA(Z);var AR=this.element_.currentStyle;var p=this.textAlign.toLowerCase();switch(p){case"left":case"center":case"right":break;case"end":p=AR.direction=="ltr"?"right":"left";break;case"start":p=AR.direction=="rtl"?"right":"left";break;default:p="left"}switch(this.textBaseline){case"hanging":case"top":AF.y=Z.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":AF.y=-Z.size/2.25;break}switch(p){case"right":i=AQ;AP=0.05;break;case"center":i=AP=AQ/2;break}var AO=this.getCoords_(AI+AF.x,AH+AF.y);AE.push('<g_vml_:line from="',-i,' 0" to="',AP,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!AG,'" stroked="',!!AG,'" style="position:absolute;width:1px;height:1px;">');if(AG){R(this,AE)}else{a(this,AE,{x:-i,y:0},{x:AP,y:Z.size})}var AL=AM[0][0].toFixed(3)+","+AM[1][0].toFixed(3)+","+AM[0][1].toFixed(3)+","+AM[1][1].toFixed(3)+",0,0";var AJ=K(AO.x/D)+","+K(AO.y/D);AE.push('<g_vml_:skew on="t" matrix="',AL,'" ',' offset="',AJ,'" origin="',i,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',AD(AK),'" style="v-text-align:',p,";font:",AD(j),'" /></g_vml_:line>');this.element_.insertAdjacentHTML("beforeEnd",AE.join(""))};M.fillText=function(j,Z,m,i){this.drawText_(j,Z,m,i,false)};M.strokeText=function(j,Z,m,i){this.drawText_(j,Z,m,i,true)};M.measureText=function(j){if(!this.textMeasureEl_){var Z='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",Z);this.textMeasureEl_=this.element_.lastChild}var i=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(i.createTextNode(j));return{width:this.textMeasureEl_.offsetWidth}};M.clip=function(){};M.arcTo=function(){};M.createPattern=function(i,Z){return new u(i,Z)};function v(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}v.prototype.addColorStop=function(i,Z){Z=Y(Z);this.colors_.push({offset:i,color:Z.color,alpha:Z.alpha})};function u(i,Z){q(i);switch(Z){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=Z;break;default:n("SYNTAX_ERR")}this.src_=i.src;this.width_=i.width;this.height_=i.height}function n(Z){throw new o(Z)}function q(Z){if(!Z||Z.nodeType!=1||Z.tagName!="IMG"){n("TYPE_MISMATCH_ERR")}if(Z.readyState!="complete"){n("INVALID_STATE_ERR")}}function o(Z){this.code=this[Z];this.message=Z+": DOM Exception "+this.code}var x=o.prototype=new Error;x.INDEX_SIZE_ERR=1;x.DOMSTRING_SIZE_ERR=2;x.HIERARCHY_REQUEST_ERR=3;x.WRONG_DOCUMENT_ERR=4;x.INVALID_CHARACTER_ERR=5;x.NO_DATA_ALLOWED_ERR=6;x.NO_MODIFICATION_ALLOWED_ERR=7;x.NOT_FOUND_ERR=8;x.NOT_SUPPORTED_ERR=9;x.INUSE_ATTRIBUTE_ERR=10;x.INVALID_STATE_ERR=11;x.SYNTAX_ERR=12;x.INVALID_MODIFICATION_ERR=13;x.NAMESPACE_ERR=14;x.INVALID_ACCESS_ERR=15;x.VALIDATION_ERR=16;x.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=E;CanvasRenderingContext2D=W;CanvasGradient=v;CanvasPattern=u;DOMException=o})()};
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
Flot plugin for rendering pie charts. The plugin assumes the data is
coming is as a single data value for each series, and each of those
values is a positive value or zero (negative numbers don't make
any sense and will cause strange effects). The data values do
NOT need to be passed in as percentage values because it
internally calculates the total and percentages.
* Created by Brian Medendorp, June 2009
* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars
* Changes:
2009-10-22: lineJoin set to round
2009-10-23: IE full circle fix, donut
2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera
2009-11-17: Added IE hover capability submitted by Anthony Aragues
2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well)
Available options are:
series: {
pie: {
show: true/false
radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
offset: {
top: integer value to move the pie up or down
left: integer value to move the pie left or right, or 'auto'
},
stroke: {
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
width: integer pixel width of the stroke
},
label: {
show: true/false, or 'auto'
formatter: a user-defined function that modifies the text/style of the label text
radius: 0-1 for percentage of fullsize, or a specified pixel length
background: {
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
opacity: 0-1
},
threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
},
combine: {
threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
label: any text value of what the combined slice should be labeled
}
highlight: {
opacity: 0-1
}
}
}
More detail and specific examples can be found in the included HTML file.
*/
(function ($)
{
function init(plot) // this is the "body" of the plugin
{
var canvas = null;
var target = null;
var maxRadius = null;
var centerLeft = null;
var centerTop = null;
var total = 0;
var redraw = true;
var redrawAttempts = 10;
var shrink = 0.95;
var legendWidth = 0;
var processed = false;
var raw = false;
// interactive variables
var highlights = [];
// add hook to determine if pie plugin in enabled, and then perform necessary operations
plot.hooks.processOptions.push(checkPieEnabled);
plot.hooks.bindEvents.push(bindEvents);
// check to see if the pie plugin is enabled
function checkPieEnabled(plot, options)
{
if (options.series.pie.show)
{
//disable grid
options.grid.show = false;
// set labels.show
if (options.series.pie.label.show=='auto')
if (options.legend.show)
options.series.pie.label.show = false;
else
options.series.pie.label.show = true;
// set radius
if (options.series.pie.radius=='auto')
if (options.series.pie.label.show)
options.series.pie.radius = 3/4;
else
options.series.pie.radius = 1;
// ensure sane tilt
if (options.series.pie.tilt>1)
options.series.pie.tilt=1;
if (options.series.pie.tilt<0)
options.series.pie.tilt=0;
// add processData hook to do transformations on the data
plot.hooks.processDatapoints.push(processDatapoints);
plot.hooks.drawOverlay.push(drawOverlay);
// add draw hook
plot.hooks.draw.push(draw);
}
}
// bind hoverable events
function bindEvents(plot, eventHolder)
{
var options = plot.getOptions();
if (options.series.pie.show && options.grid.hoverable)
eventHolder.unbind('mousemove').mousemove(onMouseMove);
if (options.series.pie.show && options.grid.clickable)
eventHolder.unbind('click').click(onClick);
}
// debugging function that prints out an object
function alertObject(obj)
{
var msg = '';
function traverse(obj, depth)
{
if (!depth)
depth = 0;
for (var i = 0; i < obj.length; ++i)
{
for (var j=0; j<depth; j++)
msg += '\t';
if( typeof obj[i] == "object")
{ // its an object
msg += ''+i+':\n';
traverse(obj[i], depth+1);
}
else
{ // its a value
msg += ''+i+': '+obj[i]+'\n';
}
}
}
traverse(obj);
alert(msg);
}
function calcTotal(data)
{
for (var i = 0; i < data.length; ++i)
{
var item = parseFloat(data[i].data[0][1]);
if (item)
total += item;
}
}
function processDatapoints(plot, series, data, datapoints)
{
if (!processed)
{
processed = true;
canvas = plot.getCanvas();
target = $(canvas).parent();
options = plot.getOptions();
plot.setData(combine(plot.getData()));
}
}
function setupPie()
{
legendWidth = target.children().filter('.legend').children().width();
// calculate maximum radius and center point
maxRadius = Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2;
centerTop = (canvas.height/2)+options.series.pie.offset.top;
centerLeft = (canvas.width/2);
if (options.series.pie.offset.left=='auto')
if (options.legend.position.match('w'))
centerLeft += legendWidth/2;
else
centerLeft -= legendWidth/2;
else
centerLeft += options.series.pie.offset.left;
if (centerLeft<maxRadius)
centerLeft = maxRadius;
else if (centerLeft>canvas.width-maxRadius)
centerLeft = canvas.width-maxRadius;
}
function fixData(data)
{
for (var i = 0; i < data.length; ++i)
{
if (typeof(data[i].data)=='number')
data[i].data = [[1,data[i].data]];
else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined')
{
if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined')
data[i].label = data[i].data.label; // fix weirdness coming from flot
data[i].data = [[1,0]];
}
}
return data;
}
function combine(data)
{
data = fixData(data);
calcTotal(data);
var combined = 0;
var numCombined = 0;
var color = options.series.pie.combine.color;
var newdata = [];
for (var i = 0; i < data.length; ++i)
{
// make sure its a number
data[i].data[0][1] = parseFloat(data[i].data[0][1]);
if (!data[i].data[0][1])
data[i].data[0][1] = 0;
if (data[i].data[0][1]/total<=options.series.pie.combine.threshold)
{
combined += data[i].data[0][1];
numCombined++;
if (!color)
color = data[i].color;
}
else
{
newdata.push({
data: [[1,data[i].data[0][1]]],
color: data[i].color,
label: data[i].label,
angle: (data[i].data[0][1]*(Math.PI*2))/total,
percent: (data[i].data[0][1]/total*100)
});
}
}
if (numCombined>0)
newdata.push({
data: [[1,combined]],
color: color,
label: options.series.pie.combine.label,
angle: (combined*(Math.PI*2))/total,
percent: (combined/total*100)
});
return newdata;
}
function draw(plot, newCtx)
{
if (!target) return; // if no series were passed
ctx = newCtx;
setupPie();
var slices = plot.getData();
var attempts = 0;
while (redraw && attempts<redrawAttempts)
{
redraw = false;
if (attempts>0)
maxRadius *= shrink;
attempts += 1;
clear();
if (options.series.pie.tilt<=0.8)
drawShadow();
drawPie();
}
if (attempts >= redrawAttempts) {
clear();
target.prepend('<div class="error">Could not draw pie with labels contained inside canvas</div>');
}
if ( plot.setSeries && plot.insertLegend )
{
plot.setSeries(slices);
plot.insertLegend();
}
// we're actually done at this point, just defining internal functions at this point
function clear()
{
ctx.clearRect(0,0,canvas.width,canvas.height);
target.children().filter('.pieLabel, .pieLabelBackground').remove();
}
function drawShadow()
{
var shadowLeft = 5;
var shadowTop = 15;
var edge = 10;
var alpha = 0.02;
// set radius
if (options.series.pie.radius>1)
var radius = options.series.pie.radius;
else
var radius = maxRadius * options.series.pie.radius;
if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge)
return; // shadow would be outside canvas, so don't draw it
ctx.save();
ctx.translate(shadowLeft,shadowTop);
ctx.globalAlpha = alpha;
ctx.fillStyle = '#000';
// center and rotate to starting position
ctx.translate(centerLeft,centerTop);
ctx.scale(1, options.series.pie.tilt);
//radius -= edge;
for (var i=1; i<=edge; i++)
{
ctx.beginPath();
ctx.arc(0,0,radius,0,Math.PI*2,false);
ctx.fill();
radius -= i;
}
ctx.restore();
}
function drawPie()
{
startAngle = Math.PI*options.series.pie.startAngle;
// set radius
if (options.series.pie.radius>1)
var radius = options.series.pie.radius;
else
var radius = maxRadius * options.series.pie.radius;
// center and rotate to starting position
ctx.save();
ctx.translate(centerLeft,centerTop);
ctx.scale(1, options.series.pie.tilt);
//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
// draw slices
ctx.save();
var currentAngle = startAngle;
for (var i = 0; i < slices.length; ++i)
{
slices[i].startAngle = currentAngle;
drawSlice(slices[i].angle, slices[i].color, true);
}
ctx.restore();
// draw slice outlines
ctx.save();
ctx.lineWidth = options.series.pie.stroke.width;
currentAngle = startAngle;
for (var i = 0; i < slices.length; ++i)
drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
ctx.restore();
// draw donut hole
drawDonutHole(ctx);
// draw labels
if (options.series.pie.label.show)
drawLabels();
// restore to original state
ctx.restore();
function drawSlice(angle, color, fill)
{
if (angle<=0)
return;
if (fill)
ctx.fillStyle = color;
else
{
ctx.strokeStyle = color;
ctx.lineJoin = 'round';
}
ctx.beginPath();
if (Math.abs(angle - Math.PI*2) > 0.000000001)
ctx.moveTo(0,0); // Center of the pie
else if ($.browser.msie)
angle -= 0.0001;
//ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera
ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false);
ctx.closePath();
//ctx.rotate(angle); // This doesn't work properly in Opera
currentAngle += angle;
if (fill)
ctx.fill();
else
ctx.stroke();
}
function drawLabels()
{
var currentAngle = startAngle;
// set radius
if (options.series.pie.label.radius>1)
var radius = options.series.pie.label.radius;
else
var radius = maxRadius * options.series.pie.label.radius;
for (var i = 0; i < slices.length; ++i)
{
if (slices[i].percent >= options.series.pie.label.threshold*100)
drawLabel(slices[i], currentAngle, i);
currentAngle += slices[i].angle;
}
function drawLabel(slice, startAngle, index)
{
if (slice.data[0][1]==0)
return;
// format label text
var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
if (lf)
text = lf(slice.label, slice);
else
text = slice.label;
if (plf)
text = plf(text, slice);
var halfAngle = ((startAngle+slice.angle) + startAngle)/2;
var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
var html = '<span class="pieLabel" id="pieLabel'+index+'" style="position:absolute;top:' + y + 'px;left:' + x + 'px;">' + text + "</span>";
target.append(html);
var label = target.children('#pieLabel'+index);
var labelTop = (y - label.height()/2);
var labelLeft = (x - label.width()/2);
label.css('top', labelTop);
label.css('left', labelLeft);
// check to make sure that the label is not outside the canvas
if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0)
redraw = true;
if (options.series.pie.label.background.opacity != 0) {
// put in the transparent background separately to avoid blended labels and label boxes
var c = options.series.pie.label.background.color;
if (c == null) {
c = slice.color;
}
var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;';
$('<div class="pieLabelBackground" style="position:absolute;width:' + label.width() + 'px;height:' + label.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').insertBefore(label).css('opacity', options.series.pie.label.background.opacity);
}
} // end individual label function
} // end drawLabels function
} // end drawPie function
} // end draw function
// Placed here because it needs to be accessed from multiple locations
function drawDonutHole(layer)
{
// draw donut hole
if(options.series.pie.innerRadius > 0)
{
// subtract the center
layer.save();
innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color
layer.beginPath();
layer.fillStyle = options.series.pie.stroke.color;
layer.arc(0,0,innerRadius,0,Math.PI*2,false);
layer.fill();
layer.closePath();
layer.restore();
// add inner stroke
layer.save();
layer.beginPath();
layer.strokeStyle = options.series.pie.stroke.color;
layer.arc(0,0,innerRadius,0,Math.PI*2,false);
layer.stroke();
layer.closePath();
layer.restore();
// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
}
}
//-- Additional Interactive related functions --
function isPointInPoly(poly, pt)
{
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
&& (c = !c);
return c;
}
function findNearbySlice(mouseX, mouseY)
{
var slices = plot.getData(),
options = plot.getOptions(),
radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
for (var i = 0; i < slices.length; ++i)
{
var s = slices[i];
if(s.pie.show)
{
ctx.save();
ctx.beginPath();
ctx.moveTo(0,0); // Center of the pie
//ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false);
ctx.closePath();
x = mouseX-centerLeft;
y = mouseY-centerTop;
if(ctx.isPointInPath)
{
if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop))
{
//alert('found slice!');
ctx.restore();
return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};
}
}
else
{
// excanvas for IE doesn;t support isPointInPath, this is a workaround.
p1X = (radius * Math.cos(s.startAngle));
p1Y = (radius * Math.sin(s.startAngle));
p2X = (radius * Math.cos(s.startAngle+(s.angle/4)));
p2Y = (radius * Math.sin(s.startAngle+(s.angle/4)));
p3X = (radius * Math.cos(s.startAngle+(s.angle/2)));
p3Y = (radius * Math.sin(s.startAngle+(s.angle/2)));
p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5)));
p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5)));
p5X = (radius * Math.cos(s.startAngle+s.angle));
p5Y = (radius * Math.sin(s.startAngle+s.angle));
arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]];
arrPoint = [x,y];
// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
if(isPointInPoly(arrPoly, arrPoint))
{
ctx.restore();
return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};
}
}
ctx.restore();
}
}
return null;
}
function onMouseMove(e)
{
triggerClickHoverEvent('plothover', e);
}
function onClick(e)
{
triggerClickHoverEvent('plotclick', e);
}
// trigger click or hover event (they send the same parameters so we share their code)
function triggerClickHoverEvent(eventname, e)
{
var offset = plot.offset(),
canvasX = parseInt(e.pageX - offset.left),
canvasY = parseInt(e.pageY - offset.top),
item = findNearbySlice(canvasX, canvasY);
if (options.grid.autoHighlight)
{
// clear auto-highlights
for (var i = 0; i < highlights.length; ++i)
{
var h = highlights[i];
if (h.auto == eventname && !(item && h.series == item.series))
unhighlight(h.series);
}
}
// highlight the slice
if (item)
highlight(item.series, eventname);
// trigger any hover bind events
var pos = { pageX: e.pageX, pageY: e.pageY };
target.trigger(eventname, [ pos, item ]);
}
function highlight(s, auto)
{
if (typeof s == "number")
s = series[s];
var i = indexOfHighlight(s);
if (i == -1)
{
highlights.push({ series: s, auto: auto });
plot.triggerRedrawOverlay();
}
else if (!auto)
highlights[i].auto = false;
}
function unhighlight(s)
{
if (s == null)
{
highlights = [];
plot.triggerRedrawOverlay();
}
if (typeof s == "number")
s = series[s];
var i = indexOfHighlight(s);
if (i != -1)
{
highlights.splice(i, 1);
plot.triggerRedrawOverlay();
}
}
function indexOfHighlight(s)
{
for (var i = 0; i < highlights.length; ++i)
{
var h = highlights[i];
if (h.series == s)
return i;
}
return -1;
}
function drawOverlay(plot, octx)
{
//alert(options.series.pie.radius);
var options = plot.getOptions();
//alert(options.series.pie.radius);
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
octx.save();
octx.translate(centerLeft, centerTop);
octx.scale(1, options.series.pie.tilt);
for (i = 0; i < highlights.length; ++i)
drawHighlight(highlights[i].series);
drawDonutHole(octx);
octx.restore();
function drawHighlight(series)
{
if (series.angle < 0) return;
//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor
octx.beginPath();
if (Math.abs(series.angle - Math.PI*2) > 0.000000001)
octx.moveTo(0,0); // Center of the pie
octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false);
octx.closePath();
octx.fill();
}
}
} // end init (plugin body)
// define pie specific options and their default values
var options = {
series: {
pie: {
show: false,
radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
innerRadius:0, /* for donut */
startAngle: 3/2,
tilt: 1,
offset: {
top: 0,
left: 'auto'
},
stroke: {
color: '#FFF',
width: 1
},
label: {
show: 'auto',
formatter: function(label, slice){
return '<div style="font-size:x-small;text-align:center;padding:2px;color:'+slice.color+';">'+label+'<br/>'+Math.round(slice.percent)+'%</div>';
}, // formatter function
radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
background: {
color: null,
opacity: 0
},
threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
},
combine: {
threshold: -1, // percentage at which to combine little slices into one larger slice
color: null, // color to give the new slice (auto-generated if null)
label: 'Other' // label to give the new slice
},
highlight: {
//color: '#FFF', // will add this functionality once parseColor is available
opacity: 0.5
}
}
}
};
$.plot.plugins.push({
init: init,
options: options,
name: "pie",
version: "1.0"
});
})(jQuery);
/*
* Horizontal Bar Graph for jQuery
* version 0.1a
*
* http://www.dumpsterdoggy.com/plugins/horiz-bar-graph
*
* Copyright (c) 2009 Chris Missal
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*/
(function($) {
$.fn.horizontalBarGraph = function(options) {
var opts = $.extend({}, $.fn.horizontalBarGraph.defaults, options);
this.children("dt,dd").each(function(i) {
var el = $(this);
if(el.is("dt")) {
el.css({display: "block", float: "left", clear: "left"}).addClass("hbg-label"); return;
} else {
(isTitleDD(el) && opts.hasTitles ? createTitle : createBar)(el, opts);
}
setBarHover(el, opts);
});
tryShowTitle(this);
if(opts.animated) {
createShowButton(opts, this).insertBefore(this);
}
if(opts.colors.length) {
setColors(this.children("dd"), opts);
}
if(opts.hoverColors.length) {
setHoverColors(this.children("dd"), opts);
}
scaleGraph(this);
return this;
};
function scaleGraph(graph) {
var maxWidth = 0;
graph.children("dt").each(function() {
maxWidth = Math.max($(this).width(), maxWidth);
}).css({width: maxWidth+"px"});
}
function setBarHover(bar, opts) {
bar.hover(function() {
bar.addClass("hbg-bar-hover");
}, function() {
bar.removeClass("hbg-bar-hover");
});
}
function createShowButton(opts, graph) {
var button = $("<span />").text(opts.button).addClass("hbg-show-button");
button.click(function() {
graph.children("dd").show('slow', function() { button.fadeOut('normal'); });
});
return button;
}
function createBar(e, opts) {
var val = e.text();
e.css({marginLeft: e.prev().is("dt") ? "5px" : "0px", width: Math.floor(val/opts.interval)+"px"});
e.html($("<span/>").html(val).addClass("hbg-value"));
applyOptions(e, opts);
}
function createTitle(e, opts) {
var title = e.text();
e.prev().attr("title", title);
e.remove();
}
function tryShowTitle(graph) {
var title = graph.attr("title");
if(title) {
$("<div/>").text(title).addClass("hbg-title").insertBefore(graph);
graph.css({overflow: "hidden"});
}
}
function setColors(bars, opts) {
var i = 0;
bars.each(function() {
var c = i++ % opts.colors.length;
$(this).css({backgroundColor: opts.colors[c]});
});
}
function setHoverColors(bars, opts) {
var i = 0;
bars.each(function(i) {
var bar = $(this);
var c = bar.css("background-color");
var hc = opts.hoverColors[i++ % opts.hoverColors.length];
bar.hover(function() {
$(this).css({backgroundColor: hc});
}, function() {
$(this).css({backgroundColor: c});
});
});
}
function applyOptions(e, opts) {
e.css({float: "left"}).addClass("hbg-bar");
if(opts.animated) { e.hide(); }
}
function isTitleDD(e) {
return (e.is(":even") && e.prev().is("dd"));
}
$.fn.horizontalBarGraph.defaults = {
interval: 1,
hasTitles: false,
animated: false,
button: 'Show Values',
colors: [],
hoverColors: []
};
})(jQuery);
$(document).ready(function() {
drawGraph();
window.setInterval("pollStats()", 1000);
})
var dataIq = [];
var dataMsg = [];
var dataPres = [];
var dataRost = [];
var id = 0;
function drawGraph() {
var options = {
lines : {
show : true
},
points : {
show : false
},
yaxis : {
min : 0
},
xaxis : {
show : false
},
grid : {
backgroundColor : {
colors : [ "#fff", "lightgray" ]
}
}
// xaxis: { tickDecimals: 0, tickSize: 1 }
};
var placeholder = $(".graph");
if (dataIq.length > 50) {
dataIq = dataIq.slice(dataIq.length - 50, dataIq.length - 1);
dataMsg = dataMsg.slice(dataMsg.length - 50, dataMsg.length - 1);
dataPres = dataPres.slice(dataPres.length - 50, dataPres.length - 1);
dataRost = dataRost.slice(dataRost.length - 50, dataRost.length - 1);
}
$.plot($(".graph"), [ {
label : "IQ",
data : dataIq
}, {
label : "Messages",
data : dataMsg
}, {
label : "Roster",
data : dataRost
}, {
label : "Presence",
data : dataPres
} ], options);
// $.plot(placeholder, d1, options);
}
function pollStats() {
var myDate = $('#lastPoll').html();
if (myDate.length < 1) {
myDate = $('#logSince').html();
}
var firstDate = $('#timeStamp:first').html();
if (firstDate == null) {
firstDate = $('#logSince').html();
}
updateData(firstDate);
}
function updateData(lastDate) {
var callback = function(data, textStatus, jqXHR) {
var i = 0;
dataIq.push([ id, data.numbers.iq ]);
dataMsg.push([ id, data.numbers.msg ]);
dataPres.push([ id, data.numbers.presence ]);
dataRost.push([ id, data.numbers.roster ]);
id++;
while (data.packets[i] != null) {
var color = "#DEDEDE";
if (i % 2 == 0) {
color = "#EBEBEB";
}
$('.tableBegin:first').after(
'<tr style="background-color:' + color
+ ';"><td id="timeStamp">' + data.packets[i].date
+ '</td><td>' + data.packets[i].type + '</td><td>'
+ data.packets[i].from + '</td><td>'
+ data.packets[i].to + '</td></tr>');
i++;
}
}
drawGraph();
$.ajax({
url : 'stats?component=xmpp.dew08299&date=' + lastDate,
dataType : 'json',
data : '',
success : callback
});
}
This source diff could not be displayed because it is too large. You can view the blob instead.
function searchSuggest(object) {
document.getElementById('search_suggest' + object).innerHTML = '';
var str = escape(document.getElementById('groupSearch' + object).value);
$("#ajaxloading" + object).html("<img src=\"images/ajax-loader.gif\">");
var request = new http();
var f = function(obj) {
var ss = document.getElementById('search_suggest' + object);
var resp = obj.responseXML.documentElement.getElementsByTagName('item');
if (resp.length > 0) {
for ( var i = 0; i < resp.length; i++) {
var suggest = '<div onmouseover="javascript:suggestOver(this);" ';
suggest += 'onmouseout="javascript:suggestOut(this);" ';
suggest += 'onclick="javascript:setSearch(this.innerHTML,'
+ object + ');" ';
suggest += 'class="suggest_link">' + resp[i].textContent
+ '</div>';
ss.innerHTML += suggest;
}
$("#search_suggest" + object).show("slow");
} else {
$("#search_suggest" + object).hide("slow");
}
};
request.callback = f;
request.load('groups?search=' + str);
}
// Mouse over function
function suggestOver(div_value) {
div_value.className = 'suggest_link_over';
}
// Mouse out function
function suggestOut(div_value) {
div_value.className = 'suggest_link';
}
// Click function
function setSearch(value, object) {
document.getElementById("groupSearch" + object).value = value;
// document.getElementById('search_suggest').innerHTML = '';
$("#search_suggest" + object).hide("slow");
checkIfExists(value, object);
}
function checkIfExists(value, object) {
var str = value;
var request = new http();
var f = function(obj) {
var ss = document.getElementById('search_suggest' + object);
var resp = obj.responseXML.documentElement.getElementsByTagName('item');
for ( var i = 0; i < resp.length; i++) {
var stri = String(str);
var respStr = String(resp[i].textContent);
if (stri == respStr) {
$("#ajaxloading" + object).html(
"<img src=\"images/correct-16x16.png\">");
break;
}
}
};
request.callback = f;
request.load('groups?search=' + str);
}
$(document).ready(function() {
$(".browser-data").horizontalBarGraph({
interval : 1
});
var i = 0;
while ($('#logiq' + i).html() != null) {
var iqs = $('#logiq'+i).html();
var msg = $('#logmsg'+i).html();
var roster = $('#logroster'+i).html();
var presence = $('#logpresence'+i).html();
var data = [ {
label : "IQ",
data : parseInt(iqs)
}, {
label : "Messages",
data : parseInt(msg)
}, {
label : "Roster",
data : parseInt(roster)
}, {
label : "Presence",
data : parseInt(presence)
} ];
$.plot($("#pie"+i), data,
{
series: {
pie: {
show: true,
radius: 1,
label: {
show: true,
radius: 3/5,
formatter: function(label, series){
return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;background-color:rgba(100,100,100,0.5);">'+label+'<br/>'+Math.round(series.percent)+'%</div>';
},
background: {
opacity: 0.5,
color: 'gray'
}
}
}
},
legend: {
show: false
}
});
++i;
}
})
function slideToggle(value) {
$(value).slideToggle();
}
<%@page import="org.jivesoftware.openfire.plugin.PermissionManager"%>
<%@ page import="org.dom4j.tree.DefaultElement"%>
<%@ page import="org.jivesoftware.openfire.group.GroupManager"%>
<%@ page import="org.jivesoftware.openfire.group.Group"%>
<%@ page import="org.jivesoftware.openfire.session.ComponentSession"%>
<%@ page import="java.util.Collection"%>
<%@ page import="org.jivesoftware.openfire.SessionManager"%>
<%@ page import="org.jivesoftware.util.JiveGlobals"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.List"%>
<%@ page import="org.jivesoftware.openfire.plugin.database.DatabaseManager"%>
<%@ page import="org.jivesoftware.util.ParamUtils"%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt"%>
<%
// webManager.init(request, response, session, application, out);
boolean componentSet = request.getParameter("component") != null;
String component = "";
if (componentSet) {
component = request.getParameter("component");
}
%>
<html>
<head>
<title>Live logs <%=componentSet ? "for " + component : ""%></title>
<meta name="decorator" content="none" />
<link href="./css/liveStats.css" rel="stylesheet" type="text/css">
<script src="./js/http.js" type="text/javascript"></script>
<script src="./js/jquery.js" type="text/javascript"></script>
<script src="./js/liveStats.js" type="text/javascript"></script>
<script language="javascript" type="text/javascript" src="./js/jquery.flot.js"></script>
</head>
<body>
<div class="div-main">
<div class="header">
Live statistics for
<%=componentSet ? component : "NOT SET"%></div>
<div class="graph">Here should appear your stats</div>
<table id="logTable">
<thead>
<tr>
<th>Date</th>
<th>Type</th>
<th>From</th>
<th>To</th>
</tr>
</thead>
<tbody class="tableBegin">
</tbody>
<tfoot>
<tr>
<td colspan="4">Live logging since <span id="logSince"><%=System.currentTimeMillis()%></span> Last poll <span
id="lastPoll"></span></td>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
\ No newline at end of file
......@@ -9,6 +9,7 @@
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.List"%>
<%@ page import="org.jivesoftware.openfire.plugin.database.DatabaseManager"%>
<%@ page import="org.jivesoftware.util.ParamUtils"%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt"%>
......@@ -24,6 +25,7 @@
boolean sparkDiscoInfo = sparkdiscoParam == null ? false : sparkdiscoParam.equals("true");
String[] componentsEnabled = request.getParameterValues("enabledComponents[]");
PermissionManager _pmanager = new PermissionManager();
DatabaseManager _db;
Map<String, String> errors = new HashMap<String, String>();
if (save) {
......@@ -49,17 +51,22 @@
SessionManager sessionManager = webManager.getSessionManager();
Collection<ComponentSession> sessions = sessionManager.getComponentSessions();
_db = DatabaseManager.getInstance();
%>
<html>
<head>
<title><fmt:message key="rr.summary.title" /></title>
<link href="rr.css" rel="stylesheet" type="text/css">
<script src="http.js" type="text/javascript"></script>
<script src="jquery.js" type="text/javascript"></script>
<script src="rr.js" type="text/javascript"></script>
<script src="jquery.sparkline.js" type="text/javascript"></script>
<link href="./css/rr.css" rel="stylesheet" type="text/css">
<script src="./js/http.js" type="text/javascript"></script>
<script src="./js/jquery.js" type="text/javascript"></script>
<script src="./js/rr.js" type="text/javascript"></script>
<script src="./js/jquery.sparkline.js" type="text/javascript"></script>
<script src="./js/jquery.horiz-bar-graph.js" type="text/javascript"></script>
<!--[if lte IE 8]><script language="javascript" type="text/javascript" src="./js/excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="./js/jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="./js/jquery.flot.pie.js"></script>
<meta name="pageID" content="remoteRoster" />
<meta name="helpPage" content="" />
......@@ -122,6 +129,12 @@
continue;
}
gatewayFound = true;
long incoming = componentSession.getNumClientPackets();
long outgoing = componentSession.getNumServerPackets();
long both = incoming + outgoing;
int incomingPercent = (int) (incoming * 100 / both);
int outgoingPercent = (int) (outgoing * 100 / both);
%>
<table class="gatewayHeader">
<tbody>
......@@ -133,37 +146,46 @@
: ""%> />
</td>
<td class="gatewayName"><%=componentSession.getExternalComponent().getName()%></td>
<td class="gatewayIcons"><img src="images/info-16x16.png" id="showConfig"
onclick="slideToggle('#config<%=i%>')"> <img src="images/permissions-16x16.png" id="showPermissions"
onclick="slideToggle('#permission<%=i%>')"> <img src="images/log-16x16.png"></td>
<td class="gatewayIcons"><img src="images/log-16x16.png" onclick="slideToggle('#logs<%=i%>')"><img
src="images/permissions-16x16.png" id="showPermissions" onclick="slideToggle('#permission<%=i%>')"><img
src="images/info-16x16.png" id="showConfig" onclick="slideToggle('#config<%=i%>')"></td>
</tr>
</tbody>
</table>
<div id="config<%=i%>" class="slider">
<div class="sildeHeader">Information</div>
<table class="configTable">
<tbody>
<tr>
<td class="configTable1Column">Domain:</td>
<tr id="logodd">
<td width="200px">Domain:</td>
<td><%=componentSession.getExternalComponent().getInitialSubdomain()%></td>
</tr>
<tr>
<td class="configTable1Column">Status:</td>
<tr id="logeven">
<td>Status:</td>
<td>Online</td>
</tr>
<tr>
<td class="configTable1Column">Packages Send/Received:</td>
<td><%=componentSession.getNumServerPackets()%> / <%=componentSession.getNumClientPackets()%><div id="inlinesparkline<%=i%>">1,4,4,7,5,9,10</div></td>
<tr id="logodd">
<td>Packages Send/Received:</td>
<td><dl class="browser-data" title="">
<dt>Incoming</dt>
<dd><%=incomingPercent%></dd>
<dt>Outgoing</dt>
<dd><%=outgoingPercent%></dd>
</dl></td>
</tr>
</tbody>
</table>
</div>
<div id="permission<%=i%>" class="slider">
> <span class="permissionTitle">You can limit the access to the external component to an existing group</span>)
<div class="sildeHeader">Access control</div>
<table class="groupTable">
<tbody>
<tr id="loghead">
<td colspan="3">You can limit the access to the external component to an existing group</td>
</tr>
<tr>
<td class="permissionTableColumn">Groupname:</td>
<td><input type="text" id="groupSearch<%=i%>"
<td><input class="groupInput" type="text" id="groupSearch<%=i%>"
name="input_group.<%=componentSession.getExternalComponent().getInitialSubdomain()%>" alt="Find Groups"
onkeyup="searchSuggest('<%=i%>');" autocomplete="off"
value="<%=_pmanager.getGroupForGateway(componentSession.getExternalComponent().getInitialSubdomain())%>">
......@@ -171,10 +193,62 @@
<td style="vertical-align: top;">
<div class="ajaxloading" id="ajaxloading<%=i%>"></div>
</td>
</tr>
</tbody>
</table>
</div>
<div id="logs<%=i%>" class="slider">
<%
int iqs = _db.getPacketCount(componentSession.getExternalComponent().getInitialSubdomain(),
Class.forName("org.xmpp.packet.IQ"));
int msgs = _db.getPacketCount(componentSession.getExternalComponent().getInitialSubdomain(),
Class.forName("org.xmpp.packet.Message"));
int rosters = _db.getPacketCount(componentSession.getExternalComponent().getInitialSubdomain(),
Class.forName("org.xmpp.packet.Roster"));
int presences = _db.getPacketCount(componentSession.getExternalComponent().getInitialSubdomain(),
Class.forName("org.xmpp.packet.Presence"));
%>
<div class="sildeHeader">Logs & Statistics</div>
<table class="logtable">
<tfoot>
<tr id="logfoot">
<td colspan="2">Packages being logged for 60 minutes</td>
<td><a style="float: right;"
onClick="window.open('liveStats.jsp?component=<%=componentSession.getExternalComponent().getInitialSubdomain()%>','mywindow','width=1200,height=700')">Show
realtime Log</a>
</tr>
</tfoot>
<tbody>
<tr id="loghead">
<td width="200px">Paket type</td>
<td width="100px">Number</td>
<td></td>
</tr>
<tr id="logodd">
<td>IQ</td>
<td id="logiq<%=i%>"><%=iqs%></td>
<td rowspan="5"><div id="pie<%=i%>" class="graph"></div></td>
</tr>
<tr id="logeven">
<td>Messages</td>
<td id="logmsg<%=i%>"><%=msgs%></td>
</tr>
<tr id="logodd">
<td>Roster</td>
<td id="logroster<%=i%>"><%=rosters%></td>
</tr>
<tr id="logeven">
<td>Presence</td>
<td id="logpresence<%=i%>"><%=presences%></td>
</tr>
<tr id="logodd">
<td><span style="font-weight: bold;">Total:</span></td>
<td><span style="font-weight: bold;"><%=iqs + msgs + rosters + presences%></span></td>
</tr>
</tbody>
</table>
</div>
......
function searchSuggest(object) {
document.getElementById('search_suggest'+object).innerHTML = '';
var str = escape(document.getElementById('groupSearch'+object).value);
$("#ajaxloading"+object).html("<img src=\"images/ajax-loader.gif\">");
var request = new http();
var f = function(obj) {
console.log(obj);
var ss = document.getElementById('search_suggest'+object);
var resp = obj.responseXML.documentElement
.getElementsByTagName('item');
if (resp.length > 0) {
for ( var i = 0; i < resp.length; i++) {
console.log(resp[i].textContent);
var suggest = '<div onmouseover="javascript:suggestOver(this);" ';
suggest += 'onmouseout="javascript:suggestOut(this);" ';
suggest += 'onclick="javascript:setSearch(this.innerHTML,'+object+');" ';
suggest += 'class="suggest_link">' + resp[i].textContent
+ '</div>';
ss.innerHTML += suggest;
}
$("#search_suggest"+object).show("slow");
} else {
$("#search_suggest"+object).hide("slow");
}
};
request.callback = f;
request.load('searchGroup.jsp?search=' + str);
}
// Mouse over function
function suggestOver(div_value) {
div_value.className = 'suggest_link_over';
}
// Mouse out function
function suggestOut(div_value) {
div_value.className = 'suggest_link';
}
// Click function
function setSearch(value, object) {
document.getElementById("groupSearch"+object).value = value;
// document.getElementById('search_suggest').innerHTML = '';
$("#search_suggest"+object).hide("slow");
checkIfExists(value,object);
}
function checkIfExists(value, object) {
var str = value;
var request = new http();
var f = function(obj) {
console.log(obj);
var ss = document.getElementById('search_suggest'+object);
var resp = obj.responseXML.documentElement.getElementsByTagName('item');
for ( var i = 0; i < resp.length; i++) {
var stri = String(str);
var respStr = String(resp[i].textContent);
if (stri == respStr) {
$("#ajaxloading"+object)
.html("<img src=\"images/correct-16x16.png\">");
break;
}
}
};
request.callback = f;
request.load('searchGroup.jsp?search=' + str);
}
$(document).ready(function () {
console.log("bind da");
var myvalues = [10,8,5,7,4,4,1];
$('#inlinesparkline0').sparkline();
})
function slideToggle(value)
{
$(value).slideToggle();
}
<?xml version="1.0" encoding="UTF-8" ?>
<%@page import="org.dom4j.tree.DefaultElement"%>
<%@page import="org.dom4j.*"%>
<%@ page language="java" contentType="text/xml" pageEncoding="ISO-8859-1"%>
<%@ page import="org.jivesoftware.openfire.group.GroupManager"%>
<%@ page import="org.jivesoftware.openfire.group.Group"%>
<%@ page import="java.util.Collection"%>
<%
String param = request.getParameter("search");
Element root = new DefaultElement("result");
if (param != null && param.length() > 0)
{
GroupManager manager = GroupManager.getInstance();
Collection<Group> groups = manager.getGroups();
for (Group gr : groups)
{
if (gr.getName().startsWith(param))
{
root.addElement("item").addText(gr.getName());
}
}
}
out.write(root.asXML());
%>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment