Added source for the dashboard with Kairosdb ext
Change-Id: Ifebd657560f685e3361a7334994fd8d590812acc
This commit is contained in:
parent
9a20239de3
commit
389fdf146b
BIN
v2/assets/img/moni/network.png
Normal file
BIN
v2/assets/img/moni/network.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> OpenStack Nova Compute </h3>\n<br/>\nThis dashboard provides information on the compute nodes and their OpenStack related processes.",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #6c9842; background-color: #7b68af; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/compute.png\"><h3 style=\"color:#ffffff;margin: 7px;\">OpenStack Nova Compute </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard provides information on the compute nodes and their OpenStack related processes.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Compute"
|
||||
},
|
||||
@ -60,7 +60,7 @@
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
@ -91,7 +91,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-api-metadata.ps_cputime.user"
|
||||
"metric": "processes.nova-api.ps_cputime.user"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -106,7 +106,8 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-compute.ps_cputime.user"
|
||||
"metric": "processes.nova-compute.ps_cputime.user",
|
||||
"hide": false
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -121,7 +122,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-consoleauth.ps_cputime.user"
|
||||
"metric": "processes.nova-conductor.ps_cputime.user"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -136,7 +137,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.ceilometer-agent-compute.ps_cputime.user"
|
||||
"metric": "processes.nova-cert.ps_cputime.user"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
@ -176,7 +177,7 @@
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
@ -207,7 +208,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-api-metadata.ps_cputime.system"
|
||||
"metric": "processes.nova-api.ps_cputime.syst"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -222,7 +223,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-compute.ps_cputime.system"
|
||||
"metric": "processes.nova-compute.ps_cputime.syst"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -237,7 +238,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-consoleauth.ps_cputime.system"
|
||||
"metric": "processes.nova-conductor.ps_cputime.syst"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -252,7 +253,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.ceilometer-agent-compute.ps_cputime.system"
|
||||
"metric": "processes.nova-cert.ps_cputime.syst"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
@ -263,7 +264,7 @@
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "New row",
|
||||
"title": "Disk Ops",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
@ -302,7 +303,7 @@
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
@ -333,7 +334,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-api-metadata.ps_disk_ops.read"
|
||||
"metric": "processes.nova-api.ps_disk_ops.read"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -363,7 +364,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-consoleauth.ps_disk_ops.read"
|
||||
"metric": "processes.nova-conductor.ps_disk_ops.read"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -378,7 +379,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.ceilometer-agent-compute.ps_disk_ops.read"
|
||||
"metric": "processes.nova-cert.ps_disk_ops.read"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
@ -449,7 +450,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-api-metadata.ps_disk_ops.write"
|
||||
"metric": "processes.nova-api.ps_disk_ops.write"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -479,7 +480,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-consoleauth.ps_disk_ops.write"
|
||||
"metric": "processes.nova-conductor.ps_disk_ops.write"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -494,7 +495,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.ceilometer-agent-compute.ps_disk_ops.write"
|
||||
"metric": "processes.nova-cert.ps_disk_ops.write"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
@ -505,7 +506,7 @@
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "New row",
|
||||
"title": "Disk Ops",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
@ -544,7 +545,7 @@
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
@ -575,7 +576,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-api-metadata.ps_disk_octets.read"
|
||||
"metric": "processes.nova-api.ps_disk_octets.read"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -605,7 +606,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-consoleauth.ps_disk_octets.read"
|
||||
"metric": "processes.nova-conductor.ps_disk_octets.read"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -620,7 +621,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.ceilometer-agent-compute.ps_disk_octets.read"
|
||||
"metric": "processes.nova-cert.ps_disk_octets.read"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
@ -691,7 +692,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-api-metadata.ps_disk_octets.write"
|
||||
"metric": "processes.nova-api.ps_disk_octets.write"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -721,7 +722,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.nova-consoleauth.ps_disk_octets.write"
|
||||
"metric": "processes.nova-conductor.ps_disk_octets.write"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -736,7 +737,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.ceilometer-agent-compute.ps_disk_octets.write"
|
||||
"metric": "processes.nova-cert.ps_disk_octets.write"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> OpenStack Controller </h3>\n<br/>\nThis dashboard provides process information specifically configured for the OpenStack Controller node. ",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #6c9842; background-color: #d6487e; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/controller.png\"><h3 style=\"color:#ffffff;margin: 7px;\">OpenStack Controller </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard provides process information specifically configured for the OpenStack Controller node. <br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Controller"
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"height": "450px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> OpenStack Supporting Database Info </H3>\n<br/>\nThis dashboard is to show information that helps discern the state of supporting databases primarily MySQL or Postgres. This may additionally include information on Redis, Cassandra, Mongo and/or ElasticSearch.",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #a0a0a0; background-color: #a0a0a0; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/database.png\"><h3 style=\"color:#ffffff;margin: 7px;\">Cluster Databases </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard is to show information that helps discern the state of supporting databases primarily MySQL or Postgres. This may additionally include information on Redis, Cassandra, Mongo and/or ElasticSearch.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "OS Database"
|
||||
},
|
||||
@ -235,6 +235,192 @@
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "450px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "kairosdb.datastore.cassandra.key_query_time",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "kairosdb.http.query_time",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "kairosdb.http.request_time",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "KairosDB Process"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "kairosdb.metric_counters",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Unique Metrics Collected"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> OpenStack Glance Image Service </h3>\n<br/>\nThis dashboard provides information on the Glance image nodes and their OpenStack related processes.",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #a0a0a0; background-color: #a0a0a0; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/database.png\"><h3 style=\"color:#ffffff;margin: 7px;\">OpenStack Glance Image Service </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard provides information on the Glance image nodes and their OpenStack related processes.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Image"
|
||||
},
|
||||
@ -112,6 +112,331 @@
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Glance Processes"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.glance-api.ps_disk_octets.read"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.glance-api.ps_disk_octets.write"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Glance Disk Octets"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.glance-registry.ps_cputime.user"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.glance-registry.ps_cputime.syst"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Glance Registry Process"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.glance-registry.ps_vm.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Glance Registry VM"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.glance-registry.ps_stacksize.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Glance Registry Stack"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Summary",
|
||||
"height": "250px",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
@ -22,8 +22,8 @@
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "markdown",
|
||||
"content": "",
|
||||
"mode": "html",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #6fb3e0; background-color: #6fb3e0; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/messagebus.png\"><h3 style=\"color:#ffffff;margin: 7px;\">Message Bus </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard provides information on RabbitMQ/AMPQ their OpenStack related processes.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Message Bus"
|
||||
},
|
||||
@ -245,7 +245,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "rabbitmq_info.gauge.pmap_mapped.value"
|
||||
"metric": "processes.rabbitmq-server.ps_cputime.syst"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -260,8 +260,8 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "rabbitmq_info.gauge.pmap_shared.value",
|
||||
"hide": true
|
||||
"metric": "processes.rabbitmq-server.ps_cputime.user",
|
||||
"hide": false
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
@ -276,7 +276,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "rabbitmq_info.gauge.pmap_used.value"
|
||||
"metric": "processes.rabbitmq-server.ps_stacksize.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
@ -374,6 +374,102 @@
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "New row",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 0,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "rabbitmq_info.gauge.ctl_consumers_compute.value"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "rabbitmq_info.gauge.ctl_consumers_conductor.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Bus Consumers"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
|
@ -4,21 +4,32 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "2014-09-04T21:59:52.065Z",
|
||||
"to": "2014-09-04T22:01:09.479Z"
|
||||
"from": "2014-10-27T17:10:56.791Z",
|
||||
"to": "2014-10-27T17:15:29.777Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "test",
|
||||
"height": "350px",
|
||||
"title": "Loads",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"error": false,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "html",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #6c9842; background-color: #6c9842; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/cluster_summary.png\"><h3 style=\"color:#ffffff;margin: 7px;\">Cluster Summary </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> Provides a variety of general metrics to get a quick idea of what is going on with the system.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Cluster Summary"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
@ -198,7 +209,7 @@
|
||||
"title": "cpu"
|
||||
},
|
||||
{
|
||||
"span": 6,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
@ -238,8 +249,8 @@
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": true,
|
||||
"avg": true
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
@ -358,7 +369,7 @@
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
@ -420,21 +431,6 @@
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "load.load.shortterm"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.fork_rate.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
@ -556,7 +552,7 @@
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "rabbit"
|
||||
"title": "Message Bus"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
@ -684,256 +680,6 @@
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "New row",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "markdown",
|
||||
"content": "this is a basic sample of notes which can be added",
|
||||
"style": {},
|
||||
"title": "Info"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 3,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {
|
||||
"groupBy": {
|
||||
"groupCount": "Group count must be an integer"
|
||||
}
|
||||
},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s",
|
||||
"tagKey": "role"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.fork_rate.value",
|
||||
"currentTagKey": "",
|
||||
"currentTagValue": "",
|
||||
"tags": {
|
||||
"host": [
|
||||
"host1"
|
||||
]
|
||||
},
|
||||
"currentGroupByType": "time"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {
|
||||
"tags": null
|
||||
},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.fork_rate.value",
|
||||
"currentTagKey": "host",
|
||||
"currentTagValue": "host2"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {
|
||||
"tags": null
|
||||
},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.fork_rate.value",
|
||||
"currentTagKey": "host",
|
||||
"currentTagValue": "host3"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {
|
||||
"tags": null
|
||||
},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.fork_rate.value",
|
||||
"currentTagKey": "host",
|
||||
"currentTagValue": "host4"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"rightYAxisLabel": "time",
|
||||
"title": "Fork Rate"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 4,
|
||||
"linewidth": 3,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": true,
|
||||
"stack": true,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "individual",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "sum",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.quantum-dhcp-agent.ps_count.processes",
|
||||
"currentTagKey": "host",
|
||||
"currentTagValue": "host3"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "sum",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.quantum-l3-agent.ps_count.processes"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Quantum Processes"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Networking",
|
||||
"height": "250px",
|
||||
@ -942,7 +688,7 @@
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
@ -1006,7 +752,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "interface.if_packets.eth1.rx",
|
||||
"metric": "interface.eth0.if_packets.rx",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
},
|
||||
{
|
||||
@ -1022,7 +768,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "interface.if_packets.eth1.tx",
|
||||
"metric": "interface.eth0.if_packets.tx",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
}
|
||||
],
|
||||
@ -1034,7 +780,7 @@
|
||||
"title": "Packet Rate"
|
||||
},
|
||||
{
|
||||
"span": 6,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
@ -1067,7 +813,7 @@
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
@ -1088,9 +834,7 @@
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {
|
||||
"tags": null
|
||||
},
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
@ -1100,7 +844,7 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "disk.vda.disk_ops.read",
|
||||
"metric": "disk.sda.disk_ops.read",
|
||||
"currentHorizontalAggregatorName": "avg",
|
||||
"currentTagKey": "cluster",
|
||||
"tags": {},
|
||||
@ -1119,12 +863,22 @@
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "disk.vda.disk_ops.write"
|
||||
"metric": "disk.sda.disk_ops.write"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Disk Ops"
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<br/><br/> The Interfaces and disk metrics names driving this chart may need to be adjusted according to the <b>naming and configuration</b> of devices on your system.<br/> <br/> ",
|
||||
"style": {},
|
||||
"title": "Note"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
|
546
v2/dash/app/dashboards/Network
Normal file
546
v2/dash/app/dashboards/Network
Normal file
@ -0,0 +1,546 @@
|
||||
{
|
||||
"title": "Networking",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Summary",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #6fb3e0; background-color: #6fb3e0; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/network.png\"><h3 style=\"color:#ffffff;margin: 7px;\">OpenStack Networking</h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\">Find various information about Neutron processes running on OpenStack cluster in this dashboard. <br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Nuetron Dashboard"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)",
|
||||
"thresholdLine": false
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": true,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_count.processes"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_count.threads"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_pagefaults.majflt"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_pagefaults.minflt"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Neutron Proc Info",
|
||||
"leftYAxisLabel": "Counts and Faults"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": true,
|
||||
"avg": true
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_data.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"processes.ps_state.sleeping.value ( ) ": "#705DA0"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Process Data Size",
|
||||
"leftYAxisLabel": ""
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Summary",
|
||||
"height": "300px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": true,
|
||||
"avg": true
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_disk_octets.read"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_disk_octets.write"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"processes.ps_state.sleeping.value ( ) ": "#705DA0"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Disk I/O",
|
||||
"leftYAxisLabel": ""
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": true,
|
||||
"avg": true
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_stacksize.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"processes.ps_state.sleeping.value ( ) ": "#705DA0"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Process Stack Size",
|
||||
"leftYAxisLabel": ""
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"bytes",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": true,
|
||||
"avg": true
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.neutron-server.ps_vm.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"processes.ps_state.sleeping.value ( ) ": "#705DA0"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Process VM Size",
|
||||
"leftYAxisLabel": ""
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "light",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
},
|
||||
{
|
||||
"type": "annotations",
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false,
|
||||
"tags": [
|
||||
"processes",
|
||||
"openstack"
|
||||
],
|
||||
"timezone": "browser"
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Summary",
|
||||
"height": "250px",
|
||||
"height": "450px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> Cluster System Process Status </h3>\n\nFind various information about process states running on your OpenStack cluster in this dashboard. This information includes:<br/>\n<ul>\n<li> Cluster Process Summary </li>\n<ul>\n<li>Note that process idle time is on a seperate graph so that other states are easily visible </li>\n</ul>\n<li> OpenStack Key Process Metrics </li>\n<ul> <li> Process Count </li> <li>Process Memory Util </li> <li>...</li> </ul>\n<li> Supporting Process Metrics </li>\n<li> Process Fork Rate </li>\n<li>Other Process Information</li>\n</ul>",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #6fb3e0; background-color: #6fb3e0; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 10px; margin-top: 5px\" src=\"/assets/img/moni/processes.png\"><h3 style=\"color:#ffffff;margin: 7px;\">Cluster Processes</h3><p style=\"margin-left: 7px; margin-bottom: 10px; margin-top: 5px\">Find various information about process states running on your OpenStack cluster in this dashboard. This information includes:<ul><li> Cluster Process Summary </li><ul>\n<li>Note that process idle time is on a seperate graph so that other states are easily visible </li></ul><li> OpenStack Key Process Metrics </li><ul> <li> Process Count </li> <li>Process Memory Util </li> <li>...</li> </ul><li> Supporting Process Metrics </li><li> Process Fork Rate </li><li>Other Process Information</li>\n</ul><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Process Dashboard"
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"height": "450px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> Security </h3>\n<br/>\nThis dashboard presents information to help assess the authentication system in OpenStack. It will be enhanced to include other security metrics as they are collected.",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #7b68af; background-color: #7b68af; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/security.png\"><h3 style=\"color:#ffffff;margin: 7px;\">Security </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard presents information to help assess the authentication system in OpenStack. It will be enhanced to include other security metrics as they are collected.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Security"
|
||||
},
|
||||
@ -129,6 +129,263 @@
|
||||
"aliasYAxis": {},
|
||||
"title": "Keystone Process"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.httpd.ps_cputime.user"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.httpd.ps_cputime.syst"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Web Processes"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "450px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"bytes",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 5,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.keystone.ps_data.value"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.httpd.ps_data.value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Process Data"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 0,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"downsampling": "avg",
|
||||
"errors": {},
|
||||
"groupBy": {
|
||||
"timeInterval": "1s"
|
||||
},
|
||||
"horAggregator": {
|
||||
"samplingRate": "1s",
|
||||
"unit": "millisecond",
|
||||
"factor": "1",
|
||||
"percentile": "0.75"
|
||||
},
|
||||
"metric": "processes.keystone.ps_stacksize.value",
|
||||
"currentHorizontalAggregatorName": "rate"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Keystone Stack Size"
|
||||
},
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
@ -23,7 +23,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<h3> OpenStack Store </h3>\n<br/>\nThis dashboard provides information on the storage nodes and their OpenStack related processes.",
|
||||
"content": "<div style=\"box-shadow: 8px 8px 5px #888888;border-color: #a0a0a0; background-color: #a0a0a0; color:#ffffff; border: 5px; border-radius: 8px; margin: 5px;\"><img width=\"100\" height=\"100\" style=\"margin-left: 4px; margin-bottom: 40px; margin-top: 5px\" src=\"/assets/img/moni/database.png\"><h3 style=\"color:#ffffff;margin: 7px;\">OpenStack Block Store </h3><p style=\"margin-left: 7px; margin-bottom: 40px; margin-top: 5px\"> This dashboard provides information on the Cinder storage nodes and their OpenStack related processes.<br/><br/><br/><br/></p></div>",
|
||||
"style": {},
|
||||
"title": "Store"
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
|
12
v2/dboards/.gitignore
vendored
Normal file
12
v2/dboards/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
.aws-config.json
|
||||
dist
|
||||
|
||||
# locally required config files
|
||||
web.config
|
||||
config.js
|
||||
|
||||
# Editor junk
|
||||
*.sublime-workspace
|
||||
*.swp
|
||||
.idea
|
13
v2/dboards/.jscs.json
Normal file
13
v2/dboards/.jscs.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"requireSpacesInFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"validateIndentation": 2
|
||||
}
|
35
v2/dboards/.jshintrc
Normal file
35
v2/dboards/.jshintrc
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"browser": true,
|
||||
|
||||
"bitwise":false,
|
||||
"curly": true,
|
||||
"eqnull": true,
|
||||
"globalstrict": true,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"forin": false,
|
||||
"immed": true,
|
||||
"supernew": true,
|
||||
"expr": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"trailing": true,
|
||||
"laxbreak": true,
|
||||
"laxcomma": true,
|
||||
"sub": true,
|
||||
"unused": true,
|
||||
"maxdepth": 5,
|
||||
"maxlen": 140,
|
||||
|
||||
"globals": {
|
||||
"define": true,
|
||||
"require": true,
|
||||
"Chromath": false,
|
||||
"setImmediate": true
|
||||
}
|
||||
}
|
5
v2/dboards/.travis.yml
Normal file
5
v2/dboards/.travis.yml
Normal file
@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_script:
|
||||
- npm install -g grunt-cli
|
217
v2/dboards/CHANGELOG.md
Normal file
217
v2/dboards/CHANGELOG.md
Normal file
@ -0,0 +1,217 @@
|
||||
vNext
|
||||
|
||||
**Changes**
|
||||
- Use unix epoch for Graphite from/to for absolute time ranges (Closes #536)
|
||||
|
||||
# 1.6.1 (2014-06-24)
|
||||
|
||||
**New features or improvements**
|
||||
- Ability to set y min/max for right y-axis (RR #519, Closes #360) - thx @acedrew
|
||||
|
||||
**Fixes**
|
||||
|
||||
- Fixes regex InfluxDB queries intoduced in 1.6.0 (PR #500)
|
||||
- Bug in when using % sign in legends (aliases), fixed by removing url decoding of metric names (Fixes #506)
|
||||
- Series names and column name typeahead cache fix (Fixes #522)
|
||||
- Fixed influxdb issue with raw query that caused wrong value column detection (Fixes #504)
|
||||
- Default property that marks which datasource is default in config.js is now optional (Fixes #526)
|
||||
- Auto-refresh caused 2 refreshes (and hence mulitple queries) each time (at least in firefox) (Fixes #342)
|
||||
|
||||
# 1.6.0 (2014-06-16)
|
||||
|
||||
#### New features or improvements
|
||||
- New Y-axis formater for metric values that represent seconds (Issue #427) - thx @jippi
|
||||
- Allow special characters in serie names (influxdb datasource), PR #390 - thx @majst01
|
||||
- Refactoring of filterSrv (Issue #428), thx @Tetha
|
||||
- New config for playlist feature. Set playlist_timespan to set default playlist interval (Issue #445) - thx @rmca
|
||||
- New graphite function definition added isNonNull (PR #461), - thx @tmonk42
|
||||
- New InfluxDB function difference add to function dropdown (PR #455)
|
||||
- Added parameter to keepLastValue graphite function definition (default 100), Closes #459
|
||||
- improved asset (css/js) build pipeline, added revision to css and js. Will remove issues related
|
||||
to the browser cache when upgrading grafana and improve load performance (Fixes #418)
|
||||
- Partial support for url encoded metrics when using Graphite datasource (PR #327) - thx @axe-felix
|
||||
- Improvement to InfluxDB query editor and function/value column selection (Issue #473)
|
||||
- Initial support for filtering (templated queries) for InfluxDB (PR #375) - thx @mavimo
|
||||
- Row editing and adding new panel is now a lot quicker and easier with the new row menu (Issue #475)
|
||||
- New datasource! Initial support for OpenTSDB (PR #211) - thx @mpage
|
||||
- Improvement and polish to the OpenTSDB query editor (Issue #492)
|
||||
- Influxdb group by support (Issue #441) thx @piis3
|
||||
|
||||
|
||||
#### Changes
|
||||
- Graphite panel is now renamed graph (Existing dashboards will still work)
|
||||
- Add panel icon and Row edit button is replaced by the Row edit menu (Issue #475)
|
||||
- New graphs now have a default empty query
|
||||
- Add Row button now creates a row with default height of 250px (no longer opens dashboard settings modal)
|
||||
- Clean up of config.sample.js, graphiteUrl removed (still works, but depricated, removed in future)
|
||||
Use datasources config instead. panel_names removed from config.js. Use plugins.panels to add custom panels
|
||||
|
||||
#### Fixes
|
||||
- Graphite query lexer change, can now handle regex parameters for aliasSub function (Fixes #126)
|
||||
- Filter option loading when having muliple nested filters now works better.
|
||||
Options are now reloaded correctly and there are no multiple renders/refresh inbetween (#447),
|
||||
After an option is changed and a nested template param is also reloaded, if the current value
|
||||
exists after the options are reloaded the current selected value is kept (Closes #447, Closes #412)
|
||||
- Legend Current value did not display when value was zero, Fixes #460
|
||||
- Fix to series toggling bug that caused annotations to be hidden when toggling (hiding) series. Fixes #328
|
||||
- Fix for graphite function selection menu that some times draws outside screen. It now displays upward (Fixes #293)
|
||||
- Fix for exclusive series toggling (hold down CTRL, SHIFT or META key) and left click a series for exclusive toggling
|
||||
CTRL does not work on MAC OSX but SHIFT or META should (depending on browser) (Closes #350, Fixes #472)
|
||||
|
||||
# 1.5.4 (2014-05-13)
|
||||
### New features and improvements
|
||||
- InfluxDB enhancement: support for multiple hosts (with retries) and raw queries (Issue #318, thx @toddboom)
|
||||
- Added rounding for graphites from and to time range filters
|
||||
for very short absolute ranges (Issue #320)
|
||||
- Increased resolution for graphite datapoints (maxDataPoints), now equal to panel pixel width. (Closes #5)
|
||||
- Improvement to influxdb query editor, can now add where clause and alias (Issue #331, thanks @mavimo)
|
||||
- New config setting for graphite datasource to control if json render request is POST or GET (Issue #345)
|
||||
- Unsaved changes warning feature (Issue #324)
|
||||
- Improvement to series toggling, CTRL+MouseClick on series name will now hide all others (Issue #350)
|
||||
|
||||
### Changes
|
||||
- Graph default setting for Y-Min changed from zero to auto scalling (will not effect existing dashboards). (Issue #386) - thx @kamaradclimber
|
||||
|
||||
### Fixes
|
||||
- Fixes to filters and "All" option. It now never uses "*" as value, but all options in a {node1, node2, node3} expression (Issue #228, #359)
|
||||
- Fix for InfluxDB query generation with columns containing dots or dashes (Issue #369, #348) - Thanks to @jbripley
|
||||
|
||||
|
||||
# 1.5.3 (2014-04-17)
|
||||
- Add support for async scripted dashboards (Issue #274)
|
||||
- Text panel now accepts html (for links to other dashboards, etc) (Issue #236)
|
||||
- Fix for Text panel, now changes take effect directly (Issue #251)
|
||||
- Fix when adding functions without params that did not cause graph to update (Issue #267)
|
||||
- Graphite errors are now much easier to see and troubleshoot with the new inspector (Issue #265)
|
||||
- Use influxdb aliases to distinguish between multiple columns (Issue #283)
|
||||
- Correction to ms axis formater, now formats days correctly. (Issue #189)
|
||||
- Css fix for Firefox and using top menu dropdowns in panel fullscren / edit mode (Issue #106)
|
||||
- Browser page title is now Grafana - {{dashboard title}} (Issue #294)
|
||||
- Disable auto refresh zooming in (every time you change to an absolute time range), refresh will be restored when you change time range back to relative (Issue #282)
|
||||
- More graphite functions
|
||||
|
||||
# 1.5.2 (2014-03-24)
|
||||
### New Features and improvements
|
||||
- Support for second optional params for functions like aliasByNode (Issue #167). Read the wiki on the [Function Editor](https://github.com/torkelo/grafana/wiki/Graphite-Function-Editor) for more info.
|
||||
- More functions added to InfluxDB query editor (Issue #218)
|
||||
- Filters can now be used inside other filters (templated segments) (Issue #128)
|
||||
- More graphite functions added
|
||||
|
||||
### Fixes
|
||||
- Float arguments now work for functions like scale (Issue #223)
|
||||
- Fix for graphite function editor, the graph & target was not updated after adding a function and leaving default params as is #191
|
||||
|
||||
The zip files now contains a sub folder with project name and version prefix. (Issue #209)
|
||||
|
||||
# 1.5.1 (2014-03-10)
|
||||
### Fixes
|
||||
- maxDataPoints must be an integer #184 (thanks @frejsoya for fixing this)
|
||||
|
||||
For people who are find Grafana slow for large time spans or high resolution metrics. This is most likely due to graphite returning a large number of datapoints. The maxDataPoints parameter solves this issue. For maxDataPoints to work you need to run the latest graphite-web (some builds of 0.9.12 does not include this feature).
|
||||
|
||||
Read this for more info:
|
||||
[Performance for large time spans](https://github.com/torkelo/grafana/wiki/Performance-for-large-time-spans)
|
||||
|
||||
# 1.5.0 (2014-03-09)
|
||||
### New Features and improvements
|
||||
- New function editor [video demo](http://youtu.be/I90WHRwE1ZM) (Issue #178)
|
||||
- Links to function documentation from function editor (Issue #3)
|
||||
- Reorder functions (Issue #130)
|
||||
- [Initial support for InfluxDB](https://github.com/torkelo/grafana/wiki/InfluxDB) as metric datasource (#103), need feedback!
|
||||
- [Dashboard playlist](https://github.com/torkelo/grafana/wiki/Dashboard-playlist) (Issue #36)
|
||||
- When adding aliasByNode smartly set node number (Issue #175)
|
||||
- Support graphite identifiers with embedded colons (Issue #173)
|
||||
- Typeahead & autocomplete when adding new function (Issue #164)
|
||||
- More graphite function definitions
|
||||
- Make "ms" axis format include hour, day, weeks, month and year (Issue #149)
|
||||
- Microsecond axis format (Issue #146)
|
||||
- Specify template paramaters in URL (Issue #123)
|
||||
|
||||
### Fixes
|
||||
- Basic Auth fix (Issue #152)
|
||||
- Fix to annotations with graphite source & null values (Issue #138)
|
||||
|
||||
# 1.4.0 (2014-02-21)
|
||||
### New Features
|
||||
- #44 Annotations! Required a lot of work to get right. Read wiki article for more info. Supported annotations data sources are graphite metrics and graphite events. Support for more will be added in the future!
|
||||
- #35 Support for multiple graphite servers! (Read wiki article for more)
|
||||
- #116 Back to dashboard link in top menu to easily exist full screen / edit mode.
|
||||
- #114, #97 Legend values now use the same y axes formatter
|
||||
- #77 Improvements and polish to the light theme
|
||||
|
||||
### Changes
|
||||
- #98 Stack is no longer by default turned on in graph display settings.
|
||||
- Hide controls (Ctrl+h) now hides the sub menu row (where filtering, and annotations are). So if you had filtering enabled and hide controls enabled you will not see the filtering sub menu.
|
||||
|
||||
### Fixes:
|
||||
- #94 Fix for bug that caused dashboard settings to sometimes not contain timepicker tab.
|
||||
- #110 Graph with many many metrics caused legend to push down graph editor below screen. You can now scroll in edit mode & full screen mode for graphs with lots of series & legends.
|
||||
- #104 Improvement to graphite target editor, select wildcard now gives you a "select metric" link for the next node.
|
||||
- #105 Added zero as a possible node value in groupByAlias function
|
||||
|
||||
# 1.3.0 (2014-02-13)
|
||||
### New features or improvements
|
||||
- #86 Dashboard tags and search (see wiki article for details)
|
||||
- #54 Enhancement to filter / template. "Include All" improvement
|
||||
- #82 Dashboard search result sorted in alphabetical order
|
||||
|
||||
### Fixes
|
||||
- #91 Custom date selector is one day behind
|
||||
- #89 Filter / template does not work after switching dashboard
|
||||
- #88 Closed / Minimized row css bug
|
||||
- #85 Added all parameters to summarize function
|
||||
- #83 Stack as percent should now work a lot better!
|
||||
|
||||
# 1.2.0 (2014-02-10)
|
||||
### New features
|
||||
- #70 Grid Thresholds (warning and error regions or lines in graph)
|
||||
- #72 Added an example of a scripted dashboard and a short wiki article documenting scripted dashboards.
|
||||
|
||||
### Fixes
|
||||
- #81 Grid min/max values are ignored bug
|
||||
- #80 "stacked as percent" graphs should always use "max" value of 100 bug
|
||||
- #73 Left Y format change did not work
|
||||
- #42 Fixes to grid min/max auto scaling
|
||||
- #69 Fixes to lexer/parser for metrics segments like "10-20".
|
||||
- #67 Allow decimal input for scale function
|
||||
- #68 Bug when trying to open dashboard while in edit mode
|
||||
|
||||
# 1.1.0 (2014-02-06)
|
||||
### New features:
|
||||
|
||||
- #22 Support for native graphite png renderer, does not support click and select zoom yet
|
||||
- #60 Support for legend values (cactiStyle, min, max, current, total, avg). The options for these are found in the new "Axes & Grid" tab for now.
|
||||
- #62 There is now a "New" button in the search/open dashboard view to quickly open a clean empty dashboard.
|
||||
- #55 Basic auth is now supported for elastic search as well
|
||||
- some new function definitions added (will focus more on this for next release).
|
||||
|
||||
### Fixes
|
||||
- #45 zero values from graphite was handled as null.
|
||||
- #63 Kibana / Grafana on same host would use same localStorage keys, now fixed
|
||||
- #46 Impossible to edit graph without a name fixed.
|
||||
- #24 fix for dashboard search when elastic search is configured to disable _all field.
|
||||
- #38 Improvement to lexer / parser to support pure numeric literals in metric segments
|
||||
|
||||
Thanks to everyone who contributed fixes and provided feedback :+1:
|
||||
|
||||
# 1.0.4 (2014-01-24)
|
||||
- Fixes #28 - Relative time range caused 500 graphite error in some cases (thx rsommer for the fix)
|
||||
|
||||
# 1.0.3 (2014-01-23)
|
||||
- #9 Add Y-axis format for milliseconds
|
||||
- #16 Add support for Basic Auth (use http://username:password@yourgraphitedomain.com)
|
||||
- #13 Relative time ranges now uses relative time ranges when issuing graphite query
|
||||
|
||||
# 1.0.2 (2014-01-21)
|
||||
- Fixes #12, should now work ok without ElasticSearch
|
||||
|
||||
# 1.0.1 (2014-01-21)
|
||||
- Resize fix
|
||||
- Improvements to drag & drop
|
||||
- Added a few graphite function definitions
|
||||
- Fixed duplicate panel bug
|
||||
- Updated default dashboard with welcome message and randomWalk graph
|
||||
|
||||
# 1.0.0 (2014-01-19)
|
||||
|
||||
First public release
|
14
v2/dboards/CONTRIBUTING.md
Normal file
14
v2/dboards/CONTRIBUTING.md
Normal file
@ -0,0 +1,14 @@
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue.
|
||||
And if you have time clone this repo and submit a pull request and help me make Grafana the
|
||||
kickass metrics & devops dashboard we all dream about!
|
||||
|
||||
Prerequisites:
|
||||
- Nodejs (for jshint & grunt & development server)
|
||||
|
||||
Clone repository:
|
||||
|
||||
npm install
|
||||
grunt server (starts development web server in src folder)
|
||||
grunt (runs jshint and less -> css compilation)
|
||||
|
||||
Please remember to run grunt before doing pull request to verify that your code passes all the jshint validations.
|
38
v2/dboards/Gruntfile.js
Normal file
38
v2/dboards/Gruntfile.js
Normal file
@ -0,0 +1,38 @@
|
||||
/* jshint node:true */
|
||||
'use strict';
|
||||
module.exports = function (grunt) {
|
||||
|
||||
var config = {
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
baseDir: '.',
|
||||
srcDir: 'src',
|
||||
destDir: 'dist',
|
||||
tempDir: 'tmp',
|
||||
docsDir: 'docs/'
|
||||
};
|
||||
|
||||
// load plugins
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// load task definitions
|
||||
grunt.loadTasks('tasks');
|
||||
|
||||
// Utility function to load plugin settings into config
|
||||
function loadConfig(config,path) {
|
||||
require('glob').sync('*', {cwd: path}).forEach(function(option) {
|
||||
var key = option.replace(/\.js$/,'');
|
||||
// If key already exists, extend it. It is your responsibility to avoid naming collisions
|
||||
config[key] = config[key] || {};
|
||||
grunt.util._.extend(config[key], require(path + option)(config,grunt));
|
||||
});
|
||||
// technically not required
|
||||
return config;
|
||||
}
|
||||
|
||||
// Merge that object with what with whatever we have here
|
||||
loadConfig(config,'./tasks/options/');
|
||||
|
||||
// pass the config to grunt
|
||||
grunt.initConfig(config);
|
||||
|
||||
};
|
14
v2/dboards/LICENSE.md
Normal file
14
v2/dboards/LICENSE.md
Normal file
@ -0,0 +1,14 @@
|
||||
Copyright 2012-2013 Elasticsearch BV, Torkel Ödegaard
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may
|
||||
obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
|
16
v2/dboards/NOTICE.md
Normal file
16
v2/dboards/NOTICE.md
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
This software is based on Kibana:
|
||||
========================================
|
||||
Copyright 2012-2013 Elasticsearch BV
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may
|
||||
obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
131
v2/dboards/README.md
Normal file
131
v2/dboards/README.md
Normal file
@ -0,0 +1,131 @@
|
||||
[Grafana](http://grafana.org) [![Build Status](https://api.travis-ci.org/grafana/grafana.png)](https://travis-ci.org/grafana/grafana)
|
||||
=================
|
||||
A beautiful, easy to use and feature rich Graphite dashboard replacement and graph editor. Visit [grafana.org](http://grafana.org) for screenshots, videos and feature descriptions.
|
||||
|
||||
![](http://grafana.org/assets/img/edit_dashboards.png)
|
||||
|
||||
## Features
|
||||
### Graphite Target Editor
|
||||
- Graphite target expression parser
|
||||
- Quickly add / edit / remove function ([video demo](http://youtu.be/I90WHRwE1ZM))
|
||||
- Function parameters can be easily changed
|
||||
- Quickly navigate graphite metric structure
|
||||
- Templating
|
||||
- Integrated links to function documentation
|
||||
- Rearrange function order
|
||||
- Native Graphite PNG render support
|
||||
|
||||
### Graphing
|
||||
- Fast rendering, even over large timespans.
|
||||
- Click and drag to zoom.
|
||||
- Multiple Y-axis.
|
||||
- Bars, Lines, Points.
|
||||
- Smart Y-axis formating
|
||||
- Series toggles & color selector
|
||||
- Axis labels
|
||||
- Grid thresholds, axis labels
|
||||
- [Annotations] (https://github.com/grafana/grafana/wiki/Annotations)
|
||||
|
||||
### Dashboards
|
||||
- Create and edit dashboards
|
||||
- Drag and drop graphs to rearrange
|
||||
- Set column spans and row heights
|
||||
- Save & [search dashboards](https://github.com/grafana/grafana/wiki/Search-features)
|
||||
- Import & export dashboard (json file)
|
||||
- Import dashboard from Graphite
|
||||
- Templating
|
||||
- [Scripted dashboards](https://github.com/grafana/grafana/wiki/Scripted-dashboards) (generate from js script and url parameters)
|
||||
- Flexible [time range controls](https://github.com/grafana/grafana/wiki/Time-range-controls)
|
||||
- [Dashboard playlists](https://github.com/grafana/grafana/wiki/Dashboard-playlist)
|
||||
|
||||
### InfluxDB
|
||||
- [Use InfluxDB](https://github.com/grafana/grafana/wiki/InfluxDB) as metric datasource
|
||||
|
||||
# Requirements
|
||||
Grafana is very easy to install. It is a client side web app with no backend. Any webserver will do. Optionally you will need ElasticSearch if you want to be able to save and load dashboards quickly instead of json files or local storage.
|
||||
|
||||
# Installation
|
||||
- Download and extract the [latest release](https://github.com/grafana/grafana/releases).
|
||||
- Rename `config.sample.js` to `config.js`, then change `graphiteUrl` and `elasticsearch` to point to the correct urls. The urls entered here must be reachable by your browser.
|
||||
- Point your browser to the installation.
|
||||
|
||||
To run from master:
|
||||
- Clone this repository
|
||||
- Start a web server in src folder
|
||||
- Or create a optimized & minified build:
|
||||
- npm install (requires nodejs)
|
||||
- grunt build (requires grunt-cli)
|
||||
|
||||
If you use ansible for provisioning and deployment [ansible-grafana](https://github.com/bobrik/ansible-grafana) should get you started.
|
||||
|
||||
When you have Grafana up an running, read the [Getting started](https://github.com/grafana/grafana/wiki/Getting-started) guide for
|
||||
an introduction on how to use Grafana and/or watch [this video](https://www.youtube.com/watch?v=OUvJamHeMpw) for a guide in creating a new dashboard and for creating
|
||||
templated dashboards.
|
||||
|
||||
# Graphite server config
|
||||
If you haven't used an alternative dashboard for graphite before you need to enable cross-domain origin request. For Apache 2.x:
|
||||
```
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
Header set Access-Control-Allow-Methods "GET, OPTIONS"
|
||||
Header set Access-Control-Allow-Headers "origin, authorization, accept"
|
||||
```
|
||||
Note that using "\*" leaves your graphite instance quite open so you might want to consider using "http://my.graphite-dom.ain" in place of "\*"
|
||||
|
||||
Here is the same thing, in nginx format:
|
||||
```
|
||||
add_header "Access-Control-Allow-Origin" "*";
|
||||
add_header "Access-Control-Allow-Credentials" "true";
|
||||
add_header "Access-Control-Allow-Methods" "GET, OPTIONS";
|
||||
add_header "Access-Control-Allow-Headers" "Authorization, origin, accept";
|
||||
```
|
||||
If your Graphite web is protected by basic authentication, you have to enable the HTTP verb OPTIONS, origin
|
||||
(no wildcards are allowed in this case) and add Access-Control-Allow-Credentials. This looks like the following for Apache:
|
||||
```
|
||||
Header set Access-Control-Allow-Origin "http://mygrafana.com:5656"
|
||||
Header set Access-Control-Allow-Credentials true
|
||||
|
||||
<Location />
|
||||
AuthName "graphs restricted"
|
||||
AuthType Basic
|
||||
AuthUserFile /etc/apache2/htpasswd
|
||||
<LimitExcept OPTIONS>
|
||||
require valid-user
|
||||
</LimitExcept>
|
||||
</Location>
|
||||
```
|
||||
And in nginx:
|
||||
```
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /path/to/my/htpasswd/file;
|
||||
if ($http_origin ~* (https?://[^/]*\.somedomain\.com(:[0-9]+)?)) { #Test if request is from allowed domain, you can use multiple if
|
||||
set $cors "true"; #statements to allow multiple domains, simply setting $cors to true in each one.
|
||||
}
|
||||
if ($cors = 'true') {
|
||||
add_header Access-Control-Allow-Origin $http_origin; #this mirrors back whatever domain the request came from as authorized, as
|
||||
add_header "Access-Control-Allow-Credentials" "true"; #as long as it matches one of your if statements
|
||||
add_header "Access-Control-Allow-Methods" "GET, OPTIONS";
|
||||
add_header "Access-Control-Allow-Headers" "Authorization, origin, accept";
|
||||
}
|
||||
```
|
||||
# Roadmap
|
||||
- Improve and refine the target parser and editing
|
||||
- Improve graphite import feature
|
||||
- Refine and simplify common tasks
|
||||
- More panel types (not just graphs)
|
||||
- Use elasticsearch to search for metrics
|
||||
- Improve template support
|
||||
- Annotate graph by querying ElasticSearch for events (or other event sources)
|
||||
|
||||
# Contribute
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana the kickass metrics & devops dashboard we all dream about!
|
||||
|
||||
Clone repository:
|
||||
- npm install
|
||||
- grunt server (starts development web server in src folder)
|
||||
- grunt (runs jshint and less -> css compilation)
|
||||
|
||||
# Notice
|
||||
This software is based on the great log dashboard [kibana](https://github.com/elasticsearch/kibana).
|
||||
|
||||
# License
|
||||
Grafana is distributed under Apache 2.0 License.
|
18
v2/dboards/grafana.sublime-project
Normal file
18
v2/dboards/grafana.sublime-project
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"follow_symlinks": true,
|
||||
"path": ".",
|
||||
"folder_exclude_patterns": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
],
|
||||
"settings":
|
||||
{
|
||||
"tab_size": 2,
|
||||
"translate_tabs_to_spaces": true,
|
||||
"trim_trailing_white_space_on_save": true
|
||||
}
|
||||
}
|
4
v2/dboards/latest.json
Normal file
4
v2/dboards/latest.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "1.6.0",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.6.0.tar.gz"
|
||||
}
|
63
v2/dboards/package.json
Normal file
63
v2/dboards/package.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"author": {
|
||||
"name": "Torkel Ödegaard",
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.6.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rjs-build-analysis": "0.0.3",
|
||||
"grunt": "~0.4.0",
|
||||
"grunt-ngmin": "0.0.3",
|
||||
"grunt-contrib-less": "~0.7.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-git-describe": "~2.3.2",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-cssmin": "~0.6.1",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-string-replace": "~0.2.4",
|
||||
"grunt-contrib-htmlmin": "~0.1.3",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
"grunt-angular-templates": "^0.5.5",
|
||||
"grunt-contrib-compress": "~0.5.2",
|
||||
"grunt-contrib-uglify": "~0.2.4",
|
||||
"load-grunt-tasks": "~0.2.0",
|
||||
"glob": "~3.2.7",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"mocha": "~1.16.1",
|
||||
"expect.js": "~0.2.0",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"karma-firefox-launcher": "~0.1.3",
|
||||
"karma-chrome-launcher": "~0.1.4",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-jasmine": "~0.2.2",
|
||||
"requirejs": "~2.1.9",
|
||||
"karma-requirejs": "~0.2.1",
|
||||
"karma-coffee-preprocessor": "~0.1.2",
|
||||
"karma-phantomjs-launcher": "~0.1.1",
|
||||
"karma": "~0.12.16",
|
||||
"grunt-karma": "~0.8.3",
|
||||
"karma-mocha": "~0.1.4",
|
||||
"karma-expect": "~1.1.0",
|
||||
"grunt-cli": "~0.1.13",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"grunt-contrib-concat": "^0.4.0",
|
||||
"grunt-usemin": "^2.1.1",
|
||||
"grunt-filerev": "^0.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "0.10.x",
|
||||
"npm": "1.2.x"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"license": "Apache License",
|
||||
"dependencies": {
|
||||
"grunt-jscs-checker": "^0.4.4"
|
||||
}
|
||||
}
|
61
v2/dboards/sample/apache_ldap.conf
Normal file
61
v2/dboards/sample/apache_ldap.conf
Normal file
@ -0,0 +1,61 @@
|
||||
# Courtesy of https://github.com/sgzijl
|
||||
# config.js includes elasticsearch: "https://"+window.location.hostname+":443",
|
||||
|
||||
<VirtualHost 1.2.3.4:80>
|
||||
ServerName your.domain.tld
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost 1.2.3.4:443>
|
||||
ServerName your.domain.tld
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /path/to/public.crt
|
||||
SSLCertificateKeyFile /path/to/private.key
|
||||
|
||||
DocumentRoot /path/to/kibana3
|
||||
<Directory /path/to/kibana3>
|
||||
Allow from all
|
||||
Options -Multiviews
|
||||
</Directory>
|
||||
|
||||
LogLevel debug
|
||||
ErrorLog /path/to/logs/error_log
|
||||
CustomLog /path/to/logs/access_log combined
|
||||
|
||||
# Set global proxy timeouts
|
||||
<Proxy http://127.0.0.1:9200>
|
||||
ProxySet connectiontimeout=5 timeout=90
|
||||
</Proxy>
|
||||
|
||||
# Proxy for _aliases and .*/_search
|
||||
<LocationMatch "^/(_nodes|_aliases|_search|.*/_search|_mapping|.*/_mapping)$">
|
||||
ProxyPassMatch http://127.0.0.1:9200/$1
|
||||
ProxyPassReverse http://127.0.0.1:9200/$1
|
||||
</LocationMatch>
|
||||
|
||||
# Proxy for kibana-int/{dashboard,temp} stuff (if you don't want auth on /, then you will want these to be protected)
|
||||
<LocationMatch "^/(kibana-int/dashboard/|kibana-int/temp)(.*)$">
|
||||
ProxyPassMatch http://127.0.0.1:9200/$1$2
|
||||
ProxyPassReverse http://127.0.0.1:9200/$1$2
|
||||
</LocationMatch>
|
||||
|
||||
# Optional disable auth for a src IP (eg: your monitoring host or subnet)
|
||||
<Location />
|
||||
Allow from 5.6.7.8
|
||||
Deny from all
|
||||
Satisfy any
|
||||
|
||||
AuthLDAPBindDN "CN=_ldapbinduser,OU=Users,DC=example,DC=com"
|
||||
AuthLDAPBindPassword "ldapbindpass"
|
||||
AuthLDAPURL "ldaps://ldap01.example.com ldap02.example.com/OU=Users,DC=example,DC=com?sAMAccountName?sub?(objectClass=*)"
|
||||
AuthType Basic
|
||||
AuthBasicProvider ldap
|
||||
AuthName "Please authenticate for Example dot com"
|
||||
AuthLDAPGroupAttributeIsDN on
|
||||
require valid-user
|
||||
</Location>
|
||||
|
||||
</VirtualHost>
|
24
v2/dboards/sample/lighttpd_basic.conf
Normal file
24
v2/dboards/sample/lighttpd_basic.conf
Normal file
@ -0,0 +1,24 @@
|
||||
$HTTP["host"] =~ "kibana" {
|
||||
|
||||
server.document-root = "/var/ww/kibana/src"
|
||||
auth.backend = "plain"
|
||||
auth.backend.plain.userfile = "/etc/lighttpd/kibanapassword"
|
||||
|
||||
index-file.names = ( "index.html", "index.htm" )
|
||||
|
||||
auth.require = ( "/" => (
|
||||
"method" => "basic",
|
||||
"realm" => "Password Protected",
|
||||
"require" => "valid-user"
|
||||
)
|
||||
)
|
||||
|
||||
$HTTP["url"] =~ "^/kibana-int/(dashboard/|temp).*$" {
|
||||
proxy.balance = "hash"
|
||||
proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "9200" ) ) )
|
||||
}
|
||||
$HTTP["url"] =~ "^.*/_(mapping|search|nodes|aliases)$" {
|
||||
proxy.balance = "hash"
|
||||
proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "9200" ) ) )
|
||||
}
|
||||
}
|
60
v2/dboards/sample/nginx.conf
Normal file
60
v2/dboards/sample/nginx.conf
Normal file
@ -0,0 +1,60 @@
|
||||
#
|
||||
# Nginx proxy for Elasticsearch + Kibana
|
||||
#
|
||||
# In this setup, we are password protecting the saving of dashboards. You may
|
||||
# wish to extend the password protection to all paths.
|
||||
#
|
||||
# Even though these paths are being called as the result of an ajax request, the
|
||||
# browser will prompt for a username/password on the first request
|
||||
#
|
||||
# If you use this, you'll want to point config.js at http://FQDN:80/ instead of
|
||||
# http://FQDN:9200
|
||||
#
|
||||
server {
|
||||
listen *:80 ;
|
||||
|
||||
server_name kibana.myhost.org;
|
||||
access_log /var/log/nginx/kibana.myhost.org.access.log;
|
||||
|
||||
location / {
|
||||
root /usr/share/kibana3;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location ~ ^/_aliases$ {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
proxy_read_timeout 90;
|
||||
}
|
||||
location ~ ^/_nodes$ {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
proxy_read_timeout 90;
|
||||
}
|
||||
location ~ ^/.*/_search$ {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
proxy_read_timeout 90;
|
||||
}
|
||||
location ~ ^/.*/_mapping$ {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
proxy_read_timeout 90;
|
||||
}
|
||||
|
||||
# Password protected end points
|
||||
location ~ ^/kibana-int/dashboard/.*$ {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
proxy_read_timeout 90;
|
||||
limit_except GET {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /etc/nginx/conf.d/kibana.myhost.org.htpasswd;
|
||||
}
|
||||
}
|
||||
location ~ ^/kibana-int/temp.*$ {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
proxy_read_timeout 90;
|
||||
limit_except GET {
|
||||
proxy_pass http://127.0.0.1:9200;
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /etc/nginx/conf.d/kibana.myhost.org.htpasswd;
|
||||
}
|
||||
}
|
||||
}
|
244
v2/dboards/sample/server.js
Normal file
244
v2/dboards/sample/server.js
Normal file
@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var util = require('util'),
|
||||
http = require('http'),
|
||||
fs = require('fs'),
|
||||
url = require('url'),
|
||||
events = require('events');
|
||||
|
||||
var DEFAULT_PORT = 8000;
|
||||
|
||||
function main(argv) {
|
||||
new HttpServer({
|
||||
'GET': createServlet(StaticServlet),
|
||||
'HEAD': createServlet(StaticServlet)
|
||||
}).start(Number(argv[2]) || DEFAULT_PORT);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return value.toString().
|
||||
replace('<', '<').
|
||||
replace('>', '>').
|
||||
replace('"', '"');
|
||||
}
|
||||
|
||||
function createServlet(Class) {
|
||||
var servlet = new Class();
|
||||
return servlet.handleRequest.bind(servlet);
|
||||
}
|
||||
|
||||
/**
|
||||
* An Http server implementation that uses a map of methods to decide
|
||||
* action routing.
|
||||
*
|
||||
* @param {Object} Map of method => Handler function
|
||||
*/
|
||||
function HttpServer(handlers) {
|
||||
this.handlers = handlers;
|
||||
this.server = http.createServer(this.handleRequest_.bind(this));
|
||||
}
|
||||
|
||||
HttpServer.prototype.start = function(port) {
|
||||
this.port = port;
|
||||
this.server.listen(port);
|
||||
util.puts('Http Server running at http://localhost:' + port + '/');
|
||||
};
|
||||
|
||||
HttpServer.prototype.parseUrl_ = function(urlString) {
|
||||
var parsed = url.parse(urlString);
|
||||
parsed.pathname = url.resolve('/', parsed.pathname);
|
||||
return url.parse(url.format(parsed), true);
|
||||
};
|
||||
|
||||
HttpServer.prototype.handleRequest_ = function(req, res) {
|
||||
var logEntry = req.method + ' ' + req.url;
|
||||
if (req.headers['user-agent']) {
|
||||
logEntry += ' ' + req.headers['user-agent'];
|
||||
}
|
||||
util.puts(logEntry);
|
||||
req.url = this.parseUrl_(req.url);
|
||||
var handler = this.handlers[req.method];
|
||||
if (!handler) {
|
||||
res.writeHead(501);
|
||||
res.end();
|
||||
} else {
|
||||
handler.call(this, req, res);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles static content.
|
||||
*/
|
||||
function StaticServlet() {}
|
||||
|
||||
StaticServlet.MimeMap = {
|
||||
'txt': 'text/plain',
|
||||
'html': 'text/html',
|
||||
'css': 'text/css',
|
||||
'xml': 'application/xml',
|
||||
'json': 'application/json',
|
||||
'js': 'application/javascript',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'gif': 'image/gif',
|
||||
'png': 'image/png',
|
||||
'svg': 'image/svg+xml'
|
||||
};
|
||||
|
||||
StaticServlet.prototype.handleRequest = function(req, res) {
|
||||
var self = this;
|
||||
var path = ('../src/' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
|
||||
return String.fromCharCode(parseInt(hex, 16));
|
||||
});
|
||||
var parts = path.split('/');
|
||||
if (parts[parts.length-1].charAt(0) === '.')
|
||||
return self.sendForbidden_(req, res, path);
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendMissing_(req, res, path);
|
||||
if (stat.isDirectory())
|
||||
return self.sendDirectory_(req, res, path);
|
||||
return self.sendFile_(req, res, path);
|
||||
});
|
||||
}
|
||||
|
||||
StaticServlet.prototype.sendError_ = function(req, res, error) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>Internal Server Error</title>\n');
|
||||
res.write('<h1>Internal Server Error</h1>');
|
||||
res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
|
||||
util.puts('500 Internal Server Error');
|
||||
util.puts(util.inspect(error));
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(404, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>404 Not Found</title>\n');
|
||||
res.write('<h1>Not Found</h1>');
|
||||
res.write(
|
||||
'<p>The requested URL ' +
|
||||
escapeHtml(path) +
|
||||
' was not found on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
util.puts('404 Not Found: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(403, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>403 Forbidden</title>\n');
|
||||
res.write('<h1>Forbidden</h1>');
|
||||
res.write(
|
||||
'<p>You do not have permission to access ' +
|
||||
escapeHtml(path) + ' on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
util.puts('403 Forbidden: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
|
||||
res.writeHead(301, {
|
||||
'Content-Type': 'text/html',
|
||||
'Location': redirectUrl
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>301 Moved Permanently</title>\n');
|
||||
res.write('<h1>Moved Permanently</h1>');
|
||||
res.write(
|
||||
'<p>The document has moved <a href="' +
|
||||
redirectUrl +
|
||||
'">here</a>.</p>'
|
||||
);
|
||||
res.end();
|
||||
util.puts('301 Moved Permanently: ' + redirectUrl);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendFile_ = function(req, res, path) {
|
||||
var self = this;
|
||||
var file = fs.createReadStream(path);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': StaticServlet.
|
||||
MimeMap[path.split('.').pop()] || 'text/plain'
|
||||
});
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
} else {
|
||||
file.on('data', res.write.bind(res));
|
||||
file.on('close', function() {
|
||||
res.end();
|
||||
});
|
||||
file.on('error', function(error) {
|
||||
self.sendError_(req, res, error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
|
||||
var self = this;
|
||||
if (path.match(/[^\/]$/)) {
|
||||
req.url.pathname += '/';
|
||||
var redirectUrl = url.format(url.parse(url.format(req.url)));
|
||||
return self.sendRedirect_(req, res, redirectUrl);
|
||||
}
|
||||
fs.readdir(path, function(err, files) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, error);
|
||||
|
||||
if (!files.length)
|
||||
return self.writeDirectoryIndex_(req, res, path, []);
|
||||
|
||||
var remaining = files.length;
|
||||
files.forEach(function(fileName, index) {
|
||||
fs.stat(path + '/' + fileName, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, err);
|
||||
if (stat.isDirectory()) {
|
||||
files[index] = fileName + '/';
|
||||
}
|
||||
if (!(--remaining))
|
||||
return self.writeDirectoryIndex_(req, res, path, files);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>' + escapeHtml(path) + '</title>\n');
|
||||
res.write('<style>\n');
|
||||
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
|
||||
res.write('</style>\n');
|
||||
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
|
||||
res.write('<ol>');
|
||||
files.forEach(function(fileName) {
|
||||
if (fileName.charAt(0) !== '.') {
|
||||
res.write('<li><a href="' +
|
||||
escapeHtml(fileName) + '">' +
|
||||
escapeHtml(fileName) + '</a></li>');
|
||||
}
|
||||
});
|
||||
res.write('</ol>');
|
||||
res.end();
|
||||
};
|
||||
|
||||
// Must be last,
|
||||
main(process.argv);
|
149
v2/dboards/src/app/app.js
Normal file
149
v2/dboards/src/app/app.js
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* main app level module
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'require',
|
||||
'elasticjs',
|
||||
'bootstrap',
|
||||
'angular-sanitize',
|
||||
'angular-strap',
|
||||
'angular-dragdrop',
|
||||
'extend-jquery',
|
||||
'bindonce'
|
||||
],
|
||||
function (angular, $, _, appLevelRequire) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var app = angular.module('kibana', []),
|
||||
// we will keep a reference to each module defined before boot, so that we can
|
||||
// go back and allow it to define new features later. Once we boot, this will be false
|
||||
pre_boot_modules = [],
|
||||
// these are the functions that we need to call to register different
|
||||
// features if we define them after boot time
|
||||
register_fns = {};
|
||||
|
||||
// This stores the grafana version number
|
||||
app.constant('grafanaVersion',"@grafanaVersion@");
|
||||
|
||||
// Use this for cache busting partials
|
||||
app.constant('cacheBust',"cache-bust="+Date.now());
|
||||
|
||||
/**
|
||||
* Tells the application to watch the module, once bootstraping has completed
|
||||
* the modules controller, service, etc. functions will be overwritten to register directly
|
||||
* with this application.
|
||||
* @param {[type]} module [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
app.useModule = function (module) {
|
||||
if (pre_boot_modules) {
|
||||
pre_boot_modules.push(module);
|
||||
} else {
|
||||
_.extend(module, register_fns);
|
||||
}
|
||||
return module;
|
||||
};
|
||||
|
||||
app.safeApply = function ($scope, fn) {
|
||||
switch($scope.$$phase) {
|
||||
case '$apply':
|
||||
// $digest hasn't started, we should be good
|
||||
$scope.$eval(fn);
|
||||
break;
|
||||
case '$digest':
|
||||
// waiting to $apply the changes
|
||||
setTimeout(function () { app.safeApply($scope, fn); }, 10);
|
||||
break;
|
||||
default:
|
||||
// clear to begin an $apply $$phase
|
||||
$scope.$apply(fn);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
|
||||
|
||||
$routeProvider
|
||||
.when('/dashboard', {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
})
|
||||
.when('/dashboard/:kbnType/:kbnId', {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
})
|
||||
.when('/dashboard/:kbnType/:kbnId/:params', {
|
||||
templateUrl: 'app/partials/dashboard.html'
|
||||
})
|
||||
.otherwise({
|
||||
redirectTo: 'dashboard'
|
||||
});
|
||||
|
||||
// this is how the internet told me to dynamically add modules :/
|
||||
register_fns.controller = $controllerProvider.register;
|
||||
register_fns.directive = $compileProvider.directive;
|
||||
register_fns.factory = $provide.factory;
|
||||
register_fns.service = $provide.service;
|
||||
register_fns.filter = $filterProvider.register;
|
||||
});
|
||||
|
||||
var apps_deps = [
|
||||
'elasticjs.service',
|
||||
'$strap.directives',
|
||||
'ngSanitize',
|
||||
'ngDragDrop',
|
||||
'kibana',
|
||||
'pasvaz.bindonce'
|
||||
];
|
||||
|
||||
var module_types = ['controllers', 'directives', 'factories', 'services', 'services.dashboard', 'filters'];
|
||||
|
||||
_.each(module_types, function (type) {
|
||||
var module_name = 'kibana.'+type;
|
||||
// create the module
|
||||
app.useModule(angular.module(module_name, []));
|
||||
// push it into the apps dependencies
|
||||
apps_deps.push(module_name);
|
||||
});
|
||||
|
||||
// load the core components
|
||||
require([
|
||||
'controllers/all',
|
||||
'directives/all',
|
||||
'filters/all',
|
||||
'components/partials',
|
||||
], function () {
|
||||
|
||||
// bootstrap the app
|
||||
angular
|
||||
.element(document)
|
||||
.ready(function() {
|
||||
$('body').attr('ng-controller', 'DashCtrl');
|
||||
angular.bootstrap(document, apps_deps)
|
||||
.invoke(['$rootScope', function ($rootScope) {
|
||||
_.each(pre_boot_modules, function (module) {
|
||||
_.extend(module, register_fns);
|
||||
});
|
||||
pre_boot_modules = false;
|
||||
|
||||
$rootScope.requireContext = appLevelRequire;
|
||||
$rootScope.require = function (deps, fn) {
|
||||
var $scope = this;
|
||||
$scope.requireContext(deps, function () {
|
||||
var deps = _.toArray(arguments);
|
||||
// Check that this is a valid scope.
|
||||
if($scope.$id) {
|
||||
$scope.$apply(function () {
|
||||
fn.apply($scope, deps);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
});
|
47
v2/dboards/src/app/components/extend-jquery.js
Normal file
47
v2/dboards/src/app/components/extend-jquery.js
Normal file
@ -0,0 +1,47 @@
|
||||
define(['jquery'],
|
||||
function ($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* jQuery extensions
|
||||
*/
|
||||
var $win = $(window);
|
||||
|
||||
$.fn.place_tt = (function () {
|
||||
var defaults = {
|
||||
offset: 5,
|
||||
css: {
|
||||
position : 'absolute',
|
||||
top : -1000,
|
||||
left : 0,
|
||||
color : "#c8c8c8",
|
||||
padding : '10px',
|
||||
'font-size': '11pt',
|
||||
'font-weight' : 200,
|
||||
'background-color': '#1f1f1f',
|
||||
'border-radius': '5px',
|
||||
'z-index': 9999
|
||||
}
|
||||
};
|
||||
|
||||
return function (x, y, opts) {
|
||||
opts = $.extend(true, {}, defaults, opts);
|
||||
return this.each(function () {
|
||||
var $tooltip = $(this), width, height;
|
||||
|
||||
$tooltip.css(opts.css);
|
||||
if (!$.contains(document.body, $tooltip[0])) {
|
||||
$tooltip.appendTo(document.body);
|
||||
}
|
||||
|
||||
width = $tooltip.outerWidth(true);
|
||||
height = $tooltip.outerHeight(true);
|
||||
|
||||
$tooltip.css('left', x + opts.offset + width > $win.width() ? x - opts.offset - width : x + opts.offset);
|
||||
$tooltip.css('top', y + opts.offset + height > $win.height() ? y - opts.offset - height : y + opts.offset);
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
return $;
|
||||
});
|
563
v2/dboards/src/app/components/kbn.js
Normal file
563
v2/dboards/src/app/components/kbn.js
Normal file
@ -0,0 +1,563 @@
|
||||
define(['jquery','underscore','moment'],
|
||||
function($, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var kbn = {};
|
||||
|
||||
/**
|
||||
* Calculate a graph interval
|
||||
*
|
||||
* from:: Date object containing the start time
|
||||
* to:: Date object containing the finish time
|
||||
* size:: Calculate to approximately this many bars
|
||||
* user_interval:: User specified histogram interval
|
||||
*
|
||||
*/
|
||||
kbn.calculate_interval = function(from,to,size,user_interval) {
|
||||
if(_.isObject(from)) {
|
||||
from = from.valueOf();
|
||||
}
|
||||
if(_.isObject(to)) {
|
||||
to = to.valueOf();
|
||||
}
|
||||
return user_interval === 0 ? kbn.round_interval((to - from)/size) : user_interval;
|
||||
};
|
||||
|
||||
kbn.round_interval = function(interval) {
|
||||
switch (true) {
|
||||
// 0.5s
|
||||
case (interval <= 500):
|
||||
return 100; // 0.1s
|
||||
// 5s
|
||||
case (interval <= 5000):
|
||||
return 1000; // 1s
|
||||
// 7.5s
|
||||
case (interval <= 7500):
|
||||
return 5000; // 5s
|
||||
// 15s
|
||||
case (interval <= 15000):
|
||||
return 10000; // 10s
|
||||
// 45s
|
||||
case (interval <= 45000):
|
||||
return 30000; // 30s
|
||||
// 3m
|
||||
case (interval <= 180000):
|
||||
return 60000; // 1m
|
||||
// 9m
|
||||
case (interval <= 450000):
|
||||
return 300000; // 5m
|
||||
// 20m
|
||||
case (interval <= 1200000):
|
||||
return 600000; // 10m
|
||||
// 45m
|
||||
case (interval <= 2700000):
|
||||
return 1800000; // 30m
|
||||
// 2h
|
||||
case (interval <= 7200000):
|
||||
return 3600000; // 1h
|
||||
// 6h
|
||||
case (interval <= 21600000):
|
||||
return 10800000; // 3h
|
||||
// 24h
|
||||
case (interval <= 86400000):
|
||||
return 43200000; // 12h
|
||||
// 48h
|
||||
case (interval <= 172800000):
|
||||
return 86400000; // 24h
|
||||
// 1w
|
||||
case (interval <= 604800000):
|
||||
return 86400000; // 24h
|
||||
// 3w
|
||||
case (interval <= 1814400000):
|
||||
return 604800000; // 1w
|
||||
// 2y
|
||||
case (interval < 3628800000):
|
||||
return 2592000000; // 30d
|
||||
default:
|
||||
return 31536000000; // 1y
|
||||
}
|
||||
};
|
||||
|
||||
kbn.secondsToHms = function(seconds) {
|
||||
var numyears = Math.floor(seconds / 31536000);
|
||||
if(numyears){
|
||||
return numyears + 'y';
|
||||
}
|
||||
var numdays = Math.floor((seconds % 31536000) / 86400);
|
||||
if(numdays){
|
||||
return numdays + 'd';
|
||||
}
|
||||
var numhours = Math.floor(((seconds % 31536000) % 86400) / 3600);
|
||||
if(numhours){
|
||||
return numhours + 'h';
|
||||
}
|
||||
var numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60);
|
||||
if(numminutes){
|
||||
return numminutes + 'm';
|
||||
}
|
||||
var numseconds = (((seconds % 31536000) % 86400) % 3600) % 60;
|
||||
if(numseconds){
|
||||
return numseconds + 's';
|
||||
}
|
||||
return 'less then a second'; //'just now' //or other string you like;
|
||||
};
|
||||
|
||||
kbn.to_percent = function(number,outof) {
|
||||
return Math.floor((number/outof)*10000)/100 + "%";
|
||||
};
|
||||
|
||||
kbn.addslashes = function(str) {
|
||||
str = str.replace(/\\/g, '\\\\');
|
||||
str = str.replace(/\'/g, '\\\'');
|
||||
str = str.replace(/\"/g, '\\"');
|
||||
str = str.replace(/\0/g, '\\0');
|
||||
return str;
|
||||
};
|
||||
|
||||
kbn.interval_regex = /(\d+(?:\.\d+)?)([Mwdhmsy])/;
|
||||
|
||||
// histogram & trends
|
||||
kbn.intervals_in_seconds = {
|
||||
y: 31536000,
|
||||
M: 2592000,
|
||||
w: 604800,
|
||||
d: 86400,
|
||||
h: 3600,
|
||||
m: 60,
|
||||
s: 1
|
||||
};
|
||||
|
||||
kbn.describe_interval = function (string) {
|
||||
var matches = string.match(kbn.interval_regex);
|
||||
if (!matches || !_.has(kbn.intervals_in_seconds, matches[2])) {
|
||||
throw new Error('Invalid interval string, expexcting a number followed by one of "Mwdhmsy"');
|
||||
} else {
|
||||
return {
|
||||
sec: kbn.intervals_in_seconds[matches[2]],
|
||||
type: matches[2],
|
||||
count: parseInt(matches[1], 10)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
kbn.interval_to_ms = function(string) {
|
||||
var info = kbn.describe_interval(string);
|
||||
return info.sec * 1000 * info.count;
|
||||
};
|
||||
|
||||
kbn.interval_to_seconds = function (string) {
|
||||
var info = kbn.describe_interval(string);
|
||||
return info.sec * info.count;
|
||||
};
|
||||
|
||||
// This should go away, moment.js can do this
|
||||
kbn.time_ago = function(string) {
|
||||
return new Date(new Date().getTime() - (kbn.interval_to_ms(string)));
|
||||
};
|
||||
|
||||
/* This is a simplified version of elasticsearch's date parser */
|
||||
kbn.parseDate = function(text) {
|
||||
if(_.isDate(text)) {
|
||||
return text;
|
||||
}
|
||||
var time,
|
||||
mathString = "",
|
||||
index,
|
||||
parseString;
|
||||
if (text.substring(0,3) === "now") {
|
||||
time = new Date();
|
||||
mathString = text.substring("now".length);
|
||||
} else {
|
||||
index = text.indexOf("||");
|
||||
parseString;
|
||||
if (index === -1) {
|
||||
parseString = text;
|
||||
mathString = ""; // nothing else
|
||||
} else {
|
||||
parseString = text.substring(0, index);
|
||||
mathString = text.substring(index + 2);
|
||||
}
|
||||
// We're going to just require ISO8601 timestamps, k?
|
||||
time = new Date(parseString);
|
||||
}
|
||||
|
||||
if (!mathString.length) {
|
||||
return time;
|
||||
}
|
||||
|
||||
//return [time,parseString,mathString];
|
||||
return kbn.parseDateMath(mathString, time);
|
||||
};
|
||||
|
||||
kbn.parseDateMath = function(mathString, time, roundUp) {
|
||||
var dateTime = moment(time);
|
||||
for (var i = 0; i < mathString.length;) {
|
||||
var c = mathString.charAt(i++),
|
||||
type,
|
||||
num,
|
||||
unit;
|
||||
if (c === '/') {
|
||||
type = 0;
|
||||
} else if (c === '+') {
|
||||
type = 1;
|
||||
} else if (c === '-') {
|
||||
type = 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNaN(mathString.charAt(i))) {
|
||||
num = 1;
|
||||
} else {
|
||||
var numFrom = i;
|
||||
while (!isNaN(mathString.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
num = parseInt(mathString.substring(numFrom, i),10);
|
||||
}
|
||||
if (type === 0) {
|
||||
// rounding is only allowed on whole numbers
|
||||
if (num !== 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
unit = mathString.charAt(i++);
|
||||
switch (unit) {
|
||||
case 'y':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('years',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('years',num);
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('months',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('months',num);
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('weeks',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('weeks',num);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('days',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('days',num);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
case 'H':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('hours',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('hours',num);
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('minutes',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('minutes',num);
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('seconds',num);
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('seconds',num);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return dateTime.toDate();
|
||||
};
|
||||
|
||||
kbn.query_color_dot = function (color, diameter) {
|
||||
return '<div class="icon-circle" style="' + [
|
||||
'display:inline-block',
|
||||
'color:' + color,
|
||||
'font-size:' + diameter + 'px',
|
||||
].join(';') + '"></div>';
|
||||
};
|
||||
|
||||
kbn.byteFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " B";
|
||||
break;
|
||||
case 1:
|
||||
ext = " KiB";
|
||||
break;
|
||||
case 2:
|
||||
ext = " MiB";
|
||||
break;
|
||||
case 3:
|
||||
ext = " GiB";
|
||||
break;
|
||||
case 4:
|
||||
ext = " TiB";
|
||||
break;
|
||||
case 5:
|
||||
ext = " PiB";
|
||||
break;
|
||||
case 6:
|
||||
ext = " EiB";
|
||||
break;
|
||||
case 7:
|
||||
ext = " ZiB";
|
||||
break;
|
||||
case 8:
|
||||
ext = " YiB";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.bitFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " b";
|
||||
break;
|
||||
case 1:
|
||||
ext = " Kib";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mib";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Gib";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tib";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Pib";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Eib";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Zib";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Yib";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.shortFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1000) {
|
||||
steps++;
|
||||
size /= 1000;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = "";
|
||||
break;
|
||||
case 1:
|
||||
ext = " K";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mil";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Bil";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tri";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Quadr";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Quint";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Sext";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Sept";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.getFormatFunction = function(formatName, decimals) {
|
||||
switch(formatName) {
|
||||
case 'short':
|
||||
return function(val) {
|
||||
return kbn.shortFormat(val, decimals);
|
||||
};
|
||||
case 'bytes':
|
||||
return function(val) {
|
||||
return kbn.byteFormat(val, decimals);
|
||||
};
|
||||
case 'bits':
|
||||
return function(val) {
|
||||
return kbn.bitFormat(val, decimals);
|
||||
};
|
||||
case 's':
|
||||
return function(val) {
|
||||
return kbn.sFormat(val, decimals);
|
||||
};
|
||||
case 'ms':
|
||||
return function(val) {
|
||||
return kbn.msFormat(val, decimals);
|
||||
};
|
||||
case 'µs':
|
||||
return function(val) {
|
||||
return kbn.microsFormat(val, decimals);
|
||||
};
|
||||
case 'ns':
|
||||
return function(val) {
|
||||
return kbn.nanosFormat(val, decimals);
|
||||
};
|
||||
default:
|
||||
return function(val) {
|
||||
return val % 1 === 0 ? val : val.toFixed(decimals);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
kbn.msFormat = function(size, decimals) {
|
||||
if (size < 1000) {
|
||||
return size.toFixed(0) + " ms";
|
||||
}
|
||||
// Less than 1 min
|
||||
else if (size < 60000) {
|
||||
return (size / 1000).toFixed(decimals) + " s";
|
||||
}
|
||||
// Less than 1 hour, devide in minutes
|
||||
else if (size < 3600000) {
|
||||
return (size / 60000).toFixed(decimals) + " min";
|
||||
}
|
||||
// Less than one day, devide in hours
|
||||
else if (size < 86400000) {
|
||||
return (size / 3600000).toFixed(decimals) + " hour";
|
||||
}
|
||||
// Less than one year, devide in days
|
||||
else if (size < 31536000000) {
|
||||
return (size / 86400000).toFixed(decimals) + " day";
|
||||
}
|
||||
|
||||
return (size / 31536000000).toFixed(decimals) + " year";
|
||||
};
|
||||
|
||||
kbn.sFormat = function(size, decimals) {
|
||||
// Less than 10 min, use seconds
|
||||
if (size < 600) {
|
||||
return size.toFixed(decimals) + " s";
|
||||
}
|
||||
// Less than 1 hour, devide in minutes
|
||||
else if (size < 3600) {
|
||||
return (size / 60).toFixed(decimals) + " min";
|
||||
}
|
||||
// Less than one day, devide in hours
|
||||
else if (size < 86400) {
|
||||
return (size / 3600).toFixed(decimals) + " hour";
|
||||
}
|
||||
// Less than one week, devide in days
|
||||
else if (size < 604800) {
|
||||
return (size / 86400).toFixed(decimals) + " day";
|
||||
}
|
||||
// Less than one year, devide in week
|
||||
else if (size < 31536000) {
|
||||
return (size / 604800).toFixed(decimals) + " week";
|
||||
}
|
||||
|
||||
return (size / 3.15569e7).toFixed(decimals) + " year";
|
||||
};
|
||||
|
||||
kbn.microsFormat = function(size, decimals) {
|
||||
if (size < 1000) {
|
||||
return size.toFixed(0) + " µs";
|
||||
}
|
||||
else if (size < 1000000) {
|
||||
return (size / 1000).toFixed(decimals) + " ms";
|
||||
}
|
||||
else {
|
||||
return (size / 1000000).toFixed(decimals) + " s";
|
||||
}
|
||||
};
|
||||
|
||||
kbn.nanosFormat = function(size, decimals) {
|
||||
if (size < 1000) {
|
||||
return size.toFixed(0) + " ns";
|
||||
}
|
||||
else if (size < 1000000) {
|
||||
return (size / 1000).toFixed(decimals) + " µs";
|
||||
}
|
||||
else if (size < 1000000000) {
|
||||
return (size / 1000000).toFixed(decimals) + " ms";
|
||||
}
|
||||
else if (size < 60000000000){
|
||||
return (size / 1000000000).toFixed(decimals) + " s";
|
||||
}
|
||||
else {
|
||||
return (size / 60000000000).toFixed(decimals) + " m";
|
||||
}
|
||||
};
|
||||
|
||||
return kbn;
|
||||
});
|
2
v2/dboards/src/app/components/partials.js
Normal file
2
v2/dboards/src/app/components/partials.js
Normal file
@ -0,0 +1,2 @@
|
||||
define([
|
||||
], function () {});
|
111
v2/dboards/src/app/components/require.config.js
Normal file
111
v2/dboards/src/app/components/require.config.js
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Bootstrap require with the needed config, then load the app.js module.
|
||||
*/
|
||||
require.config({
|
||||
baseUrl: 'app',
|
||||
|
||||
paths: {
|
||||
config: ['../config', '../config.sample'],
|
||||
settings: 'components/settings',
|
||||
kbn: 'components/kbn',
|
||||
|
||||
css: '../vendor/require/css',
|
||||
text: '../vendor/require/text',
|
||||
moment: '../vendor/moment',
|
||||
filesaver: '../vendor/filesaver',
|
||||
angular: '../vendor/angular/angular',
|
||||
'angular-dragdrop': '../vendor/angular/angular-dragdrop',
|
||||
'angular-strap': '../vendor/angular/angular-strap',
|
||||
'angular-sanitize': '../vendor/angular/angular-sanitize',
|
||||
timepicker: '../vendor/angular/timepicker',
|
||||
datepicker: '../vendor/angular/datepicker',
|
||||
bindonce: '../vendor/angular/bindonce',
|
||||
crypto: '../vendor/crypto.min',
|
||||
spectrum: '../vendor/spectrum',
|
||||
|
||||
underscore: 'components/underscore.extended',
|
||||
'underscore-src': '../vendor/underscore',
|
||||
bootstrap: '../vendor/bootstrap/bootstrap',
|
||||
|
||||
jquery: '../vendor/jquery/jquery-1.8.0',
|
||||
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
|
||||
|
||||
'extend-jquery': 'components/extend-jquery',
|
||||
|
||||
'jquery.flot': '../vendor/jquery/jquery.flot',
|
||||
'jquery.flot.pie': '../vendor/jquery/jquery.flot.pie',
|
||||
'jquery.flot.events': '../vendor/jquery/jquery.flot.events',
|
||||
'jquery.flot.selection': '../vendor/jquery/jquery.flot.selection',
|
||||
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.byte': '../vendor/jquery/jquery.flot.byte',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
elasticjs: '../vendor/elasticjs/elastic-angular-client',
|
||||
|
||||
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
|
||||
|
||||
},
|
||||
shim: {
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
|
||||
spectrum: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
|
||||
crypto: {
|
||||
exports: 'Crypto'
|
||||
},
|
||||
|
||||
angular: {
|
||||
deps: ['jquery','config'],
|
||||
exports: 'angular'
|
||||
},
|
||||
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
|
||||
modernizr: {
|
||||
exports: 'Modernizr'
|
||||
},
|
||||
|
||||
jquery: {
|
||||
exports: 'jQuery'
|
||||
},
|
||||
|
||||
// simple dependency declaration
|
||||
//
|
||||
'jquery-ui': ['jquery'],
|
||||
'jquery.flot': ['jquery'],
|
||||
'jquery.flot.byte': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.pie': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.events': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.selection':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stack': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
|
||||
'angular-sanitize': ['angular'],
|
||||
'angular-cookies': ['angular'],
|
||||
'angular-dragdrop': ['jquery','jquery-ui','angular'],
|
||||
'angular-loader': ['angular'],
|
||||
'angular-mocks': ['angular'],
|
||||
'angular-resource': ['angular'],
|
||||
'angular-route': ['angular'],
|
||||
'angular-touch': ['angular'],
|
||||
'bindonce': ['angular'],
|
||||
'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'],
|
||||
|
||||
timepicker: ['jquery', 'bootstrap'],
|
||||
datepicker: ['jquery', 'bootstrap'],
|
||||
|
||||
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
|
||||
|
||||
'bootstrap-tagsinput': ['jquery'],
|
||||
},
|
||||
waitSeconds: 60,
|
||||
});
|
86
v2/dboards/src/app/components/settings.js
Normal file
86
v2/dboards/src/app/components/settings.js
Normal file
@ -0,0 +1,86 @@
|
||||
define([
|
||||
'underscore',
|
||||
'crypto',
|
||||
],
|
||||
function (_, crypto) {
|
||||
"use strict";
|
||||
|
||||
return function Settings (options) {
|
||||
/**
|
||||
* To add a setting, you MUST define a default. Also,
|
||||
* THESE ARE ONLY DEFAULTS.
|
||||
* They are overridden by config.js in the root directory
|
||||
* @type {Object}
|
||||
*/
|
||||
var defaults = {
|
||||
elasticsearch : "http://"+window.location.hostname+":9200",
|
||||
datasources : {
|
||||
default: {
|
||||
url: "http://"+window.location.hostname+":8080",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
panels : ['graph', 'text'],
|
||||
plugins : {},
|
||||
default_route : '/dashboard/file/default.json',
|
||||
grafana_index : 'grafana-dash',
|
||||
elasticsearch_all_disabled : false,
|
||||
timezoneOffset : null,
|
||||
playlist_timespan : "1m",
|
||||
unsaved_changes_warning : true
|
||||
};
|
||||
|
||||
// This initializes a new hash on purpose, to avoid adding parameters to
|
||||
// config.js without providing sane defaults
|
||||
var settings = {};
|
||||
_.each(defaults, function(value, key) {
|
||||
settings[key] = typeof options[key] !== 'undefined' ? options[key] : defaults[key];
|
||||
});
|
||||
|
||||
var parseBasicAuth = function(datasource) {
|
||||
var passwordEnd = datasource.url.indexOf('@');
|
||||
if (passwordEnd > 0) {
|
||||
var userStart = datasource.url.indexOf('//') + 2;
|
||||
var userAndPassword = datasource.url.substring(userStart, passwordEnd);
|
||||
var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
|
||||
datasource.basicAuth = crypto.util.bytesToBase64(bytes);
|
||||
|
||||
var urlHead = datasource.url.substring(0, userStart);
|
||||
datasource.url = urlHead + datasource.url.substring(passwordEnd + 1);
|
||||
}
|
||||
|
||||
return datasource;
|
||||
};
|
||||
|
||||
var parseMultipleHosts = function(datasource) {
|
||||
datasource.urls = _.map(datasource.url.split(","), function (url) { return url.trim(); });
|
||||
return datasource;
|
||||
};
|
||||
|
||||
if (options.graphiteUrl) {
|
||||
settings.datasources = {
|
||||
graphite: {
|
||||
type: 'graphite',
|
||||
url: options.graphiteUrl,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_.each(settings.datasources, function(datasource, key) {
|
||||
datasource.name = key;
|
||||
parseBasicAuth(datasource);
|
||||
if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
|
||||
});
|
||||
|
||||
var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
|
||||
settings.elasticsearchBasicAuth = elasticParsed.basicAuth;
|
||||
settings.elasticsearch = elasticParsed.url;
|
||||
|
||||
if (settings.plugins.panels) {
|
||||
settings.panels = _.union(settings.panels, settings.plugins.panels);
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
});
|
36
v2/dboards/src/app/components/underscore.extended.js
Normal file
36
v2/dboards/src/app/components/underscore.extended.js
Normal file
@ -0,0 +1,36 @@
|
||||
define([
|
||||
'underscore-src'
|
||||
],
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
var _ = window._;
|
||||
|
||||
/*
|
||||
Mixins :)
|
||||
*/
|
||||
_.mixin({
|
||||
move: function (array, fromIndex, toIndex) {
|
||||
array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]);
|
||||
return array;
|
||||
},
|
||||
remove: function (array, index) {
|
||||
array.splice(index, 1);
|
||||
return array;
|
||||
},
|
||||
// If variable is value, then return alt. If variable is anything else, return value;
|
||||
toggle: function (variable, value, alt) {
|
||||
return variable === value ? alt : value;
|
||||
},
|
||||
toggleInOut: function(array,value) {
|
||||
if(_.contains(array,value)) {
|
||||
array = _.without(array,value);
|
||||
} else {
|
||||
array.push(value);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
});
|
||||
|
||||
return _;
|
||||
});
|
16
v2/dboards/src/app/controllers/all.js
Normal file
16
v2/dboards/src/app/controllers/all.js
Normal file
@ -0,0 +1,16 @@
|
||||
define([
|
||||
'./dash',
|
||||
'./dashLoader',
|
||||
'./row',
|
||||
'./submenuCtrl',
|
||||
'./pulldown',
|
||||
'./search',
|
||||
'./metricKeys',
|
||||
'./graphiteTarget',
|
||||
'./graphiteImport',
|
||||
'./influxTargetCtrl',
|
||||
'./playlistCtrl',
|
||||
'./inspectCtrl',
|
||||
'./opentsdbTargetCtrl',
|
||||
'./kairosdbTargetCtrl',
|
||||
], function () {});
|
152
v2/dboards/src/app/controllers/dash.js
Normal file
152
v2/dboards/src/app/controllers/dash.js
Normal file
@ -0,0 +1,152 @@
|
||||
/** @scratch /index/0
|
||||
* = Kibana
|
||||
*
|
||||
* // Why can't I have a preamble here?
|
||||
*
|
||||
* == Introduction
|
||||
*
|
||||
* Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for
|
||||
* ElasticSearch. Kibana is a snap to setup and start using. Written entirely in HTML and Javascript
|
||||
* it requires only a plain webserver, Kibana requires no fancy server side components.
|
||||
* Kibana strives to be easy to get started with, while also being flexible and powerful, just like
|
||||
* Elasticsearch.
|
||||
*
|
||||
* include::configuration/config.js.asciidoc[]
|
||||
*
|
||||
* include::panels.asciidoc[]
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'config',
|
||||
'underscore',
|
||||
'services/all',
|
||||
'services/dashboard/all'
|
||||
],
|
||||
function (angular, $, config, _) {
|
||||
"use strict";
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('DashCtrl', function(
|
||||
$scope, $rootScope, $timeout, ejsResource, dashboard, filterSrv, dashboardKeybindings,
|
||||
alertSrv, panelMove, keyboardManager, grafanaVersion) {
|
||||
|
||||
$scope.requiredElasticSearchVersion = ">=0.90.3";
|
||||
|
||||
$scope.editor = {
|
||||
index: 0
|
||||
};
|
||||
|
||||
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
|
||||
|
||||
// For moving stuff around the dashboard.
|
||||
$scope.panelMoveDrop = panelMove.onDrop;
|
||||
$scope.panelMoveStart = panelMove.onStart;
|
||||
$scope.panelMoveStop = panelMove.onStop;
|
||||
$scope.panelMoveOver = panelMove.onOver;
|
||||
$scope.panelMoveOut = panelMove.onOut;
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.config = config;
|
||||
|
||||
// Make stuff, including underscore.js available to views
|
||||
$scope._ = _;
|
||||
$scope.dashboard = dashboard;
|
||||
$scope.dashAlerts = alertSrv;
|
||||
|
||||
$scope.filter = filterSrv;
|
||||
$scope.filter.init(dashboard.current);
|
||||
|
||||
$rootScope.$on("dashboard-loaded", function(event, dashboard) {
|
||||
$scope.filter.init(dashboard);
|
||||
});
|
||||
|
||||
// Clear existing alerts
|
||||
alertSrv.clearAll();
|
||||
|
||||
$scope.reset_row();
|
||||
|
||||
$scope.ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
|
||||
|
||||
$scope.bindKeyboardShortcuts();
|
||||
};
|
||||
|
||||
$scope.bindKeyboardShortcuts = dashboardKeybindings.shortcuts;
|
||||
|
||||
$scope.isPanel = function(obj) {
|
||||
if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.add_row = function(dash, row) {
|
||||
dash.rows.push(row);
|
||||
};
|
||||
|
||||
$scope.add_row_default = function() {
|
||||
$scope.reset_row();
|
||||
$scope.row.title = 'New row';
|
||||
$scope.add_row(dashboard.current, $scope.row);
|
||||
};
|
||||
|
||||
$scope.reset_row = function() {
|
||||
$scope.row = {
|
||||
title: '',
|
||||
height: '250px',
|
||||
editable: true,
|
||||
};
|
||||
};
|
||||
|
||||
$scope.row_style = function(row) {
|
||||
return { 'min-height': row.collapse ? '5px' : row.height };
|
||||
};
|
||||
|
||||
$scope.panel_path =function(type) {
|
||||
if(type) {
|
||||
return 'app/panels/'+type.replace(".","/");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.edit_path = function(type) {
|
||||
var p = $scope.panel_path(type);
|
||||
if(p) {
|
||||
return p+'/editor.html';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setEditorTabs = function(panelMeta) {
|
||||
$scope.editorTabs = ['General','Panel'];
|
||||
if(!_.isUndefined(panelMeta.editorTabs)) {
|
||||
$scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title'));
|
||||
}
|
||||
return $scope.editorTabs;
|
||||
};
|
||||
|
||||
// This is whoafully incomplete, but will do for now
|
||||
$scope.parse_error = function(data) {
|
||||
var _error = data.match("nested: (.*?);");
|
||||
return _.isNull(_error) ? data : _error[1];
|
||||
};
|
||||
|
||||
$scope.colors = [
|
||||
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
|
||||
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
|
||||
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
|
||||
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
|
||||
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
|
||||
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
|
||||
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
|
||||
];
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
});
|
172
v2/dboards/src/app/controllers/dashLoader.js
Normal file
172
v2/dboards/src/app/controllers/dashLoader.js
Normal file
@ -0,0 +1,172 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'moment'
|
||||
],
|
||||
function (angular, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('dashLoader', function($scope, $rootScope, $http, dashboard, alertSrv, $location, playlistSrv) {
|
||||
$scope.loader = dashboard.current.loader;
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
|
||||
$scope.gist = $scope.gist || {};
|
||||
$scope.elasticsearch = $scope.elasticsearch || {};
|
||||
|
||||
$rootScope.$on('save-dashboard', function() {
|
||||
$scope.elasticsearch_save('dashboard', false);
|
||||
});
|
||||
|
||||
$rootScope.$on('zoom-out', function() {
|
||||
$scope.zoom(2);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.exitFullscreen = function() {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
};
|
||||
|
||||
$scope.showDropdown = function(type) {
|
||||
if(_.isUndefined(dashboard.current.loader)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var _l = dashboard.current.loader;
|
||||
if(type === 'load') {
|
||||
return (_l.load_elasticsearch || _l.load_gist || _l.load_local);
|
||||
}
|
||||
if(type === 'save') {
|
||||
return (_l.save_elasticsearch || _l.save_gist || _l.save_local || _l.save_default);
|
||||
}
|
||||
if(type === 'share') {
|
||||
return (_l.save_temp);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.set_default = function() {
|
||||
if(dashboard.set_default($location.path())) {
|
||||
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
|
||||
} else {
|
||||
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.purge_default = function() {
|
||||
if(dashboard.purge_default()) {
|
||||
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default',
|
||||
'success',5000);
|
||||
} else {
|
||||
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.elasticsearch_save = function(type,ttl) {
|
||||
dashboard.elasticsearch_save(type, dashboard.current.title, ttl)
|
||||
.then(function(result) {
|
||||
if(_.isUndefined(result._id)) {
|
||||
alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000);
|
||||
return;
|
||||
}
|
||||
|
||||
alertSrv.set('Dashboard Saved', 'Dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000);
|
||||
if(type === 'temp') {
|
||||
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
|
||||
}
|
||||
|
||||
$rootScope.$emit('dashboard-saved', dashboard.current);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.elasticsearch_delete = function(id) {
|
||||
if (!confirm('Are you sure you want to delete dashboard?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
dashboard.elasticsearch_delete(id).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result)) {
|
||||
if(result.found) {
|
||||
alertSrv.set('Dashboard Deleted',id+' has been deleted','success',5000);
|
||||
// Find the deleted dashboard in the cached list and remove it
|
||||
var toDelete = _.where($scope.elasticsearch.dashboards,{_id:id})[0];
|
||||
$scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,toDelete);
|
||||
} else {
|
||||
alertSrv.set('Dashboard Not Found','Could not find '+id+' in Elasticsearch','warning',5000);
|
||||
}
|
||||
} else {
|
||||
alertSrv.set('Dashboard Not Deleted','An error occurred deleting the dashboard','error',5000);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.save_gist = function() {
|
||||
dashboard.save_gist($scope.gist.title).then(function(link) {
|
||||
if (!_.isUndefined(link)) {
|
||||
$scope.gist.last = link;
|
||||
alertSrv.set('Gist saved','You will be able to access your exported dashboard file at '+
|
||||
'<a href="'+link+'">'+link+'</a> in a moment','success');
|
||||
} else {
|
||||
alertSrv.set('Save failed','Gist could not be saved','error',5000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.gist_dblist = function(id) {
|
||||
dashboard.gist_list(id).then(function(files) {
|
||||
if (files && files.length > 0) {
|
||||
$scope.gist.files = files;
|
||||
} else {
|
||||
alertSrv.set('Gist Failed','Could not retrieve dashboard list from gist','error',5000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// function $scope.zoom
|
||||
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
|
||||
$scope.zoom = function(factor) {
|
||||
var _range = this.filter.timeRange();
|
||||
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
|
||||
var _center = _range.to.valueOf() - _timespan/2;
|
||||
|
||||
var _to = (_center + (_timespan*factor)/2);
|
||||
var _from = (_center - (_timespan*factor)/2);
|
||||
|
||||
// If we're not already looking into the future, don't.
|
||||
if(_to > Date.now() && _range.to < Date.now()) {
|
||||
var _offset = _to - Date.now();
|
||||
_from = _from - _offset;
|
||||
_to = Date.now();
|
||||
}
|
||||
|
||||
this.filter.setTime({
|
||||
from:moment.utc(_from).toDate(),
|
||||
to:moment.utc(_to).toDate(),
|
||||
});
|
||||
};
|
||||
|
||||
$scope.openSaveDropdown = function() {
|
||||
$scope.isFavorite = playlistSrv.isCurrentFavorite();
|
||||
};
|
||||
|
||||
$scope.markAsFavorite = function() {
|
||||
playlistSrv.markAsFavorite();
|
||||
$scope.isFavorite = true;
|
||||
};
|
||||
|
||||
$scope.removeAsFavorite = function() {
|
||||
playlistSrv.removeAsFavorite(dashboard.current);
|
||||
$scope.isFavorite = false;
|
||||
};
|
||||
|
||||
$scope.stopPlaylist = function() {
|
||||
playlistSrv.stop(1);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
104
v2/dboards/src/app/controllers/graphiteImport.js
Normal file
104
v2/dboards/src/app/controllers/graphiteImport.js
Normal file
@ -0,0 +1,104 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, dashboard) {
|
||||
|
||||
$scope.init = function() {
|
||||
console.log('hej!');
|
||||
$scope.datasources = datasourceSrv.listOptions();
|
||||
$scope.setDatasource(null);
|
||||
};
|
||||
|
||||
$scope.setDatasource = function(datasource) {
|
||||
$scope.datasource = datasourceSrv.get(datasource);
|
||||
|
||||
if (!$scope.datasource) {
|
||||
$scope.error = "Cannot find datasource " + datasource;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.listAll = function(query) {
|
||||
delete $scope.error;
|
||||
|
||||
$scope.datasource.listDashboards(query)
|
||||
.then(function(results) {
|
||||
$scope.dashboards = results;
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.error = err.message || 'Error while fetching list of dashboards';
|
||||
});
|
||||
};
|
||||
|
||||
$scope.import = function(dashName) {
|
||||
delete $scope.error;
|
||||
|
||||
$scope.datasource.loadDashboard(dashName)
|
||||
.then(function(results) {
|
||||
if (!results.data || !results.data.state) {
|
||||
throw { message: 'no dashboard state received from graphite' };
|
||||
}
|
||||
|
||||
graphiteToGrafanaTranslator(results.data.state, $scope.datasource.name);
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.error = err.message || 'Failed to import dashboard';
|
||||
});
|
||||
};
|
||||
|
||||
function graphiteToGrafanaTranslator(state, datasource) {
|
||||
var graphsPerRow = 2;
|
||||
var rowHeight = 300;
|
||||
var rowTemplate;
|
||||
var currentRow;
|
||||
var panel;
|
||||
|
||||
rowTemplate = {
|
||||
title: '',
|
||||
panels: [],
|
||||
height: rowHeight
|
||||
};
|
||||
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
|
||||
var newDashboard = angular.copy(dashboard.current);
|
||||
newDashboard.rows = [];
|
||||
newDashboard.title = state.name;
|
||||
newDashboard.rows.push(currentRow);
|
||||
|
||||
_.each(state.graphs, function(graph) {
|
||||
if (currentRow.panels.length === graphsPerRow) {
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
newDashboard.rows.push(currentRow);
|
||||
}
|
||||
|
||||
panel = {
|
||||
type: 'graphite',
|
||||
span: 12 / graphsPerRow,
|
||||
title: graph[1].title,
|
||||
targets: [],
|
||||
datasource: datasource
|
||||
};
|
||||
|
||||
_.each(graph[1].target, function(target) {
|
||||
panel.targets.push({
|
||||
target: target
|
||||
});
|
||||
});
|
||||
|
||||
currentRow.panels.push(panel);
|
||||
});
|
||||
|
||||
dashboard.dash_load(newDashboard);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
294
v2/dboards/src/app/controllers/graphiteTarget.js
Normal file
294
v2/dboards/src/app/controllers/graphiteTarget.js
Normal file
@ -0,0 +1,294 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'config',
|
||||
'../services/graphite/gfunc',
|
||||
'../services/graphite/parser'
|
||||
],
|
||||
function (angular, _, config, gfunc, Parser) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('GraphiteTargetCtrl', function($scope) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.target.target = $scope.target.target || '';
|
||||
|
||||
parseTarget();
|
||||
};
|
||||
|
||||
// The way parsing and the target editor works needs
|
||||
// to be rewritten to handle functions that take multiple series
|
||||
function parseTarget() {
|
||||
$scope.functions = [];
|
||||
$scope.segments = [];
|
||||
$scope.showTextEditor = false;
|
||||
|
||||
delete $scope.parserError;
|
||||
|
||||
var parser = new Parser($scope.target.target);
|
||||
var astNode = parser.getAst();
|
||||
if (astNode === null) {
|
||||
checkOtherSegments(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (astNode.type === 'error') {
|
||||
$scope.parserError = astNode.message + " at position: " + astNode.pos;
|
||||
$scope.showTextEditor = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
parseTargeRecursive(astNode);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('error parsing target:', err.message);
|
||||
$scope.parserError = err.message;
|
||||
$scope.showTextEditor = true;
|
||||
}
|
||||
|
||||
checkOtherSegments($scope.segments.length - 1);
|
||||
}
|
||||
|
||||
function parseTargeRecursive(astNode, func, index) {
|
||||
if (astNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch(astNode.type) {
|
||||
case 'function':
|
||||
var innerFunc = gfunc.createFuncInstance(astNode.name);
|
||||
|
||||
_.each(astNode.params, function(param, index) {
|
||||
parseTargeRecursive(param, innerFunc, index);
|
||||
});
|
||||
|
||||
innerFunc.updateText();
|
||||
$scope.functions.push(innerFunc);
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
case 'number':
|
||||
if ((index-1) >= func.def.params.length) {
|
||||
throw { message: 'invalid number of parameters to method ' + func.def.name };
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
func.params[index] = astNode.value;
|
||||
}
|
||||
else {
|
||||
func.params[index - 1] = astNode.value;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'metric':
|
||||
if ($scope.segments.length > 0) {
|
||||
throw { message: 'Multiple metric params not supported, use text editor.' };
|
||||
}
|
||||
|
||||
$scope.segments = _.map(astNode.segments, function(segment) {
|
||||
var node = {
|
||||
type: segment.type,
|
||||
val: segment.value,
|
||||
html: segment.value
|
||||
};
|
||||
if (segment.value === '*') {
|
||||
node.html = '<i class="icon-asterisk"><i>';
|
||||
}
|
||||
if (segment.type === 'template') {
|
||||
node.val = node.html = '[[' + segment.value + ']]';
|
||||
node.html = "<span style='color: #ECEC09'>" + node.html + "</span>";
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSegmentPathUpTo(index) {
|
||||
var arr = $scope.segments.slice(0, index);
|
||||
|
||||
return _.reduce(arr, function(result, segment) {
|
||||
return result ? (result + "." + segment.val) : segment.val;
|
||||
}, "");
|
||||
}
|
||||
|
||||
function checkOtherSegments(fromIndex) {
|
||||
if (fromIndex === 0) {
|
||||
$scope.segments.push({html: 'select metric'});
|
||||
return;
|
||||
}
|
||||
|
||||
var path = getSegmentPathUpTo(fromIndex + 1);
|
||||
return $scope.datasource.metricFindQuery($scope.filter, path)
|
||||
.then(function(segments) {
|
||||
if (segments.length === 0) {
|
||||
$scope.segments = $scope.segments.splice(0, fromIndex);
|
||||
$scope.segments.push({html: 'select metric'});
|
||||
return;
|
||||
}
|
||||
if (segments[0].expandable) {
|
||||
if ($scope.segments.length === fromIndex) {
|
||||
$scope.segments.push({html: 'select metric'});
|
||||
}
|
||||
else {
|
||||
return checkOtherSegments(fromIndex + 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
});
|
||||
}
|
||||
|
||||
function setSegmentFocus(segmentIndex) {
|
||||
_.each($scope.segments, function(segment, index) {
|
||||
segment.focus = segmentIndex === index;
|
||||
});
|
||||
}
|
||||
|
||||
function wrapFunction(target, func) {
|
||||
return func.render(target);
|
||||
}
|
||||
|
||||
$scope.getAltSegments = function (index) {
|
||||
$scope.altSegments = [];
|
||||
|
||||
var query = index === 0 ?
|
||||
'*' : getSegmentPathUpTo(index) + '.*';
|
||||
|
||||
return $scope.datasource.metricFindQuery($scope.filter, query)
|
||||
.then(function(segments) {
|
||||
_.each(segments, function(segment) {
|
||||
segment.html = segment.val = segment.text;
|
||||
});
|
||||
|
||||
_.each($scope.filter.templateParameters, function(templateParameter) {
|
||||
segments.unshift({
|
||||
type: 'template',
|
||||
html: '[[' + templateParameter.name + ']]',
|
||||
val: '[[' + templateParameter.name + ']]',
|
||||
expandable: true,
|
||||
});
|
||||
});
|
||||
|
||||
segments.unshift({val: '*', html: '<i class="icon-asterisk"></i>', expandable: true });
|
||||
$scope.altSegments = segments;
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setSegment = function (altIndex, segmentIndex) {
|
||||
delete $scope.parserError;
|
||||
|
||||
$scope.segments[segmentIndex].val = $scope.altSegments[altIndex].val;
|
||||
$scope.segments[segmentIndex].html = $scope.altSegments[altIndex].html;
|
||||
|
||||
if ($scope.altSegments[altIndex].expandable) {
|
||||
return checkOtherSegments(segmentIndex + 1)
|
||||
.then(function () {
|
||||
setSegmentFocus(segmentIndex + 1);
|
||||
$scope.targetChanged();
|
||||
});
|
||||
}
|
||||
else {
|
||||
$scope.segments = $scope.segments.splice(0, segmentIndex + 1);
|
||||
}
|
||||
|
||||
setSegmentFocus(segmentIndex + 1);
|
||||
$scope.targetChanged();
|
||||
};
|
||||
|
||||
$scope.targetTextChanged = function() {
|
||||
parseTarget();
|
||||
$scope.$parent.get_data();
|
||||
};
|
||||
|
||||
$scope.targetChanged = function() {
|
||||
if ($scope.parserError) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldTarget = $scope.target.target;
|
||||
|
||||
var target = getSegmentPathUpTo($scope.segments.length);
|
||||
$scope.target.target = _.reduce($scope.functions, wrapFunction, target);
|
||||
|
||||
if ($scope.target.target !== oldTarget) {
|
||||
$scope.$parent.get_data();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeFunction = function(func) {
|
||||
$scope.functions = _.without($scope.functions, func);
|
||||
$scope.targetChanged();
|
||||
};
|
||||
|
||||
$scope.addFunction = function(funcDef) {
|
||||
var newFunc = gfunc.createFuncInstance(funcDef);
|
||||
newFunc.added = true;
|
||||
$scope.functions.push(newFunc);
|
||||
|
||||
$scope.moveAliasFuncLast();
|
||||
$scope.smartlyHandleNewAliasByNode(newFunc);
|
||||
|
||||
if (!newFunc.params.length && newFunc.added) {
|
||||
$scope.targetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveAliasFuncLast = function() {
|
||||
var aliasFunc = _.find($scope.functions, function(func) {
|
||||
return func.def.name === 'alias' ||
|
||||
func.def.name === 'aliasByNode' ||
|
||||
func.def.name === 'aliasByMetric';
|
||||
});
|
||||
|
||||
if (aliasFunc) {
|
||||
$scope.functions = _.without($scope.functions, aliasFunc);
|
||||
$scope.functions.push(aliasFunc);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.smartlyHandleNewAliasByNode = function(func) {
|
||||
if (func.def.name !== 'aliasByNode') {
|
||||
return;
|
||||
}
|
||||
for(var i = 0; i < $scope.segments.length; i++) {
|
||||
if ($scope.segments[i].val.indexOf('*') >= 0) {
|
||||
func.params[0] = i;
|
||||
func.added = false;
|
||||
$scope.targetChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.duplicate = function() {
|
||||
var clone = angular.copy($scope.target);
|
||||
$scope.panel.targets.push(clone);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('focusMe', function($timeout, $parse) {
|
||||
return {
|
||||
//scope: true, // optionally create a child scope
|
||||
link: function(scope, element, attrs) {
|
||||
var model = $parse(attrs.focusMe);
|
||||
scope.$watch(model, function(value) {
|
||||
if(value === true) {
|
||||
$timeout(function() {
|
||||
element[0].focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
90
v2/dboards/src/app/controllers/influxTargetCtrl.js
Normal file
90
v2/dboards/src/app/controllers/influxTargetCtrl.js
Normal file
@ -0,0 +1,90 @@
|
||||
define([
|
||||
'angular'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
var seriesList = null;
|
||||
|
||||
module.controller('InfluxTargetCtrl', function($scope, $timeout) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.target.function = $scope.target.function || 'mean';
|
||||
$scope.target.column = $scope.target.column || 'value';
|
||||
|
||||
$scope.rawQuery = false;
|
||||
|
||||
$scope.functions = [
|
||||
'count', 'mean', 'sum', 'min',
|
||||
'max', 'mode', 'distinct', 'median',
|
||||
'derivative', 'stddev', 'first', 'last',
|
||||
'difference'
|
||||
];
|
||||
|
||||
$scope.operators = ['=', '=~', '>', '<', '!~', '<>'];
|
||||
$scope.oldSeries = $scope.target.series;
|
||||
$scope.$on('typeahead-updated', function() {
|
||||
$timeout($scope.get_data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showQuery = function () {
|
||||
$scope.target.rawQuery = true;
|
||||
};
|
||||
|
||||
$scope.hideQuery = function () {
|
||||
$scope.target.rawQuery = false;
|
||||
};
|
||||
|
||||
// Cannot use typeahead and ng-change on blur at the same time
|
||||
$scope.seriesBlur = function() {
|
||||
if ($scope.oldSeries !== $scope.target.series) {
|
||||
$scope.oldSeries = $scope.target.series;
|
||||
$scope.columnList = null;
|
||||
$scope.get_data();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changeFunction = function(func) {
|
||||
$scope.target.function = func;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
// called outside of digest
|
||||
$scope.listColumns = function(query, callback) {
|
||||
if (!$scope.columnList) {
|
||||
$scope.$apply(function() {
|
||||
$scope.datasource.listColumns($scope.target.series).then(function(columns) {
|
||||
$scope.columnList = columns;
|
||||
callback(columns);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
return $scope.columnList;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.listSeries = function(query, callback) {
|
||||
if (!seriesList || query === '') {
|
||||
seriesList = [];
|
||||
$scope.datasource.listSeries().then(function(series) {
|
||||
seriesList = series;
|
||||
callback(seriesList);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return seriesList;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.duplicate = function() {
|
||||
var clone = angular.copy($scope.target);
|
||||
$scope.panel.targets.push(clone);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
75
v2/dboards/src/app/controllers/inspectCtrl.js
Normal file
75
v2/dboards/src/app/controllers/inspectCtrl.js
Normal file
@ -0,0 +1,75 @@
|
||||
define([
|
||||
'angular'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('InspectCtrl', function($scope) {
|
||||
var model = $scope.inspector;
|
||||
|
||||
function getParametersFromQueryString(queryString) {
|
||||
var result = [];
|
||||
var parameters = queryString.split("&");
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
var keyValue = parameters[i].split("=");
|
||||
if (keyValue[1].length > 0) {
|
||||
result.push({ key: keyValue[0], value: window.unescape(keyValue[1]) });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.editor = { index: 0 };
|
||||
|
||||
if (!model.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.error.stack) {
|
||||
$scope.editor.index = 2;
|
||||
$scope.stack_trace = model.error.stack;
|
||||
$scope.message = model.error.message;
|
||||
}
|
||||
else if (model.error.config && model.error.config.data) {
|
||||
$scope.editor.index = 1;
|
||||
|
||||
$scope.request_parameters = getParametersFromQueryString(model.error.config.data);
|
||||
|
||||
if (model.error.data.indexOf('DOCTYPE') !== -1) {
|
||||
$scope.response_html = model.error.data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('iframeContent', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, elem, attrs) {
|
||||
var getter = $parse(attrs.iframeContent), value = getter($scope);
|
||||
|
||||
$scope.$on("$destroy",function() {
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.width = '100%';
|
||||
iframe.height = '400px';
|
||||
iframe.style.border = 'none';
|
||||
iframe.src = 'about:blank';
|
||||
elem.append(iframe);
|
||||
|
||||
iframe.contentWindow.document.open('text/html', 'replace');
|
||||
iframe.contentWindow.document.write(value);
|
||||
iframe.contentWindow.document.close();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
344
v2/dboards/src/app/controllers/kairosdbTargetCtrl.js
Normal file
344
v2/dboards/src/app/controllers/kairosdbTargetCtrl.js
Normal file
@ -0,0 +1,344 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
var metricList = null;
|
||||
|
||||
module.controller('KairosDBTargetCtrl', function($scope) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.panel.stack = false;
|
||||
if (!$scope.target.downsampling) {
|
||||
$scope.target.downsampling = 'avg';
|
||||
}
|
||||
$scope.target.errors = validateTarget($scope.target);
|
||||
};
|
||||
|
||||
$scope.targetBlur = function() {
|
||||
$scope.target.errors = validateTarget($scope.target);
|
||||
if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
|
||||
$scope.oldTarget = angular.copy($scope.target);
|
||||
$scope.get_data();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.duplicate = function() {
|
||||
var clone = angular.copy($scope.target);
|
||||
$scope.panel.targets.push(clone);
|
||||
};
|
||||
|
||||
//////////////////////////////
|
||||
// SUGGESTION QUERIES
|
||||
//////////////////////////////
|
||||
|
||||
$scope.suggestMetrics = function(query, callback) {
|
||||
if (!metricList) {
|
||||
$scope.updateMetricList();
|
||||
}
|
||||
else {
|
||||
callback(metricList);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.updateMetricList = function() {
|
||||
$scope.metricListLoading = true;
|
||||
metricList = [];
|
||||
$scope.datasource.performMetricSuggestQuery().then(function(series) {
|
||||
metricList = series;
|
||||
$scope.metricListLoading = false;
|
||||
return metricList;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.suggestTagKeys = function(query, callback) {
|
||||
$scope.updateTimeRange();
|
||||
callback($scope.datasource
|
||||
.performTagSuggestQuery($scope.target.metric,$scope.rangeUnparsed, 'key',''));
|
||||
|
||||
};
|
||||
|
||||
$scope.suggestTagValues = function(query, callback) {
|
||||
callback($scope.datasource
|
||||
.performTagSuggestQuery($scope.target.metric,$scope.rangeUnparsed, 'value',$scope.target.currentTagKey));
|
||||
};
|
||||
|
||||
//////////////////////////////
|
||||
// FILTER by TAG
|
||||
//////////////////////////////
|
||||
|
||||
$scope.addFilterTag = function() {
|
||||
if (!$scope.addFilterTagMode) {
|
||||
$scope.addFilterTagMode = true;
|
||||
$scope.validateFilterTag();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.target.tags) {
|
||||
$scope.target.tags = {};
|
||||
}
|
||||
|
||||
$scope.validateFilterTag();
|
||||
if (!$scope.target.errors.tags) {
|
||||
if(!_.has($scope.target.tags,$scope.target.currentTagKey)) {
|
||||
$scope.target.tags[$scope.target.currentTagKey] = [];
|
||||
}
|
||||
$scope.target.tags[$scope.target.currentTagKey].push($scope.target.currentTagValue);
|
||||
$scope.target.currentTagKey = '';
|
||||
$scope.target.currentTagValue = '';
|
||||
$scope.targetBlur();
|
||||
}
|
||||
|
||||
$scope.addFilterTagMode = false;
|
||||
};
|
||||
|
||||
$scope.removeFilterTag = function(key) {
|
||||
delete $scope.target.tags[key];
|
||||
if(_.size($scope.target.tags)===0) {
|
||||
$scope.target.tags = null;
|
||||
}
|
||||
$scope.targetBlur();
|
||||
};
|
||||
|
||||
$scope.validateFilterTag = function() {
|
||||
$scope.target.errors.tags = null;
|
||||
if(!$scope.target.currentTagKey || !$scope.target.currentTagValue) {
|
||||
$scope.target.errors.tags = "You must specify a tag name and value.";
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////
|
||||
// GROUP BY
|
||||
//////////////////////////////
|
||||
|
||||
$scope.addGroupBy = function() {
|
||||
if (!$scope.addGroupByMode) {
|
||||
$scope.addGroupByMode = true;
|
||||
$scope.target.currentGroupByType = 'tag';
|
||||
$scope.isTagGroupBy = true;
|
||||
$scope.validateGroupBy();
|
||||
return;
|
||||
}
|
||||
$scope.validateGroupBy();
|
||||
// nb: if error is found, means that user clicked on cross : cancels input
|
||||
if (_.isEmpty($scope.target.errors.groupBy)) {
|
||||
if($scope.isTagGroupBy) {
|
||||
if (!$scope.target.groupByTags) {
|
||||
$scope.target.groupByTags = [];
|
||||
}
|
||||
console.log($scope.target.groupBy.tagKey);
|
||||
if (!_.contains($scope.target.groupByTags, $scope.target.groupBy.tagKey)) {
|
||||
$scope.target.groupByTags.push($scope.target.groupBy.tagKey);
|
||||
$scope.targetBlur();
|
||||
}
|
||||
$scope.target.groupBy.tagKey = '';
|
||||
}
|
||||
else {
|
||||
if (!$scope.target.nonTagGroupBys) {
|
||||
$scope.target.nonTagGroupBys = [];
|
||||
}
|
||||
var groupBy = {
|
||||
name: $scope.target.currentGroupByType
|
||||
};
|
||||
if($scope.isValueGroupBy) {groupBy.range_size = $scope.target.groupBy.valueRange;}
|
||||
else if($scope.isTimeGroupBy) {
|
||||
groupBy.range_size = $scope.target.groupBy.timeInterval;
|
||||
groupBy.group_count = $scope.target.groupBy.groupCount;
|
||||
}
|
||||
$scope.target.nonTagGroupBys.push(groupBy);
|
||||
}
|
||||
$scope.targetBlur();
|
||||
}
|
||||
$scope.isTagGroupBy = false;
|
||||
$scope.isValueGroupBy = false;
|
||||
$scope.isTimeGroupBy = false;
|
||||
$scope.addGroupByMode = false;
|
||||
};
|
||||
|
||||
$scope.removeGroupByTag = function(index) {
|
||||
$scope.target.groupByTags.splice(index, 1);
|
||||
if(_.size($scope.target.groupByTags)===0) {
|
||||
$scope.target.groupByTags = null;
|
||||
}
|
||||
$scope.targetBlur();
|
||||
};
|
||||
|
||||
$scope.removeNonTagGroupBy = function(index) {
|
||||
$scope.target.nonTagGroupBys.splice(index, 1);
|
||||
if(_.size($scope.target.nonTagGroupBys)===0) {
|
||||
$scope.target.nonTagGroupBys = null;
|
||||
}
|
||||
$scope.targetBlur();
|
||||
};
|
||||
|
||||
$scope.changeGroupByInput = function() {
|
||||
$scope.isTagGroupBy = $scope.target.currentGroupByType==='tag';
|
||||
$scope.isValueGroupBy = $scope.target.currentGroupByType==='value';
|
||||
$scope.isTimeGroupBy = $scope.target.currentGroupByType==='time';
|
||||
$scope.validateGroupBy();
|
||||
};
|
||||
|
||||
$scope.validateGroupBy = function() {
|
||||
delete $scope.target.errors.groupBy;
|
||||
var errors = {};
|
||||
$scope.isGroupByValid = true;
|
||||
if($scope.isTagGroupBy) {
|
||||
if(!$scope.target.groupBy.tagKey) {
|
||||
$scope.isGroupByValid = false;
|
||||
errors.tagKey = 'You must supply a tag name';
|
||||
}
|
||||
}
|
||||
if($scope.isValueGroupBy) {
|
||||
if(!$scope.target.groupBy.valueRange || !isInt($scope.target.groupBy.valueRange)) {
|
||||
errors.valueRange = "Range must be an integer";
|
||||
$scope.isGroupByValid = false;
|
||||
}
|
||||
}
|
||||
if($scope.isTimeGroupBy) {
|
||||
try {
|
||||
$scope.datasource.convertToKairosInterval($scope.target.groupBy.timeInterval);
|
||||
} catch(err) {
|
||||
errors.timeInterval = err.message;
|
||||
$scope.isGroupByValid = false;
|
||||
}
|
||||
if(!$scope.target.groupBy.groupCount || !isInt($scope.target.groupBy.groupCount)) {
|
||||
errors.groupCount = "Group count must be an integer";
|
||||
$scope.isGroupByValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!_.isEmpty(errors)) {
|
||||
$scope.target.errors.groupBy = errors;
|
||||
}
|
||||
};
|
||||
|
||||
function isInt(n) {
|
||||
return parseInt(n) % 1 === 0;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// HORIZONTAL AGGREGATION
|
||||
//////////////////////////////
|
||||
|
||||
$scope.addHorizontalAggregator = function() {
|
||||
if (!$scope.addHorizontalAggregatorMode) {
|
||||
$scope.addHorizontalAggregatorMode = true;
|
||||
$scope.target.currentHorizontalAggregatorName = 'avg';
|
||||
$scope.hasSamplingRate = true;
|
||||
$scope.validateHorizontalAggregator();
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.validateHorizontalAggregator();
|
||||
// nb: if error is found, means that user clicked on cross : cancels input
|
||||
if(_.isEmpty($scope.target.errors.horAggregator)) {
|
||||
if (!$scope.target.horizontalAggregators) {
|
||||
$scope.target.horizontalAggregators = [];
|
||||
}
|
||||
var aggregator = {
|
||||
name:$scope.target.currentHorizontalAggregatorName
|
||||
};
|
||||
if($scope.hasSamplingRate) {aggregator.sampling_rate = $scope.target.horAggregator.samplingRate;}
|
||||
if($scope.hasUnit) {aggregator.unit = $scope.target.horAggregator.unit;}
|
||||
if($scope.hasFactor) {aggregator.factor = $scope.target.horAggregator.factor;}
|
||||
if($scope.hasPercentile) {aggregator.percentile = $scope.target.horAggregator.percentile;}
|
||||
$scope.target.horizontalAggregators.push(aggregator);
|
||||
$scope.targetBlur();
|
||||
}
|
||||
|
||||
$scope.addHorizontalAggregatorMode = false;
|
||||
$scope.hasSamplingRate = false;
|
||||
$scope.hasUnit = false;
|
||||
$scope.hasFactor = false;
|
||||
$scope.hasPercentile = false;
|
||||
|
||||
};
|
||||
|
||||
$scope.removeHorizontalAggregator = function(index) {
|
||||
$scope.target.horizontalAggregators.splice(index, 1);
|
||||
if(_.size($scope.target.horizontalAggregators)===0) {
|
||||
$scope.target.horizontalAggregators = null;
|
||||
}
|
||||
|
||||
$scope.targetBlur();
|
||||
};
|
||||
|
||||
$scope.changeHorAggregationInput = function() {
|
||||
$scope.hasSamplingRate = _.contains(['avg','dev','max','min','sum','least_squares','count','percentile'],
|
||||
$scope.target.currentHorizontalAggregatorName);
|
||||
$scope.hasUnit = _.contains(['sampler','rate'], $scope.target.currentHorizontalAggregatorName);
|
||||
$scope.hasFactor = _.contains(['div','scale'], $scope.target.currentHorizontalAggregatorName);
|
||||
$scope.hasPercentile = 'percentile'===$scope.target.currentHorizontalAggregatorName;
|
||||
$scope.validateHorizontalAggregator();
|
||||
};
|
||||
|
||||
$scope.validateHorizontalAggregator = function() {
|
||||
delete $scope.target.errors.horAggregator;
|
||||
var errors = {};
|
||||
$scope.isAggregatorValid = true;
|
||||
if($scope.hasSamplingRate) {
|
||||
try {
|
||||
$scope.datasource.convertToKairosInterval($scope.target.horAggregator.samplingRate);
|
||||
} catch(err) {
|
||||
errors.samplingRate = err.message;
|
||||
$scope.isAggregatorValid = false;
|
||||
}
|
||||
}
|
||||
if($scope.hasFactor) {
|
||||
if(!$scope.target.horAggregator.factor) {
|
||||
errors.factor = 'You must supply a numeric value for this aggregator';
|
||||
$scope.isAggregatorValid = false;
|
||||
}
|
||||
else if(parseInt($scope.target.horAggregator.factor)===0 && $scope.target.currentHorizontalAggregatorName==='div') {
|
||||
errors.factor = 'Cannot divide by 0';
|
||||
$scope.isAggregatorValid = false;
|
||||
}
|
||||
}
|
||||
if($scope.hasPercentile) {
|
||||
if(!$scope.target.horAggregator.percentile ||
|
||||
$scope.target.horAggregator.percentile<=0 ||
|
||||
$scope.target.horAggregator.percentile>1) {
|
||||
errors.percentile = 'Percentile must be between 0 and 1';
|
||||
$scope.isAggregatorValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!_.isEmpty(errors)) {
|
||||
$scope.target.errors.horAggregator = errors;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.alert = function(message) {
|
||||
alert(message);
|
||||
};
|
||||
|
||||
//////////////////////////////
|
||||
// VALIDATION
|
||||
//////////////////////////////
|
||||
|
||||
function validateTarget(target) {
|
||||
var errs = {};
|
||||
|
||||
if (!target.metric) {
|
||||
errs.metric = "You must supply a metric name.";
|
||||
}
|
||||
|
||||
try {
|
||||
if (target.sampling) {
|
||||
$scope.datasource.convertToKairosInterval(target.sampling);
|
||||
}
|
||||
} catch(err) {
|
||||
errs.sampling = err.message;
|
||||
}
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
186
v2/dboards/src/app/controllers/metricKeys.js
Normal file
186
v2/dboards/src/app/controllers/metricKeys.js
Normal file
@ -0,0 +1,186 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'config'
|
||||
],
|
||||
function (angular, _, config) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('MetricKeysCtrl', function($scope, $http, $q) {
|
||||
var elasticSearchUrlForMetricIndex = config.elasticsearch + '/' + config.grafana_metrics_index + '/';
|
||||
var httpOptions = {};
|
||||
if (config.elasticsearchBasicAuth) {
|
||||
httpOptions.withCredentials = true;
|
||||
httpOptions.headers = {
|
||||
"Authorization": "Basic " + config.elasticsearchBasicAuth
|
||||
};
|
||||
}
|
||||
$scope.init = function () {
|
||||
$scope.metricPath = "prod.apps.api.boobarella.*";
|
||||
$scope.metricCounter = 0;
|
||||
};
|
||||
|
||||
$scope.createIndex = function () {
|
||||
$scope.errorText = null;
|
||||
$scope.infoText = null;
|
||||
|
||||
deleteIndex()
|
||||
.then(createIndex)
|
||||
.then(function () {
|
||||
$scope.infoText = "Index created!";
|
||||
})
|
||||
.then(null, function (err) {
|
||||
$scope.errorText = angular.toJson(err);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadMetricsFromPath = function() {
|
||||
$scope.errorText = null;
|
||||
$scope.infoText = null;
|
||||
$scope.metricCounter = 0;
|
||||
|
||||
return loadMetricsRecursive($scope.metricPath)
|
||||
.then(function() {
|
||||
$scope.infoText = "Indexing completed!";
|
||||
}, function(err) {
|
||||
$scope.errorText = "Error: " + err;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadAll = function() {
|
||||
$scope.infoText = "Fetching all metrics from graphite...";
|
||||
|
||||
getFromEachGraphite('/metrics/index.json', saveMetricsArray)
|
||||
.then(function() {
|
||||
$scope.infoText = "Indexing complete!";
|
||||
}).then(null, function(err) {
|
||||
$scope.errorText = err;
|
||||
});
|
||||
};
|
||||
|
||||
function getFromEachGraphite(request, data_callback, error_callback) {
|
||||
return $q.all(_.map(config.datasources, function(datasource) {
|
||||
if (datasource.type = 'graphite') {
|
||||
return $http.get(datasource.url + request)
|
||||
.then(data_callback, error_callback);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function saveMetricsArray(data, currentIndex) {
|
||||
if (!data && !data.data && data.data.length === 0) {
|
||||
return $q.reject('No metrics from graphite');
|
||||
}
|
||||
|
||||
if (data.data.length === currentIndex) {
|
||||
return $q.when('done');
|
||||
}
|
||||
|
||||
currentIndex = currentIndex || 0;
|
||||
|
||||
return saveMetricKey(data.data[currentIndex])
|
||||
.then(function() {
|
||||
return saveMetricsArray(data, currentIndex + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteIndex()
|
||||
{
|
||||
var deferred = $q.defer();
|
||||
$http.delete(elasticSearchUrlForMetricIndex, httpOptions)
|
||||
.success(function() {
|
||||
deferred.resolve('ok');
|
||||
})
|
||||
.error(function(data, status) {
|
||||
if (status === 404) {
|
||||
deferred.resolve('ok');
|
||||
}
|
||||
else {
|
||||
deferred.reject('elastic search returned unexpected error');
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function createIndex()
|
||||
{
|
||||
return $http.put(elasticSearchUrlForMetricIndex, {
|
||||
settings: {
|
||||
analysis: {
|
||||
analyzer: {
|
||||
metric_path_ngram : { tokenizer : "my_ngram_tokenizer" }
|
||||
},
|
||||
tokenizer: {
|
||||
my_ngram_tokenizer : {
|
||||
type : "nGram",
|
||||
min_gram : "3",
|
||||
max_gram : "8",
|
||||
token_chars: ["letter", "digit", "punctuation", "symbol"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mappings: {
|
||||
metricKey: {
|
||||
properties: {
|
||||
metricPath: {
|
||||
type: "multi_field",
|
||||
fields: {
|
||||
"metricPath": { type: "string", index: "analyzed", index_analyzer: "standard" },
|
||||
"metricPath_ng": { type: "string", index: "analyzed", index_analyzer: "metric_path_ngram" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, httpOptions);
|
||||
}
|
||||
|
||||
function receiveMetric(result) {
|
||||
var data = result.data;
|
||||
if (!data || data.length === 0) {
|
||||
console.log('no data');
|
||||
return;
|
||||
}
|
||||
|
||||
var funcs = _.map(data, function(metric) {
|
||||
if (metric.expandable) {
|
||||
return loadMetricsRecursive(metric.id + ".*");
|
||||
}
|
||||
if (metric.leaf) {
|
||||
return saveMetricKey(metric.id);
|
||||
}
|
||||
});
|
||||
|
||||
return $q.all(funcs);
|
||||
}
|
||||
|
||||
function saveMetricKey(metricId) {
|
||||
|
||||
// Create request with id as title. Rethink this.
|
||||
var request = $scope.ejs.Document(config.grafana_metrics_index, 'metricKey', metricId).source({
|
||||
metricPath: metricId
|
||||
});
|
||||
|
||||
return request.doIndex(
|
||||
function() {
|
||||
$scope.infoText = "Indexing " + metricId;
|
||||
$scope.metricCounter = $scope.metricCounter + 1;
|
||||
},
|
||||
function() {
|
||||
$scope.errorText = "failed to save metric " + metricId;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function loadMetricsRecursive(metricPath)
|
||||
{
|
||||
return getFromEachGraphite('/metrics/find/?query=' + metricPath, receiveMetric);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
118
v2/dboards/src/app/controllers/opentsdbTargetCtrl.js
Normal file
118
v2/dboards/src/app/controllers/opentsdbTargetCtrl.js
Normal file
@ -0,0 +1,118 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'kbn'
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('OpenTSDBTargetCtrl', function($scope, $timeout) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.target.errors = validateTarget($scope.target);
|
||||
$scope.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax'];
|
||||
|
||||
if (!$scope.target.aggregator) {
|
||||
$scope.target.aggregator = 'sum';
|
||||
}
|
||||
|
||||
if (!$scope.target.downsampleAggregator) {
|
||||
$scope.target.downsampleAggregator = 'sum';
|
||||
}
|
||||
|
||||
$scope.$on('typeahead-updated', function() {
|
||||
$timeout($scope.targetBlur);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.targetBlur = function() {
|
||||
$scope.target.errors = validateTarget($scope.target);
|
||||
|
||||
// this does not work so good
|
||||
if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
|
||||
$scope.oldTarget = angular.copy($scope.target);
|
||||
$scope.get_data();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.duplicate = function() {
|
||||
var clone = angular.copy($scope.target);
|
||||
$scope.panel.targets.push(clone);
|
||||
};
|
||||
|
||||
$scope.suggestMetrics = function(query, callback) {
|
||||
$scope.datasource
|
||||
.performSuggestQuery(query, 'metrics')
|
||||
.then(callback);
|
||||
};
|
||||
|
||||
$scope.suggestTagKeys = function(query, callback) {
|
||||
$scope.datasource
|
||||
.performSuggestQuery(query, 'tagk')
|
||||
.then(callback);
|
||||
};
|
||||
|
||||
$scope.suggestTagValues = function(query, callback) {
|
||||
$scope.datasource
|
||||
.performSuggestQuery(query, 'tagv')
|
||||
.then(callback);
|
||||
};
|
||||
|
||||
$scope.addTag = function() {
|
||||
if (!$scope.addTagMode) {
|
||||
$scope.addTagMode = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.target.tags) {
|
||||
$scope.target.tags = {};
|
||||
}
|
||||
|
||||
$scope.target.errors = validateTarget($scope.target);
|
||||
|
||||
if (!$scope.target.errors.tags) {
|
||||
$scope.target.tags[$scope.target.currentTagKey] = $scope.target.currentTagValue;
|
||||
$scope.target.currentTagKey = '';
|
||||
$scope.target.currentTagValue = '';
|
||||
$scope.targetBlur();
|
||||
}
|
||||
|
||||
$scope.addTagMode = false;
|
||||
};
|
||||
|
||||
$scope.removeTag = function(key) {
|
||||
delete $scope.target.tags[key];
|
||||
$scope.targetBlur();
|
||||
};
|
||||
|
||||
function validateTarget(target) {
|
||||
var errs = {};
|
||||
|
||||
if (!target.metric) {
|
||||
errs.metric = "You must supply a metric name.";
|
||||
}
|
||||
|
||||
if (target.shouldDownsample) {
|
||||
try {
|
||||
if (target.downsampleInterval) {
|
||||
kbn.describe_interval(target.downsampleInterval);
|
||||
} else {
|
||||
errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h').";
|
||||
}
|
||||
} catch(err) {
|
||||
errs.downsampleInterval = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.tags && _.has(target.tags, target.currentTagKey)) {
|
||||
errs.tags = "Duplicate tag key '" + target.currentTagKey + "'.";
|
||||
}
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
134
v2/dboards/src/app/controllers/panelBaseCtrl.js
Normal file
134
v2/dboards/src/app/controllers/panelBaseCtrl.js
Normal file
@ -0,0 +1,134 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'jquery'
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
// This function needs $inject annotations, update below
|
||||
// when changing arguments to this function
|
||||
function PanelBaseCtrl($scope, $rootScope, $timeout) {
|
||||
|
||||
var menu = [
|
||||
{
|
||||
text: 'Edit',
|
||||
configModal: "app/partials/paneleditor.html",
|
||||
condition: !$scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: 'Edit',
|
||||
click: "toggleFullscreenEdit()",
|
||||
condition: $scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: "Fullscreen",
|
||||
click: 'toggleFullscreen()',
|
||||
condition: $scope.panelMeta.fullscreenView
|
||||
},
|
||||
{
|
||||
text: 'Duplicate',
|
||||
click: 'duplicatePanel(panel)',
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Span',
|
||||
submenu: [
|
||||
{ text: '1', click: 'updateColumnSpan(1)' },
|
||||
{ text: '2', click: 'updateColumnSpan(2)' },
|
||||
{ text: '3', click: 'updateColumnSpan(3)' },
|
||||
{ text: '4', click: 'updateColumnSpan(4)' },
|
||||
{ text: '5', click: 'updateColumnSpan(5)' },
|
||||
{ text: '6', click: 'updateColumnSpan(6)' },
|
||||
{ text: '7', click: 'updateColumnSpan(7)' },
|
||||
{ text: '8', click: 'updateColumnSpan(8)' },
|
||||
{ text: '9', click: 'updateColumnSpan(9)' },
|
||||
{ text: '10', click: 'updateColumnSpan(10)' },
|
||||
{ text: '11', click: 'updateColumnSpan(11)' },
|
||||
{ text: '12', click: 'updateColumnSpan(12)' },
|
||||
],
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
click: 'remove_panel_from_row(row, panel)',
|
||||
condition: true
|
||||
}
|
||||
];
|
||||
|
||||
$scope.inspector = {};
|
||||
$scope.panelMeta.menu = _.where(menu, { condition: true });
|
||||
|
||||
$scope.updateColumnSpan = function(span) {
|
||||
$scope.panel.span = span;
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$emit('render');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.enterFullscreenMode = function(options) {
|
||||
var docHeight = $(window).height();
|
||||
var editHeight = Math.floor(docHeight * 0.3);
|
||||
var fullscreenHeight = Math.floor(docHeight * 0.7);
|
||||
var oldTimeRange = $scope.range;
|
||||
|
||||
$scope.height = options.edit ? editHeight : fullscreenHeight;
|
||||
$scope.editMode = options.edit;
|
||||
|
||||
if (!$scope.fullscreen) {
|
||||
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
|
||||
$scope.editMode = false;
|
||||
$scope.fullscreen = false;
|
||||
delete $scope.height;
|
||||
|
||||
closeEditMode();
|
||||
|
||||
$timeout(function() {
|
||||
if (oldTimeRange !== $scope.range) {
|
||||
$scope.dashboard.refresh();
|
||||
}
|
||||
else {
|
||||
$scope.$emit('render');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(window).scrollTop(0);
|
||||
|
||||
$scope.fullscreen = true;
|
||||
|
||||
$rootScope.$emit('panel-fullscreen-enter');
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$emit('render');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.toggleFullscreenEdit = function() {
|
||||
if ($scope.editMode) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.enterFullscreenMode({edit: true});
|
||||
};
|
||||
|
||||
$scope.toggleFullscreen = function() {
|
||||
if ($scope.fullscreen && !$scope.editMode) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.enterFullscreenMode({ edit: false });
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
PanelBaseCtrl['$inject'] = ['$scope', '$rootScope', '$timeout'];
|
||||
|
||||
return PanelBaseCtrl;
|
||||
|
||||
});
|
39
v2/dboards/src/app/controllers/playlistCtrl.js
Normal file
39
v2/dboards/src/app/controllers/playlistCtrl.js
Normal file
@ -0,0 +1,39 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'config'
|
||||
],
|
||||
function (angular, _, config) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('PlaylistCtrl', function($scope, playlistSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.timespan = config.playlist_timespan;
|
||||
$scope.loadFavorites();
|
||||
$scope.$on('modal-opened', $scope.loadFavorites);
|
||||
};
|
||||
|
||||
$scope.loadFavorites = function() {
|
||||
$scope.favDashboards = playlistSrv.getFavorites().dashboards;
|
||||
|
||||
_.each($scope.favDashboards, function(dashboard) {
|
||||
dashboard.include = true;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAsFavorite = function(dashboard) {
|
||||
playlistSrv.removeAsFavorite(dashboard);
|
||||
$scope.loadFavorites();
|
||||
};
|
||||
|
||||
$scope.start = function() {
|
||||
var included = _.where($scope.favDashboards, { include: true });
|
||||
playlistSrv.start(included, $scope.timespan);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
42
v2/dboards/src/app/controllers/pulldown.js
Normal file
42
v2/dboards/src/app/controllers/pulldown.js
Normal file
@ -0,0 +1,42 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('PulldownCtrl', function($scope, $rootScope, $timeout) {
|
||||
var _d = {
|
||||
collapse: false,
|
||||
notice: false,
|
||||
enable: true
|
||||
};
|
||||
|
||||
_.defaults($scope.pulldown,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
// Provide a combined skeleton for panels that must interact with panel and row.
|
||||
// This might create name spacing issues.
|
||||
$scope.panel = $scope.pulldown;
|
||||
$scope.row = $scope.pulldown;
|
||||
};
|
||||
|
||||
$scope.toggle_pulldown = function(pulldown) {
|
||||
pulldown.collapse = pulldown.collapse ? false : true;
|
||||
if (!pulldown.collapse) {
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('render');
|
||||
});
|
||||
} else {
|
||||
$scope.row.notice = false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
195
v2/dboards/src/app/controllers/row.js
Normal file
195
v2/dboards/src/app/controllers/row.js
Normal file
@ -0,0 +1,195 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('RowCtrl', function($scope, $rootScope, $timeout) {
|
||||
var _d = {
|
||||
title: "Row",
|
||||
height: "150px",
|
||||
collapse: false,
|
||||
collapsable: true,
|
||||
editable: true,
|
||||
panels: [],
|
||||
notice: false
|
||||
};
|
||||
|
||||
_.defaults($scope.row,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.reset_panel();
|
||||
};
|
||||
|
||||
$scope.toggle_row = function(row) {
|
||||
if(!row.collapsable) {
|
||||
return;
|
||||
}
|
||||
row.collapse = row.collapse ? false : true;
|
||||
if (!row.collapse) {
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('render');
|
||||
});
|
||||
} else {
|
||||
row.notice = false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.rowSpan = function(row) {
|
||||
var panels = _.filter(row.panels, function(p) {
|
||||
return $scope.isPanel(p);
|
||||
});
|
||||
return _.reduce(_.pluck(panels,'span'), function(p,v) {
|
||||
return p+v;
|
||||
},0);
|
||||
};
|
||||
|
||||
// This can be overridden by individual panels
|
||||
$scope.close_edit = function() {
|
||||
$scope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.add_panel = function(panel) {
|
||||
var rowSpan = $scope.rowSpan($scope.row);
|
||||
var panelCount = $scope.row.panels.length;
|
||||
var space = (12 - rowSpan) - panel.span;
|
||||
|
||||
// try to make room of there is no space left
|
||||
if (space <= 0) {
|
||||
if (panelCount === 1) {
|
||||
$scope.row.panels[0].span = 6;
|
||||
panel.span = 6;
|
||||
}
|
||||
else if (panelCount === 2) {
|
||||
$scope.row.panels[0].span = 4;
|
||||
$scope.row.panels[1].span = 4;
|
||||
panel.span = 4;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.row.panels.push(panel);
|
||||
};
|
||||
|
||||
$scope.delete_row = function() {
|
||||
if (confirm("Are you sure you want to delete this row?")) {
|
||||
$scope.dashboard.current.rows = _.without($scope.dashboard.current.rows, $scope.row);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.move_row = function(direction) {
|
||||
var rowsList = $scope.dashboard.current.rows;
|
||||
var rowIndex = _.indexOf(rowsList, $scope.row);
|
||||
var newIndex = rowIndex + direction;
|
||||
if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
|
||||
_.move(rowsList, rowIndex, rowIndex + direction);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.add_panel_default = function(type) {
|
||||
$scope.reset_panel(type);
|
||||
$scope.add_panel($scope.panel);
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('render');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.set_height = function(height) {
|
||||
$scope.row.height = height;
|
||||
$scope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.remove_panel_from_row = function(row, panel) {
|
||||
if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) {
|
||||
row.panels = _.without(row.panels,panel);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.duplicatePanel = function(panel, row) {
|
||||
row = row || $scope.row;
|
||||
var currentRowSpan = $scope.rowSpan(row);
|
||||
if (currentRowSpan <= 9) {
|
||||
row.panels.push(angular.copy(panel));
|
||||
}
|
||||
else {
|
||||
var rowsList = $scope.dashboard.current.rows;
|
||||
var rowIndex = _.indexOf(rowsList, row);
|
||||
if (rowIndex === rowsList.length - 1) {
|
||||
var newRow = angular.copy($scope.row);
|
||||
newRow.panels = [];
|
||||
$scope.dashboard.current.rows.push(newRow);
|
||||
$scope.duplicatePanel(panel, newRow);
|
||||
}
|
||||
else {
|
||||
$scope.duplicatePanel(panel, rowsList[rowIndex+1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @scratch /panels/0
|
||||
* [[panels]]
|
||||
* = Panels
|
||||
*
|
||||
* [partintro]
|
||||
* --
|
||||
* *Kibana* dashboards are made up of blocks called +panels+. Panels are organized into rows
|
||||
* and can serve many purposes, though most are designed to provide the results of a query or
|
||||
* multiple queries as a visualization. Other panels may show collections of documents or
|
||||
* allow you to insert instructions for your users.
|
||||
*
|
||||
* Panels can be configured easily via the Kibana web interface. For more advanced usage, such
|
||||
* as templated or scripted dashboards, documentation of panel properties is available in this
|
||||
* section. You may find settings here which are not exposed via the web interface.
|
||||
*
|
||||
* Each panel type has its own properties, hover there are several that are shared.
|
||||
*
|
||||
*/
|
||||
|
||||
$scope.reset_panel = function(type) {
|
||||
var
|
||||
defaultSpan = 4,
|
||||
_as = 12-$scope.rowSpan($scope.row);
|
||||
|
||||
$scope.panel = {
|
||||
error : false,
|
||||
/** @scratch /panels/1
|
||||
* span:: A number, 1-12, that describes the width of the panel.
|
||||
*/
|
||||
span : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
|
||||
/** @scratch /panels/1
|
||||
* editable:: Enable or disable the edit button the the panel
|
||||
*/
|
||||
editable: true,
|
||||
/** @scratch /panels/1
|
||||
* type:: The type of panel this object contains. Each panel type will require additional
|
||||
* properties. See the panel types list to the right.
|
||||
*/
|
||||
type : type
|
||||
};
|
||||
|
||||
function fixRowHeight(height) {
|
||||
if (!height) {
|
||||
return '200px';
|
||||
}
|
||||
if (!_.isString(height)) {
|
||||
return height + 'px';
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
$scope.row.height = fixRowHeight($scope.row.height);
|
||||
};
|
||||
|
||||
/** @scratch /panels/2
|
||||
* --
|
||||
*/
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
196
v2/dboards/src/app/controllers/search.js
Normal file
196
v2/dboards/src/app/controllers/search.js
Normal file
@ -0,0 +1,196 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'config',
|
||||
'jquery'
|
||||
],
|
||||
function (angular, _, config, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('SearchCtrl', function($scope, $rootScope, dashboard, $element, $location) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.giveSearchFocus = 0;
|
||||
$scope.selectedIndex = -1;
|
||||
$scope.results = {dashboards: [], tags: [], metrics: []};
|
||||
$scope.query = { query: 'title:' };
|
||||
$rootScope.$on('open-search', $scope.openSearch);
|
||||
};
|
||||
|
||||
$scope.keyDown = function (evt) {
|
||||
if (evt.keyCode === 27) {
|
||||
$element.find('.dropdown-toggle').dropdown('toggle');
|
||||
}
|
||||
if (evt.keyCode === 40) {
|
||||
$scope.selectedIndex++;
|
||||
}
|
||||
if (evt.keyCode === 38) {
|
||||
$scope.selectedIndex--;
|
||||
}
|
||||
if (evt.keyCode === 13) {
|
||||
if ($scope.tagsOnly) {
|
||||
var tag = $scope.results.tags[$scope.selectedIndex];
|
||||
if (tag) {
|
||||
$scope.filterByTag(tag.term);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
|
||||
if (selectedDash) {
|
||||
$location.path("/dashboard/elasticsearch/" + encodeURIComponent(selectedDash._id));
|
||||
setTimeout(function() {
|
||||
$('body').click(); // hack to force dropdown to close;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.searchDasboards = function(query) {
|
||||
var request = $scope.ejs.Request().indices(config.grafana_index).types('dashboard');
|
||||
var tagsOnly = query.indexOf('tags!:') === 0;
|
||||
if (tagsOnly) {
|
||||
var tagsQuery = query.substring(6, query.length);
|
||||
query = 'tags:' + tagsQuery + '*';
|
||||
}
|
||||
else {
|
||||
if (query.length === 0) {
|
||||
query = 'title:';
|
||||
}
|
||||
|
||||
if (query[query.length - 1] !== '*') {
|
||||
query += '*';
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
.query($scope.ejs.QueryStringQuery(query))
|
||||
.sort('_uid')
|
||||
.facet($scope.ejs.TermsFacet("tags").field("tags").order('term').size(50))
|
||||
.size(20).doSearch()
|
||||
.then(function(results) {
|
||||
|
||||
if(_.isUndefined(results.hits)) {
|
||||
$scope.results.dashboards = [];
|
||||
$scope.results.tags = [];
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.tagsOnly = tagsOnly;
|
||||
$scope.results.dashboards = results.hits.hits;
|
||||
$scope.results.tags = results.facets.tags.terms;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.filterByTag = function(tag, evt) {
|
||||
$scope.query.query = "tags:" + tag + " AND title:";
|
||||
$scope.search();
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
if (evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showTags = function(evt) {
|
||||
evt.stopPropagation();
|
||||
$scope.tagsOnly = !$scope.tagsOnly;
|
||||
$scope.query.query = $scope.tagsOnly ? "tags!:" : "";
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.selectedIndex = -1;
|
||||
$scope.search();
|
||||
};
|
||||
|
||||
$scope.search = function() {
|
||||
$scope.showImport = false;
|
||||
$scope.selectedIndex = -1;
|
||||
|
||||
var queryStr = $scope.query.query.toLowerCase();
|
||||
|
||||
if (queryStr.indexOf('m:') !== 0) {
|
||||
queryStr = queryStr.replace(' and ', ' AND ');
|
||||
$scope.searchDasboards(queryStr);
|
||||
return;
|
||||
}
|
||||
|
||||
queryStr = queryStr.substring(2, queryStr.length);
|
||||
|
||||
var words = queryStr.split(' ');
|
||||
var query = $scope.ejs.BoolQuery();
|
||||
var terms = _.map(words, function(word) {
|
||||
return $scope.ejs.MatchQuery('metricPath_ng', word).boost(1.2);
|
||||
});
|
||||
|
||||
var ngramQuery = $scope.ejs.BoolQuery();
|
||||
ngramQuery.must(terms);
|
||||
|
||||
var fieldMatchQuery = $scope.ejs.FieldQuery('metricPath', queryStr + "*").boost(1.2);
|
||||
query.should([ngramQuery, fieldMatchQuery]);
|
||||
|
||||
var request = $scope.ejs.Request().indices(config.grafana_index).types('metricKey');
|
||||
var results = request.query(query).size(20).doSearch();
|
||||
|
||||
results.then(function(results) {
|
||||
if (results && results.hits && results.hits.hits.length > 0) {
|
||||
$scope.results.metrics = { metrics: results.hits.hits };
|
||||
}
|
||||
else {
|
||||
$scope.results.metrics = { metric: [] };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.openSearch = function (evt) {
|
||||
if (evt) {
|
||||
$element.find('.dropdown-toggle').dropdown('toggle');
|
||||
}
|
||||
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.query.query = 'title:';
|
||||
$scope.search();
|
||||
};
|
||||
|
||||
$scope.addMetricToCurrentDashboard = function (metricId) {
|
||||
dashboard.current.rows.push({
|
||||
title: '',
|
||||
height: '250px',
|
||||
editable: true,
|
||||
panels: [
|
||||
{
|
||||
type: 'graphite',
|
||||
title: 'test',
|
||||
span: 12,
|
||||
targets: [{ target: metricId }]
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleImport = function ($event) {
|
||||
$event.stopPropagation();
|
||||
$scope.showImport = !$scope.showImport;
|
||||
};
|
||||
|
||||
$scope.newDashboard = function() {
|
||||
$location.url('/dashboard/file/empty.json');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('xngFocus', function() {
|
||||
return function(scope, element, attrs) {
|
||||
$(element).click(function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
scope.$watch(attrs.xngFocus,function (newValue) {
|
||||
setTimeout(function() {
|
||||
newValue && element.focus();
|
||||
}, 200);
|
||||
},true);
|
||||
};
|
||||
});
|
||||
|
||||
});
|
27
v2/dboards/src/app/controllers/submenuCtrl.js
Normal file
27
v2/dboards/src/app/controllers/submenuCtrl.js
Normal file
@ -0,0 +1,27 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('SubmenuCtrl', function($scope) {
|
||||
var _d = {
|
||||
enable: true
|
||||
};
|
||||
|
||||
_.defaults($scope.pulldown,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.panel = $scope.pulldown;
|
||||
$scope.row = $scope.pulldown;
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
153
v2/dboards/src/app/dashboards/default.json
Normal file
153
v2/dboards/src/app/dashboards/default.json
Normal file
@ -0,0 +1,153 @@
|
||||
{
|
||||
"title": "Welcome to Grafana!",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Welcome to Grafana",
|
||||
"height": "150px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "####Thank you for trying out Grafana! \n\nGeneral documentation is found in the readme and in the wiki section of the [Github Project](https://github.com/torkelo/grafana). If you encounter any problem or have an idea for an improvement do not hesitate to open a github issue. \n\nTips: \n\n- Ctrl+S saves the current dashboard\n- Ctrl+F Opens the dashboard finder (searches elastic search)\n- Ctrl+H Hide/show row controls \n- Click and drag graph title to move panel (only works when row controls are enabled)\n\nIf you do not see a graph in the panel below the browser cannot access your graphite installation. Please make sure that datasources property in config.js is correctly set and that any urls accessible from your browser.",
|
||||
"style": {},
|
||||
"title": "Welcome to Grafana"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "test",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": ["short", "short"],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": null
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": true,
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "randomWalk('random walk')"
|
||||
},
|
||||
{
|
||||
"target": "randomWalk('random walk2')"
|
||||
},
|
||||
{
|
||||
"target": "randomWalk('random walk3')"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Graphite test"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false
|
||||
}
|
83
v2/dboards/src/app/dashboards/empty.json
Normal file
83
v2/dboards/src/app/dashboards/empty.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"title": "New Dashboard",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false
|
||||
}
|
80
v2/dboards/src/app/dashboards/scripted.js
Normal file
80
v2/dboards/src/app/dashboards/scripted.js
Normal file
@ -0,0 +1,80 @@
|
||||
/* global _ */
|
||||
|
||||
/*
|
||||
* Complex scripted dashboard
|
||||
* This script generates a dashboard object that Grafana can load. It also takes a number of user
|
||||
* supplied URL parameters (int ARGS variable)
|
||||
*
|
||||
* Return a dashboard object, or a function
|
||||
*
|
||||
* For async scripts, return a function, this function must take a single callback function as argument,
|
||||
* call this callback function with the dashboard object (look at scripted_async.js for an example)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// accessable variables in this scope
|
||||
var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, timspan;
|
||||
|
||||
// All url parameters are available via the ARGS object
|
||||
var ARGS;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
timspan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
services : {}
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.services.filter = {
|
||||
time: {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
}
|
||||
};
|
||||
|
||||
var rows = 1;
|
||||
var seriesName = 'argName';
|
||||
|
||||
if(!_.isUndefined(ARGS.rows)) {
|
||||
rows = parseInt(ARGS.rows, 10);
|
||||
}
|
||||
|
||||
if(!_.isUndefined(ARGS.name)) {
|
||||
seriesName = ARGS.name;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rows; i++) {
|
||||
|
||||
dashboard.rows.push({
|
||||
title: 'Chart',
|
||||
height: '300px',
|
||||
panels: [
|
||||
{
|
||||
title: 'Events',
|
||||
type: 'graphite',
|
||||
span: 12,
|
||||
fill: 1,
|
||||
linewidth: 2,
|
||||
targets: [
|
||||
{
|
||||
'target': "randomWalk('" + seriesName + "')"
|
||||
},
|
||||
{
|
||||
'target': "randomWalk('random walk2')"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return dashboard;
|
81
v2/dboards/src/app/dashboards/scripted_async.js
Normal file
81
v2/dboards/src/app/dashboards/scripted_async.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* global _ */
|
||||
|
||||
/*
|
||||
* Complex scripted dashboard
|
||||
* This script generates a dashboard object that Grafana can load. It also takes a number of user
|
||||
* supplied URL parameters (int ARGS variable)
|
||||
*
|
||||
* Global accessable variables
|
||||
* window, document, $, jQuery, ARGS, moment
|
||||
*
|
||||
* Return a dashboard object, or a function
|
||||
*
|
||||
* For async scripts, return a function, this function must take a single callback function,
|
||||
* call this function with the dasboard object
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// accessable variables in this scope
|
||||
var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
|
||||
return function(callback) {
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, timspan;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
timspan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
services : {}
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.services.filter = {
|
||||
time: {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
}
|
||||
};
|
||||
|
||||
var rows = 1;
|
||||
var seriesName = 'argName';
|
||||
|
||||
if(!_.isUndefined(ARGS.rows)) {
|
||||
rows = parseInt(ARGS.rows, 10);
|
||||
}
|
||||
|
||||
if(!_.isUndefined(ARGS.name)) {
|
||||
seriesName = ARGS.name;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: '/'
|
||||
})
|
||||
.done(function(result) {
|
||||
|
||||
dashboard.rows.push({
|
||||
title: 'Chart',
|
||||
height: '300px',
|
||||
panels: [
|
||||
{
|
||||
title: 'Async dashboard test',
|
||||
type: 'text',
|
||||
span: 12,
|
||||
fill: 1,
|
||||
content: '# Async test'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// when dashboard is composed call the callback
|
||||
// function and pass the dashboard
|
||||
callback(dashboard);
|
||||
|
||||
});
|
||||
}
|
100
v2/dboards/src/app/directives/addGraphiteFunc.js
Normal file
100
v2/dboards/src/app/directives/addGraphiteFunc.js
Normal file
@ -0,0 +1,100 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'../services/graphite/gfunc',
|
||||
],
|
||||
function (angular, app, _, $, gfunc) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('graphiteAddFunc', function($compile) {
|
||||
var inputTemplate = '<input type="text"'+
|
||||
' class="grafana-target-segment-input input-medium grafana-target-segment-input"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
var buttonTemplate = '<a class="grafana-target-segment grafana-target-function dropdown-toggle"' +
|
||||
' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown"' +
|
||||
' data-placement="top"><i class="icon-plus"></i></a>';
|
||||
|
||||
return {
|
||||
link: function($scope, elem) {
|
||||
var categories = gfunc.getCategories();
|
||||
var allFunctions = getAllFunctionNames(categories);
|
||||
|
||||
$scope.functionMenu = createFunctionDropDownMenu(categories);
|
||||
|
||||
var $input = $(inputTemplate);
|
||||
var $button = $(buttonTemplate);
|
||||
$input.appendTo(elem);
|
||||
$button.appendTo(elem);
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({
|
||||
source: allFunctions,
|
||||
minLength: 1,
|
||||
items: 10,
|
||||
updater: function (value) {
|
||||
var funcDef = gfunc.getFuncDef(value);
|
||||
|
||||
$scope.$apply(function() {
|
||||
$scope.addFunction(funcDef);
|
||||
});
|
||||
|
||||
$input.trigger('blur');
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
$button.click(function() {
|
||||
$button.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
});
|
||||
|
||||
$input.keyup(function() {
|
||||
elem.toggleClass('open', $input.val() === '');
|
||||
});
|
||||
|
||||
$input.blur(function() {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
// clicking the function dropdown menu wont
|
||||
// work if you remove class at once
|
||||
setTimeout(function() {
|
||||
elem.removeClass('open');
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function getAllFunctionNames(categories) {
|
||||
return _.reduce(categories, function(list, category) {
|
||||
_.each(category, function(func) {
|
||||
list.push(func.name);
|
||||
});
|
||||
return list;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function createFunctionDropDownMenu(categories) {
|
||||
return _.map(categories, function(list, category) {
|
||||
return {
|
||||
text: category,
|
||||
submenu: _.map(list, function(value) {
|
||||
return {
|
||||
text: value.name,
|
||||
click: "addFunction('" + value.name + "')",
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
35
v2/dboards/src/app/directives/addPanel.js
Normal file
35
v2/dboards/src/app/directives/addPanel.js
Normal file
@ -0,0 +1,35 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('addPanel', function($compile) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, elem) {
|
||||
|
||||
$scope.$on("$destroy",function() {
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
$scope.$watch('panel.type', function() {
|
||||
var _type = $scope.panel.type;
|
||||
$scope.reset_panel(_type);
|
||||
if(!_.isUndefined($scope.panel.type)) {
|
||||
$scope.panel.loadingEditor = true;
|
||||
$scope.require(['panels/'+$scope.panel.type.replace(".","/") +'/module'], function () {
|
||||
var template = '<div ng-controller="'+$scope.panel.type+'" ng-include="\'app/partials/paneladd.html\'"></div>';
|
||||
elem.html($compile(angular.element(template))($scope));
|
||||
$scope.panel.loadingEditor = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
20
v2/dboards/src/app/directives/all.js
Normal file
20
v2/dboards/src/app/directives/all.js
Normal file
@ -0,0 +1,20 @@
|
||||
define([
|
||||
'./addPanel',
|
||||
'./arrayJoin',
|
||||
'./dashUpload',
|
||||
'./kibanaPanel',
|
||||
'./kibanaSimplePanel',
|
||||
'./ngBlur',
|
||||
'./ngModelOnBlur',
|
||||
'./tip',
|
||||
'./confirmClick',
|
||||
'./configModal',
|
||||
'./spectrumPicker',
|
||||
'./grafanaGraph',
|
||||
'./bootstrap-tagsinput',
|
||||
'./bodyClass',
|
||||
'./addGraphiteFunc',
|
||||
'./graphiteFuncEditor',
|
||||
'./grafanaVersionCheck',
|
||||
'./influxdbFuncEditor'
|
||||
], function () {});
|
34
v2/dboards/src/app/directives/arrayJoin.js
Normal file
34
v2/dboards/src/app/directives/arrayJoin.js
Normal file
@ -0,0 +1,34 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('arrayJoin', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ngModel) {
|
||||
|
||||
function split_array(text) {
|
||||
return (text || '').split(',');
|
||||
}
|
||||
|
||||
function join_array(text) {
|
||||
if(_.isArray(text)) {
|
||||
return (text || '').join(',');
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
ngModel.$parsers.push(split_array);
|
||||
ngModel.$formatters.push(join_array);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
44
v2/dboards/src/app/directives/bodyClass.js
Normal file
44
v2/dboards/src/app/directives/bodyClass.js
Normal file
@ -0,0 +1,44 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('bodyClass', function() {
|
||||
return {
|
||||
link: function($scope, elem) {
|
||||
|
||||
var lastPulldownVal;
|
||||
var lastHideControlsVal;
|
||||
|
||||
$scope.$watch('dashboard.current.pulldowns', function() {
|
||||
var panel = _.find($scope.dashboard.current.pulldowns, function(pulldown) { return pulldown.enable; });
|
||||
var panelEnabled = panel ? panel.enable : false;
|
||||
if (lastPulldownVal !== panelEnabled) {
|
||||
elem.toggleClass('submenu-controls-visible', panelEnabled);
|
||||
lastPulldownVal = panelEnabled;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.$watch('dashboard.current.hideControls', function() {
|
||||
var hideControls = $scope.dashboard.current.hideControls || $scope.playlist_active;
|
||||
|
||||
if (lastHideControlsVal !== hideControls) {
|
||||
elem.toggleClass('hide-controls', hideControls);
|
||||
lastHideControlsVal = hideControls;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('playlist_active', function() {
|
||||
elem.toggleClass('hide-controls', $scope.playlist_active === true);
|
||||
elem.toggleClass('playlist-active', $scope.playlist_active === true);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
134
v2/dboards/src/app/directives/bootstrap-tagsinput.js
vendored
Normal file
134
v2/dboards/src/app/directives/bootstrap-tagsinput.js
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'bootstrap-tagsinput'
|
||||
],
|
||||
function (angular, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('bootstrapTagsinput', function() {
|
||||
|
||||
function getItemProperty(scope, property) {
|
||||
if (!property) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (angular.isFunction(scope.$parent[property])) {
|
||||
return scope.$parent[property];
|
||||
}
|
||||
|
||||
return function(item) {
|
||||
return item[property];
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
scope: {
|
||||
model: '=ngModel'
|
||||
},
|
||||
template: '<select multiple></select>',
|
||||
replace: false,
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
if (!angular.isArray(scope.model)) {
|
||||
scope.model = [];
|
||||
}
|
||||
|
||||
var select = $('select', element);
|
||||
|
||||
if (attrs.placeholder) {
|
||||
select.attr('placeholder', attrs.placeholder);
|
||||
}
|
||||
|
||||
select.tagsinput({
|
||||
typeahead : {
|
||||
source : angular.isFunction(scope.$parent[attrs.typeaheadSource]) ? scope.$parent[attrs.typeaheadSource] : null
|
||||
},
|
||||
itemValue: getItemProperty(scope, attrs.itemvalue),
|
||||
itemText : getItemProperty(scope, attrs.itemtext),
|
||||
tagClass : angular.isFunction(scope.$parent[attrs.tagclass]) ?
|
||||
scope.$parent[attrs.tagclass] : function() { return attrs.tagclass; }
|
||||
});
|
||||
|
||||
select.on('itemAdded', function(event) {
|
||||
if (scope.model.indexOf(event.item) === -1) {
|
||||
scope.model.push(event.item);
|
||||
}
|
||||
});
|
||||
|
||||
select.on('itemRemoved', function(event) {
|
||||
var idx = scope.model.indexOf(event.item);
|
||||
if (idx !== -1) {
|
||||
scope.model.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch("model", function() {
|
||||
if (!angular.isArray(scope.model)) {
|
||||
scope.model = [];
|
||||
}
|
||||
|
||||
select.tagsinput('removeAll');
|
||||
|
||||
for (var i = 0; i < scope.model.length; i++) {
|
||||
select.tagsinput('add', scope.model[i]);
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('gfDropdown', function ($parse, $compile, $timeout) {
|
||||
|
||||
function buildTemplate(items, placement) {
|
||||
var upclass = placement === 'top' ? 'dropup' : '';
|
||||
var ul = [
|
||||
'<ul class="dropdown-menu ' + upclass + '" role="menu" aria-labelledby="drop1">',
|
||||
'</ul>'
|
||||
];
|
||||
|
||||
angular.forEach(items, function (item, index) {
|
||||
if (item.divider) {
|
||||
return ul.splice(index + 1, 0, '<li class="divider"></li>');
|
||||
}
|
||||
|
||||
var li = '<li' + (item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') + '>' +
|
||||
'<a tabindex="-1" ng-href="' + (item.href || '') + '"' + (item.click ? ' ng-click="' + item.click + '"' : '') +
|
||||
(item.target ? ' target="' + item.target + '"' : '') + (item.method ? ' data-method="' + item.method + '"' : '') +
|
||||
(item.configModal ? ' config-modal="' + item.configModal + '"' : "") +
|
||||
'>' + (item.text || '') + '</a>';
|
||||
|
||||
if (item.submenu && item.submenu.length) {
|
||||
li += buildTemplate(item.submenu).join('\n');
|
||||
}
|
||||
|
||||
li += '</li>';
|
||||
ul.splice(index + 1, 0, li);
|
||||
});
|
||||
return ul;
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
scope: true,
|
||||
link: function postLink(scope, iElement, iAttrs) {
|
||||
var getter = $parse(iAttrs.gfDropdown), items = getter(scope);
|
||||
$timeout(function () {
|
||||
var placement = iElement.data('placement');
|
||||
var dropdown = angular.element(buildTemplate(items, placement).join(''));
|
||||
dropdown.insertAfter(iElement);
|
||||
$compile(iElement.next('ul.dropdown-menu'))(scope);
|
||||
});
|
||||
|
||||
iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
48
v2/dboards/src/app/directives/configModal.js
Normal file
48
v2/dboards/src/app/directives/configModal.js
Normal file
@ -0,0 +1,48 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'jquery'
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('configModal', function($modal, $q, $timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attrs) {
|
||||
var partial = attrs.configModal;
|
||||
var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id;
|
||||
|
||||
elem.bind('click',function() {
|
||||
if ($(id).length) {
|
||||
elem.attr('data-target', id).attr('data-toggle', 'modal');
|
||||
scope.$apply(function() { scope.$broadcast('modal-opened'); });
|
||||
return;
|
||||
}
|
||||
|
||||
var panelModal = $modal({
|
||||
template: partial,
|
||||
persist: true,
|
||||
show: false,
|
||||
scope: scope,
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
$q.when(panelModal).then(function(modalEl) {
|
||||
elem.attr('data-target', id).attr('data-toggle', 'modal');
|
||||
|
||||
$timeout(function () {
|
||||
if (!modalEl.data('modal').isShown) {
|
||||
modalEl.modal('show');
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
|
||||
scope.$apply();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
26
v2/dboards/src/app/directives/confirmClick.js
Normal file
26
v2/dboards/src/app/directives/confirmClick.js
Normal file
@ -0,0 +1,26 @@
|
||||
define([
|
||||
'angular',
|
||||
'kbn'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.directives');
|
||||
|
||||
module.directive('confirmClick', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attrs) {
|
||||
elem.bind('click', function() {
|
||||
var message = attrs.confirmation || "Are you sure you want to do that?";
|
||||
if (window.confirm(message)) {
|
||||
var action = attrs.confirmClick;
|
||||
if (action) {
|
||||
scope.$apply(scope.$eval(action));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
37
v2/dboards/src/app/directives/dashUpload.js
Normal file
37
v2/dboards/src/app/directives/dashUpload.js
Normal file
@ -0,0 +1,37 @@
|
||||
define([
|
||||
'angular'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.directives');
|
||||
|
||||
module.directive('dashUpload', function(timer, dashboard, alertSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope) {
|
||||
function file_selected(evt) {
|
||||
var files = evt.target.files; // FileList object
|
||||
var readerOnload = function() {
|
||||
return function(e) {
|
||||
dashboard.dash_load(JSON.parse(e.target.result));
|
||||
scope.$apply();
|
||||
};
|
||||
};
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = (readerOnload)(f);
|
||||
reader.readAsText(f);
|
||||
}
|
||||
}
|
||||
// Check for the various File API support.
|
||||
if (window.File && window.FileReader && window.FileList && window.Blob) {
|
||||
// Something
|
||||
document.getElementById('dashupload').addEventListener('change', file_selected, false);
|
||||
} else {
|
||||
alertSrv.set('Oops','Sorry, the HTML5 File APIs are not fully supported in this browser.','error');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
404
v2/dboards/src/app/directives/grafanaGraph.js
Executable file
404
v2/dboards/src/app/directives/grafanaGraph.js
Executable file
@ -0,0 +1,404 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'kbn',
|
||||
'moment',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, $, kbn, moment, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.directives');
|
||||
|
||||
module.directive('grafanaGraph', function($rootScope, dashboard) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, plot, annotations;
|
||||
var hiddenData = {};
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
if (scope.otherPanelInFullscreenMode()) { return; }
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function(e, series) {
|
||||
_.each(series, function(serie) {
|
||||
if (hiddenData[serie.alias]) {
|
||||
data.push(hiddenData[serie.alias]);
|
||||
delete hiddenData[serie.alias];
|
||||
}
|
||||
});
|
||||
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(event, renderData) {
|
||||
data = renderData || data;
|
||||
annotations = data.annotations;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
var height = scope.height || scope.panel.height || scope.row.height;
|
||||
if (_.isString(height)) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height = height - 32; // subtract panel title bar
|
||||
|
||||
if (scope.panel.legend.show) {
|
||||
height = height - 21; // subtract one line legend
|
||||
}
|
||||
|
||||
elem.css('height', height + 'px');
|
||||
|
||||
return true;
|
||||
} catch(e) { // IE throws errors sometimes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldAbortRender() {
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($rootScope.fullscreen && !scope.fullscreen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!setElementHeight()) { return true; }
|
||||
|
||||
if (_.isString(data)) {
|
||||
render_panel_as_graphite_png(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (shouldAbortRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = scope.panel;
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
});
|
||||
if (dataSeries) {
|
||||
hiddenData[dataSeries.info.alias] = dataSeries;
|
||||
data = _.without(data, dataSeries);
|
||||
}
|
||||
});
|
||||
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
stack: panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: panel.fill === 0 ? 0.001 : panel.fill/10,
|
||||
lineWidth: panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: panel.bars,
|
||||
fill: 1,
|
||||
barWidth: 1,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {},
|
||||
grid: {
|
||||
markings: [],
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8'
|
||||
},
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats);
|
||||
data[i].data = _d;
|
||||
}
|
||||
|
||||
if (panel.bars && data.length && data[0].info.timeStep) {
|
||||
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
|
||||
}
|
||||
|
||||
addTimeAxis(options);
|
||||
addGridThresholds(options, panel);
|
||||
addAnnotations(options);
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
plot = $.plot(elem, data, options);
|
||||
|
||||
addAxisLabels();
|
||||
}
|
||||
|
||||
function addTimeAxis(options) {
|
||||
var ticks = elem.width() / 100;
|
||||
var min = _.isUndefined(scope.range.from) ? null : scope.range.from.getTime();
|
||||
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.getTime();
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.current.timezone,
|
||||
show: scope.panel['x-axis'],
|
||||
mode: "time",
|
||||
min: min,
|
||||
max: max,
|
||||
label: "Datetime",
|
||||
ticks: ticks,
|
||||
timeformat: time_format(scope.interval, ticks, min, max),
|
||||
};
|
||||
}
|
||||
|
||||
function addGridThresholds(options, panel) {
|
||||
if (panel.grid.threshold1) {
|
||||
var limit1 = panel.grid.thresholdLine ? panel.grid.threshold1 : (panel.grid.threshold2 || null);
|
||||
options.grid.markings.push({
|
||||
yaxis: { from: panel.grid.threshold1, to: limit1 },
|
||||
color: panel.grid.threshold1Color
|
||||
});
|
||||
|
||||
if (panel.grid.threshold2) {
|
||||
var limit2;
|
||||
if (panel.grid.thresholdLine) {
|
||||
limit2 = panel.grid.threshold2;
|
||||
} else {
|
||||
limit2 = panel.grid.threshold1 > panel.grid.threshold2 ? -Infinity : +Infinity;
|
||||
}
|
||||
options.grid.markings.push({
|
||||
yaxis: { from: panel.grid.threshold2, to: limit2 },
|
||||
color: panel.grid.threshold2Color
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addAnnotations(options) {
|
||||
if(!annotations || annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var types = {};
|
||||
|
||||
_.each(annotations, function(event) {
|
||||
if (!types[event.annotation.name]) {
|
||||
types[event.annotation.name] = {
|
||||
level: _.keys(types).length + 1,
|
||||
icon: {
|
||||
icon: "icon-chevron-down",
|
||||
size: event.annotation.iconSize,
|
||||
color: event.annotation.iconColor,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (event.annotation.showLine) {
|
||||
options.grid.markings.push({
|
||||
color: event.annotation.lineColor,
|
||||
lineWidth: 1,
|
||||
xaxis: { from: event.min, to: event.max }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
options.events = {
|
||||
levels: _.keys(types).length + 1,
|
||||
data: annotations,
|
||||
types: types
|
||||
};
|
||||
}
|
||||
|
||||
function addAxisLabels() {
|
||||
if (scope.panel.leftYAxisLabel) {
|
||||
elem.css('margin-left', '10px');
|
||||
var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
|
||||
.text(scope.panel.leftYAxisLabel)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
|
||||
} else if (elem.css('margin-left')) {
|
||||
elem.css('margin-left', '');
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options) {
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: scope.panel['y-axis'],
|
||||
min: scope.panel.grid.leftMin,
|
||||
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.findWhere(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.position = 'right';
|
||||
secondY.min = scope.panel.grid.rightMin;
|
||||
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
|
||||
options.yaxes.push(secondY);
|
||||
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
|
||||
}
|
||||
|
||||
configureAxisMode(options.yaxes[0], scope.panel.y_formats[0]);
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format !== 'none') {
|
||||
axis.tickFormatter = kbn.getFormatFunction(format, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function time_format(interval, ticks, min, max) {
|
||||
if (min && max && ticks) {
|
||||
var secPerTick = ((max - min) / ticks) / 1000;
|
||||
|
||||
if (secPerTick <= 45) {
|
||||
return "%H:%M:%S";
|
||||
}
|
||||
if (secPerTick <= 3600) {
|
||||
return "%H:%M";
|
||||
}
|
||||
if (secPerTick <= 80000) {
|
||||
return "%m/%d %H:%M";
|
||||
}
|
||||
if (secPerTick <= 2419200) {
|
||||
return "%m/%d";
|
||||
}
|
||||
return "%Y-%m";
|
||||
}
|
||||
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp, seriesInfo, format;
|
||||
|
||||
if (item) {
|
||||
seriesInfo = item.series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
(decodeURIComponent(seriesInfo.alias)) +
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = kbn.getFormatFunction(format, 2)(value);
|
||||
|
||||
timestamp = dashboard.current.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
$tooltip
|
||||
.html(
|
||||
group + value + " @ " + timestamp
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
url += '&bgcolor=1f1f1f'; // @grayDarker & @kibanaPanelBackground
|
||||
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
|
||||
url += scope.panel.stack ? '&areaMode=stacked' : '';
|
||||
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
|
||||
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
|
||||
url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
|
||||
url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : '';
|
||||
url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : '';
|
||||
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
|
||||
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
|
||||
|
||||
switch(scope.panel.y_formats[0]) {
|
||||
case 'bytes':
|
||||
url += '&yUnitSystem=binary';
|
||||
break;
|
||||
case 'bits':
|
||||
url += '&yUnitSystem=binary';
|
||||
break;
|
||||
case 'short':
|
||||
url += '&yUnitSystem=si';
|
||||
break;
|
||||
case 'none':
|
||||
url += '&yUnitSystem=none';
|
||||
break;
|
||||
}
|
||||
|
||||
switch(scope.panel.nullPointMode) {
|
||||
case 'connected':
|
||||
url += '&lineMode=connected';
|
||||
break;
|
||||
case 'null':
|
||||
break; // graphite default lineMode
|
||||
case 'null as zero':
|
||||
url += "&drawNullAsZero=true";
|
||||
break;
|
||||
}
|
||||
|
||||
url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
|
||||
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
scope.filter.setTime({
|
||||
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
33
v2/dboards/src/app/directives/grafanaVersionCheck.js
Normal file
33
v2/dboards/src/app/directives/grafanaVersionCheck.js
Normal file
@ -0,0 +1,33 @@
|
||||
define([
|
||||
'angular'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('grafanaVersionCheck', function($http, grafanaVersion) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
if (grafanaVersion[0] === '@') {
|
||||
return;
|
||||
}
|
||||
|
||||
$http({ method: 'GET', url: 'http://grafanarel.s3.amazonaws.com/latest.json' })
|
||||
.then(function(response) {
|
||||
if (!response.data || !response.data.version) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (grafanaVersion !== response.data.version) {
|
||||
elem.append('<i class="icon-info-sign"></i> ' +
|
||||
'<a href="http://grafana.org/download" target="_blank"> ' +
|
||||
'New version available: ' + response.data.version +
|
||||
'</a>');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
242
v2/dboards/src/app/directives/graphiteFuncEditor.js
Normal file
242
v2/dboards/src/app/directives/graphiteFuncEditor.js
Normal file
@ -0,0 +1,242 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('graphiteFuncEditor', function($compile) {
|
||||
|
||||
var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
|
||||
var paramTemplate = '<input type="text" style="display:none"' +
|
||||
' class="input-mini grafana-function-param-input"></input>';
|
||||
|
||||
var funcControlsTemplate =
|
||||
'<div class="graphite-func-controls">' +
|
||||
'<span class="pointer icon-arrow-left"></span>' +
|
||||
'<span class="pointer icon-info-sign"></span>' +
|
||||
'<span class="pointer icon-remove" ></span>' +
|
||||
'<span class="pointer icon-arrow-right"></span>' +
|
||||
'</div>';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function postLink($scope, elem) {
|
||||
var $funcLink = $(funcSpanTemplate);
|
||||
var $funcControls = $(funcControlsTemplate);
|
||||
var func = $scope.func;
|
||||
var funcDef = func.def;
|
||||
var scheduledRelink = false;
|
||||
var paramCountAtLink = 0;
|
||||
|
||||
function clickFuncParam(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
|
||||
var $link = $(this);
|
||||
var $input = $link.next();
|
||||
|
||||
$input.val(func.params[paramIndex]);
|
||||
$input.css('width', ($link.width() + 16) + 'px');
|
||||
|
||||
$link.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
$input.select();
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
if (typeahead) {
|
||||
$input.val('');
|
||||
typeahead.lookup();
|
||||
}
|
||||
}
|
||||
|
||||
function scheduledRelinkIfNeeded() {
|
||||
if (paramCountAtLink === func.params.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scheduledRelink) {
|
||||
scheduledRelink = true;
|
||||
setTimeout(function() {
|
||||
relink();
|
||||
scheduledRelink = false;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function inputBlur(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
|
||||
var $input = $(this);
|
||||
var $link = $input.prev();
|
||||
|
||||
if ($input.val() !== '' || func.def.params[paramIndex].optional) {
|
||||
$link.text($input.val());
|
||||
|
||||
func.updateParam($input.val(), paramIndex);
|
||||
scheduledRelinkIfNeeded();
|
||||
|
||||
$scope.$apply($scope.targetChanged);
|
||||
}
|
||||
|
||||
$input.hide();
|
||||
$link.show();
|
||||
}
|
||||
|
||||
function inputKeyPress(paramIndex, e) {
|
||||
/*jshint validthis:true */
|
||||
|
||||
if(e.which === 13) {
|
||||
inputBlur.call(this, paramIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function inputKeyDown() {
|
||||
/*jshint validthis:true */
|
||||
this.style.width = (3 + this.value.length) * 8 + 'px';
|
||||
}
|
||||
|
||||
function addTypeahead($input, paramIndex) {
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
|
||||
var options = funcDef.params[paramIndex].options;
|
||||
if (funcDef.params[paramIndex].type === 'int') {
|
||||
options = _.map(options, function(val) { return val.toString(); });
|
||||
}
|
||||
|
||||
$input.typeahead({
|
||||
source: options,
|
||||
minLength: 0,
|
||||
items: 20,
|
||||
updater: function (value) {
|
||||
setTimeout(function() {
|
||||
inputBlur.call($input[0], paramIndex);
|
||||
}, 0);
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
this.query = this.$element.val() || '';
|
||||
return this.process(this.source);
|
||||
};
|
||||
}
|
||||
|
||||
function toggleFuncControls() {
|
||||
var targetDiv = elem.closest('.grafana-target-inner');
|
||||
|
||||
if (elem.hasClass('show-function-controls')) {
|
||||
elem.removeClass('show-function-controls');
|
||||
targetDiv.removeClass('has-open-function');
|
||||
$funcControls.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
elem.addClass('show-function-controls');
|
||||
targetDiv.addClass('has-open-function');
|
||||
|
||||
$funcControls.show();
|
||||
}
|
||||
|
||||
function addElementsAndCompile() {
|
||||
$funcControls.appendTo(elem);
|
||||
$funcLink.appendTo(elem);
|
||||
|
||||
_.each(funcDef.params, function(param, index) {
|
||||
if (param.optional && !func.params[index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
$('<span>, </span>').appendTo(elem);
|
||||
}
|
||||
|
||||
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
|
||||
var $input = $(paramTemplate);
|
||||
|
||||
paramCountAtLink++;
|
||||
|
||||
$paramLink.appendTo(elem);
|
||||
$input.appendTo(elem);
|
||||
|
||||
$input.blur(_.partial(inputBlur, index));
|
||||
$input.keyup(inputKeyDown);
|
||||
$input.keypress(_.partial(inputKeyPress, index));
|
||||
$paramLink.click(_.partial(clickFuncParam, index));
|
||||
|
||||
if (funcDef.params[index].options) {
|
||||
addTypeahead($input, index);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$('<span>)</span>').appendTo(elem);
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
|
||||
function ifJustAddedFocusFistParam() {
|
||||
if ($scope.func.added) {
|
||||
$scope.func.added = false;
|
||||
setTimeout(function() {
|
||||
elem.find('.graphite-func-param-link').first().click();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
function registerFuncControlsToggle() {
|
||||
$funcLink.click(toggleFuncControls);
|
||||
}
|
||||
|
||||
function registerFuncControlsActions() {
|
||||
$funcControls.click(function(e) {
|
||||
var $target = $(e.target);
|
||||
if ($target.hasClass('icon-remove')) {
|
||||
toggleFuncControls();
|
||||
$scope.$apply(function() {
|
||||
$scope.removeFunction($scope.func);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ($target.hasClass('icon-arrow-left')) {
|
||||
$scope.$apply(function() {
|
||||
_.move($scope.functions, $scope.$index, $scope.$index - 1);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ($target.hasClass('icon-arrow-right')) {
|
||||
$scope.$apply(function() {
|
||||
_.move($scope.functions, $scope.$index, $scope.$index + 1);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ($target.hasClass('icon-info-sign')) {
|
||||
window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank');
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function relink() {
|
||||
elem.children().remove();
|
||||
|
||||
addElementsAndCompile();
|
||||
ifJustAddedFocusFistParam();
|
||||
registerFuncControlsToggle();
|
||||
registerFuncControlsActions();
|
||||
}
|
||||
|
||||
relink();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
136
v2/dboards/src/app/directives/influxdbFuncEditor.js
Normal file
136
v2/dboards/src/app/directives/influxdbFuncEditor.js
Normal file
@ -0,0 +1,136 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('influxdbFuncEditor', function($compile) {
|
||||
|
||||
var funcSpanTemplate = '<a gf-dropdown="functionMenu" class="dropdown-toggle" ' +
|
||||
'data-toggle="dropdown">{{target.function}}</a><span>(</span>';
|
||||
|
||||
var paramTemplate = '<input type="text" style="display:none"' +
|
||||
' class="input-mini grafana-function-param-input"></input>';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function postLink($scope, elem) {
|
||||
var $funcLink = $(funcSpanTemplate);
|
||||
|
||||
$scope.functionMenu = _.map($scope.functions, function(func) {
|
||||
return {
|
||||
text: func,
|
||||
click: "changeFunction('" + func + "');"
|
||||
};
|
||||
});
|
||||
|
||||
function clickFuncParam() {
|
||||
/*jshint validthis:true */
|
||||
|
||||
var $link = $(this);
|
||||
var $input = $link.next();
|
||||
|
||||
$input.val($scope.target.column);
|
||||
$input.css('width', ($link.width() + 16) + 'px');
|
||||
|
||||
$link.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
$input.select();
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
if (typeahead) {
|
||||
$input.val('');
|
||||
typeahead.lookup();
|
||||
}
|
||||
}
|
||||
|
||||
function inputBlur() {
|
||||
/*jshint validthis:true */
|
||||
|
||||
var $input = $(this);
|
||||
var $link = $input.prev();
|
||||
|
||||
if ($input.val() !== '') {
|
||||
$link.text($input.val());
|
||||
|
||||
$scope.target.column = $input.val();
|
||||
$scope.$apply($scope.get_data);
|
||||
}
|
||||
|
||||
$input.hide();
|
||||
$link.show();
|
||||
}
|
||||
|
||||
function inputKeyPress(e) {
|
||||
/*jshint validthis:true */
|
||||
|
||||
if(e.which === 13) {
|
||||
inputBlur.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
function inputKeyDown() {
|
||||
/*jshint validthis:true */
|
||||
this.style.width = (3 + this.value.length) * 8 + 'px';
|
||||
}
|
||||
|
||||
function addTypeahead($input) {
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
|
||||
$input.typeahead({
|
||||
source: function () {
|
||||
return $scope.listColumns.apply(null, arguments);
|
||||
},
|
||||
minLength: 0,
|
||||
items: 20,
|
||||
updater: function (value) {
|
||||
setTimeout(function() {
|
||||
inputBlur.call($input[0]);
|
||||
}, 0);
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
var items;
|
||||
this.query = this.$element.val() || '';
|
||||
items = this.source(this.query, $.proxy(this.process, this));
|
||||
return items ? this.process(items) : items;
|
||||
};
|
||||
}
|
||||
|
||||
function addElementsAndCompile() {
|
||||
$funcLink.appendTo(elem);
|
||||
|
||||
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + $scope.target.column + '</a>');
|
||||
var $input = $(paramTemplate);
|
||||
|
||||
$paramLink.appendTo(elem);
|
||||
$input.appendTo(elem);
|
||||
|
||||
$input.blur(inputBlur);
|
||||
$input.keyup(inputKeyDown);
|
||||
$input.keypress(inputKeyPress);
|
||||
$paramLink.click(clickFuncParam);
|
||||
|
||||
addTypeahead($input);
|
||||
|
||||
$('<span>)</span>').appendTo(elem);
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
|
||||
addElementsAndCompile();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
118
v2/dboards/src/app/directives/kibanaPanel.js
Normal file
118
v2/dboards/src/app/directives/kibanaPanel.js
Normal file
@ -0,0 +1,118 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'../controllers/panelBaseCtrl'
|
||||
],
|
||||
function (angular, $, _, PanelBaseCtrl) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('kibanaPanel', function($compile, $timeout, $rootScope, $injector) {
|
||||
|
||||
var container = '<div class="panel-container"></div>';
|
||||
var content = '<div class="panel-content"></div>';
|
||||
|
||||
var panelHeader =
|
||||
'<div class="panel-header">'+
|
||||
'<div class="row-fluid">' +
|
||||
'<div class="span12 alert-error panel-error small" ng-show="panel.error">' +
|
||||
'<a class="close" ng-click="panel.error=false">×</a>' +
|
||||
'<span><i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}} </span>' +
|
||||
'<span class="pointer panel-error-inspector-link" config-modal="app/partials/inspector.html">View details</span>' +
|
||||
'</div>' +
|
||||
'</div>\n' +
|
||||
|
||||
'<div class="row-fluid panel-extra">' +
|
||||
'<div class="panel-extra-container">' +
|
||||
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading == true">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>' +
|
||||
|
||||
'<span class="dropdown">' +
|
||||
'<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
|
||||
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
|
||||
' jqyoui-draggable="'+
|
||||
'{'+
|
||||
'animate:false,'+
|
||||
'mutate:false,'+
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="row.panels" ' +
|
||||
'>' +
|
||||
'{{panel.title || "No title"}}' +
|
||||
'</span>' +
|
||||
'</span>'+
|
||||
|
||||
'</div>'+
|
||||
'</div>\n'+
|
||||
'</div>';
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, elem, attr) {
|
||||
// once we have the template, scan it for controllers and
|
||||
// load the module.js if we have any
|
||||
var newScope = $scope.$new();
|
||||
|
||||
$scope.kbnJqUiDraggableOptions = {
|
||||
revert: 'invalid',
|
||||
helper: function() {
|
||||
return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
|
||||
},
|
||||
placeholder: 'keep'
|
||||
};
|
||||
|
||||
// compile the module and uncloack. We're done
|
||||
function loadModule($module) {
|
||||
$module.appendTo(elem);
|
||||
elem.wrap(container);
|
||||
/* jshint indent:false */
|
||||
$compile(elem.contents())(newScope);
|
||||
elem.removeClass("ng-cloak");
|
||||
}
|
||||
|
||||
newScope.$on('$destroy',function() {
|
||||
elem.unbind();
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
newScope.initBaseController = function(self, scope) {
|
||||
$injector.invoke(PanelBaseCtrl, self, { $scope: scope });
|
||||
};
|
||||
|
||||
$scope.$watch(attr.type, function (name) {
|
||||
elem.addClass("ng-cloak");
|
||||
// load the panels module file, then render it in the dom.
|
||||
var nameAsPath = name.replace(".", "/");
|
||||
$scope.require([
|
||||
'jquery',
|
||||
'text!panels/'+nameAsPath+'/module.html'
|
||||
], function ($, moduleTemplate) {
|
||||
var $module = $(moduleTemplate);
|
||||
// top level controllers
|
||||
var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller');
|
||||
// add child controllers
|
||||
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
|
||||
|
||||
if ($controllers.length) {
|
||||
$controllers.first().prepend(panelHeader);
|
||||
$controllers.first().find('.panel-header').nextAll().wrapAll(content);
|
||||
|
||||
$scope.require(['panels/' + nameAsPath + '/module'], function() {
|
||||
loadModule($module);
|
||||
});
|
||||
} else {
|
||||
loadModule($module);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
77
v2/dboards/src/app/directives/kibanaSimplePanel.js
Normal file
77
v2/dboards/src/app/directives/kibanaSimplePanel.js
Normal file
@ -0,0 +1,77 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('kibanaSimplePanel', function($compile) {
|
||||
var panelLoading = '<span ng-show="panelMeta.loading == true">' +
|
||||
'<span style="font-size:72px;font-weight:200">'+
|
||||
'<i class="icon-spinner icon-spin"></i> loading ...' +
|
||||
'</span>'+
|
||||
'</span>';
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, elem, attr) {
|
||||
|
||||
// once we have the template, scan it for controllers and
|
||||
// load the module.js if we have any
|
||||
|
||||
// compile the module and uncloack. We're done
|
||||
function loadModule($module) {
|
||||
$module.appendTo(elem);
|
||||
/* jshint indent:false */
|
||||
$compile(elem.contents())($scope);
|
||||
elem.removeClass("ng-cloak");
|
||||
}
|
||||
|
||||
function loadController(name) {
|
||||
elem.addClass("ng-cloak");
|
||||
// load the panels module file, then render it in the dom.
|
||||
var nameAsPath = name.replace(".", "/");
|
||||
$scope.require([
|
||||
'jquery',
|
||||
'text!panels/'+nameAsPath+'/module.html'
|
||||
], function ($, moduleTemplate) {
|
||||
var $module = $(moduleTemplate);
|
||||
// top level controllers
|
||||
var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller');
|
||||
// add child controllers
|
||||
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
|
||||
|
||||
if ($controllers.length) {
|
||||
$controllers.first().prepend(panelLoading);
|
||||
$scope.require([
|
||||
'panels/'+nameAsPath+'/module'
|
||||
], function() {
|
||||
loadModule($module);
|
||||
});
|
||||
} else {
|
||||
loadModule($module);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$watch(attr.type, function (name) {
|
||||
loadController(name);
|
||||
});
|
||||
|
||||
if(attr.panel) {
|
||||
$scope.$watch(attr.panel, function (panel) {
|
||||
// If the panel attribute is specified, create a new scope. This ruins configuration
|
||||
// so don't do it with anything that needs to use editor.html
|
||||
if(!_.isUndefined(panel)) {
|
||||
$scope = $scope.$new();
|
||||
$scope.panel = angular.fromJson(panel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
20
v2/dboards/src/app/directives/ngBlur.js
Normal file
20
v2/dboards/src/app/directives/ngBlur.js
Normal file
@ -0,0 +1,20 @@
|
||||
define([
|
||||
'angular'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('ngBlur', ['$parse', function($parse) {
|
||||
return function(scope, element, attr) {
|
||||
var fn = $parse(attr['ngBlur']);
|
||||
element.bind('blur', function(event) {
|
||||
scope.$apply(function() {
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
});
|
25
v2/dboards/src/app/directives/ngModelOnBlur.js
Normal file
25
v2/dboards/src/app/directives/ngModelOnBlur.js
Normal file
@ -0,0 +1,25 @@
|
||||
define(['angular'],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('ngModelOnblur', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, elm, attr, ngModelCtrl) {
|
||||
if (attr.type === 'radio' || attr.type === 'checkbox') {
|
||||
return;
|
||||
}
|
||||
|
||||
elm.unbind('input').unbind('keydown').unbind('change');
|
||||
elm.bind('blur', function() {
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(elm.val());
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
38
v2/dboards/src/app/directives/spectrumPicker.js
Normal file
38
v2/dboards/src/app/directives/spectrumPicker.js
Normal file
@ -0,0 +1,38 @@
|
||||
define([
|
||||
'angular',
|
||||
'spectrum'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('spectrumPicker', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: 'ngModel',
|
||||
scope: false,
|
||||
replace: true,
|
||||
template: "<span><input class='input-small' /></span>",
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
var input = element.find('input');
|
||||
var options = angular.extend({
|
||||
showAlpha: true,
|
||||
showButtons: false,
|
||||
color: ngModel.$viewValue,
|
||||
change: function(color) {
|
||||
scope.$apply(function() {
|
||||
ngModel.$setViewValue(color.toRgbString());
|
||||
});
|
||||
}
|
||||
}, scope.$eval(attrs.options));
|
||||
|
||||
ngModel.$render = function() {
|
||||
input.spectrum('set', ngModel.$viewValue || '');
|
||||
};
|
||||
|
||||
input.spectrum(options);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
20
v2/dboards/src/app/directives/tip.js
Normal file
20
v2/dboards/src/app/directives/tip.js
Normal file
@ -0,0 +1,20 @@
|
||||
define([
|
||||
'angular',
|
||||
'kbn'
|
||||
],
|
||||
function (angular, kbn) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('tip', function($compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attrs) {
|
||||
var _t = '<i class="icon-'+(attrs.icon||'question-sign')+'" bs-tooltip="\''+
|
||||
kbn.addslashes(elem.text())+'\'"></i>';
|
||||
elem.replaceWith($compile(angular.element(_t))(scope));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
117
v2/dboards/src/app/filters/all.js
Executable file
117
v2/dboards/src/app/filters/all.js
Executable file
@ -0,0 +1,117 @@
|
||||
define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.filters');
|
||||
|
||||
module.filter('stringSort', function() {
|
||||
return function(input) {
|
||||
return input.sort();
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
Filter an array of objects by elasticsearch version requirements
|
||||
*/
|
||||
module.filter('esVersion', function(esVersion) {
|
||||
return function(items, require) {
|
||||
var ret = _.filter(items,function(qt) {
|
||||
return esVersion.is(qt[require]) ? true : false;
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('slice', function() {
|
||||
return function(arr, start, end) {
|
||||
if(!_.isUndefined(arr)) {
|
||||
return arr.slice(start, end);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('stringify', function() {
|
||||
return function(arr) {
|
||||
if(_.isObject(arr) && !_.isArray(arr)) {
|
||||
return angular.toJson(arr);
|
||||
} else {
|
||||
return _.isNull(arr) ? null : arr.toString();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('moment', function() {
|
||||
return function(date,mode) {
|
||||
switch(mode) {
|
||||
case 'ago':
|
||||
return moment(date).fromNow();
|
||||
}
|
||||
return moment(date).fromNow();
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('noXml', function() {
|
||||
var noXml = function(text) {
|
||||
return _.isString(text)
|
||||
? text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/"/g, '"')
|
||||
: text;
|
||||
};
|
||||
return function(text) {
|
||||
return _.isArray(text)
|
||||
? _.map(text, noXml)
|
||||
: noXml(text);
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('urlLink', function() {
|
||||
var //URLs starting with http://, https://, or ftp://
|
||||
r1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim,
|
||||
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
|
||||
r2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim,
|
||||
//Change email addresses to mailto:: links.
|
||||
r3 = /(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim;
|
||||
|
||||
var urlLink = function(text) {
|
||||
var t1,t2,t3;
|
||||
if(!_.isString(text)) {
|
||||
return text;
|
||||
} else {
|
||||
_.each(text.match(r1), function() {
|
||||
t1 = text.replace(r1, "<a href=\"$1\" target=\"_blank\">$1</a>");
|
||||
});
|
||||
text = t1 || text;
|
||||
_.each(text.match(r2), function() {
|
||||
t2 = text.replace(r2, "$1<a href=\"http://$2\" target=\"_blank\">$2</a>");
|
||||
});
|
||||
text = t2 || text;
|
||||
_.each(text.match(r3), function() {
|
||||
t3 = text.replace(r3, "<a href=\"mailto:$1\">$1</a>");
|
||||
});
|
||||
text = t3 || text;
|
||||
return text;
|
||||
}
|
||||
};
|
||||
return function(text) {
|
||||
return _.isArray(text)
|
||||
? _.map(text, urlLink)
|
||||
: urlLink(text);
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('gistid', function() {
|
||||
var gist_pattern = /(\d{5,})|([a-z0-9]{10,})|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
|
||||
return function(input) {
|
||||
if(!(_.isUndefined(input))) {
|
||||
var output = input.match(gist_pattern);
|
||||
if(!_.isNull(output) && !_.isUndefined(output)) {
|
||||
return output[0].replace(/.*\//, '');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
76
v2/dboards/src/app/panels/annotations/editor.html
Normal file
76
v2/dboards/src/app/panels/annotations/editor.html
Normal file
@ -0,0 +1,76 @@
|
||||
<div bindonce class="modal-body">
|
||||
<div class="pull-right editor-title">Annotations</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<table class="table table-striped annotation-editor-table" style="width: 700px">
|
||||
<thead>
|
||||
<th width="90%">Name</th>
|
||||
<th width="1%"></th>
|
||||
<th width="1%"></th>
|
||||
<th width="1%"></th>
|
||||
</thead>
|
||||
<tr ng-repeat="annotation in panel.annotations">
|
||||
<td>
|
||||
<a ng-click="edit(annotation)" bs-tooltip="'Click to edit'">
|
||||
<i class="icon-cog"></i>
|
||||
{{annotation.name}}
|
||||
</a>
|
||||
</td>
|
||||
<td><i ng-click="_.move(panel.annotations,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
|
||||
<td><i ng-click="_.move(panel.annotations,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
|
||||
<td><i ng-click="panel.annotations = _.without(panel.annotations, annotation)" class="pointer icon-remove"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<h4 ng-show="currentIsNew">Add Annotation</h4>
|
||||
<h4 ng-show="!currentIsNew">Edit Annotation</h4>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Name</label>
|
||||
<input type="text" class="input-medium" ng-model='currentAnnnotation.name' placeholder="name"></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Type</label>
|
||||
<select ng-model="currentAnnnotation.type" ng-options="f for f in ['graphite metric', 'graphite events']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Icon color</label>
|
||||
<spectrum-picker ng-model="currentAnnnotation.iconColor"></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Icon size</label>
|
||||
<select class="input-mini" ng-model="currentAnnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Grid line</label>
|
||||
<input type="checkbox" ng-model="currentAnnnotation.showLine" ng-checked="currentAnnnotation.showLine">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line color</label>
|
||||
<spectrum-picker ng-model="currentAnnnotation.lineColor"></spectrum-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite metric'">
|
||||
<div class="editor-option">
|
||||
<label class="small">Graphite target expression</label>
|
||||
<input type="text" class="span10" ng-model='currentAnnnotation.target' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite events'">
|
||||
<div class="editor-option">
|
||||
<label class="small">Graphite event tags</label>
|
||||
<input type="text" ng-model='currentAnnnotation.tags' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button ng-show="currentIsNew" type="button" class="btn btn-success" ng-click="add()">Add annotation</button>
|
||||
<button ng-show="!currentIsNew" type="button" class="btn btn-success" ng-click="update()">Update</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="close_edit();dismiss();dashboard.refresh();">Close</button>
|
||||
</div>
|
12
v2/dboards/src/app/panels/annotations/module.html
Normal file
12
v2/dboards/src/app/panels/annotations/module.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div ng-controller='AnnotationsCtrl' ng-init="init()">
|
||||
|
||||
<div class="submenu-toggle" ng-repeat="annotation in panel.annotations" ng-class="{'annotation-disabled': !annotation.enable }">
|
||||
<i class="annotation-color-icon icon-minus"></i>
|
||||
<a ng-click="hide(annotation)" class="small">{{annotation.name}}</a>
|
||||
</div>
|
||||
|
||||
<div class="submenu-control-edit">
|
||||
<i class="icon-cog pointer" config-modal="app/panels/annotations/editor.html" bs-tooltip="'Edit annotations'" ></i>
|
||||
</div>
|
||||
|
||||
</div>
|
67
v2/dboards/src/app/panels/annotations/module.js
Normal file
67
v2/dboards/src/app/panels/annotations/module.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
|
||||
## annotations
|
||||
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.annotations', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('AnnotationsCtrl', function($scope, dashboard, $rootScope) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
description : "Annotations"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
annotations: []
|
||||
};
|
||||
|
||||
var annotationDefaults = {
|
||||
name: '',
|
||||
type: 'graphite metric',
|
||||
showLine: true,
|
||||
iconColor: '#C0C6BE',
|
||||
lineColor: 'rgba(255, 96, 96, 0.592157)',
|
||||
iconSize: 13,
|
||||
enable: true
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.currentAnnnotation = angular.copy(annotationDefaults);
|
||||
$scope.currentIsNew = true;
|
||||
};
|
||||
|
||||
$scope.edit = function(annotation) {
|
||||
$scope.currentAnnnotation = annotation;
|
||||
$scope.currentIsNew = false;
|
||||
};
|
||||
|
||||
$scope.update = function() {
|
||||
$scope.currentAnnnotation = angular.copy(annotationDefaults);
|
||||
$scope.currentIsNew = true;
|
||||
};
|
||||
|
||||
$scope.add = function() {
|
||||
$scope.panel.annotations.push($scope.currentAnnnotation);
|
||||
$scope.currentAnnnotation = angular.copy(annotationDefaults);
|
||||
};
|
||||
|
||||
$scope.hide = function (annotation) {
|
||||
annotation.enable = !annotation.enable;
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
|
||||
});
|
||||
});
|
50
v2/dboards/src/app/panels/filtering/module.html
Executable file
50
v2/dboards/src/app/panels/filtering/module.html
Executable file
@ -0,0 +1,50 @@
|
||||
<div ng-controller='filtering' ng-init="init()">
|
||||
|
||||
<div class='filtering-container'>
|
||||
|
||||
<div ng-repeat="filter in filter.templateParameters" class="small filter-panel-filter">
|
||||
<div>
|
||||
<i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(filter)"></i>
|
||||
<i class="filter-action pointer icon-edit" ng-hide="filter.editing" bs-tooltip="'Edit'" ng-click="filter.editing = true"></i>
|
||||
</div>
|
||||
|
||||
<div ng-hide="filter.editing" style="margin-right: 45px;">
|
||||
<ul class="unstyled">
|
||||
<li ng-if="filter.name" class="dropdown">
|
||||
{{filter.name}} :
|
||||
<a class="dropdown-toggle" data-toggle="dropdown">
|
||||
{{filter.current.text}}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="option in filter.options">
|
||||
<a ng-click="filterOptionSelected(filter, option)">{{option.text}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form ng-show="filter.editing">
|
||||
<ul class="unstyled">
|
||||
<li>
|
||||
<strong>name</strong>:<br/>
|
||||
<input type='text' ng-model="filter.name">
|
||||
</li>
|
||||
<li>
|
||||
<strong>filter.query</strong>:<br/>
|
||||
<input type='text' ng-model="filter.query">
|
||||
</li>
|
||||
<li>
|
||||
<label for="includeAll">Include all:</label>
|
||||
<input id="includeAll" type='checkbox' ng-model="filter.includeAll">
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<input type="submit" value="Update" ng-click="applyFilter(filter)" class="filter-apply btn btn-success btn-mini" bs-tooltip="'Update and refresh'"/>
|
||||
<button ng-click="filter.editing=undefined" class="filter-apply btn btn-mini" bs-tooltip="'Save without refresh'">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<i class="pointer icon-plus-sign add-filter-action" ng-click="add()" bs-tooltip="'Add metric filter / param'" data-placement="right"></i>
|
||||
</div>
|
||||
</div>
|
104
v2/dboards/src/app/panels/filtering/module.js
Normal file
104
v2/dboards/src/app/panels/filtering/module.js
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
|
||||
## filtering
|
||||
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.filtering', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('filtering', function($scope, datasourceSrv, $rootScope, $timeout, $q) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
description : "graphite target filters"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
// empty. Don't know if I need the function then.
|
||||
};
|
||||
|
||||
$scope.remove = function(templateParameter) {
|
||||
$scope.filter.removeTemplateParameter(templateParameter);
|
||||
};
|
||||
|
||||
$scope.filterOptionSelected = function(templateParameter, option, recursive) {
|
||||
templateParameter.current = option;
|
||||
|
||||
$scope.filter.updateTemplateData();
|
||||
|
||||
return $scope.applyFilterToOtherFilters(templateParameter)
|
||||
.then(function() {
|
||||
// only refresh in the outermost call
|
||||
if (!recursive) {
|
||||
$scope.dashboard.refresh();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.applyFilterToOtherFilters = function(updatedTemplatedParam) {
|
||||
var promises = _.map($scope.filter.templateParameters, function(templateParam) {
|
||||
if (templateParam === updatedTemplatedParam) {
|
||||
return;
|
||||
}
|
||||
if (templateParam.query.indexOf(updatedTemplatedParam.name) !== -1) {
|
||||
return $scope.applyFilter(templateParam);
|
||||
}
|
||||
});
|
||||
|
||||
return $q.all(promises);
|
||||
};
|
||||
|
||||
$scope.applyFilter = function(templateParam) {
|
||||
return datasourceSrv.default.metricFindQuery($scope.filter, templateParam.query)
|
||||
.then(function (results) {
|
||||
templateParam.editing = undefined;
|
||||
templateParam.options = _.map(results, function(node) {
|
||||
return { text: node.text, value: node.text };
|
||||
});
|
||||
|
||||
if (templateParam.includeAll) {
|
||||
var allExpr = '{';
|
||||
_.each(templateParam.options, function(option) {
|
||||
allExpr += option.text + ',';
|
||||
});
|
||||
allExpr = allExpr.substring(0, allExpr.length - 1) + '}';
|
||||
templateParam.options.unshift({text: 'All', value: allExpr});
|
||||
}
|
||||
|
||||
// if parameter has current value
|
||||
// if it exists in options array keep value
|
||||
if (templateParam.current) {
|
||||
var currentExists = _.findWhere(templateParam.options, { value: templateParam.current.value });
|
||||
if (currentExists) {
|
||||
return $scope.filterOptionSelected(templateParam, templateParam.current, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $scope.filterOptionSelected(templateParam, templateParam.options[0], true);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.add = function() {
|
||||
$scope.filter.addTemplateParameter({
|
||||
type : 'filter',
|
||||
name : 'filter name',
|
||||
editing : true,
|
||||
query : 'metric.path.query.*',
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
});
|
113
v2/dboards/src/app/panels/graph/axisEditor.html
Normal file
113
v2/dboards/src/app/panels/graph/axisEditor.html
Normal file
@ -0,0 +1,113 @@
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Left Y Axis</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a ng-click="toggleGridMinMax('leftMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMin)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.leftMin" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ng-click="toggleGridMinMax('leftMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMax)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.leftMax" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Right Y Axis</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a ng-click="toggleGridMinMax('rightMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMin)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.rightMin" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid thresholds</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Level1</label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Color</label>
|
||||
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Level2</label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Color</label>
|
||||
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Legend</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-if="panel.legend.values">
|
||||
<h5>Legend values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min</label><input type="checkbox" ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Max</label><input type="checkbox" ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Current</label><input type="checkbox" ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Total</label><input type="checkbox" ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Avg</label><input type="checkbox" ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="render();">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Show Axes</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
61
v2/dboards/src/app/panels/graph/legend.html
Executable file
61
v2/dboards/src/app/panels/graph/legend.html
Executable file
@ -0,0 +1,61 @@
|
||||
<span ng-show="panel.legend.show"
|
||||
ng-class="{'pull-right': series.yaxis === 2, 'hidden-series': hiddenSeries[series.alias]}"
|
||||
ng-repeat='series in legend'
|
||||
class="histogram-legend">
|
||||
<i class='icon-minus pointer'
|
||||
ng-style="{color: series.color}"
|
||||
bs-popover="'colorPopup.html'"
|
||||
>
|
||||
</i>
|
||||
<span class='small histogram-legend-item'>
|
||||
<a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
|
||||
{{series.alias}}
|
||||
</a>
|
||||
<span ng-if="panel.legend.values">
|
||||
<span ng-show="panel.legend.current">
|
||||
Current: {{series.current}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.min">
|
||||
Min: {{series.min}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.max">
|
||||
Max: {{series.max}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.total">
|
||||
Total: {{series.total}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.avg">
|
||||
Avg: {{series.avg}}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<script type="text/ng-template" id="colorPopup.html">
|
||||
<div class="histogram-legend-popover">
|
||||
<a class="close" ng-click="dismiss();" href="">×</a>
|
||||
|
||||
<div class="editor-row small" style="padding-bottom: 0;">
|
||||
<label>Axis:</label>
|
||||
<button ng-click="toggleYAxis(series)"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 1 }">
|
||||
Left
|
||||
</button>
|
||||
<button ng-click="toggleYAxis(series)"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 2 }">
|
||||
Right
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<i ng-repeat="color in colors"
|
||||
class="pointer"
|
||||
ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}"
|
||||
ng-style="{color:color}"
|
||||
ng-click="changeSeriesColor(series, color);dismiss();">
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
33
v2/dboards/src/app/panels/graph/module.html
Normal file
33
v2/dboards/src/app/panels/graph/module.html
Normal file
@ -0,0 +1,33 @@
|
||||
<div ng-controller='graph'
|
||||
ng-init="init()"
|
||||
style="min-height:{{panel.height || row.height}}"
|
||||
ng-class="{'panel-fullscreen': fullscreen}">
|
||||
|
||||
<div style="position: relative">
|
||||
|
||||
<div ng-if="datapointsWarning" class="datapoints-warning">
|
||||
<span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
|
||||
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
|
||||
</div>
|
||||
|
||||
<div grafana-graph class="pointer histogram-chart">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="panel.legend" class="grafana-legend-container">
|
||||
<div ng-include="'app/panels/graph/legend.html'"></div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="panel-full-edit-tabs" ng-if="editMode">
|
||||
<div ng-model="editor.index" bs-tabs>
|
||||
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
|
||||
<div ng-include src="tab.src"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
430
v2/dboards/src/app/panels/graph/module.js
Normal file
430
v2/dboards/src/app/panels/graph/module.js
Normal file
@ -0,0 +1,430 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/histogram.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/histogram/0
|
||||
* == Histogram
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The histogram panel allow for the display of time charts. It includes several modes and tranformations
|
||||
* to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
|
||||
* fields.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'kbn',
|
||||
'moment',
|
||||
'./timeSeries',
|
||||
'services/annotationsSrv',
|
||||
'services/datasourceSrv',
|
||||
'jquery.flot',
|
||||
'jquery.flot.events',
|
||||
'jquery.flot.selection',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.byte',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent'
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.graph', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('graph', function($scope, $rootScope, datasourceSrv, $timeout, annotationsSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
modals : [],
|
||||
editorTabs: [],
|
||||
fullEditorTabs : [
|
||||
{
|
||||
title: 'General',
|
||||
src:'app/partials/panelgeneral.html'
|
||||
},
|
||||
{
|
||||
title: 'Metrics',
|
||||
src:'app/partials/metrics.html'
|
||||
},
|
||||
{
|
||||
title:'Axes & Grid',
|
||||
src:'app/panels/graph/axisEditor.html'
|
||||
},
|
||||
{
|
||||
title:'Display Styles',
|
||||
src:'app/panels/graph/styleEditor.html'
|
||||
}
|
||||
],
|
||||
fullscreenEdit: true,
|
||||
fullscreenView: true,
|
||||
description : "Graphing"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
|
||||
datasource: null,
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* renderer:: sets client side (flot) or native graphite png renderer (png)
|
||||
*/
|
||||
renderer: 'flot',
|
||||
/** @scratch /panels/histogram/3
|
||||
* x-axis:: Show the x-axis
|
||||
*/
|
||||
'x-axis' : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y-axis:: Show the y-axis
|
||||
*/
|
||||
'y-axis' : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* scale:: Scale the y-axis by this factor
|
||||
*/
|
||||
scale : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y_formats :: 'none','bytes','bits','short', 's', 'ms'
|
||||
*/
|
||||
y_formats : ['short', 'short'],
|
||||
/** @scratch /panels/histogram/5
|
||||
* grid object:: Min and max y-axis values
|
||||
* grid.min::: Minimum y-axis value
|
||||
* grid.ma1::: Maximum y-axis value
|
||||
*/
|
||||
grid : {
|
||||
leftMax: null,
|
||||
rightMax: null,
|
||||
leftMin: null,
|
||||
rightMin: null,
|
||||
threshold1: null,
|
||||
threshold2: null,
|
||||
threshold1Color: 'rgba(216, 200, 27, 0.27)',
|
||||
threshold2Color: 'rgba(234, 112, 112, 0.22)'
|
||||
},
|
||||
|
||||
annotate : {
|
||||
enable : false,
|
||||
},
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* resolution:: If auto_int is true, shoot for this many bars.
|
||||
*/
|
||||
resolution : 100,
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Drawing options
|
||||
* lines:: Show line chart
|
||||
*/
|
||||
lines : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* fill:: Area fill factor for line charts, 1-10
|
||||
*/
|
||||
fill : 0,
|
||||
/** @scratch /panels/histogram/3
|
||||
* linewidth:: Weight of lines in pixels
|
||||
*/
|
||||
linewidth : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* points:: Show points on chart
|
||||
*/
|
||||
points : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* pointradius:: Size of points in pixels
|
||||
*/
|
||||
pointradius : 5,
|
||||
/** @scratch /panels/histogram/3
|
||||
* bars:: Show bars on chart
|
||||
*/
|
||||
bars : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* stack:: Stack multiple series
|
||||
*/
|
||||
stack : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend:: Display the legend
|
||||
*/
|
||||
legend: {
|
||||
show: true, // disable/enable legend
|
||||
values: false, // disable/enable legend values
|
||||
min: false,
|
||||
max: false,
|
||||
current: false,
|
||||
total: false,
|
||||
avg: false
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Transformations
|
||||
/** @scratch /panels/histogram/3
|
||||
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
|
||||
* queries
|
||||
*/
|
||||
percentage : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* zerofill:: Improves the accuracy of line charts at a small performance cost.
|
||||
*/
|
||||
zerofill : true,
|
||||
|
||||
nullPointMode : 'connected',
|
||||
|
||||
steppedLine: false,
|
||||
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
query_as_alias: true
|
||||
},
|
||||
|
||||
targets: [],
|
||||
|
||||
aliasColors: {},
|
||||
aliasYAxis: {},
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
_.defaults($scope.panel.tooltip, _d.tooltip);
|
||||
_.defaults($scope.panel.annotate, _d.annotate);
|
||||
_.defaults($scope.panel.grid, _d.grid);
|
||||
|
||||
// backward compatible stuff
|
||||
if (_.isBoolean($scope.panel.legend)) {
|
||||
$scope.panel.legend = { show: $scope.panel.legend };
|
||||
_.defaults($scope.panel.legend, _d.legend);
|
||||
}
|
||||
|
||||
if ($scope.panel.grid.min) {
|
||||
$scope.panel.grid.leftMin = $scope.panel.grid.min;
|
||||
delete $scope.panel.grid.min;
|
||||
}
|
||||
|
||||
if ($scope.panel.grid.max) {
|
||||
$scope.panel.grid.leftMax = $scope.panel.grid.max;
|
||||
delete $scope.panel.grid.max;
|
||||
}
|
||||
|
||||
if ($scope.panel.y_format) {
|
||||
$scope.panel.y_formats[0] = $scope.panel.y_format;
|
||||
delete $scope.panel.y_format;
|
||||
}
|
||||
|
||||
if ($scope.panel.y2_format) {
|
||||
$scope.panel.y_formats[1] = $scope.panel.y2_format;
|
||||
delete $scope.panel.y2_format;
|
||||
}
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.initBaseController(this, $scope);
|
||||
|
||||
$scope.fullscreen = false;
|
||||
$scope.editor = { index: 1 };
|
||||
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
|
||||
$scope.hiddenSeries = {};
|
||||
|
||||
$scope.datasources = datasourceSrv.listOptions();
|
||||
$scope.setDatasource($scope.panel.datasource);
|
||||
|
||||
if ($scope.panel.targets.length === 0) {
|
||||
$scope.panel.targets.push({});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setDatasource = function(datasource) {
|
||||
$scope.panel.datasource = datasource;
|
||||
$scope.datasource = datasourceSrv.get(datasource);
|
||||
|
||||
if (!$scope.datasource) {
|
||||
$scope.panel.error = "Cannot find datasource " + datasource;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.removeTarget = function (target) {
|
||||
$scope.panel.targets = _.without($scope.panel.targets, target);
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
$scope.range = this.filter.timeRange();
|
||||
$scope.rangeUnparsed = this.filter.timeRange(false);
|
||||
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
|
||||
$scope.interval = '10m';
|
||||
|
||||
if ($scope.range) {
|
||||
$scope.interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.get_data = function() {
|
||||
delete $scope.panel.error;
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
|
||||
$scope.updateTimeRange();
|
||||
|
||||
var graphiteQuery = {
|
||||
range: $scope.rangeUnparsed,
|
||||
interval: $scope.interval,
|
||||
targets: $scope.panel.targets,
|
||||
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
|
||||
maxDataPoints: $scope.resolution,
|
||||
datasource: $scope.panel.datasource
|
||||
};
|
||||
|
||||
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.filter, $scope.rangeUnparsed);
|
||||
|
||||
return $scope.datasource.query($scope.filter, graphiteQuery)
|
||||
.then($scope.dataHandler)
|
||||
.then(null, function(err) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.panel.error = err.message || "Timeseries data request error";
|
||||
$scope.inspector.error = err;
|
||||
$scope.render([]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.dataHandler = function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.legend = [];
|
||||
|
||||
// png renderer returns just a url
|
||||
if (_.isString(results)) {
|
||||
$scope.render(results);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.datapointsWarning = false;
|
||||
$scope.datapointsCount = 0;
|
||||
$scope.datapointsOutside = false;
|
||||
|
||||
var data = _.map(results.data, $scope.seriesHandler);
|
||||
|
||||
$scope.datapointsWarning = $scope.datapointsCount || !$scope.datapointsOutside;
|
||||
|
||||
$scope.annotationsPromise
|
||||
.then(function(annotations) {
|
||||
data.annotations = annotations;
|
||||
$scope.render(data);
|
||||
}, function() {
|
||||
$scope.render(data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.seriesHandler = function(seriesData, index) {
|
||||
var datapoints = seriesData.datapoints;
|
||||
var alias = seriesData.target;
|
||||
var color = $scope.panel.aliasColors[alias] || $scope.colors[index];
|
||||
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
|
||||
|
||||
var seriesInfo = {
|
||||
alias: alias,
|
||||
color: color,
|
||||
enable: true,
|
||||
yaxis: yaxis
|
||||
};
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
|
||||
var series = new timeSeries.ZeroFilled({
|
||||
datapoints: datapoints,
|
||||
info: seriesInfo,
|
||||
});
|
||||
|
||||
if (datapoints && datapoints.length > 0) {
|
||||
var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
|
||||
var from = moment.utc($scope.range.from);
|
||||
if (last - from < -10000) {
|
||||
$scope.datapointsOutside = true;
|
||||
}
|
||||
|
||||
$scope.datapointsCount += datapoints.length;
|
||||
}
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
$scope.add_target = function() {
|
||||
$scope.panel.targets.push({target: ''});
|
||||
};
|
||||
|
||||
$scope.otherPanelInFullscreenMode = function() {
|
||||
return $rootScope.fullscreen && !$scope.fullscreen;
|
||||
};
|
||||
|
||||
$scope.render = function(data) {
|
||||
$scope.$emit('render', data);
|
||||
};
|
||||
|
||||
$scope.changeSeriesColor = function(series, color) {
|
||||
series.color = color;
|
||||
$scope.panel.aliasColors[series.alias] = series.color;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.toggleSeries = function(serie, event) {
|
||||
if ($scope.hiddenSeries[serie.alias]) {
|
||||
delete $scope.hiddenSeries[serie.alias];
|
||||
}
|
||||
else {
|
||||
$scope.hiddenSeries[serie.alias] = true;
|
||||
}
|
||||
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
$scope.toggleSeriesExclusiveMode(serie);
|
||||
}
|
||||
|
||||
$scope.$emit('toggleLegend', $scope.legend);
|
||||
};
|
||||
|
||||
$scope.toggleSeriesExclusiveMode = function(serie) {
|
||||
var hidden = $scope.hiddenSeries;
|
||||
|
||||
if (hidden[serie.alias]) {
|
||||
delete hidden[serie.alias];
|
||||
}
|
||||
|
||||
// check if every other series is hidden
|
||||
var alreadyExclusive = _.every($scope.legend, function(value) {
|
||||
if (value.alias === serie.alias) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hidden[value.alias];
|
||||
});
|
||||
|
||||
if (alreadyExclusive) {
|
||||
// remove all hidden series
|
||||
_.each($scope.legend, function(value) {
|
||||
delete $scope.hiddenSeries[value.alias];
|
||||
});
|
||||
}
|
||||
else {
|
||||
// hide all but this serie
|
||||
_.each($scope.legend, function(value) {
|
||||
if (value.alias === serie.alias) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.hiddenSeries[value.alias] = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleYAxis = function(info) {
|
||||
info.yaxis = info.yaxis === 2 ? 1 : 2;
|
||||
$scope.panel.aliasYAxis[info.alias] = info.yaxis;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.toggleGridMinMax = function(key) {
|
||||
$scope.panel.grid[key] = _.toggle($scope.panel.grid[key], null, 0);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
66
v2/dboards/src/app/panels/graph/styleEditor.html
Normal file
66
v2/dboards/src/app/panels/graph/styleEditor.html
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Line options</h5>
|
||||
<div class="editor-option" ng-show="panel.lines">
|
||||
<label class="small">Line Fill</label>
|
||||
<select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.lines">
|
||||
<label class="small">Line Width</label>
|
||||
<select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.points">
|
||||
<label class="small">Point Radius</label>
|
||||
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Null point mode <tip>Define how null values should be drawn</tip></label>
|
||||
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Staircase line</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Multiple Series</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
|
||||
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section">
|
||||
<h5>Rendering</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Flot <tip>client side</tip></label>
|
||||
<input type="radio" class="input-small" ng-model="panel.renderer" value="flot" ng-change="get_data()" />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Graphite PNG <tip>server side</tip></label>
|
||||
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
74
v2/dboards/src/app/panels/graph/timeSeries.js
Normal file
74
v2/dboards/src/app/panels/graph/timeSeries.js
Normal file
@ -0,0 +1,74 @@
|
||||
define([
|
||||
'underscore',
|
||||
'kbn'
|
||||
],
|
||||
function (_, kbn) {
|
||||
'use strict';
|
||||
|
||||
var ts = {};
|
||||
|
||||
ts.ZeroFilled = function (opts) {
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
};
|
||||
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) {
|
||||
var result = [];
|
||||
|
||||
this.color = this.info.color;
|
||||
this.yaxis = this.info.yaxis;
|
||||
|
||||
this.info.total = 0;
|
||||
this.info.max = null;
|
||||
this.info.min = 212312321312;
|
||||
|
||||
_.each(this.datapoints, function(valueArray) {
|
||||
var currentTime = valueArray[1];
|
||||
var currentValue = valueArray[0];
|
||||
if (currentValue === null) {
|
||||
if (fillStyle === 'connected') {
|
||||
return;
|
||||
}
|
||||
if (fillStyle === 'null as zero') {
|
||||
currentValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isNumber(currentValue)) {
|
||||
this.info.total += currentValue;
|
||||
}
|
||||
|
||||
if (currentValue > this.info.max) {
|
||||
this.info.max = currentValue;
|
||||
}
|
||||
|
||||
if (currentValue < this.info.min) {
|
||||
this.info.min = currentValue;
|
||||
}
|
||||
|
||||
result.push([currentTime * 1000, currentValue]);
|
||||
}, this);
|
||||
|
||||
if (result.length > 2) {
|
||||
this.info.timeStep = result[1][0] - result[0][0];
|
||||
}
|
||||
|
||||
if (result.length) {
|
||||
|
||||
this.info.avg = (this.info.total / result.length);
|
||||
this.info.current = result[result.length-1][1];
|
||||
|
||||
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
|
||||
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
|
||||
this.info.current = this.info.current != null ? formater(this.info.current) : null;
|
||||
this.info.min = this.info.min != null ? formater(this.info.min) : null;
|
||||
this.info.max = this.info.max != null ? formater(this.info.max) : null;
|
||||
this.info.total = this.info.total != null ? formater(this.info.total) : null;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return ts;
|
||||
});
|
18
v2/dboards/src/app/panels/text/editor.html
Normal file
18
v2/dboards/src/app/panels/text/editor.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div>
|
||||
<div class="row-fluid">
|
||||
<div class="span4">
|
||||
<label class="small">Mode</label> <select class="input-medium" ng-model="panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
|
||||
</div>
|
||||
<div class="span2" ng-show="panel.mode == 'text'">
|
||||
<label class="small">Font Size</label> <select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class=small>Content
|
||||
<span ng-show="panel.mode == 'html'">(This area uses HTML sanitized via AngularJS's <a href='http://docs.angularjs.org/api/ngSanitize.$sanitize'>$sanitize</a> service)</span>
|
||||
<span ng-show="panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
|
||||
</label>
|
||||
|
||||
<textarea ng-model="panel.content" rows="6" style="width:95%" ng-change="render()" ng-model-onblur>
|
||||
</textarea>
|
||||
</div>
|
1454
v2/dboards/src/app/panels/text/lib/showdown.js
Normal file
1454
v2/dboards/src/app/panels/text/lib/showdown.js
Normal file
File diff suppressed because it is too large
Load Diff
10
v2/dboards/src/app/panels/text/module.html
Normal file
10
v2/dboards/src/app/panels/text/module.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div ng-controller='text' ng-init="init()" style="min-height:{{panel.height || row.height}}" ng-dblclick="openEditor()">
|
||||
<!--<p ng-style="panel.style" ng-bind-html-unsafe="panel.content | striphtml | newlines"></p>-->
|
||||
<markdown ng-show="ready && panel.mode == 'markdown'">
|
||||
{{panel.content}}
|
||||
</markdown>
|
||||
<p ng-show="panel.mode == 'text'" ng-style='panel.style' ng-bind-html-unsafe="panel.content | striphtml | newlines">
|
||||
</p>
|
||||
<p ng-show="panel.mode == 'html'" ng-bind-html-unsafe="panel.content">
|
||||
</p>
|
||||
</div>
|
101
v2/dboards/src/app/panels/text/module.js
Normal file
101
v2/dboards/src/app/panels/text/module.js
Normal file
@ -0,0 +1,101 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/text.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/text/0
|
||||
* == text
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The text panel is used for displaying static text formated as markdown, sanitized html or as plain
|
||||
* text.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'require'
|
||||
],
|
||||
function (angular, app, _, require) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.text', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('text', function($scope) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
mode : "markdown", // 'html', 'markdown', 'text'
|
||||
content : "",
|
||||
style: {},
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.initBaseController(this, $scope);
|
||||
|
||||
$scope.ready = false;
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.openEditor = function() {
|
||||
//$scope.$emit('open-modal','paneleditor');
|
||||
console.log('scope id', $scope.$id);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('markdown', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element) {
|
||||
scope.$on('render', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function render_panel() {
|
||||
require(['./lib/showdown'], function (Showdown) {
|
||||
scope.ready = true;
|
||||
var converter = new Showdown.converter();
|
||||
var text = scope.panel.content.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<');
|
||||
var htmlText = converter.makeHtml(text);
|
||||
element.html(htmlText);
|
||||
// For whatever reason, this fixes chrome. I don't like it, I think
|
||||
// it makes things slow?
|
||||
if(!scope.$$phase) {
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render_panel();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('newlines', function() {
|
||||
return function (input) {
|
||||
return input.replace(/\n/g, '<br/>');
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('striphtml', function () {
|
||||
return function(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<');
|
||||
};
|
||||
});
|
||||
});
|
78
v2/dboards/src/app/panels/timepicker/custom.html
Normal file
78
v2/dboards/src/app/panels/timepicker/custom.html
Normal file
@ -0,0 +1,78 @@
|
||||
<div class="modal-body">
|
||||
<style>
|
||||
.timepicker-to-column {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.timepicker-input input {
|
||||
outline: 0 !important;
|
||||
border: 0px !important;
|
||||
-webkit-box-shadow: 0;
|
||||
-moz-box-shadow: 0;
|
||||
box-shadow: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timepicker-input input::-webkit-outer-spin-button,
|
||||
.timepicker-input input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input.timepicker-date {
|
||||
width: 90px;
|
||||
}
|
||||
input.timepicker-hms {
|
||||
width: 20px;
|
||||
}
|
||||
input.timepicker-ms {
|
||||
width: 25px;
|
||||
}
|
||||
div.timepicker-now {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="timepicker form-horizontal">
|
||||
<form name="input">
|
||||
|
||||
<div class="timepicker-from-column">
|
||||
<label class="small">From</label>
|
||||
<div class="fake-input timepicker-input">
|
||||
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
|
||||
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
|
||||
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
|
||||
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
|
||||
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.from.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timepicker-to-column">
|
||||
|
||||
<label class="small">To (<a class="link" ng-class="{'strong':panel.now}" ng-click="setNow();panel.now=true">now</a>)</label>
|
||||
|
||||
<div class="fake-input timepicker-input">
|
||||
<div ng-hide="panel.now">
|
||||
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
|
||||
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
|
||||
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
|
||||
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
|
||||
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.to.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
|
||||
</div>
|
||||
<span type="text" ng-show="panel.now" ng-disabled="panel.now">  <i class="pointer icon-remove-sign" ng-click="setNow();panel.now=false"></i> Right Now <input type="text" name="dummy" style="visibility:hidden" /></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<form name="input" style="margin-bottom:0">
|
||||
<span class="" ng-hide="input.$valid">Invalid date or range</span>
|
||||
<button ng-click="setAbsoluteTimeFilter(validate(temptime));dismiss();" ng-disabled="!input.$valid" class="btn btn-success">Apply</button>
|
||||
<button ng-click="dismiss();" class="btn btn-danger">Cancel</button>
|
||||
|
||||
</form>
|
||||
</div>
|
12
v2/dboards/src/app/panels/timepicker/editor.html
Normal file
12
v2/dboards/src/app/panels/timepicker/editor.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="editor-option">
|
||||
<label class="small">Relative time options <small>comma seperated</small></label>
|
||||
<input type="text" array-join class="input-large" ng-model="panel.time_options">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Auto-refresh options <small>comma seperated</small></label>
|
||||
<input type="text" array-join class="input-large" ng-model="panel.refresh_intervals">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
56
v2/dboards/src/app/panels/timepicker/module.html
Normal file
56
v2/dboards/src/app/panels/timepicker/module.html
Normal file
@ -0,0 +1,56 @@
|
||||
<div ng-controller='timepicker' ng-init="init()">
|
||||
<style>
|
||||
.timepicker-timestring {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.timepicker-dropdown {
|
||||
margin: 0px !important;
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
<!-- This is a complete hack. The form actually exists in the modal, but due to transclusion
|
||||
$scope.input isn't available on the controller unless the form element is in this file -->
|
||||
<form name="input" style="margin:3px 0 0 0">
|
||||
<ul class="nav nav-pills timepicker-dropdown">
|
||||
<li class="dropdown">
|
||||
|
||||
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.from.date ? (time.from.date | date:'yyyy-MM-dd HH:mm:ss.sss') + ' <br>to<br>' +(time.to.date | date:'yyyy-MM-dd HH:mm:ss.sss') : 'Click to set a time filter'" data-placement="bottom" ng-click="dismiss();">
|
||||
|
||||
<span ng-show="filter.time">
|
||||
<span class="pointer" ng-hide="panel.now">{{time.from.date | date:'MMM d, y HH:mm:ss'}}</span>
|
||||
<span class="pointer" ng-show="panel.now">{{time.from.date | moment:'ago'}}</span>
|
||||
to
|
||||
<span class="pointer" ng-hide="panel.now" >{{time.to.date | date:'MMM d, y HH:mm:ss'}}</span>
|
||||
<span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
|
||||
</span>
|
||||
<span ng-hide="filter.time">Time filter</span>
|
||||
<span ng-show="dashboard.current.refresh" class="text-warning">refreshed every {{dashboard.current.refresh}} </span>
|
||||
<i class="icon-caret-down"></i>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<!-- Relative time options -->
|
||||
<li ng-repeat='timespan in panel.time_options track by $index'>
|
||||
<a ng-click="setRelativeFilter(timespan)">Last {{timespan}}</a>
|
||||
</li>
|
||||
|
||||
<!-- Auto refresh submenu -->
|
||||
<li class="dropdown-submenu">
|
||||
<a href="#">Auto-Refresh</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="dashboard.set_interval(false)">Off</a></li>
|
||||
<li ng-repeat="interval in panel.refresh_intervals track by $index"><a ng-click="dashboard.set_interval(interval)">Every {{interval}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a ng-click="customTime()">Custom</a></li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li ng-show="!dashboard.current.refresh" class="grafana-menu-refresh">
|
||||
<a class="icon-refresh" ng-click="dashboard.refresh()"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
</div>
|
197
v2/dboards/src/app/panels/timepicker/module.js
Normal file
197
v2/dboards/src/app/panels/timepicker/module.js
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
|
||||
## Timepicker2
|
||||
|
||||
### Parameters
|
||||
* mode :: The default mode of the panel. Options: 'relative', 'absolute' 'since' Default: 'relative'
|
||||
* time_options :: An array of possible time options. Default: ['5m','15m','1h','6h','12h','24h','2d','7d','30d']
|
||||
* timespan :: The default options selected for the relative view. Default: '15m'
|
||||
* timefield :: The field in which time is stored in the document.
|
||||
* refresh: Object containing refresh parameters
|
||||
* enable :: true/false, enable auto refresh by default. Default: false
|
||||
* interval :: Seconds between auto refresh. Default: 30
|
||||
* min :: The lowest interval a user may set
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'moment',
|
||||
'kbn'
|
||||
],
|
||||
function (angular, app, _, moment, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.timepicker', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('timepicker', function($scope, $modal, $q) {
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
description : "A panel for controlling the time range filters. If you have time based data, "+
|
||||
" or if you're using time stamped indices, you need one of these"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
status : "Stable",
|
||||
time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'],
|
||||
refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'],
|
||||
};
|
||||
|
||||
var customTimeModal = null;
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
// ng-pattern regexs
|
||||
$scope.patterns = {
|
||||
date: /^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/,
|
||||
hour: /^([01]?[0-9]|2[0-3])$/,
|
||||
minute: /^[0-5][0-9]$/,
|
||||
second: /^[0-5][0-9]$/,
|
||||
millisecond: /^[0-9]*$/
|
||||
};
|
||||
|
||||
$scope.$on('refresh', function() {
|
||||
$scope.init();
|
||||
});
|
||||
|
||||
$scope.init = function() {
|
||||
var time = this.filter.timeRange(true);
|
||||
if(time) {
|
||||
$scope.panel.now = this.filter.timeRange(false).to === "now" ? true : false;
|
||||
$scope.time = getScopeTimeObj(time.from,time.to);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.customTime = function() {
|
||||
if (!customTimeModal) {
|
||||
customTimeModal = $modal({
|
||||
template: './app/panels/timepicker/custom.html',
|
||||
persist: true,
|
||||
show: false,
|
||||
scope: $scope,
|
||||
keyboard: false
|
||||
});
|
||||
}
|
||||
|
||||
// Assume the form is valid since we're setting it to something valid
|
||||
$scope.input.$setValidity("dummy", true);
|
||||
$scope.temptime = cloneTime($scope.time);
|
||||
|
||||
// Date picker needs the date to be at the start of the day
|
||||
$scope.temptime.from.date.setHours(1,0,0,0);
|
||||
$scope.temptime.to.date.setHours(1,0,0,0);
|
||||
|
||||
$q.when(customTimeModal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
// Constantly validate the input of the fields. This function does not change any date variables
|
||||
// outside of its own scope
|
||||
$scope.validate = function(time) {
|
||||
// Assume the form is valid. There is a hidden dummy input for invalidating it programatically.
|
||||
$scope.input.$setValidity("dummy", true);
|
||||
|
||||
var _from = datepickerToLocal(time.from.date),
|
||||
_to = datepickerToLocal(time.to.date),
|
||||
_t = time;
|
||||
|
||||
if($scope.input.$valid) {
|
||||
|
||||
_from.setHours(_t.from.hour,_t.from.minute,_t.from.second,_t.from.millisecond);
|
||||
_to.setHours(_t.to.hour,_t.to.minute,_t.to.second,_t.to.millisecond);
|
||||
|
||||
// Check that the objects are valid and to is after from
|
||||
if(isNaN(_from.getTime()) || isNaN(_to.getTime()) || _from.getTime() >= _to.getTime()) {
|
||||
$scope.input.$setValidity("dummy", false);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {from:_from,to:_to};
|
||||
};
|
||||
|
||||
$scope.setNow = function() {
|
||||
$scope.time.to = getTimeObj(new Date());
|
||||
};
|
||||
|
||||
/*
|
||||
time : {
|
||||
from: Date
|
||||
to: Date
|
||||
}
|
||||
*/
|
||||
$scope.setAbsoluteTimeFilter = function (time) {
|
||||
// Create filter object
|
||||
var _filter = _.clone(time);
|
||||
|
||||
if($scope.panel.now) {
|
||||
_filter.to = "now";
|
||||
}
|
||||
|
||||
// Set the filter
|
||||
$scope.panel.filter_id = $scope.filter.setTime(_filter);
|
||||
|
||||
// Update our representation
|
||||
$scope.time = getScopeTimeObj(time.from,time.to);
|
||||
};
|
||||
|
||||
$scope.setRelativeFilter = function(timespan) {
|
||||
$scope.panel.now = true;
|
||||
|
||||
var _filter = {
|
||||
from : "now-"+timespan,
|
||||
to: "now"
|
||||
};
|
||||
|
||||
this.filter.setTime(_filter);
|
||||
|
||||
$scope.time = getScopeTimeObj(kbn.parseDate(_filter.from),new Date());
|
||||
};
|
||||
|
||||
var pad = function(n, width, z) {
|
||||
z = z || '0';
|
||||
n = n.toString();
|
||||
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
|
||||
};
|
||||
|
||||
var cloneTime = function(time) {
|
||||
var _n = {
|
||||
from: _.clone(time.from),
|
||||
to: _.clone(time.to)
|
||||
};
|
||||
// Create new dates as _.clone is shallow.
|
||||
_n.from.date = new Date(_n.from.date);
|
||||
_n.to.date = new Date(_n.to.date);
|
||||
return _n;
|
||||
};
|
||||
|
||||
var getScopeTimeObj = function(from,to) {
|
||||
return {
|
||||
from: getTimeObj(from),
|
||||
to: getTimeObj(to)
|
||||
};
|
||||
};
|
||||
|
||||
var getTimeObj = function(date) {
|
||||
return {
|
||||
date: new Date(date),
|
||||
hour: pad(date.getHours(),2),
|
||||
minute: pad(date.getMinutes(),2),
|
||||
second: pad(date.getSeconds(),2),
|
||||
millisecond: pad(date.getMilliseconds(),3)
|
||||
};
|
||||
};
|
||||
|
||||
// Do not use the results of this function unless you plan to use setHour/Minutes/etc on the result
|
||||
var datepickerToLocal = function(date) {
|
||||
date = moment(date).clone().toDate();
|
||||
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate();
|
||||
};
|
||||
|
||||
});
|
||||
});
|
5
v2/dboards/src/app/panels/timepicker/refreshctrl.html
Normal file
5
v2/dboards/src/app/panels/timepicker/refreshctrl.html
Normal file
@ -0,0 +1,5 @@
|
||||
<form name="refreshPopover" class='form-inline input-append' style="margin:0px">
|
||||
<label><small>Interval (seconds)</small></label><br>
|
||||
<input type="number" class="input-mini" ng-model="refresh_interval">
|
||||
<button type="button" class="btn" ng-click="set_interval(refresh_interval);dismiss()"><i class="icon-ok"></i></button>
|
||||
</form>
|
77
v2/dboards/src/app/partials/dashLoader.html
Normal file
77
v2/dboards/src/app/partials/dashLoader.html
Normal file
@ -0,0 +1,77 @@
|
||||
<style>
|
||||
.noarrow>a:after {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<li ng-show="fullscreen">
|
||||
<a ng-click="exitFullscreen()">
|
||||
Back to dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="grafana-menu-zoom-out">
|
||||
<a class='small' ng-click='zoom(2)'>
|
||||
Zoom Out
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable"><kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel></li>
|
||||
|
||||
<li class="dropdown grafana-menu-save" ng-show="showDropdown('save')">
|
||||
<a href="#" bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
|
||||
<i class='icon-save'></i>
|
||||
</a>
|
||||
|
||||
<ul class="save-dashboard-dropdown dropdown-menu">
|
||||
|
||||
<li ng-show="dashboard.current.loader.save_elasticsearch">
|
||||
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
|
||||
<input class='input-medium' ng-model="dashboard.current.title" type="text" ng-model="elasticsearch.title"/>
|
||||
<button class="btn" ng-click="elasticsearch_save('dashboard')"><i class="icon-save"></i></button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<li ng-show="dashboard.current.loader.save_default">
|
||||
<a class="link" ng-click="set_default()">Save as Home</a>
|
||||
</li>
|
||||
<li ng-show="dashboard.current.loader.save_default">
|
||||
<a class="link" ng-click="purge_default()">Reset Home</a>
|
||||
</li>
|
||||
<li ng-show="!isFavorite">
|
||||
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
|
||||
</li>
|
||||
<li ng-show="isFavorite">
|
||||
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
|
||||
</li>
|
||||
<li ng-show="dashboard.current.loader.save_local">
|
||||
<a class="link" ng-click="dashboard.to_file()">Export dashboard</a>
|
||||
</li>
|
||||
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',dashboard.current.loader.save_temp_ttl)" config-modal="app/partials/dashLoaderShare.html">Share temp copy</i></a></li>
|
||||
|
||||
<li ng-show="dashboard.current.loader.save_gist" style="margin:10px">
|
||||
<h6>Gist</h6>
|
||||
<form class="input-append">
|
||||
<input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
|
||||
<button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
|
||||
</form><br>
|
||||
<small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown grafana-menu-load" ng-show="showDropdown('load')" ng-controller="SearchCtrl" ng-init="init()" ng-include="'app/partials/search.html'">
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
|
||||
|
||||
<li class="grafana-menu-edit" ng-show="dashboard.current.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" config-modal="app/partials/dasheditor.html"><i class='icon-cog pointer'></i></a></li>
|
||||
|
||||
<li class="grafana-menu-stop-playlist hide">
|
||||
<a class='small' ng-click='stopPlaylist(2)'>
|
||||
Stop playlist
|
||||
</a>
|
||||
</li>
|
11
v2/dboards/src/app/partials/dashLoaderShare.html
Normal file
11
v2/dboards/src/app/partials/dashLoaderShare.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>{{share.title}} <small>shareable link</small></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label>Share this dashboard with this URL</label>
|
||||
<input ng-model='share.link' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()" ng-change="share = dashboard.share_link(share.title,share.type,share.id)">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>
|
||||
</div>
|
110
v2/dboards/src/app/partials/dashboard.html
Normal file
110
v2/dboards/src/app/partials/dashboard.html
Normal file
@ -0,0 +1,110 @@
|
||||
<div class="submenu-controls">
|
||||
<div class="submenu-panel" ng-controller="SubmenuCtrl" ng-repeat="pulldown in dashboard.current.pulldowns | filter:{ enable: true }">
|
||||
<div class="submenu-panel-title">
|
||||
<span class="small"><strong>{{pulldown.type}}:</strong></span>
|
||||
</div>
|
||||
<div class="submenu-panel-wrapper">
|
||||
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="container-fluid main">
|
||||
<div>
|
||||
<div class="grafana-container container">
|
||||
<!-- Rows -->
|
||||
<div class="kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
|
||||
<div class="row-control">
|
||||
<div class="grafana-row" style="padding:0px;margin:0px;position:relative;">
|
||||
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
|
||||
<span class="row-button bgWarning" config-modal="app/partials/roweditor.html" class="pointer">
|
||||
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
|
||||
</span>
|
||||
<span class="row-button bgPrimary" ng-click="toggle_row(row)" ng-show="row.collapsable">
|
||||
<i bs-tooltip="'Expand row'" data-placement="right" class="icon-caret-left pointer" ></i>
|
||||
</span>
|
||||
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
|
||||
</div>
|
||||
<div class="row-open" ng-show="!row.collapse">
|
||||
<div ng-show="row.collapsable" class='row-tab bgPrimary' ng-click="toggle_row(row)">
|
||||
<span class="row-tab-button">
|
||||
<i class="icon-caret-right" ></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class='row-tab bgSuccess dropdown' ng-show="row.editable">
|
||||
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="icon-th-list"></i>
|
||||
</span>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="drop1">
|
||||
<li class="dropdown-submenu">
|
||||
<a href="javascript:void();">Add Panel</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="add_panel_default('graph')">Graph</li></a>
|
||||
<li><a ng-click="add_panel_default('text')">Text</li></a>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown-submenu">
|
||||
<a href="javascript:void();">Set height</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="set_height('100px')">100 px</li></a>
|
||||
<li><a ng-click="set_height('150px')">150 px</li></a>
|
||||
<li><a ng-click="set_height('200px')">200 px</li></a>
|
||||
<li><a ng-click="set_height('250px')">250 px</li></a>
|
||||
<li><a ng-click="set_height('300px')">300 px</li></a>
|
||||
<li><a ng-click="set_height('350px')">350 px</li></a>
|
||||
<li><a ng-click="set_height('450px')">450 px</li></a>
|
||||
<li><a ng-click="set_height('500px')">500 px</li></a>
|
||||
<li><a ng-click="set_height('600px')">600 px</li></a>
|
||||
<li><a ng-click="set_height('700px')">700 px</li></a>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown-submenu">
|
||||
<a href="javascript:void();">Move</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="move_row(-1)">Up</li></a>
|
||||
<li><a ng-click="move_row(1)">Down</li></a>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a config-modal="app/partials/roweditor.html">Row editor</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="delete_row()">Delete row</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding-top:0px" ng-if="!row.collapse">
|
||||
|
||||
<!-- Panels -->
|
||||
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.hide" class="panel nospace" ng-style="{'width':!panel.span?'100%':(panel.span/1.2)*10+'%'}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}" ng-class="{'dragInProgress':dashboard.panelDragging}">
|
||||
<!-- Content Panel -->
|
||||
<div style="position:relative">
|
||||
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="rowSpan(row) < 10 && dashboard.panelDragging" class="panel" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-class="{'dragInProgress':dashboard.panelDragging}" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show='dashboard.current.editable && dashboard.current.panel_hints' class="row-fluid add-row-panel-hint">
|
||||
<div class="span12" style="text-align:right;">
|
||||
<span style="margin-right: 10px;" ng-click="add_row_default()" class="pointer btn btn-info btn-mini">
|
||||
<span><i class="icon-plus-sign"></i> ADD A ROW</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user