Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
O
OpnSense
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kulya
OpnSense
Commits
381fc845
Commit
381fc845
authored
Sep 25, 2015
by
Jos Schellevis
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
(systemhealth) First commit WIP
parent
38cb88a3
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1505 additions
and
0 deletions
+1505
-0
ServiceController.php
...ntrollers/OPNsense/SystemHealth/Api/ServiceController.php
+587
-0
SettingsController.php
...trollers/OPNsense/SystemHealth/Api/SettingsController.php
+44
-0
IndexController.php
...app/controllers/OPNsense/SystemHealth/IndexController.php
+43
-0
SystemHealth.php
...nse/mvc/app/models/OPNsense/SystemHealth/SystemHealth.php
+35
-0
SystemHealth.xml
...nse/mvc/app/models/OPNsense/SystemHealth/SystemHealth.xml
+8
-0
index.volt
src/opnsense/mvc/app/views/OPNsense/SystemHealth/index.volt
+736
-0
system-processor.xml
...nsense/scripts/systemhealth/metadata/system-processor.xml
+12
-0
queryDetails.py
src/opnsense/scripts/systemhealth/queryDetails.py
+35
-0
actions_systemhealth.conf
...opnsense/service/conf/actions.d/actions_systemhealth.conf
+5
-0
No files found.
src/opnsense/mvc/app/controllers/OPNsense/SystemHealth/Api/ServiceController.php
0 → 100644
View file @
381fc845
<?php
/**
* Copyright (C) 2015 Deciso B.V. - J. Schellevis
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace
OPNsense\SystemHealth\Api
;
use
\OPNsense\Base\ApiControllerBase
;
use
\OPNsense\Core\Backend
;
/**
* Class ServiceController
* @package OPNsense\SystemHealth
*/
class
ServiceController
extends
ApiControllerBase
{
/**
* retrieve Available RRD data
* @return array
*/
public
function
getRRDlistAction
()
{
# Suurce of data: filelisting of /var/db/rrd/*.rrd
$result
=
array
();
$output
=
[
"system"
=>
[
"processor"
,
"states"
,
"mbuf"
],
"traffic"
=>
[
"lan"
,
"wan"
,
"ipsec"
],
"packets"
=>
[
"wan"
,
"lan"
,
"ipsec"
],
"quality"
=>
[
"GW_WAN"
]
];
$result
[
"result"
]
=
"ok"
;
$result
[
"data"
]
=
$output
;
// Category => Items
return
$result
;
}
private
function
getRRDdetails
(
$rrd
=
""
)
{
# Source of data: xml fields of corresponding .xml metadata
$result
=
array
();
if
(
$rrd
==
"system-processor"
)
{
$backend
=
new
Backend
();
$response
=
$backend
->
configdpRun
(
"systemhealth query details"
);
#, array(1, 0, "filepos/".$id, $fileid));
// $response = $backend->configdRun("configd actions");
$xml
=
simplexml_load_string
(
$response
);
$json
=
json_encode
(
$xml
);
$output
=
json_decode
(
$json
,
true
);
// $output = [
// "title" => "System Information - Utilization and Processes",
// "x-axis_label" => "[U]tilization , [#]Number",
// "field_units" => [
// "user" => "[U]",
// "nice" => "[U]",
// "system" => "[U]",
// "interrupt" => "[#]",
// "processes" => "[#]"
// ]
// ];
$result
[
"result"
]
=
"ok"
;
}
else
{
$result
[
"result"
]
=
"not found"
;
$output
=
[
"title"
=>
""
,
"x-axis_label"
=>
""
,
"field_units"
=>
[]];
// always return a valid (empty) data set
}
$result
[
"data"
]
=
$output
;
return
$result
;
}
/**
* retrieve SystemHealth Data (previously called RRD Graphs)
* @param string $rrd
* @param int $from
* @param int $to
* @param int $max_values
* @param bool $inverse
* @param int $detail
* @return array
*/
public
function
getSystemHealthAction
(
$rrd
=
""
,
$from
=
0
,
$to
=
0
,
$max_values
=
120
,
$inverse
=
false
,
$detail
=
-
1
)
{
/**
* $rrd = rrd filename without extension
* $from = from timestamp (0=min)
* $to = to timestamp (0=max)
* $max_values = limit datapoint as close as possible to this number (or twice if detail (zoom) + overview )
* $inverse = Inverse every odd row (multiply by -1)
* $detail = limits processing of dataSets to max given (-1 = all ; 1 = 0,1 ; 2 = 0,1,2 ; etc)
*/
$rrd_details
=
$this
->
getRRDdetails
(
$rrd
)[
"data"
];
$rrd
=
$rrd
.
".xml"
;
// Test data
$xml
=
$this
->
getXMLdata
(
$rrd
);
$data_sets_full
=
$this
->
getDataSetInfo
(
$xml
);
// get dataSet information to include in answer
if
(
$inverse
==
'true'
)
{
$inverse
=
true
;
}
else
{
$inverse
=
false
;
}
if
((
int
)
$detail
>=
0
)
{
for
(
$count
=
count
(
$xml
->
rra
);
$count
>
$detail
;
$count
--
)
{
unset
(
$xml
->
rra
[
$count
]);
}
}
// determine available dataSets within range and how to handle them
$selected_archives
=
$this
->
getSelection
(
$this
->
getDataSetInfo
(
$xml
),
$from
,
$to
,
$max_values
);
// get condensed dataSets and translate them to d3 usable data
$result
=
$this
->
translateD3
(
$this
->
getCondensedArchive
(
$xml
,
$selected_archives
),
$inverse
,
$rrd_details
[
"field_units"
]
);
return
[
"sets"
=>
$data_sets_full
,
"d3"
=>
$result
,
"title"
=>
$rrd_details
[
"title"
],
"x-axis_label"
=>
$rrd_details
[
"x-axis_label"
]
];
// return details and d3 data
}
/**
* Return XML data dump for given rrd
* @param $rrd
* @return \SimpleXMLElement
*/
private
function
getXMLdata
(
$rrd
)
{
# Source: rrdtool dump filename.rrd
$xml
=
simplexml_load_file
(
__DIR__
.
'/../../../../../../../../../../opnsense_gui/test/conf/'
.
$rrd
);
return
$xml
;
}
/**
* Return full archive information
* @param array $xml
* @return array
*/
private
function
getDataSetInfo
(
$xml
)
{
$info
=
array
();
if
(
isset
(
$xml
))
{
$step
=
intval
(
$xml
->
step
);
$lastUpdate
=
intval
(
$xml
->
lastupdate
);
foreach
(
$xml
->
rra
as
$key
=>
$value
)
{
$step_size
=
(
int
)
$value
->
pdp_per_row
*
$step
;
$first
=
floor
((
$lastUpdate
/
$step_size
))
*
$step_size
-
(
$step_size
*
(
count
(
$value
->
database
->
children
())
-
1
));
$last
=
floor
((
$lastUpdate
/
$step_size
))
*
$step_size
;
$firstValue_rowNumber
=
(
int
)
$this
->
findFirstValue
(
$value
);
$firstValue_timestamp
=
(
int
)
$first
+
((
int
)
$firstValue_rowNumber
*
$step_size
);
array_push
(
$info
,
[
"step"
=>
$step
,
"pdp_per_row"
=>
(
int
)
$value
->
pdp_per_row
,
"rowCount"
=>
$this
->
countRows
(
$value
),
"first_timestamp"
=>
(
int
)
$first
,
"last_timestamp"
=>
(
int
)
$last
,
"firstValue_rowNumber"
=>
$firstValue_rowNumber
,
"firstValue_timestamp"
=>
$firstValue_timestamp
,
"available_rows"
=>
(
$this
->
countRows
(
$value
)
-
$firstValue_rowNumber
),
"full_step"
=>
(
$step
*
(
int
)
$value
->
pdp_per_row
),
"recorded_time"
=>
(
$step
*
(
int
)
$value
->
pdp_per_row
)
*
(
$this
->
countRows
(
$value
)
-
$firstValue_rowNumber
)
]);
}
}
return
(
$info
);
}
/**
* Returns row number of first row with values other than 'NaN'
* @param array $data
* @return int
*/
private
function
findFirstValue
(
$data
=
array
())
{
$rowNumber
=
0
;
$containsValues
=
false
;
// used to break foreach on first row with collected data
foreach
(
$data
->
database
->
row
as
$item
=>
$row
)
{
foreach
(
$row
as
$rowKey
=>
$rowVal
)
{
if
(
trim
(
$rowVal
)
!=
"NaN"
)
{
$containsValues
=
true
;
}
}
if
(
$containsValues
==
true
)
{
break
;
}
$rowNumber
++
;
}
return
$rowNumber
;
}
/**
* Return total number of rows in rra
* @param array $data
* @return int
*/
private
function
countRows
(
$data
=
array
())
{
$rowCount
=
0
;
foreach
(
$data
->
database
->
row
as
$item
=>
$row
)
{
$rowCount
++
;
}
return
$rowCount
;
}
/**
* internal: retrieve selections within range (0-0=full range) and limit number of datapoints (max_values)
* @param array $rra_info
* @param int $from_timestamp
* @param int $to_timestamp
* @param $max_values
* @return array
*/
private
function
getSelection
(
$rra_info
=
array
(),
$from_timestamp
=
0
,
$to_timestamp
=
0
,
$max_values
=
120
)
{
$full_range
=
false
;
if
(
$from_timestamp
==
0
&&
$to_timestamp
==
0
)
{
$full_range
=
true
;
$from_timestamp
=
$this
->
getMaxRange
(
$rra_info
)[
"oldest_timestamp"
];
$to_timestamp
=
$this
->
getMaxRange
(
$rra_info
)[
"newest_timestamp"
];
}
$archives
=
array
();
// find archive match
foreach
(
$rra_info
as
$key
=>
$value
)
{
if
(
$from_timestamp
>=
$value
[
'firstValue_timestamp'
]
&&
$to_timestamp
<=
(
$value
[
'last_timestamp'
]
+
$value
[
'full_step'
]))
{
// calculate number of rows in set
$rowCount
=
(
$to_timestamp
-
$from_timestamp
)
/
$value
[
'full_step'
]
+
1
;
// factor to be used to compress the data.
// example if 2 then 2 values will be used to calculate one data point.
$condense_factor
=
round
(
$rowCount
/
$max_values
);
if
(
$condense_factor
==
0
)
{
// if rounded to 0 we will not condense the data
$condense_factor
=
1
;
// and thus return the full set of data points
}
// actual number of rows after compressing/condensing the dataSet
$condensed_rowCount
=
(
int
)(
$rowCount
/
$condense_factor
);
// count the number if rra's (sets), deduct 1 as we need the counter to start at 0
$last_rra_key
=
count
(
$rra_info
)
-
1
;
// dynamic (condensed) values for full overview to detail level
$overview
=
round
(
$rra_info
[
$last_rra_key
][
"available_rows"
]
/
(
int
)
$max_values
);
if
(
$full_range
==
false
)
{
// JSC WIP removed: && count($rra_info)==1 // add detail when selected
array_push
(
$archives
,
[
"key"
=>
$key
,
"condensed_rowCount"
=>
$condensed_rowCount
,
"condense_by"
=>
(
int
)
$condense_factor
,
"type"
=>
"detail"
]);
}
else
{
// add condensed detail
array_push
(
$archives
,
[
"key"
=>
$key
,
"condensed_rowCount"
=>
(
int
)(
$condensed_rowCount
/
(
$rra_info
[
$last_rra_key
][
"pdp_per_row"
]
/
$value
[
"pdp_per_row"
])),
"condense_by"
=>
(
int
)
$condense_factor
*
(
$rra_info
[
$last_rra_key
][
"pdp_per_row"
]
/
$value
[
"pdp_per_row"
]),
"type"
=>
"detail"
]);
}
// search for last dataSet with actual values, used to exclude sets that do not contain data
for
(
$count
=
$last_rra_key
;
$count
>
0
;
$count
--
)
{
if
(
$rra_info
[
$count
][
"available_rows"
]
>
0
)
{
// Found last rra set with values
$last_rra_key
=
$count
;
break
;
}
}
array_push
(
$archives
,
[
"key"
=>
$last_rra_key
,
"condensed_rowCount"
=>
(
int
)(
$rra_info
[
$last_rra_key
][
"available_rows"
]
/
$overview
),
"condense_by"
=>
(
int
)
$overview
,
"type"
=>
"overview"
]);
break
;
}
}
return
([
"from"
=>
$from_timestamp
,
"to"
=>
$to_timestamp
,
"full_range"
=>
$full_range
,
"data"
=>
$archives
]);
}
/**
* internal: get full available range
* @param array $rra_info
* @return array
*/
private
function
getMaxRange
(
$rra_info
=
array
())
{
// count the number if rra's (sets), deduct 1 as we need the counter to start at 0
$last_rra_key
=
count
(
$rra_info
)
-
1
;
for
(
$count
=
$last_rra_key
;
$count
>
0
;
$count
--
)
{
if
(
$rra_info
[
$count
][
"available_rows"
]
>
0
)
{
// Found last rra set with values
$last_rra_key
=
$count
;
break
;
}
}
if
(
isset
(
$rra_info
[
0
]))
{
$last
=
$rra_info
[
0
][
"firstValue_timestamp"
];
$first
=
$rra_info
[
$last_rra_key
][
"firstValue_timestamp"
]
+
$rra_info
[
$last_rra_key
][
"recorded_time"
]
-
$rra_info
[
$last_rra_key
][
"full_step"
];
}
else
{
$first
=
0
;
$last
=
0
;
}
return
[
"newest_timestamp"
=>
$first
,
"oldest_timestamp"
=>
$last
];
}
/**
* translate rrd data to usable format for d3 charts
* @param array $data
* @param boolean $applyInverse
* @return array
*/
private
function
translateD3
(
$data
=
array
(),
$applyInverse
=
false
,
$field_units
)
{
$d3_data
=
array
();
$from_timestamp
=
0
;
$to_timestamp
=
0
;
foreach
(
$data
[
'archive'
]
as
$row
=>
$rowValues
)
{
$timestamp
=
$rowValues
[
'timestamp'
]
*
1000
;
// javascript works with milliseconds
foreach
(
$data
[
'columns'
]
as
$key
=>
$value
)
{
$name
=
$value
[
'name'
];
if
(
$value
[
'type'
]
==
"GAUGE"
)
{
// return values as float
$value
=
$rowValues
[
'condensed_values'
][
$key
];
}
else
{
// return values as int
if
((
string
)
$rowValues
[
'condensed_values'
][
$key
]
!=
"NaN"
)
{
$value
=
(
int
)
$rowValues
[
'condensed_values'
][
$key
];
}
else
{
$value
=
$rowValues
[
'condensed_values'
][
$key
];
}
}
if
(
!
isset
(
$d3_data
[
$key
]))
{
$d3_data
[
$key
]
=
[];
$d3_data
[
$key
][
"area"
]
=
true
;
if
(
isset
(
$field_units
[
$name
]))
{
$d3_data
[
$key
][
"key"
]
=
$name
.
" "
.
$field_units
[
$name
];
}
else
{
$d3_data
[
$key
][
"key"
]
=
$name
;
}
$d3_data
[
$key
][
"values"
]
=
[];
}
if
(
$value
==
"NaN"
)
{
// If first or the last NaN value in series then add a value of 0 for presentation purposes
$nan
=
false
;
if
(
isset
(
$data
[
'archive'
][
$row
-
1
][
'condensed_values'
][
$key
])
&&
(
string
)
$data
[
'archive'
][
$row
-
1
][
'condensed_values'
][
$key
]
!=
"NaN"
)
{
// Translate NaN to 0 as d3chart can't render NaN - (first NaN item before value)
$value
=
0
;
}
elseif
(
isset
(
$data
[
'archive'
][
$row
+
1
][
'condensed_values'
][
$key
])
&&
(
string
)
$data
[
'archive'
][
$row
+
1
][
'condensed_values'
][
$key
]
!=
"NaN"
)
{
$value
=
0
;
// Translate NaN to 0 as d3chart can't render NaN - (last NaN item before value)
}
else
{
$nan
=
true
;
// suppress NaN item as we already drawn a line to 0
}
}
else
{
$nan
=
false
;
// Not a NaN value, so add to list
}
if
(
$applyInverse
==
true
)
{
$check_value
=
$key
/
2
;
// every odd row gets data inversed (* -1)
if
(
$check_value
!=
(
int
)
$check_value
)
{
$value
=
$value
*
-
1
;
}
}
if
(
$nan
==
false
)
{
if
(
$from_timestamp
==
0
||
$timestamp
<
$from_timestamp
)
{
$from_timestamp
=
$timestamp
;
// Actual from_timestamp after condensing and cleaning data
}
if
(
$to_timestamp
==
0
||
$timestamp
>
$to_timestamp
)
{
$to_timestamp
=
$timestamp
;
// Actual to_timestamp after condensing and cleaning data
}
array_push
(
$d3_data
[
$key
][
"values"
],
[
$timestamp
,
$value
]);
}
}
}
// Sort value sets based on timestamp
foreach
(
$d3_data
as
$key
=>
$value
)
{
usort
(
$value
[
"values"
],
array
(
$this
,
"orderByTimestampASC"
));
$d3_data
[
$key
][
"values"
]
=
$value
[
"values"
];
}
return
[
"stepSize"
=>
$data
[
'condensed_step'
],
"from_timestamp"
=>
$from_timestamp
,
"to_timestamp"
=>
$to_timestamp
,
"count"
=>
count
(
$d3_data
[
0
][
'values'
]),
"data"
=>
$d3_data
];
}
private
function
getCondensedArchive
(
$xml
=
array
(),
$selection
=
array
())
{
$key_counter
=
0
;
$info
=
$this
->
getDataSetInfo
(
$xml
);
$count_values
=
0
;
$condensed_row_values
=
array
();
$condensed_archive
=
array
();
$condensed_step
=
0
;
$skip_nan
=
false
;
$selected_archives
=
$selection
[
"data"
];
foreach
(
$xml
->
rra
as
$key
=>
$value
)
{
$calculation_type
=
trim
(
$value
->
cf
);
foreach
(
$value
->
database
as
$db_key
=>
$db_value
)
{
foreach
(
$selected_archives
as
$archKey
=>
$archValue
)
{
if
(
$archValue
[
'key'
]
==
$key_counter
)
{
$rowCount
=
0
;
$condense_counter
=
0
;
$condense
=
$archValue
[
'condense_by'
];
foreach
(
$db_value
as
$rowKey
=>
$rowValues
)
{
if
(
$rowCount
>=
$info
[
$key_counter
][
'firstValue_rowNumber'
])
{
$timestamp
=
$info
[
$key_counter
][
'first_timestamp'
]
+
(
$rowCount
*
$info
[
$key_counter
][
'step'
]
*
$info
[
$key_counter
][
'pdp_per_row'
]);
if
((
$timestamp
>=
$selection
[
"from"
]
&&
$timestamp
<=
$selection
[
"to"
]
&&
$archValue
[
"type"
]
==
"detail"
)
||
(
$archValue
[
"type"
]
==
"overview"
&&
$timestamp
<=
$selection
[
"from"
])
||
(
$archValue
[
"type"
]
==
"overview"
&&
$timestamp
>=
$selection
[
"to"
]))
{
$condense_counter
++
;
// Find smallest step in focus area = detail
if
(
$archValue
[
'type'
]
==
"detail"
&&
$selection
[
"full_range"
]
==
false
)
{
// Set new calculated step size
$condensed_step
=
(
$info
[
$key_counter
][
'full_step'
]
*
$condense
);
}
else
{
if
(
$selection
[
"full_range"
]
==
true
&&
$archValue
[
'type'
]
==
"overview"
)
{
$condensed_step
=
(
$info
[
$key_counter
][
'full_step'
]
*
$condense
);
}
}
$column_counter
=
0
;
if
(
!
isset
(
$condensed_row_values
[
$count_values
]))
{
$condensed_row_values
[
$count_values
]
=
[];
}
foreach
(
$rowValues
->
v
as
$columnKey
=>
$columnValue
)
{
if
(
!
isset
(
$condensed_row_values
[
$count_values
][
$column_counter
]))
{
$condensed_row_values
[
$count_values
][
$column_counter
]
=
0
;
}
if
(
trim
(
$columnValue
)
==
"NaN"
)
{
// skip processing the rest of the values as this set has a NaN value
$skip_nan
=
true
;
$condensed_row_values
[
$count_values
][
$column_counter
]
=
"NaN"
;
}
elseif
(
$skip_nan
==
false
)
{
if
(
$archValue
[
"type"
]
==
"overview"
)
{
// overwrite this values and skip averaging, looks better for overview
$condensed_row_values
[
$count_values
][
$column_counter
]
=
((
float
)
$columnValue
);
}
elseif
(
$calculation_type
==
"AVERAGE"
)
{
// For AVERAGE always add the values
$condensed_row_values
[
$count_values
][
$column_counter
]
+=
(
float
)
$columnValue
;
}
elseif
(
$calculation_type
==
"MINIMUM"
||
$condense_counter
==
1
)
{
// For MINIMUM update value if smaller one found or first
if
(
$condensed_row_values
[
$count_values
][
$column_counter
]
>
(
float
)
$columnValue
)
{
$condensed_row_values
[
$count_values
][
$column_counter
]
=
(
float
)
$columnValue
;
}
}
elseif
(
$calculation_type
==
"MAXIMUM"
||
$condense_counter
==
1
)
{
// For MAXIMUM update value if higher one found or first
if
(
$condensed_row_values
[
$count_values
][
$column_counter
]
<
(
float
)
$columnValue
)
{
$condensed_row_values
[
$count_values
][
$column_counter
]
=
(
float
)
$columnValue
;
}
}
}
$column_counter
++
;
}
if
(
$condense_counter
==
$condense
)
{
foreach
(
$condensed_row_values
[
$count_values
]
as
$crvKey
=>
$crValue
)
{
if
(
$condensed_row_values
[
$count_values
][
$crvKey
]
!=
"NaN"
&&
$calculation_type
==
"AVERAGE"
&&
$archValue
[
"type"
]
!=
"overview"
)
{
// For AVERAGE we need to calculate it,
// dividing by the total number of values collected
$condensed_row_values
[
$count_values
][
$crvKey
]
=
(
float
)
$condensed_row_values
[
$count_values
][
$crvKey
]
/
$condense
;
}
}
$skip_nan
=
false
;
if
(
$info
[
$key_counter
][
'available_rows'
]
>
0
)
{
array_push
(
$condensed_archive
,
[
"timestamp"
=>
$timestamp
-
(
$info
[
$key_counter
][
'step'
]
*
$info
[
$key_counter
][
'pdp_per_row'
]),
"condensed_values"
=>
$condensed_row_values
[
$count_values
]
]);
}
$count_values
++
;
$condense_counter
=
0
;
}
}
}
$rowCount
++
;
}
}
}
}
$key_counter
++
;
}
// get value information to include in set
$column_data
=
array
();
foreach
(
$xml
->
ds
as
$key
=>
$value
)
{
array_push
(
$column_data
,
[
"name"
=>
trim
(
$value
->
name
),
"type"
=>
trim
(
$value
->
type
)]);
}
return
[
"condensed_step"
=>
$condensed_step
,
"columns"
=>
$column_data
,
"archive"
=>
$condensed_archive
];
}
/**
* Custom Compare for usort
* @param $a
* @param $b
* @return mixed
*/
private
function
orderByTimestampASC
(
$a
,
$b
)
{
return
$a
[
0
]
-
$b
[
0
];
}
}
src/opnsense/mvc/app/controllers/OPNsense/SystemHealth/Api/SettingsController.php
0 → 100644
View file @
381fc845
<?php
/**
* Copyright (C) 2015 Deciso B.V. - J. Schellevis
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace
OPNsense\SystemHealth\Api
;
use
\OPNsense\Base\ApiControllerBase
;
/**
* Class SettingsController
* @package OPNsense\SystemHealth
*/
class
SettingsController
extends
ApiControllerBase
{
}
?>
src/opnsense/mvc/app/controllers/OPNsense/SystemHealth/IndexController.php
0 → 100644
View file @
381fc845
<?php
/**
* Copyright (C) 2015 Deciso B.V. - J.Schellevis
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace
OPNsense\SystemHealth
;
/**
* Class IndexController
* @package OPNsense\Proxy
*/
class
IndexController
extends
\OPNsense\Base\IndexController
{
public
function
indexAction
()
{
$this
->
view
->
title
=
"System Health"
;
$this
->
view
->
pick
(
'OPNsense/SystemHealth/index'
);
}
}
src/opnsense/mvc/app/models/OPNsense/SystemHealth/SystemHealth.php
0 → 100644
View file @
381fc845
<?php
/**
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace
OPNsense\SystemHealth
;
use
OPNsense\Base\BaseModel
;
class
Sample
extends
BaseModel
{
}
src/opnsense/mvc/app/models/OPNsense/SystemHealth/SystemHealth.xml
0 → 100644
View file @
381fc845
<model>
<mount>
//OPNsense/systemhealth
</mount>
<description>
(rrd) System Health Settings
</description>
<items>
</items>
</model>
\ No newline at end of file
src/opnsense/mvc/app/views/OPNsense/SystemHealth/index.volt
0 → 100644
View file @
381fc845
<!--
/**
* Copyright (C) 2015 Deciso B.V. - J.Schellevis
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
-->
<!-- nvd3 -->
<link rel="stylesheet" href="/ui/css/nv.d3.css">
<!-- d3 -->
<script type="text/javascript" src="/ui/js/d3.min.js"></script>
<!-- nvd3 -->
<script type="text/javascript" src="/ui/js/nv.d3.min.js"></script>
<!-- System Health -->
<style>
#chart svg {
height: 500px;
}
</style>
<script type="application/javascript">
var chart;
var data = [];
var fetching_data = true;
var current_selection_from = 0;
var current_selection_to = 0;
var disabled = [];
var brushendTimer;
var resizeTimer;
var current_detail = 0;
var csvData = [];
var zoom_buttons;
var rrd="";
// Load data when document is ready
$(document).ready(function () {
getRRDlist();
});
// create our chart
nv.addGraph(function () {
chart = nv.models.lineWithFocusChart()
.margin( {left:70})
.x(function (d) {
return d[0]
})
.y(function (d) {
return d[1]
});
chart.xAxis
.tickFormat(function (d) {
return d3.time.format('%b %e %H:%M')(new Date(d))
});
chart.x2Axis
.tickFormat(function (d) {
return d3.time.format('%x')(new Date(d))
});
chart.yAxis
.tickFormat(d3.format(',.2s'));
chart.y2Axis
.tickFormat(d3.format(',.1s'));
chart.focusHeight(80);
chart.interpolate('step-before');
// dispatch when one of the streams is enabled/disabled
chart.dispatch.on('stateChange', function (e) {
disabled = e['disabled'];
});
// dispatch when the focus area has changed - delay action with 500ms timer
chart.dispatch.on('brush.brushend', function (b) {
window.onresize = null; // clear any pending resize events
if (fetching_data == false) {
if ($('input:radio[name=inverse]:checked').val() == 1) {
inverse = true;
} else {
inverse = false;
}
var detail = $('input:radio[name=detail]:checked').val();
var resolution = $('input:radio[name=resolution]:checked').val();
if ((window.selmin != b.extent[0]) || (window.selmax != b.extent[1])) {
if (current_selection_from * 1000 != b.extent[0] || current_selection_to != b.extent[1]) {
if (brushendTimer) {
clearTimeout(brushendTimer);
}
brushendTimer = setTimeout(function () {
if (chart.xAxis.scale().domain()[0] == b.extent[0] && chart.xAxis.scale().domain()[1] == b.extent[1]) {
getdata(rrd, 0, 0, resolution, detail);
} else {
getdata(rrd, Math.floor(b.extent[0] / 1000), Math.floor(b.extent[1] / 1000), resolution, detail);
}
brushendTimer = null;
}, 500);
}
}
}
});
// dispatch on window resize - delay action with 500ms timer
nv.utils.windowResize(function () {
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(function () {
chart.update();
resizeTimer = null;
}, 500);
});
return chart;
});
// Some options have changed, check and fetch data
function UpdateOptions() {
window.onresize = null; // clear any pending resize events
var inverse = false;
var detail = 0;
var resolution = 120;
if ($('input:radio[name=inverse]:checked').val() == 1) {
inverse = true;
}
detail = $('input:radio[name=detail]:checked').val();
resolution = $('input:radio[name=resolution]:checked').val();
if (detail != current_detail) {
chart.brushExtent([0, 0]);
getdata(rrd, 0, 0, resolution, detail);
current_detail = detail;
} else {
getdata(rrd, current_selection_from, current_selection_to, resolution, detail);
current_detail = detail;
}
}
function getRRDlist() {
ajaxGet(url = "/api/systemhealth/service/getRRDlist/", sendData = {}, callback = function (data, status) {
if (status == "success") {
var category;
var tabs="";
var subitem="";
var active_category=Object.keys(data["data"])[0];
var active_subitem=data["data"][active_category][0];
var rrd_name="";
for ( category in data["data"]) {
if (category == active_category) {
tabs += '<li role="presentation" class="dropdown active">';
} else {
tabs += '<li role="presentation" class="dropdown">';
}
subitem=data["data"][category][0]; // first sub item
if (category=="system") {
rrd_name = category + '-' + subitem;
} else {
rrd_name = subitem + '-' + category;
}
// create dropdown menu
tabs+='<a data-toggle="dropdown" href="#" class="dropdown-toggle pull-right visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block" role="button" style="border-left: 1px dashed lightgray;">';
tabs+='<b><span class="caret"></span></b>';
tabs+='</a>';
tabs+='<a data-toggle="tab" onclick="$(\'#'+rrd_name+'\').click();" class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block" style="border-right:0px;"><b>'+category[0].toUpperCase() + category.slice(1)+'</b></a>';
tabs+='<ul class="dropdown-menu" role="menu">';
rrd_name="";
// add subtabs
for (var count=0; count<data["data"][category].length;++count ) {
subitem=data["data"][category][count];
if (category=="system") {
rrd_name = category + '-' + subitem;
} else {
rrd_name = subitem + '-' + category;
}
if (subitem==active_subitem) {
tabs += '<li class="active"><a data-toggle="tab" onclick="getdata(\''+rrd_name+'\',0,0,120,0);" id="'+rrd_name+'"><i class="fa fa-check-square"></i> ' + subitem[0].toUpperCase() + subitem.slice(1) + '</a></li>';
rrd=rrd_name;
getdata(rrd_name,0,0,120,false,0); // load initial data
} else {
tabs += '<li><a data-toggle="tab" onclick="getdata(\''+rrd_name+'\',0,0,120,0);" id="'+rrd_name+'"><i class="fa fa-check-square"></i> ' + subitem[0].toUpperCase() + subitem.slice(1) + '</a></li>';
}
}
tabs+='</ul>';
tabs+='</li>';
}
$('#maintabs').html(tabs);
$('#tab_1').toggleClass('active');
} else {
alert("Error while fetching RRD list : "+status);
}
});
}
function getdata(rrd_name, from, to, maxitems, detail) {
if (zoom_buttons===undefined) {
zoom_buttons="";
}
// Set defaults if not specified
if (rrd_name === undefined) {
rrd_name = rrd;
disabled = []; // clear disabled stream data
} else {
if ( rrd_name!=rrd ) {
rrd = rrd_name; // set global rrd name to current rrd
disabled = []; // clear disabled stream data
zoom_buttons=""; // clear zoom_buttons
chart.brushExtent([0, 0]); // clear focus area
$('#res0').parent().click(); // reset resolution
}
}
if (from === undefined) {
from = 0;
}
if (to === undefined) {
to = 0;
}
if (maxitems === undefined) {
maxitems = 120;
}
if ($('input:radio[name=inverse]:checked').val() == 1) {
inverse = true;
} else {
inverse = false;
}
if (detail === undefined) {
detail = 0;
}
// Remember selected area
current_selection_from = from;
current_selection_to = to;
// Flag to know when we are fetching data
fetching_data = true;
// Used to set render the zoom/detail buttons
//zoom_buttons = "";
// array used for cvs export option
csvData = [];
// array used for min/max/average table when shown
min_max_average = {};
// info bar - hide averages info bar while refreshing data
$('#averages').hide();
// info bar - show loading info bar while refreshing data
$('#loading').show();
// API call to request data
ajaxGet(url = "/api/systemhealth/service/getSystemHealth/" + rrd_name + "/" + String(from) + "/" + String(to) + "/" + String(maxitems) + "/" + String(inverse) + "/" + String(detail), sendData = {}, callback = function (data, status) {
if (status == "success") {
var stepsize = data["d3"]["stepSize"];
var scale = "{{ lang._('seconds') }}";
var dtformat = '%m/%d %H:%M';
var visable_time=to-from;
// set defaults based on stepsize
if (stepsize >= 86400) {
stepsize = stepsize / 86400;
scale = "{{ lang._('days') }}";
dtformat = '\'%y w%U%';
} else if (stepsize >= 3600) {
stepsize = stepsize / 3600;
scale = "{{ lang._('hours') }}";
dtformat = '\'%y d%j%';
} else if (stepsize >= 60) {
stepsize = stepsize / 60;
scale = "{{ lang._('minutes') }}";
dtformat = '%H:%M';
}
// if we have a focus area then change the x-scale to reflect current view
if (visable_time >= (86400*7)) { // one week
console.log('a');
dtformat = '\'%y w%U%';
} else if (visable_time >= (3600*48)) { // 48 hours
console.log('b');
dtformat = '\'%y d%j%';
} else if (visable_time >= (60*maxitems)) { // max minutes
console.log('c');
dtformat = '%H:%M';
}
// Add zoomlevel buttons/options
if ($('input:radio[name=detail]:checked').val() == undefined || zoom_buttons==="") {
for (setcount = 0; setcount < data["sets"].length; ++setcount) {
recordedtime = data["sets"][setcount]["recorded_time"];
// Find out what text matches best
if (recordedtime >= 31536000) {
detail_text = Math.floor(recordedtime / 31536000).toString() + " {{ lang._('Year(s)') }}";
} else if (recordedtime >= 259200) {
detail_text = Math.floor(recordedtime / 86400).toString() + " {{ lang._('Days') }}";
} else if (recordedtime > 3600) {
detail_text = Math.floor(recordedtime / 3600).toString() + " {{ lang._('Hours') }}";
} else {
detail_text = Math.floor(recordedtime / 60).toString() + " {{ lang._('Minutes') }}";
}
if (setcount == 0) {
zoom_buttons += '<label class="btn btn-default active"> <input type="radio" id="d' + setcount.toString() + '" name="detail" checked="checked" value="' + setcount.toString() + '" /> ' + detail_text + ' </label>';
} else {
zoom_buttons += '<label class="btn btn-default"> <input type="radio" id="d' + setcount.toString() + '" name="detail" value="' + setcount.toString() + '" /> ' + detail_text + ' </label>';
}
}
if (zoom_buttons === "") {
zoom_buttons = "<b>No data available</b>";
}
// insert zoom buttons html code
$('#zoom').html(zoom_buttons);
}
$('#stepsize').text(stepsize + " " + scale);
// Check for enabled or disabled stream, to make sure that same set stays selected after update
for (index = 0; index < disabled.length; ++index) {
window.resize = null;
data["d3"]["data"][index]["disabled"] = disabled[index]; // disable stream if it was disabled before updating dataset
}
// Create tables (general and detail)
if ($('input:radio[name=show_table]:checked').val() == 1) { // check if togle table is on
table_head = "<th>#</th>";
if ($('input:radio[name=toggle_time]:checked').val() == 1) {
table_head += "<th>{{ lang._('full date & time') }}</th>";
} else {
table_head += "<th>{{ lang._('timestamp') }}</th>";
}
// Setup variables for table data
var table_head; // used for table headings in html format
var table_row_data = {}; // holds row data for table
var table_view_rows = ""; // holds row data in html format
var keyname = ""; // used for name of key
var rowcounter = 0;// general row counter
var min; // holds calculated minimum value
var max; // holds calculated maximum value
var average; // holds calculated average
var t; // general date/time variable
var item; // used for name of key
var counter = 1; // used for row count
for (index = 0; index < data["d3"]["data"].length; ++index) {
rowcounter = 0;
min = 0;
max = 0;
average = 0;
if (data["d3"]["data"][index]["disabled"] != true) {
table_head += '<th>' + data["d3"]["data"][index]["key"] + '</th>';
keyname = data["d3"]["data"][index]["key"].toString();
for (var value_index = 0; value_index < data["d3"]["data"][index]["values"].length; ++value_index) {
if (data["d3"]["data"][index]["values"][value_index][0] >= (from * 1000) && data["d3"]["data"][index]["values"][value_index][0] <= (to * 1000) || ( from == 0 && to == 0 )) {
if (table_row_data[data["d3"]["data"][index]["values"][value_index][0]] === undefined) {
table_row_data[data["d3"]["data"][index]["values"][value_index][0]] = {};
}
if (table_row_data[data["d3"]["data"][index]["values"][value_index][0]][data["d3"]["data"][index]["key"]] === undefined) {
table_row_data[data["d3"]["data"][index]["values"][value_index][0]][data["d3"]["data"][index]["key"]] = data["d3"]["data"][index]["values"][value_index][1];
}
if (csvData[rowcounter] === undefined) {
csvData[rowcounter] = {};
}
if (csvData[rowcounter]["timestamp"] === undefined) {
t = new Date(parseInt(data["d3"]["data"][index]["values"][value_index][0]));
csvData[rowcounter]["timestamp"] = data["d3"]["data"][index]["values"][value_index][0] / 1000;
csvData[rowcounter]["date_time"] = t.toString();
}
csvData[rowcounter][keyname] = data["d3"]["data"][index]["values"][value_index][1];
if (data["d3"]["data"][index]["values"][value_index][1] < min) {
min = data["d3"]["data"][index]["values"][value_index][1];
}
if (data["d3"]["data"][index]["values"][value_index][1] > max) {
max = data["d3"]["data"][index]["values"][value_index][1];
}
average += data["d3"]["data"][index]["values"][value_index][1];
++rowcounter;
}
}
if (min_max_average[keyname] === undefined) {
min_max_average[keyname] = {};
min_max_average[keyname]["min"] = min;
min_max_average[keyname]["max"] = max;
min_max_average[keyname]["average"] = average / rowcounter;
}
}
}
for ( item in min_max_average) {
table_view_rows += "<tr>";
table_view_rows += "<td>" + item + "</td>";
table_view_rows += "<td>" + min_max_average[item]["min"].toString() + "</td>";
table_view_rows += "<td>" + min_max_average[item]["max"].toString() + "</td>";
table_view_rows += "<td>" + min_max_average[item]["average"].toString() + "</td>";
table_view_rows += "</tr>";
}
$('#table_view_general_heading').html('<th>item</th><th>min</th><th>max</th><th>average</th>');
$('#table_view_general_rows').html(table_view_rows);
table_view_rows = "";
for ( item in table_row_data) {
if ($('input:radio[name=toggle_time]:checked').val() == 1) {
t = new Date(parseInt(item));
table_view_rows += "<tr><td>" + counter.toString() + "</td><td>" + t.toString() + "</td>";
} else {
table_view_rows += "<tr><td>" + counter.toString() + "</td><td>" + parseInt(item / 1000).toString() + "</td>";
}
for (var value in table_row_data[item]) {
table_view_rows += "<td>" + table_row_data[item][value] + "</td>";
}
++counter;
table_view_rows += "</tr>";
}
$('#table_view_heading').html(table_head);
$('#table_view_rows').html(table_view_rows);
$('#chart_details_table').show();
$('#chart_general_table').show();
} else {
$('#chart_details_table').hide();
$('#chart_general_table').hide();
}
chart.xAxis
.tickFormat(function (d) {
return d3.time.format(dtformat)(new Date(d))
});
chart.yAxis.axisLabel(data["x-axis_label"]);
chart.useInteractiveGuideline(true);
chart.interactive(true);
d3.select('#chart svg')
.datum(data["d3"]["data"])
.transition().duration(0)
.call(chart);
chart.update();
window.onresize = null; // clear any pending resize events
fetching_data = false;
$('#loading').hide(); // Data has been found and chart will be drawn
$('#averages').show();
if (data["title"]!="") {
$('#chart_title').show();
$('#chart_title').text(data["title"]);
} else
{
$('#chart_title').hide();
}
} else {
alert("Error while fetching data : "+status);
}
});
}
// convert a data Array to CSV format
function convertToCSV(args) {
var result, ctr, keys, columnDelimiter, lineDelimiter, data;
data = args.data || null;
if (data == null || !data.length) {
return null;
}
columnDelimiter = args.columnDelimiter || ';';
lineDelimiter = args.lineDelimiter || '\n';
keys = Object.keys(data[0]);
result = '';
result += keys.join(columnDelimiter);
result += lineDelimiter;
data.forEach(function (item) {
ctr = 0;
keys.forEach(function (key) {
if (ctr > 0) result += columnDelimiter;
result += item[key];
ctr++;
});
result += lineDelimiter;
});
return result;
}
// download CVS file
function downloadCSV(args) {
var data, filename, link;
var csv = convertToCSV({
data: csvData
});
if (csv == null) return;
console.log(csv);
filename = args.filename || 'export.csv';
if (!csv.match(/^data:text\/csv/i)) {
csv = 'data:text/csv;charset=utf-8,' + csv;
}
data = encodeURI(csv);
link = document.createElement('a');
link.href = data;
link.target = '_blank';
link.download = filename;
document.body.appendChild(link);
link.click();
}
</script>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
{# Tab Content #}
</ul>
<div class="content-box tab-content">
<div id="tab_1" class="tab-pane fade in">
<div class="panel panel-primary">
<div class="panel-heading"><b>{{ lang._('Options') }}</b></div>
<div class="panel-body">
<div class="container-fluid">
<div class="row">
<div class="col-md-12"></div>
<div class="col-md-4">
<b>{{ lang._('Zoom level') }}:</b>
<form onChange="UpdateOptions()">
<div class="btn-group" data-toggle="buttons" id="zoom">
<!-- The zoom buttons are generated based upon the current dataset -->
</div>
</form>
</div>
<div class="col-md-2">
<b>{{ lang._('Inverse') }}:</b>
<form onChange="UpdateOptions()">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default active">
<input type="radio" id="in0" name="inverse" checked="checked" value="0"/> {{
lang._('Off') }}
</label>
<label class="btn btn-default">
<input type="radio" id="in1" name="inverse" value="1"/> {{ lang._('On') }}
</label>
</div>
</form>
</div>
<div class="col-md-4">
<b>{{ lang._('Resolution') }}:</b>
<form onChange="UpdateOptions()">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default active">
<input type="radio" id="res0" name="resolution" checked="checked" value="120"/>
{{ lang._('Standard') }}
</label>
<label class="btn btn-default">
<input type="radio" id="res1" name="resolution" value="240"/> {{
lang._('Medium') }}
</label>
<label class="btn btn-default">
<input type="radio" id="res2" name="resolution" value="600"/> {{ lang._('High')
}}
</label>
</div>
</form>
</div>
<div class="col-md-2">
<b>{{ lang._('Show Tables') }}:</b>
<form onChange="UpdateOptions()">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default active">
<input type="radio" id="tab0" name="show_table" checked="checked" value="0"/> {{
lang._('Off') }}
</label>
<label class="btn btn-default">
<input type="radio" id="tab1" name="show_table" value="1"/> {{ lang._('On') }}
</label>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div id="averages" class="alert alert-info" role="alert">
<b>{{ lang._('Current detail is showing') }} <span id="stepsize"></span> {{ lang._('averages') }}.</b>
</div>
<div id="loading" class="alert bg-primary"><i class="fa fa-spinner fa-spin"></i>
<b>{{ lang._('Please wait while loading data...') }}</b>
</div>
<!--<div id="stepsize"></div>-->
<!-- place holder for the chart itself -->
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title" id="chart_title">
</h3>
</div>
<div class="panel-body">
<div id="chart">
<svg></svg>
</div>
</div>
</div>
<!-- place holder for the general table with min/max/averages, is hidden by default -->
<div id="chart_general_table" class="col-md-12" style="display: none;">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"> {{ lang._('Current View - Overview') }}</h3>
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-condensed table-hover table-striped">
<thead>
<tr id="table_view_general_heading" class="active">
<!-- Dynamic data -->
</tr>
</thead>
<tbody id="table_view_general_rows">
<!-- Dynamic data -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div id="chart_details_table" class="col-md-12" style="display: none;">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"> {{ lang._('Current View - Details') }}</h3>
</div>
<div class="panel-body">
<div class="btn-toolbar" role="toolbar">
<i>{{ lang._('Toggle Timeview') }}:</i>
<form onChange="UpdateOptions();">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-xs btn-default active">
<input type="radio" id="time0" name="toggle_time" checked="checked" value="0"/> {{
lang._('Timestamp') }}
</label>
<label class="btn btn-xs btn-default">
<input type="radio" id="time1" name="toggle_time" value="1"/> {{ lang._('Full Date &
Time') }}
</label>
</div>
</form>
<div class="btn btn-xs btn-primary inline"
onclick='downloadCSV({ filename: rrd+".csv" });'><i
class="glyphicon glyphicon-download-alt"></i>{{ lang._('Download as CSV') }}
</div>
</div>
<div class="table-responsive">
<table class="table table-condensed table-hover table-striped">
<thead>
<tr id="table_view_heading" class="active">
<!-- Dynamic data -->
</tr>
</thead>
<tbody id="table_view_rows">
<!-- Dynamic data -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
src/opnsense/scripts/systemhealth/metadata/system-processor.xml
0 → 100644
View file @
381fc845
<?xml version="1.0"?>
<details>
<title>
System Information - Utilization and Processes
</title>
<x-axis_label>
[U]tilization , [#]Number
</x-axis_label>
<field_units>
<user>
[U]
</user>
<nice>
[U]
</nice>
<system>
[U]
</system>
<interrupt>
[#]
</interrupt>
<processes>
[#]
</processes>
</field_units>
</details>
\ No newline at end of file
src/opnsense/scripts/systemhealth/queryDetails.py
0 → 100755
View file @
381fc845
#!/usr/local/bin/python2.7
"""
Copyright (c) 2015 Deciso B.V. - J. Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------------
"""
file
=
open
(
'/Users/josschellevis/Development/opnsense/scripts/systemhealth/metadata/system-processor.xml'
,
'r'
)
file_contents
=
file
.
read
()
print
(
file_contents
)
file
.
close
()
src/opnsense/service/conf/actions.d/actions_systemhealth.conf
0 → 100644
View file @
381fc845
[
query
.
details
]
command
:/
Users
/
josschellevis
/
Development
/
opnsense
/
scripts
/
systemhealth
/
queryDetails
.
py
parameters
:
type
:
script_output
message
:
request
rrd
graph
details
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment