diff --git a/Gemfile b/Gemfile new file mode 100755 index 0000000..e88555c --- /dev/null +++ b/Gemfile @@ -0,0 +1,10 @@ +source "https://rubygems.org" + +# Declare your gem's dependencies in #{yourplugin}.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# assume jquery-rails will be used +gem "jquery-rails" + diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..cd8aa11 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +Your plugin license! + +Copyright 2013 Amahi, http://www.amahi.org + +This software is licensed under the AGPL v3 +https://www.gnu.org/licenses/agpl + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.rdoc b/README.rdoc new file mode 100755 index 0000000..8fba33b --- /dev/null +++ b/README.rdoc @@ -0,0 +1,3 @@ += Amahi plugin Disks + +Disk information for the Amahi platform diff --git a/Rakefile b/Rakefile new file mode 100755 index 0000000..6e87172 --- /dev/null +++ b/Rakefile @@ -0,0 +1,38 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end +begin + require 'rdoc/task' +rescue LoadError + require 'rdoc/rdoc' + require 'rake/rdoctask' + RDoc::Task = Rake::RDocTask +end + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Amahi Plugin' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +# APP_RAKEFILE = File.expand_path("../../../Rakefile", __FILE__) +# load 'rails/tasks/engine.rake' + +Bundler::GemHelper.install_tasks + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task :default => :test diff --git a/app/assets/images/.gitkeep b/app/assets/images/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/app/assets/javascripts/chartkick.js b/app/assets/javascripts/chartkick.js new file mode 100755 index 0000000..9b90e4c --- /dev/null +++ b/app/assets/javascripts/chartkick.js @@ -0,0 +1,758 @@ +/* + * Chartkick.js + * Create beautiful Javascript charts with minimal code + * https://github.com/ankane/chartkick.js + * v1.2.1 + * MIT License + */ + +/*jslint browser: true, indent: 2, plusplus: true, vars: true */ + +(function (window) { + 'use strict'; + + var Chartkick, ISO8601_PATTERN, DECIMAL_SEPARATOR, adapters = []; + + var $ = window.jQuery || window.Zepto || window.$; + + // helpers + + function isArray(variable) { + return Object.prototype.toString.call(variable) === "[object Array]"; + } + + function isFunction(variable) { + return variable instanceof Function; + } + + function isPlainObject(variable) { + return !isFunction(variable) && variable instanceof Object; + } + + // https://github.com/madrobby/zepto/blob/master/src/zepto.js + function extend(target, source) { + var key; + for (key in source) { + if (isPlainObject(source[key]) || isArray(source[key])) { + if (isPlainObject(source[key]) && !isPlainObject(target[key])) { + target[key] = {}; + } + if (isArray(source[key]) && !isArray(target[key])) { + target[key] = []; + } + extend(target[key], source[key]); + } else if (source[key] !== undefined) { + target[key] = source[key]; + } + } + } + + function merge(obj1, obj2) { + var target = {}; + extend(target, obj1); + extend(target, obj2); + return target; + } + + // https://github.com/Do/iso8601.js + ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i; + DECIMAL_SEPARATOR = String(1.5).charAt(1); + + function parseISO8601(input) { + var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year; + type = Object.prototype.toString.call(input); + if (type === '[object Date]') { + return input; + } + if (type !== '[object String]') { + return; + } + if (matches = input.match(ISO8601_PATTERN)) { + year = parseInt(matches[1], 10); + month = parseInt(matches[3], 10) - 1; + day = parseInt(matches[5], 10); + hour = parseInt(matches[7], 10); + minutes = matches[9] ? parseInt(matches[9], 10) : 0; + seconds = matches[11] ? parseInt(matches[11], 10) : 0; + milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0; + result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds); + if (matches[13] && matches[14]) { + offset = matches[15] * 60; + if (matches[17]) { + offset += parseInt(matches[17], 10); + } + offset *= matches[14] === '-' ? -1 : 1; + result -= offset * 60 * 1000; + } + return new Date(result); + } + } + // end iso8601.js + + function negativeValues(series) { + var i, j, data; + for (i = 0; i < series.length; i++) { + data = series[i].data; + for (j = 0; j < data.length; j++) { + if (data[j][1] < 0) { + return true; + } + } + } + return false; + } + + function jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked) { + return function (series, opts, chartOptions) { + var options = merge({}, defaultOptions); + options = merge(options, chartOptions || {}); + + // hide legend + // this is *not* an external option! + if (opts.hideLegend) { + hideLegend(options); + } + + // min + if ("min" in opts) { + setMin(options, opts.min); + } else if (!negativeValues(series)) { + setMin(options, 0); + } + + // max + if ("max" in opts) { + setMax(options, opts.max); + } + + if (opts.stacked) { + setStacked(options); + } + + if (opts.colors) { + options.colors = opts.colors; + } + + // merge library last + options = merge(options, opts.library || {}); + + return options; + }; + } + + function setText(element, text) { + if (document.body.innerText) { + element.innerText = text; + } else { + element.textContent = text; + } + } + + function chartError(element, message) { + setText(element, "Error Loading Chart: " + message); + element.style.color = "#ff0000"; + } + + function getJSON(element, url, success) { + $.ajax({ + dataType: "json", + url: url, + success: success, + error: function (jqXHR, textStatus, errorThrown) { + var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message; + chartError(element, message); + } + }); + } + + function errorCatcher(chart, callback) { + try { + callback(chart); + } catch (err) { + chartError(chart.element, err.message); + throw err; + } + } + + function fetchDataSource(chart, callback) { + if (typeof chart.dataSource === "string") { + getJSON(chart.element, chart.dataSource, function (data, textStatus, jqXHR) { + chart.data = data; + errorCatcher(chart, callback); + }); + } else { + chart.data = chart.dataSource; + errorCatcher(chart, callback); + } + } + + // type conversions + + function toStr(n) { + return "" + n; + } + + function toFloat(n) { + return parseFloat(n); + } + + function toDate(n) { + if (typeof n !== "object") { + if (typeof n === "number") { + n = new Date(n * 1000); // ms + } else { // str + // try our best to get the str into iso8601 + // TODO be smarter about this + var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z"); + n = parseISO8601(str) || new Date(n); + } + } + return n; + } + + function toArr(n) { + if (!isArray(n)) { + var arr = [], i; + for (i in n) { + if (n.hasOwnProperty(i)) { + arr.push([i, n[i]]); + } + } + n = arr; + } + return n; + } + + function sortByTime(a, b) { + return a[0].getTime() - b[0].getTime(); + } + + if ("Highcharts" in window) { + var HighchartsAdapter = new function () { + var Highcharts = window.Highcharts; + + var defaultOptions = { + chart: {}, + xAxis: { + labels: { + style: { + fontSize: "12px" + } + } + }, + yAxis: { + title: { + text: null + }, + labels: { + style: { + fontSize: "12px" + } + } + }, + title: { + text: null + }, + credits: { + enabled: false + }, + legend: { + borderWidth: 0 + }, + tooltip: { + style: { + fontSize: "12px" + } + }, + plotOptions: { + areaspline: {}, + series: { + marker: {} + } + } + }; + + var hideLegend = function (options) { + options.legend.enabled = false; + }; + + var setMin = function (options, min) { + options.yAxis.min = min; + }; + + var setMax = function (options, max) { + options.yAxis.max = max; + }; + + var setStacked = function (options) { + options.plotOptions.series.stacking = "normal"; + }; + + var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked); + + this.renderLineChart = function (chart, chartType) { + chartType = chartType || "spline"; + var chartOptions = {}; + if (chartType === "areaspline") { + chartOptions = { + plotOptions: { + areaspline: { + stacking: "normal" + }, + series: { + marker: { + enabled: false + } + } + } + }; + } + var options = jsOptions(chart.data, chart.options, chartOptions), data, i, j; + options.xAxis.type = chart.options.discrete ? "category" : "datetime"; + options.chart.type = chartType; + options.chart.renderTo = chart.element.id; + + var series = chart.data; + for (i = 0; i < series.length; i++) { + data = series[i].data; + if (!chart.options.discrete) { + for (j = 0; j < data.length; j++) { + data[j][0] = data[j][0].getTime(); + } + } + series[i].marker = {symbol: "circle"}; + } + options.series = series; + new Highcharts.Chart(options); + }; + + this.renderPieChart = function (chart) { + var chartOptions = {}; + if (chart.options.colors) { + chartOptions.colors = chart.options.colors; + } + var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); + options.chart.renderTo = chart.element.id; + options.series = [{ + type: "pie", + name: "Value", + data: chart.data + }]; + new Highcharts.Chart(options); + }; + + this.renderColumnChart = function (chart, chartType) { + var chartType = chartType || "column"; + var series = chart.data; + var options = jsOptions(series, chart.options), i, j, s, d, rows = []; + options.chart.type = chartType; + options.chart.renderTo = chart.element.id; + + for (i = 0; i < series.length; i++) { + s = series[i]; + + for (j = 0; j < s.data.length; j++) { + d = s.data[j]; + if (!rows[d[0]]) { + rows[d[0]] = new Array(series.length); + } + rows[d[0]][i] = d[1]; + } + } + + var categories = []; + for (i in rows) { + if (rows.hasOwnProperty(i)) { + categories.push(i); + } + } + options.xAxis.categories = categories; + + var newSeries = []; + for (i = 0; i < series.length; i++) { + d = []; + for (j = 0; j < categories.length; j++) { + d.push(rows[categories[j]][i] || 0); + } + + newSeries.push({ + name: series[i].name, + data: d + }); + } + options.series = newSeries; + + new Highcharts.Chart(options); + }; + + var self = this; + + this.renderBarChart = function (chart) { + self.renderColumnChart(chart, "bar"); + }; + + this.renderAreaChart = function (chart) { + self.renderLineChart(chart, "areaspline"); + }; + }; + adapters.push(HighchartsAdapter); + } + if ("google" in window) { + var GoogleChartsAdapter = new function () { + var google = window.google; + + // load from google + var loaded = false; + google.setOnLoadCallback(function () { + loaded = true; + }); + google.load("visualization", "1.0", {"packages": ["corechart"]}); + + var waitForLoaded = function (callback) { + google.setOnLoadCallback(callback); // always do this to prevent race conditions (watch out for other issues due to this) + if (loaded) { + callback(); + } + }; + + // Set chart options + var defaultOptions = { + chartArea: {}, + fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif", + pointSize: 6, + legend: { + textStyle: { + fontSize: 12, + color: "#444" + }, + alignment: "center", + position: "right" + }, + curveType: "function", + hAxis: { + textStyle: { + color: "#666", + fontSize: 12 + }, + gridlines: { + color: "transparent" + }, + baselineColor: "#ccc", + viewWindow: {} + }, + vAxis: { + textStyle: { + color: "#666", + fontSize: 12 + }, + baselineColor: "#ccc", + viewWindow: {} + }, + tooltip: { + textStyle: { + color: "#666", + fontSize: 12 + } + } + }; + + var hideLegend = function (options) { + options.legend.position = "none"; + }; + + var setMin = function (options, min) { + options.vAxis.viewWindow.min = min; + }; + + var setMax = function (options, max) { + options.vAxis.viewWindow.max = max; + }; + + var setBarMin = function (options, min) { + options.hAxis.viewWindow.min = min; + }; + + var setBarMax = function (options, max) { + options.hAxis.viewWindow.max = max; + }; + + var setStacked = function (options) { + options.isStacked = true; + }; + + var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked); + + // cant use object as key + var createDataTable = function (series, columnType) { + var data = new google.visualization.DataTable(); + data.addColumn(columnType, ""); + + var i, j, s, d, key, rows = []; + for (i = 0; i < series.length; i++) { + s = series[i]; + data.addColumn("number", s.name); + + for (j = 0; j < s.data.length; j++) { + d = s.data[j]; + key = (columnType === "datetime") ? d[0].getTime() : d[0]; + if (!rows[key]) { + rows[key] = new Array(series.length); + } + rows[key][i] = toFloat(d[1]); + } + } + + var rows2 = []; + for (i in rows) { + if (rows.hasOwnProperty(i)) { + rows2.push([(columnType === "datetime") ? new Date(toFloat(i)) : i].concat(rows[i])); + } + } + if (columnType === "datetime") { + rows2.sort(sortByTime); + } + data.addRows(rows2); + + return data; + }; + + var resize = function (callback) { + if (window.attachEvent) { + window.attachEvent("onresize", callback); + } else if (window.addEventListener) { + window.addEventListener("resize", callback, true); + } + callback(); + }; + + this.renderLineChart = function (chart) { + waitForLoaded(function () { + var options = jsOptions(chart.data, chart.options); + var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime"); + chart.chart = new google.visualization.LineChart(chart.element); + resize(function () { + chart.chart.draw(data, options); + }); + }); + }; + + this.renderPieChart = function (chart) { + waitForLoaded(function () { + var chartOptions = { + chartArea: { + top: "10%", + height: "80%" + } + }; + if (chart.options.colors) { + chartOptions.colors = chart.options.colors; + } + var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); + + var data = new google.visualization.DataTable(); + data.addColumn("string", ""); + data.addColumn("number", "Value"); + data.addRows(chart.data); + + chart.chart = new google.visualization.PieChart(chart.element); + resize(function () { + chart.chart.draw(data, options); + }); + }); + }; + + this.renderColumnChart = function (chart) { + waitForLoaded(function () { + var options = jsOptions(chart.data, chart.options); + var data = createDataTable(chart.data, "string"); + chart.chart = new google.visualization.ColumnChart(chart.element); + resize(function () { + chart.chart.draw(data, options); + }); + }); + }; + + this.renderBarChart = function (chart) { + waitForLoaded(function () { + var chartOptions = { + hAxis: { + gridlines: { + color: "#ccc" + } + } + }; + var options = jsOptionsFunc(defaultOptions, hideLegend, setBarMin, setBarMax, setStacked)(chart.data, chart.options, chartOptions); + var data = createDataTable(chart.data, "string"); + chart.chart = new google.visualization.BarChart(chart.element); + resize(function () { + chart.chart.draw(data, options); + }); + }); + }; + + this.renderAreaChart = function (chart) { + waitForLoaded(function () { + var chartOptions = { + isStacked: true, + pointSize: 0, + areaOpacity: 0.5 + }; + var options = jsOptions(chart.data, chart.options, chartOptions); + var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime"); + chart.chart = new google.visualization.AreaChart(chart.element); + resize(function () { + chart.chart.draw(data, options); + }); + }); + }; + + this.renderGeoChart = function (chart) { + waitForLoaded(function () { + var chartOptions = { + legend: "none", + colorAxis: { + colors: chart.options.colors || ["#f6c7b6", "#ce502d"] + } + }; + var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); + + var data = new google.visualization.DataTable(); + data.addColumn("string", ""); + data.addColumn("number", "Value"); + data.addRows(chart.data); + + chart.chart = new google.visualization.GeoChart(chart.element); + resize(function () { + chart.chart.draw(data, options); + }); + }); + }; + }; + adapters.push(GoogleChartsAdapter); + } + + // TODO add adapter option + // TODO remove chartType if cross-browser way + // to get the name of the chart class + function renderChart(chartType, chart) { + var i, adapter, fnName; + fnName = "render" + chartType + "Chart"; + + for (i = 0; i < adapters.length; i++) { + adapter = adapters[i]; + if (isFunction(adapter[fnName])) { + return adapter[fnName](chart); + } + } + throw new Error("No adapter found"); + } + + // process data + + function processSeries(series, opts, time) { + var i, j, data, r, key; + + // see if one series or multiple + if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) { + series = [{name: "Value", data: series}]; + opts.hideLegend = true; + } else { + opts.hideLegend = false; + } + if (opts.discrete) { + time = false; + } + + // right format + for (i = 0; i < series.length; i++) { + data = toArr(series[i].data); + r = []; + for (j = 0; j < data.length; j++) { + key = data[j][0]; + key = time ? toDate(key) : toStr(key); + r.push([key, toFloat(data[j][1])]); + } + if (time) { + r.sort(sortByTime); + } + series[i].data = r; + } + + return series; + } + + function processSimple(data) { + var perfectData = toArr(data), i; + for (i = 0; i < perfectData.length; i++) { + perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])]; + } + return perfectData; + } + + function processLineData(chart) { + chart.data = processSeries(chart.data, chart.options, true); + renderChart("Line", chart); + } + + function processColumnData(chart) { + chart.data = processSeries(chart.data, chart.options, false); + renderChart("Column", chart); + } + + function processPieData(chart) { + chart.data = processSimple(chart.data); + renderChart("Pie", chart); + } + + function processBarData(chart) { + chart.data = processSeries(chart.data, chart.options, false); + renderChart("Bar", chart); + } + + function processAreaData(chart) { + chart.data = processSeries(chart.data, chart.options, true); + renderChart("Area", chart); + } + + function processGeoData(chart) { + chart.data = processSimple(chart.data); + renderChart("Geo", chart); + } + + function setElement(chart, element, dataSource, opts, callback) { + if (typeof element === "string") { + element = document.getElementById(element); + } + chart.element = element; + chart.options = opts || {}; + chart.dataSource = dataSource; + Chartkick.charts[element.id] = chart; + fetchDataSource(chart, callback); + } + + // define classes + + Chartkick = { + LineChart: function (element, dataSource, opts) { + setElement(this, element, dataSource, opts, processLineData); + }, + PieChart: function (element, dataSource, opts) { + setElement(this, element, dataSource, opts, processPieData); + }, + ColumnChart: function (element, dataSource, opts) { + setElement(this, element, dataSource, opts, processColumnData); + }, + BarChart: function (element, dataSource, opts) { + setElement(this, element, dataSource, opts, processBarData); + }, + AreaChart: function (element, dataSource, opts) { + setElement(this, element, dataSource, opts, processAreaData); + }, + GeoChart: function (element, dataSource, opts) { + setElement(this, element, dataSource, opts, processGeoData); + }, + charts: {} + }; + + window.Chartkick = Chartkick; +}(window)); diff --git a/app/assets/javascripts/disks.js b/app/assets/javascripts/disks.js new file mode 100755 index 0000000..7c8cd21 --- /dev/null +++ b/app/assets/javascripts/disks.js @@ -0,0 +1,7 @@ +$(document).ready(function() { + + $("#disk-table-head").click(function() { + + $("#disk-table-body").slideToggle(); + }); +}); diff --git a/app/assets/javascripts/disks.js.coffee b/app/assets/javascripts/disks.js.coffee new file mode 100755 index 0000000..e69de29 diff --git a/app/assets/stylesheets/disks.css b/app/assets/stylesheets/disks.css new file mode 100755 index 0000000..76ec769 --- /dev/null +++ b/app/assets/stylesheets/disks.css @@ -0,0 +1,22 @@ +.center_block { + margin-left: auto; + margin-right: auto; + width: 70%; +} + +#disk-table-head { + cursor: pointer; +} + +#disk-table-body { + display: none; +} + +#content { + overflow: hidden; +} + +.td_box { + width: 40%; +} + diff --git a/app/assets/stylesheets/disks.css.scss b/app/assets/stylesheets/disks.css.scss new file mode 100755 index 0000000..9c6d4a0 --- /dev/null +++ b/app/assets/stylesheets/disks.css.scss @@ -0,0 +1,14 @@ + +#disks-table{ + td.cool { + color:green; + } + td.warm{ + color:orange; + } + td.hot { + color:red; + font-weight: bold; + } + a { color: #777; } +} diff --git a/app/assets/stylesheets/disks.css~ b/app/assets/stylesheets/disks.css~ new file mode 100755 index 0000000..d231162 --- /dev/null +++ b/app/assets/stylesheets/disks.css~ @@ -0,0 +1,18 @@ + +.center_block { + margin-left: auto; + margin-right: auto; + width: 70%; +} + +#disk-table-head { + cursor: pointer; +} + +#disk-table-body { + display: none; +} + +#content { + overflow: hidden; +} diff --git a/app/controllers/disks_info_controller.rb b/app/controllers/disks_info_controller.rb new file mode 100755 index 0000000..6d3cd24 --- /dev/null +++ b/app/controllers/disks_info_controller.rb @@ -0,0 +1,23 @@ +class DisksInfoController < ApplicationController + before_filter :admin_required + + def index + @page_title = t('disks') + unless use_sample_data? + @disks = DiskUtils.stats + else + # NOTE: this is to get sample fake data in development + @disks = SampleData.load('disks') + end + end + + def mounts + @page_title =t('disks') + unless use_sample_data? + @mounts = DiskUtils.mounts + else + # NOTE: this is to get sample fake data in development + @mounts = SampleData.load('mounts') + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100755 index 0000000..de6be79 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/views/disks_info/index.html.slim b/app/views/disks_info/index.html.slim new file mode 100755 index 0000000..4140f55 --- /dev/null +++ b/app/views/disks_info/index.html.slim @@ -0,0 +1,22 @@ +.settings-table#disks-table + table.settings + thead + tr + th.settings-col1= t 'model' + th= t 'device' + th + = t 'temperature' + |  ( + = link_to_function "F", '$("#disks-table .temperature").toggle()', class: 'temperature', style: 'display:none' + = link_to_function "C", '$("#disks-table .temperature").toggle()', class: 'temperature' + | ) + tbody + - @disks.each do | disk | + tr class=cycle("odd", "even") + td.settings-col1 = disk[:model] + td = disk[:device] + td[class=disk[:tempcolor]] + span.temperature + = disk[:temp_c] + span.temperature[style="display:none"] + = disk[:temp_f] diff --git a/app/views/disks_info/mounts.html.slim b/app/views/disks_info/mounts.html.slim new file mode 100755 index 0000000..202d654 --- /dev/null +++ b/app/views/disks_info/mounts.html.slim @@ -0,0 +1,96 @@ += javascript_include_tag "//www.google.com/jsapi", "chartkick" +.settings-table#disks-table + table.settings + thead#disk-table-head + tr + th.settings-col1= t 'partition' + th = t 'total_space' + th = t 'free_space' + th = t 'used_space' + th = t 'mount point' + tbody#disk-table-body + -if @mounts.any? + - total_space = 0 + - used_space = 0 + - free_space = 0 + - @mounts.each do | mount | + tr class=cycle("odd", "even") + - total_space += mount[:bytes] + - used_space += mount[:used] + - free_space += mount[:available] + td.settings-col1 = mount[:filesystem] + td = number_to_human_size mount[:bytes] + td = number_to_human_size mount[:available] + td + = number_to_human_size mount[:used] + = " (#{mount[:use_percent]})" + td = mount[:mount] + -else + tr + td colspan="6" + strong No data returned + + .settings + .partitions_summary.center_block + table.center + tr + td + = "Free space:" + td + = number_to_human_size(free_space) + tr + td + = "Used space:" + td + = number_to_human_size(used_space) + tr + td + = "Total space:" + td + = number_to_human_size(total_space) + + -if @mounts.any? + table + - @mounts.each_slice(2) do | first, second | + tr + td.td_box + h3.center_block + = first[:mount] + |     + = number_to_human_size first[:bytes] + + = pie_chart({"Free: #{number_to_human_size first[:available]}" => first[:available], \ + "Used: #{number_to_human_size first[:used]}" => first[:used]},\ + library: {legend: {position: 'top'}, \ + is3D: true, \ + pieSliceText: 'percentage', \ + tooltip: {text: 'percentage' }, \ + width: 400, \ + slices: [{color: '#A44585'},{color: '#00B5F0'}] \ + }) + - if second + td.td_box + h3.center_block + = second[:mount] + |     + = number_to_human_size second[:bytes] + + = pie_chart({"Free: #{number_to_human_size second[:available]}" => second[:available], \ + "Used: #{number_to_human_size second[:used]}" => second[:used]},\ + library: {legend: {position: 'top'}, \ + is3D: true, \ + pieSliceText: 'percentage', \ + tooltip: {text: 'percentage' }, \ + width: 400, \ + slices: [{color: '#A44585'},{color: '#00B5F0'}] \ + + }) + + + + + -else + tr + td colspan="6" + strong No data returned + diff --git a/config/amahi-plugin.yml b/config/amahi-plugin.yml new file mode 100755 index 0000000..067ba3d --- /dev/null +++ b/config/amahi-plugin.yml @@ -0,0 +1,6 @@ +# human readable name (no localization supported yet) +name: DisksInfo +# class to be mounted +class: DisksInfo +# root url where this plugin will be mounted +url: /tab/disks_info diff --git a/config/initializers/plugin_init.rb b/config/initializers/plugin_init.rb new file mode 100755 index 0000000..2b84af8 --- /dev/null +++ b/config/initializers/plugin_init.rb @@ -0,0 +1,5 @@ +# plugin initialization +t = Tab.new("disks_info", "Disk Stats", "/tab/disks_info") +# add any subtabs with what you need. params are controller and the label, for example +t.add("index", "devices") +t.add("mounts", "partitions") diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100755 index 0000000..fb36bfb --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,5 @@ +# Localization in English + +'en': + # put here your string symbols and translations + hello_world: Hello World! (yay for the Amahi plugins!) diff --git a/config/routes.rb b/config/routes.rb new file mode 100755 index 0000000..654495e --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,6 @@ +DisksInfo::Engine.routes.draw do + # root of the plugin + root :to => 'disks_info#index' + # examples of controllers built in this generator. delete at will + match 'mounts' => 'disks_info#mounts' +end diff --git a/db/migrate/.gitkeep b/db/migrate/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/disks.gemspec b/disks.gemspec new file mode 100755 index 0000000..97f94b6 --- /dev/null +++ b/disks.gemspec @@ -0,0 +1,24 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "disks_info/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "disks_info" + s.version = DisksInfo::VERSION + s.authors = ["Amahi Dev"] + s.email = ["slm4996+git@gmail.com"] + s.homepage = "http://www.amahi.org/" + s.license = "AGPLv3" + s.summary = %{Disk information for the Amahi iplatform(Enhanced).} + s.description = %{This is an Amahi 7 platform plugin that allows the viewing of disk related information(Enhanced).} + + s.files = Dir["{app,config,db,lib}/**/*"] + ["LICENSE", "Rakefile", "README.rdoc"] + s.test_files = Dir["test/**/*"] + + s.add_dependency "rails", "~> 3.2.12" + s.add_dependency "jquery-rails" + + s.add_development_dependency "sqlite3" +end diff --git a/lib/chartkick.rb b/lib/chartkick.rb new file mode 100755 index 0000000..038c277 --- /dev/null +++ b/lib/chartkick.rb @@ -0,0 +1,12 @@ +require "chartkick/version" +require "chartkick/helper" +require "chartkick/rails" if defined?(Rails) +require "chartkick/sinatra" if defined?(Sinatra) + +module Chartkick + class << self + attr_accessor :content_for + attr_accessor :options + end + self.options = {} +end diff --git a/lib/chartkick/helper.rb b/lib/chartkick/helper.rb new file mode 100755 index 0000000..8fbd73a --- /dev/null +++ b/lib/chartkick/helper.rb @@ -0,0 +1,71 @@ +require "json" +require "erb" + +module Chartkick + module Helper + + def line_chart(data_source, options = {}) + chartkick_chart "LineChart", data_source, options + end + + def pie_chart(data_source, options = {}) + chartkick_chart "PieChart", data_source, options + end + + def column_chart(data_source, options = {}) + chartkick_chart "ColumnChart", data_source, options + end + + def bar_chart(data_source, options = {}) + chartkick_chart "BarChart", data_source, options + end + + def area_chart(data_source, options = {}) + chartkick_chart "AreaChart", data_source, options + end + + def geo_chart(data_source, options = {}) + chartkick_chart "GeoChart", data_source, options + end + + private + + def chartkick_chart(klass, data_source, options, &block) + @chartkick_chart_id ||= 0 + options = chartkick_deep_merge(Chartkick.options, options) + element_id = options.delete(:id) || "chart-#{@chartkick_chart_id += 1}" + height = options.delete(:height) || "300px" + # content_for: nil must override default + content_for = options.has_key?(:content_for) ? options.delete(:content_for) : Chartkick.content_for + + html = < + Loading... + +HTML + js = < + new Chartkick.#{klass}(#{element_id.to_json}, #{data_source.to_json}, #{options.to_json}); + +JS + if content_for + content_for(content_for) { js.respond_to?(:html_safe) ? js.html_safe : js } + else + html += js + end + + html.respond_to?(:html_safe) ? html.html_safe : html + end + + # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/deep_merge.rb + def chartkick_deep_merge(hash_a, hash_b) + hash_a = hash_a.dup + hash_b.each_pair do |k,v| + tv = hash_a[k] + hash_a[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v + end + hash_a + end + + end +end diff --git a/lib/chartkick/rails.rb b/lib/chartkick/rails.rb new file mode 100755 index 0000000..5811a7d --- /dev/null +++ b/lib/chartkick/rails.rb @@ -0,0 +1 @@ +ActionView::Base.send :include, Chartkick::Helper diff --git a/lib/chartkick/sinatra.rb b/lib/chartkick/sinatra.rb new file mode 100755 index 0000000..4c8fe58 --- /dev/null +++ b/lib/chartkick/sinatra.rb @@ -0,0 +1,3 @@ +class Sinatra::Base + helpers Chartkick::Helper +end diff --git a/lib/chartkick/version.rb b/lib/chartkick/version.rb new file mode 100755 index 0000000..98e74fc --- /dev/null +++ b/lib/chartkick/version.rb @@ -0,0 +1,3 @@ +module Chartkick + VERSION = "1.2.4" +end diff --git a/lib/disks_info.rb b/lib/disks_info.rb new file mode 100755 index 0000000..70f5eef --- /dev/null +++ b/lib/disks_info.rb @@ -0,0 +1,10 @@ +require "disks_info/version" +require "disks_info/engine" +require "chartkick.rb" +module DisksInfo + class Lib + require "disks_info/disk_utils" + # or inside lib/disks/whatever.rb and required here + end +end + diff --git a/lib/disks_info/disk_utils.rb b/lib/disks_info/disk_utils.rb new file mode 100644 index 0000000..a3cf497 --- /dev/null +++ b/lib/disks_info/disk_utils.rb @@ -0,0 +1,90 @@ +# Amahi Home Server +# Copyright (C) 2007-2013 Amahi +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License v3 +# (29 June 2007), as published in the COPYING file. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# file COPYING for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Amahi +# team at http://www.amahi.org/ under "Contact Us." + +require 'socket' # Sockets are in standard library + +class DiskUtils + + # return information on hdd temperature - requires hddtemp service running! + class << self + def stats + host = 'localhost' + port = 7634 + + begin + s = TCPSocket.open(host, port) + rescue + return [] + end + + res = '' + while line = s.gets # Read lines from the socket + res += line.chop # And print with platform line terminator + end + s.close # Close the socket when done + + disks = res.split '||' + + res = [] + disks.each do |disk| + # split the info and do cleanup + i = disk.gsub(/^\||\|$/, '').split('|') + model = i[1].gsub(/[^A-Za-z0-9\-_\s\.]/, '') rescue "Unkown" + next if model == '???' + t = i[2].to_i rescue 0 + tempcolor = "cool" + celsius = "-" + farenheight = "-" + if t > 0 + celsius = t.to_s + tempcolor = "warm" if t > 39 + tempcolor = "hot" if t > 49 + farenheight = (t * 1.8 + 32).to_i.to_s + end + d = Hash.new + d[:device] = i[0] + d[:model] = model + d[:temp_c] = celsius + d[:temp_f] = farenheight + d[:tempcolor] = tempcolor + res.push(d) + end + res + end + + def mounts + s = `df -BK`.split( /\r?\n/ )[1..-1] || ["","Incorrect data returned"] + + mount = [] + res = [] + s.each do |line| + word = line.split(/\s+/) + mount.push(word) + end + mount.each do |key| + d = Hash.new + d[:filesystem] = key[0] + d[:bytes] = key[1].to_i * 1024 + d[:used] = key[2].to_i * 1024 + d[:available] = key[3].to_i * 1024 + d[:use_percent] = key[4] + d[:mount] = key[5] + res.push(d) unless ['tmpfs', 'devtmpfs'].include? d[:filesystem] + end + res.sort { |x,y| x[:filesystem] <=> y[:filesystem] } + end + end +end diff --git a/lib/disks_info/engine.rb b/lib/disks_info/engine.rb new file mode 100755 index 0000000..7018f5f --- /dev/null +++ b/lib/disks_info/engine.rb @@ -0,0 +1,8 @@ +module DisksInfo + class Engine < ::Rails::Engine + # NOTE: do not isolate the namespace unless you really really + # want to adjust all your controllers views, etc., making Amahi's + # platform hard to reach from here + # isolate_namespace Disks + end +end diff --git a/lib/disks_info/version.rb b/lib/disks_info/version.rb new file mode 100755 index 0000000..aa8ca85 --- /dev/null +++ b/lib/disks_info/version.rb @@ -0,0 +1,3 @@ +module DisksInfo + VERSION = "0.0.1" +end diff --git a/lib/tasks/.gitkeep b/lib/tasks/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/lib/tasks/disks.rake b/lib/tasks/disks.rake new file mode 100755 index 0000000..68db4f3 --- /dev/null +++ b/lib/tasks/disks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :disks do +# # Task goes here +# end diff --git a/script/.gitkeep b/script/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/script/rails b/script/rails new file mode 100755 index 0000000..a44e62e --- /dev/null +++ b/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/disks/engine', __FILE__) +require 'rails/all' +require 'rails/engine/commands'