├── django └── minecraft-app │ ├── tests.py │ ├── templatetags │ ├── __init__.py │ └── splunk_wftoolkit.py │ ├── static │ └── minecraft-app │ │ ├── Clock.png │ │ ├── Head.png │ │ ├── blockimages │ │ ├── Stone.png │ │ └── Cobblestone.png │ │ ├── compass_lower-left.png │ │ ├── compass_lower-right.png │ │ ├── compass_upper-left.png │ │ ├── compass_upper-right.png │ │ ├── oreimages │ │ ├── Coal_Ore.png │ │ ├── Gold_Ore.png │ │ ├── Iron_Ore.png │ │ ├── Diamond_Ore.png │ │ ├── Emerald_Ore.png │ │ ├── Redstone_Ore.png │ │ ├── Lapis_Lazuli_Ore.png │ │ └── Nether_Quartz_Ore.png │ │ ├── components │ │ ├── d3 │ │ │ ├── bower.json │ │ │ └── LICENSE │ │ ├── resultsview │ │ │ ├── bower.json │ │ │ └── resultsview.js │ │ ├── underscore-nest │ │ │ ├── bower.json │ │ │ ├── LICENSE │ │ │ └── underscore-nest.js │ │ ├── sankey │ │ │ ├── bower.json │ │ │ ├── sankey.css │ │ │ └── contrib │ │ │ │ └── LICENSE │ │ ├── bubblechart │ │ │ ├── bower.json │ │ │ ├── bubblechart.css │ │ │ └── bubblechart.js │ │ ├── parallelsets │ │ │ ├── bower.json │ │ │ ├── parallelsets.css │ │ │ ├── contrib │ │ │ │ └── LICENSE │ │ │ └── parallelsets.js │ │ ├── forcedirected │ │ │ ├── bower.json │ │ │ └── forcedirected.css │ │ ├── calendarheatmap │ │ │ ├── bower.json │ │ │ ├── calendarheatmap.css │ │ │ ├── contrib │ │ │ │ ├── LICENSE │ │ │ │ └── cal-heatmap.css │ │ │ └── calendarheatmap.js │ │ ├── parallelcoords │ │ │ ├── bower.json │ │ │ ├── contrib │ │ │ │ ├── d3-parcoords.css │ │ │ │ └── LICENSE │ │ │ └── parallelcoords.js │ │ └── sunburst │ │ │ ├── bower.json │ │ │ ├── sunburst.css │ │ │ └── sunburst.js │ │ ├── dashboard.css │ │ └── custom.css │ ├── nav.py │ ├── urls.py │ ├── views.py │ ├── __init__.py │ └── templates │ ├── placedblocks.html │ ├── home.html │ ├── players.html │ ├── livemap.html │ └── brokenblocks.html ├── static ├── appIcon.png ├── appIconAlt.png ├── appIcon_2x.png └── appIconAlt_2x.png ├── appserver ├── static │ ├── appIcon.png │ ├── appIconAlt.png │ ├── appIcon_2x.png │ └── appIconAlt_2x.png └── templates │ └── redirect.tmpl ├── default ├── data │ └── ui │ │ ├── views │ │ └── default.xml │ │ └── nav │ │ └── default.xml └── app.conf ├── forge ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── resources │ │ └── mcmod.info │ │ ├── doc │ │ └── README │ │ └── java │ │ └── com │ │ └── splunk │ │ └── forge │ │ ├── event_loggers │ │ ├── DeathEventLogger.java │ │ ├── BlockEventLogger.java │ │ └── PlayerEventLogger.java │ │ └── LogToSplunkMod.java ├── gradlew.bat ├── build.gradle ├── pom.xml └── gradlew ├── sampleModConfig └── splunk_mod.properties ├── spigot ├── src │ └── main │ │ ├── resources │ │ └── plugin.yml │ │ └── java │ │ └── com │ │ └── splunk │ │ └── spigot │ │ ├── eventloggers │ │ ├── BlockEventLogger.java │ │ ├── DeathEventLogger.java │ │ └── PlayerEventLogger.java │ │ └── LogToSplunkPlugin.java └── pom.xml ├── logtosplunk-plugin ├── src │ └── main │ │ ├── config │ │ └── splunk.properties │ │ └── doc │ │ └── README └── pom.xml ├── .gitignore ├── shared-mc ├── src │ └── main │ │ └── java │ │ └── com │ │ └── splunk │ │ └── sharedmc │ │ ├── loggable_events │ │ ├── LoggableEvent.java │ │ ├── LoggableEventType.java │ │ ├── LoggableDeathEvent.java │ │ ├── AbstractLoggableEvent.java │ │ ├── LoggableBlockEvent.java │ │ └── LoggablePlayerEvent.java │ │ ├── SplunkConnection.java │ │ ├── Constants.java │ │ ├── Point3dLong.java │ │ ├── event_loggers │ │ └── AbstractEventLogger.java │ │ └── SingleSplunkConnection.java └── pom.xml ├── THIRD-PARTY-CREDITS.md ├── README-PLUGMOD.md ├── pom.xml ├── README.md └── LICENSE /django/minecraft-app/tests.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/minecraft-app/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/appIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/static/appIcon.png -------------------------------------------------------------------------------- /static/appIconAlt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/static/appIconAlt.png -------------------------------------------------------------------------------- /static/appIcon_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/static/appIcon_2x.png -------------------------------------------------------------------------------- /static/appIconAlt_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/static/appIconAlt_2x.png -------------------------------------------------------------------------------- /appserver/static/appIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/appserver/static/appIcon.png -------------------------------------------------------------------------------- /default/data/ui/views/default.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /appserver/static/appIconAlt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/appserver/static/appIconAlt.png -------------------------------------------------------------------------------- /appserver/static/appIcon_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/appserver/static/appIcon_2x.png -------------------------------------------------------------------------------- /appserver/static/appIconAlt_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/appserver/static/appIconAlt_2x.png -------------------------------------------------------------------------------- /forge/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/forge/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sampleModConfig/splunk_mod.properties: -------------------------------------------------------------------------------- 1 | mod.splunk.enable.consolelog=false 2 | mod.splunk.connection.host=localhost 3 | mod.splunk.connection.port=8888 -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/Clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/Clock.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/Head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/Head.png -------------------------------------------------------------------------------- /spigot/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: LogToSplunk 2 | main: com.splunk.spigot.LogToSplunkPlugin 3 | version: 1.0-SNAPSHOT 4 | api-version: '1.20.5' 5 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/blockimages/Stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/blockimages/Stone.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/compass_lower-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/compass_lower-left.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/compass_lower-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/compass_lower-right.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/compass_upper-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/compass_upper-left.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/compass_upper-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/compass_upper-right.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Coal_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Coal_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Gold_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Gold_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Iron_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Iron_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Diamond_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Diamond_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Emerald_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Emerald_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Redstone_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Redstone_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/blockimages/Cobblestone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/blockimages/Cobblestone.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Lapis_Lazuli_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Lapis_Lazuli_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/oreimages/Nether_Quartz_Ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/minecraft-app/HEAD/django/minecraft-app/static/minecraft-app/oreimages/Nether_Quartz_Ore.png -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/d3/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3", 3 | "version": "3.3.5", 4 | "main": "d3.js", 5 | "ignore": [], 6 | "dependencies": {}, 7 | "devDependencies": {} 8 | } -------------------------------------------------------------------------------- /logtosplunk-plugin/src/main/config/splunk.properties: -------------------------------------------------------------------------------- 1 | splunk.craft.connection.host=127.0.0.1 2 | splunk.craft.connection.port=8088 3 | splunk.craft.token=CHANGEME-1234-5678-1234-123456789012 4 | splunk.craft.enable.consolelog=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyo 2 | *.pyc 3 | *.swp 4 | build 5 | .gradle 6 | bin/ 7 | .project 8 | *.iml 9 | .idea/ 10 | eclipse/ 11 | .classpath 12 | logs/ 13 | .settings 14 | target/ 15 | 16 | #forge .idea things: 17 | *.ipr 18 | *.iws -------------------------------------------------------------------------------- /appserver/templates/redirect.tmpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/resultsview/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resultsview", 3 | "version": "1.0.0", 4 | "main": "resultsview.js", 5 | "ignore": [], 6 | "dependencies": {}, 7 | "devDependencies": {} 8 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/underscore-nest/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "underscore-nest", 3 | "version": "0.1.1", 4 | "main": "underscore-nest.js", 5 | "ignore": [], 6 | "dependencies": {}, 7 | "devDependencies": {} 8 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/sankey/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sankey", 3 | "version": "1.0.0", 4 | "main": "sankey.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x" 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/bubblechart/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bubblechart", 3 | "version": "1.0.0", 4 | "main": "bubblechart.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x" 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelsets/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallelsets", 3 | "version": "1.0.0", 4 | "main": "parallelsets.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x" 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /forge/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 26 21:37:31 EDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip 7 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/forcedirected/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forcedirected", 3 | "version": "1.0.0", 4 | "main": "forcedirected.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x" 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/calendarheatmap/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calendarheatmap", 3 | "version": "1.0.0", 4 | "main": "calendarheatmap.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x" 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelcoords/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallelcoords", 3 | "version": "1.0.0", 4 | "main": "parallelcoords.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x" 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /django/minecraft-app/nav.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.utils import importlib 3 | from django.core.urlresolvers import reverse, resolve 4 | 5 | NAV = [ 6 | { 7 | "name": "MineCraft", 8 | "link": reverse("minecraft-app:home") # TODO don't hardcode the app name 9 | } 10 | 11 | ] -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/sunburst/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sunburst", 3 | "version": "1.0.0", 4 | "main": "sunburst.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "d3": "3.3.x", 8 | "underscore-nest": "0.1.x" 9 | }, 10 | "devDependencies": {} 11 | } -------------------------------------------------------------------------------- /django/minecraft-app/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from splunkdj.utility.views import render_template as render 3 | 4 | urlpatterns = patterns('', 5 | url(r'^home/$', 'minecraft-app.views.home', name='home'), 6 | url(r'^(?P.*)/$', 'minecraft-app.views.render_page', name='tmpl_render') 7 | ) 8 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/loggable_events/LoggableEvent.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.loggable_events; 2 | 3 | import com.splunk.sharedmc.Point3dLong; 4 | 5 | public interface LoggableEvent { 6 | 7 | /** 8 | * Gets a JSON String of this object. 9 | * 10 | * @return JSON representing this object. 11 | */ 12 | String toJson(); 13 | } 14 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/SplunkConnection.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc; 2 | 3 | /** 4 | * Has a way of connecting to Splunk and sends information to it. 5 | */ 6 | public interface SplunkConnection { 7 | 8 | /** 9 | * Sends a message to Splunk(s). 10 | * 11 | * @param message The message to send. 12 | */ 13 | public void sendToSplunk(String message); 14 | } 15 | -------------------------------------------------------------------------------- /default/app.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Splunk app configuration file 3 | # 4 | 5 | [install] 6 | state = enabled 7 | state_change_requires_restart = 0 8 | is_configured = 0 9 | build = 1 10 | 11 | [ui] 12 | is_visible = 1 13 | label = Minecraft 14 | 15 | [launcher] 16 | author = mpapale@splunk.com 17 | description = The Splunk App for Minecraft let's you visualize your Minecraft server data. 18 | version = 1.0 19 | 20 | [package] 21 | id = minecraft-app 22 | -------------------------------------------------------------------------------- /forge/src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "logtosplunk", 4 | "name": "Splunk for Minecraft", 5 | "description": "Adds amazingness with Splunk", 6 | "version": "${version}", 7 | "mcversion": "${mcversion}", 8 | "url": "https://github.com/splunk/minecraft-app", 9 | "updateUrl": "", 10 | "authorList": [""], 11 | "credits": "", 12 | "logoFile": "", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /default/data/ui/nav/default.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/Constants.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc; 2 | 3 | public class Constants { 4 | public static final int MAX_PLAYERS = 128; 5 | public static final String SPLUNK_HOST_PROP_KEY = "mod.splunk.connection.host"; 6 | public static final String SPLUNK_PORT_PROP_KEY = "mod.splunk.connection.port"; 7 | public static final String DEFAULT_HOST = "localhost"; 8 | public static final String DEFAULT_PORT = "8888"; 9 | 10 | private Constants() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/loggable_events/LoggableEventType.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.loggable_events; 2 | 3 | /** 4 | * Categories of loggable events. 5 | */ 6 | public enum LoggableEventType { 7 | PLAYER("PlayerEvent"), 8 | BLOCK("BlockEvent"), 9 | DEATH("DeathEvent"); 10 | 11 | private final String eventName; 12 | 13 | LoggableEventType(String eventName) { 14 | this.eventName = eventName; 15 | } 16 | 17 | public String getEventName() { 18 | return eventName; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/dashboard.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 30px; 3 | } 4 | 5 | .panel { 6 | background-color: #fff; 7 | border: 1px solid #ccc; 8 | border-radius: 4px; 9 | box-shadow: 0px 1px 1px rgba(0,0,0,0.08); 10 | overflow: visible; 11 | margin: 0 0 20px 0; 12 | } 13 | .panel-head { 14 | padding-top: 10px; 15 | padding-left: 20px; 16 | padding-right: 20px; 17 | } 18 | .panel-body { 19 | clear: both; 20 | padding-top: 10px; 21 | padding-left: 20px; 22 | padding-bottom: 20px; 23 | padding-right: 20px; 24 | } -------------------------------------------------------------------------------- /django/minecraft-app/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from splunkdj.decorators.render import render_to 3 | 4 | @render_to('minecraft-app:home.html') 5 | @login_required 6 | def home(request): 7 | return { 8 | "message": "Hello World from minecraft-app!", 9 | "app_name": "minecraft-app", 10 | "app_label": "Splunk for Minecraft" 11 | } 12 | 13 | @render_to() 14 | @login_required 15 | def render_page(request, tmpl="minecraft-app:home.html"): 16 | return { 17 | "TEMPLATE": "minecraft-app:%s.html" % tmpl, 18 | "app_label": "Splunk for Minecraft" 19 | } 20 | -------------------------------------------------------------------------------- /THIRD-PARTY-CREDITS.md: -------------------------------------------------------------------------------- 1 | ## Third-Party Software Credits 2 | 3 | The Splunk Web Framework Toolkit contains some libraries that were written by others and are being redistributed as part of the Splunk Web Framework Toolkit under their respective licenses. We wish to think the contributors to these projects. 4 | 5 | Minecraft images are Copyright Mojang and published under the Creative Commons 3.0 license. 6 | 7 | | Library | Version | URL | License | 8 | | ------- |:-------:|:---:|:-------:| 9 | | CraftBukkit | v1.6.x and 1.5.x | [bukkit/CraftBukkit](https://github.com/Bukkit/CraftBukkit) | [LGPL](https://github.com/Bukkit/CraftBukkit/blob/master/LGPL.txt) | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/calendarheatmap/calendarheatmap.css: -------------------------------------------------------------------------------- 1 | .splunk-toolkit-cal-heatmap { 2 | margin-left: 20px; 3 | margin-right: 20px; 4 | margin-top: 20px; 5 | } 6 | 7 | .splunk-toolkit-cal-heatmap .heatmap-container { 8 | overflow: hidden; 9 | } 10 | 11 | .splunk-toolkit-cal-heatmap .heatmap-container hr { 12 | margin-top: 0px; 13 | } 14 | 15 | .splunk-toolkit-cal-heatmap .heatmap-series-title { 16 | margin-bottom: 5px; 17 | display: inline-block; 18 | } 19 | 20 | .splunk-toolkit-cal-heatmap .heatmap-buttons { 21 | margin-bottom: 5px; 22 | float: right; 23 | display: inline; 24 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/bubblechart/bubblechart.css: -------------------------------------------------------------------------------- 1 | .splunk-toolkit-bubble-chart { 2 | font-family: arial; 3 | position: relative; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | .splunk-toolkit-bubble-chart svg { 9 | display: block; 10 | margin: 0px auto; 11 | } 12 | .splunk-toolkit-bubble-chart g { 13 | display: block; 14 | margin: 0px auto; 15 | } 16 | 17 | .bubble-chart-tooltip { 18 | position: absolute; 19 | background-color: #424242; 20 | border-radius: 3px 3px 3px 3px; 21 | padding: 7px; 22 | font-size: 1.0em; 23 | color: white; 24 | opacity:0; 25 | } 26 | 27 | .node:hover{ 28 | opacity: .7; 29 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/sankey/sankey.css: -------------------------------------------------------------------------------- 1 | .splunk-toolkit-sankey node rect { 2 | cursor: move; 3 | fill-opacity: 0.9; 4 | shape-rendering: crispEdges; 5 | } 6 | 7 | .splunk-toolkit-sankey node text { 8 | pointer-events: none; 9 | text-shadow: 0 1px 0 white; 10 | } 11 | 12 | .splunk-toolkit-sankey .link { 13 | fill: none; 14 | stroke: black; 15 | stroke-opacity: 0.2; 16 | } 17 | 18 | .splunk-toolkit-sankey .link:hover, .splunk-toolkit-sankey .link.hovering { 19 | stroke-opacity: 0.5; 20 | } 21 | 22 | .splunk-toolkit-sankey .link.my-selected { 23 | stroke: yellow; 24 | } 25 | 26 | .splunk-toolkit-sankey scrollable { 27 | overflow-y: auto; 28 | } 29 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/sunburst/sunburst.css: -------------------------------------------------------------------------------- 1 | .splunk-toolkit-sunburst { 2 | height: 100%; 3 | width: 100%; 4 | } 5 | 6 | .splunk-toolkit-sunburst path { 7 | stroke: #8D8C8C; 8 | stroke-width: 1; 9 | cursor: pointer; 10 | } 11 | 12 | .splunk-toolkit-sunburst text { 13 | font: 11px sans-serif; 14 | cursor: pointer; 15 | fill: #3F3F3F !important; 16 | } 17 | 18 | .splunk-toolkit-sunburst p#intro { 19 | text-align: center; 20 | margin: 1em 0; 21 | } 22 | 23 | .splunk-toolkit-sunburst #breadcrumbs { 24 | display: none; 25 | } 26 | 27 | .sunburstTooltip { 28 | position: absolute; 29 | background-color: #424242; 30 | border-radius: 3px 3px 3px 3px; 31 | padding: 7px; 32 | font-size: 1.0em; 33 | color: white; 34 | } -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelsets/parallelsets.css: -------------------------------------------------------------------------------- 1 | path { fill-opacity: .5; } 2 | .ribbon path { stroke-opacity: 0; } 3 | path.active { fill-opacity: .9; } 4 | .ribbon-mouse path { fill-opacity: 0; } 5 | 6 | .category-0 { fill: #1f77b4; stroke: #1f77b4; } 7 | .category-1 { fill: #ff7f0e; stroke: #ff7f0e; } 8 | .category-2 { fill: #2ca02c; stroke: #2ca02c; } 9 | .category-3 { fill: #d62728; stroke: #d62728; } 10 | .category-4 { fill: #9467bd; stroke: #9467bd; } 11 | .category-5 { fill: #8c564b; stroke: #8c564b; } 12 | .category-6 { fill: #e377c2; stroke: #e377c2; } 13 | .category-7 { fill: #7f7f7f; stroke: #7f7f7f; } 14 | .category-8 { fill: #bcbd22; stroke: #bcbd22; } 15 | .category-9 { fill: #17becf; stroke: #17becf; } 16 | 17 | rect { 18 | fill:white; 19 | opacity: 0; 20 | } 21 | 22 | line { 23 | stroke: #2E2E2E; 24 | } -------------------------------------------------------------------------------- /forge/src/main/doc/README: -------------------------------------------------------------------------------- 1 | Splunk logger for Minecraft Forge 0.1 Alpha 2 | 3 | The Splunk logger for Minecraft Forge provides additional logging 4 | functionality to the Minecraft Forge server. The logs are written 5 | to the console and the Minecraft server log, and to any number of Splunk 6 | TCP input ports declared in config.yml. 7 | 8 | The messages conform to Splunk's logging best practices: 9 | 10 | http://dev.splunk.com/view/logging-best-practices/SP-CAAADP6 11 | 12 | INSTALLATION 13 | 14 | **SPECIFY CONFIG DIRECTORY HERE*** In that directory write a config.yml 15 | file specifying what Splunk TCP ports to log to, e.g., 16 | 17 | splunks: localhost:10000, mc.splunk.local:10000, pq.5f.as.23:111 18 | 19 | Then start your Forge server or, if it is already running, run the command 20 | 'reload' in the server console *** DOES FORGE HAVE RELOAD?***. 21 | -------------------------------------------------------------------------------- /django/minecraft-app/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Splunk, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Splunking Minecraft. At work.""" 16 | 17 | __author__ = "Splunk Inc" 18 | __label__ = "minecraft-app" 19 | __license__ = "http://www.apache.org/licenses/LICENSE-2.0" 20 | __version_info__ = (0, 8, 0) 21 | __version__ = ".".join(map(str, __version_info__)) 22 | -------------------------------------------------------------------------------- /logtosplunk-plugin/src/main/doc/README: -------------------------------------------------------------------------------- 1 | Splunk logger for Minecraft Forge 1.0-SNAPSHOT 2 | 3 | To install, add the LogToSplunkPlugin build by this project 4 | to your plugins directory for Spigot and the mod directory for 5 | Forge (see respective docs in Spigot/Forge for more info). 6 | 7 | The Splunk logger for Minecraft Forge provides additional logging 8 | functionality to the Minecraft Forge server. The logs are written 9 | to the console/Minecraft server log and to the http collector at 10 | a given Splunk host:port. A splunk token must be generated for the 11 | collector and assigned to the property `splunk.craft.token`. 12 | 13 | The config file should be located here: `config/splunk.properties` 14 | where that path is relative to the directory minecraft is run from. 15 | 16 | The messages conform to Splunk's logging best practices: 17 | 18 | http://dev.splunk.com/view/logging-best-practices/SP-CAAADP6 19 | 20 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelcoords/contrib/d3-parcoords.css: -------------------------------------------------------------------------------- 1 | .parcoords > svg, .parcoords > canvas { 2 | font: 14px sans-serif; 3 | position: absolute; 4 | } 5 | .parcoords > canvas { 6 | pointer-events: none; 7 | } 8 | .parcoords rect.background { 9 | fill: transparent; 10 | } 11 | .parcoords rect.background:hover { 12 | fill: rgba(120,120,120,0.2); 13 | } 14 | .parcoords .resize rect { 15 | fill: rgba(0,0,0,0.1); 16 | } 17 | .parcoords rect.extent { 18 | fill: rgba(255,255,255,0.25); 19 | stroke: rgba(0,0,0,0.6); 20 | } 21 | .parcoords .axis line, .parcoords .axis path { 22 | fill: none; 23 | stroke: #222; 24 | shape-rendering: crispEdges; 25 | } 26 | .parcoords canvas { 27 | opacity: 1; 28 | -moz-transition: opacity 0.3s; 29 | -webkit-transition: opacity 0.3s; 30 | -o-transition: opacity 0.3s; 31 | } 32 | .parcoords canvas.faded { 33 | opacity: 0.25; 34 | } 35 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/underscore-nest/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Irene Ros 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/calendarheatmap/contrib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Tyler Kellen, contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/d3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Michael Bostock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Michael Bostock may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelcoords/contrib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Kai Chang 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Kai Chang may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/sankey/contrib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Michael Bostock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Michael Bostock may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelsets/contrib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Jason Davies 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Jason Davies may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL JASON DAVIES BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 25 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/custom.css: -------------------------------------------------------------------------------- 1 | .grey_border > div { 2 | border: 1px solid grey; 3 | } 4 | 5 | .row_margins > div { 6 | margin-top: 30px; 7 | margin-right: 30px; 8 | margin-left: 30px; 9 | } 10 | 11 | .row_margins_top > div { 12 | margin-top: 30px; 13 | } 14 | 15 | .ore_grid { 16 | border: 1px solid grey; 17 | padding: 15px; 18 | background: white; 19 | } 20 | 21 | .ore_row { 22 | margin-top: 15px; 23 | } 24 | 25 | .ore_row_last { 26 | margin-bottom: 15px; 27 | } 28 | 29 | .mcheader > div { 30 | border: 1px solid grey; 31 | margin-top: 30px; 32 | margin-left: 30px; 33 | margin-right: 30px; 34 | background: white; 35 | padding: 10px; 36 | } 37 | 38 | .white_background > div { 39 | background: white; 40 | } 41 | 42 | .active_player_list > div { 43 | min-height: 100px; 44 | } 45 | 46 | 47 | .mcmap { 48 | border: 2px solid grey; 49 | height:800px; 50 | width:100%; 51 | } 52 | 53 | .mcmap img { 54 | max-width: none; 55 | } 56 | .mcmap label { 57 | width: auto; 58 | display:inline; 59 | } 60 | .legend { 61 | display: block; 62 | margin: auto; 63 | max-height: 800px; 64 | } 65 | 66 | .legend li { 67 | padding: 10px; 68 | } 69 | 70 | .legend .color-block { 71 | height: 50px; 72 | width: 50px; 73 | border: 1px solid grey; 74 | border-radius: 3px; 75 | } 76 | 77 | .player_dropdown { 78 | margin-top: 15px; 79 | } 80 | 81 | .chart_title { 82 | float : center; 83 | } 84 | 85 | .bottom_border { 86 | margin-bottom : 30px; 87 | } 88 | 89 | #example-sunburst { 90 | min-height : 600px; 91 | } -------------------------------------------------------------------------------- /README-PLUGMOD.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Run `mvn clean package -Pinclude-forge` from the root directory to package the LogToSplunk plugmod. 4 | 5 | The forge mod / spigot plugin that this project builds can be found in `logtosplunk-plugin/target` and is called `logtosplunk-plugin-{version}.jar` 6 | 7 | Currently the supported veresion of minecraft are 1.8.7 for spigot and 1.8-1504 for forge; possible that newer versions are compatible with the plugmod. 8 | 9 | # Splunk Logging Library 10 | 11 | This library is currently hacked into the build because I don't have access to the splunk maven remote repo that stores this artifact. Currently the correctly-named jar needs to live in the `lib` directory where it will be automatically installed by maven during the build. This way of including the jar needs to be treated as a regular dependency when the aftifact becomes public. Email a Splunk developer to handle any NDAs etc. to get the logging library jar. 12 | 13 | # Config 14 | 15 | Place properties at `config/splunk.properties` (works for both plugin and mod); path is relative to directory where minecraft is executed from (generally where minecraft server jar is...). 16 | 17 | The splunk host, port, and tokencan be configured with the properties: `splunk.craft.connection.host`, `splunk.craft.connection.port` and `splunk.craft.token`; for example: 18 | 19 | ``` 20 | splunk.craft.connection.host=127.0.0.1 21 | splunk.craft.connection.port=8088 22 | splunk.craft.token=12345678-1234-5678-1234-123456789012 23 | splunk.craft.enable.consolelog=true 24 | ``` 25 | 26 | Note that the values listed here are the defaults. 27 | 28 | Further configuration of Splunk via the splunk UI is needed as well as installing the LogToSplunk app (not currently covered in this readme). 29 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/resultsview/resultsview.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var _ = require('underscore'); 4 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview"); 5 | 6 | var ResultsViewer = SimpleSplunkView.extend({ 7 | 8 | className: "splunk-toolkit-results-viewer", 9 | 10 | options : { 11 | "number" : 20, 12 | }, 13 | 14 | output_mode: "json", 15 | 16 | createView: function() { 17 | 18 | return true; 19 | }, 20 | 21 | formatData: function(data){ 22 | var number = this.settings.get("number"); 23 | if (data.length > number) 24 | data = _.first(data, number); 25 | 26 | return data; 27 | }, 28 | 29 | updateView: function(viz, data) { 30 | var rawFields = this.resultsModel.data().fields; 31 | 32 | var fields = rawFields; 33 | if (!rawFields || rawFields.length === 0) { 34 | fields = _.keys(data[0]); 35 | } 36 | else if (rawFields && _.isObject(rawFields[0])) { 37 | fields = _.pluck(this.resultsModel.data().fields, "name"); 38 | } 39 | 40 | this.$el.html(''); 41 | this.$el.append( 42 | '
Fields
'+ 43 | '
' + fields + '
'+ 44 | '
Results (may be truncated)
'+ 45 | '
' + JSON.stringify(data, undefined, 2) + '
'); 46 | }, 47 | 48 | getData: function(){ 49 | return this.resultsModel.data().results; 50 | } 51 | }); 52 | return ResultsViewer; 53 | }); -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/loggable_events/LoggableDeathEvent.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.loggable_events; 2 | 3 | import com.splunk.sharedmc.Point3dLong; 4 | 5 | /** 6 | * Almost pojo with fields for information that might be associated with a block event. 7 | */ 8 | public class LoggableDeathEvent extends AbstractLoggableEvent { 9 | public static final String VICTIM = "victim"; 10 | public static final String KILLER = "killer"; 11 | public static final String DAMAGE_SOURCE = "damage_source"; 12 | 13 | /** 14 | * Constructor. 15 | * 16 | * @param action The type of block action this represents, e.g. 'break'. 17 | */ 18 | public LoggableDeathEvent(DeathEventAction action, long gameTime, String worldName, Point3dLong location, String killer, String victim, String damageSource) { 19 | super(LoggableEventType.DEATH, gameTime, worldName, location); 20 | this.addField(ACTION, action.asString()); 21 | this.addField(VICTIM, victim); 22 | this.addField(KILLER, killer); 23 | this.addField(DAMAGE_SOURCE, damageSource); 24 | } 25 | 26 | /** 27 | * Different types of actions that can occur as part of a DeathEvent. 28 | */ 29 | public enum DeathEventAction { 30 | MOB_DIED("mob_died"), 31 | PLAYER_DIED("player_died"); 32 | 33 | /** 34 | * The name of the action. 35 | */ 36 | private final String action; 37 | 38 | DeathEventAction(String action) { 39 | this.action = action; 40 | } 41 | 42 | /** 43 | * String representation of the action. 44 | * 45 | * @return The action in friendly String format. 46 | */ 47 | public String asString() { 48 | return action; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/calendarheatmap/contrib/cal-heatmap.css: -------------------------------------------------------------------------------- 1 | /* Cal-HeatMap CSS */ 2 | 3 | 4 | .graph 5 | { 6 | clear: both; 7 | display: block; 8 | font-family: "Lucida Grande", Lucida, Verdana, sans-serif; 9 | overflow: hidden; 10 | } 11 | 12 | .graph-label 13 | { 14 | fill: #999; 15 | font-size: 10px 16 | } 17 | 18 | .graph, .graph-legend rect { 19 | shape-rendering: crispedges 20 | } 21 | 22 | .graph-rect 23 | { 24 | fill: #ededed; 25 | } 26 | 27 | .graph rect:hover 28 | { 29 | stroke: #000; 30 | stroke-width: 1px 31 | } 32 | 33 | .subdomain-text { 34 | font-size: 8px; 35 | fill: #999; 36 | pointer-events: none; 37 | } 38 | 39 | .hover_cursor:hover { 40 | cursor: pointer; 41 | } 42 | 43 | .qi { 44 | background-color: #999; 45 | fill: #999; 46 | } 47 | 48 | .q0 49 | { 50 | background-color: #fff; 51 | fill: #fff; 52 | stroke: #ededed 53 | } 54 | 55 | .q1 56 | { 57 | background-color: #dae289; 58 | fill: #dae289 59 | } 60 | 61 | .q2 62 | { 63 | background-color: #cedb9c; 64 | fill: #9cc069 65 | } 66 | 67 | .q3 68 | { 69 | background-color: #b5cf6b; 70 | fill: #669d45 71 | } 72 | 73 | .q4 74 | { 75 | background-color: #637939; 76 | fill: #637939 77 | } 78 | 79 | .q5 80 | { 81 | background-color: #3b6427; 82 | fill: #3b6427 83 | } 84 | 85 | rect.highlight 86 | { 87 | stroke:#444; 88 | stroke-width:1; 89 | } 90 | 91 | text.highlight 92 | { 93 | fill: #444; 94 | } 95 | 96 | rect.now 97 | { 98 | stroke: red; 99 | } 100 | 101 | text.now 102 | { 103 | fill: red; 104 | font-weight: 800; 105 | } 106 | 107 | .domain-background { 108 | fill: none; 109 | shape-rendering: crispedges; 110 | } 111 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/loggable_events/AbstractLoggableEvent.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.loggable_events; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.splunk.logging.SplunkCimLogEvent; 6 | import com.splunk.sharedmc.Point3dLong; 7 | 8 | /** 9 | * Classes extending this benefit from a convenient way to get a Json representation, time of creation and event type, 10 | * world name, coordinates and location. 11 | */ 12 | public class AbstractLoggableEvent extends SplunkCimLogEvent implements LoggableEvent { 13 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 14 | public static final String PLAYER_NAME = "player"; 15 | public static final String CAUSE = "cause"; 16 | public static final String ACTION = "action"; 17 | 18 | /** 19 | * Constructor. Enforces that subclasses must have a loggable event type. 20 | * 21 | * @param type The type of event that this is. 22 | */ 23 | public AbstractLoggableEvent(LoggableEventType type, long worldTime, String worldName, Point3dLong coordinates) { 24 | super(type.getEventName(), ""); 25 | 26 | this.addField("time", System.currentTimeMillis()); 27 | 28 | 29 | this.addField("game_time", worldTime); 30 | if(worldName != null) { 31 | this.addField("world", worldName); 32 | } 33 | this.addField("xCoord", coordinates.xCoord); 34 | this.addField("yCoord", coordinates.yCoord); 35 | this.addField("zCoord", coordinates.zCoord); 36 | } 37 | 38 | @Override 39 | public String toJson() { 40 | return gson.toJson(this); 41 | } 42 | 43 | @Override 44 | public void addField(String key, Object value){ 45 | if(value == null){ 46 | return; 47 | } 48 | super.addField(key, value); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/loggable_events/LoggableBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.loggable_events; 2 | 3 | import com.splunk.sharedmc.Point3dLong; 4 | 5 | /** 6 | * Almost pojo with fields for information that might be associated with a block event. 7 | */ 8 | public class LoggableBlockEvent extends AbstractLoggableEvent { 9 | public static final String BASE_TYPE = "base_type"; 10 | public static final String BLOCK_NAME = "block_type"; 11 | 12 | /** 13 | * Constructor. 14 | * 15 | * @param action The type of block action this represents, e.g. 'break'. 16 | */ 17 | public LoggableBlockEvent(BlockEventAction action, long gameTime, String worldName, Point3dLong location, String blockName, String playerName) { 18 | super(LoggableEventType.BLOCK, gameTime, worldName, location); 19 | setBlockName(blockName); 20 | setPlayerName(playerName); 21 | this.addField(ACTION, action.asString().toUpperCase()); 22 | } 23 | 24 | public void setPlayerName(String playerName) { 25 | this.addField(PLAYER_NAME, playerName); 26 | } 27 | 28 | public void setBlockName(String blockName) { 29 | this.addField(BLOCK_NAME, blockName); 30 | } 31 | 32 | public void setBaseType(String baseType) { 33 | this.addField(BASE_TYPE, baseType); 34 | } 35 | 36 | /** 37 | * Different types of actions that can occur as part of a BlockEvent. 38 | */ 39 | public enum BlockEventAction { 40 | BREAK("block_broken"), 41 | PLACE("block_placed"); 42 | 43 | /** 44 | * The name of the action. 45 | */ 46 | private final String action; 47 | 48 | BlockEventAction(String action) { 49 | this.action = action; 50 | } 51 | 52 | /** 53 | * String representation of the action. 54 | * 55 | * @return The action in friendly String format. 56 | */ 57 | public String asString() { 58 | return action; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/forcedirected/forcedirected.css: -------------------------------------------------------------------------------- 1 | .splunk-toolkit-force-directed { 2 | overflow: hidden; 3 | font-family: arial; 4 | } 5 | 6 | .splunk-toolkit-force-directed circle.node { 7 | stroke: #fff; 8 | stroke-width: 1.5px; 9 | } 10 | 11 | .splunk-toolkit-force-directed .link, .splunk-toolkit-force-directed #arrowEnd { 12 | stroke: #999; 13 | stroke-opacity: .6; 14 | fill: none; 15 | } 16 | .splunk-toolkit-force-directed #arrowEnd { 17 | fill: #999; 18 | } 19 | 20 | .splunk-toolkit-force-directed circle.node { 21 | stroke: #fff; 22 | stroke-width: 1.5px; 23 | } 24 | 25 | .splunk-toolkit-force-directed circle.nodeHighlight, 26 | .splunk-toolkit-force-directed circle.highlight { 27 | stroke-width: 2px; 28 | stroke: #E89595; 29 | } 30 | 31 | .linkHighlight { 32 | stroke: red !important; 33 | } 34 | 35 | .splunk-toolkit-force-directed circle.nodeHighlight.highlight { 36 | stroke-width: 3px; 37 | } 38 | 39 | 40 | .splunk-toolkit-force-directed line.link { 41 | stroke: #999; 42 | stroke-opacity: .6; 43 | } 44 | 45 | .splunk-toolkit-force-directed #chart { 46 | width: 100%; 47 | height: 100%; 48 | } 49 | 50 | .splunk-toolkit-force-directed #tooltipContainer { 51 | border: 1px solid hsl(0, 0%, 80%); 52 | position: absolute; 53 | min-width: 200px; 54 | min-height: 50px; 55 | border-radius:3px; 56 | z-index:100; 57 | background: #3A3A3A; 58 | padding: 10px; 59 | color: white; 60 | top:50px; 61 | } 62 | 63 | .splunk-toolkit-force-directed .group-swatch { 64 | width:20px; 65 | height:20px; 66 | float:left; 67 | margin:2px; 68 | margin-right: 10px 69 | } 70 | 71 | .splunk-toolkit-force-directed .group-name { 72 | padding-top: 5px; 73 | } 74 | 75 | .splunk-toolkit-force-directed .tooltipLabel { 76 | font-weight:bold; 77 | padding-right:5px; 78 | } 79 | 80 | .splunk-toolkit-force-directed .tooltipRow { 81 | margin-bottom:10px; 82 | } 83 | 84 | .splunk-toolkit-force-directed .panCursor { 85 | cursor: move; 86 | } -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/Point3dLong.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc; 2 | 3 | /** 4 | * Rolling our own point because we don't need decimals for position... 5 | */ 6 | public class Point3dLong { 7 | public final long xCoord; 8 | public final long yCoord; 9 | public final long zCoord; 10 | 11 | /** 12 | * Constructs a new point. 13 | */ 14 | public Point3dLong(double x, double y, double z) { 15 | //Noticed performance issues on using Math.Round, apparently slower than this in Java 6 according to: 16 | // http://stackoverflow.com/questions/12091014/what-is-the-most-efficient-way-to-round-a-float-value-to-the 17 | // -nearest-integer-in , maybe add in http://labs.carrotsearch.com/junit-benchmarks.html to investigate 18 | 19 | xCoord = (long) (x + 0.5); 20 | yCoord = (long) (y + 0.5); 21 | zCoord = (long) (z + 0.5); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "Point3dInt{" + 27 | "xCoord=" + xCoord + 28 | ", yCoord=" + yCoord + 29 | ", zCoord=" + zCoord + 30 | '}'; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) { 36 | return true; 37 | } 38 | if (o == null || getClass() != o.getClass()) { 39 | return false; 40 | } 41 | 42 | final Point3dLong that = (Point3dLong) o; 43 | 44 | if (xCoord != that.xCoord) { 45 | return false; 46 | } 47 | if (yCoord != that.yCoord) { 48 | return false; 49 | } 50 | if (zCoord != that.zCoord) { 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | int result = (int) (xCoord ^ (xCoord >>> 32)); 60 | result = 31 * result + (int) (yCoord ^ (yCoord >>> 32)); 61 | result = 31 * result + (int) (zCoord ^ (zCoord >>> 32)); 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /spigot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | splunk.minecraft.app 6 | com.splunk 7 | 1.0-SNAPSHOT 8 | 9 | 4.0.0 10 | 11 | spigot 12 | 13 | 14 | 15 | spigot-repo 16 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 17 | 18 | 19 | 20 | 21 | 22 | net.md-5 23 | bungeecord-chat 24 | 1.21-R0.4 25 | 26 | 27 | 28 | org.spigotmc 29 | spigot-api 30 | 1.21.10-R0.1-SNAPSHOT 31 | 32 | 33 | 34 | org.bukkit 35 | bukkit 36 | 1.21.10 37 | system 38 | ${HOME}/Code/spigot/craftbukkit-1.21.10.jar 39 | 40 | 41 | com.github.cryptomorin 42 | XSeries 43 | 13.5.1 44 | 45 | 46 | 47 | 48 | 49 | 50 | com.splunk 51 | shared-mc 52 | ${project.version} 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.splunk 6 | splunk.minecraft.app 7 | 1.0-SNAPSHOT 8 | 9 | spigot 10 | shared-mc 11 | forge 12 | logtosplunk-plugin 13 | 14 | pom 15 | 16 | 17 | 18 | mvnrepository-central 19 | mvnrepository.com Central 20 | https://repo1.maven.org/maven2/ 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.apache.logging.log4j 28 | log4j-api 29 | 2.25.1 30 | 31 | 32 | org.apache.logging.log4j 33 | log4j-core 34 | 2.25.1 35 | 36 | 37 | com.splunk.logging 38 | splunk-library-javalogging 39 | 1.11.8 40 | 41 | 42 | junit 43 | junit 44 | 4.8.2 45 | test 46 | 47 | 48 | com.googlecode.json-simple 49 | json-simple 50 | 1.1 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-compiler-plugin 59 | 2.3.2 60 | 61 | 1.8 62 | 1.8 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/event_loggers/AbstractEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.event_loggers; 2 | 3 | import java.util.Properties; 4 | 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import com.splunk.sharedmc.SingleSplunkConnection; 9 | import com.splunk.sharedmc.loggable_events.LoggableEvent; 10 | 11 | /** 12 | * EventLoggers log to the Minecraft server console and send data to Splunk. 13 | */ 14 | public class AbstractEventLogger { 15 | public static final String LOGGER_NAME = "LogToSplunk"; 16 | 17 | public static final String LOG_EVENTS_TO_CONSOLE_PROP_KEY = "splunk.craft.enable.consolelog"; 18 | public static final String SPLUNK_HOST = "splunk.craft.connection.host"; 19 | public static final String SPLUNK_PORT = "splunk.craft.connection.port"; 20 | public static final String SPLUNK_TOKEN = "splunk.craft.token"; 21 | 22 | protected static final Logger logger = LogManager.getLogger(LOGGER_NAME); 23 | 24 | private static SingleSplunkConnection connection; 25 | 26 | /** 27 | * If true, events will be logged to the server console. 28 | */ 29 | private static boolean logEventsToConsole; 30 | private static String host; 31 | private static int port; 32 | private static String token; 33 | 34 | public AbstractEventLogger(Properties properties) { 35 | // brittle way to do this 36 | if (connection == null) { 37 | logEventsToConsole = Boolean.valueOf(properties.getProperty(LOG_EVENTS_TO_CONSOLE_PROP_KEY, "true")); 38 | host = properties.getProperty(SPLUNK_HOST, "127.0.0.1"); 39 | port = Integer.valueOf(properties.getProperty(SPLUNK_PORT, "8088")); 40 | token = properties.getProperty(SPLUNK_TOKEN); 41 | if(token == null){ 42 | throw new IllegalArgumentException("The property `splunk.craft.token` must be set with the value of a" + 43 | " splunk token in order to use the Splunk minecraft plugin/mod!"); 44 | } 45 | 46 | connection = new SingleSplunkConnection(host, port, token, true); 47 | } 48 | } 49 | 50 | /** 51 | * Logs via slf4j-simple and forwards the message to the message preparer. 52 | * 53 | * @param loggable The message to log. 54 | */ 55 | protected void logAndSend(LoggableEvent loggable) { 56 | String message = loggable.toString().replace("\"", "").replaceAll("\\r\\n", ""); 57 | if(logEventsToConsole) { 58 | logger.info(message); 59 | } 60 | connection.sendToSplunk(loggable.toJson()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /forge/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /spigot/src/main/java/com/splunk/spigot/eventloggers/BlockEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.spigot.eventloggers; 2 | 3 | import java.util.Properties; 4 | 5 | import org.bukkit.Location; 6 | import org.bukkit.Material; 7 | import org.bukkit.World; 8 | import org.bukkit.block.Block; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.block.data.BlockData; 12 | import org.bukkit.event.block.BlockBreakEvent; 13 | import org.bukkit.event.block.BlockEvent; 14 | import org.bukkit.event.block.BlockPlaceEvent; 15 | 16 | import com.splunk.sharedmc.Point3dLong; 17 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger; 18 | import com.splunk.sharedmc.loggable_events.LoggableBlockEvent; 19 | import com.splunk.sharedmc.loggable_events.LoggableBlockEvent.BlockEventAction; 20 | 21 | /** 22 | * Handles the logging of block events. 23 | */ 24 | public class BlockEventLogger extends AbstractEventLogger implements Listener { 25 | 26 | public BlockEventLogger(Properties properties) { 27 | super(properties); 28 | } 29 | 30 | /** 31 | * Captures Block BreakEvents. 32 | * 33 | * @param event The captured BreakEvent. 34 | */ 35 | @EventHandler 36 | public void captureBreakEvent(BlockBreakEvent event) { 37 | logAndSend(getLoggableBlockBreakPlaceEvent(BlockEventAction.BREAK, event)); 38 | } 39 | 40 | /** 41 | * Captures Block PlaceEvents and sends them to the message preparer. 42 | * 43 | * @param event The captured PlaceEvent. 44 | */ 45 | @EventHandler 46 | public void capturePlaceEvent(BlockPlaceEvent event) { 47 | logAndSend(getLoggableBlockBreakPlaceEvent(BlockEventAction.PLACE, event)); 48 | } 49 | 50 | private LoggableBlockEvent getLoggableBlockBreakPlaceEvent(BlockEventAction action, BlockEvent event) { 51 | 52 | final Block block = event.getBlock(); 53 | final Location location = event.getBlock().getLocation(); 54 | 55 | final String name = block.getBlockData().getMaterial().name(); 56 | final String baseType = block.getType().name(); 57 | final World w = block.getWorld(); 58 | 59 | final Point3dLong coords = new Point3dLong(location.getX(), location.getY(), location.getZ()); 60 | String playerName = null; 61 | 62 | if (event instanceof BlockBreakEvent) { 63 | playerName = ((BlockBreakEvent) event).getPlayer().getName(); 64 | } else if (event instanceof BlockPlaceEvent) { 65 | playerName = ((BlockPlaceEvent) event).getPlayer().getName(); 66 | } 67 | 68 | return new LoggableBlockEvent(action, w.getFullTime(), w.getWorldType().getName(), coords, name, playerName); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spigot/src/main/java/com/splunk/spigot/LogToSplunkPlugin.java: -------------------------------------------------------------------------------- 1 | package com.splunk.spigot; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.util.Properties; 6 | 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | import org.bukkit.Location; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.plugin.java.JavaPlugin; 12 | 13 | import com.splunk.sharedmc.Point3dLong; 14 | import com.splunk.spigot.eventloggers.BlockEventLogger; 15 | import com.splunk.spigot.eventloggers.DeathEventLogger; 16 | import com.splunk.spigot.eventloggers.PlayerEventLogger; 17 | 18 | public class LogToSplunkPlugin extends JavaPlugin implements Listener { 19 | public static final String MODID = "logtosplunk"; 20 | public static final String VERSION = "1.0-SNAPSHOT"; 21 | public static final String NAME = "Splunk for Minecraft"; 22 | public static final String SPLUNK_PROPERTIES = "/config/splunk.properties"; 23 | 24 | private Properties properties; 25 | 26 | private static final Logger logger = LogManager.getLogger(LogToSplunkPlugin.class.getName()); 27 | 28 | /** 29 | * Called when the mod is initialized. 30 | */ 31 | @Override 32 | public void onEnable() { 33 | // Could probably move this to the AbstractEventLogger in shared 34 | properties = new Properties(); 35 | final String path = System.getProperty("user.dir") + SPLUNK_PROPERTIES; 36 | try (final FileReader reader = new FileReader(new File(path))) { 37 | 38 | properties.load(reader); 39 | } catch (final Exception e) { 40 | logger.warn( 41 | String.format( 42 | "Unable to load properties for LogToSplunkMod at %s! Default values will be used.", path), 43 | e); 44 | } 45 | 46 | getServer().getPluginManager().registerEvents(new BlockEventLogger(properties), this); 47 | getServer().getPluginManager().registerEvents(new DeathEventLogger(properties), this); 48 | getServer().getPluginManager().registerEvents(new PlayerEventLogger(properties), this); 49 | 50 | logAndSend("Splunk for Minecraft initialized."); 51 | } 52 | 53 | /** 54 | * Logs and sends messages to be prepared for Splunk. 55 | * 56 | * @param message The message to log. 57 | */ 58 | private void logAndSend(String message) { 59 | logger.info(message); 60 | } 61 | 62 | // nullable... 63 | public static Point3dLong locationAsPoint(Location location) { 64 | if (location == null) { 65 | return null; 66 | } 67 | return new Point3dLong(location.getX(), location.getY(), location.getZ()); 68 | } 69 | } -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/loggable_events/LoggablePlayerEvent.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc.loggable_events; 2 | 3 | import com.splunk.sharedmc.Point3dLong; 4 | 5 | /** 6 | * Almost pojo with fields for information that might be associated with a player event. 7 | */ 8 | public class LoggablePlayerEvent extends AbstractLoggableEvent { 9 | public static final String MESSAGE = "message"; 10 | public static final String REASON = "reason"; 11 | 12 | /** 13 | * Constructor. 14 | * 15 | * @param action The type of player action this represents, e.g. 'player_disconnected'. 16 | */ 17 | public LoggablePlayerEvent(PlayerEventAction action, long gameTime, String worldName, Point3dLong location) { 18 | super(LoggableEventType.PLAYER, gameTime, worldName, location); 19 | this.addField(ACTION, action.asString()); 20 | } 21 | 22 | public LoggablePlayerEvent setPlayerName(String playerName) { 23 | this.addField(PLAYER_NAME, playerName); 24 | return this; 25 | } 26 | 27 | public LoggablePlayerEvent setMessage(String message) { 28 | this.addField(MESSAGE, message); 29 | return this; 30 | } 31 | 32 | public LoggablePlayerEvent setReason(String reason) { 33 | this.addField(REASON, reason); 34 | return this; 35 | } 36 | 37 | public LoggablePlayerEvent setTo(Point3dLong to) { 38 | if(to == null){ 39 | return this; 40 | } 41 | this.addField("to_x", to.xCoord); 42 | this.addField("to_y", to.yCoord); 43 | this.addField("to_z", to.zCoord); 44 | return this; 45 | } 46 | 47 | public LoggablePlayerEvent setFrom(Point3dLong from) { 48 | if(from == null){ 49 | return this; 50 | } 51 | this.addField("from_x", from.xCoord); 52 | this.addField("from_y", from.yCoord); 53 | this.addField("from_z", from.zCoord); 54 | 55 | return this; 56 | } 57 | 58 | /** 59 | * Different types of actions that can occur as part of a PlayerEvent. 60 | */ 61 | public enum PlayerEventAction { 62 | PLAYER_CONNECT("player_connect"), 63 | PLAYER_DISCONNECT("player_disconnect"), 64 | CHAT("chat"), 65 | LOCATION("move"); 66 | 67 | /** 68 | * The name of the action. 69 | */ 70 | private final String action; 71 | 72 | PlayerEventAction(String action) { 73 | this.action = action; 74 | } 75 | 76 | /** 77 | * String representation of the action. 78 | * 79 | * @return The action in friendly String format. 80 | */ 81 | public String asString() { 82 | return action; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /shared-mc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | splunk.minecraft.app 6 | com.splunk 7 | 1.0-SNAPSHOT 8 | 9 | 4.0.0 10 | 11 | 12 | 13 | mvnrepository-central 14 | mvnrepository.com Central 15 | https://repo1.maven.org/maven2/ 16 | 17 | 18 | splunk-releases 19 | Splunk Artifactory 20 | https://splunk.jfrog.io/splunk/ext-releases-local/ 21 | 22 | 23 | 24 | shared-mc 25 | 26 | 27 | org.apache.logging.log4j 28 | log4j-api 29 | 2.25.1 30 | 31 | 32 | org.apache.logging.log4j 33 | log4j-core 34 | 2.25.1 35 | 36 | 37 | org.apache.httpcomponents.core5 38 | httpcore5 39 | 5.3.6 40 | 41 | 42 | org.apache.httpcomponents.client5 43 | httpclient5 44 | 5.5.1 45 | 46 | 47 | com.google.guava 48 | guava 49 | 33.5.0-jre 50 | 51 | 52 | com.google.code.gson 53 | gson 54 | 2.13.2 55 | 56 | com.splunk.logging 57 | splunk-library-javalogging 58 | 1.11.8 59 | 60 | 61 | com.googlecode.json-simple 62 | json-simple 63 | 1.1 64 | 65 | 66 | 67 | junit 68 | junit 69 | test 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /forge/build.gradle: -------------------------------------------------------------------------------- 1 | task wrapper(type: Wrapper) { 2 | gradleVersion = '2.5' 3 | } 4 | 5 | buildscript { 6 | repositories { 7 | mavenCentral() 8 | mavenLocal() 9 | 10 | maven { 11 | url "https://plugins.gradle.org/m2/" 12 | } 13 | maven { 14 | name = "sonatype" 15 | url = "https://oss.sonatype.org/content/repositories/snapshots/" 16 | } 17 | } 18 | dependencies { 19 | classpath 'gradle.plugin.net.minecraftforge.gradle:ForgeGradle:2.0.0' 20 | classpath 'org.mockito:mockito-all:1.8.4' 21 | classpath 'junit:junit:4.4' 22 | classpath 'org.jmockit:jmockit:1.13' 23 | } 24 | } 25 | 26 | apply plugin: 'net.minecraftforge.gradle.forge' 27 | 28 | group = "com.splunk" 29 | archivesBaseName = "logtosplunk-forge" 30 | version = "1.0-SNAPSHOT" 31 | 32 | minecraft { 33 | version = "1.8-11.14.3.1504" 34 | runDir = "eclipse" 35 | 36 | // the mappings can be changed at any time, and must be in the following format. 37 | // snapshot_YYYYMMDD snapshot are built nightly. 38 | // stable_# stables are built at the discretion of the MCP team. 39 | // Use non-default mappings at your own risk. they may not allways work. 40 | // simply re-run your setup task after changing the mappings to update your workspace. 41 | mappings = "snapshot_nodoc_20150826" 42 | } 43 | 44 | 45 | apply plugin: 'java' 46 | 47 | repositories { 48 | mavenCentral() 49 | mavenLocal() 50 | maven { 51 | url "https://plugins.gradle.org/m2/" 52 | } 53 | maven { 54 | name = "sonatype" 55 | url = "https://oss.sonatype.org/content/repositories/snapshots/" 56 | } 57 | } 58 | 59 | 60 | dependencies { 61 | compile(group: 'com.splunk.logging', name: 'splunk-library-javalogging', version: '1.5.0') 62 | compile 'com.splunk:shared-mc:1.0-SNAPSHOT' 63 | compile 'org.slf4j:slf4j-api:1.7.6' 64 | compile 'org.apache.logging.log4j:log4j-api:2.3' 65 | compile 'org.apache.logging.log4j:log4j-core:2.3' 66 | 67 | testCompile 'junit:junit:4.4' 68 | testCompile 'org.mockito:mockito-all:1.8.4' 69 | testCompile 'org.jmockit:jmockit:1.13' 70 | } 71 | 72 | processResources 73 | { 74 | // this will ensure that this task is redone when the versions change. 75 | inputs.property "version", project.version 76 | inputs.property "mcversion", project.minecraft.version 77 | 78 | // replace stuff in mcmod.info, nothing else 79 | from(sourceSets.main.resources.srcDirs) { 80 | include 'mcmod.info' 81 | 82 | // replace version and mcversion 83 | expand 'version': project.version, 'mcversion': project.minecraft.version 84 | } 85 | 86 | // copy everything else, thats not the mcmod.info 87 | from(sourceSets.main.resources.srcDirs) { 88 | exclude 'mcmod.info' 89 | } 90 | } -------------------------------------------------------------------------------- /django/minecraft-app/templatetags/splunk_wftoolkit.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from splunkdj.templatetags.tagutils import component_context 3 | 4 | register = template.Library() 5 | 6 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 7 | def sankey(context, id, *args, **kwargs): 8 | return component_context( 9 | context, 10 | "splunk-toolkit-sankey", 11 | id, 12 | "view", 13 | "minecraft-app/components/sankey/sankey", 14 | kwargs 15 | ) 16 | 17 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 18 | def calendarheatmap(context, id, *args, **kwargs): 19 | return component_context( 20 | context, 21 | "calendarheatmap", 22 | id, 23 | "view", 24 | "minecraft-app/components/calendarheatmap/calendarheatmap", 25 | kwargs 26 | ) 27 | 28 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 29 | def bubblechart(context, id, *args, **kwargs): 30 | return component_context( 31 | context, 32 | "splunk-toolkit-bubble-chart", 33 | id, 34 | "view", 35 | "minecraft-app/components/bubblechart/bubblechart", 36 | kwargs 37 | ) 38 | 39 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 40 | def forcedirected(context, id, *args, **kwargs): 41 | return component_context( 42 | context, 43 | "forcedirected", 44 | id, 45 | "view", 46 | "minecraft-app/components/forcedirected/forcedirected", 47 | kwargs 48 | ) 49 | 50 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 51 | def parallelsets(context, id, *args, **kwargs): 52 | return component_context( 53 | context, 54 | "parallelsets", 55 | id, 56 | "view", 57 | "minecraft-app/components/parallelsets/parallelsets", 58 | kwargs 59 | ) 60 | 61 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 62 | def sunburst(context, id, *args, **kwargs): 63 | return component_context( 64 | context, 65 | "sunburst", 66 | id, 67 | "view", 68 | "minecraft-app/components/sunburst/sunburst", 69 | kwargs 70 | ) 71 | 72 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 73 | def resultsview(context, id, *args, **kwargs): 74 | return component_context( 75 | context, 76 | "splunk-toolkit-results-viewer", 77 | id, 78 | "view", 79 | "minecraft-app/components/resultsview/resultsview", 80 | kwargs 81 | ) 82 | 83 | @register.inclusion_tag('splunkdj:components/component.html', takes_context=True) 84 | def parallelcoords(context, id, *args, **kwargs): 85 | return component_context( 86 | context, 87 | "parallelcoords", 88 | id, 89 | "view", 90 | "minecraft-app/components/parallelcoords/parallelcoords", 91 | kwargs 92 | ) -------------------------------------------------------------------------------- /forge/src/main/java/com/splunk/forge/event_loggers/DeathEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.forge.event_loggers; 2 | 3 | import java.util.Properties; 4 | 5 | import com.splunk.sharedmc.Point3dLong; 6 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger; 7 | import com.splunk.sharedmc.loggable_events.LoggableDeathEvent; 8 | 9 | import net.minecraft.entity.player.EntityPlayer; 10 | import net.minecraft.util.Vec3; 11 | import net.minecraft.world.World; 12 | import net.minecraftforge.event.entity.living.LivingDeathEvent; 13 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 14 | import net.minecraftforge.fml.relauncher.Side; 15 | import net.minecraftforge.fml.relauncher.SideOnly; 16 | 17 | /** 18 | * Handles the logging of death events. 19 | */ 20 | public class DeathEventLogger extends AbstractEventLogger { 21 | 22 | /** 23 | * Whether to turn off logging non-player related monster deaths. Monsters causing their own death generates a lot 24 | * of spammy events. E.g. Bats flying into lava... 25 | */ 26 | public static final boolean IGNORE_MONSTER_ACCIDENTS = true; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param props Properties to configure this EventLogger with. 32 | */ 33 | public DeathEventLogger(Properties props) { 34 | super(props); 35 | } 36 | 37 | /** 38 | * Captures DeathEvents and sends them to the message preparer. 39 | * 40 | * @param event The captured BreakEvent. 41 | */ 42 | @SubscribeEvent 43 | @SideOnly(Side.SERVER) 44 | public void captureDeathEvent(LivingDeathEvent event) { 45 | final Boolean playerDied = event.entity instanceof EntityPlayer; 46 | String killer = null; 47 | if (event.source.getEntity() == null) { 48 | if (!playerDied && IGNORE_MONSTER_ACCIDENTS) { 49 | return; 50 | } 51 | } else { 52 | killer = event.source.getEntity().getDisplayName().getUnformattedText().replace(' ', '_'); 53 | } 54 | final LoggableDeathEvent.DeathEventAction deathAction = 55 | playerDied ? LoggableDeathEvent.DeathEventAction.PLAYER_DIED : 56 | LoggableDeathEvent.DeathEventAction.MOB_DIED; 57 | 58 | final String victim = event.entity.getDisplayName().getUnformattedText().replace(' ', '_'); 59 | final String damageSource = event.source.getDamageType().replace(' ', '_'); 60 | 61 | final World world = event.entity.getEntityWorld(); 62 | final long gameTime = world.getWorldTime(); 63 | final Vec3 entityPos = event.entity.getPositionVector(); 64 | final Point3dLong position = new Point3dLong(entityPos.xCoord, entityPos.yCoord, entityPos.zCoord); 65 | final String worldName = world.getWorldInfo().getWorldName(); 66 | 67 | if (killer == null) { 68 | killer = damageSource; 69 | } 70 | 71 | logAndSend( 72 | new LoggableDeathEvent(deathAction, gameTime, worldName, position).setKiller(killer).setVictim(victim) 73 | .setDamageSource(damageSource)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spigot/src/main/java/com/splunk/spigot/eventloggers/DeathEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.spigot.eventloggers; 2 | 3 | import static com.splunk.spigot.LogToSplunkPlugin.locationAsPoint; 4 | 5 | import java.util.List; 6 | import java.util.Properties; 7 | 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.Listener; 10 | import org.bukkit.event.entity.EntityDeathEvent; 11 | import org.bukkit.event.entity.PlayerDeathEvent; 12 | 13 | import com.google.common.collect.Lists; 14 | import com.splunk.sharedmc.Point3dLong; 15 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger; 16 | import com.splunk.sharedmc.loggable_events.LoggableDeathEvent; 17 | 18 | /** 19 | * Handles the logging of death events. 20 | */ 21 | public class DeathEventLogger extends AbstractEventLogger implements Listener { 22 | 23 | /** 24 | * Whether to turn off logging non-player related monster deaths. Monsters causing their own death generates a lot 25 | * of spammy events. E.g. Bats flying into lava... 26 | */ 27 | public static final boolean IGNORE_MONSTER_ACCIDENTS = true; 28 | 29 | // ouch: 30 | public static final List monsterNames = Lists.newArrayList( 31 | "Wolf", "Creeper", "Skeleton", "Blaze", "Cave Spider", "Spider", "Zombie Pigman", "Zombie", "Endermite", 32 | "Enderman", "Magma Cube", "Witch", "Wither", "Guardian", "Ghast", "Slime", "Silverfish"); 33 | 34 | 35 | public DeathEventLogger(Properties properties) { 36 | super(properties); 37 | } 38 | 39 | /** 40 | * Captures DeathEvents. 41 | * 42 | * @param event The captured BreakEvent. 43 | */ 44 | @EventHandler 45 | public void captureDeathEvent(EntityDeathEvent event) { 46 | String victim = event.getEntity().getName(); 47 | String killer = null; 48 | long gameTime = event.getEntity().getWorld().getTime(); 49 | String world = event.getEntity().getWorld().getName(); 50 | Point3dLong location = locationAsPoint(event.getEntity().getLocation()); 51 | LoggableDeathEvent deathEvent; 52 | 53 | if (event instanceof PlayerDeathEvent) { 54 | event.getEntity().getLastDamageCause(); 55 | if(event.getEntity().getKiller() != null){ 56 | killer = event.getEntity().getKiller().getDisplayName(); 57 | }else{ 58 | for(String mob : monsterNames){ 59 | if(((PlayerDeathEvent) event).getDeathMessage().contains(mob)){ 60 | killer = mob; 61 | break; 62 | } 63 | } 64 | } 65 | 66 | if(killer == null){ 67 | killer = event.getEntity().getLastDamageCause().getCause().name(); 68 | } 69 | logAndSend(new LoggableDeathEvent(LoggableDeathEvent.DeathEventAction.PLAYER_DIED, gameTime, world, location, killer, victim, event.getEntity().getLastDamageCause().getCause().name())); 70 | } else { 71 | if (event.getEntity().getKiller() != null) { 72 | killer = event.getEntity().getKiller().getDisplayName(); 73 | logAndSend(new LoggableDeathEvent(LoggableDeathEvent.DeathEventAction.MOB_DIED, gameTime, world, location, killer, victim, event.getEntity().getLastDamageCause().getCause().name())); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /forge/src/main/java/com/splunk/forge/event_loggers/BlockEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.forge.event_loggers; 2 | 3 | import java.util.Properties; 4 | 5 | import com.splunk.sharedmc.Point3dLong; 6 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger; 7 | import com.splunk.sharedmc.loggable_events.LoggableBlockEvent; 8 | 9 | import net.minecraft.block.Block; 10 | import net.minecraft.item.Item; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.world.World; 13 | import net.minecraftforge.event.world.BlockEvent; 14 | import net.minecraftforge.event.world.BlockEvent.BreakEvent; 15 | import net.minecraftforge.event.world.BlockEvent.PlaceEvent; 16 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 17 | import net.minecraftforge.fml.relauncher.Side; 18 | import net.minecraftforge.fml.relauncher.SideOnly; 19 | 20 | /** 21 | * Handles the logging of block events. 22 | */ 23 | public class BlockEventLogger extends AbstractEventLogger { 24 | 25 | /** 26 | * Constructor. 27 | * 28 | * @param props Properties to configure this EventLogger with. 29 | */ 30 | public BlockEventLogger(Properties props) { 31 | super(props); 32 | } 33 | 34 | /** 35 | * Captures Block BreakEvents. 36 | * 37 | * @param event The captured BreakEvent. 38 | */ 39 | @SubscribeEvent 40 | @SideOnly(Side.SERVER) 41 | public void captureBreakEvent(BreakEvent event) { 42 | logAndSend(getLoggableBlockBreakPlaceEvent(LoggableBlockEvent.BlockEventAction.BREAK, event)); 43 | } 44 | 45 | /** 46 | * Captures Block PlaceEvents and sends them to the message preparer. 47 | * 48 | * @param event The captured PlaceEvent. 49 | */ 50 | @SubscribeEvent 51 | @SideOnly(Side.SERVER) 52 | public void capturePlaceEvent(PlaceEvent event) { 53 | logAndSend(getLoggableBlockBreakPlaceEvent(LoggableBlockEvent.BlockEventAction.PLACE, event)); 54 | } 55 | 56 | private LoggableBlockEvent getLoggableBlockBreakPlaceEvent( 57 | LoggableBlockEvent.BlockEventAction action, BlockEvent event) { 58 | 59 | final Block block = event.state.getBlock(); 60 | final String base_type = block.getUnlocalizedName(); 61 | final World w = event.world; 62 | final Item item = Item.getItemFromBlock(block); 63 | 64 | String blockName = null; 65 | if (item != null) { 66 | final int damVal = block.getDamageValue(event.world, event.pos); 67 | final ItemStack stack = new ItemStack(block, 1, damVal); 68 | blockName = item.getItemStackDisplayName(stack).replace(' ', '_'); 69 | } else { 70 | 71 | blockName = base_type; 72 | if (event instanceof PlaceEvent) { 73 | blockName = ((PlaceEvent) event).itemInHand.getDisplayName(); 74 | } 75 | } 76 | final Point3dLong coords = new Point3dLong(event.pos.getX(), event.pos.getY(), event.pos.getZ()); 77 | String playerName = null; 78 | if (event instanceof BreakEvent) { 79 | playerName = ((BreakEvent) event).getPlayer().getDisplayNameString(); 80 | } else if (event instanceof PlaceEvent) { 81 | playerName = ((PlaceEvent) event).player.getDisplayNameString(); 82 | } 83 | 84 | return new LoggableBlockEvent(action, w.getWorldTime(), w.getWorldInfo().getWorldName(), coords) 85 | .setBlockName(blockName).setPlayerName(playerName).setBaseType(base_type); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/underscore-nest/underscore-nest.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var _ = require('underscore'); 4 | 5 | /// BEGIN LIBRARY CODE 6 | 7 | /** 8 | * underscore.nest - v0.1.1 - 7/13/2012 9 | * http://github.com/iros/underscore.nest/ 10 | * Copyright (c) 2012 Irene Ros; 11 | * Underscore.Nest is freely distributable under the MIT license. 12 | */ 13 | (function(global, _) { 14 | 15 | var nester = global.nest = {}; 16 | 17 | // helper that converts a group object like so: 18 | // { category : [{}, {}, {}, ... ], category2 : ...} 19 | // to: 20 | // [{ name : category, children : [{}, {}, {}, ...]}, 21 | // { name : category2, children : [{}, {}, {}, ...]}] 22 | var _transformGrouping = function(group) { 23 | return _.map(group, function(rows, key) { 24 | return { name : key, children : rows }; 25 | }); 26 | }; 27 | 28 | /** 29 | * convert a series of rows to a nested tree structure 30 | * based on the list of keys to bin by. 31 | * optionally reduce the resulting sub collections of rows 32 | * with a "reduce" function that returns a single row. 33 | */ 34 | nester.nest = function(rows, keys, reduce) { 35 | 36 | if (_.isString(keys)) { 37 | keys = [keys]; 38 | } 39 | 40 | var _infiniteNest = function(parent, keyIndex, childIndex) { 41 | 42 | if (keyIndex === 0) { 43 | // build initial children arrays by grouping first level 44 | parent.children = _transformGrouping(_.groupBy(rows, keys[0])); 45 | 46 | // if we have more keys to traverse, go through every 47 | // child grouping and nest that. 48 | if (keyIndex < keys.length) { 49 | for (var i = 0; i < parent.children.length; i++) { 50 | _infiniteNest(parent.children[i], keyIndex + 1, i); 51 | } 52 | } 53 | } else { 54 | 55 | // save the position of this specific child in 56 | // its parent child heirarchy 57 | parent.index = childIndex; 58 | 59 | if (keyIndex >= keys.length) { 60 | 61 | // if we have a reduce method provided, reduce the 62 | // children 63 | if (typeof reduce !== "undefined") { 64 | 65 | parent.value = reduce(parent.children); 66 | 67 | // remove the original children array, since we've 68 | // reduced it. 69 | delete parent.children; 70 | } 71 | 72 | } else { 73 | 74 | parent.children = _transformGrouping( 75 | _.groupBy(parent.children, keys[keyIndex]) 76 | ); 77 | 78 | for(var m = 0; m < parent.children.length; m++) { 79 | _infiniteNest(parent.children[m], keyIndex + 1, m); 80 | } 81 | } 82 | } 83 | 84 | return parent; 85 | }; 86 | 87 | return _infiniteNest({}, 0); 88 | }; 89 | 90 | // CommonJS module is defined 91 | if (typeof exports !== 'undefined') { 92 | if (typeof module !== 'undefined' && module.exports) { 93 | // Export module 94 | module.exports = nester; 95 | } 96 | exports.nester = nester; 97 | 98 | } else if (typeof define === 'function' && define.amd) { 99 | // Register as a named module with AMD. 100 | define('underscore.nest', [], function() { 101 | return nester; 102 | }); 103 | 104 | } else { 105 | // Integrate with Underscore.js if defined 106 | global._.mixin(nester); 107 | } 108 | 109 | }(this, _)); 110 | 111 | /// END LIBRARY CODE 112 | 113 | return _.nest; 114 | 115 | }); -------------------------------------------------------------------------------- /forge/src/main/java/com/splunk/forge/LogToSplunkMod.java: -------------------------------------------------------------------------------- 1 | package com.splunk.forge; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.util.Properties; 6 | 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | import com.google.common.annotations.VisibleForTesting; 11 | import com.splunk.forge.event_loggers.BlockEventLogger; 12 | import com.splunk.forge.event_loggers.DeathEventLogger; 13 | import com.splunk.forge.event_loggers.PlayerEventLogger; 14 | 15 | import net.minecraftforge.common.MinecraftForge; 16 | import net.minecraftforge.fml.common.FMLCommonHandler; 17 | import net.minecraftforge.fml.common.Mod; 18 | import net.minecraftforge.fml.common.Mod.EventHandler; 19 | import net.minecraftforge.fml.common.event.FMLInitializationEvent; 20 | import net.minecraftforge.fml.common.eventhandler.EventBus; 21 | 22 | @Mod(modid = LogToSplunkMod.MODID, version = LogToSplunkMod.VERSION, name = LogToSplunkMod.NAME, 23 | acceptableRemoteVersions = "*") 24 | public class LogToSplunkMod { 25 | public static final String MODID = "logtosplunk"; 26 | public static final String VERSION = "1.0-SNAPSHOT"; 27 | public static final String NAME = "Splunk for Minecraft"; 28 | public static final String LOGGER_NAME = "LogToSplunk"; 29 | public static final String SPLUNK_MOD_PROPERTIES = "/config/splunk.properties"; 30 | 31 | private static final Logger logger = LogManager.getLogger(LogToSplunkMod.class); 32 | /** 33 | * Used for registering listeners to FML events. 34 | */ 35 | private final EventBus fmlBus; 36 | 37 | /** 38 | * Used for registering listeners to ForgeMinecraft events. 39 | */ 40 | private final EventBus mcBus; 41 | 42 | public LogToSplunkMod() { 43 | this(FMLCommonHandler.instance().bus(), MinecraftForge.EVENT_BUS); 44 | } 45 | 46 | /** 47 | * Constructor. 48 | * 49 | * @param fmlBus EventBus to register FML event listeners on. For when you're having a bad day. 50 | * @param mcBus Used to register MinecraftForge event listeners. Not actually Irish. 51 | */ 52 | @VisibleForTesting 53 | public LogToSplunkMod(EventBus fmlBus, EventBus mcBus) { 54 | this.fmlBus = fmlBus; 55 | this.mcBus = mcBus; 56 | } 57 | 58 | /** 59 | * Called when the mod is initialized. 60 | */ 61 | @EventHandler 62 | @SuppressWarnings("unused") 63 | public void init(FMLInitializationEvent event) { 64 | //duplicated in the spigot module, this code could be moved to the AbstractEventLogger in shared 65 | final Properties properties = new Properties(); 66 | final String path = System.getProperty("user.dir") + SPLUNK_MOD_PROPERTIES; 67 | try { 68 | final FileReader reader = new FileReader(new File(path)); 69 | properties.load(reader); 70 | } catch (final Exception e) { 71 | logger.warn( 72 | String.format( 73 | "Unable to load properties for LogToSplunkMod at %s! Default values will be used.", path), 74 | e); 75 | } 76 | 77 | final PlayerEventLogger playerEventLogger = new PlayerEventLogger(properties); 78 | fmlBus.register(playerEventLogger); 79 | mcBus.register(playerEventLogger); 80 | 81 | final BlockEventLogger blockLogger = new BlockEventLogger(properties); 82 | mcBus.register(blockLogger); 83 | 84 | final DeathEventLogger deathLogger = new DeathEventLogger(properties); 85 | mcBus.register(deathLogger); 86 | 87 | logAndSend("Splunk for Minecraft initialized."); 88 | } 89 | 90 | /** 91 | * Logs and sends messages to be prepared for Splunk. 92 | * 93 | * @param message The message to log. 94 | */ 95 | private void logAndSend(String message) { 96 | logger.info(message); 97 | } 98 | } -------------------------------------------------------------------------------- /django/minecraft-app/templates/placedblocks.html: -------------------------------------------------------------------------------- 1 | {% extends "splunkdj:base_with_app_bar.html" %} 2 | 3 | {% load splunkmvc %} 4 | {% load splunk_wftoolkit %} 5 | 6 | {% block title %}{{app_label}}{% endblock title %} 7 | 8 | {% block css %} 9 | 10 | {% endblock css %} 11 | 12 | {% block content %} 13 |
14 |
15 |
16 |
17 |

Most Placed Blocks

18 |
19 | {% chart id="most-placed-chart" managerid="most-placed-search" height="940" %} 20 |
21 |
22 |
23 |

Wood Building Breakdown

24 |
25 | {% sunburst id="logs-sunburst" managerid="wood-sundburst-search" height="300" %} 26 |
27 |

Stone Building Breakdown

28 |
29 | {% sunburst id="stone-sunburst" managerid="stone-sundburst-search" height="300" %} 30 |
31 |

Stone Building Breakdown

32 |
33 | {% chart id="top-builders-chart" managerid="top-builders-search" %} 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

Building Rate By Block

44 |
45 | {% chart id="building-rate-chart" managerid="building-rate-search" %} 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {% endblock content %} 58 | 59 | {% block managers %} 60 | {% searchmanager id="most-placed-search" search="sourcetype=minecraft_log action=block_placed block_type!=TORCH block_type!=DIRT block_type!=CROPS | top limit=30 block_type" %} 61 | {% searchmanager id="wood-sundburst-search" search="sourcetype=minecraft_log action=block_placed AND (block_type=SPRUCE* OR block_type=OAK* OR block_type=JUNGLE* OR block_type=BIRCH*) | rex \".*block_type=(?[A-Z]*)_(LOG|SLAB|STAIR)\" | table wood_type block_type" %} 62 | {% searchmanager id="stone-sundburst-search" search="sourcetype=minecraft_log action=block_placed AND (block_type=COBBLESTONE* OR block_type=SANDSTONE* OR block_type=STONE* OR block_type=SMOOTH*) | rex \".*block_type=(?[A-Z]*)(_|$)\" | table stone_type block_type" %} 63 | {% searchmanager id="top-builders-search" search="sourcetype=minecraft_log action=block_placed | chart count by player" %} 64 | 65 | {% searchmanager id="building-rate-search" search="sourcetype=minecraft_log action=block_placed block_type!=TORCH block_type!=CROPS | timechart count by block_type" %} 66 | 67 | {% endblock managers %} 68 | 69 | {% block js %} 70 | 71 | 91 | 92 | {% endblock js %} -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelcoords/parallelcoords.js: -------------------------------------------------------------------------------- 1 | // parallel coords! 2 | // a visualisation technique for multidimensional categorical data 3 | // you can drag the vertical axis for each section to filter things (try it out for yourself) 4 | 5 | // --- settings --- 6 | // none for the time being. 7 | // TODO: add settings to choose which data goes where 8 | 9 | // --- expected data format --- 10 | // a splunk search like this: index=_internal sourcetype=splunkd_access | table method status 11 | 12 | define(function(require, exports, module) { 13 | 14 | var _ = require('underscore'); 15 | var d3 = require("../d3/d3"); 16 | var parcoords = require("./contrib/d3-parcoords"); 17 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview"); 18 | 19 | var ParCoords = SimpleSplunkView.extend({ 20 | moduleId: module.id, 21 | 22 | className: "splunk-toolkit-parcoords", 23 | 24 | options: { 25 | managerid: null, // your MANAGER ID 26 | data: "preview", // Results type 27 | }, 28 | 29 | output_mode: "json_rows", 30 | 31 | initialize: function() { 32 | SimpleSplunkView.prototype.initialize.apply(this, arguments); 33 | 34 | this.settings.enablePush("value"); 35 | 36 | // Set up resize callback. The first argument is a this 37 | // pointer which gets passed into the callback event 38 | $(window).resize(this, _.debounce(this._handleResize, 20)); 39 | }, 40 | 41 | _handleResize: function(e){ 42 | 43 | // e.data is the this pointer passed to the callback. 44 | // here it refers to this object and we call render() 45 | e.data.render(); 46 | }, 47 | 48 | createView: function() { 49 | this.$el.html(''); // clearing all prior junk from the view (eg. 'waiting for data...') 50 | return true; 51 | }, 52 | 53 | // making the data look how we want it to for updateView to do its job 54 | formatData: function(data) { 55 | 56 | // Decide what fields we want 57 | // TODO: this should be specifialbe 58 | var fields = _.filter(this.resultsModel.data().fields, function(d){return d[0] !== "_" }); 59 | var objects = _.map(data, function(row) { 60 | var obj = {}; 61 | _.each(fields, function(field, idx) { 62 | if (row[idx] !== null) { 63 | obj[field] = row[idx]; 64 | } 65 | else { 66 | obj[field] = ""; 67 | } 68 | }); 69 | 70 | return obj; 71 | }); 72 | 73 | data = { 74 | 'results': objects, 75 | 'fields': fields 76 | } 77 | 78 | return data; 79 | }, 80 | 81 | updateView: function(viz, data) { 82 | var that = this; 83 | var availableHeight = parseInt(this.settings.get("height") || this.$el.height()); 84 | 85 | this.$el.html(''); 86 | var fields = data.fields; 87 | viz = $("
").appendTo(this.el) 88 | .css("height", availableHeight) 89 | var colorgen = d3.scale.category20(); 90 | var colors = {}; 91 | _(data.results).chain() 92 | .pluck(fields[0]) 93 | .uniq() 94 | .each(function(d,i) { 95 | colors[d] = colorgen(i); 96 | }); 97 | 98 | var color = function(d) {return colors[d[fields[0]]]; }; 99 | 100 | var pc_progressive = d3.parcoords()('#' + this.id + '_parallelcoords') 101 | .data(data.results) 102 | .color(color) 103 | .alpha(0.4) 104 | .margin({ top: 24, left: 150, bottom: 12, right: 0 }) 105 | .mode("queue") 106 | .render() 107 | .brushable() // enable brushing 108 | .interactive() // command line mode 109 | .on("brush", function(selected) { 110 | that.trigger("select", {selected: selected}); 111 | }); 112 | 113 | pc_progressive.svg.selectAll("text") 114 | .style("font", "10px sans-serif"); 115 | } 116 | }); 117 | return ParCoords; 118 | }); 119 | -------------------------------------------------------------------------------- /logtosplunk-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | splunk.minecraft.app 6 | com.splunk 7 | 1.0-SNAPSHOT 8 | 9 | 4.0.0 10 | 11 | logtosplunk-plugin 12 | 13 | 14 | 15 | mvnrepository-central 16 | mvnrepository.com Central 17 | https://repo1.maven.org/maven2/ 18 | 19 | 20 | splunk-artifactory 21 | Splunk Releases 22 | https://splunk.jfrog.io/splunk/ext-releases-local 23 | 24 | 25 | 26 | 27 | 28 | org.apache.logging.log4j 29 | log4j-api 30 | 2.25.1 31 | 32 | 33 | org.apache.logging.log4j 34 | log4j-core 35 | 2.25.1 36 | 37 | 38 | com.splunk.logging 39 | splunk-library-javalogging 40 | 1.11.8 41 | 42 | 43 | com.splunk 44 | shared-mc 45 | ${project.version} 46 | 47 | 48 | com.splunk 49 | spigot 50 | ${project.version} 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-shade-plugin 59 | 60 | 61 | ${project.build.directory}/dependency-reduced-pom.xml 62 | 63 | 64 | 65 | 2.4.1 66 | 67 | 68 | package 69 | 70 | shade 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | include-forge 82 | 83 | 84 | com.splunk 85 | logtosplunk-forge 86 | ${project.version} 87 | 88 | 89 | org.apache.logging.log4j 90 | log4j-api 91 | 92 | 93 | org.apache.logging.log4j 94 | log4j-core 95 | 96 | 97 | com.splunk 98 | splunk-library-javalogging 99 | 1.0.1 100 | 101 | 102 | com.splunk 103 | shared-mc 104 | ${project.version} 105 | 106 | 107 | com.splunk 108 | spigot 109 | ${project.version} 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /spigot/src/main/java/com/splunk/spigot/eventloggers/PlayerEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.spigot.eventloggers; 2 | 3 | import static com.splunk.spigot.LogToSplunkPlugin.locationAsPoint; 4 | 5 | import java.util.Properties; 6 | 7 | import org.bukkit.Location; 8 | import org.bukkit.World; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.event.player.PlayerEvent; 12 | import org.bukkit.event.player.PlayerKickEvent; 13 | import org.bukkit.event.player.PlayerLoginEvent; 14 | import org.bukkit.event.player.PlayerMoveEvent; 15 | import org.bukkit.event.player.PlayerQuitEvent; 16 | 17 | import com.google.common.cache.Cache; 18 | import com.google.common.cache.CacheBuilder; 19 | import com.google.common.cache.CacheLoader; 20 | import com.splunk.sharedmc.Point3dLong; 21 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger; 22 | import com.splunk.sharedmc.loggable_events.LoggablePlayerEvent; 23 | import com.splunk.sharedmc.loggable_events.LoggablePlayerEvent.PlayerEventAction; 24 | 25 | /** 26 | * Handles the logging of player events. 27 | */ 28 | public class PlayerEventLogger extends AbstractEventLogger implements Listener { 29 | public static final double GRANULARITY = 1.5; 30 | public static final int MAX_PLAYERS = 128; 31 | 32 | /** 33 | * Keeps track players last positions, in a guava cache for it's eviction policy. 34 | */ 35 | private final Cache lastKnownCoordinates = 36 | CacheBuilder.newBuilder().maximumSize(MAX_PLAYERS).build( 37 | new CacheLoader() { 38 | @Override 39 | public Location load(String key) throws Exception { 40 | return lastKnownCoordinates.getIfPresent(key); 41 | } 42 | }); 43 | 44 | /** 45 | * Constructs a new PlayerEventLogger. 46 | * 47 | * @param props Properties to configure this EventLogger with. 48 | */ 49 | public PlayerEventLogger(Properties props) { 50 | super(props); 51 | } 52 | 53 | /** 54 | * Logs to Splunk when a player logs in. 55 | * 56 | * @param event The captured event. 57 | */ 58 | @EventHandler 59 | public void onPlayerConnect(PlayerLoginEvent event) { 60 | logAndSend( 61 | generateLoggablePlayerEvent(event, PlayerEventAction.PLAYER_CONNECT, null, event.getKickMessage())); 62 | } 63 | 64 | /** 65 | * Logs to Splunk when a player logs out. 66 | * 67 | * @param event The captured event. 68 | */ 69 | @EventHandler 70 | public void onPlayerDisconnect(PlayerKickEvent event) { 71 | logAndSend( 72 | generateLoggablePlayerEvent( 73 | event, PlayerEventAction.PLAYER_DISCONNECT, event.getReason(), event.getLeaveMessage())); 74 | } 75 | 76 | @EventHandler 77 | public void onPlayerQuit(PlayerQuitEvent event) { 78 | logAndSend( 79 | generateLoggablePlayerEvent( 80 | event, PlayerEventAction.PLAYER_DISCONNECT, null, event.getQuitMessage())); 81 | } 82 | 83 | @EventHandler 84 | public void onPlayerMove(PlayerMoveEvent event) { 85 | Location previous = lastKnownCoordinates.getIfPresent(event.getPlayer().getDisplayName()); 86 | 87 | if (previous != null && previous.distance(event.getTo()) < GRANULARITY) { 88 | return; 89 | } 90 | 91 | logAndSend( 92 | generateLoggablePlayerEvent( 93 | event, PlayerEventAction.LOCATION, null, null)); 94 | } 95 | 96 | private LoggablePlayerEvent generateLoggablePlayerEvent( 97 | PlayerEvent event, PlayerEventAction actionType, String reason, String message) { 98 | final World world = event.getPlayer().getWorld(); 99 | final long worldTime = world.getTime(); 100 | final String worldName = world.getName(); 101 | final LoggablePlayerEvent loggable = new LoggablePlayerEvent( 102 | actionType, worldTime, worldName, locationAsPoint(event.getPlayer().getLocation())); 103 | 104 | loggable.setPlayerName(event.getPlayer().getDisplayName()); 105 | loggable.setReason(reason); 106 | loggable.setMessage(message); 107 | 108 | if (event.getClass().equals(PlayerMoveEvent.class)) { 109 | Point3dLong from = locationAsPoint(lastKnownCoordinates.getIfPresent(event.getPlayer().getDisplayName())); 110 | 111 | if (from != null) { 112 | loggable.setFrom(from); 113 | }else{ 114 | loggable.setFrom(locationAsPoint(((PlayerMoveEvent) event).getFrom())); 115 | } 116 | loggable.setTo(locationAsPoint(((PlayerMoveEvent) event).getTo())); 117 | lastKnownCoordinates.put(event.getPlayer().getDisplayName(), ((PlayerMoveEvent) event).getTo()); 118 | } 119 | 120 | return loggable; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/parallelsets/parallelsets.js: -------------------------------------------------------------------------------- 1 | // parallel sets! 2 | // a visualisation technique for multidimensional categorical data 3 | // you can drag the vertical or horizontal axis independently and 4 | // watch as your data is represented in completely different ways 5 | 6 | // --- settings --- 7 | // none for the time being. 8 | // TODO: add settings to choose which data goes where 9 | 10 | // --- expected data format --- 11 | // a splunk search like this: index=_internal sourcetype=splunkd_access | table method status 12 | 13 | define(function(require, exports, module) { 14 | 15 | var _ = require('underscore'); 16 | var d3 = require('../d3/d3'); 17 | var d3p = require('./contrib/d3-parsets'); 18 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview"); 19 | 20 | require("css!./parallelsets.css"); 21 | 22 | var ParallelSets = SimpleSplunkView.extend({ 23 | moduleId: module.id, 24 | 25 | className: "splunk-toolkit-parellel-sets", 26 | 27 | options: { 28 | managerid: null, 29 | data: "preview", 30 | tension: 0.5 31 | }, 32 | 33 | output_mode: "json_rows", 34 | 35 | initialize: function() { 36 | SimpleSplunkView.prototype.initialize.apply(this, arguments); 37 | 38 | this.settings.enablePush("value"); 39 | 40 | this.settings.on("change:tension", this.render, this); 41 | 42 | // Set up resize callback. The first argument is a this 43 | // pointer which gets passed into the callback event 44 | $(window).resize(this, _.debounce(this._handleResize, 20)); 45 | }, 46 | 47 | _handleResize: function(e){ 48 | 49 | // e.data is the this pointer passed to the callback. 50 | // here it refers to this object and we call render() 51 | e.data.render(); 52 | }, 53 | 54 | createView: function() { 55 | // Here we wet up the initial view layout 56 | var margin = {top: 10, right: 10, bottom: 10, left: 10}; 57 | var availableWidth = parseInt(this.settings.get("width") || this.$el.width()); 58 | var availableHeight = parseInt(this.settings.get("height") || this.$el.height()); 59 | 60 | this.$el.html(""); 61 | 62 | var svg = d3.select(this.el) 63 | .append("svg") 64 | .attr("width", availableWidth) 65 | .attr("height", availableHeight) 66 | .attr("pointer-events", "all") 67 | 68 | // The returned object gets passed to updateView as viz 69 | return { container: this.$el, svg: svg, margin: margin}; 70 | }, 71 | 72 | // making the data look how we want it to for updateView to do its job 73 | formatData: function(data) { 74 | var fields = this.resultsModel.data().fields; 75 | var objects = _.map(data, function(row) { 76 | return _.object(fields, row); 77 | }); 78 | 79 | return { 80 | 'results': objects, 81 | 'fields': fields 82 | } 83 | }, 84 | 85 | updateView: function(viz, data) { 86 | var that = this; 87 | var containerHeight = this.$el.height(); 88 | var containerWidth = this.$el.width(); 89 | 90 | // Clear svg 91 | var svg = $(viz.svg[0]); 92 | svg.empty(); 93 | svg.height(containerHeight); 94 | svg.width(containerWidth); 95 | 96 | // Add the graph group as a child of the main svg 97 | var graphWidth = containerWidth - viz.margin.left - viz.margin.right 98 | var graphHeight = containerHeight - viz.margin.top - viz.margin.bottom; 99 | var graph = viz.svg 100 | .append("g") 101 | .attr("width", graphWidth) 102 | .attr("height", graphHeight) 103 | .attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")"); 104 | 105 | var tension = this.settings.get("tension"); 106 | var fields = data.fields; 107 | 108 | this.parset = d3p() 109 | .dimensions(fields) 110 | .width(graphWidth) 111 | .height(graphHeight) 112 | .on("sortCategories", function(){ 113 | that.trigger("sort:categories"); 114 | }); 115 | 116 | graph.datum(data.results).call(this.parset); 117 | 118 | graph.selectAll("g.ribbon-mouse path") 119 | .on("click", function(e) { 120 | that.trigger('click', { 121 | source: e.source.node.name, 122 | sourceDimension: e.source.node.dimension.name, 123 | target: e.target.node.name, 124 | targetDimension: e.target.node.dimension.name, 125 | dimension: e.dimension, 126 | value: e.count 127 | }); 128 | }); 129 | 130 | t = graph.transition().duration(500); 131 | t.call(this.parset.tension(tension)); 132 | } 133 | }); 134 | 135 | return ParallelSets; 136 | }); -------------------------------------------------------------------------------- /forge/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | splunk.minecraft.app 6 | com.splunk 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | pom 12 | 13 | forge 14 | 15 | 16 | 17 | 18 | install-forge-libs 19 | 20 | 21 | 22 | include-forge 23 | 24 | 25 | 26 | org.apache.logging.log4j 27 | log4j-api 28 | 29 | 30 | org.apache.logging.log4j 31 | log4j-core 32 | 33 | 34 | 35 | 36 | com.splunk 37 | splunk-library-javalogging 38 | 1.0.1 39 | 40 | 41 | com.splunk 42 | shared-mc 43 | ${project.version} 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-clean-plugin 51 | 2.6.1 52 | 53 | 54 | auto-clean 55 | initialize 56 | 57 | clean 58 | 59 | 60 | 61 | 62 | build 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.codehaus.mojo 71 | exec-maven-plugin 72 | 1.4.0 73 | 74 | 75 | gradle-build 76 | 77 | exec 78 | 79 | compile 80 | 81 | gradlew 82 | 83 | build 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-install-plugin 92 | 2.5.2 93 | 94 | 95 | install-splunkforge 96 | package 97 | 98 | install-file 99 | 100 | 101 | ${project.basedir}/build/libs/logtosplunk-forge-1.0-SNAPSHOT.jar 102 | com.splunk 103 | logtosplunk-forge 104 | ${project.version} 105 | true 106 | jar 107 | ${project.basedir}/pom.xml 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /forge/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /django/minecraft-app/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "splunkdj:base_with_app_bar.html" %} 2 | 3 | {% load splunkmvc %} 4 | 5 | {% block title %}{{app_label}}{% endblock title %} 6 | 7 | {% block css %} 8 | 9 | {% endblock css %} 10 | 11 | {% block content %} 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 | {% single managerid="broken-block-search" id="broken-block-single" afterLabel="Blocks Mined" %} 20 |
21 |
22 | 23 |
24 |
25 | {% single managerid="placed-block-search" id="placed-block-single" afterLabel="Blocks Placed" %} 26 |
27 |
28 | 29 |
30 |
31 | {% single managerid="time-played-search" id="duration-single" afterLabel="Hours Played" %} 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |

Active Players

41 |
42 | {% table id="active-player-table" managerid="active-player-search" pageSize="20" %} 43 |
44 |
45 |
46 |

Mining Activity

47 |
48 | {% chart id="mining-chart" managerid="mining-search" %} 49 |
50 |

Building Activity

51 |
52 | {% chart id="building-chart" managerid="building-search" %} 53 |
54 |

Player Deaths

55 |
56 | {% chart id="player-death-chart" managerid="player-death-search" %} 57 |
58 |
59 |
60 |
61 |
62 | 63 | 64 | {% endblock content %} 65 | 66 | {% block managers %} 67 | {% searchmanager id="broken-block-search" search="sourcetype=minecraft_log action=block_broken | stats count" %} 68 | {% searchmanager id="placed-block-search" search="sourcetype=minecraft_log action=block_placed | stats count" %} 69 | {% searchmanager id="time-played-search" 70 | search="search sourcetype=minecraft_log | eval player=if(player not null, player, victim) | transaction player startswith=player_connect endswith=player_disconnect maxspan=2d | stats sum(duration) as total_time_sec | eval hours_played=total_time_sec/60.0" %} 71 | {% searchmanager id="player-death-search" 72 | search="sourcetype=minecraft_log action=player_died killer!=lava killer!=fire killer!=fire_tick killer!=drowning killer!=suffocation killer!=fall killer!=suicide | chart count by killer,victim" %} 73 | 74 | {% endblock managers %} 75 | 76 | {% block js %} 77 | 78 | 118 | 119 | {% endblock js %} -------------------------------------------------------------------------------- /django/minecraft-app/templates/players.html: -------------------------------------------------------------------------------- 1 | {% extends "splunkdj:base_with_app_bar.html" %} 2 | 3 | {% load splunkmvc %} 4 | 5 | {% block title %}{{app_label}}{% endblock title %} 6 | 7 | {% block css %} 8 | 9 | {% endblock css %} 10 | 11 | {% block content %} 12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 |
Player:
21 | {% dropdown id="player-dropdown" managerid="player-search" labelField="player" 22 | valueField="player" value="$player$"|token_safe default="*" showClearButton=false %} 23 |
24 |
25 | {% single id="blocks-placed-single" managerid="blocks-placed-search" afterLabel="Blocks Placed" %} 26 |
27 |
28 | {% single id="blocks-broken-single" managerid="blocks-broken-search" afterLabel="Blocks Broken" %} 29 |
30 |
31 | {% single id="total-killed-single" managerid="total-killed-search" afterLabel="Critters Offed" %} 32 |
33 |
34 | {% single id="time-played-single" managerid="time-played-search" afterLabel="Hours Played" %} 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

Session Activity

44 |
45 | {% eventsviewer id="test-resulttable" managerid="session-event-search" %} 46 |
47 |
48 |
49 |

Favored Building Blocks

50 |
51 | {% chart id="primary-building-blocks" managerid="building-blocks-search" %} 52 |
53 |

Most Mined Blocks

54 |
55 | {% chart id="primary-mining-blocks" managerid="mining-blocks-search" %} 56 |
57 |

Most Hunted Mobs

58 |
59 | {% chart id="mob-kills-chart" managerid="mob-kills-search" %} 60 |
61 |

Player Deaths

62 |
63 | {% chart id="player-kills-chart" managerid="player-kills-search" %} 64 |
65 |
66 |
67 |
68 |
69 | 70 | {% endblock content %} 71 | 72 | {% block managers %} 73 | 74 | {% searchmanager id="session-event-search" 75 | search="sourcetype=minecraft_log player=$player$ action=player_connect OR action=player_disconnect"|token_safe %} 76 | {% searchmanager id="blocks-placed-search" 77 | search="sourcetype=minecraft_log player=$player$ action=block_placed | stats count"|token_safe %} 78 | {% searchmanager id="blocks-broken-search" 79 | search="sourcetype=minecraft_log player=$player$ action=block_broken | stats count"|token_safe %} 80 | {% searchmanager id="total-killed-search" 81 | search="sourcetype=minecraft_log killer=$player$ action=mob_died | stats count"|token_safe %} 82 | 83 | {% searchmanager id="time-played-search" 84 | search="sourcetype=minecraft_log player=$player$ | transaction player startswith=player_connect endswith=player_disconnect maxspan=2d | stats sum(duration) as total_time_sec | eval hours_played=total_time_sec/60.0"|token_safe %} 85 | 86 | {% searchmanager id="building-blocks-search" 87 | search="sourcetype=minecraft_log action=block_placed player=$player$ block_type!=TORCH block_type!=DIRT block_type!=CROPS | top limit=5 block_type"|token_safe %} 88 | 89 | {% searchmanager id="mining-blocks-search" 90 | search="sourcetype=minecraft_log action=block_broken player=$player$ block_type!=TORCH block_type!=DIRT block_type!=CROPS | top limit=5 block_type"|token_safe %} 91 | 92 | {% searchmanager id="mob-kills-search" 93 | search="sourcetype=minecraft_log killer=$player$ action=mob_died | top limit=5 victim"|token_safe %} 94 | 95 | {% searchmanager id="player-kills-search" 96 | search="sourcetype=minecraft_log action=player_died victim=$player$ killer!=lava killer!=fire killer!=fire_tick killer!=drowning killer!=suffocation killer!=fall killer!=suicide | timechart count by killer"|token_safe %} 97 | 98 | {% searchmanager id="player-search" 99 | search="sourcetype=minecraft_log | dedup player | table player" %} 100 | 101 | {% endblock managers %} 102 | 103 | {% block js %} 104 | 105 | 131 | 132 | {% endblock js %} -------------------------------------------------------------------------------- /shared-mc/src/main/java/com/splunk/sharedmc/SingleSplunkConnection.java: -------------------------------------------------------------------------------- 1 | package com.splunk.sharedmc; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.security.KeyManagementException; 6 | import java.security.KeyStoreException; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.Calendar; 9 | 10 | //import org.apache.http.client.methods.CloseableHttpResponse; 11 | //import org.apache.http.client.methods.HttpPost; 12 | //import org.apache.http.entity.ContentType; 13 | //import org.apache.http.entity.StringEntity; 14 | //import org.apache.http.impl.client.CloseableHttpClient; 15 | //import org.apache.http.impl.client.HttpClients; 16 | import org.apache.logging.log4j.LogManager; 17 | import org.apache.logging.log4j.Logger; 18 | 19 | import org.apache.hc.core5.http.ContentType; 20 | import org.apache.hc.core5.http.io.entity.StringEntity; 21 | import org.apache.hc.client5.http.classic.methods.HttpPost; 22 | import org.apache.hc.client5.http.impl.classic.HttpClients; 23 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 24 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; 25 | import org.apache.hc.core5.io.CloseMode; 26 | 27 | import org.json.simple.JSONObject; 28 | 29 | /** 30 | * Knows a single Splunk instance by its host:port and forwards data to it. 31 | */ 32 | public class SingleSplunkConnection implements SplunkConnection, Runnable { 33 | private static final String LOGGER_PREFIX = "SplunkConnection - "; 34 | private static final String DEFAULT_RECONNECT_TIME = "10"; 35 | private String BASE_URL = "http://%s:%s/services/collector/event/1.0"; 36 | 37 | /** 38 | * Interval in seconds between attempts to connect to Splunk. 39 | */ 40 | private static final int RECONNECT_TIME = 41 | Integer.valueOf(System.getProperty("splunk_mc.reconnect_time", DEFAULT_RECONNECT_TIME)); 42 | 43 | private final Logger logger; 44 | private final String url; 45 | 46 | private CloseableHttpClient httpClient; 47 | private CloseableHttpResponse response; 48 | 49 | private String token; 50 | 51 | // lazy 52 | private StringBuilder messagesToSend = new StringBuilder(); 53 | private StringBuilder messagesOnRunway; 54 | 55 | /** 56 | * Constructor. Determines which Splunk instance this will connect to based on the host:port passed in. Set up a 57 | * shutdown hook to send any remaining messages to Splunk on close. 58 | * 59 | * @param host Host of Splunk to connect to. 60 | * @param port Port of Splunk to connect to. 61 | * @param startImmediately If true, creates a thread and starts this Splunk on construction. 62 | */ 63 | public SingleSplunkConnection(String host, int port, String token, boolean startImmediately) { 64 | logger = LogManager.getLogger(LOGGER_PREFIX + host + ':' + port); 65 | this.token = token; 66 | url = String.format(BASE_URL, host, port); 67 | 68 | addFlushShutdownHook(); 69 | 70 | if (startImmediately) { 71 | new Thread(this).start(); 72 | } 73 | } 74 | 75 | @Override 76 | public void run() { 77 | while (true) { 78 | sendData(); 79 | try { 80 | Thread.sleep(1000 * RECONNECT_TIME); 81 | } catch (final InterruptedException e) { 82 | //eat exception. 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Queues up a message to send to this Spunk connections' Splunk instance. 89 | * 90 | * @param message The message to send. 91 | */ 92 | @Override 93 | public void sendToSplunk(String message) { 94 | JSONObject event = new JSONObject(); 95 | //message = Calendar.getInstance().getTime().toString() + ' ' + message; 96 | event.put("event", message); 97 | 98 | messagesToSend.append(event.toString()); 99 | } 100 | 101 | private boolean sendData() { 102 | boolean success = false; 103 | // probably a better way to do this. 104 | if (messagesOnRunway == null && messagesToSend.length() > 0) { 105 | messagesOnRunway = messagesToSend; 106 | messagesToSend = new StringBuilder(); 107 | }else{ 108 | // no messages to send, so safe to say messages have been sent. 109 | return messagesOnRunway == null; 110 | } 111 | try { 112 | logger.info("Sending data to splunk..."); 113 | httpClient = HttpClients.createDefault(); 114 | HttpPost post = new HttpPost(url); 115 | post.setHeader("Authorization", "Splunk " + token); 116 | StringEntity entity = new StringEntity(messagesOnRunway.toString(), ContentType.APPLICATION_JSON); 117 | post.setEntity(entity); 118 | response = httpClient.execute(post); 119 | int responseCode = response.getCode(); 120 | 121 | if (responseCode > 199 && responseCode < 300) { 122 | messagesOnRunway = null; 123 | success = true; 124 | } else { 125 | ByteArrayOutputStream outstream = new ByteArrayOutputStream(); 126 | response.getEntity().writeTo(outstream); 127 | byte[] responseBody = outstream.toByteArray(); 128 | logger.error(new String(responseBody)); 129 | } 130 | 131 | //post.completed(); 132 | } catch (final IOException e) { 133 | logger.error("Unable to send message!", e); 134 | success = false; 135 | }finally{ 136 | httpClient.close(CloseMode.GRACEFUL); 137 | response.close(CloseMode.GRACEFUL); 138 | } 139 | 140 | return success; 141 | } 142 | 143 | /** 144 | * Adds a shutdown hook that flushes this classes data buffer ({@code data}) by sending it to Splunk. 145 | */ 146 | private void addFlushShutdownHook() { 147 | Runtime.getRuntime().addShutdownHook( 148 | new Thread() { 149 | @Override 150 | public void run() { 151 | logger.info("Shutting down: attempting to send remaining data."); 152 | if (sendData()) { 153 | logger.info("Remaining data sent!"); 154 | } else { 155 | logger.error("Couldn't send all remaining data to Splunk!"); 156 | // TODO: Write data to a log file, 'unsent_data.splunk' or some such... 157 | } 158 | } 159 | }); 160 | } 161 | 162 | private void initHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { 163 | 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minecraft App 2 | 3 | ### Version 1.0 4 | 5 | The Minecraft App lets you visualize the minecraft world from the guts side. Wondering how many blocks have been dug up by your buddies? Not a problem. Wondering who's found the most diamonds? Yep, got it covered. Have you been planting enough wheat? Carrots? Pototoes? The Minecraft App will let you know. 6 | 7 | ### Release Notes 8 | 9 | * Uses the new Splunk HTTP Event Collector as it's input instead of TCP. 10 | * Now supports Spigot and Forge as well as Craftbukkit as a unified plugin jar. 11 | * Plugin will cache items that the server does not acknowledge so restarting Splunk is no longer an issue 12 | * Ore images in the "Mined Blocks" page of the app now display correctly. 13 | * Minor bug fixes. 14 | 15 | ### Getting Started 16 | This section provides information about installing and using the Minecraft App. 17 | 18 | #### Requirements 19 | 20 | * Operating System: Windows, Linux, or Mac OS X. 21 | * Web browsers: Latest versions of Chrome, Safari, or Firefox, Internet Explorer 9 or later. 22 | * Craftbukkit, Spigot or Forge 23 | * [Craftbukkit](http://bukkit.org/) 24 | * [Spigot](https://www.spigotmc.org) 25 | * [Forge](http://www.minecraftforge.net/) 26 | * LogToSplunk Plugin: The log to splunk plugin that allows input of more detailed minecraft data to splunk from [CraftBukkit](http://dev.bukkit.org/bukkit-plugins/logtosplunk/) 27 | * The Splunk Web Framework: The Web Framework is included in Splunk 6 and is available for download for Splunk 5 from the 28 | [Splunk Developer Portal](http://dev.splunk.com/view/webframework-standalone/SP-CAAAEMA). 29 | * Minecraft Overviewer (Optional): The Google Maps based minecraft word renderer from [Overviewer](http://overviewer.org) 30 | 31 | #### Installing the Minecraft App 32 | The Minecraft App is built as a Splunk App on the Splunk Web Framework and must be installed on top of it. 33 | 34 | ##### Installing from Splunk Web 35 | If you downloaded the Minecraft App from [Splunk Apps](http://apps.splunk.com), you can install the app within Splunk Web. 36 | 37 | * For more, see [Where to get more apps and add-ons](http://docs.splunk.com/Documentation/Splunk/latest/Admin/Wheretogetmoreapps). 38 | 39 | ##### Installing from a ZIP Source File 40 | 41 | 1. [Download and unzip the Minecraft App](https://github.com/splunk/minecraft-app/archive/develop.zip) 42 | or clone the repository from [GitHub](https://github.com/splunk/minecraft-app.git). 43 | 2. Copy the entire `/minecraft-app` subdirectory into `$SPLUNK_HOME/etc/apps/`. 44 | 3. Restart Splunk. 45 | 4. In Splunk Web, navigate to the Minecraft App (*http://localhost:8000/dj/minecraft-app*). 46 | 47 | #### Event Collector Configuration 48 | 49 | 1. Confgiure the Splunk Http Event Collector as noted in the [Documentation](http://dev.splunk.com/view/event-collector/SP-CAAAE6M) for whatever port you like. 50 | 2. Ensure firewalls and NAT are properly configured if applicable 51 | 3. Take note of the application key as you will need it later on 52 | 53 | 54 | #### Installing the LogToSplunk Plugin 55 | 56 | 1. Copy the LogToSplunk jar from the app tgz or app directory into your craftbukkit server's `plugins` directory. 57 | 2. Create a `config` directory in the root server folder (the directory that contains the `plugins` folder). 58 | 3. Create and edit a `splunk.properties` text file in the `config` directory created in 3. Replace the port and application in your `splunk.properties` and adjust other options as necessary. 59 | 60 | * Use the app token from the Event Collector Configuration above for this property `splunk.craft.token=BEEFCAFE-1337-F00D-8BDA-2410D44E3453` 61 | 62 | * If you're running splunk on a separate machine from your minecraft server update this property `splunk.craft.connection.host=127.0.0.1` 63 | 64 | * Use the app token from the Event Collector Configuration above for this property `splunk.craft.connection.port=8088` 65 | 66 | * If you wish to log the output to a local log as well set this property to "true" `mod.splunk.enable.consolelog=false` 67 | 68 | #### Configuring The Livemap 69 | 70 | 1. Download and configure Overview as described int the [Overviewer Docs](http://docs.overviewer.org/en/latest/) 71 | 2. Serve the overviewer via a webserver like [Apache](http://httpd.apache.org) or [IIS](http://www.iis.net). Many operating systems have a web service built in that just needs to be enabled. 72 | 3. Create an initial render from overviewer with at least one of the "Normal","Lighting", and "Night" options 73 | 4. Copy the overviewer.css,overviewer.js, and overviewerConfig.js scripts from the base render directory to `$SPLUNK_HOME/etc/apps/minecraft-app/django/minecraft-app/static/minecraft-app/` on your splunk server. 74 | 5. Edit `$SPLUNK_HOME/etc/apps/minecraft-app/django/minecraft-app/static/minecraft-app/overviewerConfig.js` and modify the path variable of each tileset object to include your webserver path. For example, change `"path": "world-normal"` to `"path": "http://webserver:81/world-normal"`. The external hostname must be used in order for the map to be visible to clients. Using "localhost" as the webserver will not work as the minecraft app does not reserve the map, it simply redirects to it. 75 | 76 | NOTE: The minecraft-app does not refresh overviewer renders automatically. This will need to be scheduled by another service (ie. cron or task scheduler). 77 | 78 | 79 | #### Known Issues 80 | 81 | 1) Time calculations and active players may be mis-reported if player disconnects are not logged properly (ie. due to a server crash). Orphaned sessions may be estimated by running sessions from connection to the subsequent server start. 82 | 2) The live map may appear to "shift" as the minecraft world expands and overviewer resets it's origin in future renders. This can be corrected by recopying and modifying the overviewerConfig.js script with the same steps as the installation. 83 | 84 | 85 | 86 | ## Documentation and resources 87 | 88 | When you need to know more: 89 | 90 | * For Overviewer documentation, see [Overviewer](http://overviewer.org) 91 | 92 | * For Spigt documentation, see [Spigot](https://www.spigotmc.org) 93 | 94 | * For Forge documentation, see [Forge](http://www.minecraftforge.net/) 95 | 96 | * For CraftBukkit documentation, see [Craftbukkit](http://bukkit.org/) 97 | 98 | * For all things developer with Splunk, your main resource is the [Splunk Developer Portal](http://dev.splunk.com). 99 | 100 | * For component reference documentation, see the [Splunk Web Framework Reference](http://docs.splunk.com/Documentation/WebFramework). 101 | 102 | * For more about Splunk in general, see [Splunk>Docs](http://docs.splunk.com/Documentation/Splunk). 103 | 104 | 105 | ### How to contribute 106 | 107 | If you would like to contribute to the Minecraft App, go here for more information: 108 | 109 | * [Minecraft App Github](https://github.com/splunk/minecraft-app) 110 | 111 | Please feel free to open issues and provide feedback through GitHub Issues. 112 | 113 | ## License 114 | The Minecraft_App is licensed under the Apache License 2.0. Details can be found in the LICENSE file. 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /forge/src/main/java/com/splunk/forge/event_loggers/PlayerEventLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.forge.event_loggers; 2 | 3 | import java.util.Properties; 4 | 5 | import com.google.common.cache.Cache; 6 | import com.google.common.cache.CacheBuilder; 7 | import com.google.common.cache.CacheLoader; 8 | import com.splunk.sharedmc.Point3dLong; 9 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger; 10 | import com.splunk.sharedmc.loggable_events.LoggablePlayerEvent; 11 | 12 | import net.minecraft.entity.player.EntityPlayer; 13 | import net.minecraft.util.Vec3; 14 | import net.minecraft.world.World; 15 | import net.minecraftforge.event.ServerChatEvent; 16 | import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; 17 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 18 | import net.minecraftforge.fml.common.gameevent.PlayerEvent; 19 | import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; 20 | import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent; 21 | import net.minecraftforge.fml.relauncher.Side; 22 | import net.minecraftforge.fml.relauncher.SideOnly; 23 | 24 | /** 25 | * Handles the logging of player events. 26 | */ 27 | public class PlayerEventLogger extends AbstractEventLogger { 28 | public static final double GRANULARITY = 1.5; 29 | public static final int MAX_PLAYERS = 128; 30 | 31 | /** 32 | * Keeps track players last positions, in a guava cache for it's eviction policy. 33 | */ 34 | private final Cache lastKnownCoordinates = CacheBuilder.newBuilder().maximumSize(MAX_PLAYERS).build( 35 | new CacheLoader() { 36 | @Override 37 | public Vec3 load(String key) throws Exception { 38 | return lastKnownCoordinates.getIfPresent(key); 39 | } 40 | }); 41 | 42 | /** 43 | * Constructs a new PlayerEventLogger. 44 | * 45 | * @param props Properties to configure this EventLogger with. 46 | */ 47 | public PlayerEventLogger(Properties props) { 48 | super(props); 49 | } 50 | 51 | /** 52 | * Logs to Splunk when a player logs in. 53 | * 54 | * @param event The captured event. 55 | */ 56 | @SubscribeEvent 57 | @SideOnly(Side.SERVER) 58 | public void onPlayerConnect(PlayerLoggedInEvent event) { 59 | logAndSend( 60 | generateLoggablePlayerEvent(event, LoggablePlayerEvent.PlayerEventAction.PLAYER_CONNECT, null, null)); 61 | } 62 | 63 | /** 64 | * Logs to Splunk when a player logs out. 65 | * 66 | * @param event The captured event. 67 | */ 68 | @SubscribeEvent 69 | @SideOnly(Side.SERVER) 70 | public void onPlayerDisconnect(PlayerLoggedOutEvent event) { 71 | logAndSend( 72 | generateLoggablePlayerEvent( 73 | event, LoggablePlayerEvent.PlayerEventAction.PLAYER_DISCONNECT, null, null)); 74 | } 75 | 76 | /** 77 | * Logs to Splunk when a player chats and what they chat. 78 | * 79 | * @param chatEvent The captured chat event. 80 | */ 81 | @SubscribeEvent 82 | @SideOnly(Side.SERVER) 83 | public void onPlayerChat(ServerChatEvent chatEvent) { 84 | logAndSend( 85 | generateLoggablePlayerEvent(chatEvent, LoggablePlayerEvent.PlayerEventAction.CHAT, chatEvent.message)); 86 | } 87 | 88 | private static LoggablePlayerEvent generateLoggablePlayerEvent( 89 | PlayerEvent event, LoggablePlayerEvent.PlayerEventAction actionType, String reason, String message) { 90 | final World world = event.player.getEntityWorld(); 91 | final long worldTime = world.getWorldTime(); 92 | final String worldName = world.getWorldInfo().getWorldName(); 93 | final Vec3 playerPos = event.player.getPositionVector(); 94 | final Point3dLong coordinates = new Point3dLong(playerPos.xCoord, playerPos.yCoord, playerPos.zCoord); 95 | final LoggablePlayerEvent loggable = new LoggablePlayerEvent(actionType, worldTime, worldName, coordinates); 96 | loggable.setPlayerName(event.player.getDisplayNameString()); 97 | loggable.setReason(reason); 98 | loggable.setMessage(message); 99 | 100 | return loggable; 101 | } 102 | 103 | private static LoggablePlayerEvent generateLoggablePlayerEvent( 104 | ServerChatEvent event, LoggablePlayerEvent.PlayerEventAction actionType, String message) { 105 | final World world = event.player.getEntityWorld(); 106 | final long worldTime = world.getWorldTime(); 107 | final String worldName = world.getWorldInfo().getWorldName(); 108 | final Vec3 playerPos = event.player.getPositionVector(); 109 | final Point3dLong coordinates = new Point3dLong(playerPos.xCoord, playerPos.yCoord, playerPos.zCoord); 110 | final LoggablePlayerEvent loggable = new LoggablePlayerEvent(actionType, worldTime, worldName, coordinates); 111 | loggable.setPlayerName(event.player.getDisplayNameString()); 112 | loggable.setMessage(message); 113 | 114 | return loggable; 115 | } 116 | 117 | /** 118 | * Living update seems to get called about 10x/sec. We check if the update belongs to a player and if so we check if 119 | * the players position has changed significantly based on {@code GRANULARITY}. 120 | * 121 | * @param playerMove The captured event. 122 | */ 123 | @SubscribeEvent 124 | @SideOnly(Side.SERVER) 125 | public void onPlayerStatusReported(LivingUpdateEvent playerMove) { 126 | if (playerMove.entity instanceof EntityPlayer) { 127 | final String playerName = ((EntityPlayer) playerMove.entity).getDisplayNameString(); 128 | final Vec3 playerPos = playerMove.entity.getPositionVector(); 129 | 130 | //Don't log if position hasn't changed significantly. 131 | final Vec3 lastCoords = lastKnownCoordinates.getIfPresent(playerName); 132 | if (lastCoords != null && playerPos.distanceTo(lastCoords) < GRANULARITY) { 133 | return; 134 | } 135 | 136 | lastKnownCoordinates.put(playerName, playerPos); 137 | final Point3dLong coordinates = new Point3dLong(playerPos.xCoord, playerPos.yCoord, playerPos.zCoord); 138 | 139 | final World world = playerMove.entity.getEntityWorld(); 140 | final long worldTime = world.getWorldTime(); 141 | final String worldName = world.getWorldInfo().getWorldName(); 142 | 143 | // TODO: this needs to be in the from/to format. 144 | 145 | LoggablePlayerEvent playerEvent =new LoggablePlayerEvent( 146 | LoggablePlayerEvent.PlayerEventAction.LOCATION, worldTime, worldName, coordinates) 147 | .setPlayerName(playerName); 148 | 149 | if(lastCoords != null){ 150 | playerEvent.setFrom(new Point3dLong(lastCoords.xCoord, lastCoords.yCoord, lastCoords.zCoord)); 151 | playerEvent.setTo(coordinates); 152 | logAndSend(playerEvent); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /django/minecraft-app/templates/livemap.html: -------------------------------------------------------------------------------- 1 | {% extends "splunkdj:base_with_app_bar.html" %} 2 | 3 | {% load splunkmvc %} 4 | 5 | {% block title %}{{app_label}}{% endblock title %} 6 | 7 | {% block css %} 8 | 9 | 10 | 11 | {% endblock css %} 12 | 13 | {% block content %} 14 | 15 |
16 |

Loading...

17 |
18 |
19 |
20 |
21 |

22 |

23 |

24 |
25 |
26 |

27 |

    28 |

    29 |
    30 |
    31 |
    32 |
    33 |
    34 |
    35 |
    36 |
    37 |
    38 | 39 | {% chart id="block-count" managerid="block-count-search" type="column" height="400" %} 40 |
    41 |
    42 |
    43 | {% single id="blocks-placed-single" managerid="blocks-placed-search" afterLabel="Blocks Placed" %} 44 |
    45 |
    46 | {% single id="blocks-broken-single" managerid="blocks-broken-search" afterLabel="Blocks Collected" %} 47 |
    48 |
    49 |
    50 |
    51 |
    52 | 53 | {% endblock content%} 54 | 55 | {% block managers %} 56 | 57 | {% searchmanager id="block-count-search" earliest_time="-20w" latest_time="now" search="sourcetype=minecraft_log action = block_placed block_type!=DIRT block_type!=CROPS block_type!=TORCH player!=dlg597 player!=PikaCheeseaweed | chart count by player,block_type" %} 58 | 59 | {% endblock managers %} 60 | 61 | {% block js %} 62 | 63 | 64 | 65 | 215 | 216 | 217 | {% endblock js %} -------------------------------------------------------------------------------- /django/minecraft-app/templates/brokenblocks.html: -------------------------------------------------------------------------------- 1 | {% extends "splunkdj:base_with_app_bar.html" %} 2 | 3 | {% load splunkmvc %} 4 | 5 | {% block title %}{{app_label}}{% endblock title %} 6 | 7 | {% block css %} 8 | 9 | {% endblock css %} 10 | 11 | {% block content %} 12 |
    13 |
    14 |
    15 |
    16 |

    Top Mined Blocks

    17 |
    18 | {% chart id="most-mined-chart" managerid="most-mined-search" %} 19 |
    20 |
    21 |
    22 |

    Deforestation

    23 |
    24 | {% chart id="logging-chart" managerid="logging-search" %} 25 |
    26 |
    27 |
    28 |

    Agriculture

    29 |
    30 | {% chart id="agriculture-chart" managerid="agriculture-search" %} 31 |
    32 |
    33 |
    34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 |

    Rare (and Less Rare) Ores

    41 |
    42 |
    43 |
    44 | 45 |
    46 |
    47 | {% single managerid="coal-ore-search" id="coal-single" afterLabel="Coal Ore Mined" %} 48 |
    49 |
    50 | 51 |
    52 |
    53 | {% single managerid="redstone-ore-search" id="redstone-single" afterLabel="Redstone Ore Mined" %} 54 |
    55 |
    56 |
    57 |
    58 | 59 |
    60 |
    61 | {% single managerid="lapis-ore-search" id="lapis-single" afterLabel="Lapis Ore Mined" %} 62 |
    63 |
    64 | 65 |
    66 |
    67 | {% single managerid="iron-ore-search" id="iron-single" afterLabel="Iron Ore Mined" %} 68 |
    69 |
    70 |
    71 |
    72 | 73 |
    74 |
    75 | {% single managerid="gold-ore-search" id="gold-single" afterLabel="Gold Ore Mined" %} 76 |
    77 |
    78 | 79 |
    80 |
    81 | {% single managerid="diamond-ore-search" id="diamond-single" afterLabel="Diamond Ore Mined" %} 82 |
    83 |
    84 |
    85 |
    86 | 87 |
    88 |
    89 | {% single managerid="emerald-ore-search" id="emerald-single" afterLabel="Emerald Ore Mined" %} 90 |
    91 |
    92 | 93 |
    94 |
    95 | {% single managerid="quartz-ore-search" id="quartz-single" afterLabel="Nether Quartz Ore Mined" %} 96 |
    97 |
    98 |
    99 |
    100 |
    101 |

    Top Miners

    102 |
    103 | {% chart id="top-miners-chart" managerid="top-miners-search" height="336" %} 104 |
    105 |
    106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    112 |
    113 |

    Mining Rate By Block

    114 |
    115 | {% chart id="mining-rate-chart" managerid="mining-rate-search" %} 116 |
    117 |
    118 |
    119 |
    120 |
    121 |
    122 |
    123 |
    124 |
    125 |
    126 |
    127 | {% endblock content %} 128 | 129 | {% block managers %} 130 | 131 | {% searchmanager id="most-mined-search" search="sourcetype=minecraft_log action=block_broken block_type!=TORCH block_type!=CROPS | top limit=5 block_type" %} 132 | {% searchmanager id="logging-search" search="sourcetype=minecraft_log action=block_broken block_type=*_LOG | stats count by block_type" %} 133 | {% searchmanager id="agriculture-search" search="sourcetype=minecraft_log action=block_broken AND (block_type=potato OR block_type=carrot OR block_type=watermelon OR block_type=pumpkin OR block_type=crops) | stats count by block_type" %} 134 | 135 | {% searchmanager id="coal-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=COAL_ORE | stats count" %} 136 | {% searchmanager id="redstone-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=REDSTONE_ORE OR block_type=GLOWING_REDSTONE_ORE | stats count" %} 137 | {% searchmanager id="lapis-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=LAPIS_ORE | stats count" %} 138 | {% searchmanager id="iron-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=IRON_ORE | stats count" %} 139 | {% searchmanager id="gold-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=GOLD_ORE | stats count" %} 140 | {% searchmanager id="diamond-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=DIAMOND_ORE | stats count" %} 141 | {% searchmanager id="emerald-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=EMERALD_ORE | stats count" %} 142 | {% searchmanager id="quartz-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=QUARTZ_ORE | stats count" %} 143 | 144 | {% searchmanager id="top-miners-search" search="sourcetype=minecraft_log action=block_broken | chart count by player" %} 145 | 146 | {% searchmanager id="mining-rate-search" search="sourcetype=minecraft_log action=block_broken block_type!=TORCH block_type!=CROPS | timechart count by block_type" %} 147 | 148 | {% endblock managers %} 149 | 150 | {% block js %} 151 | 152 | 181 | 182 | {% endblock js %} -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/calendarheatmap/calendarheatmap.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // calheat! 4 | // shows a cool looking heatmap based on different time signatures 5 | // requires a timechart search. it dynamically guesses how to set up the 6 | // way to show the time, but you can define any settings you want in the html 7 | // docs: http://kamisama.github.io/cal-heatmap 8 | 9 | // ---settings--- 10 | 11 | // domain: (hour, day, week, month, year) 12 | // subDomain: (min, x_min, hour, x_hour, day, x_day, week, x_week, month, x_month) 13 | // -- x_ variants are used to rotate the reading order to left to right, then top to bottom. 14 | // start: set to 'current' for current time or 'earliest' for your earliest data point 15 | 16 | // TODO: 17 | // add a setting for each option at http://kamisama.github.io/cal-heatmap/#options 18 | // rather than using the JS method in the HTML like i'm doing now. 19 | 20 | 21 | 22 | // the data is expected in this format after formatData (epoch time: event count): 23 | // { 24 | // "timestamps":[ 25 | // { 26 | // "1378225500":"8", 27 | // "1378225560":"8", 28 | // "1378225620":"8", 29 | // }, 30 | // { 31 | // "1378230300":"4", 32 | // "1378230360":"4", 33 | // "1378230660":"2" 34 | // }, 35 | // { 36 | // "1378225500":"7", 37 | // "1378225560":"7", 38 | // }, 39 | // { 40 | // "1378225500":"6", 41 | // "1378225560":"6", 42 | // "1378225620":"7", 43 | // }, 44 | // { 45 | // "1378225500":"41", 46 | // "1378225560":"41", 47 | // }, 48 | // { 49 | // "1378225500":"22", 50 | // "1378225560":"22", 51 | // } 52 | // ], 53 | 54 | // -- we add this part onto the actual data -- 55 | 56 | // "start":"2013-09-03T16:25:00.000Z", 57 | // "domain":"hour", 58 | // "subDomain":"min" 59 | // } 60 | 61 | define(function(require, exports, module) { 62 | 63 | var _ = require('underscore'); 64 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview"); 65 | var d3 = require("../d3/d3"); 66 | var CalHeatMap = require("./contrib/cal-heatmap"); 67 | 68 | require("css!./calendarheatmap.css"); 69 | 70 | var CalendarHeatMap = SimpleSplunkView.extend({ 71 | moduleId: module.id, 72 | 73 | className: "splunk-toolkit-cal-heatmap", 74 | 75 | options: { 76 | managerid: "search1", // your MANAGER ID 77 | data: "preview", // Results type 78 | domain: 'hour', // the largest unit it will differentiate by in squares 79 | subDomain: 'min', // the smaller unit the calheat goes off of 80 | formatLabel: _.identity, 81 | uID: null, 82 | options: {} // the default for custom heatmap options. 83 | }, 84 | 85 | output_mode: "json_rows", 86 | 87 | initialize: function() { 88 | 89 | SimpleSplunkView.prototype.initialize.apply(this, arguments); 90 | 91 | this.settings.enablePush("value"); 92 | 93 | // whenever domain or subDomain are changed, we will re-render. 94 | this.settings.on("change:domain", this.onDomainChange, this); 95 | this.settings.on("change:subDomain", this.onDomainChange, this); 96 | var uniqueID=Math.floor(Math.random()*1000001); 97 | this.settings.set("uID", uniqueID); 98 | }, 99 | 100 | onDomainChange: function() { 101 | 102 | var dom = this.settings.get('domain'); 103 | var sd = this.settings.get('subDomain'); 104 | 105 | // Knock off the prefix cause it doesnt matter here 106 | var sdShort = sd.replace("x_", ""); 107 | 108 | var validDomains = { 109 | 'min' : ['hour'], 110 | 'hour' : ['day', 'week'], 111 | 'day' : ['week', 'month', 'year'], 112 | 'week' : ['month', 'year'], 113 | 'month' : ['year'] 114 | }; 115 | 116 | // If the current domain is valid for this subdomain 117 | if (_.contains(validDomains[sdShort], dom)){ 118 | this.render(); 119 | } 120 | else{ 121 | console.log(sd + " is and invalid subDomain for " + dom); 122 | } 123 | }, 124 | 125 | createView: function() { 126 | return true; 127 | }, 128 | 129 | // making the data look how we want it to for updateView to do its job 130 | // in this case, it looks like this: 131 | // {timestamp1: count, timestamp2: count, ... } 132 | formatData: function(data) { 133 | var rawFields = this.resultsModel.data().fields; 134 | var domain = this.settings.get('domain'); 135 | var subDomain = this.settings.get('subDomain'); 136 | 137 | var filteredFields = _.filter(rawFields, function(d){return d[0] !== "_" }); 138 | var objects = _.map(data, function(row) { 139 | return _.object(rawFields, row); 140 | }); 141 | 142 | var series = []; 143 | for(var i = 0; i < filteredFields.length; i++) { 144 | series.push({ name: filteredFields[i], timestamps: {}, min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY }); 145 | } 146 | 147 | _.each(objects, function(object) { 148 | // Get the timestamp for this object 149 | var time = new Date(object['_time']); 150 | var timeValue = time.valueOf() / 1000; 151 | 152 | // For each actual value, store it in the timestamp object 153 | _.each(filteredFields, function(field, i) { 154 | var value = object[field]; 155 | series[i].timestamps[timeValue] = parseInt(value, 10) || 0; 156 | series[i].min = Math.min(series[i].min, value); 157 | series[i].max = Math.max(series[i].max, value); 158 | }); 159 | }); 160 | 161 | _.each(series, function(serie) { 162 | 163 | }); 164 | 165 | return { 166 | series: series, 167 | domain: domain, 168 | subDomain: subDomain, 169 | start: new Date(objects[0]['_time']), 170 | min: new Date(objects[0]['_time']), 171 | max: new Date(objects[objects.length - 1]['_time']), 172 | }; 173 | }, 174 | 175 | updateView: function(viz, data) { 176 | userOptions = this.settings.get('options') 177 | 178 | this.$el.html(''); 179 | 180 | var that = this; 181 | _.each(data.series, function(series, idx) { 182 | var scale = d3.scale.quantile() 183 | .domain([series.min, series.max]) 184 | .range([0,1,2,3,4]); 185 | var legend = _.map(scale.quantiles(), function(x) { return Math.round(x); }); 186 | 187 | var $el = $("
    ").appendTo(that.el); 188 | var $title = $("

    Heatmap for: " + series.name + "

    ").appendTo($el); 189 | var $buttons = $("
    ").appendTo($el); 190 | var $prev = $("").appendTo($buttons); 191 | var $next = $("").appendTo($buttons); 192 | var options = _.extend({ 193 | itemSelector: $el[0], 194 | previousSelector: $prev[0], 195 | nextSelector: $next[0], 196 | data: series.timestamps, 197 | domain: data.domain, 198 | subDomain: data.subDomain, 199 | start: data.start, 200 | range: 4, 201 | cellSize: 12, 202 | cellPadding: 3, 203 | domainGutter: 10, 204 | highlight: ['now', new Date()], 205 | legend: legend, 206 | legendMargin: [0, 0, 20, 0], 207 | legendCellSize: 14, 208 | minDate: data.min, 209 | maxDate: data.max, 210 | onMinDomainReached: function(hit) { 211 | $prev.attr("disabled", hit ? "disabled" : false); 212 | }, 213 | onMaxDomainReached: function(hit) { 214 | $next.attr("disabled", hit ? "disabled" : false); 215 | }, 216 | onClick: function(date, value) { 217 | that.trigger('click', { date: date, value: value }); 218 | that.settings.set('value', date.valueOf()); 219 | }, 220 | }, userOptions); 221 | 222 | var cal = new CalHeatMap(); 223 | cal.init(options); // create the calendar using either default or user defined options */ 224 | 225 | if (idx < data.series.length - 1) { 226 | $("
    ").appendTo($el); 227 | } 228 | }); 229 | } 230 | }); 231 | 232 | return CalendarHeatMap; 233 | }); -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/sunburst/sunburst.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var _ = require('underscore'); 4 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview"); 5 | var nester = require("../underscore-nest/underscore-nest"); 6 | var d3 = require("../d3/d3"); 7 | 8 | require("css!./sunburst.css"); 9 | 10 | window.nester = nester; 11 | 12 | var Sunburst = SimpleSplunkView.extend({ 13 | moduleId: module.id, 14 | 15 | className: "splunk-toolkit-sunburst", 16 | 17 | options: { 18 | managerid: null, 19 | data: 'preview', 20 | chartTitle: null, 21 | valueField: null, 22 | categoryFields: null, 23 | formatLabel: _.identity, 24 | formatTooltip: function(d) { 25 | return (d.name || "Total") + ": " + d.value; 26 | } 27 | }, 28 | 29 | output_mode: "json_rows", 30 | 31 | initialize: function() { 32 | SimpleSplunkView.prototype.initialize.apply(this, arguments); 33 | 34 | // TODO: enable push 35 | // TODO: wire up changes 36 | 37 | this.settings.on("change:valueField", this.render, this); 38 | this.settings.on("change:categoryFields", this.render, this); 39 | 40 | // Set up resize callback. The first argument is a this 41 | // pointer which gets passed into the callback event 42 | $(window).resize(this, _.debounce(this._handleResize, 20)); 43 | }, 44 | 45 | _handleResize: function(e){ 46 | 47 | // e.data is the this pointer passed to the callback. 48 | // here it refers to this object and we call render() 49 | e.data.render(); 50 | }, 51 | 52 | createView: function() { 53 | // Here we wet up the initial view layout 54 | var margin = {top: 30, right: 30, bottom: 30, left: 30}; 55 | var availableWidth = parseInt(this.settings.get("width") || this.$el.width()); 56 | var availableHeight = parseInt(this.settings.get("height") || this.$el.height()); 57 | 58 | this.$el.html(""); 59 | 60 | var svg = d3.select(this.el) 61 | .append("svg") 62 | .attr("width", availableWidth) 63 | .attr("height", availableHeight) 64 | .attr("pointer-events", "all"); 65 | 66 | // The returned object gets passed to updateView as viz 67 | return { container: this.$el, svg: svg, margin: margin}; 68 | }, 69 | 70 | // making the data look how we want it to for updateView to do its job 71 | formatData: function(data) { 72 | var valueField = this.settings.get('valueField'); 73 | var rawFields = this.resultsModel.data().fields; 74 | var fieldList = this.settings.get("categoryFields"); 75 | if(fieldList){ 76 | fieldList = fieldList.split(/[ ,]+/); 77 | } 78 | else{ 79 | fieldList = this.resultsModel.data().fields; 80 | } 81 | var objects = _.map(data, function(row) { 82 | return _.object(rawFields, row); 83 | }); 84 | var dataResults = nester.nest(objects, fieldList, function(children) { 85 | var total = 0; 86 | _.each(children, function(child){ 87 | var size = child[valueField] || 1; 88 | total += size; 89 | }) 90 | return total; 91 | }); 92 | dataResults['name'] = this.settings.get("chartTitle") || ""; 93 | data = { 94 | 'results': dataResults, 95 | 'fields': fieldList 96 | } 97 | return data; 98 | }, 99 | 100 | updateView: function(viz, data) { 101 | var that = this; 102 | var formatLabel = this.settings.get("formatLabel") || _.identity; 103 | var formatTooltip = this.settings.get("formatTooltip") || function(d) { return d.name; }; 104 | var containerHeight = this.$el.height(); 105 | var containerWidth = this.$el.width(); 106 | 107 | // Clear svg 108 | var svg = $(viz.svg[0]); 109 | svg.empty(); 110 | svg.height(containerHeight); 111 | svg.width(containerWidth); 112 | 113 | // Add the graph group as a child of the main svg 114 | var graphWidth = containerWidth - viz.margin.left - viz.margin.right 115 | var graphHeight = containerHeight - viz.margin.top - viz.margin.bottom; 116 | var graph = viz.svg 117 | .append("g") 118 | .attr("width", graphWidth) 119 | .attr("height", graphHeight) 120 | .attr("transform", "translate(" 121 | + ((graphWidth/2) + viz.margin.left ) + "," 122 | + ((graphHeight/2) + viz.margin.top ) + ")"); 123 | 124 | var radius = Math.min(graphWidth, graphHeight) / 2; 125 | 126 | var color = d3.scale.category20c(); 127 | 128 | var x = d3.scale.linear() 129 | .range([0, 2 * Math.PI]); 130 | 131 | var y = d3.scale.linear() 132 | .range([0, radius]); 133 | 134 | var partition = d3.layout.partition() 135 | .value(function(d) { return d['value']; }); 136 | 137 | var arc = d3.svg.arc() 138 | .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) 139 | .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) 140 | .innerRadius(function(d) { return Math.max(0, y(d.y)); }) 141 | .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); 142 | 143 | var root = data.results; 144 | 145 | var g = graph.selectAll("g") 146 | .data(partition.nodes(root)) 147 | .enter().append("g"); 148 | 149 | var path = g.append("path") 150 | .attr("d", arc) 151 | .style("fill", function(d) {return color((d.children ? d : d.parent).name); }) 152 | .on("click", click); 153 | 154 | path.append("title") 155 | .text(formatTooltip); 156 | 157 | var text = g.append("text") 158 | .attr("text-anchor", function(d) { 159 | return x(d.x + d.dx / 2) > Math.PI ? "end" : "start"; 160 | }) 161 | .attr("transform", function(d) { 162 | var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90; 163 | var rotate = angle; 164 | var padding = 5; 165 | return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")"; 166 | }) 167 | .attr("dy", ".2em") 168 | .attr("x", 0) 169 | .text(function(d) { return formatLabel(d.name); }) 170 | .on("click", click); 171 | 172 | text.append("title") 173 | .text(formatTooltip); 174 | 175 | function click(d) { 176 | // fade out all text elements 177 | text.transition().attr("opacity", 0); 178 | 179 | path.transition() 180 | .duration(750) 181 | .attrTween("d", arcTween(d)) 182 | .each("end", function(e, i) { 183 | // check if the animated element's data e lies within the visible angle span given in d 184 | if (e.x >= d.x && e.x < (d.x + d.dx)) { 185 | // get a selection of the associated text element 186 | var arcText = d3.select(this.parentNode).select("text"); 187 | // fade in the text element and recalculate positions 188 | arcText.transition().duration(750) 189 | .attr("opacity", 1) 190 | .attr("text-anchor", function(d) { 191 | return x(d.x + d.dx / 2) > Math.PI ? "end" : "start"; 192 | }) 193 | .attr("transform", function(d) { 194 | var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90; 195 | var rotate = angle; 196 | var padding = 5; 197 | return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")"; 198 | }) 199 | .attr("dy", ".2em") 200 | .attr("x", 0) 201 | } 202 | }); 203 | } 204 | 205 | // Interpolate the scales! 206 | function arcTween(d) { 207 | var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), 208 | yd = d3.interpolate(y.domain(), [d.y, 1]), 209 | yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); 210 | return function(d, i) { 211 | return i 212 | ? function(t) { return arc(d); } 213 | : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); }; 214 | }; 215 | } 216 | 217 | function computeTextRotation(d) { 218 | return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180; 219 | } 220 | 221 | } 222 | }); 223 | return Sunburst; 224 | }); -------------------------------------------------------------------------------- /django/minecraft-app/static/minecraft-app/components/bubblechart/bubblechart.js: -------------------------------------------------------------------------------- 1 | // Bubble Chart 2 | // this displays information as different 'bubbles,' their unique values represented with 3 | // the size of the bubble. 4 | // supports drilldown clicks 5 | 6 | // available settings: 7 | // - labelField: the field to use as the label on each bubble 8 | // - valueField: the field to use as the value of each bubble (also dictates size) 9 | // - categoryField: the field to use for grouping similar data (usually the same field as labelField) 10 | 11 | // ---expected data format--- 12 | // a splunk search like this: source=foo | stats count by artist_name, track_name 13 | 14 | define(function(require, exports, module) { 15 | 16 | var _ = require('underscore'); 17 | var d3 = require("../d3/d3"); 18 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview"); 19 | 20 | require("css!./bubblechart.css"); 21 | 22 | var BubbleChart = SimpleSplunkView.extend({ 23 | moduleId: module.id, 24 | 25 | className: "splunk-toolkit-bubble-chart", 26 | 27 | options: { 28 | managerid: null, 29 | data: "preview", 30 | labelField: null, 31 | labelField: null, 32 | valueField: 'count', 33 | categoryField: null, 34 | }, 35 | 36 | output_mode: "json", 37 | 38 | initialize: function() { 39 | 40 | SimpleSplunkView.prototype.initialize.apply(this, arguments); 41 | 42 | this.settings.enablePush("value"); 43 | 44 | // in the case that any options are changed, it will dynamically update 45 | // without having to refresh. copy the following line for whichever field 46 | // you'd like dynamic updating on 47 | this.settings.on("change:valueField", this.render, this); 48 | this.settings.on("change:labelField", this.render, this); 49 | this.settings.on("change:categoryField", this.render, this); 50 | 51 | // NOTE: nameField is depricated. Use labelField 52 | if (this.settings.get("nameField")) { 53 | this.settings.set("labelField", this.settings.get("nameField")); 54 | } 55 | this.settings.on("change:nameField", this._onNameFieldChange, this); 56 | 57 | // Set up resize callback. The first argument is a this 58 | // pointer which gets passed into the callback event 59 | $(window).resize(this, _.debounce(this._handleResize, 20)); 60 | }, 61 | 62 | _handleResize: function(e){ 63 | 64 | // e.data is the this pointer passed to the callback. 65 | // here it refers to this object and we call render() 66 | e.data.render(); 67 | }, 68 | 69 | // This is for compatibility. nameField is depricated and labelField should be used 70 | _onNameFieldChange: function(e){ 71 | this.settings.set("labelField", this.settings.get("nameField")); 72 | this.render(); 73 | }, 74 | 75 | createView: function() { 76 | 77 | // Here we wet up the initial view layout 78 | var margin = {top: 0, right: 0, bottom: 0, left: 0}; 79 | var availableWidth = parseInt(this.settings.get("width") || this.$el.width()); 80 | var availableHeight = parseInt(this.settings.get("height") || this.$el.height()); 81 | 82 | this.$el.html(""); 83 | 84 | var svg = d3.select(this.el) 85 | .append("svg") 86 | .attr("width", availableWidth) 87 | .attr("height", availableHeight) 88 | .attr("pointer-events", "all"); 89 | 90 | var tooltip = d3.select(this.el).append("div") 91 | .attr("class", "bubble-chart-tooltip"); 92 | 93 | // The returned object gets passed to updateView as viz 94 | return { container: this.$el, svg: svg, margin: margin, tooltip: tooltip}; 95 | }, 96 | 97 | // making the data look how we want it to for updateView to do its job 98 | formatData: function(data) { 99 | // getting settings 100 | var labelField = this.settings.get('labelField'); 101 | var valueField = this.settings.get('valueField'); 102 | var categoryField = this.settings.get('categoryField'); 103 | var collection = data; 104 | var bubblechart = { 'name': labelField+"s", 'children': [ ] }; // how we want it to look 105 | 106 | // making the children formatted array 107 | for (var i=0; i < collection.length; i++) { 108 | var Idx = -1; 109 | $.each(bubblechart.children, function(idx, el) { 110 | if (el.name == collection[i][categoryField]) { 111 | Idx = idx; 112 | } 113 | }); 114 | if (Idx == -1) { 115 | bubblechart.children.push({ 'name': collection[i][categoryField], children: [ ] }); 116 | Idx = bubblechart.children.length - 1; 117 | } 118 | 119 | bubblechart.children[Idx].children.push({ 'name': collection[i][labelField], 'size': collection[i][valueField] || 1 }); 120 | } 121 | return bubblechart; // this is passed into updateView as 'data' 122 | }, 123 | 124 | updateView: function(viz, data) { 125 | var that = this; 126 | 127 | // Clear svg 128 | var svg = $(viz.svg[0]); 129 | svg.empty(); 130 | 131 | var tooltip = viz.tooltip; 132 | 133 | // Add the graph group as a child of the main svg 134 | var graph = viz.svg 135 | .append("g") 136 | .attr("class", "bubble") 137 | .attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")"); 138 | 139 | // Set format and color 140 | var format = d3.format(",d"); 141 | var color = d3.scale.category20c(); 142 | 143 | // We have two phases in layout. We tell the 144 | // d3 lout how much room it has, then set 145 | // the sizes of it's containers to match 146 | // the size it returns. 147 | var containerHeight = this.$el.height(); 148 | var containerWidth = this.$el.width(); 149 | var diameter = Math.min(containerWidth, containerHeight); 150 | 151 | // Tell the layout to layout 152 | var bubble = d3.layout.pack() 153 | .sort(null) 154 | .size([diameter, diameter]) 155 | .padding(1.5); 156 | 157 | // Set containers' sizes to match actual layout 158 | var width = bubble.size()[0]; 159 | var height = bubble.size()[1]; 160 | graph.attr("width", width) 161 | .attr("height", height); 162 | svg.height(height); 163 | svg.width(width); 164 | 165 | var node = graph.selectAll(".node") 166 | .data(bubble.nodes(classes(data)) 167 | .filter(function(d) { return !d.children; })) 168 | .enter().append("g") 169 | .attr("class", "node") 170 | .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); 171 | 172 | // NOTE: this is taken out because we have a custom tooltip. 173 | // It may need to be put back for accessibility 174 | // node.append("title") 175 | // .text(function(d) { return d.className + ": " + format(d.value); }); 176 | 177 | node.append("circle") 178 | .attr("r", function(d) { return d.r; }) 179 | .style("fill", function(d) { return color(d.packageName); }); 180 | 181 | node.append("text") 182 | .attr("dy", ".3em") 183 | .style("text-anchor", "middle") 184 | // ensure the text is truncated if the bubble is tiny 185 | .text(function(d) { return (d.className + " " + format(d.value)).substring(0, d.r / 3); }); 186 | 187 | // Re-flatten the child array 188 | function classes(data) { 189 | var classes = []; 190 | function recurse(name, node) { 191 | if (node.children) 192 | node.children.forEach(function(child) { 193 | recurse(node.name, child); 194 | }); 195 | else 196 | classes.push({packageName: name || "", className: node.name || "", value: node.size}); 197 | } 198 | 199 | recurse(null, data); 200 | return {children: classes}; 201 | } 202 | 203 | // Tooltips 204 | function doMouseEnter(d){ 205 | var text; 206 | if(d.className === undefined || d.className === ""){ 207 | text = "Event: " + d.value; 208 | } else { 209 | text = d.className+": " + d.value; 210 | } 211 | tooltip 212 | .text(text) 213 | .style("opacity", function(){ 214 | if(d.value !== undefined) { return 1; } 215 | return 0; 216 | }) 217 | .style("left", (d3.mouse(that.el)[0]) + "px") 218 | .style("top", (d3.mouse(that.el)[1]) + "px"); 219 | } 220 | 221 | // More tooltips 222 | function doMouseOut(d){ 223 | tooltip.style("opacity", 1e-6); 224 | } 225 | 226 | node.on("mouseover", doMouseEnter); 227 | node.on("mouseout", doMouseOut); 228 | 229 | // Drilldown clickings. edit this in order to change the search token that 230 | // is set to 'value' (a token in bubbles django), this will change the drilldown 231 | // search. 232 | node.on('click', function(e) { 233 | var clickEvent = { 234 | name: e.className, 235 | category: e.packageName, 236 | value: e.value 237 | }; 238 | that.settings.set("value", e.className); 239 | that.trigger("click", clickEvent); 240 | }); 241 | } 242 | }); 243 | return BubbleChart; 244 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | --------------------------------------------------------------------------------