Retire stackforge/cloud-pydashie
This commit is contained in:
parent
87bd1d7188
commit
66ab2867ff
22
.gitattributes
vendored
22
.gitattributes
vendored
@ -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
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.DS_Store
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg*
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=stackforge/cloud-pydashie.git
|
@ -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.
|
@ -1,4 +0,0 @@
|
||||
recursive-include pydashie/templates *
|
||||
recursive-include pydashie/assets *
|
||||
recursive-include pydashie/widgets *
|
||||
recursive-include pydashie/samplers *
|
124
README.rst
124
README.rst
@ -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.
|
||||
|
56
compile.py
56
compile.py
@ -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()
|
485
ez_setup.py
485
ez_setup.py
@ -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:])
|
BIN
mainscreen.png
BIN
mainscreen.png
Binary file not shown.
Before Width: | Height: | Size: 218 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 976 B |
@ -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);
|
@ -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
4
pydashie/assets/javascripts/d3.v2.min.js
vendored
4
pydashie/assets/javascripts/d3.v2.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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()
|
@ -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
5
pydashie/assets/javascripts/jquery.js
vendored
5
pydashie/assets/javascripts/jquery.js
vendored
File diff suppressed because one or more lines are too long
@ -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();
|
||||
};
|
||||
|
||||
});
|
@ -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);
|
2
pydashie/assets/javascripts/rickshaw.min.js
vendored
2
pydashie/assets/javascripts/rickshaw.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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; }
|
||||
|
303
pydashie/assets/stylesheets/font-awesome.css
vendored
303
pydashie/assets/stylesheets/font-awesome.css
vendored
@ -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"; }
|
@ -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;
|
||||
}*/
|
1
pydashie/assets/stylesheets/rickshaw.min.css
vendored
1
pydashie/assets/stylesheets/rickshaw.min.css
vendored
File diff suppressed because one or more lines are too long
@ -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'
|
@ -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)
|
224
pydashie/main.py
224
pydashie/main.py
@ -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()
|
@ -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
|
@ -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"
|
@ -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
|
@ -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
|
@ -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
|
@ -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}
|
@ -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>
|
@ -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>
|
@ -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
|
@ -1,2 +0,0 @@
|
||||
<h1 data-bind="date"></h1>
|
||||
<h2 data-bind="time"></h2>
|
@ -1,13 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sass declarations
|
||||
// ----------------------------------------------------------------------------
|
||||
$background-color: #666666;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Widget-clock styles
|
||||
// ----------------------------------------------------------------------------
|
||||
.widget-clock {
|
||||
|
||||
background-color: $background-color;
|
||||
|
||||
}
|
@ -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()
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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/
|
@ -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
|
@ -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>
|
@ -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; }
|
@ -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.
|
@ -1 +0,0 @@
|
||||
<iframe data-bind-src="url" frameborder=0></iframe>
|
@ -1,8 +0,0 @@
|
||||
.widget-iframe {
|
||||
padding: 3px 0px 0px 0px !important;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
@ -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.
|
@ -1 +0,0 @@
|
||||
<img data-bind-src="image | prepend '/assets'" data-bind-width="width"/>
|
@ -1,13 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sass declarations
|
||||
// ----------------------------------------------------------------------------
|
||||
$background-color: #FFFFFF;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Widget-image styles
|
||||
// ----------------------------------------------------------------------------
|
||||
.widget-image {
|
||||
|
||||
background-color: $background-color;
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
class Dashing.List extends Dashing.Widget
|
||||
ready: ->
|
||||
if @get('unordered')
|
||||
$(@node).find('ol').remove()
|
||||
else
|
||||
$(@node).find('ul').remove()
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
class Dashing.NagiosList extends Dashing.Widget
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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}")
|
@ -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>
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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)
|
@ -1,6 +0,0 @@
|
||||
<h1 class="title" data-bind="title"></h1>
|
||||
|
||||
<div class="rows-container">
|
||||
</div>
|
||||
|
||||
<p class="updated-at" data-bind="updatedAtMessage"></p>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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]
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1 +0,0 @@
|
||||
class Dashing.Text extends Dashing.Widget
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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)
|
@ -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>
|
@ -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
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
Flask
|
||||
CoffeeScript
|
||||
pyyaml
|
||||
python-novaclient
|
||||
python-neutronclient
|
||||
python-cinderclient
|
||||
python-keystoneclient
|
||||
paramiko
|
||||
pynag
|
||||
|
28
setup.py
28
setup.py
@ -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(),
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user