Retire stackforge/cloud-pydashie

This commit is contained in:
Monty Taylor 2015-10-17 16:02:43 -04:00
parent 87bd1d7188
commit 66ab2867ff
90 changed files with 5 additions and 40489 deletions

22
.gitattributes vendored
View File

@ -1,22 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
.DS_Store
__pycache__/
*.py[cod]
*.egg*

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=stackforge/cloud-pydashie.git

View File

@ -1,7 +0,0 @@
Copyright (c) 2013 Stephen Brown
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
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.

View File

@ -1,4 +0,0 @@
recursive-include pydashie/templates *
recursive-include pydashie/assets *
recursive-include pydashie/widgets *
recursive-include pydashie/samplers *

View File

@ -1,121 +1,7 @@
Cloud-PyDashie
########
This project is no longer maintained.
PyDashie is a port of `Dashing <https://github.com/Shopify/dashing>`_ by `Shopify <http://www.shopify.com/>`_ to Python 2.7
The contents of this repository are still available in the Git source code
management system. To see the contents of this repository before it reached
its end of life, please check out the previous commit with
"git checkout HEAD^1".
This is simply an implementation of pydashie tailored to showing information about an openstack cluster with nagios/icinga for monitoring. It is primarily for internal use by maintainers of openstack deloyments, as the current flask file servicing may be somewhat unsafe for public access.
It uses the standard python clients for collecting formation from openstack across multiple regions.
The nagios/icinga data is currently collected via ssh but in future might be moved to MKlivestatus as the current method is roundabout.
.. image:: https://raw.githubusercontent.com/catalyst/openstack-pydashie/master/mainscreen.png
**NOTE**: The current layout is hardcoded for 1080p. This might be changed to be configurable by the conf.yaml later. If you need to change the sizing, you can do so by changing the widget dimensions and number of columns within this function:
https://github.com/catalyst/openstack-pydashie/blob/master/pydashie/assets/javascripts/app.js#L336
Configuration
############
Configuration is handled via a yaml file as follows:
.. code-block:: yaml
main:
log_file: pydashie.log
openstack:
allocation:
RegionOne:
vcpus_allocation_ratio: 2.0
ram_allocation_ratio: 1.0
# remove this amount per node available metric:
reserved_ram_per_node: 0
reserved_vcpus_per_node: 0
# remove this amount from total
# to take into account possible nova evacuate:
reserved_vcpus: 0
# ram in bytes
reserved_ram: 0
# total IPs are here as getting this from Neutron is
# far from straightforward
total_floating_ips: 256
auth:
auth_url: 'http://localhost:5000/v2.0'
username: 'admin'
password: 'openstack'
project_name: 'demo'
insecure: False
nagios:
services:
RegionOne:
statfile: './RegionOne-status.dat'
host: 'RegionOne-mon0'
username: 'admin'
Because of differences between allocation per region, and the need for a region list, each region is given it's own allocation data. We use this to know which regions to build clients for and aggregate data over, but in future might try and query a for a full region list and for allocation data from openstack itself.
The nagios collection relies on a local ssh key for the given username, and access for that key on the given host.
Widgets
############
Info on adding/removing/updating widgets will go here later.
Installation
############
**NOTE**: Development/deployment has been done in a Ubuntu environment, so the following might be different for you. Also, the following is a step by step guide for installing into a clean server.
Some of the python libraries have certain requirements, and the app itself needs a javascript service to deal with javascript files. As such you will need the following packages:
sudo apt-get install python-dev nodejs
You will ideally want to run the app inside a virtualenv. If you don't have virtualenv installed you can get it via:
sudo apt-get install python-virtualenv
And then create the environment by (this will create a directory for the environment, so be careful where you do this):
virtualenv <name_of_environment>
To then activate it:
source <name_of_environment>/bin/activate
Now that you are in your environment, you will need to install all the required python libraries:
pip install -r requirements.txt
At this point you can install the app itself.
For development purposes use:
python setup.py develop
Which will build a python egg pointing to the local git files so that you can edit them and just restart the service when you change them.
If you aren't planning to develop or edit the files:
python setup.py install
But if the files are changed, or you pull an update, you will need to rerun the install.
Running
############
Provided you have a conf with working credentials and correctly named regions, you can run the application by:
pydashie -c conf.yaml
Goto localhost:5050 to view the application in action.
**NOTE**: Getting the app up and running quickly with just openstack credentials is relatively easy, and you can simply comment out the nagios samplers from:
https://github.com/catalyst/openstack-pydashie/blob/master/pydashie/openstack_app.py
The port and interface can also be set via the commandline:
pydashie -c conf.yaml -ip 0.0.0.0 -p 5050
Although they default to 0.0.0.0 and 5050 if not manually given.

View File

@ -1,56 +0,0 @@
import os
import logging
import StringIO
from scss import Scss
log = logging.getLogger('PydashieCompiler')
logging.basicConfig()
log.setLevel(logging.INFO)
#Requirements:
#pip install pyScss
#
def main():
current_directory = os.getcwd()
logging.info("Compiling from local files ...")
dashing_dir = os.path.join(current_directory, 'pydashie')
logging.info("Using walk path : %s" % dashing_dir)
fileList = []
for root, subFolders, files in os.walk(dashing_dir):
for fileName in files:
if 'scss' in fileName:
fileList.append(os.path.join(root, fileName))
log.info('Found SCSS to compile: %s' % fileName)
css_output = StringIO.StringIO()
css = Scss()
css_output.write('\n'.join([css.compile(open(filePath).read()) for filePath in fileList]))
fileList = []
for root, subFolders, files in os.walk(dashing_dir):
for fileName in files:
if 'css' in fileName and 'scss' not in fileName:
if (not fileName.endswith('~') and
not fileName == "application.css"):
# discard any temporary files
# ignore the base application.css (duplication issues)
fileList.append(os.path.join(root, fileName))
log.info('Found CSS to append: %s' % fileName)
css_output.write('\n'.join([open(filePath).read() for filePath in fileList]))
app_css_filepath = os.path.join(current_directory,
'pydashie/assets/stylesheets/application.css')
with open(app_css_filepath, 'w') as outfile:
outfile.write(css_output.getvalue())
log.info('Wrote CSS out to : %s' % app_css_filepath)
if __name__ == '__main__':
main()

View File

@ -1,485 +0,0 @@
#!python
"""Bootstrap distribute installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from distribute_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import os
import sys
import time
import fnmatch
import tempfile
import tarfile
from distutils import log
try:
from site import USER_SITE
except ImportError:
USER_SITE = None
try:
import subprocess
def _python_cmd(*args):
args = (sys.executable,) + args
return subprocess.call(args) == 0
except ImportError:
# will be used for python 2.3
def _python_cmd(*args):
args = (sys.executable,) + args
# quoting arguments if windows
if sys.platform == 'win32':
def quote(arg):
if ' ' in arg:
return '"%s"' % arg
return arg
args = [quote(arg) for arg in args]
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
DEFAULT_VERSION = "0.6.14"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_FAKED_VERSION = "0.6c11"
SETUPTOOLS_PKG_INFO = """\
Metadata-Version: 1.0
Name: setuptools
Version: %s
Summary: xxxx
Home-page: xxx
Author: xxx
Author-email: xxx
License: xxx
Description: xxx
""" % SETUPTOOLS_FAKED_VERSION
def _install(tarball):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# installing
log.warn('Installing Distribute')
if not _python_cmd('setup.py', 'install'):
log.warn('Something went wrong during the installation.')
log.warn('See the error message above.')
finally:
os.chdir(old_wd)
def _build_egg(egg, tarball, to_dir):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# building an egg
log.warn('Building a Distribute egg in %s', to_dir)
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
finally:
os.chdir(old_wd)
# returning the result
log.warn(egg)
if not os.path.exists(egg):
raise IOError('Could not build the egg.')
def _do_download(version, download_base, to_dir, download_delay):
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
% (version, sys.version_info[0], sys.version_info[1]))
if not os.path.exists(egg):
tarball = download_setuptools(version, download_base,
to_dir, download_delay)
_build_egg(egg, tarball, to_dir)
sys.path.insert(0, egg)
import setuptools
setuptools.bootstrap_install_from = egg
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, download_delay=15, no_fake=True):
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
was_imported = 'pkg_resources' in sys.modules or \
'setuptools' in sys.modules
try:
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
if not no_fake:
_fake_setuptools()
raise ImportError
except ImportError:
return _do_download(version, download_base, to_dir, download_delay)
try:
pkg_resources.require("distribute>="+version)
return
except pkg_resources.VersionConflict:
e = sys.exc_info()[1]
if was_imported:
sys.stderr.write(
"The required version of distribute (>=%s) is not available,\n"
"and can't be installed while this script is running. Please\n"
"install a more recent version first, using\n"
"'easy_install -U distribute'."
"\n\n(Currently using %r)\n" % (version, e.args[0]))
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return _do_download(version, download_base, to_dir,
download_delay)
except pkg_resources.DistributionNotFound:
return _do_download(version, download_base, to_dir,
download_delay)
finally:
if not no_fake:
_create_fake_setuptools_pkg_info(to_dir)
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, delay=15):
"""Download distribute from a specified location and return its filename
`version` should be a valid distribute version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download
attempt.
"""
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
tgz_name = "distribute-%s.tar.gz" % version
url = download_base + tgz_name
saveto = os.path.join(to_dir, tgz_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
log.warn("Downloading %s", url)
src = urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = src.read()
dst = open(saveto, "wb")
dst.write(data)
finally:
if src:
src.close()
if dst:
dst.close()
return os.path.realpath(saveto)
def _no_sandbox(function):
def __no_sandbox(*args, **kw):
try:
from setuptools.sandbox import DirectorySandbox
if not hasattr(DirectorySandbox, '_old'):
def violation(*args):
pass
DirectorySandbox._old = DirectorySandbox._violation
DirectorySandbox._violation = violation
patched = True
else:
patched = False
except ImportError:
patched = False
try:
return function(*args, **kw)
finally:
if patched:
DirectorySandbox._violation = DirectorySandbox._old
del DirectorySandbox._old
return __no_sandbox
def _patch_file(path, content):
"""Will backup the file then patch it"""
existing_content = open(path).read()
if existing_content == content:
# already patched
log.warn('Already patched.')
return False
log.warn('Patching...')
_rename_path(path)
f = open(path, 'w')
try:
f.write(content)
finally:
f.close()
return True
_patch_file = _no_sandbox(_patch_file)
def _same_content(path, content):
return open(path).read() == content
def _rename_path(path):
new_name = path + '.OLD.%s' % time.time()
log.warn('Renaming %s into %s', path, new_name)
os.rename(path, new_name)
return new_name
def _remove_flat_installation(placeholder):
if not os.path.isdir(placeholder):
log.warn('Unkown installation at %s', placeholder)
return False
found = False
for file in os.listdir(placeholder):
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
found = True
break
if not found:
log.warn('Could not locate setuptools*.egg-info')
return
log.warn('Removing elements out of the way...')
pkg_info = os.path.join(placeholder, file)
if os.path.isdir(pkg_info):
patched = _patch_egg_dir(pkg_info)
else:
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
if not patched:
log.warn('%s already patched.', pkg_info)
return False
# now let's move the files out of the way
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
element = os.path.join(placeholder, element)
if os.path.exists(element):
_rename_path(element)
else:
log.warn('Could not find the %s element of the '
'Setuptools distribution', element)
return True
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
def _after_install(dist):
log.warn('After install bootstrap.')
placeholder = dist.get_command_obj('install').install_purelib
_create_fake_setuptools_pkg_info(placeholder)
def _create_fake_setuptools_pkg_info(placeholder):
if not placeholder or not os.path.exists(placeholder):
log.warn('Could not find the install location')
return
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
setuptools_file = 'setuptools-%s-py%s.egg-info' % \
(SETUPTOOLS_FAKED_VERSION, pyver)
pkg_info = os.path.join(placeholder, setuptools_file)
if os.path.exists(pkg_info):
log.warn('%s already exists', pkg_info)
return
log.warn('Creating %s', pkg_info)
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
pth_file = os.path.join(placeholder, 'setuptools.pth')
log.warn('Creating %s', pth_file)
f = open(pth_file, 'w')
try:
f.write(os.path.join(os.curdir, setuptools_file))
finally:
f.close()
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
def _patch_egg_dir(path):
# let's check if it's already patched
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
if os.path.exists(pkg_info):
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
log.warn('%s already patched.', pkg_info)
return False
_rename_path(path)
os.mkdir(path)
os.mkdir(os.path.join(path, 'EGG-INFO'))
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
return True
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
def _before_install():
log.warn('Before install bootstrap.')
_fake_setuptools()
def _under_prefix(location):
if 'install' not in sys.argv:
return True
args = sys.argv[sys.argv.index('install')+1:]
for index, arg in enumerate(args):
for option in ('--root', '--prefix'):
if arg.startswith('%s=' % option):
top_dir = arg.split('root=')[-1]
return location.startswith(top_dir)
elif arg == option:
if len(args) > index:
top_dir = args[index+1]
return location.startswith(top_dir)
if arg == '--user' and USER_SITE is not None:
return location.startswith(USER_SITE)
return True
def _fake_setuptools():
log.warn('Scanning installed packages')
try:
import pkg_resources
except ImportError:
# we're cool
log.warn('Setuptools or Distribute does not seem to be installed.')
return
ws = pkg_resources.working_set
try:
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
replacement=False))
except TypeError:
# old distribute API
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
if setuptools_dist is None:
log.warn('No setuptools distribution found')
return
# detecting if it was already faked
setuptools_location = setuptools_dist.location
log.warn('Setuptools installation detected at %s', setuptools_location)
# if --root or --preix was provided, and if
# setuptools is not located in them, we don't patch it
if not _under_prefix(setuptools_location):
log.warn('Not patching, --root or --prefix is installing Distribute'
' in another location')
return
# let's see if its an egg
if not setuptools_location.endswith('.egg'):
log.warn('Non-egg installation')
res = _remove_flat_installation(setuptools_location)
if not res:
return
else:
log.warn('Egg installation')
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
if (os.path.exists(pkg_info) and
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
log.warn('Already patched.')
return
log.warn('Patching...')
# let's create a fake egg replacing setuptools one
res = _patch_egg_dir(setuptools_location)
if not res:
return
log.warn('Patched done.')
_relaunch()
def _relaunch():
log.warn('Relaunching...')
# we have to relaunch the process
# pip marker to avoid a relaunch bug
if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
sys.argv[0] = 'setup.py'
args = [sys.executable] + sys.argv
sys.exit(subprocess.call(args))
def _extractall(self, path=".", members=None):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
"""
import copy
import operator
from tarfile import ExtractError
directories = []
if members is None:
members = self
for tarinfo in members:
if tarinfo.isdir():
# Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 448 # decimal for oct 0700
self.extract(tarinfo, path)
# Reverse sort directories.
if sys.version_info < (2, 4):
def sorter(dir1, dir2):
return cmp(dir1.name, dir2.name)
directories.sort(sorter)
directories.reverse()
else:
directories.sort(key=operator.attrgetter('name'), reverse=True)
# Set correct owner, mtime and filemode on directories.
for tarinfo in directories:
dirpath = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
except ExtractError:
e = sys.exc_info()[1]
if self.errorlevel > 1:
raise
else:
self._dbg(1, "tarfile: %s" % e)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
tarball = download_setuptools()
_install(tarball)
if __name__ == '__main__':
main(sys.argv[1:])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

View File

@ -1,355 +0,0 @@
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Clock = (function(_super) {
__extends(Clock, _super);
function Clock() {
this.startTime = __bind(this.startTime, this);
return Clock.__super__.constructor.apply(this, arguments);
}
Clock.prototype.ready = function() {
return setInterval(this.startTime, 500);
};
Clock.prototype.startTime = function() {
var h, m, s, today;
today = new Date();
h = today.getHours();
m = today.getMinutes();
s = today.getSeconds();
m = this.formatTime(m);
s = this.formatTime(s);
this.set('time', h + ":" + m + ":" + s);
return this.set('date', today.toDateString());
};
Clock.prototype.formatTime = function(i) {
if (i < 10) {
return "0" + i;
} else {
return i;
}
};
return Clock;
})(Dashing.Widget);
}).call(this);
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Comments = (function(_super) {
__extends(Comments, _super);
function Comments() {
this.nextComment = __bind(this.nextComment, this);
return Comments.__super__.constructor.apply(this, arguments);
}
Comments.accessor('quote', function() {
var _ref;
return "“" + ((_ref = this.get('current_comment')) != null ? _ref.body : void 0) + "”";
});
Comments.prototype.ready = function() {
this.currentIndex = 0;
this.commentElem = $(this.node).find('.comment-container');
this.nextComment();
return this.startCarousel();
};
Comments.prototype.onData = function(data) {
return this.currentIndex = 0;
};
Comments.prototype.startCarousel = function() {
return setInterval(this.nextComment, 8000);
};
Comments.prototype.nextComment = function() {
var comments,
_this = this;
comments = this.get('comments');
if (comments) {
return this.commentElem.fadeOut(function() {
_this.currentIndex = (_this.currentIndex + 1) % comments.length;
_this.set('current_comment', comments[_this.currentIndex]);
return _this.commentElem.fadeIn();
});
}
};
return Comments;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Graph = (function(_super) {
__extends(Graph, _super);
function Graph() {
return Graph.__super__.constructor.apply(this, arguments);
}
Graph.accessor('current', function() {
var points;
if (this.get('displayedValue')) {
return this.get('displayedValue');
}
points = this.get('points');
if (points) {
return points[points.length - 1].y;
}
});
Graph.prototype.ready = function() {
var container, height, width, x_axis, y_axis;
container = $(this.node).parent();
width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1);
height = Dashing.widget_base_dimensions[1] * container.data("sizey");
this.graph = new Rickshaw.Graph({
element: this.node,
width: width,
height: height,
series: [
{
color: "#fff",
data: [
{
x: 0,
y: 0
}
]
}
]
});
if (this.get('points')) {
this.graph.series[0].data = this.get('points');
}
x_axis = new Rickshaw.Graph.Axis.Time({
graph: this.graph
});
y_axis = new Rickshaw.Graph.Axis.Y({
graph: this.graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
});
return this.graph.render();
};
Graph.prototype.onData = function(data) {
if (this.graph) {
this.graph.series[0].data = data.points;
return this.graph.render();
}
};
return Graph;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Iframe = (function(_super) {
__extends(Iframe, _super);
function Iframe() {
return Iframe.__super__.constructor.apply(this, arguments);
}
Iframe.prototype.ready = function() {};
Iframe.prototype.onData = function(data) {};
return Iframe;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Image = (function(_super) {
__extends(Image, _super);
function Image() {
return Image.__super__.constructor.apply(this, arguments);
}
Image.prototype.ready = function() {};
Image.prototype.onData = function(data) {};
return Image;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.List = (function(_super) {
__extends(List, _super);
function List() {
return List.__super__.constructor.apply(this, arguments);
}
List.prototype.ready = function() {
if (this.get('unordered')) {
return $(this.node).find('ol').remove();
} else {
return $(this.node).find('ul').remove();
}
};
return List;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Meter = (function(_super) {
__extends(Meter, _super);
Meter.accessor('value', Dashing.AnimatedValue);
function Meter() {
Meter.__super__.constructor.apply(this, arguments);
this.observe('value', function(value) {
return $(this.node).find(".meter").val(value).trigger('change');
});
}
Meter.prototype.ready = function() {
var meter;
meter = $(this.node).find(".meter");
meter.attr("data-bgcolor", meter.css("background-color"));
meter.attr("data-fgcolor", meter.css("color"));
return meter.knob();
};
return Meter;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Number = (function(_super) {
__extends(Number, _super);
function Number() {
return Number.__super__.constructor.apply(this, arguments);
}
Number.accessor('current', Dashing.AnimatedValue);
Number.accessor('difference', function() {
var current, diff, last;
if (this.get('last')) {
last = parseInt(this.get('last'));
current = parseInt(this.get('current'));
if (last !== 0) {
diff = Math.abs(Math.round((current - last) / last * 100));
return "" + diff + "%";
}
} else {
return "";
}
});
Number.accessor('arrow', function() {
if (this.get('last')) {
if (parseInt(this.get('current')) > parseInt(this.get('last'))) {
return 'icon-arrow-up';
} else {
return 'icon-arrow-down';
}
}
});
Number.accessor('needsAttention', function() {
return this.get('status') === 'warning' || this.get('status') === 'danger';
});
Number.prototype.onData = function(data) {
if (data.status) {
return $(this.get('node')).addClass("status-" + data.status);
}
};
return Number;
})(Dashing.Widget);
}).call(this);
(function() {
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Dashing.Text = (function(_super) {
__extends(Text, _super);
function Text() {
return Text.__super__.constructor.apply(this, arguments);
}
return Text;
})(Dashing.Widget);
}).call(this);
(function() {
console.log("Yeah! The dashboard has started!");
Dashing.on('ready', function() {
var contentWidth;
Dashing.widget_margins || (Dashing.widget_margins = [5, 5]);
Dashing.widget_base_dimensions || (Dashing.widget_base_dimensions = [300, 340]);
Dashing.numColumns || (Dashing.numColumns = 6);
contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns;
return Batman.setImmediate(function() {
$('.gridster').width(contentWidth);
return $('.gridster ul:first').gridster({
widget_margins: Dashing.widget_margins,
widget_base_dimensions: Dashing.widget_base_dimensions,
avoid_overlapped_widgets: !Dashing.customGridsterLayout,
draggable: {
stop: Dashing.showGridsterInstructions
}
});
});
});
}).call(this);

View File

@ -1,18 +0,0 @@
console.log("Yeah! The dashboard has started!")
Dashing.on 'ready', ->
Dashing.widget_margins ||= [5, 5]
Dashing.widget_base_dimensions ||= [300, 360]
Dashing.numColumns ||= 4
contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns
Batman.setImmediate ->
$('.gridster').width(contentWidth)
$('.gridster ul:first').gridster
widget_margins: Dashing.widget_margins
widget_base_dimensions: Dashing.widget_base_dimensions
avoid_overlapped_widgets: !Dashing.customGridsterLayout
draggable:
stop: Dashing.showGridsterInstructions
start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,111 +0,0 @@
#= require jquery
#= require es5-shim
#= require batman
#= require batman.jquery
#Batman.Filters.prettyNumber = (num) ->
# num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") unless isNaN(num)
#
#Batman.Filters.dashize = (str) ->
# dashes_rx1 = /([A-Z]+)([A-Z][a-z])/g;
# dashes_rx2 = /([a-z\d])([A-Z])/g;
#
# return str.replace(dashes_rx1, '$1_$2').replace(dashes_rx2, '$1_$2').replace('_', '-').toLowerCase()
#
#Batman.Filters.shortenedNumber = (num) ->
# return num if isNaN(num)
# if num >= 1000000000
# (num / 1000000000).toFixed(1) + 'B'
# else if num >= 1000000
# (num / 1000000).toFixed(1) + 'M'
# else if num >= 1000
# (num / 1000).toFixed(1) + 'K'
# else
# num
#class window.Dashing extends Batman.App
# @root ->
#Dashing.params = Batman.URI.paramsFromQuery(window.location.search.slice(1));
#
#class Dashing.Widget extends Batman.View
# constructor: ->
# # Set the view path
# @constructor::source = Batman.Filters.underscore(@constructor.name)
# super
#
# @mixin($(@node).data())
# Dashing.widgets[@id] ||= []
# Dashing.widgets[@id].push(@)
# @mixin(Dashing.lastEvents[@id]) # in case the events from the server came before the widget was rendered
#
# type = Batman.Filters.dashize(@view)
# $(@node).addClass("widget widget-#{type} #{@id}")
#
# @accessor 'updatedAtMessage', ->
# if updatedAt = @get('updatedAt')
# timestamp = updatedAt.toString().match(/\d*:\d*/)[0]
# "Last updated at #{timestamp}"
#
# @::on 'ready', ->
# Dashing.Widget.fire 'ready'
#
# receiveData: (data) =>
# @mixin(data)
# @onData(data)
#
# onData: (data) =>
# # Widgets override this to handle incoming data
#
Dashing.AnimatedValue =
get: Batman.Property.defaultAccessor.get
set: (k, to) ->
if !to? || isNaN(to)
@[k] = to
else
timer = "interval_#{k}"
num = if (!isNaN(@[k]) && @[k]?) then @[k] else 0
unless @[timer] || num == to
to = parseFloat(to)
num = parseFloat(num)
up = to > num
num_interval = Math.abs(num - to) / 90
@[timer] =
setInterval =>
num = if up then Math.ceil(num+num_interval) else Math.floor(num-num_interval)
if (up && num > to) || (!up && num < to)
num = to
clearInterval(@[timer])
@[timer] = null
delete @[timer]
@[k] = num
@set k, to
, 10
@[k] = num
Dashing.widgets = widgets = {}
Dashing.lastEvents = lastEvents = {}
Dashing.debugMode = false
source = new EventSource('/events')
source.addEventListener 'open', (e) ->
console.log("Connection opened")
source.addEventListener 'error', (e)->
console.log("Connection error")
if (e.readyState == EventSource.CLOSED)
console.log("Connection closed")
source.addEventListener 'message', (e) =>
data = JSON.parse(e.data)
if Dashing.debugMode
console.log("Received data for #{data.id}", data)
lastEvents[data.id] = data
if widgets[data.id]?.length > 0
for widget in widgets[data.id]
widget.receiveData(data)
$(document).ready ->
Dashing.run()

View File

@ -1,25 +0,0 @@
#= require_directory ./gridster
# This file enables gridster integration (http://gridster.net/)
# Delete it if you'd rather handle the layout yourself.
# You'll miss out on a lot if you do, but we won't hold it against you.
Dashing.gridsterLayout = (positions) ->
Dashing.customGridsterLayout = true
positions = positions.replace(/^"|"$/g, '')
positions = $.parseJSON(positions)
widgets = $("[data-row^=]")
for widget, index in widgets
$(widget).attr('data-row', positions[index].row)
$(widget).attr('data-col', positions[index].col)
Dashing.showGridsterInstructions = ->
data = $(".gridster ul:first").gridster().data('gridster').serialize();
$('#save-gridster').slideDown();
return $('#gridster-code').text(" <script type='text/javascript'>\n $(function() {\n \ \ Dashing.gridsterLayout('" + (JSON.stringify(data)) + "')\n });\n </script> ")
$ ->
$('#save-gridster').leanModal()
$('#save-gridster').click ->
$('#save-gridster').slideUp()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,646 +0,0 @@
/*!jQuery Knob*/
/**
* Downward compatible, touchable dial
*
* Version: 1.2.0 (15/07/2012)
* Requires: jQuery v1.7+
*
* Copyright (c) 2012 Anthony Terrien
* Under MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Thanks to vor, eskimoblood, spiffistan, FabrizioC
*/
$(function () {
/**
* Kontrol library
*/
"use strict";
/**
* Definition of globals and core
*/
var k = {}, // kontrol
max = Math.max,
min = Math.min;
k.c = {};
k.c.d = $(document);
k.c.t = function (e) {
return e.originalEvent.touches.length - 1;
};
/**
* Kontrol Object
*
* Definition of an abstract UI control
*
* Each concrete component must call this one.
* <code>
* k.o.call(this);
* </code>
*/
k.o = function () {
var s = this;
this.o = null; // array of options
this.$ = null; // jQuery wrapped element
this.i = null; // mixed HTMLInputElement or array of HTMLInputElement
this.g = null; // 2D graphics context for 'pre-rendering'
this.v = null; // value ; mixed array or integer
this.cv = null; // change value ; not commited value
this.x = 0; // canvas x position
this.y = 0; // canvas y position
this.$c = null; // jQuery canvas element
this.c = null; // rendered canvas context
this.t = 0; // touches index
this.isInit = false;
this.fgColor = null; // main color
this.pColor = null; // previous color
this.dH = null; // draw hook
this.cH = null; // change hook
this.eH = null; // cancel hook
this.rH = null; // release hook
this.run = function () {
var cf = function (e, conf) {
var k;
for (k in conf) {
s.o[k] = conf[k];
}
s.init();
s._configure()
._draw();
};
if(this.$.data('kontroled')) return;
this.$.data('kontroled', true);
this.extend();
this.o = $.extend(
{
// Config
min : this.$.data('min') || 0,
max : this.$.data('max') || 100,
stopper : true,
readOnly : this.$.data('readonly'),
// UI
cursor : (this.$.data('cursor') === true && 30)
|| this.$.data('cursor')
|| 0,
thickness : this.$.data('thickness') || 0.35,
width : this.$.data('width') || 200,
height : this.$.data('height') || 200,
displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'),
displayPrevious : this.$.data('displayprevious'),
fgColor : this.$.data('fgcolor') || '#87CEEB',
inline : false,
// Hooks
draw : null, // function () {}
change : null, // function (value) {}
cancel : null, // function () {}
release : null // function (value) {}
}, this.o
);
// routing value
if(this.$.is('fieldset')) {
// fieldset = array of integer
this.v = {};
this.i = this.$.find('input')
this.i.each(function(k) {
var $this = $(this);
s.i[k] = $this;
s.v[k] = $this.val();
$this.bind(
'change'
, function () {
var val = {};
val[k] = $this.val();
s.val(val);
}
);
});
this.$.find('legend').remove();
} else {
// input = integer
this.i = this.$;
this.v = this.$.val();
(this.v == '') && (this.v = this.o.min);
this.$.bind(
'change'
, function () {
s.val(s.$.val());
}
);
}
(!this.o.displayInput) && this.$.hide();
this.$c = $('<canvas width="' +
this.o.width + 'px" height="' +
this.o.height + 'px"></canvas>');
this.c = this.$c[0].getContext("2d");
this.$
.wrap($('<div style="' + (this.o.inline ? 'display:inline;' : '') +
'width:' + this.o.width + 'px;height:' +
this.o.height + 'px;"></div>'))
.before(this.$c);
if (this.v instanceof Object) {
this.cv = {};
this.copy(this.v, this.cv);
} else {
this.cv = this.v;
}
this.$
.bind("configure", cf)
.parent()
.bind("configure", cf);
this._listen()
._configure()
._xy()
.init();
this.isInit = true;
this._draw();
return this;
};
this._draw = function () {
// canvas pre-rendering
var d = true,
c = document.createElement('canvas');
c.width = s.o.width;
c.height = s.o.height;
s.g = c.getContext('2d');
s.clear();
s.dH
&& (d = s.dH());
(d !== false) && s.draw();
s.c.drawImage(c, 0, 0);
c = null;
};
this._touch = function (e) {
var touchMove = function (e) {
var v = s.xy2val(
e.originalEvent.touches[s.t].pageX,
e.originalEvent.touches[s.t].pageY
);
if (v == s.cv) return;
if (
s.cH
&& (s.cH(v) === false)
) return;
s.change(v);
s._draw();
};
// get touches index
this.t = k.c.t(e);
// First touch
touchMove(e);
// Touch events listeners
k.c.d
.bind("touchmove.k", touchMove)
.bind(
"touchend.k"
, function () {
k.c.d.unbind('touchmove.k touchend.k');
if (
s.rH
&& (s.rH(s.cv) === false)
) return;
s.val(s.cv);
}
);
return this;
};
this._mouse = function (e) {
var mouseMove = function (e) {
var v = s.xy2val(e.pageX, e.pageY);
if (v == s.cv) return;
if (
s.cH
&& (s.cH(v) === false)
) return;
s.change(v);
s._draw();
};
// First click
mouseMove(e);
// Mouse events listeners
k.c.d
.bind("mousemove.k", mouseMove)
.bind(
// Escape key cancel current change
"keyup.k"
, function (e) {
if (e.keyCode === 27) {
k.c.d.unbind("mouseup.k mousemove.k keyup.k");
if (
s.eH
&& (s.eH() === false)
) return;
s.cancel();
}
}
)
.bind(
"mouseup.k"
, function (e) {
k.c.d.unbind('mousemove.k mouseup.k keyup.k');
if (
s.rH
&& (s.rH(s.cv) === false)
) return;
s.val(s.cv);
}
);
return this;
};
this._xy = function () {
var o = this.$c.offset();
this.x = o.left;
this.y = o.top;
return this;
};
this._listen = function () {
if (!this.o.readOnly) {
this.$c
.bind(
"mousedown"
, function (e) {
e.preventDefault();
s._xy()._mouse(e);
}
)
.bind(
"touchstart"
, function (e) {
e.preventDefault();
s._xy()._touch(e);
}
);
this.listen();
} else {
this.$.attr('readonly', 'readonly');
}
return this;
};
this._configure = function () {
// Hooks
if (this.o.draw) this.dH = this.o.draw;
if (this.o.change) this.cH = this.o.change;
if (this.o.cancel) this.eH = this.o.cancel;
if (this.o.release) this.rH = this.o.release;
if (this.o.displayPrevious) {
this.pColor = this.h2rgba(this.o.fgColor, "0.4");
this.fgColor = this.h2rgba(this.o.fgColor, "0.6");
} else {
this.fgColor = this.o.fgColor;
}
return this;
};
this._clear = function () {
this.$c[0].width = this.$c[0].width;
};
// Abstract methods
this.listen = function () {}; // on start, one time
this.extend = function () {}; // each time configure triggered
this.init = function () {}; // each time configure triggered
this.change = function (v) {}; // on change
this.val = function (v) {}; // on release
this.xy2val = function (x, y) {}; //
this.draw = function () {}; // on change / on release
this.clear = function () { this._clear(); };
// Utils
this.h2rgba = function (h, a) {
var rgb;
h = h.substring(1,7)
rgb = [parseInt(h.substring(0,2),16)
,parseInt(h.substring(2,4),16)
,parseInt(h.substring(4,6),16)];
return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")";
};
this.copy = function (f, t) {
for (var i in f) { t[i] = f[i]; }
};
};
/**
* k.Dial
*/
k.Dial = function () {
k.o.call(this);
this.startAngle = null;
this.xy = null;
this.radius = null;
this.lineWidth = null;
this.cursorExt = null;
this.w2 = null;
this.PI2 = 2*Math.PI;
this.extend = function () {
this.o = $.extend(
{
bgColor : this.$.data('bgcolor') || '#EEEEEE',
angleOffset : this.$.data('angleoffset') || 0,
angleArc : this.$.data('anglearc') || 360,
inline : true
}, this.o
);
};
this.val = function (v) {
if (null != v) {
this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v;
this.v = this.cv;
this.$.val(this.v);
this._draw();
} else {
return this.v;
}
};
this.xy2val = function (x, y) {
var a, ret;
a = Math.atan2(
x - (this.x + this.w2)
, - (y - this.y - this.w2)
) - this.angleOffset;
if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) {
// if isset angleArc option, set to min if .5 under min
a = 0;
} else if (a < 0) {
a += this.PI2;
}
ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc))
+ this.o.min;
this.o.stopper
&& (ret = max(min(ret, this.o.max), this.o.min));
return ret;
};
this.listen = function () {
// bind MouseWheel
var s = this,
mw = function (e) {
e.preventDefault();
var ori = e.originalEvent
,deltaX = ori.detail || ori.wheelDeltaX
,deltaY = ori.detail || ori.wheelDeltaY
,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0);
if (
s.cH
&& (s.cH(v) === false)
) return;
s.val(v);
}
, kval, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1};
this.$
.bind(
"keydown"
,function (e) {
var kc = e.keyCode;
kval = parseInt(String.fromCharCode(kc));
if (isNaN(kval)) {
(kc !== 13) // enter
&& (kc !== 8) // bs
&& (kc !== 9) // tab
&& (kc !== 189) // -
&& e.preventDefault();
// arrows
if ($.inArray(kc,[37,38,39,40]) > -1) {
e.preventDefault();
var v = parseInt(s.$.val()) + kv[kc] * m;
s.o.stopper
&& (v = max(min(v, s.o.max), s.o.min));
s.change(v);
s._draw();
// long time keydown speed-up
to = window.setTimeout(
function () { m*=2; }
,30
);
}
}
}
)
.bind(
"keyup"
,function (e) {
if (isNaN(kval)) {
if (to) {
window.clearTimeout(to);
to = null;
m = 1;
s.val(s.$.val());
}
} else {
// kval postcond
(s.$.val() > s.o.max && s.$.val(s.o.max))
|| (s.$.val() < s.o.min && s.$.val(s.o.min));
}
}
);
this.$c.bind("mousewheel DOMMouseScroll", mw);
this.$.bind("mousewheel DOMMouseScroll", mw)
};
this.init = function () {
if (
this.v < this.o.min
|| this.v > this.o.max
) this.v = this.o.min;
this.$.val(this.v);
this.w2 = this.o.width / 2;
this.cursorExt = this.o.cursor / 100;
this.xy = this.w2;
this.lineWidth = this.xy * this.o.thickness;
this.radius = this.xy - this.lineWidth / 2;
this.o.angleOffset
&& (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset);
this.o.angleArc
&& (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc);
// deg to rad
this.angleOffset = this.o.angleOffset * Math.PI / 180;
this.angleArc = this.o.angleArc * Math.PI / 180;
// compute start and end angles
this.startAngle = 1.5 * Math.PI + this.angleOffset;
this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc;
var s = max(
String(Math.abs(this.o.max)).length
, String(Math.abs(this.o.min)).length
, 2
) + 2;
this.o.displayInput
&& this.i.css({
'width' : ((this.o.width / 2 + 4) >> 0) + 'px'
,'height' : ((this.o.width / 3) >> 0) + 'px'
,'position' : 'absolute'
,'vertical-align' : 'middle'
,'margin-top' : ((this.o.width / 3) >> 0) + 'px'
,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px'
,'border' : 0
,'background' : 'none'
,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial'
,'text-align' : 'center'
,'color' : this.o.fgColor
,'padding' : '0px'
,'-webkit-appearance': 'none'
})
|| this.i.css({
'width' : '0px'
,'visibility' : 'hidden'
});
};
this.change = function (v) {
this.cv = v;
this.$.val(v);
};
this.angle = function (v) {
return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min);
};
this.draw = function () {
var c = this.g, // context
a = this.angle(this.cv) // Angle
, sat = this.startAngle // Start angle
, eat = sat + a // End angle
, sa, ea // Previous angles
, r = 1;
c.lineWidth = this.lineWidth;
this.o.cursor
&& (sat = eat - this.cursorExt)
&& (eat = eat + this.cursorExt);
c.beginPath();
c.strokeStyle = this.o.bgColor;
c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true);
c.stroke();
if (this.o.displayPrevious) {
ea = this.startAngle + this.angle(this.v);
sa = this.startAngle;
this.o.cursor
&& (sa = ea - this.cursorExt)
&& (ea = ea + this.cursorExt);
c.beginPath();
c.strokeStyle = this.pColor;
c.arc(this.xy, this.xy, this.radius, sa, ea, false);
c.stroke();
r = (this.cv == this.v);
}
c.beginPath();
c.strokeStyle = r ? this.o.fgColor : this.fgColor ;
c.arc(this.xy, this.xy, this.radius, sat, eat, false);
c.stroke();
};
this.cancel = function () {
this.val(this.v);
};
};
$.fn.dial = $.fn.knob = function (o) {
return this.each(
function () {
var d = new k.Dial();
d.o = o;
d.$ = $(this);
d.run();
}
).parent();
};
});

View File

@ -1,5 +0,0 @@
// leanModal v1.1 by Ray Stone - http://finelysliced.com.au
// Dual licensed under the MIT and GPL
(function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("<div id='lean_overlay'></div>");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth();
$("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,255 +0,0 @@
/*
//=require_directory .
//=require_tree ../../widgets
*/
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #222;
$text-color: #fff;
$background-warning-color-1: #e82711;
$background-warning-color-2: #9b2d23;
$text-warning-color: #fff;
$background-danger-color-1: #eeae32;
$background-danger-color-2: #ff9618;
$text-danger-color: #fff;
@-webkit-keyframes status-warning-background {
0% { background-color: $background-warning-color-1; }
50% { background-color: $background-warning-color-2; }
100% { background-color: $background-warning-color-1; }
}
@-webkit-keyframes status-danger-background {
0% { background-color: $background-danger-color-1; }
50% { background-color: $background-danger-color-2; }
100% { background-color: $background-danger-color-1; }
}
@mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){
-webkit-animation: $animation-name $duration $function #{$animation-iteration-count};
-moz-animation: $animation-name $duration $function #{$animation-iteration-count};
-ms-animation: $animation-name $duration $function #{$animation-iteration-count};
}
// ----------------------------------------------------------------------------
// Base styles
// ----------------------------------------------------------------------------
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
margin: 0;
background-color: $background-color;
font-size: 20px;
color: $text-color;
font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif;
}
b, strong {
font-weight: bold;
}
a {
text-decoration: none;
color: inherit;
}
img {
border: 0;
-ms-interpolation-mode: bicubic;
vertical-align: middle;
}
img, object {
max-width: 100%;
}
iframe {
max-width: 100%;
}
table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
}
td {
vertical-align: middle;
}
ul, ol {
padding: 0;
margin: 0;
}
h1, h2, h3, h4, h5, p {
padding: 0;
margin: 0;
}
h1 {
margin-bottom: 12px;
text-align: center;
font-size: 30px;
font-weight: 400;
}
h2 {
text-transform: uppercase;
font-size: 50px;
font-weight: 700;
color: $text-color;
}
h3 {
font-size: 25px;
font-weight: 600;
color: $text-color;
}
// ----------------------------------------------------------------------------
// Base widget styles
// ----------------------------------------------------------------------------
.gridster {
margin: 0px auto;
}
.icon-background {
width: 100%!important;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0.1;
font-size: 275px;
}
.list-nostyle {
list-style: none;
}
.gridster ul {
list-style: none;
}
.gs_w {
width: 100%;
display: table;
cursor: pointer;
}
.widget {
padding: 25px 12px;
text-align: center;
width: 100%;
display: table-cell;
vertical-align: middle;
}
.widget.status-warning {
background-color: $background-warning-color-1;
@include animation(status-warning-background, 2s, ease, infinite);
.icon-warning-sign {
display: inline-block;
}
.title, .more-info {
color: $text-warning-color;
}
}
.widget.status-danger {
color: $text-danger-color;
background-color: $background-danger-color-1;
@include animation(status-danger-background, 2s, ease, infinite);
.icon-warning-sign {
display: inline-block;
}
.title, .more-info {
color: $text-danger-color;
}
}
.more-info {
font-size: 15px;
position: absolute;
bottom: 32px;
left: 0;
right: 0;
}
.updated-at {
font-size: 15px;
position: absolute;
bottom: 12px;
left: 0;
right: 0;
}
#save-gridster {
display: none;
position: fixed;
top: 0;
margin: 0px auto;
left: 50%;
z-index: 1000;
background: black;
width: 190px;
text-align: center;
border: 1px solid white;
border-top: 0px;
margin-left: -95px;
padding: 15px;
}
#save-gridster:hover {
padding-top: 25px;
}
#saving-instructions {
display: none;
padding: 10px;
width: 500px;
height: 122px;
z-index: 1000;
background: white;
top: 100px;
color: black;
font-size: 15px;
padding-bottom: 4px;
textarea {
white-space: nowrap;
width: 494px;
height: 80px;
}
}
#lean_overlay {
position: fixed;
z-index:100;
top: 0px;
left: 0px;
height:100%;
width:100%;
background: #000;
display: none;
}
#container {
padding-top: 5px;
}
// ----------------------------------------------------------------------------
// Clearfix
// ----------------------------------------------------------------------------
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
.clearfix:after { clear: both; }
.clearfix { zoom: 1; }

View File

@ -1,303 +0,0 @@
/* Font Awesome
the iconic font designed for use with Twitter Bootstrap
-------------------------------------------------------
The full suite of pictographic icons, examples, and documentation
can be found at: http://fortawesome.github.com/Font-Awesome/
License
-------------------------------------------------------
The Font Awesome webfont, CSS, and LESS files are licensed under CC BY 3.0:
http://creativecommons.org/licenses/by/3.0/ A mention of
'Font Awesome - http://fortawesome.github.com/Font-Awesome' in human-readable
source code is considered acceptable attribution (most common on the web).
If human readable source code is not available to the end user, a mention in
an 'About' or 'Credits' screen is considered acceptable (most common in desktop
or mobile software).
Contact
-------------------------------------------------------
Email: dave@davegandy.com
Twitter: http://twitter.com/fortaweso_me
Work: http://lemonwi.se co-founder
*/
@font-face {
font-family: "FontAwesome";
src: url('/assets/fontawesome-webfont.eot');
src: url('/assets/fontawesome-webfont.eot?#iefix') format('eot'), url('/assets/fontawesome-webfont.woff') format('woff'), url('/assets/fontawesome-webfont.ttf') format('truetype'), url('/assets/fontawesome-webfont.svg#FontAwesome') format('svg');
font-weight: normal;
font-style: normal;
}
/* Font Awesome styles
------------------------------------------------------- */
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
display: inline-block;
text-decoration: inherit;
}
a [class^="icon-"], a [class*=" icon-"] {
display: inline-block;
text-decoration: inherit;
}
/* makes the font 33% larger relative to the icon container */
.icon-large:before {
vertical-align: top;
font-size: 1.3333333333333333em;
}
.btn [class^="icon-"], .btn [class*=" icon-"] {
/* keeps button heights with and without icons the same */
line-height: .9em;
}
li [class^="icon-"], li [class*=" icon-"] {
display: inline-block;
width: 1.25em;
text-align: center;
}
li .icon-large[class^="icon-"], li .icon-large[class*=" icon-"] {
/* 1.5 increased font size for icon-large * 1.25 width */
width: 1.875em;
}
li[class^="icon-"], li[class*=" icon-"] {
margin-left: 0;
list-style-type: none;
}
li[class^="icon-"]:before, li[class*=" icon-"]:before {
text-indent: -2em;
text-align: center;
}
li[class^="icon-"].icon-large:before, li[class*=" icon-"].icon-large:before {
text-indent: -1.3333333333333333em;
}
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.icon-glass:before { content: "\f000"; }
.icon-music:before { content: "\f001"; }
.icon-search:before { content: "\f002"; }
.icon-envelope:before { content: "\f003"; }
.icon-heart:before { content: "\f004"; }
.icon-star:before { content: "\f005"; }
.icon-star-empty:before { content: "\f006"; }
.icon-user:before { content: "\f007"; }
.icon-film:before { content: "\f008"; }
.icon-th-large:before { content: "\f009"; }
.icon-th:before { content: "\f00a"; }
.icon-th-list:before { content: "\f00b"; }
.icon-ok:before { content: "\f00c"; }
.icon-remove:before { content: "\f00d"; }
.icon-zoom-in:before { content: "\f00e"; }
.icon-zoom-out:before { content: "\f010"; }
.icon-off:before { content: "\f011"; }
.icon-signal:before { content: "\f012"; }
.icon-cog:before { content: "\f013"; }
.icon-trash:before { content: "\f014"; }
.icon-home:before { content: "\f015"; }
.icon-file:before { content: "\f016"; }
.icon-time:before { content: "\f017"; }
.icon-road:before { content: "\f018"; }
.icon-download-alt:before { content: "\f019"; }
.icon-download:before { content: "\f01a"; }
.icon-upload:before { content: "\f01b"; }
.icon-inbox:before { content: "\f01c"; }
.icon-play-circle:before { content: "\f01d"; }
.icon-repeat:before { content: "\f01e"; }
/* \f020 doesn't work in Safari. all shifted one down */
.icon-refresh:before { content: "\f021"; }
.icon-list-alt:before { content: "\f022"; }
.icon-lock:before { content: "\f023"; }
.icon-flag:before { content: "\f024"; }
.icon-headphones:before { content: "\f025"; }
.icon-volume-off:before { content: "\f026"; }
.icon-volume-down:before { content: "\f027"; }
.icon-volume-up:before { content: "\f028"; }
.icon-qrcode:before { content: "\f029"; }
.icon-barcode:before { content: "\f02a"; }
.icon-tag:before { content: "\f02b"; }
.icon-tags:before { content: "\f02c"; }
.icon-book:before { content: "\f02d"; }
.icon-bookmark:before { content: "\f02e"; }
.icon-print:before { content: "\f02f"; }
.icon-camera:before { content: "\f030"; }
.icon-font:before { content: "\f031"; }
.icon-bold:before { content: "\f032"; }
.icon-italic:before { content: "\f033"; }
.icon-text-height:before { content: "\f034"; }
.icon-text-width:before { content: "\f035"; }
.icon-align-left:before { content: "\f036"; }
.icon-align-center:before { content: "\f037"; }
.icon-align-right:before { content: "\f038"; }
.icon-align-justify:before { content: "\f039"; }
.icon-list:before { content: "\f03a"; }
.icon-indent-left:before { content: "\f03b"; }
.icon-indent-right:before { content: "\f03c"; }
.icon-facetime-video:before { content: "\f03d"; }
.icon-picture:before { content: "\f03e"; }
.icon-pencil:before { content: "\f040"; }
.icon-map-marker:before { content: "\f041"; }
.icon-adjust:before { content: "\f042"; }
.icon-tint:before { content: "\f043"; }
.icon-edit:before { content: "\f044"; }
.icon-share:before { content: "\f045"; }
.icon-check:before { content: "\f046"; }
.icon-move:before { content: "\f047"; }
.icon-step-backward:before { content: "\f048"; }
.icon-fast-backward:before { content: "\f049"; }
.icon-backward:before { content: "\f04a"; }
.icon-play:before { content: "\f04b"; }
.icon-pause:before { content: "\f04c"; }
.icon-stop:before { content: "\f04d"; }
.icon-forward:before { content: "\f04e"; }
.icon-fast-forward:before { content: "\f050"; }
.icon-step-forward:before { content: "\f051"; }
.icon-eject:before { content: "\f052"; }
.icon-chevron-left:before { content: "\f053"; }
.icon-chevron-right:before { content: "\f054"; }
.icon-plus-sign:before { content: "\f055"; }
.icon-minus-sign:before { content: "\f056"; }
.icon-remove-sign:before { content: "\f057"; }
.icon-ok-sign:before { content: "\f058"; }
.icon-question-sign:before { content: "\f059"; }
.icon-info-sign:before { content: "\f05a"; }
.icon-screenshot:before { content: "\f05b"; }
.icon-remove-circle:before { content: "\f05c"; }
.icon-ok-circle:before { content: "\f05d"; }
.icon-ban-circle:before { content: "\f05e"; }
.icon-arrow-left:before { content: "\f060"; }
.icon-arrow-right:before { content: "\f061"; }
.icon-arrow-up:before { content: "\f062"; }
.icon-arrow-down:before { content: "\f063"; }
.icon-share-alt:before { content: "\f064"; }
.icon-resize-full:before { content: "\f065"; }
.icon-resize-small:before { content: "\f066"; }
.icon-plus:before { content: "\f067"; }
.icon-minus:before { content: "\f068"; }
.icon-asterisk:before { content: "\f069"; }
.icon-exclamation-sign:before { content: "\f06a"; }
.icon-gift:before { content: "\f06b"; }
.icon-leaf:before { content: "\f06c"; }
.icon-fire:before { content: "\f06d"; }
.icon-eye-open:before { content: "\f06e"; }
.icon-eye-close:before { content: "\f070"; }
.icon-warning-sign:before { content: "\f071"; }
.icon-plane:before { content: "\f072"; }
.icon-calendar:before { content: "\f073"; }
.icon-random:before { content: "\f074"; }
.icon-comment:before { content: "\f075"; }
.icon-magnet:before { content: "\f076"; }
.icon-chevron-up:before { content: "\f077"; }
.icon-chevron-down:before { content: "\f078"; }
.icon-retweet:before { content: "\f079"; }
.icon-shopping-cart:before { content: "\f07a"; }
.icon-folder-close:before { content: "\f07b"; }
.icon-folder-open:before { content: "\f07c"; }
.icon-resize-vertical:before { content: "\f07d"; }
.icon-resize-horizontal:before { content: "\f07e"; }
.icon-bar-chart:before { content: "\f080"; }
.icon-twitter-sign:before { content: "\f081"; }
.icon-facebook-sign:before { content: "\f082"; }
.icon-camera-retro:before { content: "\f083"; }
.icon-key:before { content: "\f084"; }
.icon-cogs:before { content: "\f085"; }
.icon-comments:before { content: "\f086"; }
.icon-thumbs-up:before { content: "\f087"; }
.icon-thumbs-down:before { content: "\f088"; }
.icon-star-half:before { content: "\f089"; }
.icon-heart-empty:before { content: "\f08a"; }
.icon-signout:before { content: "\f08b"; }
.icon-linkedin-sign:before { content: "\f08c"; }
.icon-pushpin:before { content: "\f08d"; }
.icon-external-link:before { content: "\f08e"; }
.icon-signin:before { content: "\f090"; }
.icon-trophy:before { content: "\f091"; }
.icon-github-sign:before { content: "\f092"; }
.icon-upload-alt:before { content: "\f093"; }
.icon-lemon:before { content: "\f094"; }
.icon-phone:before { content: "\f095"; }
.icon-check-empty:before { content: "\f096"; }
.icon-bookmark-empty:before { content: "\f097"; }
.icon-phone-sign:before { content: "\f098"; }
.icon-twitter:before { content: "\f099"; }
.icon-facebook:before { content: "\f09a"; }
.icon-github:before { content: "\f09b"; }
.icon-unlock:before { content: "\f09c"; }
.icon-credit-card:before { content: "\f09d"; }
.icon-rss:before { content: "\f09e"; }
.icon-hdd:before { content: "\f0a0"; }
.icon-bullhorn:before { content: "\f0a1"; }
.icon-bell:before { content: "\f0a2"; }
.icon-certificate:before { content: "\f0a3"; }
.icon-hand-right:before { content: "\f0a4"; }
.icon-hand-left:before { content: "\f0a5"; }
.icon-hand-up:before { content: "\f0a6"; }
.icon-hand-down:before { content: "\f0a7"; }
.icon-circle-arrow-left:before { content: "\f0a8"; }
.icon-circle-arrow-right:before { content: "\f0a9"; }
.icon-circle-arrow-up:before { content: "\f0aa"; }
.icon-circle-arrow-down:before { content: "\f0ab"; }
.icon-globe:before { content: "\f0ac"; }
.icon-wrench:before { content: "\f0ad"; }
.icon-tasks:before { content: "\f0ae"; }
.icon-filter:before { content: "\f0b0"; }
.icon-briefcase:before { content: "\f0b1"; }
.icon-fullscreen:before { content: "\f0b2"; }
.icon-group:before { content: "\f0c0"; }
.icon-link:before { content: "\f0c1"; }
.icon-cloud:before { content: "\f0c2"; }
.icon-beaker:before { content: "\f0c3"; }
.icon-cut:before { content: "\f0c4"; }
.icon-copy:before { content: "\f0c5"; }
.icon-paper-clip:before { content: "\f0c6"; }
.icon-save:before { content: "\f0c7"; }
.icon-sign-blank:before { content: "\f0c8"; }
.icon-reorder:before { content: "\f0c9"; }
.icon-list-ul:before { content: "\f0ca"; }
.icon-list-ol:before { content: "\f0cb"; }
.icon-strikethrough:before { content: "\f0cc"; }
.icon-underline:before { content: "\f0cd"; }
.icon-table:before { content: "\f0ce"; }
.icon-magic:before { content: "\f0d0"; }
.icon-truck:before { content: "\f0d1"; }
.icon-pinterest:before { content: "\f0d2"; }
.icon-pinterest-sign:before { content: "\f0d3"; }
.icon-google-plus-sign:before { content: "\f0d4"; }
.icon-google-plus:before { content: "\f0d5"; }
.icon-money:before { content: "\f0d6"; }
.icon-caret-down:before { content: "\f0d7"; }
.icon-caret-up:before { content: "\f0d8"; }
.icon-caret-left:before { content: "\f0d9"; }
.icon-caret-right:before { content: "\f0da"; }
.icon-columns:before { content: "\f0db"; }
.icon-sort:before { content: "\f0dc"; }
.icon-sort-down:before { content: "\f0dd"; }
.icon-sort-up:before { content: "\f0de"; }
.icon-envelope-alt:before { content: "\f0e0"; }
.icon-linkedin:before { content: "\f0e1"; }
.icon-undo:before { content: "\f0e2"; }
.icon-legal:before { content: "\f0e3"; }
.icon-dashboard:before { content: "\f0e4"; }
.icon-comment-alt:before { content: "\f0e5"; }
.icon-comments-alt:before { content: "\f0e6"; }
.icon-bolt:before { content: "\f0e7"; }
.icon-sitemap:before { content: "\f0e8"; }
.icon-umbrella:before { content: "\f0e9"; }
.icon-paste:before { content: "\f0ea"; }
.icon-user-md:before { content: "\f200"; }

View File

@ -1,57 +0,0 @@
/*! gridster.js - v0.1.0 - 2012-08-14
* http://gridster.net/
* Copyright (c) 2012 ducksboard; Licensed MIT */
.gridster {
position:relative;
}
.gridster > * {
margin: 0 auto;
-webkit-transition: height .4s;
-moz-transition: height .4s;
-o-transition: height .4s;
-ms-transition: height .4s;
transition: height .4s;
}
.gridster .gs_w{
z-index: 2;
position: absolute;
}
.ready .gs_w:not(.preview-holder) {
-webkit-transition: opacity .3s, left .3s, top .3s;
-moz-transition: opacity .3s, left .3s, top .3s;
-o-transition: opacity .3s, left .3s, top .3s;
transition: opacity .3s, left .3s, top .3s;
}
.gridster .preview-holder {
z-index: 1;
position: absolute;
background-color: #fff;
border-color: #fff;
opacity: 0.3;
}
.gridster .player-revert {
z-index: 10!important;
-webkit-transition: left .3s, top .3s!important;
-moz-transition: left .3s, top .3s!important;
-o-transition: left .3s, top .3s!important;
transition: left .3s, top .3s!important;
}
.gridster .dragging {
z-index: 10!important;
-webkit-transition: all 0s !important;
-moz-transition: all 0s !important;
-o-transition: all 0s !important;
transition: all 0s !important;
}
/* Uncomment this if you set helper : "clone" in draggable options */
/*.gridster .player {
opacity:0;
}*/

File diff suppressed because one or more lines are too long

View File

@ -1,30 +0,0 @@
main:
log_file: pydashie.log
openstack:
allocation:
RegionOne:
vcpus_allocation_ratio: 2.0
ram_allocation_ratio: 1.0
# remove this amount per node available metric:
reserved_ram_per_node: 0
reserved_vcpus_per_node: 0
# remove this amount from total
# to take into account possible nova evacuate:
reserved_vcpus: 0
# ram in bytes
reserved_ram: 0
# total IPs are here as getting this from Neutron is
# far from straightforward
total_floating_ips: 256
auth:
auth_url: 'http://localhost:5000/v2.0'
username: 'admin'
password: 'openstack'
project_name: 'demo'
insecure: False
nagios:
services:
RegionOne:
statfile: './RegionOne-status.dat'
host: 'RegionOne-mon0'
username: 'admin'

View File

@ -1,39 +0,0 @@
import datetime
import json
from repeated_timer import RepeatedTimer
class DashieSampler(object):
def __init__(self, app, interval):
self._app = app
self._timer = RepeatedTimer(interval, self._sample)
def stop(self):
self._timer.stop()
def name(self):
'''
Child class implements this function
'''
return 'UnknownSampler'
def sample(self):
'''
Child class implements this function
'''
return {}
def _send_event(self, widget_id, body):
body['id'] = widget_id
body['updatedAt'] = (datetime.datetime.now().
strftime('%Y-%m-%d %H:%M:%S +0000'))
formatted_json = 'data: %s\n\n' % (json.dumps(body))
self._app.last_events[widget_id] = formatted_json
for event_queue in self._app.events_queue.values():
event_queue.put(formatted_json)
def _sample(self):
data = self.sample()
if data:
self._send_event(self.name(), data)

View File

@ -1,224 +0,0 @@
import argparse
import logging
import os
import sys
import Queue
import yaml
from flask import (
current_app,
Flask,
render_template,
Response,
send_from_directory,
request,
)
app = Flask(__name__)
# we setup the log in __main__
log = None
@app.route("/")
def main():
return render_template('main.html', title='pyDashie')
@app.route("/dashboard/<dashlayout>/")
def custom_layout(dashlayout):
return render_template('{}.html'.format(dashlayout), title='pyDashie')
@app.route("/assets/application.js")
def javascripts():
if not hasattr(current_app, 'javascripts'):
import coffeescript
scripts = [
'assets/javascripts/jquery.js',
'assets/javascripts/es5-shim.js',
'assets/javascripts/d3.v2.min.js',
'assets/javascripts/batman.js',
'assets/javascripts/batman.jquery.js',
'assets/javascripts/jquery.gridster.js',
'assets/javascripts/jquery.leanModal.min.js',
# 'assets/javascripts/dashing.coffee',
'assets/javascripts/dashing.gridster.coffee',
'assets/javascripts/jquery.knob.js',
'assets/javascripts/rickshaw.min.js',
# 'assets/javascripts/application.coffee',
'assets/javascripts/app.js',
# 'widgets/clock/clock.coffee',
'widgets/number/number.coffee',
'widgets/hotness/hotness.coffee',
'widgets/progress_bars/progress_bars.coffee',
'widgets/usage_gauge/usage_gauge.coffee',
'widgets/nagios/nagios.coffee',
'widgets/nagios_list/nagios_list.coffee',
'widgets/rickshawgraph/rickshawgraph.coffee'
]
nizzle = True
if not nizzle:
scripts = ['assets/javascripts/application.js']
output = []
for path in scripts:
path = ('{1}/{0}'.
format(path,
os.path.dirname(os.path.realpath(__file__))))
output.append('// JS: %s\n' % path)
if '.coffee' in path:
log.info('Compiling Coffee for %s ' % path)
contents = coffeescript.compile_file(path)
else:
f = open(path)
contents = f.read()
f.close()
output.append(contents)
if nizzle:
f = open('/tmp/foo.js', 'w')
for o in output:
print >> f, o
f.close()
f = open('/tmp/foo.js', 'rb')
output = f.read()
f.close()
current_app.javascripts = output
else:
current_app.javascripts = ''.join(output)
return Response(current_app.javascripts, mimetype='application/javascript')
@app.route('/assets/application.css')
def application_css():
scripts = [
'assets/stylesheets/application.css',
]
output = ''
for path in scripts:
path = '{1}/{0}'.format(path,
os.path.dirname(os.path.realpath(__file__)))
output = output + open(path).read()
return Response(output, mimetype='text/css')
@app.route('/assets/images/<path:filename>')
def send_static_img(filename):
directory = os.path.join('assets', 'images')
return send_from_directory(directory, filename)
@app.route('/views/<widget_name>.html')
def widget_html(widget_name):
html = '%s.html' % widget_name
path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'widgets', widget_name, html)
if os.path.isfile(path):
f = open(path)
contents = f.read()
f.close()
return contents
class Z:
pass
xyzzy = Z()
xyzzy.events_queue = {}
xyzzy.last_events = {}
xyzzy.using_events = True
xyzzy.MAX_QUEUE_LENGTH = 20
xyzzy.stopped = False
@app.route('/events')
def events():
if xyzzy.using_events:
event_stream_port = request.environ['REMOTE_PORT']
current_event_queue = Queue.Queue()
xyzzy.events_queue[event_stream_port] = current_event_queue
current_app.logger.info('New Client %s connected. Total Clients: %s' %
(event_stream_port, len(xyzzy.events_queue)))
# Start the newly connected client off by pushing
# the current last events
for event in xyzzy.last_events.values():
current_event_queue.put(event)
return Response(pop_queue(current_event_queue),
mimetype='text/event-stream')
return Response(xyzzy.last_events.values(), mimetype='text/event-stream')
def pop_queue(current_event_queue):
while not xyzzy.stopped:
try:
data = current_event_queue.get(timeout=0.1)
yield data
except Queue.Empty:
# this makes the server quit nicely - previously the queue
# threads would block and never exit. This makes it keep
# checking for dead application
pass
def purge_streams():
big_queues = [port for port, queue in xyzzy.events_queue
if len(queue) > xyzzy.MAX_QUEUE_LENGTH]
for big_queue in big_queues:
current_app.logger.info(('Client %s is stale. Disconnecting.' +
' Total Clients: %s') %
(big_queue, len(xyzzy.events_queue)))
del queue[big_queue]
def close_stream(*args, **kwargs):
event_stream_port = args[2][1]
del xyzzy.events_queue[event_stream_port]
log.info(('Client %s disconnected. Total Clients: %s' %
(event_stream_port, len(xyzzy.events_queue))))
def run_sample_app():
a = argparse.ArgumentParser("Openstack-PyDashboard")
a.add_argument("-c", "--config", dest="config", help="Path to config file",
required=True)
a.add_argument("-ip", "--interface", dest="ip",
help="IP address to serve on.", default="0.0.0.0")
a.add_argument("-p", "--port", help="port to serve on", default="5050")
args = a.parse_args()
conf = None
try:
with open(args.config) as f:
conf = yaml.load(f)
except IOError as e:
print "Couldn't load config file: %s" % e
sys.exit(1)
logging.basicConfig(filename=conf['main']['log_file'],
level=logging.INFO,
format='%(asctime)s %(message)s')
global log
log = logging.getLogger(__name__)
import SocketServer
SocketServer.BaseServer.handle_error = close_stream
import openstack_app
openstack_app.run(args, conf, app, xyzzy)
if __name__ == "__main__":
run_sample_app()

View File

@ -1,44 +0,0 @@
#!/usr/bin/env python
# quick checker for nagios status
import paramiko
from pynag.Parsers import status
def get_statusfiles(services):
# ssh to each server in the config, grab the status.dat and put it in
# the location specified in the config
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
for region in services.keys():
ssh.connect(services[region]['host'],
username=services[region]['username'])
ftp = ssh.open_sftp()
ftp.get('/var/lib/icinga/status.dat', services[region]['statfile'])
ftp.close()
ssh.close()
def parse_status(services):
# parse the status.dat files listed in the config
# return the status of the servers in a hash
for region in services.keys():
services[region]['warning'] = 0
services[region]['critical'] = 0
services[region]['unknown'] = 0
s = status(services[region]['statfile'])
s.parse()
for service in s.data.get('servicestatus', []):
if (int(service.get('scheduled_downtime_depth', None)) == 0
and int(service.get('problem_has_been_acknowledged',
None)) == 0):
# get all the 'not OK' services
if (int(service.get('current_state', None)) == 1):
services[region]['warning'] += 1
elif (int(service.get('current_state', None)) == 2):
services[region]['critical'] += 1
elif (int(service.get('current_state', None)) == 3):
services[region]['unknown'] += 1
return services

View File

@ -1,61 +0,0 @@
import collections
from openstack_samplers import (
CPUSampler,
RAMSampler,
IPSampler,
RegionsRAMSampler,
RegionsCPUSampler,
RegionIPSampler,
NagiosSampler,
NagiosRegionSampler,
ResourceSampler,
APIRegionSampler,
ServiceAPISampler
)
def run(args, conf, app, xyzzy):
client_cache = {}
response_cache = {'regions': {}, 'services': {},
'events': {'service': collections.deque(),
'region': collections.deque()}}
samplers = [
CPUSampler(xyzzy, 60, conf['openstack'], client_cache, response_cache),
RAMSampler(xyzzy, 60, conf['openstack'], client_cache, response_cache),
IPSampler(xyzzy, 60, conf['openstack'], client_cache, response_cache),
RegionsCPUSampler(xyzzy, 60, conf['openstack'], client_cache,
response_cache),
RegionsRAMSampler(xyzzy, 60, conf['openstack'], client_cache,
response_cache),
RegionIPSampler(xyzzy, 60, conf['openstack'], client_cache,
response_cache),
NagiosSampler(xyzzy, 15, conf['nagios']),
NagiosRegionSampler(xyzzy, 15, conf['nagios']),
ResourceSampler(xyzzy, 60, conf['openstack'], client_cache,
response_cache),
APIRegionSampler(xyzzy, 30, conf['openstack'], client_cache,
response_cache),
ServiceAPISampler(xyzzy, 30, conf['openstack'], client_cache,
response_cache),
]
try:
app.run(debug=True,
host=args.ip,
port=args.port,
threaded=True,
use_reloader=False,
use_debugger=True
)
finally:
print "Disconnecting clients"
xyzzy.stopped = True
print "Stopping %d timers" % len(samplers)
for (i, sampler) in enumerate(samplers):
sampler.stop()
print "Done"

View File

@ -1,572 +0,0 @@
import collections
import datetime
from contextlib import contextmanager
import nagios
from dashie_sampler import DashieSampler
from novaclient.v1_1 import client as novaclient
from cinderclient.v1 import client as cinderclient
from keystoneclient.v2_0 import client as keystoneclient
from neutronclient.v2_0 import client as neutronclient
class BaseOpenstackSampler(DashieSampler):
"""docstring for ClassName"""
def __init__(self, app, interval, conf=None, client_cache={},
response_cache={}):
self._os_clients = client_cache
self._conf = conf
self._response_cache = response_cache
super(BaseOpenstackSampler, self).__init__(app, interval)
def _convert(self, values, min_rounded):
# takes array of byte counts; returns array of value,unit pairs
units = ['TB', 'GB', 'MB', 'KB', 'B']
min_value = min(values)
for i, unit in enumerate(units):
threshold = 1024 ** (len(units) - i - 1)
if min_value >= threshold:
rounded = [(int(round(v / threshold)), unit) for v in values]
over_min = True
for value in rounded:
if value[0] < min_rounded:
over_min = False
if over_min:
return rounded
def _client(self, service, region):
if not self._os_clients.get(region):
self._os_clients[region] = {}
if not self._os_clients[region].get(service):
if service == 'compute':
client = novaclient.Client(
self._conf['auth']['username'],
self._conf['auth']['password'],
self._conf['auth']['project_name'],
self._conf['auth']['auth_url'],
region_name=region,
insecure=self._conf['auth']['insecure'])
self._os_clients[region][service] = client
elif service == 'network':
client = neutronclient.Client(
username=self._conf['auth']['username'],
password=self._conf['auth']['password'],
tenant_name=self._conf['auth']['project_name'],
auth_url=self._conf['auth']['auth_url'],
region_name=region,
insecure=self._conf['auth']['insecure'])
self._os_clients[region][service] = client
elif service == 'storage':
client = cinderclient.Client(
self._conf['auth']['username'],
self._conf['auth']['password'],
self._conf['auth']['project_name'],
self._conf['auth']['auth_url'],
region_name=region,
insecure=self._conf['auth']['insecure'])
self._os_clients[region][service] = client
elif service == 'identity':
client = keystoneclient.Client(
username=self._conf['auth']['username'],
password=self._conf['auth']['password'],
project_name=self._conf['auth']['project_name'],
auth_url=self._conf['auth']['auth_url'],
region_name=region,
insecure=self._conf['auth']['insecure'])
self._os_clients[region][service] = client
return self._os_clients[region][service]
@contextmanager
def timed(self, region, service):
start = datetime.datetime.utcnow()
yield
end = datetime.datetime.utcnow()
self._api_response(int((end - start).total_seconds() * 1000),
region, service)
def _api_response(self, ms, region, service):
self._response_cache['events']['region'].append(
{'region': region, 'service': service, 'ms': ms})
self._response_cache['events']['service'].append(
{'region': region, 'service': service, 'ms': ms})
class CPUSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(CPUSampler, self).__init__(*args, **kwargs)
self._last = 0
def name(self):
return 'cpu'
def sample(self):
max_cpu = 0
cur_cpu = 0
for region, allocation in self._conf['allocation'].iteritems():
nova = self._client('compute', region)
with self.timed(region, 'compute'):
stats = nova.hypervisors.statistics()
reserved = allocation['reserved_vcpus']
for i in range(stats.count):
reserved = reserved + allocation['reserved_vcpus_per_node']
cpu_ratio = allocation['vcpus_allocation_ratio']
max_cpu = max_cpu + (stats.vcpus * cpu_ratio) - reserved
cur_cpu = cur_cpu + stats.vcpus_used
s = {'min': 0,
'max': max_cpu,
'value': cur_cpu,
'last': self._last}
s['moreinfo'] = "%s out of %s" % (s['value'], s['max'])
s['current'] = s['value']
self._last = s['value']
return s
class RAMSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(RAMSampler, self).__init__(*args, **kwargs)
self._last = 0
def name(self):
return 'ram'
def sample(self):
max_ram = 0
cur_ram = 0
for region, allocation in self._conf['allocation'].iteritems():
nova = self._client('compute', region)
with self.timed(region, 'compute'):
stats = nova.hypervisors.statistics()
reserved = allocation['reserved_ram']
for i in range(stats.count):
reserved = reserved + allocation['reserved_ram_per_node']
ram_ratio = allocation['ram_allocation_ratio']
max_ram = (max_ram +
(stats.memory_mb * ram_ratio * 1024 * 1024) - reserved)
cur_ram = cur_ram + stats.memory_mb_used * 1024 * 1024
ram_converted, ram_converted_used = self._convert((max_ram, cur_ram), 5)
s = {'min': 0,
'max': ram_converted[0],
'value': ram_converted_used[0],
'last': self._last}
s['moreinfo'] = "%s%s out of %s%s" % (ram_converted_used[0],
ram_converted_used[1],
ram_converted[0],
ram_converted[1])
s['current'] = s['value']
self._last = s['value']
return s
class IPSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(IPSampler, self).__init__(*args, **kwargs)
self._last = 0
def name(self):
return 'ips'
def sample(self):
max_ips = 0
cur_ips = 0
for region in self._conf['allocation'].keys():
max_ips = (max_ips +
self._conf['allocation'][region]['total_floating_ips'])
neutron = self._client('network', region)
with self.timed(region, 'network'):
ips = neutron.list_floatingips()
with self.timed(region, 'network'):
routers = neutron.list_routers()
net_gateways = 0
for router in routers['routers']:
if router['external_gateway_info'] is not None:
net_gateways = net_gateways + 1
cur_ips = cur_ips + len(ips['floatingips']) + net_gateways
s = {'min': 0,
'max': max_ips,
'value': cur_ips,
'last': self._last}
s['moreinfo'] = "%s out of %s" % (cur_ips, max_ips)
s['current'] = s['value']
self._last = s['value']
return s
class RegionsCPUSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(RegionsCPUSampler, self).__init__(*args, **kwargs)
def name(self):
return 'cpu_regions'
def sample(self):
regions = []
for region, allocation in self._conf['allocation'].iteritems():
nova = self._client('compute', region)
with self.timed(region, 'compute'):
stats = nova.hypervisors.statistics()
reserved = allocation['reserved_vcpus']
for i in range(stats.count):
reserved = reserved + allocation['reserved_vcpus_per_node']
cpu_ratio = allocation['vcpus_allocation_ratio']
max_cpu = (stats.vcpus * cpu_ratio) - reserved
cur_cpu = stats.vcpus_used
regions.append({'name': region,
'progress': (cur_cpu * 100.0) / max_cpu,
'max': max_cpu, 'value': cur_cpu})
return {'progress_items': regions}
class RegionsRAMSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(RegionsRAMSampler, self).__init__(*args, **kwargs)
def name(self):
return 'ram_regions'
def sample(self):
regions = []
for region, allocation in self._conf['allocation'].iteritems():
nova = self._client('compute', region)
with self.timed(region, 'compute'):
stats = nova.hypervisors.statistics()
reserved = allocation['reserved_ram']
for i in range(stats.count):
reserved = reserved + allocation['reserved_ram_per_node']
ram_ratio = allocation['ram_allocation_ratio']
max_ram = (stats.memory_mb * ram_ratio * 1024 * 1024) - reserved
cur_ram = stats.memory_mb_used * 1024 * 1024
ram_converted, ram_converted_used = self._convert((max_ram,
cur_ram), 5)
regions.append({'name': region,
'progress': ((ram_converted_used[0] * 100.0) /
ram_converted[0]),
'max': ram_converted,
'value': ram_converted_used[0]})
return {'progress_items': regions}
class RegionIPSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(RegionIPSampler, self).__init__(*args, **kwargs)
self._last = 0
def name(self):
return 'ips_regions'
def sample(self):
regions = []
for region in self._conf['allocation'].keys():
neutron = self._client('network', region)
with self.timed(region, 'network'):
ips = neutron.list_floatingips()
with self.timed(region, 'network'):
routers = neutron.list_routers()
net_gateways = 0
for router in routers['routers']:
if router['external_gateway_info'] is not None:
net_gateways = net_gateways + 1
cur_ips = len(ips['floatingips']) + net_gateways
max_ips = self._conf['allocation'][region]['total_floating_ips']
regions.append({'name': region,
'progress': ((cur_ips * 100.0) /
max_ips),
'max': max_ips, 'value': cur_ips})
return {'progress_items': regions}
class NagiosSampler(BaseOpenstackSampler):
def __init__(self, *args, **kwargs):
super(NagiosSampler, self).__init__(*args, **kwargs)
self._last = 0
def name(self):
return 'nagios'
def sample(self):
try:
nagios.get_statusfiles(self._conf['services'])
servicestatus = nagios.parse_status(self._conf['services'])
criticals = 0
warnings = 0
for region in servicestatus:
criticals = criticals + servicestatus[region]['critical']
warnings = warnings + servicestatus[region]['warning']
status = 'green'
if criticals > 0:
status = 'red'
elif warnings > 0:
status = 'yellow'
s = {'criticals': criticals,
'warnings': warnings,
'status': status}
return s
except Exception, e:
print e
class NagiosRegionSampler(BaseOpenstackSampler):
def name(self):
return 'nagios_regions'
def sample(self):
try:
nagios.get_statusfiles(self._conf['services'])
servicestatus = nagios.parse_status(self._conf['services'])
criticals = []
warnings = []
for region in servicestatus:
criticals.append({'label': region,
'value': servicestatus[region]['critical']})
warnings.append({'label': region,
'value': servicestatus[region]['warning']})
# (adriant) the following is for easy testing:
# regions = ['region1', 'region2', 'region3']
# criticals = []
# warnings = []
# for region in regions:
# criticals.append({'label': region, 'value': random.randint(0, 5)})
# warnings.append({'label': region, 'value': random.randint(0, 5)})
return {'criticals': criticals, 'warnings': warnings}
except Exception, e:
print e
class ResourceSampler(BaseOpenstackSampler):
def name(self):
return 'resources'
def sample(self):
resources = {'instances': 0,
'routers': 0,
'networks': 0,
# 'volumes': 0,
# 'images': 0,
'vpns': 0}
for region in self._conf['allocation'].keys():
neutron = self._client('network', region)
nova = self._client('compute', region)
# cinder = self._client('storage', region)
with self.timed(region, 'compute'):
stats = nova.hypervisors.statistics()
resources['instances'] = resources['instances'] + stats.running_vms
with self.timed(region, 'network'):
routers = neutron.list_routers()
resources['routers'] = (resources['routers'] +
len(routers['routers']))
with self.timed(region, 'network'):
networks = neutron.list_networks()
resources['networks'] = (resources['networks'] +
len(networks['networks']))
with self.timed(region, 'network'):
vpns = neutron.list_vpnservices()
resources['vpns'] = (resources['vpns'] +
len(vpns['vpnservices']))
# with self.timed(region, 'volume'):
# volumes = cinder.volumes.list(search_opts={'all_tenants': 1})
# resources['volumes'] = (resources['volumes'] +
# len(volumes))
items = []
for key, value in resources.iteritems():
items.append({'label': key, 'value': value})
return {'items': items}
class BaseAPISamper(BaseOpenstackSampler):
def _process_event(self, cache, key, ms, base_cache):
if cache:
cache['items'].append({'x': cache['x'],
'y': ms})
else:
cache = {}
cache['items'] = collections.deque()
cache['x'] = 0
cache['items'].append({'x': cache['x'],
'y': ms})
base_cache[key] = cache
cache['x'] += 1
# to stop the x value getting too high
if cache['x'] == 1000000:
# reset the x value, and adjust the items
cache['x'] = 0
for time in cache['items']:
time['x'] = cache['x']
cache['x'] += 1
if len(cache['items']) > 100:
cache['items'].popleft()
stats = {'min': -1, 'max': -1, 'avg': -1}
total = 0
for time in cache['items']:
total += time['y']
if time['y'] > stats['max']:
stats['max'] = time['y']
if stats['min'] == -1 or time['y'] < stats['min']:
stats['min'] = time['y']
stats['avg'] = int(total / len(cache['items']))
cache['stats'] = stats
class APIRegionSampler(BaseAPISamper):
def __init__(self, app, interval, conf=None, client_cache={},
response_cache={}, resync_period=180):
super(APIRegionSampler, self).__init__(app, interval, conf,
client_cache, response_cache)
self._no_sync = None
self._resync_period = resync_period
def name(self):
return 'api_region_response'
def sample(self):
while self._response_cache['events']['region']:
event = self._response_cache['events']['region'].popleft()
cache = self._response_cache['regions'].get(event['region'])
self._process_event(cache, event['region'], event['ms'],
self._response_cache['regions'])
displayedValue = ""
series = []
x = None
equal = True
for region, cache in self._response_cache['regions'].iteritems():
if x is None:
x = cache['x']
if x != cache['x']:
equal = False
displayedValue += ("%s - (min: %s max: %s avg: %s)\n" %
(region,
cache['stats']['min'],
cache['stats']['max'],
cache['stats']['avg']))
series.append({'name': region, 'data': list(cache['items'])})
if equal:
self._no_sync = None
elif self._no_sync is None:
self._no_sync = datetime.datetime.utcnow()
elif ((datetime.datetime.utcnow() - self._no_sync).total_seconds() >
self._resync_period):
self._response_cache['regions'].clear()
displayedValue = "Clearing Cache to resync graphs"
series = []
self._no_sync = None
return {'displayedValue': displayedValue, 'series': series}
class ServiceAPISampler(BaseAPISamper):
def __init__(self, *args, **kwargs):
super(ServiceAPISampler, self).__init__(*args, **kwargs)
self.service_list = []
def name(self):
return 'api_service_response'
def sample(self):
while self._response_cache['events']['service']:
event = self._response_cache['events']['service'].popleft()
cache = self._response_cache['services'].get(event['service'])
self._process_event(cache, event['service'], event['ms'],
self._response_cache['services'])
if not self.service_list:
self.service_list = self._response_cache['services'].keys()
self.next_service = 0
displayedValue = ""
series = []
while True:
try:
service = self.service_list[self.next_service]
cache = self._response_cache['services'][service]
displayedValue += ("%s - (min: %s max: %s avg: %s)\n" %
(service,
cache['stats']['min'],
cache['stats']['max'],
cache['stats']['avg']))
series.append({'name': service, 'data': list(cache['items'])})
self.next_service += 1
return {'displayedValue': displayedValue, 'series': series}
except IndexError:
if self.next_service == 0:
break
self.service_list = self._response_cache['services'].keys()
self.next_service = 0

View File

@ -1,28 +0,0 @@
from threading import Timer
class RepeatedTimer(object):
def __init__(self, interval, function, *args, **kwargs):
self._timer = None
self.interval = interval
self.function = function
self.args = args
self.kwargs = kwargs
self.is_running = False
self.start()
def _run(self):
self.is_running = False
self.start()
self.function(*self.args, **self.kwargs)
def start(self):
if not self.is_running:
self._timer = Timer(self.interval, self._run)
self._timer.start()
self.is_running = True
def stop(self):
self._timer.cancel()
self.is_running = False

View File

@ -1,23 +0,0 @@
import trello
from pydashie.dashie_sampler import DashieSampler
class TrelloSampler(DashieSampler):
def __init__(self, *args, **kwargs):
super(TrelloSampler, self).__init__(*args, **kwargs)
self._last = 0
self.key = '0667001bfafc26b53864fd08124159f0'
self.secret = 'cb6a56444019998b879015c53a17f695700e6dd2f6f147870f27aff89e156c2b'
self.token = 'b4c5cdbfcda87de13c3a8b487d4c24b0ad9536672e24675d50aa1e08d9d89c67'
self.api = trello.TrelloApi
def name(self):
return 'trello'
def sample(self):
s = {'value': 1,
'current': 2,
'last': self._last}
self._last = s['current']
return s

View File

@ -1,23 +0,0 @@
import requests
from pydashie.dashie_sampler import DashieSampler
class WebsiteUpSampler(DashieSampler):
def __init__(self, *args, **kwargs):
super(WebsiteUpSampler, self).__init__(*args, **kwargs)
self._last = 0
self.page = 'http://www.google.com'
def name(self):
return 'website_up'
def sample(self):
try:
r = requests.get(self.page)
assert r.status_code == 200
up = 'UP'
except:
up = 'DOWN'
return {'text': up}

View File

@ -1,76 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{title}}</title>
<!-- The javascript and css are managed by sprockets. The files can be found in the /assets folder-->
<script type="text/javascript" src="/assets/application.js"></script>
<link rel="stylesheet" href="/assets/application.css">
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700' rel='stylesheet' type='text/css'>
<link rel="icon" href="/assets/images/favicon.ico">
</head>
<body>
<div id="container">
<div class="gridster">
<ul>
<li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
<div data-id="clock" data-view="Clock" data-title="Clock"></div>
</li>
<li data-row="1" data-col="2" data-sizex="1" data-sizey="1">
<div data-id="nagios" data-view="Nagios" data-unordered="true" data-title="Icinga Prod" data-moreinfo="Unacknowledged events"></div>
</li>
<li data-row="2" data-col="3" data-sizex="1" data-sizey="2">
<div data-id="resources" data-view="List" data-title="Resource Totals" data-unordered="true" data-moreinfo="Aggregate resources across all regions." ></div>
</li>
<li data-row="1" data-col="3" data-sizex="1" data-sizey="1">
<div data-id="nagios_regions" data-view="NagiosList" data-title="Nagios by region" data-subtitle1="Criticals" data-subtitle2="Warnings"></div>
</li>
<li data-row="1" data-col="4" data-sizex="1" data-sizey="1">
<div data-id="cpu" data-view="Meter" data-title="VCPU"></div>
</li>
<li data-row="2" data-col="4" data-sizex="1" data-sizey="1">
<div data-id="ram" data-view="Meter" data-title="RAM"></div>
</li>
<li data-row="3" data-col="4" data-sizex="1" data-sizey="1">
<div data-id="ips" data-view="Meter" data-title="Floating IPs"></div>
</li>
<li data-row="1" data-col="5" data-sizex="2" data-sizey="1">
<div data-id="cpu_regions" data-view="ProgressBars" data-title="VCPU provisioned per region"></div>
</li>
<li data-row="2" data-col="5" data-sizex="2" data-sizey="1">
<div data-id="ram_regions" data-view="ProgressBars" data-title="RAM provisioned per region"></div>
</li>
<li data-row="3" data-col="5" data-sizex="2" data-sizey="1">
<div data-id="ips_regions" data-view="ProgressBars" data-title="IPs provisioned per region"></div>
</li>
<li data-row="2" data-col="1" data-sizex="2" data-sizey="1">
<div data-id="api_region_response" data-view="Rickshawgraph" data-title="API Response Times by Region (ms)" data-moreinfo="out of 100 last queries" data-color-scheme="default" data-legend="true" data-unstack='true' data-renderer="area"
data-max='3200'></div>
</li>
<li data-row="3" data-col="1" data-sizex="2" data-sizey="1">
<div data-id="api_service_response" data-view="Rickshawgraph" data-title="API Response Times by Service (ms)" data-moreinfo="out of 100 last queries" data-color-scheme="default" data-legend="true" data-unstack='true' data-renderer="area"
data-max='3200'></div>
</li>
</ul>
</div>
</body>
</html>

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{title}}</title>
<!-- The javascript and css are managed by sprockets. The files can be found in the /assets folder-->
<script type="text/javascript" src="/assets/application.js"></script>
<link rel="stylesheet" href="/assets/application.css">
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700' rel='stylesheet' type='text/css'>
<link rel="icon" href="/assets/images/favicon.ico">
</head>
<body>
<div id="container">
<div class="gridster">
<ul>
<li data-row="1" data-col="1" data-sizex="2" data-sizey="1">
<div data-id="welcome" data-view="Text" data-title="Hello" data-text="This is your shiny new (python powered) dashboard." data-moreinfo="Protip: You can drag the widgets around!"></div>
</li>
<li data-row="1" data-col="3" data-sizex="1" data-sizey="1">
<div data-id="synergy" data-view="Meter" data-title="Synergy" data-min="0" data-max="100"></div>
</li>
<li data-row="2" data-col="1" data-sizex="1" data-sizey="1">
<div data-id="clock" data-view="Clock" data-title="Clock"></div>
</li>
<li data-row="2" data-col="2" data-sizex="1" data-sizey="1">
<div data-view="Image" data-image="/images/dashie.png"></div>
</li>
</ul>
</div>
</body>
</html>

View File

@ -1,18 +0,0 @@
class Dashing.Clock extends Dashing.Widget
ready: ->
setInterval(@startTime, 500)
startTime: =>
today = new Date()
h = (today.getHours() % 13) + 1
m = today.getMinutes()
s = today.getSeconds()
m = @formatTime(m)
s = @formatTime(s)
@set('time', h + ":" + m + ":" + s)
@set('date', today.toDateString())
formatTime: (i) ->
if i < 10 then "0" + i else i

View File

@ -1,2 +0,0 @@
<h1 data-bind="date"></h1>
<h2 data-bind="time"></h2>

View File

@ -1,13 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #666666;
// ----------------------------------------------------------------------------
// Widget-clock styles
// ----------------------------------------------------------------------------
.widget-clock {
background-color: $background-color;
}

View File

@ -1,24 +0,0 @@
class Dashing.Comments extends Dashing.Widget
@accessor 'quote', ->
"#{@get('current_comment')?.body}"
ready: ->
@currentIndex = 0
@commentElem = $(@node).find('.comment-container')
@nextComment()
@startCarousel()
onData: (data) ->
@currentIndex = 0
startCarousel: ->
setInterval(@nextComment, 8000)
nextComment: =>
comments = @get('comments')
if comments
@commentElem.fadeOut =>
@currentIndex = (@currentIndex + 1) % comments.length
@set 'current_comment', comments[@currentIndex]
@commentElem.fadeIn()

View File

@ -1,7 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<div class="comment-container">
<h3><img data-bind-src='current_comment.avatar'/><span data-bind='current_comment.name' class="name"></span></h3>
<p class="comment" data-bind='quote'></p>
</div>
<p class="more-info" data-bind="moreinfo | raw"></p>

View File

@ -1,33 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #eb9c3c;
$title-color: rgba(255, 255, 255, 0.7);
$moreinfo-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-comment styles
// ----------------------------------------------------------------------------
.widget-comments {
background-color: $background-color;
.title {
color: $title-color;
margin-bottom: 15px;
}
.name {
padding-left: 5px;
}
.comment-container {
display: none;
}
.more-info {
color: $moreinfo-color;
}
}

View File

@ -1,6 +0,0 @@
Direct port of the Hotness Widget from Shopify's Dashie Widget Challenge to pydashie
All attribution to Dashboard Dude -
https://gist.github.com/rowanu/6246149
http://dashboarddude.com/blog/2013/08/16/dashing-dashboard-widget-challenge-the-hotness/

View File

@ -1,26 +0,0 @@
class Dashing.Hotness extends Dashing.Widget
@accessor 'value', Dashing.AnimatedValue
constructor: ->
super
onData: (data) ->
node = $(@node)
value = parseInt data.value
cool = parseInt node.data "cool"
warm = parseInt node.data "warm"
level = switch
when value <= cool then 0
when value >= warm then 4
else
bucketSize = (warm - cool) / 3 # Total # of colours in middle
Math.ceil (value - cool) / bucketSize
backgroundClass = "hotness#{level}"
lastClass = @get "lastClass"
node.toggleClass "#{lastClass} #{backgroundClass}"
@set "lastClass", backgroundClass

View File

@ -1,5 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<h2 class="value" data-bind="value | shortenedNumber | prepend prefix | append suffix"></h2>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,59 +0,0 @@
//-----------------------------------------------------------------------------
// Direct port of the Hotness Widget from Shopify's Dashie Widget Challenge to pydashie
// All attribution to Dashboard Dude -
// https://gist.github.com/rowanu/6246149
// http://dashboarddude.com/blog/2013/08/16/dashing-dashboard-widget-challenge-the-hotness/
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Mixins
// ----------------------------------------------------------------------------
@mixin transition($transition-property, $transition-time, $method) {
-webkit-transition: $transition-property $transition-time $method;
-moz-transition: $transition-property $transition-time $method;
-o-transition: $transition-property $transition-time $method;
transition: $transition-property $transition-time $method;
}
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #000000;
$value-color: #FFFFFF;
$title-color: rgba(255, 255, 255, 0.7);
$updated-at-color: rgba(0, 0, 0, 0.3);
// ----------------------------------------------------------------------------
// Widget-hotness styles
// ----------------------------------------------------------------------------
.widget-hotness {
background-color: $background-color;
@include transition(background-color, 1s, linear);
.title {
color: $title-color;
}
.value {
color: $value-color;
}
.updated-at {
color: $updated-at-color;
}
}
.hotness0 { background-color: #00C176; }
.hotness1 { background-color: #88C100; }
.hotness2 { background-color: #FABE28; }
.hotness3 { background-color: #FF8A00; }
.hotness4 { background-color: #FF003C; }
// // More colour-blind friendly palette
// .hotness0 { background-color: #046D8B; }
// .hotness1 { background-color: #309292; }
// .hotness2 { background-color: #2FB8AC; }
// .hotness3 { background-color: #93A42A; }
// .hotness4 { background-color: #ECBE13; }

View File

@ -1,9 +0,0 @@
class Dashing.Iframe extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
# Handle incoming data
# You can access the html node of this widget with `@node`
# Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in.

View File

@ -1 +0,0 @@
<iframe data-bind-src="url" frameborder=0></iframe>

View File

@ -1,8 +0,0 @@
.widget-iframe {
padding: 3px 0px 0px 0px !important;
iframe {
width: 100%;
height: 100%;
}
}

View File

@ -1,9 +0,0 @@
class Dashing.Image extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
# Handle incoming data
# You can access the html node of this widget with `@node`
# Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in.

View File

@ -1 +0,0 @@
<img data-bind-src="image | prepend '/assets'" data-bind-width="width"/>

View File

@ -1,13 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #FFFFFF;
// ----------------------------------------------------------------------------
// Widget-image styles
// ----------------------------------------------------------------------------
.widget-image {
background-color: $background-color;
}

View File

@ -1,6 +0,0 @@
class Dashing.List extends Dashing.Widget
ready: ->
if @get('unordered')
$(@node).find('ol').remove()
else
$(@node).find('ul').remove()

View File

@ -1,18 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<ol>
<li data-foreach-item="items">
<span class="label" data-bind="item.label"></span>
<span class="value" data-bind="item.value"></span>
</li>
</ol>
<ul class="list-nostyle">
<li data-foreach-item="items">
<span class="label" data-bind="item.label"></span>
<span class="value" data-bind="item.value"></span>
</li>
</ul>
<p class="more-info" data-bind="moreinfo"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,61 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #118E9E;
$value-color: #fff;
$title-color: rgba(255, 255, 255, 1);
$label-color: rgba(255, 255, 255, 1);
$moreinfo-color: rgba(255, 255, 255, 1);
// ----------------------------------------------------------------------------
// Widget-list styles
// ----------------------------------------------------------------------------
.widget-list {
background-color: $background-color;
vertical-align: top;
.title {
color: $title-color;
}
ol, ul {
margin: 0 15px;
text-align: left;
color: $label-color;
}
ol {
list-style-position: inside;
}
li {
margin-bottom: 5px;
}
.list-nostyle {
list-style: none;
}
.label {
color: $label-color;
}
.value {
float: right;
margin-left: 12px;
font-weight: 600;
color: $value-color;
}
.updated-at {
color: rgba(0, 0, 0, 0.6);
font-size: 0.8em;
}
.more-info {
color: $moreinfo-color;
}
}

View File

@ -1,14 +0,0 @@
class Dashing.Meter extends Dashing.Widget
@accessor 'value', Dashing.AnimatedValue
constructor: ->
super
@observe 'value', (value) ->
$(@node).find(".meter").val(value).trigger('change')
ready: ->
meter = $(@node).find(".meter")
meter.attr("data-bgcolor", meter.css("background-color"))
meter.attr("data-fgcolor", meter.css("color"))
meter.knob()

View File

@ -1,7 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<input class="meter" data-angleOffset=-125 data-angleArc=250 data-width=200 data-readOnly=true data-bind-value="value | shortenedNumber" data-bind-data-min="min" data-bind-data-max="max">
<p class="more-info" data-bind="moreinfo"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,37 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #5B7F97;
$title-color: rgb(255, 255, 255);
$moreinfo-color: rgb(255, 255, 255);
$meter-background: darken($background-color, 20%);
// ----------------------------------------------------------------------------
// Widget-meter styles
// ----------------------------------------------------------------------------
.widget-meter {
background-color: $background-color;
input.meter {
background-color: $meter-background;
color: #fff;
}
.title {
color: $title-color;
}
.more-info {
color: $moreinfo-color;
font-size: 1em;
}
.updated-at {
color: rgba(0, 0, 0, 0.6);
font-size: 0.8em;
}
}

View File

@ -1,9 +0,0 @@
class Dashing.Nagios extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
# Handle incoming data
# You can access the html node of this widget with `@node`
# Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in.

View File

@ -1,21 +0,0 @@
<ul>
<li data-bind-class="status">
<h1 class="title" data-bind="title"></h1>
<div data-showif="status | equals 'error'"> <!-- error -->
<h4>Error querying Icinga</h4>
</div> <!-- /no error -->
<div data-hideif="status | equals 'error'"> <!-- no error -->
<div>
<h3 data-bind="criticals"></h3>
<h4 data-showif="criticals | equals 1">critical</h4>
<h4 data-hideif="criticals | equals 1">criticals</h4>
</div>
<div>
<h3 data-bind="warnings"></h3>
<h4 data-showif="warnings | equals 1">warning</h4>
<h4 data-hideif="warnings | equals 1">warnings</h4>
</div>
<p class="updated-at" data-bind="updatedAtMessage"></p>
</div> <!-- /no error -->
</li>
</ul>

View File

@ -1,51 +0,0 @@
$background: #444;
$text: #fff;
$success: #86d751;
$warning: #edde43;
$failure: #e3394f;
$error: #f75f00;
.widget-nagios {
li {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding-top: 25px;
h3 {
font-size: 60px;
font-weight: bold;
}
h4 {
font-size: 20px;
font-weight: bold;
text-transform: uppercase;
}
p {
&.updated-at {
color: rgba(0, 0, 0, 0.6);
font-size: 0.8em;
}
}
&.green {
background-color: $success;
}
&.yellow {
background-color: $warning;
}
&.red {
background-color: $failure;
}
&.error {
background-color: $error;
}
}
}

View File

@ -1 +0,0 @@
class Dashing.NagiosList extends Dashing.Widget

View File

@ -1,22 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<h2 class="subtitle" data-bind="subtitle1"></h1>
<ul>
<li data-foreach-item="criticals">
<span class="label" data-bind="item.label"></span>
<span class="value" data-bind="item.value"></span>
</li>
</ul>
<h2 class="subtitle" data-bind="subtitle2"></h1>
<ul>
<li data-foreach-item="warnings">
<span class="label" data-bind="item.label"></span>
<span class="value" data-bind="item.value"></span>
</li>
</ul>
<p class="more-info" data-bind="moreinfo"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,68 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #5B7F97;
$value-color: #fff;
$title-color: rgba(255, 255, 255, 1);
$label-color: rgba(255, 255, 255, 1);
$moreinfo-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-nagios-list styles
// ----------------------------------------------------------------------------
.widget-nagios-list {
background-color: $background-color;
vertical-align: top;
.title {
color: $title-color;
font-size: 1.2em;
}
.subtitle {
color: $title-color;
font-size: 1em;
}
ol, ul {
margin: 0 15px;
text-align: left;
color: $label-color;
}
ol {
nagios-list-style-position: inside;
}
li {
font-size: 1.1em;
}
.nagios-list-nostyle {
nagios-list-style: none;
}
.label {
color: $label-color;
font-weight: 600;
}
.value {
float: right;
margin-left: 10px;
font-weight: 600;
color: $value-color;
}
.updated-at {
color: rgba(0, 0, 0, 0.6);
font-size: 0.8em;
}
.more-info {
color: $moreinfo-color;
}
}

View File

@ -1,21 +0,0 @@
class Dashing.Number extends Dashing.Widget
@accessor 'current', Dashing.AnimatedValue
@accessor 'difference', ->
if @get('last')
last = parseInt(@get('last'))
current = parseInt(@get('current'))
if last != 0
diff = Math.abs(Math.round((current - last) / last * 100))
"#{diff}%"
else
""
@accessor 'arrow', ->
if @get('last')
if parseInt(@get('current')) > parseInt(@get('last')) then '+' else '-'
#if parseInt(@get('current')) > parseInt(@get('last')) then 'icon-arrow-up' else 'icon-arrow-down'
onData: (data) ->
if data.status
$(@get('node')).addClass("status-#{data.status}")

View File

@ -1,11 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<h2 class="value" data-bind="current | shortenedNumber | prepend prefix"></h2>
<p class="change-rate">
<i data-bind-class="arrow"></i><span data-bind="difference"></span>
</p>
<p class="more-info" data-bind="moreinfo | raw"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,39 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #47bbb3;
$value-color: #fff;
$title-color: rgba(255, 255, 255, 0.7);;
$moreinfo-color: rgba(255, 255, 255, 0.7);;
// ----------------------------------------------------------------------------
// Widget-number styles
// ----------------------------------------------------------------------------
.widget-number {
background-color: $background-color;
.title {
color: $title-color;
}
.value {
color: $value-color;
}
.change-rate {
font-weight: 500;
font-size: 30px;
color: $value-color;
}
.more-info {
color: $moreinfo-color;
}
.updated-at {
color: rgba(0, 0, 0, 0.3);
}
}

View File

@ -1,5 +0,0 @@
Direct port of the Progress Bars Widget from Shopify's Dashie Widget Challenge to pydashie
All attribution to mdirienzo -
https://gist.github.com/mdirienzo/6716905

View File

@ -1,205 +0,0 @@
class Dashing.ProgressBars extends Dashing.Widget
@accessor 'title'
ready: ->
@drawWidget( @get('progress_items') )
onData: (eventData) ->
@drawWidget(eventData.progress_items)
drawWidget: (progress_items) ->
container = $(@node)
rowsContainer = container.find('.rows-container')
if progress_items.length == 0
rowsContainer.empty()
else
# Float value used to scale the rows to use the entire space of the widget
rowHeight = 100 / progress_items.length
counter = 0
@clearIntervals()
# Add or move rows for each project. Checks first if the row already exists.
progress_items.forEach (item) =>
normalizedItemName = item.name.replace(/\W+/g, "_")
referenceRow = rowsContainer.children().eq(counter)
existingRow = rowsContainer.find("."+normalizedItemName)
if existingRow.length
if referenceRow.attr("class").indexOf(normalizedItemName) == -1
existingRow.detach().insertBefore(referenceRow)
existingRow.hide().fadeIn(1200)
else
row = createRow(item)
if referenceRow.length
row.insertBefore(referenceRow)
else
rowsContainer.append(row)
row.hide().fadeIn(1200)
elem = rowsContainer.find("."+normalizedItemName+" .inner-progress-bar")
if elem.length
@animateProgressBarContent(elem[0], parseFloat(elem[0].style.width),
parseFloat(item.progress), parseFloat(item.value),
parseFloat(item.max), 1000)
++counter
# Remove any nodes that were not in the new data, these will be the rows
# at the end of the widget.
currentNode = rowsContainer.children().eq(counter-1)
while currentNode.next().length
currentNode = currentNode.next()
currentNode.fadeOut(100, -> $(this).remove() )
# Set the height after rows were added/removed.
rows = rowsContainer.children()
percentageOfTotalHeight = 100 / progress_items.length
applyCorrectedRowHeight(rows, percentageOfTotalHeight)
applyZebraStriping(rows)
#***/
# Create a JQuery row object with the proper structure and base
# settings for the item passed in.
#
# The Row DOM Hierarchy:
# Row
# Row Content (here so we can use vertical alignment)
# Project Name
# Outer Bar Container (The border and background)
# Inner Bar Container (The progress and text)
#
# @item - object representing an item and it's progress
# /
createRow = (item) ->
row = ( $("<div/>")
.attr("class", "row " + item.name.replace(/\W+/g, "_") ) )
rowContent = ( $("<div/>")
.attr("class", "row-content") )
projectName = ( $("<div/>")
.attr("class", "project-name")
.text(item.name)
.attr("title", item.name) )
outerProgressBar = ( $("<div/>")
.attr("class", "outer-progress-bar") )
innerProgressBar = $("<div/>")
.attr("class", "inner-progress-bar")
.text("0%")
innerProgressBar.css("width", "0%")
# Put it all together.
outerProgressBar.append(innerProgressBar)
rowContent.append(projectName)
rowContent.append(outerProgressBar)
row.append(rowContent)
return row
#***/
# Does calculations for the animation and sets up the javascript
# interval to perform the animation.
#
# @element - element that is going to be animated.
# @from - the value that the element starts at.
# @to - the value that the element is going to.
# @value - the actual value (not percentage) to display.
# @max - the max value (used for percentage).
# @baseDuration - the minimum time the animation will perform.
# /
animateProgressBarContent: (element, from, to, value, max, baseDuration) ->
endpointDifference = (to-from)
if endpointDifference != 0
currentValue = from
# Every x milliseconds, the function should run.
stepInterval = 16.667
# Change the duration based on the distance between points.
duration = baseDuration + Math.abs(endpointDifference) * 25
numberOfSteps = duration / stepInterval
valueIncrement = endpointDifference / numberOfSteps
interval = setInterval(
->
currentValue += valueIncrement
if Math.abs(currentValue - from) >= Math.abs(endpointDifference)
setProgressBarValue(element, to, value, max)
clearInterval(interval)
else
setProgressBarValue(element, currentValue, value, max)
stepInterval)
@addInterval(interval)
#***/
# Sets the text and width of the element in question to the specified value
# after making sure it is bounded between [0-100]
#
# @element - element to be set
# @value - the numeric value to set the element to. This can be a float.
# @literal - the actual value (not in percentage).
# @max - the max value (which was used for percentage).
# /
setProgressBarValue = (element, value, literal, max) ->
if (value > 100)
value = 100
else if (value < 0)
value = 0
element.textContent = Math.floor(value) + "% - " + Math.floor(literal) + "/" + Math.floor(max) + ""
element.style.width = value + "%"
#***/
# Applies a percentage-based row height to the list of rows passed in.
#
# @rows - the elements to apply this height value to
# @percentageOfTotalHeight - The height to be applied to each row.
# /
applyCorrectedRowHeight = (rows, percentageOfTotalHeight) ->
height = percentageOfTotalHeight + "%"
for row in rows
row.style.height = height
#***/
# Adds a class to every other row to change the background color. This
# was done mainly for readability.
#
# @rows - list of elements to run zebra-striping on
# /
applyZebraStriping = (rows) ->
isZebraStripe = false
for row in rows
# In case elements are moved around, we don't want them to retain this.
row.classList.remove("zebra-stripe")
if isZebraStripe
row.classList.add("zebra-stripe")
isZebraStripe = !isZebraStripe
#***/
# Stops all javascript intervals from running and clears the list.
#/
clearIntervals: ->
if @intervalList
for interval in @intervalList
clearInterval(interval)
@intervalList = []
#***/
# Adds a javascript interval to a list so that it can be tracked and cleared
# ahead of time if the need arises.
#
# @interval - the javascript interval to add
#/
addInterval: (interval) ->
if !@intervalList
@intervalList = []
@intervalList.push(interval)

View File

@ -1,6 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<div class="rows-container">
</div>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,104 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
// row-size is a magic number used for scaling. It will make things bigger
// or smaller but always in proportion with each other. Feel free to change
// this to reflect your personal needs.
$row-size: 1em;
$blue: #30A2BD;
$white: #ffffff;
$base-color: darken($blue, 20%);
$base-color-dark: darken($base-color, 10%);
$base-color-light: lighten($base-color, 10%);
$base-color-lighter: lighten($base-color, 5%);
$base-color-lightest: lighten($base-color, 45%);
$text-color: $white;
// ----------------------------------------------------------------------------
// Widget-project-completion styles
// ----------------------------------------------------------------------------
.widget.widget-progress-bars {
height: 100%;
width: 100%;
padding: 5px;
position:relative;
background-color: $base-color;
vertical-align: baseline;
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing:border-box;
}
.title {
color: $text-color;
margin-bottom: 5px;
}
.rows-container {
height: 85%;
width:100%;
color: $text-color;
font-size: $row-size;
text-align:center;
padding-bottom: 12px;
}
.row {
height:0%;
width:100%;
vertical-align: middle;
display:table;
transition-property: height;
transition-duration: 0.3s;
transition-timing-function: linear;
}
.row-content {
padding-left: 5px;
display:table-cell;
vertical-align: middle;
}
.project-name {
display:inline-block;
width:35%;
padding-right: $row-size;
text-align: left;
vertical-align: middle;
text-overflow: ellipsis;
overflow:hidden;
white-space: nowrap;
}
.outer-progress-bar {
display:inline-block;
width: 65%;
vertical-align: middle;
border: ($row-size / 3) solid $base-color-dark;
border-radius: 2 * $row-size;
background-color: $base-color-lighter;
.inner-progress-bar {
background-color: $base-color-dark;
border-radius: $row-size / 2;
color: $white;
white-space: pre;
}
}
.zebra-stripe {
background-color: $base-color-light;
}
.updated-at {
color: rgba(0, 0, 0, 0.6);
font-size: 0.8em;
}
}

View File

@ -1,446 +0,0 @@
# Rickshawgraph v0.1.0
class Dashing.Rickshawgraph extends Dashing.Widget
DIVISORS = [
{number: 100000000000000000000000, label: 'Y'},
{number: 100000000000000000000, label: 'Z'},
{number: 100000000000000000, label: 'E'},
{number: 1000000000000000, label: 'P'},
{number: 1000000000000, label: 'T'},
{number: 1000000000, label: 'G'},
{number: 1000000, label: 'M'},
{number: 1000, label: 'K'}
]
# Take a long number like "2356352" and turn it into "2.4M"
formatNumber = (number) ->
for divisior in DIVISORS
if number > divisior.number
number = "#{Math.round(number / (divisior.number/10))/10}#{divisior.label}"
break
return number
getRenderer: () -> return @get('renderer') or @get('graphtype') or 'area'
# Retrieve the `current` value of the graph.
@accessor 'current', ->
answer = null
# Return the value supplied if there is one.
if @get('displayedValue') != null and @get('displayedValue') != undefined
answer = @get('displayedValue')
if answer == null
# Compute a value to return based on the summaryMethod
series = @_parseData {points: @get('points'), series: @get('series')}
if !(series?.length > 0)
# No data in series
answer = ''
else
switch @get('summaryMethod')
when "sum"
answer = 0
answer += (point?.y or 0) for point in s.data for s in series
when "sumLast"
answer = 0
answer += s.data[s.data.length - 1].y or 0 for s in series
when "highest"
answer = 0
if @get('unstack') or (@getRenderer() is "line")
answer = Math.max(answer, (point?.y or 0)) for point in s.data for s in series
else
# Compute the sum of values at each point along the graph
for index in [0...series[0].data.length]
value = 0
for s in series
value += s.data[index]?.y or 0
answer = Math.max(answer, value)
when "none"
answer = ''
else
# Otherwise if there's only one series, pick the most recent value from the series.
if series.length == 1 and series[0].data?.length > 0
data = series[0].data
answer = data[data.length - 1].y
else
# Otherwise just return nothing.
answer = ''
answer = formatNumber answer
return answer
ready: ->
@assignedColors = @get('colors').split(':') if @get('colors')
@strokeColors = @get('strokeColors').split(':') if @get('strokeColors')
@graph = @_createGraph()
@graph.render()
clear: ->
# Remove the old graph/legend if there is one.
$node = $(@node)
$node.find('.rickshaw_graph').remove()
if @$legendDiv
@$legendDiv.remove()
@$legendDiv = null
# Handle new data from Dashing.
onData: (data) ->
series = @_parseData data
if @graph
# Remove the existing graph if the number of series has changed or any names have changed.
needClear = false
needClear |= (series.length != @graph.series.length)
if @get("legend") then for subseries, index in series
needClear |= @graph.series[index]?.name != series[index]?.name
if needClear then @graph = @_createGraph()
# Copy over the new graph data
for subseries, index in series
@graph.series[index] = subseries
@graph.render()
# Create a new Rickshaw graph.
_createGraph: ->
$node = $(@node)
$container = $node.parent()
@clear()
# Gross hacks. Let's fix this.
width = (Dashing.widget_base_dimensions[0] * $container.data("sizex")) + Dashing.widget_margins[0] * 2 * ($container.data("sizex") - 1)
height = (Dashing.widget_base_dimensions[1] * $container.data("sizey"))
if @get("legend")
# Shave 20px off the bottom of the graph for the legend
height -= 30
$graph = $("<div style='height: #{height}px;'></div>")
$node.append $graph
series = @_parseData {points: @get('points'), series: @get('series')}
graphOptions = {
element: $graph.get(0),
renderer: @getRenderer(),
width: width,
height: height,
series: series
}
if !!@get('stroke') then graphOptions.stroke = true
if @get('min') != null then graphOptions.max = @get('min')
if @get('max') != null then graphOptions.max = @get('max')
try
graph = new Rickshaw.Graph graphOptions
catch err
if err.toString() is "x and y properties of points should be numbers instead of number and object"
# This will happen with older versions of Rickshaw that don't support nulls in the data set.
nullsFound = false
for s in series
for point in s.data
if point.y is null
nullsFound = true
point.y = 0
if nullsFound
# Try to create the graph again now that we've patched up the data.
graph = new Rickshaw.Graph graphOptions
if !@rickshawVersionWarning
console.log "#{@get 'id'} - Nulls were found in your data, but Rickshaw didn't like" +
" them. Consider upgrading your rickshaw to 1.4.3 or higher."
@rickshawVersionWarning = true
else
# No nulls were found - this is some other problem, so just re-throw the exception.
throw err
graph.renderer.unstack = !!@get('unstack')
xAxisOptions = {
graph: graph
}
if Rickshaw.Fixtures.Time.Local
xAxisOptions.timeFixture = new Rickshaw.Fixtures.Time.Local()
# x_axis = new Rickshaw.Graph.Axis.Time xAxisOptions
y_axis = new Rickshaw.Graph.Axis.Y(graph: graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT)
if @get("legend")
# Add a legend
@$legendDiv = $("<div style='width: #{width}px;'></div>")
$node.append(@$legendDiv)
legend = new Rickshaw.Graph.Legend {
graph: graph
element: @$legendDiv.get(0)
}
return graph
# Parse a {series, points} object with new data from Dashing.
#
_parseData: (data) ->
series = []
# Figure out what kind of data we've been passed
if data.series
dataSeries = if isString(data.series) then JSON.parse data.series else data.series
for subseries, index in dataSeries
try
series.push @_parseSeries subseries
catch err
console.log "Error while parsing series: #{err}"
else if data.points
points = data.points
if isString(points) then points = JSON.parse points
if points[0]? and !points[0].x?
# Not already in Rickshaw format; assume graphite data
points = graphiteDataToRickshaw(points)
series.push {data: points}
if series.length is 0
# No data - create a dummy series to keep Rickshaw happy
series.push {data: [{x:0, y:0}]}
@_updateColors(series)
# Fix any missing data in the series.
if Rickshaw.Series.fill then Rickshaw.Series.fill(series, null)
return series
# Parse a series of data from an array passed to `_parseData()`.
# This accepts both Graphite and Rickshaw style data sets.
_parseSeries: (series) ->
if series?.datapoints?
# This is a Graphite series
answer = {
name: series.target
data: graphiteDataToRickshaw series.datapoints
color: series.color
stroke: series.stroke
}
else if series?.data?
# Rickshaw data. Need to clone, otherwise we could end up with multiple graphs sharing
# the same data, and Rickshaw really doesn't like that.
answer = {
name: series.name
data: series.data
color: series.color
stroke: series.stroke
}
else if !series
throw new Error("No data received for #{@get 'id'}")
else
throw new Error("Unknown data for #{@get 'id'}. series: #{series}")
answer.data.sort (a,b) -> a.x - b.x
return answer
# Update the color assignments for a series. This will assign colors to any data that
# doesn't have a color already.
_updateColors: (series) ->
# If no colors were provided, or of there aren't enough colors, then generate a set of
# colors to use.
if !@defaultColors or @defaultColors?.length != series.length
@defaultColors = computeDefaultColors @, @node, series
for subseries, index in series
# Preferentially pick supplied colors instead of defaults, but don't overwrite a color
# if one was supplied with the data.
subseries.color ?= @assignedColors?[index] or @defaultColors[index]
subseries.stroke ?= @strokeColors?[index] or "#000"
# Convert a collection of Graphite data points into data that Rickshaw will understand.
graphiteDataToRickshaw = (datapoints) ->
answer = []
for datapoint in datapoints
# Need to convert potential nulls from Graphite into a real number for Rickshaw.
answer.push {x: datapoint[1], y: (datapoint[0] or 0)}
answer
# Compute a pleasing set of default colors. This works by starting with the background color,
# and picking colors of intermediate luminance between the background and white (or the
# background and black, for light colored backgrounds.) We use the brightest color for the
# first series, because then multiple series will appear to blend in to the background.
computeDefaultColors = (self, node, series) ->
defaultColors = []
# Use a neutral color if we can't get the background-color for some reason.
backgroundColor = parseColor($(node).css('background-color')) or [50, 50, 50, 1.0]
hsl = rgbToHsl backgroundColor
alpha = if self.get('defaultAlpha')? then self.get('defaultAlpha') else 1
if self.get('colorScheme') in ['rainbow', 'near-rainbow']
saturation = (interpolate hsl[1], 1.0, 3)[1]
luminance = if (hsl[2] < 0.6) then 0.7 else 0.3
hueOffset = 0
if self.get('colorScheme') is 'rainbow'
# Note the first and last values in `hues` will both have the same hue as the background,
# hence the + 2.
hues = interpolate hsl[0], hsl[0] + 1, (series.length + 2)
hueOffset = 1
else
hues = interpolate hsl[0] - 0.25, hsl[0] + 0.25, series.length
for hue, index in hues
if hue > 1 then hues[index] -= 1
if hue < 0 then hues[index] += 1
for index in [0...series.length]
defaultColors[index] = rgbToColor hslToRgb([hues[index + hueOffset], saturation, luminance, alpha])
else
hue = if self.get('colorScheme') is "compliment" then hsl[0] + 0.5 else hsl[0]
if hsl[0] > 1 then hsl[0] -= 1
saturation = hsl[1]
saturationSource = if (saturation < 0.6) then 0.7 else 0.3
saturations = interpolate saturationSource, saturation, (series.length + 1)
luminance = hsl[2]
luminanceSource = if (luminance < 0.6) then 0.9 else 0.1
luminances = interpolate luminanceSource, luminance, (series.length + 1)
for index in [0...series.length]
defaultColors[index] = rgbToColor hslToRgb([hue, saturations[index], luminances[index], alpha])
return defaultColors
# Helper functions
# ================
isString = (obj) ->
return toString.call(obj) is "[object String]"
# Parse a `rgb(x,y,z)` or `rgba(x,y,z,a)` string.
parseRgbaColor = (colorString) ->
match = /^rgb\(\s*([\d]+)\s*,\s*([\d]+)\s*,\s*([\d]+)\s*\)/.exec(colorString)
if match
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), 1.0]
match = /^rgba\(\s*([\d]+)\s*,\s*([\d]+)\s*,\s*([\d]+)\s*,\s*([\d]+)\s*\)/.exec(colorString)
if match
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), parseInt(match[4])]
return null
# Parse a color string as RGBA
parseColor = (colorString) ->
answer = null
# Try to use the browser to parse the color for us.
div = document.createElement('div')
div.style.color = colorString
if div.style.color
answer = parseRgbaColor div.style.color
if !answer
match = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})/.exec(colorString)
if match then answer = [parseInt(match[1], 16), parseInt(match[2], 16), parseInt(match[3], 16), 1.0]
if !answer
match = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])/.exec(colorString)
if match then answer = [parseInt(match[1], 16) * 0x11, parseInt(match[2], 16) * 0x11, parseInt(match[3], 16) * 0x11, 1.0]
if !answer then answer = parseRgbaColor colorString
return answer
# Convert an RGB or RGBA color to a CSS color.
rgbToColor = (rgb) ->
if (!3 of rgb) or (rgb[3] == 1.0)
return "rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]})"
else
return "rgba(#{rgb[0]},#{rgb[1]},#{rgb[2]},#{rgb[3]})"
# Returns an array of size `steps`, where the first value is `source`, the last value is `dest`,
# and the intervening values are interpolated. If steps < 2, then returns `[dest]`.
#
interpolate = (source, dest, steps) ->
if steps < 2
answer =[dest]
else
stepSize = (dest - source) / (steps - 1)
answer = (num for num in [source..dest] by stepSize)
# Rounding errors can cause us to drop the last value
if answer.length < steps then answer.push dest
return answer
# Adapted from http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
#
# Converts an RGBA color value to HSLA. Conversion formula
# adapted from http://en.wikipedia.org/wiki/HSL_color_space.
# Assumes r, g, and b are contained in the set [0, 255] and
# a in [0, 1]. Returns h, s, l, a in the set [0, 1].
#
# Returns the HSLA representation as an array.
rgbToHsl = (rgba) ->
[r,g,b,a] = rgba
r /= 255
g /= 255
b /= 255
max = Math.max(r, g, b)
min = Math.min(r, g, b)
l = (max + min) / 2
if max == min
h = s = 0 # achromatic
else
d = max - min
s = if l > 0.5 then d / (2 - max - min) else d / (max + min)
switch max
when r then h = (g - b) / d + (g < b ? 6 : 0)
when g then h = (b - r) / d + 2
when b then h = (r - g) / d + 4
h /= 6;
return [h, s, l, a]
# Adapted from http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
#
# Converts an HSLA color value to RGBA. Conversion formula
# adapted from http://en.wikipedia.org/wiki/HSL_color_space.
# Assumes h, s, l, and a are contained in the set [0, 1] and
# returns r, g, and b in the set [0, 255] and a in [0, 1].
#
# Retunrs the RGBA representation as an array.
hslToRgb = (hsla) ->
[h,s,l,a] = hsla
if s is 0
r = g = b = l # achromatic
else
hue2rgb = (p, q, t) ->
if(t < 0) then t += 1
if(t > 1) then t -= 1
if(t < 1/6) then return p + (q - p) * 6 * t
if(t < 1/2) then return q
if(t < 2/3) then return p + (q - p) * (2/3 - t) * 6
return p
q = if l < 0.5 then l * (1 + s) else l + s - l * s
p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a]

View File

@ -1,5 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<h2 class="value" data-bind="current | prepend prefix"></h2>
<p class="more-info" data-bind="moreinfo"></p>

View File

@ -1,114 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #59615F;
$title-color: rgba(255, 255, 255, 1);
$moreinfo-color: rgba(255, 255, 255, 1);
$tick-color: rgba(0, 0, 0, 1);
// ----------------------------------------------------------------------------
// Widget-graph styles
// ----------------------------------------------------------------------------
.widget-rickshawgraph {
background-color: $background-color;
position: relative;
.rickshaw_graph {
position: absolute;
left: 0px;
top: 0px;
}
h2{
font-size: 16px;
white-space: pre;
}
svg {
position: absolute;
fill-opacity: 0.6;
left: 0px;
top: 0px;
}
.title, .value {
position: relative;
z-index: 99;
}
.title {
color: $title-color;
}
.more-info {
color: $moreinfo-color;
font-weight: 600;
font-size: 18px;
margin-top: 0;
margin-bottom: 20px;
z-index: 20;
}
.x_tick {
position: absolute;
bottom: 0;
.title {
font-size: 20px;
color: $tick-color;
opacity: 0.5;
padding-bottom: 3px;
}
}
.y_ticks {
font-size: 20px;
fill: $tick-color;
text {
opacity: 0.5;
}
}
.domain {
display: none;
}
.rickshaw_legend {
position: absolute;
left: 0px;
bottom: 0px;
white-space: nowrap;
overflow-x: hidden;
font-size: 15px;
height: 20px;
padding: 5px 0px;
overflow-y: hidden;
ul {
margin: 0;
padding: 0;
list-style-type: none;
text-align: center;
}
ul li {
display: inline;
}
.swatch {
display: inline-block;
width: 14px;
height: 14px;
margin-left: 5px;
}
.label {
display: inline-block;
margin-left: 5px;
font-size: 17px;
}
}
}

View File

@ -1 +0,0 @@
class Dashing.Text extends Dashing.Widget

View File

@ -1,7 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<h3 data-bind="text | raw"></h3>
<p class="more-info" data-bind="moreinfo | raw"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,32 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #ec663c;
$title-color: rgba(255, 255, 255, 0.7);
$moreinfo-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-text styles
// ----------------------------------------------------------------------------
.widget-text {
background-color: $background-color;
.title {
color: $title-color;
}
.more-info {
color: $moreinfo-color;
}
.updated-at {
color: rgba(255, 255, 255, 0.7);
}
&.large h3 {
font-size: 65px;
}
}

View File

@ -1,5 +0,0 @@
Direct port of the Usage Gauge Widget from Shopify's Dashie Widget Challenge to pydashie
All attribution to kamisama -
https://gist.github.com/kamisama/6587791

View File

@ -1,20 +0,0 @@
class Dashing.UsageGauge extends Dashing.Widget
@accessor 'value', Dashing.AnimatedValue
ready: ->
$(@node).find(".value").before("<div class='gauge'></div>")
$(@node).find(".gauge").append("<div class='gauge-meter'></div>")
@meter = $(@node).find(".gauge-meter");
onData: (data) ->
@meter.animate({height: Batman.mixin Batman.Filters.percentage(data.value, @get("max")) + "%"})
@accessor "total", ->
@get("max")
@accessor "suffix", ->
" %"
Batman.mixin Batman.Filters,
percentage: (n, total) ->
Math.round(n * 100 / total)

View File

@ -1,5 +0,0 @@
<h1 class="title" data-bind="title"></h1>
<h2 class="value" data-bind="value | percentage total | append suffix"></h2>
<p class="updated-at" data-bind="updatedAtMessage"></p>

View File

@ -1,56 +0,0 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #ec223f;
$title-color: rgba(255, 255, 255, 0.7);
$updated-at-color: rgba(255, 255, 255, 0.5);
$meter-background: darken($background-color, 15%);
// ----------------------------------------------------------------------------
// Widget-meter styles
// ----------------------------------------------------------------------------
.widget-usage-gauge {
background-color: $background-color;
.title {
color: $title-color;
}
.value {
font-weight: 600;
font-size: 30px
}
.gauge {
width: 100px;
height: 180px;
display: block;
margin: 0 auto 10px;
position: relative;
}
.gauge-meter, .gauge::after {
display: block;
background: url("/assets/images/usage-gauge-background.png") repeat-y center bottom;
}
.gauge::after {
content: "";
height: 100%;
opacity: 0.15;
}
.gauge-meter {
width: 100px;
position: absolute;
bottom: 0;
opacity: 0.6;
}
.updated-at {
color: $updated-at-color
}
}

View File

@ -1,10 +0,0 @@
Flask
CoffeeScript
pyyaml
python-novaclient
python-neutronclient
python-cinderclient
python-keystoneclient
paramiko
pynag

View File

@ -1,28 +0,0 @@
#! /usr/bin/env python
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
from pip.req import parse_requirements
install_reqs = parse_requirements('requirements.txt')
reqs = [str(ir.req) for ir in install_reqs]
setup(
name='Cloud-PyDashie',
version='0.2',
packages=['pydashie',],
include_package_data=True,
install_requires=reqs,
entry_points={
'console_scripts': ['pydashie = pydashie.main:run_sample_app']
},
license='MIT',
long_description=open('README.rst').read(),
)