graphite-web-1.1.4/0000755000000000000000000000000013343335472014040 5ustar rootroot00000000000000graphite-web-1.1.4/bin/0000755000000000000000000000000013343335472014610 5ustar rootroot00000000000000graphite-web-1.1.4/bin/build-index.sh0000755000000000000000000000022413343334667017356 0ustar rootroot00000000000000#!/bin/bash export PYTHONPATH="/opt/graphite/webapp/:$PYTHONPATH" BINDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ${BINDIR}/build-index graphite-web-1.1.4/bin/build-index0000755000000000000000000000152613343334667016753 0ustar rootroot00000000000000#!/usr/bin/env python from graphite import settings from optparse import OptionParser import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "graphite.settings") django.setup() from graphite.storage import write_index if __name__ == "__main__": description = "A tool to generate the search index file used by"\ " graphite-web." prog = "build-index" version = "1.0" epilog = "Defaults are read from graphite.settings, it is unlikely"\ " that you would want to run this program with anything except the"\ " defaults." parser = OptionParser(description=description, prog=prog, version=version, epilog=epilog) parser.add_option("-i", "--index", default=settings.INDEX_FILE, help="default: %default") (options, args) = parser.parse_args() write_index(options.index) graphite-web-1.1.4/bin/run-graphite-devel-server.py0000755000000000000000000000251013343334667022176 0ustar rootroot00000000000000#!/usr/bin/env python import sys, os from optparse import OptionParser from django.core import management option_parser = OptionParser(usage=''' %prog [options] GRAPHITE_ROOT ''') option_parser.add_option('--port', default=8080, action='store', type=int, help='Port to listen on') option_parser.add_option('--interface', default='0.0.0.0', action='store', help='Interface to listen on') option_parser.add_option('--libs', default=None, help='Path to the directory containing the graphite python package') option_parser.add_option('--noreload', action='store_true', help='Disable monitoring for changes') (options, args) = option_parser.parse_args() if not args: option_parser.print_usage() sys.exit(1) graphite_root = args[0] python_path = os.path.join(graphite_root, 'webapp') if options.libs: libdir = os.path.expanduser(options.libs) print('Adding %s to your PYTHONPATH' % libdir) os.environ['PYTHONPATH'] = libdir + ':' + os.environ.get('PYTHONPATH','') print("Running Graphite from %s under django development server\n" % graphite_root) command = [ 'django-admin.py', 'runserver', '--pythonpath', python_path, '--settings', 'graphite.settings', '%s:%d' % (options.interface, options.port) ] if options.noreload: command.append('--noreload') print(' '.join(command)) management.execute_from_command_line(command) graphite-web-1.1.4/conf/0000755000000000000000000000000013343335472014765 5ustar rootroot00000000000000graphite-web-1.1.4/conf/dashboard.conf.example0000644000000000000000000000354313343334667021227 0ustar rootroot00000000000000# This configuration file controls the behavior of the Dashboard UI, available # at http://my-graphite-server/dashboard/. # # This file must contain a [ui] section that defines values for all of the # following settings. [ui] default_graph_width = 400 default_graph_height = 250 automatic_variants = true refresh_interval = 60 autocomplete_delay = 375 merge_hover_delay = 750 # You can set this 'default', 'white', or a custom theme name. # To create a custom theme, copy the dashboard-default.css file # to dashboard-myThemeName.css in the content/css directory and # modify it to your liking. theme = default [keyboard-shortcuts] toggle_toolbar = ctrl-z toggle_metrics_panel = ctrl-space erase_all_graphs = alt-x save_dashboard = alt-s completer_add_metrics = alt-enter completer_del_metrics = alt-backspace give_completer_focus = shift-space # These settings apply to the UI as a whole, all other sections in this file # pertain only to specific metric types. # # The dashboard presents only metrics that fall into specified naming schemes # defined in this file. This creates a simpler, more targeted view of the # data. The general form for defining a naming scheme is as follows: # #[Metric Type] #scheme = basis.path... #field1.label = Foo #field2.label = Bar # # # Where each will be displayed as a dropdown box # in the UI and the remaining portion of the namespace # shown in the Metric Selector panel. The .label options set the labels # displayed for each dropdown. # # For example: # #[Sales] #scheme = sales... #channel.label = Channel #type.label = Product Type #brand.label = Brand # # This defines a 'Sales' metric type that uses 3 dropdowns in the Context Selector # (the upper-left panel) while any deeper metrics (per-product counts or revenue, etc) # will be available in the Metric Selector (upper-right panel). graphite-web-1.1.4/conf/graphite.wsgi.example0000755000000000000000000000013213343334667021121 0ustar rootroot00000000000000import sys sys.path.append('/opt/graphite/webapp') from graphite.wsgi import application graphite-web-1.1.4/conf/graphTemplates.conf.example0000644000000000000000000000461513343334667022261 0ustar rootroot00000000000000[default] background = white foreground = black minorLine = grey majorLine = rose lineColors = blue,green,red,purple,brown,yellow,aqua,grey,magenta,pink,gold,rose fontName = Sans fontSize = 10 fontBold = False fontItalic = False [solarized-light] background = #fdf6e3 foreground = #657b83 majorLine = #073642 minorLine = #586e75 lineColors = 268bd2aa,859900aa,dc322faa,d33682aa,db4b16aa,b58900aa,2aa198aa,6c71c4aa fontName = Sans fontSize = 10 fontBold = False fontItalic = False [solarized-dark] background = #002b36 foreground = #839496 majorLine = #fdf6e3 minorLine = #eee8d5 lineColors = 268bd2aa,859900aa,dc322faa,d33682aa,db4b16aa,b58900aa,2aa198aa,6c71c4aa fontName = Sans fontSize = 10 fontBold = False fontItalic = False [classic] background = black foreground = white majorLine = white minorLine = grey lineColors = blue,green,red,purple,brown,yellow,aqua,grey,magenta,pink,gold,rose fontName = Sans fontSize = 10 fontBold = False fontItalic = False [noc] background = black foreground = white majorLine = white minorLine = grey lineColors = blue,green,red,yellow,purple,brown,aqua,grey,magenta,pink,gold,rose fontName = Sans fontSize = 10 fontBold = False fontItalic = False [summary] background = black lineColors = #6666ff, #66ff66, #ff6666 [alphas] background = white foreground = black majorLine = grey minorLine = rose lineColors = 00ff00aa,ff000077,00337799 [grafana] background = white foreground = black minorLine = grey majorLine = rose lineColors = #7eb26d,#eab839,#6ed0e0,#ef843c,#e24d42,#1f78c1,#ba43a9,#705da0,#508642,#cca300,#447ebc fontName = Sans fontSize = 10 fontBold = False fontItalic = False [ocean1] lineColors = f7fcf0,e0f3db,ccebc5,a8ddb5,7bccc4,4eb3d3,2b8cbe,0868ac,084081 [ocean2] lineColors = 084081,0868ac,2b8cbe,4eb3d3,7bccc4,a8ddb5,ccebc5,e0f3db,f7fcf0 [forest1] lineColors = f7fcf5,e5f5e0,c7e9c0,a1d99b,74c476,41ab5d,238b45,005a32 [forest2] lineColors = 005a32,238b45,41ab5d,74c476,a1d99b,c7e9c0,e5f5e0,f7fcf5 [sunset1] lineColors = fff5eb,fee6ce,fdd0a2,fdae6b,fd8d3c,f16913,d94801,8c2d04 [sunset2] lineColors = 8c2d04,d94801,f16913,fd8d3c,fdae6b,fdd0a2,fee6ce,fff5eb [moonlight1] lineColors = fcfbfd,efedf5,dadaeb,bcbddc,9e9ac8,807dba,6a51a3,4a1486 [moonlight2] lineColors = 4a1486,6a51a3,807dba,9e9ac8,bcbddc,dadaeb,efedf5,fcfbfd [lava1] lineColors = fff5f0,fee0d2,fcbba1,fc9272,fb6a4a,ef3b2c,cb181d,99000d [lava2] lineColors = 99000d,cb181d,ef3b2c,fb6a4a,fc9272,fcbba1,fee0d2,fff5f0 graphite-web-1.1.4/PKG-INFO0000644000000000000000000000165713343335472015146 0ustar rootroot00000000000000Metadata-Version: 1.1 Name: graphite-web Version: 1.1.4 Summary: Enterprise scalable realtime graphing Home-page: http://graphiteapp.org/ Author: Chris Davis Author-email: chrismd@gmail.com License: Apache Software License 2.0 Description: UNKNOWN Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy graphite-web-1.1.4/setup.py0000644000000000000000000001074413343334714015556 0ustar rootroot00000000000000#!/usr/bin/env python from __future__ import with_statement import os try: from ConfigParser import ConfigParser, DuplicateSectionError # Python 2 except ImportError: from configparser import ConfigParser, DuplicateSectionError # Python 3 from glob import glob from collections import defaultdict # io.StringIO is strictly unicode only. Python 2 StringIO.StringIO accepts # bytes, so we'll conveniently ignore decoding and reencoding the file there. try: from StringIO import StringIO # Python 2 except ImportError: from io import StringIO # Python 3 # Graphite historically has an install prefix set in setup.cfg. Being in a # configuration file, it's not easy to override it or unset it (for installing # graphite in a virtualenv for instance). # The prefix is now set by ``setup.py`` and *unset* if an environment variable # named ``GRAPHITE_NO_PREFIX`` is present. # While ``setup.cfg`` doesn't contain the prefix anymore, the *unset* step is # required for installations from a source tarball because running # ``python setup.py sdist`` will re-add the prefix to the tarball's # ``setup.cfg``. with open('setup.cfg', 'r') as f: orig_setup_cfg = f.read() cf = ConfigParser() cf.readfp(StringIO(orig_setup_cfg), 'setup.cfg') if os.environ.get('GRAPHITE_NO_PREFIX') or os.environ.get('READTHEDOCS'): cf.remove_section('install') else: try: cf.add_section('install') except DuplicateSectionError: pass if not cf.has_option('install', 'prefix'): cf.set('install', 'prefix', '/opt/graphite') if not cf.has_option('install', 'install-lib'): cf.set('install', 'install-lib', '%(prefix)s/webapp') with open('setup.cfg', 'w') as f: cf.write(f) if os.environ.get('USE_SETUPTOOLS'): from setuptools import setup setup_kwargs = dict(zip_safe=0) else: from distutils.core import setup setup_kwargs = dict() storage_dirs = [] for subdir in ('whisper/dummy.txt', 'ceres/dummy.txt', 'rrd/dummy.txt', 'log/dummy.txt', 'log/webapp/dummy.txt'): storage_dirs.append( ('storage/%s' % subdir, []) ) webapp_content = defaultdict(list) for root, dirs, files in os.walk('webapp/content'): for filename in files: filepath = os.path.join(root, filename) webapp_content[root].append(filepath) conf_files = [ ('conf', glob('conf/*.example')) ] examples = [ ('examples', glob('examples/example-*')) ] try: setup( name='graphite-web', version='1.1.4', url='http://graphiteapp.org/', author='Chris Davis', author_email='chrismd@gmail.com', license='Apache Software License 2.0', description='Enterprise scalable realtime graphing', package_dir={'' : 'webapp'}, packages=[ 'graphite', 'graphite.account', 'graphite.account.migrations', 'graphite.browser', 'graphite.composer', 'graphite.dashboard', 'graphite.dashboard.migrations', 'graphite.events', 'graphite.events.migrations', 'graphite.finders', 'graphite.functions', 'graphite.functions.custom', 'graphite.metrics', 'graphite.readers', 'graphite.render', 'graphite.tags', 'graphite.tags.migrations', 'graphite.url_shortener', 'graphite.url_shortener.migrations', 'graphite.version', 'graphite.whitelist', 'graphite.worker_pool', ], package_data={'graphite' : ['templates/*', 'local_settings.py.example']}, scripts=glob('bin/*'), data_files=list(webapp_content.items()) + storage_dirs + conf_files + examples, install_requires=['Django>=1.8,<2.1', 'django-tagging==0.4.3', 'pytz', 'pyparsing', 'cairocffi', 'urllib3', 'scandir', 'six'], classifiers=[ 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], **setup_kwargs ) finally: with open('setup.cfg', 'w') as f: f.write(orig_setup_cfg) graphite-web-1.1.4/LICENSE0000644000000000000000000002616313343334667015062 0ustar rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2008-2012 Chris Davis; 2011-2016 The Graphite Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. graphite-web-1.1.4/webapp/0000755000000000000000000000000013343335472015316 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/0000755000000000000000000000000013343335472017121 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/user_util.py0000644000000000000000000000361713343334667021522 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django import VERSION as DJANGO_VERSION from django.contrib.auth.models import User from graphite.account.models import Profile from graphite.logger import log def isAuthenticated(user): # is_authenticated() is changed to a boolean since 1.10, 2.0 removes the # backwards compatibilty if DJANGO_VERSION >= (1, 10): return user.is_authenticated else: return user.is_authenticated() def getProfile(request, allowDefault=True): if isAuthenticated(request.user): return Profile.objects.get_or_create(user=request.user)[0] elif allowDefault: return default_profile() def getProfileByUsername(username): try: return Profile.objects.get(user__username=username) except Profile.DoesNotExist: return None def default_profile(): # '!' is an unusable password. Since the default user never authenticates # this avoids creating a default (expensive!) password hash at every # default_profile() call. user, created = User.objects.get_or_create( username='default', defaults={'email': 'default@localhost.localdomain', 'password': '!'}) if created: log.info("Default user didn't exist, created it") profile, created = Profile.objects.get_or_create(user=user) if created: log.info("Default profile didn't exist, created it") return profile graphite-web-1.1.4/webapp/graphite/tags/0000755000000000000000000000000013343335472020057 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/tags/__init__.py0000644000000000000000000000000013343334667022163 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/tags/base.py0000644000000000000000000002132013343334667021346 0ustar rootroot00000000000000"""Base tag database""" import abc import bisect import re import time from graphite.tags.utils import TaggedSeries class BaseTagDB(object): __metaclass__ = abc.ABCMeta def __init__(self, settings, *args, **kwargs): """Initialize the tag db.""" self.settings = settings self.cache = kwargs.get('cache') self.log = kwargs.get('log') def find_series(self, tags, requestContext=None): """ Find series by tag, accepts a list of tag specifiers and returns a list of matching paths. Tags specifiers are strings, and may have the following formats: .. code-block:: none tag=spec tag value exactly matches spec tag!=spec tag value does not exactly match spec tag=~value tag value matches the regular expression spec tag!=~spec tag value does not match the regular expression spec Any tag spec that matches an empty value is considered to match series that don't have that tag. At least one tag spec must require a non-empty value. Regular expression conditions are treated as being anchored at the start of the value. Matching paths are returned as a list of strings. """ start_time = time.time() log_msg = 'completed in' try: cacheKey = self.find_series_cachekey(tags, requestContext=requestContext) result = self.cache.get(cacheKey) if self.cache else None if result is not None: log_msg = 'completed (cached) in' else: result = self._find_series(tags, requestContext) if self.cache: self.cache.set(cacheKey, result, self.settings.TAGDB_CACHE_DURATION) except Exception: log_msg = 'failed in' raise finally: self.log_info( 'find_series', '{msg} {sec:.6}s'.format( msg=log_msg, sec=time.time() - start_time, ) ) return result def find_series_cachekey(self, tags, requestContext=None): return 'TagDB.find_series:' + ':'.join(sorted(tags)) @abc.abstractmethod def _find_series(self, tags, requestContext=None): """ Internal function called by find_series, follows the same semantics allowing base class to implement caching """ @abc.abstractmethod def get_series(self, path, requestContext=None): """ Get series by path, accepts a path string and returns a TaggedSeries object describing the series. If the path is not found in the TagDB, returns None. """ @abc.abstractmethod def list_tags(self, tagFilter=None, limit=None, requestContext=None): """ List defined tags, returns a list of dictionaries describing the tags stored in the TagDB. Each tag dict contains the key "tag" which holds the name of the tag. Additional keys may be returned. .. code-block:: none [ { 'tag': 'tag1', }, ] Accepts an optional tagFilter parameter which is a regular expression used to filter the list of returned tags. """ @abc.abstractmethod def get_tag(self, tag, valueFilter=None, limit=None, requestContext=None): """ Get details of a particular tag, accepts a tag name and returns a dict describing the tag. The dict contains the key "tag" which holds the name of the tag. It also includes a "values" key, which holds a list of the values for each tag. See list_values() for the structure of each value. .. code-block:: none { 'tag': 'tag1', 'values': [ { 'value': 'value1', 'count': 1, } ], } Accepts an optional valueFilter parameter which is a regular expression used to filter the list of returned values. """ @abc.abstractmethod def list_values(self, tag, valueFilter=None, limit=None, requestContext=None): """ List values for a particular tag, returns a list of dictionaries describing the values stored in the TagDB. Each value dict contains the key "value" which holds the value, and the key "count" which is the number of series that have that value. Additional keys may be returned. .. code-block:: none [ { 'value': 'value1', 'count': 1, }, ] Accepts an optional valueFilter parameter which is a regular expression used to filter the list of returned values. """ @abc.abstractmethod def tag_series(self, series, requestContext=None): """ Enter series into database. Accepts a series string, upserts into the TagDB and returns the canonicalized series name. """ def tag_multi_series(self, seriesList, requestContext=None): """ Enter series into database. Accepts a list of series strings, upserts into the TagDB and returns a list of canonicalized series names. """ return [self.tag_series(series, requestContext) for series in seriesList] @abc.abstractmethod def del_series(self, series, requestContext=None): """ Remove series from database. Accepts a series string and returns True """ def del_multi_series(self, seriesList, requestContext=None): """ Remove series from database. Accepts a list of series strings, removes them from the TagDB and returns True """ for series in seriesList: self.del_series(series, requestContext) return True def auto_complete_tags(self, exprs, tagPrefix=None, limit=None, requestContext=None): """ Return auto-complete suggestions for tags based on the matches for the specified expressions, optionally filtered by tag prefix """ if limit is None: limit = self.settings.TAGDB_AUTOCOMPLETE_LIMIT else: limit = int(limit) if not exprs: return [ tagInfo['tag'] for tagInfo in self.list_tags( tagFilter='^(' + re.escape(tagPrefix) + ')' if tagPrefix else None, limit=limit, requestContext=requestContext, ) ] result = [] searchedTags = set([self.parse_tagspec(expr)[0] for expr in exprs]) for path in self.find_series(exprs, requestContext=requestContext): tags = self.parse(path).tags for tag in tags: if tag in searchedTags: continue if tagPrefix and not tag.startswith(tagPrefix): continue if tag in result: continue if len(result) == 0 or tag >= result[-1]: if len(result) >= limit: continue result.append(tag) else: bisect.insort_left(result, tag) if len(result) > limit: del result[-1] return result def auto_complete_values(self, exprs, tag, valuePrefix=None, limit=None, requestContext=None): """ Return auto-complete suggestions for tags and values based on the matches for the specified expressions, optionally filtered by tag and/or value prefix """ if limit is None: limit = self.settings.TAGDB_AUTOCOMPLETE_LIMIT else: limit = int(limit) if not exprs: return [ v['value'] for v in self.list_values( tag, valueFilter='^(' + re.escape(valuePrefix) + ')' if valuePrefix else None, limit=limit, requestContext=requestContext, ) ] result = [] for path in self.find_series(exprs + [tag + '!='], requestContext=requestContext): tags = self.parse(path).tags if tag not in tags: continue value = tags[tag] if valuePrefix and not value.startswith(valuePrefix): continue if value in result: continue if len(result) == 0 or value >= result[-1]: if len(result) >= limit: continue result.append(value) else: bisect.insort_left(result, value) if len(result) > limit: del result[-1] return result def log_info(self, func, msg): if self.log: self.log.info('%s.%s.%s :: %s' % (self.__module__, self.__class__.__name__, func, msg)) @staticmethod def parse(path): return TaggedSeries.parse(path) @staticmethod def parse_tagspec(tagspec): m = re.match('^([^;!=]+)(!?=~?)([^;]*)$', tagspec) if m is None: raise ValueError("Invalid tagspec %s" % tagspec) tag = m.group(1) operator = m.group(2) spec = m.group(3) return (tag, operator, spec) class DummyTagDB(BaseTagDB): def _find_series(self, tags, requestContext=None): return [] def get_series(self, path, requestContext=None): return None def list_tags(self, tagFilter=None, limit=None, requestContext=None): return [] def get_tag(self, tag, valueFilter=None, limit=None, requestContext=None): return None def list_values(self, tag, valueFilter=None, limit=None, requestContext=None): return [] def tag_series(self, series, requestContext=None): raise NotImplementedError('Tagging not implemented with DummyTagDB') def del_series(self, series, requestContext=None): return True graphite-web-1.1.4/webapp/graphite/tags/migrations/0000755000000000000000000000000013343335472022233 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/tags/migrations/__init__.py0000644000000000000000000000000013343334667024337 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/tags/migrations/0001_initial.py0000644000000000000000000000406313343334667024706 0ustar rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9.11 on 2017-08-31 17:38 from __future__ import unicode_literals from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Series', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('hash', models.CharField(max_length=64, unique=True)), ('path', models.TextField()), ], ), migrations.CreateModel( name='SeriesTag', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('series', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tags.Series')), ], ), migrations.CreateModel( name='Tag', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('tag', models.CharField(max_length=191, unique=True)), ], ), migrations.CreateModel( name='TagValue', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('value', models.CharField(max_length=191, unique=True)), ], ), migrations.AddField( model_name='seriestag', name='tag', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tags.Tag'), ), migrations.AddField( model_name='seriestag', name='value', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tags.TagValue'), ), migrations.AlterUniqueTogether( name='seriestag', unique_together=set([('series', 'tag')]), ), ] graphite-web-1.1.4/webapp/graphite/tags/models.py0000644000000000000000000000146513343334667021727 0ustar rootroot00000000000000from django.db import models class Series(models.Model): hash = models.CharField(max_length=64, unique=True) path = models.TextField() __str__ = lambda self: "Series [%s]" % self.path class Tag(models.Model): tag = models.CharField(max_length=191, unique=True) __str__ = lambda self: "Tag [%s]" % self.tag class TagValue(models.Model): value = models.CharField(max_length=191, unique=True) __str__ = lambda self: "TagValue [%s]" % self.value class SeriesTag(models.Model): series = models.ForeignKey(Series, on_delete=models.CASCADE) tag = models.ForeignKey(Tag, on_delete=models.CASCADE) value = models.ForeignKey(TagValue, on_delete=models.CASCADE) class Meta: unique_together = ("series", "tag") __str__ = lambda self: "SeriesTag [%s %s %s]" % (self.series, self.tag, self.value) graphite-web-1.1.4/webapp/graphite/tags/http.py0000644000000000000000000001217013343334667021416 0ustar rootroot00000000000000from __future__ import absolute_import from binascii import b2a_base64 import sys from graphite.http_pool import http from graphite.util import json from graphite.tags.base import BaseTagDB class HttpTagDB(BaseTagDB): """ Stores tag information using an external http service that implements the graphite tags API. """ def __init__(self, settings, *args, **kwargs): super(HttpTagDB, self).__init__(settings, *args, **kwargs) self.base_url = settings.TAGDB_HTTP_URL self.username = settings.TAGDB_HTTP_USER self.password = settings.TAGDB_HTTP_PASSWORD def request(self, method, url, fields, requestContext=None): headers = requestContext.get('forwardHeaders') if requestContext else {} if 'Authorization' not in headers and self.username and self.password: user_pw = '%s:%s' % (self.username, self.password) if sys.version_info[0] >= 3: user_pw_b64 = b2a_base64(user_pw.encode('utf-8')).decode('ascii') else: user_pw_b64 = user_pw.encode('base64') headers['Authorization'] = 'Basic ' + user_pw_b64 req_fields = [] for (field, value) in fields.items(): if value is None: continue if isinstance(value, list) or isinstance(value, tuple): req_fields.extend([(field, v) for v in value if v is not None]) else: req_fields.append((field, value)) result = http.request( method, self.base_url + url, fields=req_fields, headers=headers, timeout=self.settings.FIND_TIMEOUT, ) if result.status == 400: raise ValueError(json.loads(result.data.decode('utf-8')).get('error')) if result.status != 200: raise Exception('HTTP Error from remote tagdb: %s %s' % (result.status, result.data)) return json.loads(result.data.decode('utf-8')) def find_series_cachekey(self, tags, requestContext=None): headers = [ header + '=' + value for (header, value) in (requestContext.get('forwardHeaders', {}) if requestContext else {}).items() ] return 'TagDB.find_series:' + ':'.join(sorted(tags)) + ':' + ':'.join(sorted(headers)) def _find_series(self, tags, requestContext=None): return self.request( 'POST', '/tags/findSeries', {'expr': tags}, requestContext=requestContext, ) def get_series(self, path, requestContext=None): parsed = self.parse(path) seriesList = self.find_series( [('%s=%s' % (tag, parsed.tags[tag])) for tag in parsed.tags], requestContext=requestContext, ) if parsed.path in seriesList: return parsed def list_tags(self, tagFilter=None, limit=None, requestContext=None): return self.request('GET', '/tags', {'filter': tagFilter, 'limit': limit}, requestContext) def get_tag(self, tag, valueFilter=None, limit=None, requestContext=None): return self.request('GET', '/tags/' + tag, {'filter': valueFilter, 'limit': limit}, requestContext) def list_values(self, tag, valueFilter=None, limit=None, requestContext=None): tagInfo = self.get_tag(tag, valueFilter=valueFilter, limit=limit, requestContext=requestContext) if not tagInfo: return [] return tagInfo['values'] def tag_series(self, series, requestContext=None): return self.request('POST', '/tags/tagSeries', {'path': series}, requestContext) def tag_multi_series(self, seriesList, requestContext=None): return self.request('POST', '/tags/tagMultiSeries', {'path': seriesList}, requestContext) def del_series(self, series, requestContext=None): return self.request('POST', '/tags/delSeries', {'path': series}, requestContext) def del_multi_series(self, seriesList, requestContext=None): return self.request('POST', '/tags/delSeries', {'path': seriesList}, requestContext) def auto_complete_tags(self, exprs, tagPrefix=None, limit=None, requestContext=None): """ Return auto-complete suggestions for tags based on the matches for the specified expressions, optionally filtered by tag prefix """ if not self.settings.TAGDB_HTTP_AUTOCOMPLETE: return super(HttpTagDB, self).auto_complete_tags( exprs, tagPrefix=tagPrefix, limit=limit, requestContext=requestContext) if limit is None: limit = self.settings.TAGDB_AUTOCOMPLETE_LIMIT fields = { 'tagPrefix': tagPrefix or '', 'limit': str(limit), 'expr': exprs, } return self.request('POST', '/tags/autoComplete/tags', fields, requestContext) def auto_complete_values(self, exprs, tag, valuePrefix=None, limit=None, requestContext=None): """ Return auto-complete suggestions for tags and values based on the matches for the specified expressions, optionally filtered by tag and/or value prefix """ if not self.settings.TAGDB_HTTP_AUTOCOMPLETE: return super(HttpTagDB, self).auto_complete_values( exprs, tag, valuePrefix=valuePrefix, limit=limit, requestContext=requestContext) if limit is None: limit = self.settings.TAGDB_AUTOCOMPLETE_LIMIT fields = { 'tag': tag or '', 'valuePrefix': valuePrefix or '', 'limit': str(limit), 'expr': exprs, } return self.request('POST', '/tags/autoComplete/values', fields, requestContext) graphite-web-1.1.4/webapp/graphite/tags/localdatabase.py0000644000000000000000000002334213343334667023221 0ustar rootroot00000000000000import re from django.db import connection from hashlib import sha256 from graphite.tags.base import BaseTagDB, TaggedSeries class LocalDatabaseTagDB(BaseTagDB): def find_series_query(self, tags): # sql will select series that match all tag expressions that don't match empty tags sql = 'SELECT s.path' sql += ' FROM tags_series AS s' params = [] where = [] whereparams = [] all_match_empty = True # expressions that do match empty tags will be used to filter the result filters = [] i = 0 for tagspec in tags: (tag, operator, spec) = self.parse_tagspec(tagspec) i += 1 s = str(i) if operator == '=': matches_empty = spec == '' if not matches_empty: where.append('v' + s + '.value=%s') whereparams.append(spec) elif operator == '=~': # make sure regex is anchored if not spec.startswith('^'): spec = '^(' + spec + ')' matches_empty = bool(re.match(spec, '')) if not matches_empty: where.append('v' + s + '.value ' + self._regexp_operator(connection) + ' %s') whereparams.append(spec) elif operator == '!=': matches_empty = spec != '' if not matches_empty: where.append('v' + s + '.value<>%s') whereparams.append(spec) elif operator == '!=~': # make sure regex is anchored if not spec.startswith('^'): spec = '^(' + spec + ')' matches_empty = not re.match(spec, '') if not matches_empty: where.append('v' + s + '.value ' + self._regexp_not_operator(connection) + ' %s') whereparams.append(spec) else: raise ValueError("Invalid operator %s" % operator) if matches_empty: filters.append((tag, operator, spec)) else: sql += ' JOIN tags_tag AS t' + s + ' ON t' + s + '.tag=%s' params.append(tag) sql += ' JOIN tags_seriestag AS st' + s + ' ON st' + s + '.series_id=s.id AND st' + s + '.tag_id=t' + s + '.id' sql += ' JOIN tags_tagvalue AS v' + s + ' ON v' + s + '.id=st' + s + '.value_id' all_match_empty = all_match_empty and matches_empty if all_match_empty: raise ValueError("At least one tagspec must not match the empty string") if where: sql += ' WHERE ' + ' AND '.join(where) params.extend(whereparams) sql += ' ORDER BY s.path' return sql, params, filters def _find_series(self, tags, requestContext=None): sql, params, filters = self.find_series_query(tags) def matches_filters(path): if not filters: return True parsed = self.parse(path) for (tag, operator, spec) in filters: value = parsed.tags.get(tag, '') if ( (operator == '=' and value != spec) or (operator == '=~' and re.match(spec, value) is None) or (operator == '!=' and value == spec) or (operator == '!=~' and re.match(spec, value) is not None) ): return False return True with connection.cursor() as cursor: cursor.execute(sql, params) return [row[0] for row in cursor if matches_filters(row[0])] def get_series(self, path, requestContext=None): with connection.cursor() as cursor: sql = 'SELECT s.id, t.tag, v.value' sql += ' FROM tags_series AS s' sql += ' JOIN tags_seriestag AS st ON st.series_id=s.id' sql += ' JOIN tags_tag AS t ON t.id=st.tag_id' sql += ' JOIN tags_tagvalue AS v ON v.id=st.value_id' sql += ' WHERE s.path=%s' params = [path] cursor.execute(sql, params) series_id = None tags = {tag: value for (series_id, tag, value) in cursor} if not tags: return None return TaggedSeries(tags['name'], tags, series_id=series_id) def list_tags(self, tagFilter=None, limit=None, requestContext=None): with connection.cursor() as cursor: sql = 'SELECT t.id, t.tag' sql += ' FROM tags_tag AS t' params = [] if tagFilter: # make sure regex is anchored if not tagFilter.startswith('^'): tagFilter = '^(' + tagFilter + ')' sql += ' WHERE t.tag ' + self._regexp_operator(connection) + ' %s' params.append(tagFilter) sql += ' ORDER BY t.tag' if limit: sql += ' LIMIT %s' params.append(int(limit)) cursor.execute(sql, params) return [{'id': tag_id, 'tag': tag} for (tag_id, tag) in cursor] def get_tag(self, tag, valueFilter=None, limit=None, requestContext=None): with connection.cursor() as cursor: sql = 'SELECT t.id, t.tag' sql += ' FROM tags_tag AS t' sql += ' WHERE t.tag=%s' params = [tag] cursor.execute(sql, params) row = cursor.fetchone() if not row: return None (tag_id, tag) = row return { 'id': tag_id, 'tag': tag, 'values': self.list_values( tag, valueFilter=valueFilter, limit=limit, requestContext=requestContext ), } def list_values(self, tag, valueFilter=None, limit=None, requestContext=None): with connection.cursor() as cursor: sql = 'SELECT v.id, v.value, COUNT(st.id)' sql += ' FROM tags_tagvalue AS v' sql += ' JOIN tags_seriestag AS st ON st.value_id=v.id' sql += ' JOIN tags_tag AS t ON t.id=st.tag_id' sql += ' WHERE t.tag=%s' params = [tag] if valueFilter: # make sure regex is anchored if not valueFilter.startswith('^'): valueFilter = '^(' + valueFilter + ')' sql += ' AND v.value ' + self._regexp_operator(connection) + ' %s' params.append(valueFilter) sql += ' GROUP BY v.id, v.value' sql += ' ORDER BY v.value' if limit: sql += ' LIMIT %s' params.append(int(limit)) cursor.execute(sql, params) return [{'id': value_id, 'value': value, 'count': count} for (value_id, value, count) in cursor] @staticmethod def _insert_ignore(table, cols, data): sql = table + ' (' + ','.join(cols) + ') VALUES ' + ', '.join(['(' + ', '.join(['%s'] * len(cols)) + ')'] * len(data)) params = [] for row in data: params.extend(row) if connection.vendor == 'mysql': sql = 'INSERT IGNORE INTO ' + sql elif connection.vendor == 'sqlite': sql = 'INSERT OR IGNORE INTO ' + sql elif connection.vendor == 'postgresql': sql = 'INSERT INTO ' + sql + ' ON CONFLICT DO NOTHING' # nosec else: raise Exception('Unsupported database vendor ' + connection.vendor) with connection.cursor() as cursor: cursor.execute(sql, params) @staticmethod def _regexp_operator(connection): if connection.vendor == 'mysql': return 'REGEXP' if connection.vendor == 'sqlite': # django provides an implementation of REGEXP for sqlite return 'REGEXP' if connection.vendor == 'postgresql': return '~*' raise Exception('Database vendor ' + connection.vendor + ' does not support regular expressions') @staticmethod def _regexp_not_operator(connection): if connection.vendor == 'mysql': return 'NOT REGEXP' if connection.vendor == 'sqlite': # django provides an implementation of REGEXP for sqlite return 'NOT REGEXP' if connection.vendor == 'postgresql': return '!~*' raise Exception('Database vendor ' + connection.vendor + ' does not support regular expressions') def tag_series(self, series, requestContext=None): # extract tags and normalize path parsed = self.parse(series) path = parsed.path # check if path is already tagged curr = self.get_series(path) if curr and parsed.tags == curr.tags: return path with connection.cursor() as cursor: # tags self._insert_ignore('tags_tag', ['tag'], [[tag] for tag in parsed.tags.keys()]) sql = 'SELECT id, tag FROM tags_tag WHERE tag IN (' + ', '.join(['%s'] * len(parsed.tags)) + ')' # nosec params = list(parsed.tags.keys()) cursor.execute(sql, params) tag_ids = {tag: tag_id for (tag_id, tag) in cursor} # tag values self._insert_ignore('tags_tagvalue', ['value'], [[value] for value in parsed.tags.values()]) sql = 'SELECT id, value FROM tags_tagvalue WHERE value IN (' + ', '.join(['%s'] * len(parsed.tags)) + ')' # nosec params = list(parsed.tags.values()) cursor.execute(sql, params) value_ids = {value: value_id for (value_id, value) in cursor} # series if curr: series_id = curr.id else: # hash column is used to support a unique index in mysql since path can be longer than 191 characters path_hash = sha256(path.encode('utf8')).hexdigest() self._insert_ignore('tags_series', ['hash', 'path'], [[path_hash, path]]) sql = 'SELECT id FROM tags_series WHERE path=%s' params = [path] cursor.execute(sql, params) series_id = cursor.fetchone()[0] # series tags self._insert_ignore( 'tags_seriestag', ['series_id', 'tag_id', 'value_id'], [[series_id, tag_ids[tag], value_ids[value]] for tag, value in parsed.tags.items()] ) return path def del_series(self, series, requestContext=None): # extract tags and normalize path parsed = self.parse(series) path = parsed.path with connection.cursor() as cursor: sql = 'SELECT id' sql += ' FROM tags_series' sql += ' WHERE path=%s' params = [path] cursor.execute(sql, params) row = cursor.fetchone() if not row: return True (series_id, ) = row sql = 'DELETE FROM tags_seriestag WHERE series_id=%s' params = [series_id] cursor.execute(sql, params) sql = 'DELETE FROM tags_series WHERE id=%s' params = [series_id] cursor.execute(sql, params) return True graphite-web-1.1.4/webapp/graphite/tags/urls.py0000644000000000000000000000223513343334667021425 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/tagSeries$', views.tagSeries, name='tagSeries'), url(r'^/tagMultiSeries$', views.tagMultiSeries, name='tagMultiSeries'), url(r'^/delSeries$', views.delSeries, name='delSeries'), url(r'^/findSeries$', views.findSeries, name='findSeries'), url(r'^/autoComplete/tags$', views.autoCompleteTags, name='tagAutoCompleteTags'), url(r'^/autoComplete/values$', views.autoCompleteValues, name='tagAutoCompleteValues'), url(r'^/(.+)$', views.tagDetails, name='tagDetails'), url(r'^/?$', views.tagList, name='tagList'), ] graphite-web-1.1.4/webapp/graphite/tags/views.py0000644000000000000000000001064013343334667021574 0ustar rootroot00000000000000from graphite.util import jsonResponse, HttpResponse, HttpError from graphite.storage import STORE, extractForwardHeaders def _requestContext(request, queryParams): return { 'forwardHeaders': extractForwardHeaders(request), 'localOnly': queryParams.get('local') == '1', } @jsonResponse def tagSeries(request, queryParams): if request.method != 'POST': return HttpResponse(status=405) path = queryParams.get('path') if not path: raise HttpError('no path specified', status=400) return STORE.tagdb.tag_series(path, requestContext=_requestContext(request, queryParams)) @jsonResponse def tagMultiSeries(request, queryParams): if request.method != 'POST': return HttpResponse(status=405) paths = [] # Normal format: ?path=name;tag1=value1;tag2=value2&path=name;tag1=value2;tag2=value2 if len(queryParams.getlist('path')) > 0: paths = queryParams.getlist('path') # Rails/PHP/jQuery common practice format: ?path[]=...&path[]=... elif len(queryParams.getlist('path[]')) > 0: paths = queryParams.getlist('path[]') else: raise HttpError('no paths specified',status=400) return STORE.tagdb.tag_multi_series(paths, requestContext=_requestContext(request, queryParams)) @jsonResponse def delSeries(request, queryParams): if request.method != 'POST': return HttpResponse(status=405) paths = [] # Normal format: ?path=name;tag1=value1;tag2=value2&path=name;tag1=value2;tag2=value2 if len(queryParams.getlist('path')) > 0: paths = queryParams.getlist('path') # Rails/PHP/jQuery common practice format: ?path[]=...&path[]=... elif len(queryParams.getlist('path[]')) > 0: paths = queryParams.getlist('path[]') else: raise HttpError('no path specified', status=400) return STORE.tagdb.del_multi_series(paths, requestContext=_requestContext(request, queryParams)) @jsonResponse def findSeries(request, queryParams): if request.method not in ['GET', 'POST']: return HttpResponse(status=405) exprs = [] # Normal format: ?expr=tag1=value1&expr=tag2=value2 if len(queryParams.getlist('expr')) > 0: exprs = queryParams.getlist('expr') # Rails/PHP/jQuery common practice format: ?expr[]=tag1=value1&expr[]=tag2=value2 elif len(queryParams.getlist('expr[]')) > 0: exprs = queryParams.getlist('expr[]') if not exprs: raise HttpError('no tag expressions specified', status=400) return STORE.tagdb.find_series(exprs, requestContext=_requestContext(request, queryParams)) @jsonResponse def tagList(request, queryParams): if request.method != 'GET': return HttpResponse(status=405) return STORE.tagdb.list_tags( tagFilter=request.GET.get('filter'), limit=request.GET.get('limit'), requestContext=_requestContext(request, queryParams), ) @jsonResponse def tagDetails(request, queryParams, tag): if request.method != 'GET': return HttpResponse(status=405) return STORE.tagdb.get_tag( tag, valueFilter=queryParams.get('filter'), limit=queryParams.get('limit'), requestContext=_requestContext(request, queryParams), ) @jsonResponse def autoCompleteTags(request, queryParams): if request.method not in ['GET', 'POST']: return HttpResponse(status=405) exprs = [] # Normal format: ?expr=tag1=value1&expr=tag2=value2 if len(queryParams.getlist('expr')) > 0: exprs = queryParams.getlist('expr') # Rails/PHP/jQuery common practice format: ?expr[]=tag1=value1&expr[]=tag2=value2 elif len(queryParams.getlist('expr[]')) > 0: exprs = queryParams.getlist('expr[]') return STORE.tagdb_auto_complete_tags( exprs, tagPrefix=queryParams.get('tagPrefix'), limit=queryParams.get('limit'), requestContext=_requestContext(request, queryParams) ) @jsonResponse def autoCompleteValues(request, queryParams): if request.method not in ['GET', 'POST']: return HttpResponse(status=405) exprs = [] # Normal format: ?expr=tag1=value1&expr=tag2=value2 if len(queryParams.getlist('expr')) > 0: exprs = queryParams.getlist('expr') # Rails/PHP/jQuery common practice format: ?expr[]=tag1=value1&expr[]=tag2=value2 elif len(queryParams.getlist('expr[]')) > 0: exprs = queryParams.getlist('expr[]') tag = queryParams.get('tag') if not tag: raise HttpError('no tag specified', status=400) return STORE.tagdb_auto_complete_values( exprs, tag, valuePrefix=queryParams.get('valuePrefix'), limit=queryParams.get('limit'), requestContext=_requestContext(request, queryParams) ) graphite-web-1.1.4/webapp/graphite/tags/utils.py0000644000000000000000000001035513343334667021602 0ustar rootroot00000000000000"""Utility functions for tag databases.""" import re from hashlib import sha256 class TaggedSeries(object): @classmethod def parse(cls, path): # if path is in openmetrics format: metric{tag="value",...} if path[-2:] == '"}' and '{' in path: return cls.parse_openmetrics(path) # path is a carbon path with optional tags: metric;tag=value;... return cls.parse_carbon(path) @classmethod def parse_openmetrics(cls, path): """parse a path in openmetrics format: metric{tag="value",...} https://github.com/RichiH/OpenMetrics """ (metric, rawtags) = path[0:-1].split('{', 2) if not metric: raise Exception('Cannot parse path %s, no metric found' % path) tags = {} while len(rawtags) > 0: m = re.match(r'([^=]+)="((?:[\\]["\\]|[^"\\])+)"(:?,|$)', rawtags) if not m: raise Exception('Cannot parse path %s, invalid segment %s' % (path, rawtags)) tags[m.group(1)] = m.group(2).replace(r'\"', '"').replace(r'\\', '\\') rawtags = rawtags[len(m.group(0)):] tags['name'] = metric return cls(metric, tags) @classmethod def parse_carbon(cls, path): """parse a carbon path with optional tags: metric;tag=value;...""" segments = path.split(';') metric = segments[0] if not metric: raise Exception('Cannot parse path %s, no metric found' % path) tags = {} for segment in segments[1:]: tag = segment.split('=', 1) if len(tag) != 2 or not tag[0]: raise Exception('Cannot parse path %s, invalid segment %s' % (path, segment)) tags[tag[0]] = tag[1] tags['name'] = metric return cls(metric, tags) @staticmethod def format(tags): return tags.get('name', '') + ''.join(sorted([ ';%s=%s' % (tag, value) for tag, value in tags.items() if tag != 'name' ])) @staticmethod def encode(metric, sep='.', hash_only=False): """ Helper function to encode tagged series for storage in whisper etc When tagged series are detected, they are stored in a separate hierarchy of folders under a top-level _tagged folder, where subfolders are created by using the first 3 hex digits of the sha256 hash of the tagged metric path (4096 possible folders), and second-level subfolders are based on the following 3 hex digits (another 4096 possible folders) for a total of 4096^2 possible subfolders. The metric files themselves are created with any . in the metric path replaced with -, to avoid any issues where metrics, tags or values containing a '.' would end up creating further subfolders. This helper is used by both whisper and ceres, but by design each carbon database and graphite-web finder is responsible for handling its own encoding so that different backends can create their own schemes if desired. The hash_only parameter can be set to True to use the hash as the filename instead of a human-readable name. This avoids issues with filename length restrictions, at the expense of being unable to decode the filename and determine the original metric name. A concrete example: .. code-block:: none some.metric;tag1=value2;tag2=value.2 with sha256 hash starting effaae would be stored in: _tagged/eff/aae/some-metric;tag1=value2;tag2=value-2.wsp (whisper) _tagged/eff/aae/some-metric;tag1=value2;tag2=value-2 (ceres) """ if ';' in metric: metric_hash = sha256(metric.encode('utf8')).hexdigest() return sep.join([ '_tagged', metric_hash[0:3], metric_hash[3:6], metric_hash if hash_only else metric.replace('.', '_DOT_') ]) # metric isn't tagged, just replace dots with the separator and trim any leading separator return metric.replace('.', sep).lstrip(sep) @staticmethod def decode(path, sep='.'): """ Helper function to decode tagged series from storage in whisper etc """ if path.startswith('_tagged'): return path.split(sep, 3)[-1].replace('_DOT_', '.') # metric isn't tagged, just replace the separator with dots return path.replace(sep, '.') def __init__(self, metric, tags, series_id=None): self.metric = metric self.tags = tags self.id = series_id @property def path(self): return self.__class__.format(self.tags) graphite-web-1.1.4/webapp/graphite/tags/redis.py0000644000000000000000000001711513343334667021551 0ustar rootroot00000000000000from __future__ import absolute_import import re import bisect import sys from graphite.tags.base import BaseTagDB, TaggedSeries class RedisTagDB(BaseTagDB): """ Stores tag information in a Redis database. Keys used are: .. code-block:: none series # Set of all paths series::tags # Hash of all tag:value pairs for path tags # Set of all tags tags::series # Set of paths with entry for tag tags::values # Set of values for tag tags::values: # Set of paths matching tag/value """ def __init__(self, settings, *args, **kwargs): super(RedisTagDB, self).__init__(settings, *args, **kwargs) from redis import Redis self.r = Redis( host=settings.TAGDB_REDIS_HOST, port=settings.TAGDB_REDIS_PORT, db=settings.TAGDB_REDIS_DB, decode_responses=(sys.version_info[0] >= 3), ) def _find_series(self, tags, requestContext=None): selector = None selector_cnt = None filters = [] # loop through tagspecs, look for best spec to use as selector for tagspec in tags: (tag, operator, spec) = self.parse_tagspec(tagspec) if operator == '=': matches_empty = spec == '' if not matches_empty: cnt = self.r.scard('tags:' + tag + ':values:' + spec) if not selector or selector[1] != '=' or selector_cnt > cnt: if selector: filters.append(selector) selector = (tag, operator, spec) selector_cnt = cnt continue filters.append((tag, operator, spec)) elif operator == '=~': pattern = re.compile(spec) matches_empty = bool(pattern.match('')) if not matches_empty and (not selector or selector[1] != '='): cnt = self.r.scard('tags:' + tag + ':values') if not selector or selector_cnt > cnt: if selector: filters.append(selector) selector = (tag, operator, pattern) selector_cnt = cnt continue filters.append((tag, operator, pattern)) elif operator == '!=': matches_empty = spec != '' if not matches_empty and (not selector or selector[1] != '='): cnt = self.r.scard('tags:' + tag + ':values') if not selector or selector_cnt > cnt: if selector: filters.append(selector) selector = (tag, operator, spec) selector_cnt = cnt continue filters.append((tag, operator, spec)) elif operator == '!=~': pattern = re.compile(spec) matches_empty = not pattern.match('') if not matches_empty and (not selector or selector[1] != '='): cnt = self.r.scard('tags:' + tag + ':values') if not selector or selector_cnt > cnt: if selector: filters.append(selector) selector = (tag, operator, pattern) selector_cnt = cnt continue filters.append((tag, operator, pattern)) else: raise ValueError("Invalid operator %s" % operator) if not selector: raise ValueError("At least one tagspec must not match the empty string") # get initial list of series (tag, operator, spec) = selector # find list of values that match the tagspec values = None if operator == '=': values = [spec] elif operator == '=~': # see if we can identify a literal prefix to filter by in redis match = None m = re.match('([a-z0-9]+)([^*?|][^|]*)?$', spec.pattern) if m: match = m.group(1) + '*' values = [value for value in self.r.sscan_iter('tags:' + tag + ':values', match=match) if spec.match(value) is not None] elif operator == '!=': values = [value for value in self.r.sscan_iter('tags:' + tag + ':values') if value != spec] elif operator == '!=~': values = [value for value in self.r.sscan_iter('tags:' + tag + ':values') if spec.match(value) is None] # if this query matched no values, just short-circuit since the result of the final intersect will be empty if not values: return [] results = [] # apply filters operators = ['=','!=','=~','!=~'] filters.sort(key=lambda a: operators.index(a[1])) for series in self.r.sunion(*['tags:' + tag + ':values:' + value for value in values]): parsed = self.parse(series) matched = True for (tag, operator, spec) in filters: value = parsed.tags.get(tag, '') if ( (operator == '=' and value != spec) or (operator == '=~' and spec.match(value) is None) or (operator == '!=' and value == spec) or (operator == '!=~' and spec.match(value) is not None) ): matched = False break if matched: bisect.insort_left(results, series) return results def get_series(self, path, requestContext=None): tags = {} tags = self.r.hgetall('series:' + path + ':tags') if not tags: return None return TaggedSeries(tags['name'], tags) def list_tags(self, tagFilter=None, limit=None, requestContext=None): result = [] if tagFilter: tagFilter = re.compile(tagFilter) for tag in self.r.sscan_iter('tags'): if tagFilter and tagFilter.match(tag) is None: continue if len(result) == 0 or tag >= result[-1]: if limit and len(result) >= limit: continue result.append(tag) else: bisect.insort_left(result, tag) if limit and len(result) > limit: del result[-1] return [ {'tag': tag} for tag in result ] def get_tag(self, tag, valueFilter=None, limit=None, requestContext=None): if not self.r.sismember('tags', tag): return None return { 'tag': tag, 'values': self.list_values( tag, valueFilter=valueFilter, limit=limit, requestContext=requestContext ), } def list_values(self, tag, valueFilter=None, limit=None, requestContext=None): result = [] if valueFilter: valueFilter = re.compile(valueFilter) for value in self.r.sscan_iter('tags:' + tag + ':values'): if valueFilter and valueFilter.match(value) is None: continue if len(result) == 0 or value >= result[-1]: if limit and len(result) >= limit: continue result.append(value) else: bisect.insort_left(result, value) if limit and len(result) > limit: del result[-1] return [ {'value': value, 'count': self.r.scard('tags:' + tag + ':values:' + value)} for value in result ] def tag_series(self, series, requestContext=None): # extract tags and normalize path parsed = self.parse(series) path = parsed.path with self.r.pipeline() as pipe: pipe.sadd('series', path) for tag, value in parsed.tags.items(): pipe.hset('series:' + path + ':tags', tag, value) pipe.sadd('tags', tag) pipe.sadd('tags:' + tag + ':series', path) pipe.sadd('tags:' + tag + ':values', value) pipe.sadd('tags:' + tag + ':values:' + value, path) pipe.execute() return path def del_series(self, series, requestContext=None): # extract tags and normalize path parsed = self.parse(series) path = parsed.path with self.r.pipeline() as pipe: pipe.srem('series', path) pipe.delete('series:' + path + ':tags') for tag, value in parsed.tags.items(): pipe.srem('tags:' + tag + ':series', path) pipe.srem('tags:' + tag + ':values:' + value, path) pipe.execute() return True graphite-web-1.1.4/webapp/graphite/node.py0000644000000000000000000000171313343334667020427 0ustar rootroot00000000000000 class Node(object): __slots__ = ('name', 'path', 'local', 'is_leaf') def __init__(self, path): self.path = path self.name = path.split('.')[-1] self.local = True self.is_leaf = False def __repr__(self): return '<%s[%x]: %s>' % (self.__class__.__name__, id(self), self.path) class BranchNode(Node): pass class LeafNode(Node): __slots__ = ('reader', ) def __init__(self, path, reader): Node.__init__(self, path) self.reader = reader self.is_leaf = True def fetch(self, startTime, endTime, now=None, requestContext=None): try: result = self.reader.fetch(startTime, endTime, now, requestContext) except TypeError: # Support for legacy 3rd party, readers. result = self.reader.fetch(startTime, endTime) return result @property def intervals(self): return self.reader.get_intervals() def __repr__(self): return '' % (id(self), self.path, self.reader) graphite-web-1.1.4/webapp/graphite/functions/0000755000000000000000000000000013343335472021131 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/functions/__init__.py0000644000000000000000000000540113343334667023247 0ustar rootroot00000000000000import inspect from importlib import import_module from os import listdir from os.path import dirname, join, splitext from django.conf import settings from graphite.functions.params import Param, ParamTypes # noqa from graphite.logger import log customDir = join(dirname(__file__), 'custom') customModPrefix = 'graphite.functions.custom.' _SeriesFunctions = {} _PieFunctions = {} def loadFunctions(force=False): if _SeriesFunctions and not force: return from graphite.render import functions _SeriesFunctions.clear() _SeriesFunctions.update(functions.SeriesFunctions) _PieFunctions.clear() _PieFunctions.update(functions.PieFunctions) custom_modules = [] for filename in listdir(customDir): module_name, extension = splitext(filename) if extension != '.py' or module_name == '__init__': continue custom_modules.append(customModPrefix + module_name) for module_name in custom_modules + settings.FUNCTION_PLUGINS: try: module = import_module(module_name) except Exception as e: log.warning('Error loading function plugin %s: %s' % (module_name, e)) continue for func_name, func in getattr(module, 'SeriesFunctions', {}).items(): try: addFunction(_SeriesFunctions, func, func_name) except Exception as e: log.warning('Error loading function plugin %s: %s' % (module_name, e)) for func_name, func in getattr(module, 'PieFunctions', {}).items(): try: addFunction(_PieFunctions, func, func_name) except Exception as e: log.warning('Error loading function plugin %s: %s' % (module_name, e)) def addFunction(dest, func, func_name): if not hasattr(func, 'group'): func.group = 'Ungrouped' if not hasattr(func, 'params'): raise Exception('No params defined for %s' % func_name) for param in func.params: if not isinstance(param, Param): raise Exception('Invalid param specified for %s' % func_name) dest[func_name] = func def SeriesFunctions(): loadFunctions() return _SeriesFunctions def SeriesFunction(name): loadFunctions() try: return _SeriesFunctions[name] except KeyError: raise KeyError('Function "%s" not found' % name) def PieFunctions(): loadFunctions() return _PieFunctions def PieFunction(name): loadFunctions() try: return _PieFunctions[name] except KeyError: raise KeyError('Function "%s" not found' % name) def functionInfo(name, func): argspec = inspect.getargspec(func) return { 'name': name, 'function': name + inspect.formatargspec(argspec[0][1:], argspec[1], argspec[2], argspec[3]), 'description': inspect.getdoc(func), 'module': inspect.getmodule(func).__name__, 'group': getattr(func, 'group', 'Ungrouped'), 'params': getattr(func, 'params', None), } graphite-web-1.1.4/webapp/graphite/functions/custom/0000755000000000000000000000000013343335472022443 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/functions/custom/__init__.py0000644000000000000000000000000013343334667024547 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/functions/params.py0000644000000000000000000000241113343334667022771 0ustar rootroot00000000000000class ParamTypes(object): pass for paramType in [ 'aggFunc', 'boolean', 'date', 'float', 'integer', 'interval', 'intOrInterval', 'node', 'nodeOrTag', 'series', 'seriesList', 'seriesLists', 'string', 'tag', ]: setattr(ParamTypes, paramType, paramType) class Param(object): __slots__ = ('name', 'type', 'required', 'default', 'multiple', 'options', 'suggestions') def __init__(self, name, paramtype, required=False, default=None, multiple=False, options=None, suggestions=None): self.name = name if not hasattr(ParamTypes, paramtype): raise Exception('Invalid type %s for parameter %s' % (paramtype, name)) self.type = paramtype self.required = bool(required) self.default = default self.multiple = bool(multiple) self.options = options self.suggestions = suggestions def toJSON(self): jsonVal = { 'name': self.name, 'type': self.type, } if self.required: jsonVal['required'] = True if self.default is not None: jsonVal['default'] = self.default if self.multiple: jsonVal['multiple'] = True if self.options: jsonVal['options'] = self.options if self.suggestions: jsonVal['suggestions'] = self.suggestions return jsonVal graphite-web-1.1.4/webapp/graphite/functions/urls.py0000644000000000000000000000034613343334667022500 0ustar rootroot00000000000000from django.conf.urls import url from graphite.functions.views import functionList, functionDetails urlpatterns = [ url(r'^/(.+)$', functionDetails, name='functionDetails'), url(r'^/?$', functionList, name='functionList'), ] graphite-web-1.1.4/webapp/graphite/functions/views.py0000644000000000000000000000251213343334667022645 0ustar rootroot00000000000000from graphite.util import jsonResponse, HttpResponse, HttpError from graphite.functions import SeriesFunctions, SeriesFunction, PieFunctions, PieFunction, functionInfo def jsonEncoder(obj): if hasattr(obj, 'toJSON'): return obj.toJSON() return obj.__dict__ @jsonResponse(default=jsonEncoder) def functionList(request, queryParams): if request.method != 'GET': return HttpResponse(status=405) if queryParams.get('type') == 'pie': funcs = PieFunctions() else: funcs = SeriesFunctions() grouped = queryParams.get('grouped', '').lower() in ['1', 'true'] group = queryParams.get('group') result = {} for (name, func) in funcs.items(): info = functionInfo(name, func) if group is not None and group != info['group']: continue if grouped: if info['group'] not in result: result[info['group']] = {} result[info['group']][name] = info else: result[name] = info return result @jsonResponse(default=jsonEncoder) def functionDetails(request, queryParams, name): if request.method != 'GET': return HttpResponse(status=405) try: if queryParams.get('type') == 'pie': func = PieFunction(name) else: func = SeriesFunction(name) except KeyError: raise HttpError('Function not found: %s' % name, status=404) return functionInfo(name, func) graphite-web-1.1.4/webapp/graphite/render/0000755000000000000000000000000013343335472020400 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/render/grammar.py0000644000000000000000000000675013343334667022415 0ustar rootroot00000000000000from pyparsing import ( Forward, Combine, Optional, Word, Literal, CaselessKeyword, CaselessLiteral, Group, FollowedBy, LineEnd, OneOrMore, ZeroOrMore, alphas, alphanums, printables, delimitedList, quotedString, Regex, __version__, Suppress, Empty ) grammar = Forward() expression = Forward() # Literals intNumber = Regex(r'-?\d+')('integer') floatNumber = Regex(r'-?\d+\.\d+')('float') sciNumber = Combine( (floatNumber | intNumber) + CaselessLiteral('e') + intNumber )('scientific') aString = quotedString('string') # Use lookahead to match only numbers in a list (can't remember why this is necessary) afterNumber = FollowedBy(",") ^ FollowedBy(")") ^ FollowedBy(LineEnd()) number = Group( (sciNumber + afterNumber) | (floatNumber + afterNumber) | (intNumber + afterNumber) )('number') boolean = Group( CaselessKeyword("true") | CaselessKeyword("false") )('boolean') none = Group( CaselessKeyword('none') )('none') argname = Word(alphas + '_', alphanums + '_')('argname') funcname = Word(alphas + '_', alphanums + '_')('funcname') ## Symbols leftParen = Literal('(').suppress() rightParen = Literal(')').suppress() comma = Literal(',').suppress() equal = Literal('=').suppress() # Function calls ## Symbols leftBrace = Literal('{') rightBrace = Literal('}') leftParen = Literal('(').suppress() rightParen = Literal(')').suppress() comma = Literal(',').suppress() equal = Literal('=').suppress() backslash = Literal('\\').suppress() symbols = '''(){},.'"\\|''' arg = Group( boolean | number | none | aString | expression )('args*') kwarg = Group(argname + equal + arg)('kwargs*') args = delimitedList(~kwarg + arg) # lookahead to prevent failing on equals kwargs = delimitedList(kwarg) def setRaw(s, loc, toks): toks[0].raw = s[toks[0].start:toks[0].end] call = Group( Empty().setParseAction(lambda s, l, t: l)('start') + funcname + leftParen + Optional( args + Optional( comma + kwargs ) ) + rightParen + Empty().leaveWhitespace().setParseAction(lambda s, l, t: l)('end') ).setParseAction(setRaw)('call') # Metric pattern (aka. pathExpression) validMetricChars = ''.join((set(printables) - set(symbols))) escapedChar = backslash + Word(symbols + '=', exact=1) partialPathElem = Combine( OneOrMore( escapedChar | Word(validMetricChars) ) ) matchEnum = Combine( leftBrace + delimitedList(partialPathElem, combine=True) + rightBrace ) pathElement = Combine( Group(partialPathElem | matchEnum) + ZeroOrMore(matchEnum | partialPathElem) ) pathExpression = delimitedList(pathElement, delim='.', combine=True)('pathExpression') litarg = Group( number | aString )('args*') litkwarg = Group(argname + equal + litarg)('kwargs*') litargs = delimitedList(~litkwarg + litarg) # lookahead to prevent failing on equals litkwargs = delimitedList(litkwarg) template = Group( Literal('template') + leftParen + (call | pathExpression) + Optional(comma + (litargs | litkwargs)) + rightParen )('template') pipeSep = ZeroOrMore(Literal(' ')) + Literal('|') + ZeroOrMore(Literal(' ')) pipedExpression = Group( (template | call | pathExpression) + Group(ZeroOrMore(Suppress(pipeSep) + Group(call)('pipedCall')))('pipedCalls') )('expression') if __version__.startswith('1.'): expression << pipedExpression grammar << expression else: expression <<= pipedExpression grammar <<= expression def enableDebug(): for name, obj in globals().items(): try: obj.setName(name) obj.setDebug(True) except Exception: pass graphite-web-1.1.4/webapp/graphite/render/__init__.py0000644000000000000000000000000013343334667022504 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/render/hashing.py0000644000000000000000000001114113343334667022376 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Copyright 2011 Chris Davis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from hashlib import md5 from itertools import chain import bisect import sys try: import mmh3 except ImportError: mmh3 = None try: import pyhash hasher = pyhash.fnv1a_32() def fnv32a(data, seed=0x811c9dc5): return hasher(data, seed=seed) except ImportError: def fnv32a(data, seed=0x811c9dc5): """ FNV-1a Hash (http://isthe.com/chongo/tech/comp/fnv/) in Python. Taken from https://gist.github.com/vaiorabbit/5670985 """ hval = seed fnv_32_prime = 0x01000193 uint32_max = 2 ** 32 if sys.version_info >= (3, 0): # data is a bytes object, s is an integer for s in data: hval = hval ^ s hval = (hval * fnv_32_prime) % uint32_max else: # data is an str object, s is a single character for s in data: hval = hval ^ ord(s) hval = (hval * fnv_32_prime) % uint32_max return hval def hashRequest(request): # Normalize the request parameters to ensure we're deterministic queryParams = [ "%s=%s" % (key, '&'.join(values)) for (key,values) in chain(request.POST.lists(), request.GET.lists()) if not key.startswith('_') ] normalizedParams = ','.join( sorted(queryParams) ) return compactHash(normalizedParams) def hashData(targets, startTime, endTime, xFilesFactor): targetsString = ','.join(sorted(targets)) startTimeString = startTime.strftime("%Y%m%d_%H%M") endTimeString = endTime.strftime("%Y%m%d_%H%M") myHash = targetsString + '@' + startTimeString + ':' + endTimeString + ':' + str(xFilesFactor) return compactHash(myHash) def compactHash(string): return md5(string.encode('utf-8')).hexdigest() def carbonHash(key, hash_type): if hash_type == 'fnv1a_ch': big_hash = int(fnv32a(key.encode('utf-8'))) small_hash = (big_hash >> 16) ^ (big_hash & 0xffff) elif hash_type == 'mmh3_ch': if mmh3 is None: raise Exception('Install "mmh3" to use this hashing function.') small_hash = mmh3.hash(key) else: big_hash = compactHash(key) small_hash = int(big_hash[:4], 16) return small_hash class ConsistentHashRing: def __init__(self, nodes, replica_count=100, hash_type='carbon_ch'): self.ring = [] self.ring_len = len(self.ring) self.nodes = set() self.nodes_len = len(self.nodes) self.replica_count = replica_count self.hash_type = hash_type for node in nodes: self.add_node(node) def compute_ring_position(self, key): return carbonHash(key, self.hash_type) def add_node(self, key): self.nodes.add(key) self.nodes_len = len(self.nodes) for i in range(self.replica_count): if self.hash_type == 'fnv1a_ch': replica_key = "%d-%s" % (i, key[1]) else: replica_key = "%s:%d" % (key, i) position = self.compute_ring_position(replica_key) while position in [r[0] for r in self.ring]: position = position + 1 entry = (position, key) bisect.insort(self.ring, entry) self.ring_len = len(self.ring) def remove_node(self, key): self.nodes.discard(key) self.nodes_len = len(self.nodes) self.ring = [entry for entry in self.ring if entry[1] != key] self.ring_len = len(self.ring) def get_node(self, key): assert self.ring position = self.compute_ring_position(key) search_entry = (position, ()) index = bisect.bisect_left(self.ring, search_entry) % self.ring_len entry = self.ring[index] return entry[1] def get_nodes(self, key): nodes = set() if not self.ring: return if self.nodes_len == 1: for node in self.nodes: yield node position = self.compute_ring_position(key) search_entry = (position, ()) index = bisect.bisect_left(self.ring, search_entry) % self.ring_len last_index = (index - 1) % self.ring_len nodes_len = len(nodes) while nodes_len < self.nodes_len and index != last_index: next_entry = self.ring[index] (position, next_node) = next_entry if next_node not in nodes: nodes.add(next_node) nodes_len += 1 yield next_node index = (index + 1) % self.ring_len graphite-web-1.1.4/webapp/graphite/render/functions.py0000644000000000000000000054621013343334667022777 0ustar rootroot00000000000000#Copyright 2008 Orbitz WorldWide # #Licensed under the Apache License, Version 2.0 (the "License"); #you may not use this file except in compliance with the License. #You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # #Unless required by applicable law or agreed to in writing, software #distributed under the License is distributed on an "AS IS" BASIS, #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #See the License for the specific language governing permissions and #limitations under the License. # make / work consistently between python 2.x and 3.x # https://www.python.org/dev/peps/pep-0238/ from __future__ import division import math import random import re import time from datetime import datetime, timedelta from functools import reduce from six.moves import range, zip import six try: from itertools import izip, izip_longest except ImportError: # Python 3 from itertools import zip_longest as izip_longest izip = zip from os import environ from django.conf import settings from graphite.errors import NormalizeEmptyResultError from graphite.events import models from graphite.functions import SeriesFunction, ParamTypes, Param from graphite.logger import log from graphite.render.attime import getUnitString, parseTimeOffset, parseATTime, SECONDS_STRING, MINUTES_STRING, HOURS_STRING, DAYS_STRING, WEEKS_STRING, MONTHS_STRING, YEARS_STRING from graphite.render.evaluator import evaluateTarget from graphite.render.grammar import grammar from graphite.storage import STORE from graphite.util import epoch, epoch_to_dt, timestamp, deltaseconds # XXX format_units() should go somewhere else if environ.get('READTHEDOCS'): format_units = lambda *args, **kwargs: (0,'') else: from graphite.render.glyph import format_units from graphite.render.datalib import TimeSeries NAN = float('NaN') INF = float('inf') DAY = 86400 HOUR = 3600 MINUTE = 60 #Utility functions def safeSum(values): safeValues = [v for v in values if v is not None] if safeValues: return sum(safeValues) def safeDiff(values): safeValues = [v for v in values if v is not None] if safeValues: values = list(map(lambda x: x*-1, safeValues[1:])) values.insert(0, safeValues[0]) return sum(values) def safeLen(values): return len([v for v in values if v is not None]) def safeDiv(a, b): if a is None: return None if b in (0,None): return None return a / b def safePow(a, b): if a is None: return None if b is None: return None try: result = math.pow(a, b) except (ValueError, OverflowError): return None return result def safeMul(*factors): if None in factors: return None factors = [float(x) for x in factors] product = reduce(lambda x,y: x*y, factors) return product def safeSubtract(a,b): if a is None or b is None: return None return float(a) - float(b) def safeAvg(values): safeValues = [v for v in values if v is not None] if safeValues: return sum(safeValues) / len(safeValues) def safeMedian(values): safeValues = [v for v in values if v is not None] if safeValues: sortedVals = sorted(safeValues) mid = len(sortedVals) // 2 if len(sortedVals) % 2 == 0: return float(sortedVals[mid-1] + sortedVals[mid]) / 2 else: return sortedVals[mid] def safeStdDev(a): sm = safeSum(a) ln = safeLen(a) avg = safeDiv(sm,ln) if avg is None: return None sum = 0 safeValues = [v for v in a if v is not None] for val in safeValues: sum = sum + (val - avg) * (val - avg) return math.sqrt(sum/ln) def safeLast(values): for v in reversed(values): if v is not None: return v def safeMin(values, default=None): safeValues = [v for v in values if v is not None] if safeValues: return min(safeValues) else: return default def safeMax(values, default=None): safeValues = [v for v in values if v is not None] if safeValues: return max(safeValues) else: return default def safeMap(function, values): safeValues = [v for v in values if v is not None] if safeValues: return [function(x) for x in safeValues] def safeAbs(value): if value is None: return None return abs(value) # In Py2, None < (any integer) . Use -inf for same sorting on Python 3 def keyFunc(func): def safeFunc(values): val = func(values) return val if val is not None else -INF return safeFunc # Greatest common divisor def gcd(a, b): if b == 0: return a return gcd(b, a%b) # Least common multiple def lcm(a, b): if a == b: return a if a < b: (a, b) = (b, a) #ensure a > b return a // gcd(a,b) * b # check list of values against xFilesFactor def xffValues(values, xFilesFactor): if not values: return False return xff(len([v for v in values if v is not None]), len(values), xFilesFactor) def xff(nonNull, total, xFilesFactor=None): if not nonNull or not total: return False return nonNull / total >= (xFilesFactor if xFilesFactor is not None else settings.DEFAULT_XFILES_FACTOR) def getNodeOrTag(series, n, pathExpression=None): try: # if n is an integer use it as a node, otherwise catch ValueError and treat it as a tag if n == int(n): # first split off any tags, then list of nodes is name split on . return (pathExpression or series.name).split(';', 2)[0].split('.')[n] except (ValueError, IndexError): # if node isn't an integer or isn't found then try it as a tag name pass # return tag value, default to '' if not found return series.tags.get(str(n), '') def aggKey(series, nodes, pathExpression=None): # if series.name looks like it includes a function, use the first path expression if pathExpression is None and series.name[-1] == ')': pathExpression = _getFirstPathExpression(series.name) return '.'.join([getNodeOrTag(series, n, pathExpression) for n in nodes]) def normalize(seriesLists): if seriesLists: seriesList = reduce(lambda L1,L2: L1+L2,seriesLists) if seriesList: step = reduce(lcm,[s.step for s in seriesList]) for s in seriesList: s.consolidate( step // s.step ) start = min([s.start for s in seriesList]) end = max([s.end for s in seriesList]) end -= (end - start) % step return (seriesList,start,end,step) raise NormalizeEmptyResultError() def matchSeries(seriesList1, seriesList2): assert len(seriesList2) == len(seriesList1), "The number of series in each argument must be the same" return izip(sorted(seriesList1, key=lambda a: a.name), sorted(seriesList2, key=lambda a: a.name)) def formatPathExpressions(seriesList): # remove duplicates pathExpressions = [] [pathExpressions.append(s.pathExpression) for s in seriesList if not pathExpressions.count(s.pathExpression)] return ','.join(pathExpressions) # Series Functions aggFuncs = { 'average': safeAvg, 'median': safeMedian, 'sum': safeSum, 'min': safeMin, 'max': safeMax, 'diff': safeDiff, 'stddev': safeStdDev, 'count': safeLen, 'range': lambda row: safeSubtract(safeMax(row), safeMin(row)), 'multiply': lambda row: safeMul(*row), 'last': safeLast, } aggFuncAliases = { 'rangeOf': aggFuncs['range'], 'avg': aggFuncs['average'], 'total': aggFuncs['sum'], 'current': aggFuncs['last'], } aggFuncNames = sorted(aggFuncs.keys()) def getAggFunc(func, rawFunc=None): if func in aggFuncs: return aggFuncs[func] if func in aggFuncAliases: return aggFuncAliases[func] raise ValueError('Unsupported aggregation function: %s' % (rawFunc or func)) def aggregate(requestContext, seriesList, func, xFilesFactor=None): """ Aggregate series using the specified function. Example: .. code-block:: none &target=aggregate(host.cpu-[0-7].cpu-{user,system}.value, "sum") This would be the equivalent of .. code-block:: none &target=sumSeries(host.cpu-[0-7].cpu-{user,system}.value) This function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``. """ # strip Series from func if func was passed like sumSeries rawFunc = func if func[-6:] == 'Series': func = func[:-6] consolidationFunc = getAggFunc(func, rawFunc) # if seriesList is empty then just short-circuit if not seriesList: return [] # if seriesList is a single series then wrap it for normalize if isinstance(seriesList[0], TimeSeries): seriesList = [seriesList] try: (seriesList, start, end, step) = normalize(seriesList) except NormalizeEmptyResultError: return [] xFilesFactor = xFilesFactor if xFilesFactor is not None else requestContext.get('xFilesFactor') name = "%sSeries(%s)" % (func, formatPathExpressions(seriesList)) values = ( consolidationFunc(row) if xffValues(row, xFilesFactor) else None for row in izip_longest(*seriesList) ) tags = seriesList[0].tags for series in seriesList: tags = {tag: tags[tag] for tag in tags if tag in series.tags and tags[tag] == series.tags[tag]} if 'name' not in tags: tags['name'] = name tags['aggregatedBy'] = func series = TimeSeries(name, start, end, step, values, xFilesFactor=xFilesFactor, tags=tags) return [series] aggregate.group = 'Combine' aggregate.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('func', ParamTypes.aggFunc, required=True, options=aggFuncNames), Param('xFilesFactor', ParamTypes.float), ] def sumSeries(requestContext, *seriesLists): """ Short form: sum() This will add metrics together and return the sum at each datapoint. (See integral for a sum over time) Example: .. code-block:: none &target=sum(company.server.application*.requestsHandled) This would show the sum of all requests handled per minute (provided requestsHandled are collected once a minute). If metrics with different retention rates are combined, the coarsest metric is graphed, and the sum of the other metrics is averaged for the metrics with finer retention rates. This is an alias for :py:func:`aggregate ` with aggregation ``sum``. """ return aggregate(requestContext, seriesLists, 'sum') sumSeries.group = 'Combine' sumSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def sumSeriesWithWildcards(requestContext, seriesList, *position): #XXX """ Call sumSeries after inserting wildcards at the given position(s). Example: .. code-block:: none &target=sumSeriesWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, 1) This would be the equivalent of .. code-block:: none &target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value) This is an alias for :py:func:`aggregateWithWildcards ` with aggregation ``sum``. """ return aggregateWithWildcards(requestContext, seriesList, 'sum', *position) sumSeriesWithWildcards.group = 'Combine' sumSeriesWithWildcards.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('position', ParamTypes.node, multiple=True), ] def averageSeriesWithWildcards(requestContext, seriesList, *position): #XXX """ Call averageSeries after inserting wildcards at the given position(s). Example: .. code-block:: none &target=averageSeriesWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, 1) This would be the equivalent of .. code-block:: none &target=averageSeries(host.*.cpu-user.value)&target=averageSeries(host.*.cpu-system.value) This is an alias for :py:func:`aggregateWithWildcards ` with aggregation ``average``. """ return aggregateWithWildcards(requestContext, seriesList, 'average', *position) averageSeriesWithWildcards.group = 'Combine' averageSeriesWithWildcards.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('position', ParamTypes.node, multiple=True), ] def multiplySeriesWithWildcards(requestContext, seriesList, *position): #XXX """ Call multiplySeries after inserting wildcards at the given position(s). Example: .. code-block:: none &target=multiplySeriesWithWildcards(web.host-[0-7].{avg-response,total-request}.value, 2) This would be the equivalent of .. code-block:: none &target=multiplySeries(web.host-0.{avg-response,total-request}.value)&target=multiplySeries(web.host-1.{avg-response,total-request}.value)... This is an alias for :py:func:`aggregateWithWildcards ` with aggregation ``multiply``. """ return aggregateWithWildcards(requestContext, seriesList, 'multiply', *position) multiplySeriesWithWildcards.group = 'Combine' multiplySeriesWithWildcards.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('position', ParamTypes.node, multiple=True), ] def aggregateWithWildcards(requestContext, seriesList, func, *positions): """ Call aggregator after inserting wildcards at the given position(s). Example: .. code-block:: none &target=aggregateWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, "sum", 1) This would be the equivalent of .. code-block:: none &target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value) # or &target=aggregate(host.cpu-[0-7].cpu-user.value,"sum")&target=aggregate(host.cpu-[0-7].cpu-system.value,"sum") This function can be used with all aggregation functions supported by :py:func:`aggregate `: ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``range`` & ``multiply``. This complements :py:func:`groupByNodes ` which takes a list of nodes that must match in each group. """ metaSeries = {} keys = [] for series in seriesList: key = '.'.join(map(lambda x: x[1], filter(lambda i: i[0] not in positions, enumerate(series.name.split('.'))))) if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) for key in metaSeries.keys(): metaSeries[key] = aggregate(requestContext, metaSeries[key], func)[0] metaSeries[key].name = key return [ metaSeries[key] for key in keys ] aggregateWithWildcards.group = 'Combine' aggregateWithWildcards.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('func', ParamTypes.aggFunc, required=True, options=aggFuncNames), Param('position', ParamTypes.node, multiple=True), ] def diffSeries(requestContext, *seriesLists): """ Subtracts series 2 through n from series 1. Example: .. code-block:: none &target=diffSeries(service.connections.total,service.connections.failed) To diff a series and a constant, one should use offset instead of (or in addition to) diffSeries Example: .. code-block:: none &target=offset(service.connections.total,-5) &target=offset(diffSeries(service.connections.total,service.connections.failed),-4) This is an alias for :py:func:`aggregate ` with aggregation ``diff``. """ return aggregate(requestContext, seriesLists, 'diff') diffSeries.group = 'Combine' diffSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def averageSeries(requestContext, *seriesLists): """ Short Alias: avg() Takes one metric or a wildcard seriesList. Draws the average value of all metrics passed at each time. Example: .. code-block:: none &target=averageSeries(company.server.*.threads.busy) This is an alias for :py:func:`aggregate ` with aggregation ``average``. """ return aggregate(requestContext, seriesLists, 'average') averageSeries.group = 'Combine' averageSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def stddevSeries(requestContext, *seriesLists): """ Takes one metric or a wildcard seriesList. Draws the standard deviation of all metrics passed at each time. Example: .. code-block:: none &target=stddevSeries(company.server.*.threads.busy) This is an alias for :py:func:`aggregate ` with aggregation ``stddev``. """ return aggregate(requestContext, seriesLists, 'stddev') stddevSeries.group = 'Combine' stddevSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def minSeries(requestContext, *seriesLists): """ Takes one metric or a wildcard seriesList. For each datapoint from each metric passed in, pick the minimum value and graph it. Example: .. code-block:: none &target=minSeries(Server*.connections.total) This is an alias for :py:func:`aggregate ` with aggregation ``min``. """ return aggregate(requestContext, seriesLists, 'min') minSeries.group = 'Combine' minSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def maxSeries(requestContext, *seriesLists): """ Takes one metric or a wildcard seriesList. For each datapoint from each metric passed in, pick the maximum value and graph it. Example: .. code-block:: none &target=maxSeries(Server*.connections.total) This is an alias for :py:func:`aggregate ` with aggregation ``max``. """ return aggregate(requestContext, seriesLists, 'max') maxSeries.group = 'Combine' maxSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def rangeOfSeries(requestContext, *seriesLists): """ Takes a wildcard seriesList. Distills down a set of inputs into the range of the series Example: .. code-block:: none &target=rangeOfSeries(Server*.connections.total) This is an alias for :py:func:`aggregate ` with aggregation ``rangeOf``. """ return aggregate(requestContext, seriesLists, 'rangeOf') rangeOfSeries.group = 'Combine' rangeOfSeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def percentileOfSeries(requestContext, seriesList, n, interpolate=False): """ percentileOfSeries returns a single series which is composed of the n-percentile values taken across a wildcard series at each point. Unless `interpolate` is set to True, percentile values are actual values contained in one of the supplied series. """ if n <= 0: raise ValueError('The requested percent is required to be greater than 0') # if seriesList is empty then just short-circuit if not seriesList: return [] name = 'percentileOfSeries(%s,%g)' % (seriesList[0].pathExpression, n) (start, end, step) = normalize([seriesList])[1:] values = [ _getPercentile(row, n, interpolate) for row in izip_longest(*seriesList) ] resultSeries = TimeSeries(name, start, end, step, values, xFilesFactor=requestContext.get('xFilesFactor')) return [resultSeries] percentileOfSeries.group = 'Combine' percentileOfSeries.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), Param('interpolate', ParamTypes.boolean, default=False), ] def keepLastValue(requestContext, seriesList, limit = INF): """ Takes one metric or a wildcard seriesList, and optionally a limit to the number of 'None' values to skip over. Continues the line with the last received value when gaps ('None' values) appear in your data, rather than breaking your line. Example: .. code-block:: none &target=keepLastValue(Server01.connections.handled) &target=keepLastValue(Server01.connections.handled, 10) """ for series in seriesList: series.name = "keepLastValue(%s)" % (series.name) series.pathExpression = series.name consecutiveNones = 0 lastVal = None for i,value in enumerate(series): if value is None: consecutiveNones += 1 else: if 0 < consecutiveNones <= limit and lastVal is not None: # If a non-None value is seen before the limit of Nones is hit, # backfill all the missing datapoints with the last known value. for index in range(i - consecutiveNones, i): series[index] = lastVal consecutiveNones = 0 lastVal = value # If the series ends with some None values, try to backfill a bit to cover it. if 0 < consecutiveNones <= limit and lastVal is not None: for index in range(len(series) - consecutiveNones, len(series)): series[index] = lastVal return seriesList keepLastValue.group = 'Transform' keepLastValue.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('limit', ParamTypes.integer, default='INF'), ] def interpolate(requestContext, seriesList, limit = INF): """ Takes one metric or a wildcard seriesList, and optionally a limit to the number of 'None' values to skip over. Continues the line with the last received value when gaps ('None' values) appear in your data, rather than breaking your line. Example: .. code-block:: none &target=interpolate(Server01.connections.handled) &target=interpolate(Server01.connections.handled, 10) """ for series in seriesList: series.name = "interpolate(%s)" % (series.name) series.pathExpression = series.name consecutiveNones = 0 for i,value in enumerate(series): series[i] = value # No 'keeping' can be done on the first value because we have no idea # what came before it. if i == 0: continue if value is None: consecutiveNones += 1 elif consecutiveNones == 0: # have a value but no need to interpolate continue elif series[i - consecutiveNones - 1] is None: # have a value but can't interpolate: reset count consecutiveNones = 0 continue else: # have a value and can interpolate # If a non-None value is seen before the limit of Nones is hit, # backfill all the missing datapoints with the last known value. if 0 < consecutiveNones <= limit: for index in range(i - consecutiveNones, i): series[index] = series[i - consecutiveNones - 1] + (index - (i - consecutiveNones -1)) * (value - series[i - consecutiveNones - 1]) / (consecutiveNones + 1) consecutiveNones = 0 # If the series ends with some None values, try to backfill a bit to cover it. # if 0 < consecutiveNones < limit: # for index in xrange(len(series) - consecutiveNones, len(series)): # series[index] = series[len(series) - consecutiveNones - 1] return seriesList interpolate.group = 'Transform' interpolate.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('limit', ParamTypes.integer, default='INF'), ] def changed(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. Output 1 when the value changed, 0 when null or the same Example: .. code-block:: none &target=changed(Server01.connections.handled) """ for series in seriesList: series.name = "changed(%s)" % (series.name) series.pathExpression = series.name previous = None for i,value in enumerate(series): if previous is None: previous = value series[i] = 0 elif value is not None and previous != value: series[i] = 1 previous = value else: series[i] = 0 return seriesList changed.group = 'Special' changed.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def asPercent(requestContext, seriesList, total=None, *nodes): """ Calculates a percentage of the total of a wildcard series. If `total` is specified, each series will be calculated as a percentage of that total. If `total` is not specified, the sum of all points in the wildcard series will be used instead. A list of nodes can optionally be provided, if so they will be used to match series with their corresponding totals following the same logic as :py:func:`groupByNodes `. When passing `nodes` the `total` parameter may be a series list or `None`. If it is `None` then for each series in `seriesList` the percentage of the sum of series in that group will be returned. When not passing `nodes`, the `total` parameter may be a single series, reference the same number of series as `seriesList` or be a numeric value. Example: .. code-block:: none # Server01 connections failed and succeeded as a percentage of Server01 connections attempted &target=asPercent(Server01.connections.{failed,succeeded}, Server01.connections.attempted) # For each server, its connections failed as a percentage of its connections attempted &target=asPercent(Server*.connections.failed, Server*.connections.attempted) # For each server, its connections failed and succeeded as a percentage of its connections attemped &target=asPercent(Server*.connections.{failed,succeeded}, Server*.connections.attempted, 0) # apache01.threads.busy as a percentage of 1500 &target=asPercent(apache01.threads.busy,1500) # Server01 cpu stats as a percentage of its total &target=asPercent(Server01.cpu.*.jiffies) # cpu stats for each server as a percentage of its total &target=asPercent(Server*.cpu.*.jiffies, None, 0) When using `nodes`, any series or totals that can't be matched will create output series with names like ``asPercent(someSeries,MISSING)`` or ``asPercent(MISSING,someTotalSeries)`` and all values set to None. If desired these series can be filtered out by piping the result through ``|exclude("MISSING")`` as shown below: .. code-block:: none &target=asPercent(Server{1,2}.memory.used,Server{1,3}.memory.total,0) # will produce 3 output series: # asPercent(Server1.memory.used,Server1.memory.total) [values will be as expected] # asPercent(Server2.memory.used,MISSING) [all values will be None] # asPercent(MISSING,Server3.memory.total) [all values will be None] &target=asPercent(Server{1,2}.memory.used,Server{1,3}.memory.total,0)|exclude("MISSING") # will produce 1 output series: # asPercent(Server1.memory.used,Server1.memory.total) [values will be as expected] Each node may be an integer referencing a node in the series name or a string identifying a tag. .. note:: When `total` is a seriesList, specifying `nodes` to match series with the corresponding total series will increase reliability. """ normalize([seriesList]) xFilesFactor=requestContext.get('xFilesFactor') # if nodes are specified, use them to match series & total if nodes: keys = [] # group series together by key metaSeries = {} for series in seriesList: key = aggKey(series, nodes) if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) # make list of totals totalSeries = {} # no total seriesList was specified, sum the values for each group of series if total is None: for key in keys: if len(metaSeries[key]) == 1: totalSeries[key] = metaSeries[key][0] else: name = 'sumSeries(%s)' % formatPathExpressions(metaSeries[key]) (seriesList,start,end,step) = normalize([metaSeries[key]]) totalValues = [ safeSum(row) for row in izip_longest(*metaSeries[key]) ] totalSeries[key] = TimeSeries(name,start,end,step,totalValues,xFilesFactor=xFilesFactor) # total seriesList was specified, sum the values for each group of totals elif isinstance(total, list): for series in total: key = aggKey(series, nodes) if key not in totalSeries: totalSeries[key] = [series] if key not in keys: keys.append(key) else: totalSeries[key].append(series) for key in totalSeries.keys(): if len(totalSeries[key]) == 1: totalSeries[key] = totalSeries[key][0] else: name = 'sumSeries(%s)' % formatPathExpressions(totalSeries[key]) (seriesList,start,end,step) = normalize([totalSeries[key]]) totalValues = [ safeSum(row) for row in izip_longest(*totalSeries[key]) ] totalSeries[key] = TimeSeries(name,start,end,step,totalValues,xFilesFactor=xFilesFactor) # trying to use nodes with a total value, which isn't supported because it has no effect else: raise ValueError('total must be None or a seriesList') resultList = [] for key in keys: # no series, must have total only if key not in metaSeries: series2 = totalSeries[key] name = "asPercent(%s,%s)" % ('MISSING', series2.name) resultValues = [ None for v1 in series2 ] resultSeries = TimeSeries(name,start,end,step,resultValues,xFilesFactor=xFilesFactor) resultList.append(resultSeries) continue for series1 in metaSeries[key]: # no total if key not in totalSeries: name = "asPercent(%s,%s)" % (series1.name, 'MISSING') resultValues = [ None for v1 in series1 ] # series and total else: series2 = totalSeries[key] name = "asPercent(%s,%s)" % (series1.name, series2.name) (seriesList,start,end,step) = normalize([(series1, series2)]) resultValues = [ safeMul(safeDiv(v1, v2), 100.0) for v1,v2 in izip_longest(series1,series2) ] resultSeries = TimeSeries(name,start,end,step,resultValues,xFilesFactor=xFilesFactor) resultList.append(resultSeries) return resultList if total is None: totalValues = [ safeSum(row) for row in izip_longest(*seriesList) ] totalText = "sumSeries(%s)" % formatPathExpressions(seriesList) elif type(total) is list: if len(total) != 1 and len(total) != len(seriesList): raise ValueError("asPercent second argument must be missing, a single digit, reference exactly 1 series or reference the same number of series as the first argument") if len(total) == 1: normalize([seriesList, total]) totalValues = total[0] totalText = totalValues.name else: totalValues = [total] * len(seriesList[0]) totalText = str(total) resultList = [] if type(total) is list and len(total) == len(seriesList): for series1, series2 in matchSeries(seriesList, total): name = "asPercent(%s,%s)" % (series1.name,series2.name) (seriesList,start,end,step) = normalize([(series1, series2)]) resultValues = [ safeMul(safeDiv(v1, v2), 100.0) for v1,v2 in izip_longest(series1,series2) ] resultSeries = TimeSeries(name,start,end,step,resultValues,xFilesFactor=xFilesFactor) resultList.append(resultSeries) else: for series in seriesList: resultValues = [ safeMul(safeDiv(val, totalVal), 100.0) for val,totalVal in izip_longest(series,totalValues) ] name = "asPercent(%s,%s)" % (series.name, totalText or series.pathExpression) resultSeries = TimeSeries(name,series.start,series.end,series.step,resultValues,xFilesFactor=xFilesFactor) resultList.append(resultSeries) return resultList asPercent.group = 'Combine' asPercent.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('total', ParamTypes.seriesList), Param('nodes', ParamTypes.nodeOrTag, multiple=True), ] def divideSeriesLists(requestContext, dividendSeriesList, divisorSeriesList): """ Iterates over a two lists and divides list1[0] by list2[0], list1[1] by list2[1] and so on. The lists need to be the same length """ if len(dividendSeriesList) != len(divisorSeriesList): raise ValueError("dividendSeriesList and divisorSeriesList argument must have equal length") results = [] #for dividendSeries in dividendSeriesList: for i in range(0, len(dividendSeriesList)): dividendSeries = dividendSeriesList[i] divisorSeries = divisorSeriesList[i] name = "divideSeries(%s,%s)" % (dividendSeries.name, divisorSeries.name) bothSeries = (dividendSeries, divisorSeries) step = reduce(lcm,[s.step for s in bothSeries]) for s in bothSeries: s.consolidate( step // s.step ) start = min([s.start for s in bothSeries]) end = max([s.end for s in bothSeries]) end -= (end - start) % step values = ( safeDiv(v1,v2) for v1,v2 in izip_longest(*bothSeries) ) quotientSeries = TimeSeries(name, start, end, step, values, xFilesFactor=requestContext.get('xFilesFactor')) results.append(quotientSeries) return results divideSeriesLists.group = 'Combine' divideSeriesLists.params = [ Param('dividendSeriesList', ParamTypes.seriesList, required=True), Param('divisorSeriesList', ParamTypes.seriesList, required=True), ] def divideSeries(requestContext, dividendSeriesList, divisorSeries): """ Takes a dividend metric and a divisor metric and draws the division result. A constant may *not* be passed. To divide by a constant, use the scale() function (which is essentially a multiplication operation) and use the inverse of the dividend. (Division by 8 = multiplication by 1/8 or 0.125) Example: .. code-block:: none &target=divideSeries(Series.dividends,Series.divisors) """ if len(divisorSeries) == 0: for series in dividendSeriesList: series.name = "divideSeries(%s,MISSING)" % series.name series.pathExpression = series.name for i in range(len(series)): series[i] = None return dividendSeriesList if len(divisorSeries) > 1: raise ValueError("divideSeries second argument must reference exactly 1 series (got {0})".format(len(divisorSeries))) divisorSeries = divisorSeries[0] results = [] for dividendSeries in dividendSeriesList: name = "divideSeries(%s,%s)" % (dividendSeries.name, divisorSeries.name) bothSeries = (dividendSeries, divisorSeries) step = reduce(lcm,[s.step for s in bothSeries]) for s in bothSeries: s.consolidate( step // s.step ) start = min([s.start for s in bothSeries]) end = max([s.end for s in bothSeries]) end -= (end - start) % step values = ( safeDiv(v1,v2) for v1,v2 in izip_longest(*bothSeries) ) quotientSeries = TimeSeries(name, start, end, step, values, xFilesFactor=requestContext.get('xFilesFactor')) results.append(quotientSeries) return results divideSeries.group = 'Combine' divideSeries.params = [ Param('dividendSeriesList', ParamTypes.seriesList, required=True), Param('divisorSeries', ParamTypes.seriesList, required=True), ] def multiplySeries(requestContext, *seriesLists): """ Takes two or more series and multiplies their points. A constant may not be used. To multiply by a constant, use the scale() function. Example: .. code-block:: none &target=multiplySeries(Series.dividends,Series.divisors) This is an alias for :py:func:`aggregate ` with aggregation ``multiply``. """ # special handling for legacy multiplySeries behavior with a single series if len(seriesLists) == 1 and len(seriesLists[0]) == 1: return seriesLists[0] return aggregate(requestContext, seriesLists, 'multiply') multiplySeries.group = 'Combine' multiplySeries.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def weightedAverage(requestContext, seriesListAvg, seriesListWeight, *nodes): """ Takes a series of average values and a series of weights and produces a weighted average for all values. The corresponding values should share one or more zero-indexed nodes and/or tags. Example: .. code-block:: none &target=weightedAverage(*.transactions.mean,*.transactions.count,0) Each node may be an integer referencing a node in the series name or a string identifying a tag. """ sortedSeries={} for seriesAvg, seriesWeight in izip_longest(seriesListAvg , seriesListWeight): key = aggKey(seriesAvg, nodes) if key not in sortedSeries: sortedSeries[key]={} sortedSeries[key]['avg']=seriesAvg key = aggKey(seriesWeight, nodes) if key not in sortedSeries: sortedSeries[key]={} sortedSeries[key]['weight']=seriesWeight productList = [] for key in sortedSeries.keys(): if 'weight' not in sortedSeries[key]: continue if 'avg' not in sortedSeries[key]: continue seriesWeight = sortedSeries[key]['weight'] seriesAvg = sortedSeries[key]['avg'] productValues = [ safeMul(val1, val2) for val1,val2 in izip_longest(seriesAvg,seriesWeight) ] name='product(%s,%s)' % (seriesWeight.name, seriesAvg.name) productSeries = TimeSeries(name,seriesAvg.start,seriesAvg.end,seriesAvg.step,productValues,xFilesFactor=requestContext.get('xFilesFactor')) productList.append(productSeries) if not productList: return [] sumProducts=sumSeries(requestContext, productList)[0] sumWeights=sumSeries(requestContext, seriesListWeight)[0] resultValues = [ safeDiv(val1, val2) for val1,val2 in izip_longest(sumProducts,sumWeights) ] name = "weightedAverage(%s, %s, %s)" % (','.join(sorted(set(s.pathExpression for s in seriesListAvg))) ,','.join(sorted(set(s.pathExpression for s in seriesListWeight))), ','.join(map(str,nodes))) resultSeries = TimeSeries(name,sumProducts.start,sumProducts.end,sumProducts.step,resultValues,xFilesFactor=requestContext.get('xFilesFactor')) return [resultSeries] weightedAverage.group = 'Combine' weightedAverage.params = [ Param('seriesListAvg', ParamTypes.seriesList, required=True), Param('seriesListWeight', ParamTypes.seriesList, required=True), Param('nodes', ParamTypes.nodeOrTag, multiple=True), ] intOrIntervalSuggestions = [5, 7, 10, '1min', '5min', '10min', '30min', '1hour'] def movingWindow(requestContext, seriesList, windowSize, func='average', xFilesFactor=None): """ Graphs a moving window function of a metric (or metrics) over a fixed number of past points, or a time interval. Takes one metric or a wildcard seriesList, a number N of datapoints or a quoted string with a length of time like '1hour' or '5min' (See ``from / until`` in the render\_api_ for examples of time formats), a function to apply to the points in the window to produce the output, and an xFilesFactor value to specify how many points in the window must be non-null for the output to be considered valid. Graphs the output of the function for the preceeding datapoints for each point on the graph. Example: .. code-block:: none &target=movingWindow(Server.instance01.threads.busy,10) &target=movingWindow(Server.instance*.threads.idle,'5min','median',0.5) .. note:: `xFilesFactor` follows the same semantics as in Whisper storage schemas. Setting it to 0 (the default) means that only a single value in a given interval needs to be non-null, setting it to 1 means that all values in the interval must be non-null. A setting of 0.5 means that at least half the values in the interval must be non-null. """ if not seriesList: return [] if isinstance(windowSize, six.string_types): delta = parseTimeOffset(windowSize) previewSeconds = abs(delta.seconds + (delta.days * 86400)) else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) consolidateFunc = getAggFunc(func) # ignore original data and pull new, including our preview # data from earlier is needed to calculate the early results newContext = requestContext.copy() newContext['startTime'] = requestContext['startTime'] - timedelta(seconds=previewSeconds) previewList = evaluateTarget(newContext, requestContext['args'][0]) result = [] tagName = 'moving' + func.capitalize() for series in previewList: if isinstance(windowSize, six.string_types): newName = '%s(%s,"%s")' % (tagName, series.name, windowSize) windowPoints = previewSeconds // series.step else: newName = '%s(%s,%s)' % (tagName, series.name, windowSize) windowPoints = int(windowSize) series.tags[tagName] = windowSize newSeries = series.copy(name=newName, start=series.start + previewSeconds, values=[]) effectiveXFF = xFilesFactor if xFilesFactor is not None else series.xFilesFactor for i in range(windowPoints, len(series)): nonNull = [v for v in series[i - windowPoints:i] if v is not None] if nonNull and xff(len(nonNull), windowPoints, effectiveXFF): val = consolidateFunc(nonNull) else: val = None newSeries.append(val) result.append(newSeries) return result movingWindow.group = 'Calculate' movingWindow.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), Param('func', ParamTypes.string, options=aggFuncNames, default='average'), Param('xFilesFactor', ParamTypes.float), ] def exponentialMovingAverage(requestContext, seriesList, windowSize): """ Takes a series of values and a window size and produces an exponential moving average utilizing the following formula: .. code-block:: none ema(current) = constant * (Current Value) + (1 - constant) * ema(previous) The Constant is calculated as: .. code-block:: none constant = 2 / (windowSize + 1) The first period EMA uses a simple moving average for its value. Example: .. code-block:: none &target=exponentialMovingAverage(*.transactions.count, 10) &target=exponentialMovingAverage(*.transactions.count, '-10s') """ # EMA = C * (current_value) + (1 - C) + EMA # C = 2 / (windowSize + 1) # The following was copied from movingAverage, and altered for ema if not seriesList: return [] # set previewSeconds and constant based on windowSize string or integer if isinstance(windowSize, six.string_types): delta = parseTimeOffset(windowSize) previewSeconds = abs(delta.seconds + (delta.days * 86400)) constant = (float(2) / (int(previewSeconds) + 1)) else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) constant = (float(2) / (int(windowSize) + 1)) # ignore original data and pull new, including our preview # data from earlier is needed to calculate the early results newContext = requestContext.copy() newContext['startTime'] = requestContext['startTime'] - timedelta(seconds=previewSeconds) previewList = evaluateTarget(newContext, requestContext['args'][0]) result = [] for series in previewList: if isinstance(windowSize, six.string_types): newName = 'exponentialMovingAverage(%s,"%s")' % (series.name, windowSize) windowPoints = previewSeconds // series.step else: newName = "exponentialMovingAverage(%s,%s)" % (series.name, windowSize) windowPoints = int(windowSize) series.tags['exponentialMovingAverage'] = windowSize newSeries = series.copy(name=newName, start=series.start + previewSeconds, values=[]) ema = safeAvg(series[:windowPoints]) or 0 newSeries.append(round(ema, 6)) for i in range(windowPoints, len(series)): if series[i] is not None: ema = constant * series[i] + (1 - constant) * ema newSeries.append(round(ema, 6)) else: newSeries.append(None) result.append(newSeries) return result exponentialMovingAverage.group = 'Calculate' exponentialMovingAverage.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), ] def movingMedian(requestContext, seriesList, windowSize, xFilesFactor=None): """ Graphs the moving median of a metric (or metrics) over a fixed number of past points, or a time interval. Takes one metric or a wildcard seriesList followed by a number N of datapoints or a quoted string with a length of time like '1hour' or '5min' (See ``from / until`` in the render\_api_ for examples of time formats), and an xFilesFactor value to specify how many points in the window must be non-null for the output to be considered valid. Graphs the median of the preceeding datapoints for each point on the graph. Example: .. code-block:: none &target=movingMedian(Server.instance01.threads.busy,10) &target=movingMedian(Server.instance*.threads.idle,'5min') """ return movingWindow(requestContext, seriesList, windowSize, 'median', xFilesFactor) movingMedian.group = 'Calculate' movingMedian.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), Param('xFilesFactor', ParamTypes.float), ] def scale(requestContext, seriesList, factor): """ Takes one metric or a wildcard seriesList followed by a constant, and multiplies the datapoint by the constant provided at each point. Example: .. code-block:: none &target=scale(Server.instance01.threads.busy,10) &target=scale(Server.instance*.threads.busy,10) """ for series in seriesList: series.tags['scale'] = factor series.name = "scale(%s,%g)" % (series.name,float(factor)) series.pathExpression = series.name for i,value in enumerate(series): series[i] = safeMul(value,factor) return seriesList scale.group = 'Transform' scale.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('factor', ParamTypes.float, required=True), ] def scaleToSeconds(requestContext, seriesList, seconds): """ Takes one metric or a wildcard seriesList and returns "value per seconds" where seconds is a last argument to this functions. Useful in conjunction with derivative or integral function if you want to normalize its result to a known resolution for arbitrary retentions """ for series in seriesList: series.tags['scaleToSeconds'] = seconds series.name = "scaleToSeconds(%s,%d)" % (series.name,seconds) series.pathExpression = series.name factor = seconds / series.step for i,value in enumerate(series): if value is not None: # Division long by float cause OverflowError try: series[i] = round(value * factor, 6) except OverflowError: series[i] = (value // series.step) * seconds return seriesList scaleToSeconds.group = 'Transform' scaleToSeconds.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('seconds', ParamTypes.integer, required=True), ] def pow(requestContext, seriesList, factor): """ Takes one metric or a wildcard seriesList followed by a constant, and raises the datapoint by the power of the constant provided at each point. Example: .. code-block:: none &target=pow(Server.instance01.threads.busy,10) &target=pow(Server.instance*.threads.busy,10) """ for series in seriesList: series.tags['pow'] = factor series.name = "pow(%s,%g)" % (series.name, float(factor)) series.pathExpression = series.name for i,value in enumerate(series): series[i] = safePow(value, factor) return seriesList pow.group = 'Transform' pow.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('factor', ParamTypes.float, required=True), ] def powSeries(requestContext, *seriesLists): """ Takes two or more series and pows their points. A constant line may be used. Example: .. code-block:: none &target=powSeries(Server.instance01.app.requests, Server.instance01.app.replies) """ try: (seriesList, start, end, step) = normalize(seriesLists) except NormalizeEmptyResultError: return [] name = "powSeries(%s)" % ','.join([s.name for s in seriesList]) values = [] for row in izip_longest(*seriesList): first = True tmpVal = None for element in row: # If it is a first iteration - tmpVal needs to be element if first: tmpVal = element first = False else: tmpVal = safePow(tmpVal, element) values.append(tmpVal) series = TimeSeries(name,start,end,step,values,xFilesFactor=requestContext.get('xFilesFactor')) return [series] powSeries.group = 'Transform' powSeries.params = [ Param('seriesList', ParamTypes.seriesList, required=True, multiple=True), ] def squareRoot(requestContext, seriesList): """ Takes one metric or a wildcard seriesList, and computes the square root of each datapoint. Example: .. code-block:: none &target=squareRoot(Server.instance01.threads.busy) """ for series in seriesList: series.tags['squareRoot'] = 1 series.name = "squareRoot(%s)" % (series.name) series.pathExpression = series.name for i,value in enumerate(series): series[i] = safePow(value, 0.5) return seriesList squareRoot.group = 'Transform' squareRoot.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def invert(requestContext, seriesList): """ Takes one metric or a wildcard seriesList, and inverts each datapoint (i.e. 1/x). Example: .. code-block:: none &target=invert(Server.instance01.threads.busy) """ for series in seriesList: series.tags['invert'] = 1 series.name = "invert(%s)" % (series.name) series.pathExpression = series.name for i,value in enumerate(series): series[i] = safePow(value, -1) return seriesList invert.group = 'Transform' invert.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def absolute(requestContext, seriesList): """ Takes one metric or a wildcard seriesList and applies the mathematical abs function to each datapoint transforming it to its absolute value. Example: .. code-block:: none &target=absolute(Server.instance01.threads.busy) &target=absolute(Server.instance*.threads.busy) """ for series in seriesList: series.tags['absolute'] = 1 series.name = "absolute(%s)" % (series.name) series.pathExpression = series.name for i,value in enumerate(series): series[i] = safeAbs(value) return seriesList absolute.group = 'Transform' absolute.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def offset(requestContext, seriesList, factor): """ Takes one metric or a wildcard seriesList followed by a constant, and adds the constant to each datapoint. Example: .. code-block:: none &target=offset(Server.instance01.threads.busy,10) """ for series in seriesList: series.tags['offset'] = factor series.name = "offset(%s,%g)" % (series.name,float(factor)) series.pathExpression = series.name for i,value in enumerate(series): if value is not None: series[i] = value + factor return seriesList offset.group = 'Transform' offset.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('factor', ParamTypes.float, required=True), ] def offsetToZero(requestContext, seriesList): """ Offsets a metric or wildcard seriesList by subtracting the minimum value in the series from each datapoint. Useful to compare different series where the values in each series may be higher or lower on average but you're only interested in the relative difference. An example use case is for comparing different round trip time results. When measuring RTT (like pinging a server), different devices may come back with consistently different results due to network latency which will be different depending on how many network hops between the probe and the device. To compare different devices in the same graph, the network latency to each has to be factored out of the results. This is a shortcut that takes the fastest response (lowest number in the series) and sets that to zero and then offsets all of the other datapoints in that series by that amount. This makes the assumption that the lowest response is the fastest the device can respond, of course the more datapoints that are in the series the more accurate this assumption is. Example: .. code-block:: none &target=offsetToZero(Server.instance01.responseTime) &target=offsetToZero(Server.instance*.responseTime) """ for series in seriesList: minimum = safeMin(series) series.tags['offsetToZero'] = minimum series.name = "offsetToZero(%s)" % (series.name) series.pathExpression = series.name for i,value in enumerate(series): if value is not None: series[i] = value - minimum return seriesList offsetToZero.group = 'Transform' offsetToZero.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def roundFunction(requestContext, seriesList, precision=None): """ Takes one metric or a wildcard seriesList optionally followed by a precision, and rounds each datapoint to the specified precision. Example: .. code-block:: none &target=round(Server.instance01.threads.busy) &target=round(Server.instance01.threads.busy,2) """ for series in seriesList: series.tags['round'] = precision or 0 if precision is None: series.name = "round(%s)" % (series.name) else: series.name = "round(%s,%g)" % (series.name,int(precision)) series.pathExpression = series.name for i,value in enumerate(series): if value is not None: series[i] = round(value, precision or 0) return seriesList roundFunction.group = 'Transform' roundFunction.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('precision', ParamTypes.integer, default=0), ] def movingAverage(requestContext, seriesList, windowSize, xFilesFactor=None): """ Graphs the moving average of a metric (or metrics) over a fixed number of past points, or a time interval. Takes one metric or a wildcard seriesList followed by a number N of datapoints or a quoted string with a length of time like '1hour' or '5min' (See ``from / until`` in the render\_api_ for examples of time formats), and an xFilesFactor value to specify how many points in the window must be non-null for the output to be considered valid. Graphs the average of the preceeding datapoints for each point on the graph. Example: .. code-block:: none &target=movingAverage(Server.instance01.threads.busy,10) &target=movingAverage(Server.instance*.threads.idle,'5min') """ return movingWindow(requestContext, seriesList, windowSize, 'average', xFilesFactor) movingAverage.group = 'Calculate' movingAverage.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), Param('xFilesFactor', ParamTypes.float), ] def movingSum(requestContext, seriesList, windowSize, xFilesFactor=None): """ Graphs the moving sum of a metric (or metrics) over a fixed number of past points, or a time interval. Takes one metric or a wildcard seriesList followed by a number N of datapoints or a quoted string with a length of time like '1hour' or '5min' (See ``from / until`` in the render\_api_ for examples of time formats), and an xFilesFactor value to specify how many points in the window must be non-null for the output to be considered valid. Graphs the sum of the preceeding datapoints for each point on the graph. Example: .. code-block:: none &target=movingSum(Server.instance01.requests,10) &target=movingSum(Server.instance*.errors,'5min') """ return movingWindow(requestContext, seriesList, windowSize, 'sum', xFilesFactor) movingSum.group = 'Calculate' movingSum.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), Param('xFilesFactor', ParamTypes.float), ] def movingMin(requestContext, seriesList, windowSize, xFilesFactor=None): """ Graphs the moving minimum of a metric (or metrics) over a fixed number of past points, or a time interval. Takes one metric or a wildcard seriesList followed by a number N of datapoints or a quoted string with a length of time like '1hour' or '5min' (See ``from / until`` in the render\_api_ for examples of time formats), and an xFilesFactor value to specify how many points in the window must be non-null for the output to be considered valid. Graphs the minimum of the preceeding datapoints for each point on the graph. Example: .. code-block:: none &target=movingMin(Server.instance01.requests,10) &target=movingMin(Server.instance*.errors,'5min') """ return movingWindow(requestContext, seriesList, windowSize, 'min', xFilesFactor) movingMin.group = 'Calculate' movingMin.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), Param('xFilesFactor', ParamTypes.float), ] def movingMax(requestContext, seriesList, windowSize, xFilesFactor=None): """ Graphs the moving maximum of a metric (or metrics) over a fixed number of past points, or a time interval. Takes one metric or a wildcard seriesList followed by a number N of datapoints or a quoted string with a length of time like '1hour' or '5min' (See ``from / until`` in the render\_api_ for examples of time formats), and an xFilesFactor value to specify how many points in the window must be non-null for the output to be considered valid. Graphs the maximum of the preceeding datapoints for each point on the graph. Example: .. code-block:: none &target=movingMax(Server.instance01.requests,10) &target=movingMax(Server.instance*.errors,'5min') """ return movingWindow(requestContext, seriesList, windowSize, 'max', xFilesFactor) movingMax.group = 'Calculate' movingMax.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('windowSize', ParamTypes.intOrInterval, required=True, suggestions=intOrIntervalSuggestions), Param('xFilesFactor', ParamTypes.float), ] def cumulative(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. When a graph is drawn where width of the graph size in pixels is smaller than the number of datapoints to be graphed, Graphite consolidates the values to to prevent line overlap. The cumulative() function changes the consolidation function from the default of 'average' to 'sum'. This is especially useful in sales graphs, where fractional values make no sense and a 'sum' of consolidated values is appropriate. Alias for :func:`consolidateBy(series, 'sum') ` .. code-block:: none &target=cumulative(Sales.widgets.largeBlue) """ return consolidateBy(requestContext, seriesList, 'sum') cumulative.group = 'Special' cumulative.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def consolidateBy(requestContext, seriesList, consolidationFunc): """ Takes one metric or a wildcard seriesList and a consolidation function name. Valid function names are 'sum', 'average', 'min', 'max', 'first' & 'last'. When a graph is drawn where width of the graph size in pixels is smaller than the number of datapoints to be graphed, Graphite consolidates the values to to prevent line overlap. The consolidateBy() function changes the consolidation function from the default of 'average' to one of 'sum', 'max', 'min', 'first', or 'last'. This is especially useful in sales graphs, where fractional values make no sense and a 'sum' of consolidated values is appropriate. .. code-block:: none &target=consolidateBy(Sales.widgets.largeBlue, 'sum') &target=consolidateBy(Servers.web01.sda1.free_space, 'max') """ for series in seriesList: # datalib will throw an exception, so it's not necessary to validate here series.consolidationFunc = consolidationFunc series.tags['consolidateBy'] = consolidationFunc series.name = 'consolidateBy(%s,"%s")' % (series.name, series.consolidationFunc) series.pathExpression = series.name return seriesList consolidateBy.group = 'Special' consolidateBy.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('consolidationFunc', ParamTypes.string, required=True, options=['sum', 'average', 'min', 'max', 'first', 'last']), ] def setXFilesFactor(requestContext, seriesList, xFilesFactor): """ Short form: xFilesFactor() Takes one metric or a wildcard seriesList and an xFilesFactor value between 0 and 1 When a series needs to be consolidated, this sets the fraction of values in an interval that must not be null for the consolidation to be considered valid. If there are not enough values then None will be returned for that interval. .. code-block:: none &target=xFilesFactor(Sales.widgets.largeBlue, 0.5) &target=Servers.web01.sda1.free_space|consolidateBy('max')|xFilesFactor(0.5) The `xFilesFactor` set via this function is used as the default for all functions that accept an `xFilesFactor` parameter, all functions that aggregate data across multiple series and/or intervals, and `maxDataPoints `_ consolidation. A default for the entire render request can also be set using the `xFilesFactor `_ query parameter. .. note:: `xFilesFactor` follows the same semantics as in Whisper storage schemas. Setting it to 0 (the default) means that only a single value in a given interval needs to be non-null, setting it to 1 means that all values in the interval must be non-null. A setting of 0.5 means that at least half the values in the interval must be non-null. """ requestContext['xFilesFactor'] = xFilesFactor for series in seriesList: series.xFilesFactor = xFilesFactor series.tags['xFilesFactor'] = xFilesFactor return seriesList setXFilesFactor.group = 'Special' setXFilesFactor.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('xFilesFactor', ParamTypes.float, required=True), ] def derivative(requestContext, seriesList): """ This is the opposite of the integral function. This is useful for taking a running total metric and calculating the delta between subsequent data points. This function does not normalize for periods of time, as a true derivative would. Instead see the perSecond() function to calculate a rate of change over time. Example: .. code-block:: none &target=derivative(company.server.application01.ifconfig.TXPackets) Each time you run ifconfig, the RX and TXPackets are higher (assuming there is network traffic.) By applying the derivative function, you can get an idea of the packets per minute sent or received, even though you're only recording the total. """ results = [] for series in seriesList: newValues = [] prev = None for val in series: if None in (prev,val): newValues.append(None) prev = val continue newValues.append(val - prev) prev = val series.tags['derivative'] = 1 newName = "derivative(%s)" % series.name newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results derivative.group = 'Transform' derivative.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def perSecond(requestContext, seriesList, maxValue=None): """ NonNegativeDerivative adjusted for the series time interval This is useful for taking a running total metric and showing how many requests per second were handled. Example: .. code-block:: none &target=perSecond(company.server.application01.ifconfig.TXPackets) Each time you run ifconfig, the RX and TXPackets are higher (assuming there is network traffic.) By applying the perSecond function, you can get an idea of the packets per second sent or received, even though you're only recording the total. """ results = [] for series in seriesList: newValues = [] prev = None step = series.step for val in series: delta, prev = _nonNegativeDelta(val, prev, maxValue) if delta is not None: # Division long by float cause OverflowError try: newValues.append(round(delta / step, 6)) except OverflowError: newValues.append(delta // step) else: newValues.append(None) series.tags['perSecond'] = 1 newName = "perSecond(%s)" % series.name newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results perSecond.group = 'Transform' perSecond.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('maxValue', ParamTypes.float), ] def delay(requestContext, seriesList, steps): """ This shifts all samples later by an integer number of steps. This can be used for custom derivative calculations, among other things. Note: this will pad the early end of the data with None for every step shifted. This complements other time-displacement functions such as timeShift and timeSlice, in that this function is indifferent about the step intervals being shifted. Example: .. code-block:: none &target=divideSeries(server.FreeSpace,delay(server.FreeSpace,1)) This computes the change in server free space as a percentage of the previous free space. """ results = [] for series in seriesList: if steps < 0: newValues = series[-steps:] + [None] * min(-steps, len(series)) else: newValues = [None] * min(steps, len(series)) + series[:-steps] series.tags['delay'] = steps newName = "delay(%s,%d)" % (series.name, steps) newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results delay.group = 'Transform' delay.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('steps', ParamTypes.integer, required=True), ] def integral(requestContext, seriesList): """ This will show the sum over time, sort of like a continuous addition function. Useful for finding totals or trends in metrics that are collected per minute. Example: .. code-block:: none &target=integral(company.sales.perMinute) This would start at zero on the left side of the graph, adding the sales each minute, and show the total sales for the time period selected at the right side, (time now, or the time specified by '&until='). """ results = [] for series in seriesList: newValues = [] current = 0.0 for val in series: if val is None: newValues.append(None) else: current += val newValues.append(current) series.tags['integral'] = 1 newName = "integral(%s)" % series.name newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results integral.group = 'Transform' integral.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def integralByInterval(requestContext, seriesList, intervalUnit): """ This will do the same as integral() funcion, except resetting the total to 0 at the given time in the parameter "from" Useful for finding totals per hour/day/week/.. Example: .. code-block:: none &target=integralByInterval(company.sales.perMinute, "1d")&from=midnight-10days This would start at zero on the left side of the graph, adding the sales each minute, and show the evolution of sales per day during the last 10 days. """ intervalDuration = int(abs(deltaseconds(parseTimeOffset(intervalUnit)))) startTime = int(timestamp(requestContext['startTime'])) results = [] for series in seriesList: newValues = [] currentTime = series.start # current time within series iteration current = 0.0 # current accumulated value for val in series: # reset integral value if crossing an interval boundary if (currentTime - startTime)//intervalDuration != (currentTime - startTime - series.step)//intervalDuration: current = 0.0 if val is None: # keep previous value since val can be None when resetting current to 0.0 newValues.append(current) else: current += val newValues.append(current) currentTime += series.step series.tags['integralByInterval'] = intervalUnit newName = "integralByInterval(%s,'%s')" % (series.name, intervalUnit) newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results integralByInterval.group = 'Transform' integralByInterval.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('intervalUnit', ParamTypes.string, required=True), ] def nonNegativeDerivative(requestContext, seriesList, maxValue=None): """ Same as the derivative function above, but ignores datapoints that trend down. Useful for counters that increase for a long time, then wrap or reset. (Such as if a network interface is destroyed and recreated by unloading and re-loading a kernel module, common with USB / WiFi cards. Example: .. code-block:: none &target=nonNegativederivative(company.server.application01.ifconfig.TXPackets) """ results = [] for series in seriesList: newValues = [] prev = None for val in series: delta, prev = _nonNegativeDelta(val, prev, maxValue) newValues.append(delta) series.tags['nonNegativeDerivative'] = 1 newName = "nonNegativeDerivative(%s)" % series.name newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results nonNegativeDerivative.group = 'Transform' nonNegativeDerivative.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('maxValue', ParamTypes.float), ] def _nonNegativeDelta(val, prev, maxValue): # ignore values larger than maxValue if maxValue is not None and val > maxValue: return None, None # first reading if None in (prev, val): return None, val # counter increased, use the difference if val >= prev: return val - prev, val # counter wrapped and we have maxValue # calculate delta based on maxValue + 1 + val - prev if maxValue is not None: return maxValue + 1 + val - prev, val # counter wrapped or reset and we don't have maxValue # just use None return None, val def stacked(requestContext,seriesLists,stackName='__DEFAULT__'): """ Takes one metric or a wildcard seriesList and change them so they are stacked. This is a way of stacking just a couple of metrics without having to use the stacked area mode (that stacks everything). By means of this a mixed stacked and non stacked graph can be made It can also take an optional argument with a name of the stack, in case there is more than one, e.g. for input and output metrics. Example: .. code-block:: none &target=stacked(company.server.application01.ifconfig.TXPackets, 'tx') """ if 'totalStack' in requestContext: totalStack = requestContext['totalStack'].get(stackName, []) else: requestContext['totalStack'] = {} totalStack = []; results = [] for series in seriesLists: newValues = [] for i in range(len(series)): if len(totalStack) <= i: totalStack.append(0) if series[i] is not None: totalStack[i] += series[i] newValues.append(totalStack[i]) else: newValues.append(None) # Work-around for the case when legend is set if stackName=='__DEFAULT__': series.tags['stacked'] = stackName newName = "stacked(%s)" % series.name else: newName = series.name newSeries = series.copy(name=newName, values=newValues) newSeries.options['stacked'] = True results.append(newSeries) requestContext['totalStack'][stackName] = totalStack return results stacked.group = 'Graph' stacked.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('stack', ParamTypes.string), ] def areaBetween(requestContext, seriesList): """ Draws the vertical area in between the two series in seriesList. Useful for visualizing a range such as the minimum and maximum latency for a service. areaBetween expects **exactly one argument** that results in exactly two series (see example below). The order of the lower and higher values series does not matter. The visualization only works when used in conjunction with ``areaMode=stacked``. Most likely use case is to provide a band within which another metric should move. In such case applying an ``alpha()``, as in the second example, gives best visual results. Example: .. code-block:: none &target=areaBetween(service.latency.{min,max})&areaMode=stacked &target=alpha(areaBetween(service.latency.{min,max}),0.3)&areaMode=stacked If for instance, you need to build a seriesList, you should use the ``group`` function, like so: .. code-block:: none &target=areaBetween(group(minSeries(a.*.min),maxSeries(a.*.max))) """ assert len(seriesList) == 2, "areaBetween series argument must reference *exactly* 2 series" lower = seriesList[0] upper = seriesList[1] lower.options['stacked'] = True lower.options['invisible'] = True upper.options['stacked'] = True upper.tags['areaBetween'] = 1 lower.tags = upper.tags lower.name = upper.name = "areaBetween(%s)" % upper.pathExpression return seriesList areaBetween.group = 'Graph' areaBetween.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def aliasSub(requestContext, seriesList, search, replace): """ Runs series names through a regex search/replace. .. code-block:: none &target=aliasSub(ip.*TCP*,"^.*TCP(\d+)","\\1") """ try: seriesList.name = re.sub(search, replace, seriesList.name) except AttributeError: for series in seriesList: series.name = re.sub(search, replace, series.name) return seriesList aliasSub.group = 'Alias' aliasSub.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('search', ParamTypes.string, required=True), Param('replace', ParamTypes.string, required=True), ] def aliasQuery(requestContext, seriesList, search, replace, newName): """ Performs a query to alias the metrics in seriesList. .. code-block:: none &target=aliasQuery(channel.power.*,"channel\.power\.([0-9]+)","channel.frequency.\\1", "Channel %d MHz") The series in seriesList will be aliased by first translating the series names using the search & replace parameters, then using the last value of the resulting series to construct the alias using sprintf-style syntax. """ for series in seriesList: newQuery = re.sub(search, replace, series.name) newSeriesList = evaluateTarget(requestContext, newQuery) if newSeriesList is None or len(newSeriesList) == 0: raise Exception('No series found with query: ' + newQuery) current = safeLast(newSeriesList[0]) if current is None: raise Exception('Cannot get last value of series: ' + newSeriesList[0]) series.name = newName % current return seriesList aliasQuery.group = 'Alias' aliasQuery.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('search', ParamTypes.string, required=True), Param('replace', ParamTypes.string, required=True), Param('newName', ParamTypes.string, required=True), ] def alias(requestContext, seriesList, newName): """ Takes one metric or a wildcard seriesList and a string in quotes. Prints the string instead of the metric name in the legend. .. code-block:: none &target=alias(Sales.widgets.largeBlue,"Large Blue Widgets") """ try: seriesList.name = newName except AttributeError: for series in seriesList: series.name = newName return seriesList alias.group = 'Alias' alias.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('newName', ParamTypes.string, required=True), ] def cactiStyle(requestContext, seriesList, system=None, units=None): """ Takes a series list and modifies the aliases to provide column aligned output with Current, Max, and Min values in the style of cacti. Optionally takes a "system" value to apply unit formatting in the same style as the Y-axis, or a "unit" string to append an arbitrary unit suffix. .. code-block:: none &target=cactiStyle(ganglia.*.net.bytes_out,"si") &target=cactiStyle(ganglia.*.net.bytes_out,"si","b") A possible value for ``system`` is ``si``, which would express your values in multiples of a thousand. A second option is to use ``binary`` which will instead express your values in multiples of 1024 (useful for network devices). Column alignment of the Current, Max, Min values works under two conditions: you use a monospace font such as terminus and use a single cactiStyle call, as separate cactiStyle calls are not aware of each other. In case you have different targets for which you would like to have cactiStyle to line up, you can use ``group()`` to combine them before applying cactiStyle, such as: .. code-block:: none &target=cactiStyle(group(metricA,metricB)) """ if 0 == len(seriesList): return seriesList if system: if units: fmt = lambda x:"%.2f %s" % format_units(x,system=system,units=units) else: fmt = lambda x:"%.2f%s" % format_units(x,system=system) else: if units: fmt = lambda x:"%.2f %s"%(x,units) else: fmt = lambda x:"%.2f"%x nameLen = max([0] + [len(getattr(series,"name")) for series in seriesList]) lastLen = max([0] + [len(fmt(int(safeLast(series) or 3))) for series in seriesList]) + 3 maxLen = max([0] + [len(fmt(int(safeMax(series) or 3))) for series in seriesList]) + 3 minLen = max([0] + [len(fmt(int(safeMin(series) or 3))) for series in seriesList]) + 3 for series in seriesList: last = safeLast(series) maximum = safeMax(series) minimum = safeMin(series) if last is None: last = NAN else: last = fmt(float(last)) if maximum is None: maximum = NAN else: maximum = fmt(float(maximum)) if minimum is None: minimum = NAN else: minimum = fmt(float(minimum)) series.name = "%*s Current:%*s Max:%*s Min:%*s " % \ (-nameLen, series.name, -lastLen, last, -maxLen, maximum, -minLen, minimum) return seriesList cactiStyle.group = 'Special' cactiStyle.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('system', ParamTypes.string, options=['si', 'binary']), Param('units', ParamTypes.string), ] def _getFirstPathExpression(name): """Returns the first metric path in an expression.""" tokens = grammar.parseString(name) pathExpression = None while pathExpression is None: if tokens.pathExpression: pathExpression = tokens.pathExpression elif tokens.expression: tokens = tokens.expression elif tokens.call: tokens = tokens.call.args[0] else: break return pathExpression def aliasByNode(requestContext, seriesList, *nodes): """ Takes a seriesList and applies an alias derived from one or more "node" portion/s of the target name or tags. Node indices are 0 indexed. .. code-block:: none &target=aliasByNode(ganglia.*.cpu.load5,1) Each node may be an integer referencing a node in the series name or a string identifying a tag. .. code-block:: none &target=seriesByTag("name=~cpu.load.*", "server=~server[1-9]+", "datacenter=dc1")|aliasByNode("datacenter", "server", 1) # will produce output series like # dc1.server1.load5, dc1.server2.load5, dc1.server1.load10, dc1.server2.load10 """ for series in seriesList: series.name = aggKey(series, nodes) return seriesList aliasByNode.group = 'Alias' aliasByNode.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('nodes', ParamTypes.nodeOrTag, required=True, multiple=True), ] def aliasByMetric(requestContext, seriesList): """ Takes a seriesList and applies an alias derived from the base metric name. .. code-block:: none &target=aliasByMetric(carbon.agents.graphite.creates) """ return substr(requestContext, seriesList, -1, 0) aliasByMetric.group = 'Alias' aliasByMetric.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def legendValue(requestContext, seriesList, *valueTypes): """ Takes one metric or a wildcard seriesList and a string in quotes. Appends a value to the metric name in the legend. Currently one or several of: `last`, `avg`, `total`, `min`, `max`. The last argument can be `si` (default) or `binary`, in that case values will be formatted in the corresponding system. .. code-block:: none &target=legendValue(Sales.widgets.largeBlue, 'avg', 'max', 'si') """ system = None if valueTypes[-1] in ('si', 'binary'): system = valueTypes[-1] valueTypes = valueTypes[:-1] for valueType in valueTypes: if valueType in aggFuncs: valueFunc = aggFuncs[valueType] else: valueFunc = aggFuncAliases.get(valueType, lambda s: '(?)') if system is None: for series in seriesList: series.name += " (%s: %s)" % (valueType, valueFunc(series)) else: for series in seriesList: value = valueFunc(series) formatted = None if isinstance(value, six.string_types): formatted = value elif value is not None: formatted = "%.2f%s" % format_units(value, system=system) series.name = "%-20s%-5s%-10s" % (series.name, valueType, formatted) return seriesList legendValue.group = 'Alias' legendValue.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('valuesTypes', ParamTypes.string, multiple=True, options=aggFuncNames + ['si', 'binary']), ] def alpha(requestContext, seriesList, alpha): """ Assigns the given alpha transparency setting to the series. Takes a float value between 0 and 1. """ for series in seriesList: series.options['alpha'] = alpha return seriesList alpha.group = 'Graph' alpha.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('alpha', ParamTypes.float, required=True), ] def color(requestContext, seriesList, theColor): """ Assigns the given color to the seriesList Example: .. code-block:: none &target=color(collectd.hostname.cpu.0.user, 'green') &target=color(collectd.hostname.cpu.0.system, 'ff0000') &target=color(collectd.hostname.cpu.0.idle, 'gray') &target=color(collectd.hostname.cpu.0.idle, '6464ffaa') """ for series in seriesList: series.color = theColor return seriesList color.group = 'Graph' color.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('theColor', ParamTypes.string, required=True), ] def substr(requestContext, seriesList, start=0, stop=0): """ Takes one metric or a wildcard seriesList followed by 1 or 2 integers. Assume that the metric name is a list or array, with each element separated by dots. Prints n - length elements of the array (if only one integer n is passed) or n - m elements of the array (if two integers n and m are passed). The list starts with element 0 and ends with element (length - 1). Example: .. code-block:: none &target=substr(carbon.agents.hostname.avgUpdateTime,2,4) The label would be printed as "hostname.avgUpdateTime". """ for series in seriesList: left = series.name.rfind('(') + 1 right = series.name.find(')') if right < 0: right = len(series.name)+1 cleanName = series.name[left:right:] if int(stop) == 0: series.name = '.'.join(cleanName.split('.')[int(start)::]) else: series.name = '.'.join(cleanName.split('.')[int(start):int(stop):]) # substr(func(a.b,'c'),1) becomes b instead of b,'c' series.name = re.sub(',.*$', '', series.name) return seriesList substr.group = 'Special' substr.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('start', ParamTypes.node, default=0), Param('stop', ParamTypes.node, default=0), ] def logarithm(requestContext, seriesList, base=10): """ Takes one metric or a wildcard seriesList, a base, and draws the y-axis in logarithmic format. If base is omitted, the function defaults to base 10. Example: .. code-block:: none &target=log(carbon.agents.hostname.avgUpdateTime,2) """ results = [] for series in seriesList: newValues = [] for val in series: if val is None: newValues.append(None) elif val <= 0: newValues.append(None) else: newValues.append(math.log(val, base)) series.tags['log'] = base newName = "log(%s, %s)" % (series.name, base) newSeries = series.copy(name=newName, values=newValues) results.append(newSeries) return results logarithm.group = 'Transform' logarithm.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('base', ParamTypes.integer, default=10), ] operatorFuncs = { '=': (lambda val, threshold: val is not None and val == threshold), '!=': (lambda val, threshold: val is None or val != threshold), '>': (lambda val, threshold: val is not None and val > threshold), '>=': (lambda val, threshold: val is not None and val >= threshold), '<': (lambda val, threshold: val is None or val < threshold), '<=': (lambda val, threshold: val is None or val <= threshold), } def filterSeries(requestContext, seriesList, func, operator, threshold): """ Takes one metric or a wildcard seriesList followed by a consolidation function, an operator and a threshold. Draws only the metrics which match the filter expression. Example: .. code-block:: none &target=filterSeries(system.interface.eth*.packetsSent, 'max', '>', 1000) This would only display interfaces which has a peak throughput higher than 1000 packets/min. Supported aggregation functions: ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``range``, ``multiply`` & ``last``. Supported operators: ``=``, ``!=``, ``>``, ``>=``, ``<`` & ``<=``. """ consolidationFunc = getAggFunc(func) if operator not in operatorFuncs: raise Exception('Unsupported operator: %s' % (operator)) operatorFunc = operatorFuncs[operator] # if seriesList is empty then just short-circuit if not seriesList: return [] return [series for series in seriesList if operatorFunc(consolidationFunc(series), threshold)] filterSeries.group = 'Filter Series' filterSeries.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('func', ParamTypes.string, required=True, options=aggFuncNames), Param('operator', ParamTypes.string, required=True, options=sorted(operatorFuncs.keys())), Param('threshold', ParamTypes.float, required=True), ] def maximumAbove(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by a constant n. Draws only the metrics with a maximum value above n. Example: .. code-block:: none &target=maximumAbove(system.interface.eth*.packetsSent,1000) This would only display interfaces which sent more than 1000 packets/min. """ return filterSeries(requestContext, seriesList, 'max', '>', n) maximumAbove.group = 'Filter Series' maximumAbove.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def minimumAbove(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by a constant n. Draws only the metrics with a minimum value above n. Example: .. code-block:: none &target=minimumAbove(system.interface.eth*.packetsSent,1000) This would only display interfaces which sent more than 1000 packets/min. """ return filterSeries(requestContext, seriesList, 'min', '>', n) minimumAbove.group = 'Filter Series' minimumAbove.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def maximumBelow(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by a constant n. Draws only the metrics with a maximum value below n. Example: .. code-block:: none &target=maximumBelow(system.interface.eth*.packetsSent,1000) This would only display interfaces which sent less than 1000 packets/min. """ return filterSeries(requestContext, seriesList, 'max', '<=', n) maximumBelow.group = 'Filter Series' maximumBelow.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def minimumBelow(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by a constant n. Draws only the metrics with a minimum value below n. Example: .. code-block:: none &target=minimumBelow(system.interface.eth*.packetsSent,1000) This would only display interfaces which at one point sent less than 1000 packets/min. """ return filterSeries(requestContext, seriesList, 'min', '<=', n) minimumBelow.group = 'Filter Series' minimumBelow.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def highest(requestContext, seriesList, n=1, func='average'): """ Takes one metric or a wildcard seriesList followed by an integer N and an aggregation function. Out of all metrics passed, draws only the N metrics with the highest aggregated value over the time period specified. Example: .. code-block:: none &target=highest(server*.instance*.threads.busy,5,'max') Draws the 5 servers with the highest number of busy threads. """ seriesList.sort(key=keyFunc(getAggFunc(func)), reverse=True) return seriesList[:int(n)] highest.group = 'Filter Series' highest.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), Param('func', ParamTypes.string, options=aggFuncNames, default='average'), ] def lowest(requestContext, seriesList, n=1, func='average'): """ Takes one metric or a wildcard seriesList followed by an integer N and an aggregation function. Out of all metrics passed, draws only the N metrics with the lowest aggregated value over the time period specified. Example: .. code-block:: none &target=lowest(server*.instance*.threads.busy,5,'min') Draws the 5 servers with the lowest number of busy threads. """ seriesList.sort(key=keyFunc(getAggFunc(func))) return seriesList[:int(n)] lowest.group = 'Filter Series' lowest.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), Param('func', ParamTypes.string, options=aggFuncNames, default='average'), ] def highestCurrent(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the N metrics with the highest value at the end of the time period specified. Example: .. code-block:: none &target=highestCurrent(server*.instance*.threads.busy,5) Draws the 5 servers with the highest busy threads. This is an alias for :py:func:`highest ` with aggregation ``current``. """ return highest(requestContext, seriesList, n, 'current') highestCurrent.group = 'Filter Series' highestCurrent.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def highestMax(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the N metrics with the highest maximum value in the time period specified. Example: .. code-block:: none &target=highestMax(server*.instance*.threads.busy,5) Draws the top 5 servers who have had the most busy threads during the time period specified. This is an alias for :py:func:`highest ` with aggregation ``max``. """ return highest(requestContext, seriesList, n, 'max') highestMax.group = 'Filter Series' highestMax.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def lowestCurrent(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the N metrics with the lowest value at the end of the time period specified. Example: .. code-block:: none &target=lowestCurrent(server*.instance*.threads.busy,5) Draws the 5 servers with the least busy threads right now. This is an alias for :py:func:`lowest ` with aggregation ``current``. """ return lowest(requestContext, seriesList, n, 'current') lowestCurrent.group = 'Filter Series' lowestCurrent.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def currentAbove(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the metrics whose value is above N at the end of the time period specified. Example: .. code-block:: none &target=currentAbove(server*.instance*.threads.busy,50) Draws the servers with more than 50 busy threads. """ return filterSeries(requestContext, seriesList, 'last', '>', n) currentAbove.group = 'Filter Series' currentAbove.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def currentBelow(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the metrics whose value is below N at the end of the time period specified. Example: .. code-block:: none &target=currentBelow(server*.instance*.threads.busy,3) Draws the servers with less than 3 busy threads. """ return filterSeries(requestContext, seriesList, 'last', '<=', n) currentBelow.group = 'Filter Series' currentBelow.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def highestAverage(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the top N metrics with the highest average value for the time period specified. Example: .. code-block:: none &target=highestAverage(server*.instance*.threads.busy,5) Draws the top 5 servers with the highest average value. This is an alias for :py:func:`highest ` with aggregation ``average``. """ return highest(requestContext, seriesList, n, 'average') highestAverage.group = 'Filter Series' highestAverage.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def lowestAverage(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the bottom N metrics with the lowest average value for the time period specified. Example: .. code-block:: none &target=lowestAverage(server*.instance*.threads.busy,5) Draws the bottom 5 servers with the lowest average value. This is an alias for :py:func:`lowest ` with aggregation ``average``. """ return lowest(requestContext, seriesList, n, 'average') lowestAverage.group = 'Filter Series' lowestAverage.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def averageAbove(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the metrics with an average value above N for the time period specified. Example: .. code-block:: none &target=averageAbove(server*.instance*.threads.busy,25) Draws the servers with average values above 25. """ return filterSeries(requestContext, seriesList, 'average', '>', n) averageAbove.group = 'Filter Series' averageAbove.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def averageBelow(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Out of all metrics passed, draws only the metrics with an average value below N for the time period specified. Example: .. code-block:: none &target=averageBelow(server*.instance*.threads.busy,25) Draws the servers with average values below 25. """ return filterSeries(requestContext, seriesList, 'average', '<=', n) averageBelow.group = 'Filter Series' averageBelow.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def _getPercentile(points, n, interpolate=False): """ Percentile is calculated using the method outlined in the NIST Engineering Statistics Handbook: http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm """ sortedPoints = sorted([ p for p in points if p is not None]) if len(sortedPoints) == 0: return None fractionalRank = (n/100.0) * (len(sortedPoints) + 1) rank = int(fractionalRank) rankFraction = fractionalRank - rank if not interpolate: rank += int(math.ceil(rankFraction)) if rank == 0: percentile = sortedPoints[0] elif rank - 1 == len(sortedPoints): percentile = sortedPoints[-1] else: percentile = sortedPoints[rank - 1] # Adjust for 0-index if interpolate: if rank != len(sortedPoints): # if a next value exists nextValue = sortedPoints[rank] percentile = percentile + rankFraction * (nextValue - percentile) return percentile def nPercentile(requestContext, seriesList, n): """Returns n-percent of each series in the seriesList.""" assert n, 'The requested percent is required to be greater than 0' results = [] for s in seriesList: # Create a sorted copy of the TimeSeries excluding None values in the values list. s_copy = s.copy(values=sorted( [item for item in s if item is not None] )) if not s_copy: continue # Skip this series because it is empty. perc_val = _getPercentile(s_copy, n) if perc_val is not None: s_copy.tags['nPercentile'] = n name = 'nPercentile(%s, %g)' % (s_copy.name, n) point_count = int((s.end - s.start)/s.step) perc_series = s_copy.copy(name=name, values=[perc_val] * point_count) results.append(perc_series) return results nPercentile.group = 'Calculate' nPercentile.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def averageOutsidePercentile(requestContext, seriesList, n): """ Removes series lying inside an average percentile interval """ averages = [safeAvg(s) for s in seriesList] if n < 50: n = 100 - n; lowPercentile = _getPercentile(averages, 100 - n) highPercentile = _getPercentile(averages, n) return [s for s in seriesList if not lowPercentile < safeAvg(s) < highPercentile] averageOutsidePercentile.group = 'Filter Series' averageOutsidePercentile.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def removeBetweenPercentile(requestContext, seriesList, n): """ Removes series that do not have an value lying in the x-percentile of all the values at a moment """ if n < 50: n = 100 - n transposed = list(zip(*seriesList)) lowPercentiles = [_getPercentile(col, 100-n) for col in transposed] highPercentiles = [_getPercentile(col, n) for col in transposed] return [l for l in seriesList if sum([not lowPercentiles[val_i] < val < highPercentiles[val_i] for (val_i, val) in enumerate(l)]) > 0] removeBetweenPercentile.group = 'Filter Series' removeBetweenPercentile.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def removeAbovePercentile(requestContext, seriesList, n): """ Removes data above the nth percentile from the series or list of series provided. Values above this percentile are assigned a value of None. """ for s in seriesList: s.name = 'removeAbovePercentile(%s, %g)' % (s.name, n) s.pathExpression = s.name try: percentile = nPercentile(requestContext, [s], n)[0][0] except IndexError: continue for (index, val) in enumerate(s): if val is not None and val > percentile: s[index] = None return seriesList removeAbovePercentile.group = 'Filter Data' removeAbovePercentile.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def removeAboveValue(requestContext, seriesList, n): """ Removes data above the given threshold from the series or list of series provided. Values above this threshold are assigned a value of None. """ for s in seriesList: s.name = 'removeAboveValue(%s, %g)' % (s.name, n) s.pathExpression = s.name for (index, val) in enumerate(s): if val is not None and val > n: s[index] = None return seriesList removeAboveValue.group = 'Filter Data' removeAboveValue.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def removeBelowPercentile(requestContext, seriesList, n): """ Removes data below the nth percentile from the series or list of series provided. Values below this percentile are assigned a value of None. """ for s in seriesList: s.name = 'removeBelowPercentile(%s, %g)' % (s.name, n) s.pathExpression = s.name try: percentile = nPercentile(requestContext, [s], n)[0][0] except IndexError: continue for (index, val) in enumerate(s): if val is None or val < percentile: s[index] = None return seriesList removeBelowPercentile.group = 'Filter Data' removeBelowPercentile.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def removeBelowValue(requestContext, seriesList, n): """ Removes data below the given threshold from the series or list of series provided. Values below this threshold are assigned a value of None. """ for s in seriesList: s.name = 'removeBelowValue(%s, %g)' % (s.name, n) s.pathExpression = s.name for (index, val) in enumerate(s): if val is None or val < n: s[index] = None return seriesList removeBelowValue.group = 'Filter Data' removeBelowValue.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def limit(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Only draw the first N metrics. Useful when testing a wildcard in a metric. Example: .. code-block:: none &target=limit(server*.instance*.memory.free,5) Draws only the first 5 instance's memory free. """ return seriesList[0:n] limit.group = 'Filter Series' limit.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def sortBy(requestContext, seriesList, func='average', reverse=False): """ Takes one metric or a wildcard seriesList followed by an aggregation function and an optional ``reverse`` parameter. Returns the metrics sorted according to the specified function. Example: .. code-block:: none &target=sortBy(server*.instance*.threads.busy,'max') Draws the servers in ascending order by maximum. """ seriesList.sort(key=keyFunc(getAggFunc(func)), reverse=reverse) return seriesList sortBy.group = 'Sorting' sortBy.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('func', ParamTypes.string, options=aggFuncNames, default='average'), Param('reverse', ParamTypes.boolean, default=False), ] def sortByName(requestContext, seriesList, natural=False, reverse=False): """ Takes one metric or a wildcard seriesList. Sorts the list of metrics by the metric name using either alphabetical order or natural sorting. Natural sorting allows names containing numbers to be sorted more naturally, e.g: - Alphabetical sorting: server1, server11, server12, server2 - Natural sorting: server1, server2, server11, server12 """ def natSortKey(series): return re.sub("(\d+)", lambda x: "{0:010}".format(int(x.group(0))), series.name) if natural: seriesList.sort(key=natSortKey, reverse=reverse) else: seriesList.sort(key=lambda x: x.name, reverse=reverse) return seriesList sortByName.group = 'Sorting' sortByName.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('natural', ParamTypes.boolean, default=False), Param('reverse', ParamTypes.boolean, default=False), ] def sortByTotal(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. Sorts the list of metrics in descending order by the sum of values across the time period specified. """ return sortBy(requestContext, seriesList, 'sum', reverse=True) sortByTotal.group = 'Sorting' sortByTotal.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def sortByMaxima(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. Sorts the list of metrics in descending order by the maximum value across the time period specified. Useful with the &areaMode=all parameter, to keep the lowest value lines visible. Example: .. code-block:: none &target=sortByMaxima(server*.instance*.memory.free) """ seriesList.sort(key=keyFunc(safeMax), reverse=True) return seriesList sortByMaxima.group = 'Sorting' sortByMaxima.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def sortByMinima(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. Sorts the list of metrics by the lowest value across the time period specified, including only series that have a maximum value greater than 0. Example: .. code-block:: none &target=sortByMinima(server*.instance*.memory.free) """ newSeries = [series for series in seriesList if safeMax(series, default=0) > 0] newSeries.sort(key=keyFunc(safeMin)) return newSeries sortByMinima.group = 'Sorting' sortByMinima.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def useSeriesAbove(requestContext, seriesList, value, search, replace): """ Compares the maximum of each series against the given `value`. If the series maximum is greater than `value`, the regular expression search and replace is applied against the series name to plot a related metric e.g. given useSeriesAbove(ganglia.metric1.reqs,10,'reqs','time'), the response time metric will be plotted only when the maximum value of the corresponding request/s metric is > 10 .. code-block:: none &target=useSeriesAbove(ganglia.metric1.reqs,10,"reqs","time") """ newNames = [] for series in seriesList: newname = re.sub(search, replace, series.name) if max(series) > value: newNames.append(newname) if not newNames: return [] newSeries = evaluateTarget(requestContext, 'group(%s)' % ','.join(newNames)) return [n for n in newSeries if n is not None and len(n) > 0] useSeriesAbove.group = 'Filter Series' useSeriesAbove.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('value', ParamTypes.string, required=True), Param('search', ParamTypes.string, required=True), Param('replace', ParamTypes.string, required=True), ] def fallbackSeries(requestContext, seriesList, fallback): """ Takes a wildcard seriesList, and a second fallback metric. If the wildcard does not match any series, draws the fallback metric. Example: .. code-block:: none &target=fallbackSeries(server*.requests_per_second, constantLine(0)) Draws a 0 line when server metric does not exist. """ if len(seriesList) > 0: return seriesList else: return fallback fallbackSeries.group = 'Special' fallbackSeries.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('fallback', ParamTypes.seriesList, required=True), ] def mostDeviant(requestContext, seriesList, n): """ Takes one metric or a wildcard seriesList followed by an integer N. Draws the N most deviant metrics. To find the deviants, the standard deviation (sigma) of each series is taken and ranked. The top N standard deviations are returned. Example: .. code-block:: none &target=mostDeviant(server*.instance*.memory.free, 5) Draws the 5 instances furthest from the average memory free. """ deviants = [] for series in seriesList: mean = safeAvg(series) if mean is None: continue square_sum = sum([ (value - mean) ** 2 for value in series if value is not None ]) sigma = safeDiv(square_sum, safeLen(series)) if sigma is None: continue deviants.append( (sigma, series) ) deviants.sort(key=keyFunc(lambda i: i[0]), reverse=True) #sort by sigma return [ series for (_, series) in deviants ][:n] #return the n most deviant series mostDeviant.group = 'Filter Series' mostDeviant.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('n', ParamTypes.integer, required=True), ] def stdev(requestContext, seriesList, points, windowTolerance=0.1): """ Takes one metric or a wildcard seriesList followed by an integer N. Draw the Standard Deviation of all metrics passed for the past N datapoints. If the ratio of null points in the window is greater than windowTolerance, skip the calculation. The default for windowTolerance is 0.1 (up to 10% of points in the window can be missing). Note that if this is set to 0.0, it will cause large gaps in the output anywhere a single point is missing. Example: .. code-block:: none &target=stdev(server*.instance*.threads.busy,30) &target=stdev(server*.instance*.cpu.system,30,0.0) """ # For this we take the standard deviation in terms of the moving average # and the moving average of series squares. for (seriesIndex,series) in enumerate(seriesList): series.tags['stdev'] = points name = "stdev(%s,%d)" % (series.name, int(points)) stdevSeries = series.copy(name=name, values=[]) validPoints = 0 currentSum = 0 currentSumOfSquares = 0 for (index, newValue) in enumerate(series): # Mark whether we've reached our window size - dont drop points out otherwise if index < points: bootstrapping = True droppedValue = None else: bootstrapping = False droppedValue = series[index - points] # Track non-None points in window if not bootstrapping and droppedValue is not None: validPoints -= 1 if newValue is not None: validPoints += 1 # Remove the value that just dropped out of the window if not bootstrapping and droppedValue is not None: currentSum -= droppedValue currentSumOfSquares -= droppedValue**2 # Add in the value that just popped in the window if newValue is not None: currentSum += newValue currentSumOfSquares += newValue**2 if validPoints > 0 and \ float(validPoints)/points >= windowTolerance: try: deviation = math.sqrt(validPoints * currentSumOfSquares - currentSum**2)/validPoints except ValueError: deviation = None stdevSeries.append(deviation) else: stdevSeries.append(None) seriesList[seriesIndex] = stdevSeries return seriesList stdev.group = 'Calculate' stdev.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('points', ParamTypes.integer, required=True), Param('windowTolerance', ParamTypes.float, default=0.1), ] def secondYAxis(requestContext, seriesList): """ Graph the series on the secondary Y axis. """ for series in seriesList: series.options['secondYAxis'] = True series.tags['secondYAxis'] = 1 series.name= 'secondYAxis(%s)' % series.name return seriesList secondYAxis.group = 'Graph' secondYAxis.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def holtWintersIntercept(alpha,actual,last_season,last_intercept,last_slope): return alpha * (actual - last_season) \ + (1 - alpha) * (last_intercept + last_slope) def holtWintersSlope(beta,intercept,last_intercept,last_slope): return beta * (intercept - last_intercept) + (1 - beta) * last_slope def holtWintersSeasonal(gamma,actual,intercept,last_season): return gamma * (actual - intercept) + (1 - gamma) * last_season def holtWintersDeviation(gamma,actual,prediction,last_seasonal_dev): if prediction is None: prediction = 0 return gamma * math.fabs(actual - prediction) + (1 - gamma) * last_seasonal_dev def holtWintersAnalysis(series, seasonality='1d'): alpha = gamma = 0.1 beta = 0.0035 # season is currently one day seasonality_time = parseTimeOffset(seasonality) season_length = (seasonality_time.seconds + (seasonality_time.days * 86400)) // series.step intercept = 0 slope = 0 intercepts = list() slopes = list() seasonals = list() predictions = list() deviations = list() def getLastSeasonal(i): j = i - season_length if j >= 0: return seasonals[j] return 0 def getLastDeviation(i): j = i - season_length if j >= 0: return deviations[j] return 0 last_seasonal = 0 last_seasonal_dev = 0 next_last_seasonal = 0 next_pred = None for i,actual in enumerate(series): if actual is None: # missing input values break all the math # do the best we can and move on intercepts.append(None) slopes.append(0) seasonals.append(0) predictions.append(next_pred) deviations.append(0) next_pred = None continue if i == 0: last_intercept = actual last_slope = 0 # seed the first prediction as the first actual prediction = actual else: last_intercept = intercepts[-1] last_slope = slopes[-1] if last_intercept is None: last_intercept = actual prediction = next_pred last_seasonal = getLastSeasonal(i) next_last_seasonal = getLastSeasonal(i+1) last_seasonal_dev = getLastDeviation(i) intercept = holtWintersIntercept(alpha,actual,last_seasonal ,last_intercept,last_slope) slope = holtWintersSlope(beta,intercept,last_intercept,last_slope) seasonal = holtWintersSeasonal(gamma,actual,intercept,last_seasonal) next_pred = intercept + slope + next_last_seasonal deviation = holtWintersDeviation(gamma,actual,prediction,last_seasonal_dev) intercepts.append(intercept) slopes.append(slope) seasonals.append(seasonal) predictions.append(prediction) deviations.append(deviation) # make the new forecast series forecastTags = series.tags forecastTags['holtWintersForecast'] = 1 forecastName = "holtWintersForecast(%s)" % series.name forecastSeries = TimeSeries(forecastName, series.start, series.end , series.step, predictions, tags=forecastTags, xFilesFactor=series.xFilesFactor) # make the new deviation series deviationTags = series.tags deviationTags['holtWintersDeviation'] = 1 deviationName = "holtWintersDeviation(%s)" % series.name deviationSeries = TimeSeries(deviationName, series.start, series.end , series.step, deviations, tags=deviationTags, xFilesFactor=series.xFilesFactor) results = { 'predictions': forecastSeries , 'deviations': deviationSeries , 'intercepts': intercepts , 'slopes': slopes , 'seasonals': seasonals } return results def holtWintersForecast(requestContext, seriesList, bootstrapInterval='7d', seasonality='1d'): """ Performs a Holt-Winters forecast using the series as input data. Data from `bootstrapInterval` (one week by default) previous to the series is used to bootstrap the initial forecast. """ bootstrap = parseTimeOffset(bootstrapInterval) previewSeconds = bootstrap.seconds + (bootstrap.days * 86400) # ignore original data and pull new, including our preview newContext = requestContext.copy() newContext['startTime'] = requestContext['startTime'] - timedelta(seconds=previewSeconds) previewList = evaluateTarget(newContext, requestContext['args'][0]) results = [] for series in previewList: analysis = holtWintersAnalysis(series, seasonality) predictions = analysis['predictions'] windowPoints = previewSeconds // predictions.step series.tags['holtWintersForecast'] = 1 forecastName = "holtWintersForecast(%s)" % series.name result = TimeSeries(forecastName, predictions.start + previewSeconds, predictions.end, predictions.step, predictions[windowPoints:], tags=series.tags, xFilesFactor=series.xFilesFactor) results.append(result) return results holtWintersForecast.group = 'Calculate' holtWintersForecast.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('bootstrapInterval', ParamTypes.interval, default='7d', suggestions=['7d', '30d']), Param('seasonality', ParamTypes.interval, default='1d', suggestions=['1d', '7d']), ] def holtWintersConfidenceBands(requestContext, seriesList, delta=3, bootstrapInterval='7d', seasonality='1d'): """ Performs a Holt-Winters forecast using the series as input data and plots upper and lower bands with the predicted forecast deviations. """ bootstrap = parseTimeOffset(bootstrapInterval) previewSeconds = bootstrap.seconds + (bootstrap.days * 86400) # ignore original data and pull new, including our preview newContext = requestContext.copy() newContext['startTime'] = requestContext['startTime'] - timedelta(seconds=previewSeconds) previewList = evaluateTarget(newContext, requestContext['args'][0]) results = [] for series in previewList: analysis = holtWintersAnalysis(series, seasonality) data = analysis['predictions'] windowPoints = previewSeconds // data.step forecast = TimeSeries(data.name, data.start + previewSeconds, data.end, data.step, data[windowPoints:], xFilesFactor=series.xFilesFactor) forecast.pathExpression = data.pathExpression data = analysis['deviations'] windowPoints = previewSeconds // data.step deviation = TimeSeries(data.name, data.start + previewSeconds, data.end, data.step, data[windowPoints:], xFilesFactor=series.xFilesFactor) deviation.pathExpression = data.pathExpression seriesLength = len(forecast) i = 0 upperBand = list() lowerBand = list() while i < seriesLength: forecast_item = forecast[i] deviation_item = deviation[i] i = i + 1 if forecast_item is None or deviation_item is None: upperBand.append(None) lowerBand.append(None) else: scaled_deviation = delta * deviation_item upperBand.append(forecast_item + scaled_deviation) lowerBand.append(forecast_item - scaled_deviation) upperTags = series.tags upperTags['holtWintersConfidenceUpper'] = 1 upperName = "holtWintersConfidenceUpper(%s)" % series.name lowerTags = series.tags lowerTags['holtWintersConfidenceLower'] = 1 lowerName = "holtWintersConfidenceLower(%s)" % series.name upperSeries = TimeSeries(upperName, forecast.start, forecast.end , forecast.step, upperBand, tags=upperTags, xFilesFactor=series.xFilesFactor) lowerSeries = TimeSeries(lowerName, forecast.start, forecast.end , forecast.step, lowerBand, tags=lowerTags, xFilesFactor=series.xFilesFactor) upperSeries.pathExpression = series.pathExpression lowerSeries.pathExpression = series.pathExpression results.append(lowerSeries) results.append(upperSeries) return results holtWintersConfidenceBands.group = 'Calculate' holtWintersConfidenceBands.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('delta', ParamTypes.integer, default=3), Param('bootstrapInterval', ParamTypes.interval, default='7d', suggestions=['7d', '30d']), Param('seasonality', ParamTypes.interval, default='1d', suggestions=['1d', '7d']), ] def holtWintersAberration(requestContext, seriesList, delta=3, bootstrapInterval='7d', seasonality='1d'): """ Performs a Holt-Winters forecast using the series as input data and plots the positive or negative deviation of the series data from the forecast. """ results = [] confidenceBands = holtWintersConfidenceBands(requestContext, seriesList, delta, bootstrapInterval, seasonality) confidenceBands = {s.name: s for s in confidenceBands} for series in seriesList: lowerBand = confidenceBands['holtWintersConfidenceLower(%s)' % series.name] upperBand = confidenceBands['holtWintersConfidenceUpper(%s)' % series.name] aberration = list() for i, actual in enumerate(series): if series[i] is None: aberration.append(0) elif upperBand[i] is not None and series[i] > upperBand[i]: aberration.append(series[i] - upperBand[i]) elif lowerBand[i] is not None and series[i] < lowerBand[i]: aberration.append(series[i] - lowerBand[i]) else: aberration.append(0) series.tags['holtWintersAberration'] = 1 newName = "holtWintersAberration(%s)" % series.name results.append(TimeSeries(newName, series.start, series.end , series.step, aberration, tags=series.tags, xFilesFactor=series.xFilesFactor)) return results holtWintersAberration.group = 'Calculate' holtWintersAberration.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('delta', ParamTypes.integer, default=3), Param('bootstrapInterval', ParamTypes.interval, default='7d', suggestions=['7d', '30d']), Param('seasonality', ParamTypes.interval, default='1d', suggestions=['1d', '7d']), ] def holtWintersConfidenceArea(requestContext, seriesList, delta=3, bootstrapInterval='7d', seasonality='1d'): """ Performs a Holt-Winters forecast using the series as input data and plots the area between the upper and lower bands of the predicted forecast deviations. """ bands = holtWintersConfidenceBands(requestContext, seriesList, delta, bootstrapInterval, seasonality) results = areaBetween(requestContext, bands) for series in results: if 'areaBetween' in series.tags: del series.tags['areaBetween'] series.tags['holtWintersConfidenceArea'] = 1 series.name = series.name.replace('areaBetween', 'holtWintersConfidenceArea') series.pathExpression = series.name return results holtWintersConfidenceArea.group = 'Calculate' holtWintersConfidenceArea.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('delta', ParamTypes.integer, default=3), Param('bootstrapInterval', ParamTypes.interval, default='7d', suggestions=['7d', '30d']), Param('seasonality', ParamTypes.interval, default='1d', suggestions=['1d', '7d']), ] def linearRegressionAnalysis(series): """ Returns factor and offset of linear regression function by least squares method. """ n = safeLen(series) sumI = sum([i for i,v in enumerate(series) if v is not None]) sumV = sum([v for i,v in enumerate(series) if v is not None]) sumII = sum([i*i for i,v in enumerate(series) if v is not None]) sumIV = sum([i*v for i,v in enumerate(series) if v is not None]) denominator = float(n*sumII - sumI*sumI) if denominator == 0: return None else: factor = (n * sumIV - sumI * sumV) / denominator / series.step offset = (sumII * sumV - sumIV * sumI) / denominator - factor * series.start return factor, offset def linearRegression(requestContext, seriesList, startSourceAt=None, endSourceAt=None): """ Graphs the linear regression function by least squares method. Takes one metric or a wildcard seriesList, followed by a quoted string with the time to start the line and another quoted string with the time to end the line. The start and end times are inclusive (default range is from to until). See ``from / until`` in the render\_api_ for examples of time formats. Datapoints in the range is used to regression. Example: .. code-block:: none &target=linearRegression(Server.instance01.threads.busy, '-1d') &target=linearRegression(Server.instance*.threads.busy, "00:00 20140101","11:59 20140630") """ results = [] sourceContext = requestContext.copy() if startSourceAt is not None: sourceContext['startTime'] = parseATTime(startSourceAt) if endSourceAt is not None: sourceContext['endTime'] = parseATTime(endSourceAt) sourceList = evaluateTarget(sourceContext, requestContext['args'][0]) for source,series in zip(sourceList, seriesList): series.tags['linearRegressions'] = '%s, %s' % ( int(time.mktime(sourceContext['startTime'].timetuple())), int(time.mktime(sourceContext['endTime'].timetuple())) ) newName = 'linearRegression(%s, %s, %s)' % ( series.name, int(time.mktime(sourceContext['startTime'].timetuple())), int(time.mktime(sourceContext['endTime'].timetuple())) ) forecast = linearRegressionAnalysis(source) if forecast is None: continue factor, offset = forecast values = [ offset + (series.start + i * series.step) * factor for i in range(len(series)) ] newSeries = TimeSeries(newName, series.start, series.end, series.step, values, tags=series.tags, xFilesFactor=series.xFilesFactor) newSeries.pathExpression = newSeries.name results.append(newSeries) return results linearRegression.group = 'Calculate' linearRegression.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('startSourceAt', ParamTypes.date), Param('endSourceAt', ParamTypes.date), ] def drawAsInfinite(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. If the value is zero, draw the line at 0. If the value is above zero, draw the line at infinity. If the value is null or less than zero, do not draw the line. Useful for displaying on/off metrics, such as exit codes. (0 = success, anything else = failure.) Example: .. code-block:: none drawAsInfinite(Testing.script.exitCode) """ for series in seriesList: series.options['drawAsInfinite'] = True series.tags['drawAsInfinite'] = 1 series.name = 'drawAsInfinite(%s)' % series.name return seriesList drawAsInfinite.group = 'Graph' drawAsInfinite.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def lineWidth(requestContext, seriesList, width): """ Takes one metric or a wildcard seriesList, followed by a float F. Draw the selected metrics with a line width of F, overriding the default value of 1, or the &lineWidth=X.X parameter. Useful for highlighting a single metric out of many, or having multiple line widths in one graph. Example: .. code-block:: none &target=lineWidth(server01.instance01.memory.free,5) """ for series in seriesList: series.options['lineWidth'] = width return seriesList lineWidth.group = 'Graph' lineWidth.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('width', ParamTypes.float, required=True), ] def dashed(requestContext, seriesList, dashLength=5): """ Takes one metric or a wildcard seriesList, followed by a float F. Draw the selected metrics with a dotted line with segments of length F If omitted, the default length of the segments is 5.0 Example: .. code-block:: none &target=dashed(server01.instance01.memory.free,2.5) """ for series in seriesList: series.tags['dashed'] = dashLength series.name = 'dashed(%s, %g)' % (series.name, dashLength) series.options['dashed'] = dashLength return seriesList dashed.group = 'Graph' dashed.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('dashLength', ParamTypes.integer, default=5), ] def timeStack(requestContext, seriesList, timeShiftUnit='1d', timeShiftStart=0, timeShiftEnd=7): """ Takes one metric or a wildcard seriesList, followed by a quoted string with the length of time (See ``from / until`` in the render\_api_ for examples of time formats). Also takes a start multiplier and end multiplier for the length of time create a seriesList which is composed the original metric series stacked with time shifts starting time shifts from the start multiplier through the end multiplier Useful for looking at history, or feeding into averageSeries or stddevSeries. Example: .. code-block:: none &target=timeStack(Sales.widgets.largeBlue,"1d",0,7) # create a series for today and each of the previous 7 days """ # Default to negative. parseTimeOffset defaults to + if timeShiftUnit[0].isdigit(): timeShiftUnit = '-' + timeShiftUnit delta = parseTimeOffset(timeShiftUnit) if len(seriesList) < 1: return [] series = seriesList[0] results = [] timeShiftStartint = int(timeShiftStart) timeShiftEndint = int(timeShiftEnd) for shft in range(timeShiftStartint,timeShiftEndint): myContext = requestContext.copy() innerDelta = delta * shft myContext['startTime'] = requestContext['startTime'] + innerDelta myContext['endTime'] = requestContext['endTime'] + innerDelta for shiftedSeries in evaluateTarget(myContext, requestContext['args'][0]): shiftedSeries.tags['timeShiftUnit'] = timeShiftUnit shiftedSeries.tags['timeShift'] = shft shiftedSeries.name = 'timeShift(%s, %s, %s)' % (shiftedSeries.name, timeShiftUnit,shft) shiftedSeries.pathExpression = shiftedSeries.name shiftedSeries.start = series.start shiftedSeries.end = series.end results.append(shiftedSeries) return results timeStack.group = 'Transform' timeStack.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('timeShiftUnit', ParamTypes.interval, default='1d', suggestions=['1h', '6h', '12h', '1d', '2d', '7d', '14d', '30d']), Param('timeShiftStart', ParamTypes.integer, default=0), Param('timeShiftEnd', ParamTypes.integer, default=7), ] def timeShift(requestContext, seriesList, timeShift, resetEnd=True, alignDST=False): """ Takes one metric or a wildcard seriesList, followed by a quoted string with the length of time (See ``from / until`` in the render\_api_ for examples of time formats). Draws the selected metrics shifted in time. If no sign is given, a minus sign ( - ) is implied which will shift the metric back in time. If a plus sign ( + ) is given, the metric will be shifted forward in time. Will reset the end date range automatically to the end of the base stat unless resetEnd is False. Example case is when you timeshift to last week and have the graph date range set to include a time in the future, will limit this timeshift to pretend ending at the current time. If resetEnd is False, will instead draw full range including future time. Because time is shifted by a fixed number of seconds, comparing a time period with DST to a time period without DST, and vice-versa, will result in an apparent misalignment. For example, 8am might be overlaid with 7am. To compensate for this, use the alignDST option. Useful for comparing a metric against itself at a past periods or correcting data stored at an offset. Example: .. code-block:: none &target=timeShift(Sales.widgets.largeBlue,"7d") &target=timeShift(Sales.widgets.largeBlue,"-7d") &target=timeShift(Sales.widgets.largeBlue,"+1h") """ # Default to negative. parseTimeOffset defaults to + if timeShift[0].isdigit(): timeShift = '-' + timeShift delta = parseTimeOffset(timeShift) myContext = requestContext.copy() myContext['startTime'] = requestContext['startTime'] + delta myContext['endTime'] = requestContext['endTime'] + delta if alignDST: def localDST(dt): return time.localtime(time.mktime(dt.timetuple())).tm_isdst reqStartDST = localDST(requestContext['startTime']) reqEndDST = localDST(requestContext['endTime']) myStartDST = localDST(myContext['startTime']) myEndDST = localDST(myContext['endTime']) dstOffset = timedelta(hours=0) # If the requestContext is entirely in DST, and we are entirely NOT in DST if ((reqStartDST and reqEndDST) and (not myStartDST and not myEndDST)): dstOffset = timedelta(hours=1) # Or if the requestContext is entirely NOT in DST, and we are entirely in DST elif ((not reqStartDST and not reqEndDST) and (myStartDST and myEndDST)): dstOffset = timedelta(hours=-1) # Otherwise, we don't do anything, because it would be visually confusing myContext['startTime'] += dstOffset myContext['endTime'] += dstOffset results = [] if len(seriesList) < 1: return [] series = seriesList[0] for shiftedSeries in evaluateTarget(myContext, requestContext['args'][0]): shiftedSeries.tags['timeShift'] = timeShift shiftedSeries.name = 'timeShift(%s, "%s")' % (shiftedSeries.name, timeShift) if resetEnd: shiftedSeries.end = series.end else: shiftedSeries.end = shiftedSeries.end - shiftedSeries.start + series.start shiftedSeries.start = series.start results.append(shiftedSeries) return results timeShift.group = 'Transform' timeShift.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('timeShift', ParamTypes.interval, required=True, suggestions=['1h', '6h', '12h', '1d', '2d', '7d', '14d', '30d']), Param('resetEnd', ParamTypes.boolean, default=True), Param('alignDst', ParamTypes.boolean, default=False), ] def timeSlice(requestContext, seriesList, startSliceAt, endSliceAt="now"): """ Takes one metric or a wildcard metric, followed by a quoted string with the time to start the line and another quoted string with the time to end the line. The start and end times are inclusive. See ``from / until`` in the render\_api_ for examples of time formats. Useful for filtering out a part of a series of data from a wider range of data. Example: .. code-block:: none &target=timeSlice(network.core.port1,"00:00 20140101","11:59 20140630") &target=timeSlice(network.core.port1,"12:00 20140630","now") """ results = [] start = time.mktime(parseATTime(startSliceAt).timetuple()) end = time.mktime(parseATTime(endSliceAt).timetuple()) for slicedSeries in seriesList: slicedSeries.tags['timeSliceStart'] = int(start) slicedSeries.tags['timeSliceEnd'] = int(end) slicedSeries.name = 'timeSlice(%s, %s, %s)' % (slicedSeries.name, int(start), int(end)) curr = time.mktime(requestContext["startTime"].timetuple()) for i, v in enumerate(slicedSeries): if v is None or curr < start or curr > end: slicedSeries[i] = None curr += slicedSeries.step results.append(slicedSeries) return results timeSlice.group = 'Transform' timeSlice.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('startSliceAt', ParamTypes.date, required=True), Param('endSliceAt', ParamTypes.date, default='now'), ] def constantLine(requestContext, value): """ Takes a float F. Draws a horizontal line at value F across the graph. Example: .. code-block:: none &target=constantLine(123.456) """ name = "constantLine(%s)" % str(value) start = int(epoch( requestContext['startTime'] ) ) end = int(epoch( requestContext['endTime'] ) ) step = int((end - start) / 2.0) series = TimeSeries(str(value), start, end, step, [value, value, value], xFilesFactor=requestContext.get('xFilesFactor')) series.pathExpression = name return [series] constantLine.group = 'Special' constantLine.params = [ Param('value', ParamTypes.float, required=True), ] def aggregateLine(requestContext, seriesList, func='average', keepStep=False): """ Takes a metric or wildcard seriesList and draws a horizontal line based on the function applied to each series. If the optional keepStep parameter is set to True, the result will have the same time period and step as the source series. Note: By default, the graphite renderer consolidates data points by averaging data points over time. If you are using the 'min' or 'max' function for aggregateLine, this can cause an unusual gap in the line drawn by this function and the data itself. To fix this, you should use the consolidateBy() function with the same function argument you are using for aggregateLine. This will ensure that the proper data points are retained and the graph should line up correctly. Example: .. code-block:: none &target=aggregateLine(server01.connections.total, 'avg') &target=aggregateLine(server*.connections.total, 'avg') """ aggFunc = getAggFunc(func) results = [] for series in seriesList: value = aggFunc(series) if value is not None: name = 'aggregateLine(%s, %g)' % (series.name, value) else: name = 'aggregateLine(%s, None)' % (series.name) if keepStep: aggSeries = series.copy(name=name, values=[value] * len(series)) else: [aggSeries] = constantLine(requestContext, value) aggSeries.name = name aggSeries.pathExpression = name results.append(aggSeries) return results aggregateLine.group = 'Calculate' aggregateLine.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('func', ParamTypes.aggFunc, default='average', options=aggFuncNames), Param('keepStep', ParamTypes.boolean, default=False), ] def verticalLine(requestContext, ts, label=None, color=None): """ Takes a timestamp string ts. Draws a vertical line at the designated timestamp with optional 'label' and 'color'. Supported timestamp formats include both relative (e.g. -3h) and absolute (e.g. 16:00_20110501) strings, such as those used with ``from`` and ``until`` parameters. When set, the 'label' will appear in the graph legend. Note: Any timestamps defined outside the requested range will raise a 'ValueError' exception. Example: .. code-block:: none &target=verticalLine("12:3420131108","event","blue") &target=verticalLine("16:00_20110501","event") &target=verticalLine("-5mins") """ ts = int(timestamp( parseATTime(ts, requestContext['tzinfo']) )) start = int(timestamp( requestContext['startTime'] )) end = int(timestamp( requestContext['endTime'] )) if ts < start: raise ValueError("verticalLine(): timestamp %s exists before start of range" % ts) elif ts > end: raise ValueError("verticalLine(): timestamp %s exists after end of range" % ts) start = end = ts step = 1.0 series = TimeSeries(label, start, end, step, [1.0, 1.0], xFilesFactor=requestContext.get('xFilesFactor')) series.options['drawAsInfinite'] = True if color: series.color = color return [series] verticalLine.group = 'Graph' verticalLine.params = [ Param('ts', ParamTypes.date, required=True), Param('label', ParamTypes.string), Param('color', ParamTypes.string), ] def threshold(requestContext, value, label=None, color=None): """ Takes a float F, followed by a label (in double quotes) and a color. (See ``bgcolor`` in the render\_api_ for valid color names & formats.) Draws a horizontal line at value F across the graph. Example: .. code-block:: none &target=threshold(123.456, "omgwtfbbq", "red") """ series = constantLine(requestContext, value)[0] if label: series.name = label if color: series.color = color return [series] threshold.group = 'Graph' threshold.params = [ Param('value', ParamTypes.float, required=True), Param('label', ParamTypes.string), Param('color', ParamTypes.string), ] def transformNull(requestContext, seriesList, default=0, referenceSeries=None): """ Takes a metric or wildcard seriesList and replaces null values with the value specified by `default`. The value 0 used if not specified. The optional referenceSeries, if specified, is a metric or wildcard series list that governs which time intervals nulls should be replaced. If specified, nulls are replaced only in intervals where a non-null is found for the same interval in any of referenceSeries. This method compliments the drawNullAsZero function in graphical mode, but also works in text-only mode. Example: .. code-block:: none &target=transformNull(webapp.pages.*.views,-1) This would take any page that didn't have values and supply negative 1 as a default. Any other numeric value may be used as well. """ def transform(v, d): if v is None: return d else: return v if referenceSeries: defaults = [default if any(v is not None for v in x) else None for x in izip_longest(*referenceSeries)] else: defaults = None for series in seriesList: series.tags['transformNull'] = default if referenceSeries: series.tags['referenceSeries'] = 1 if referenceSeries: series.name = "transformNull(%s,%g,referenceSeries)" % (series.name, default) else: series.name = "transformNull(%s,%g)" % (series.name, default) series.pathExpression = series.name if defaults: values = [transform(v, d) for v, d in izip_longest(series, defaults)] else: values = [transform(v, default) for v in series] series.extend(values) del series[:len(values)] return seriesList transformNull.group = 'Transform' transformNull.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('default', ParamTypes.float, default=0), Param('referenceSeries', ParamTypes.seriesList), ] def isNonNull(requestContext, seriesList): """ Takes a metric or wildcard seriesList and counts up the number of non-null values. This is useful for understanding the number of metrics that have data at a given point in time (i.e. to count which servers are alive). Example: .. code-block:: none &target=isNonNull(webapp.pages.*.views) Returns a seriesList where 1 is specified for non-null values, and 0 is specified for null values. """ def transform(v): if v is None: return 0 else: return 1 for series in seriesList: series.tags['isNonNull'] = 1 series.name = "isNonNull(%s)" % (series.name) series.pathExpression = series.name values = [transform(v) for v in series] series.extend(values) del series[:len(values)] return seriesList isNonNull.group = 'Combine' isNonNull.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def identity(requestContext, name): """ Identity function: Returns datapoints where the value equals the timestamp of the datapoint. Useful when you have another series where the value is a timestamp, and you want to compare it to the time of the datapoint, to render an age Example: .. code-block:: none &target=identity("The.time.series") This would create a series named "The.time.series" that contains points where x(t) == t. """ step = 60 start = int(epoch(requestContext["startTime"])) end = int(epoch(requestContext["endTime"])) values = list(range(start, end, step)) series = TimeSeries(name, start, end, step, values, xFilesFactor=requestContext.get('xFilesFactor')) series.pathExpression = 'identity("%s")' % name return [series] identity.group = 'Calculate' identity.params = [ Param('name', ParamTypes.string, required=True), ] def countSeries(requestContext, *seriesLists): """ Draws a horizontal line representing the number of nodes found in the seriesList. .. code-block:: none &target=countSeries(carbon.agents.*.*) """ if seriesLists: (seriesList,start,end,step) = normalize(seriesLists) name = "countSeries(%s)" % formatPathExpressions(seriesList) values = ( int(len(row)) for row in izip_longest(*seriesList) ) series = TimeSeries(name,start,end,step,values, xFilesFactor=requestContext.get('xFilesFactor')) series.pathExpression = name else: series = constantLine(requestContext, 0).pop() series.pathExpression = "countSeries()" return [series] countSeries.group = 'Combine' countSeries.params = [ Param('seriesLists', ParamTypes.seriesList, multiple=True), ] def group(requestContext, *seriesLists): """ Takes an arbitrary number of seriesLists and adds them to a single seriesList. This is used to pass multiple seriesLists to a function which only takes one """ seriesGroup = [] for s in seriesLists: seriesGroup.extend(s) return seriesGroup group.group = 'Combine' group.params = [ Param('seriesLists', ParamTypes.seriesList, multiple=True), ] def mapSeries(requestContext, seriesList, *mapNodes): """ Short form: ``map()`` Takes a seriesList and maps it to a list of seriesList. Each seriesList has the given mapNodes in common. .. note:: This function is not very useful alone. It should be used with :py:func:`reduceSeries` .. code-block:: none mapSeries(servers.*.cpu.*,1) => [ servers.server1.cpu.*, servers.server2.cpu.*, ... servers.serverN.cpu.* ] Each node may be an integer referencing a node in the series name or a string identifying a tag. """ metaSeries = {} keys = [] for series in seriesList: key = aggKey(series, mapNodes) if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) return [ metaSeries[k] for k in keys ] mapSeries.group = 'Combine' mapSeries.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('mapNodes', ParamTypes.nodeOrTag, required=True, multiple=True), ] def reduceSeries(requestContext, seriesLists, reduceFunction, reduceNode, *reduceMatchers): """ Short form: ``reduce()`` Takes a list of seriesLists and reduces it to a list of series by means of the reduceFunction. Reduction is performed by matching the reduceNode in each series against the list of reduceMatchers. Then each series is passed to the reduceFunction as arguments in the order given by reduceMatchers. The reduceFunction should yield a single series. The resulting list of series are aliased so that they can easily be nested in other functions. **Example**: Map/Reduce asPercent(bytes_used,total_bytes) for each server Assume that metrics in the form below exist: .. code-block:: none servers.server1.disk.bytes_used servers.server1.disk.total_bytes servers.server2.disk.bytes_used servers.server2.disk.total_bytes servers.server3.disk.bytes_used servers.server3.disk.total_bytes ... servers.serverN.disk.bytes_used servers.serverN.disk.total_bytes To get the percentage of disk used for each server: .. code-block:: none reduceSeries(mapSeries(servers.*.disk.*,1),"asPercent",3,"bytes_used","total_bytes") => alias(asPercent(servers.server1.disk.bytes_used,servers.server1.disk.total_bytes),"servers.server1.disk.reduce.asPercent"), alias(asPercent(servers.server2.disk.bytes_used,servers.server2.disk.total_bytes),"servers.server2.disk.reduce.asPercent"), alias(asPercent(servers.server3.disk.bytes_used,servers.server3.disk.total_bytes),"servers.server3.disk.reduce.asPercent"), ... alias(asPercent(servers.serverN.disk.bytes_used,servers.serverN.disk.total_bytes),"servers.serverN.disk.reduce.asPercent") In other words, we will get back the following metrics:: servers.server1.disk.reduce.asPercent servers.server2.disk.reduce.asPercent servers.server3.disk.reduce.asPercent ... servers.serverN.disk.reduce.asPercent .. seealso:: :py:func:`mapSeries` """ metaSeries = {} keys = [] for seriesList in seriesLists: for series in seriesList: nodes = series.name.split('.') node = nodes[reduceNode] reduceSeriesName = '.'.join(nodes[0:reduceNode]) + '.reduce.' + reduceFunction if node in reduceMatchers: if reduceSeriesName not in metaSeries: metaSeries[reduceSeriesName] = [None] * len(reduceMatchers) keys.append(reduceSeriesName) i = reduceMatchers.index(node) metaSeries[reduceSeriesName][i] = series for key in keys: metaSeries[key] = SeriesFunction(reduceFunction)(requestContext,*[[l] for l in metaSeries[key]])[0] metaSeries[key].name = key return [ metaSeries[key] for key in keys ] reduceSeries.group = 'Combine' reduceSeries.params = [ Param('seriesLists', ParamTypes.seriesLists, required=True), Param('reduceFunction', ParamTypes.string, required=True), Param('reduceNode', ParamTypes.node, required=True), Param('reduceMatchers', ParamTypes.string, required=True, multiple=True), ] def applyByNode(requestContext, seriesList, nodeNum, templateFunction, newName=None): """ Takes a seriesList and applies some complicated function (described by a string), replacing templates with unique prefixes of keys from the seriesList (the key is all nodes up to the index given as `nodeNum`). If the `newName` parameter is provided, the name of the resulting series will be given by that parameter, with any "%" characters replaced by the unique prefix. Example: .. code-block:: none &target=applyByNode(servers.*.disk.bytes_free,1,"divideSeries(%.disk.bytes_free,sumSeries(%.disk.bytes_*))") Would find all series which match `servers.*.disk.bytes_free`, then trim them down to unique series up to the node given by nodeNum, then fill them into the template function provided (replacing % by the prefixes). Additional Examples: Given keys of - `stats.counts.haproxy.web.2XX` - `stats.counts.haproxy.web.3XX` - `stats.counts.haproxy.web.5XX` - `stats.counts.haproxy.microservice.2XX` - `stats.counts.haproxy.microservice.3XX` - `stats.counts.haproxy.microservice.5XX` The following will return the rate of 5XX's per service: .. code-block:: none applyByNode(stats.counts.haproxy.*.*XX, 3, "asPercent(%.5XX, sumSeries(%.*XX))", "%.pct_5XX") The output series would have keys `stats.counts.haproxy.web.pct_5XX` and `stats.counts.haproxy.microservice.pct_5XX`. """ prefixes = set() for series in seriesList: prefix = '.'.join(series.name.split('.')[:nodeNum + 1]) prefixes.add(prefix) results = [] for prefix in sorted(prefixes): for resultSeries in evaluateTarget(requestContext, templateFunction.replace('%', prefix)): if newName: resultSeries.name = newName.replace('%', prefix) resultSeries.pathExpression = prefix resultSeries.start = series.start resultSeries.end = series.end results.append(resultSeries) return results applyByNode.group = 'Combine' applyByNode.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('nodeNum', ParamTypes.node, required=True), Param('templateFunction', ParamTypes.string, required=True), Param('newName', ParamTypes.string), ] def groupByNode(requestContext, seriesList, nodeNum, callback='average'): """ Takes a serieslist and maps a callback to subgroups within as defined by a common node .. code-block:: none &target=groupByNode(ganglia.by-function.*.*.cpu.load5,2,"sumSeries") Would return multiple series which are each the result of applying the "sumSeries" function to groups joined on the second node (0 indexed) resulting in a list of targets like .. code-block:: none sumSeries(ganglia.by-function.server1.*.cpu.load5),sumSeries(ganglia.by-function.server2.*.cpu.load5),... Node may be an integer referencing a node in the series name or a string identifying a tag. This is an alias for using :py:func:`groupByNodes ` with a single node. """ return groupByNodes(requestContext, seriesList, callback, nodeNum) groupByNode.group = 'Combine' groupByNode.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('nodeNum', ParamTypes.nodeOrTag, required=True), Param('callback', ParamTypes.aggFunc, default='average', options=aggFuncNames, required=True), ] def groupByNodes(requestContext, seriesList, callback, *nodes): """ Takes a serieslist and maps a callback to subgroups within as defined by multiple nodes .. code-block:: none &target=groupByNodes(ganglia.server*.*.cpu.load*,"sum",1,4) Would return multiple series which are each the result of applying the "sum" aggregation to groups joined on the nodes' list (0 indexed) resulting in a list of targets like .. code-block:: none sumSeries(ganglia.server1.*.cpu.load5),sumSeries(ganglia.server1.*.cpu.load10),sumSeries(ganglia.server1.*.cpu.load15),sumSeries(ganglia.server2.*.cpu.load5),sumSeries(ganglia.server2.*.cpu.load10),sumSeries(ganglia.server2.*.cpu.load15),... This function can be used with all aggregation functions supported by :py:func:`aggregate `: ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``range`` & ``multiply``. Each node may be an integer referencing a node in the series name or a string identifying a tag. .. code-block:: none &target=seriesByTag("name=~cpu.load.*", "server=~server[1-9]+", "datacenter=~dc[1-9]+")|groupByNodes("average", "datacenter", 1) # will produce output series like # dc1.load5, dc2.load5, dc1.load10, dc2.load10 This complements :py:func:`aggregateWithWildcards ` which takes a list of wildcard nodes. """ metaSeries = {} keys = [] for series in seriesList: key = aggKey(series, nodes) if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) for key in metaSeries.keys(): if callback in SeriesFunctions: metaSeries[key] = SeriesFunctions[callback](requestContext, metaSeries[key])[0] else: metaSeries[key] = aggregate(requestContext, metaSeries[key], callback)[0] metaSeries[key].name = key return [ metaSeries[key] for key in keys ] groupByNodes.group = 'Combine' groupByNodes.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('callback', ParamTypes.aggFunc, required=True, options=aggFuncNames), Param('nodes', ParamTypes.nodeOrTag, required=True, multiple=True), ] def exclude(requestContext, seriesList, pattern): """ Takes a metric or a wildcard seriesList, followed by a regular expression in double quotes. Excludes metrics that match the regular expression. Example: .. code-block:: none &target=exclude(servers*.instance*.threads.busy,"server02") """ regex = re.compile(pattern) return [s for s in seriesList if not regex.search(s.name)] exclude.group = 'Filter Series' exclude.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('pattern', ParamTypes.string, required=True), ] def grep(requestContext, seriesList, pattern): """ Takes a metric or a wildcard seriesList, followed by a regular expression in double quotes. Excludes metrics that don't match the regular expression. Example: .. code-block:: none &target=grep(servers*.instance*.threads.busy,"server02") """ regex = re.compile(pattern) return [s for s in seriesList if regex.search(s.name)] grep.group = 'Filter Series' grep.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('pattern', ParamTypes.string, required=True), ] def smartSummarize(requestContext, seriesList, intervalString, func='sum', alignTo=None): """ Smarter version of summarize. The alignToFrom boolean parameter has been replaced by alignTo and no longer has any effect. Alignment can be to years, months, weeks, days, hours, and minutes. This function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``. """ if isinstance(alignTo, bool): log.info("Deprecated parameter 'alignToFrom' is being ignored.") else: # Adjust the start time aligning it according to interval unit if alignTo is not None: alignToUnit = getUnitString(alignTo) requestContext = requestContext.copy() s = requestContext['startTime'] if alignToUnit == YEARS_STRING: requestContext['startTime'] = datetime(s.year, 1, 1, tzinfo = s.tzinfo) elif alignToUnit == MONTHS_STRING: requestContext['startTime'] = datetime(s.year, s.month, 1, tzinfo = s.tzinfo) elif alignToUnit == WEEKS_STRING: isoWeekDayToAlignTo = 1 if alignTo[-1].isalpha() else int(alignTo[-1]) daysTosubtract = s.isoweekday() - isoWeekDayToAlignTo if daysTosubtract < 0: daysTosubtract += 7 requestContext['startTime'] = datetime(s.year, s.month, s.day, tzinfo = s.tzinfo) - timedelta(days = daysTosubtract) elif alignToUnit == DAYS_STRING: requestContext['startTime'] = datetime(s.year, s.month, s.day, tzinfo = s.tzinfo) elif alignToUnit == HOURS_STRING: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour, tzinfo = s.tzinfo) elif alignToUnit == MINUTES_STRING: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour, s.minute, tzinfo = s.tzinfo) elif alignToUnit == SECONDS_STRING: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour, s.minute, s.second, tzinfo = s.tzinfo) # Ignore the originally fetched data and pull new using the modified requestContext seriesList = evaluateTarget(requestContext, requestContext['args'][0]) results = [] delta = parseTimeOffset(intervalString) interval = delta.seconds + (delta.days * 86400) for series in seriesList: (newValues, alignedEnd) = _summarizeValues(series, func, interval) series.tags['smartSummarize'] = intervalString series.tags['smartSummarizeFunction'] = func newName = "smartSummarize(%s, \"%s\", \"%s\")" % (series.name, intervalString, func) newSeries = series.copy(name=newName, end=alignedEnd, step=interval, values=newValues) results.append(newSeries) return results smartSummarize.group = 'Transform' smartSummarize.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('intervalString', ParamTypes.interval, required=True, suggestions=['10min', '1h', '1d']), Param('func', ParamTypes.aggFunc, default='sum', options=aggFuncNames), Param('alignTo', ParamTypes.string, options=[None, YEARS_STRING, MONTHS_STRING, WEEKS_STRING, DAYS_STRING, HOURS_STRING, MINUTES_STRING, SECONDS_STRING]), ] def summarize(requestContext, seriesList, intervalString, func='sum', alignToFrom=False): """ Summarize the data into interval buckets of a certain size. By default, the contents of each interval bucket are summed together. This is useful for counters where each increment represents a discrete event and retrieving a "per X" value requires summing all the events in that interval. Specifying 'average' instead will return the mean for each bucket, which can be more useful when the value is a gauge that represents a certain value in time. This function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``. By default, buckets are calculated by rounding to the nearest interval. This works well for intervals smaller than a day. For example, 22:32 will end up in the bucket 22:00-23:00 when the interval=1hour. Passing alignToFrom=true will instead create buckets starting at the from time. In this case, the bucket for 22:32 depends on the from time. If from=6:30 then the 1hour bucket for 22:32 is 22:30-23:30. Example: .. code-block:: none &target=summarize(counter.errors, "1hour") # total errors per hour &target=summarize(nonNegativeDerivative(gauge.num_users), "1week") # new users per week &target=summarize(queue.size, "1hour", "avg") # average queue size per hour &target=summarize(queue.size, "1hour", "max") # maximum queue size during each hour &target=summarize(metric, "13week", "avg", true)&from=midnight+20100101 # 2010 Q1-4 """ results = [] delta = parseTimeOffset(intervalString) interval = delta.seconds + (delta.days * 86400) for series in seriesList: if alignToFrom: newStart = series.start newEnd = series.end else: newStart = series.start - (series.start % interval) newEnd = series.end - (series.end % interval) + interval (newValues, alignedEnd) = _summarizeValues(series, func, interval, newStart, newEnd) if alignToFrom: newEnd = alignedEnd series.tags['summarize'] = intervalString series.tags['summarizeFunction'] = func newName = "summarize(%s, \"%s\", \"%s\"%s)" % (series.name, intervalString, func, alignToFrom and ", true" or "") newSeries = series.copy(name=newName, start=newStart, end=newEnd, step=interval, values=newValues) results.append(newSeries) return results summarize.group = 'Transform' summarize.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('intervalString', ParamTypes.interval, required=True, suggestions=['10min', '1h', '1d']), Param('func', ParamTypes.aggFunc, default='sum', options=aggFuncNames), Param('alignToFrom', ParamTypes.boolean, default=False), ] def _summarizeValues(series, func, interval, newStart=None, newEnd=None): if newStart is None: newStart = series.start if newEnd is None: newEnd = series.end aggFunc = getAggFunc(func) timestamps = list(range( int(series.start), int(series.end), int(series.step))) datapoints = list(series) intervalPoints = interval / series.step i = 0 numPoints = min(len(timestamps), len(datapoints)) newValues = [] timestamp_ = newStart while timestamp_ < newEnd: s = i nonNull = 0 while i < numPoints and timestamps[i] < timestamp_ + interval: if timestamps[i] <= timestamp_: s = i if timestamps[i] >= timestamp_ and datapoints[i] is not None: nonNull += 1 i += 1 if xff(nonNull, intervalPoints, series.xFilesFactor): newValues.append(aggFunc(datapoints[s:i])) else: newValues.append(None) timestamp_ += interval return (newValues, timestamp_) def hitcount(requestContext, seriesList, intervalString, alignToInterval = False): """ Estimate hit counts from a list of time series. This function assumes the values in each time series represent hits per second. It calculates hits per some larger interval such as per day or per hour. This function is like summarize(), except that it compensates automatically for different time scales (so that a similar graph results from using either fine-grained or coarse-grained records) and handles rarely-occurring events gracefully. """ results = [] delta = parseTimeOffset(intervalString) interval = int(delta.seconds + (delta.days * 86400)) if alignToInterval: requestContext = requestContext.copy() s = requestContext['startTime'] if interval >= DAY: requestContext['startTime'] = datetime(s.year, s.month, s.day, tzinfo = s.tzinfo) elif interval >= HOUR: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour, tzinfo = s.tzinfo) elif interval >= MINUTE: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour, s.minute, tzinfo = s.tzinfo) # Ignore the originally fetched data and pull new using # the modified requestContext. seriesList = evaluateTarget(requestContext, requestContext['args'][0]) for series in seriesList: intervalCount = int((series.end - series.start) // interval) series.end = series.start + (intervalCount * interval) + interval for series in seriesList: step = int(series.step) bucket_count = int(math.ceil(float(series.end - series.start) / interval)) buckets = [[] for _ in range(bucket_count)] newStart = int(series.end - bucket_count * interval) for i, value in enumerate(series): if value is None: continue start_time = int(series.start + i * step) start_bucket, start_mod = divmod(start_time - newStart, interval) end_time = start_time + step end_bucket, end_mod = divmod(end_time - newStart, interval) if end_bucket >= bucket_count: end_bucket = bucket_count - 1 end_mod = interval if start_bucket == end_bucket: # All of the hits go to a single bucket. if start_bucket >= 0: buckets[start_bucket].append(value * (end_mod - start_mod)) else: # Spread the hits among 2 or more buckets. if start_bucket >= 0: buckets[start_bucket].append(value * (interval - start_mod)) hits_per_bucket = value * interval for j in range(start_bucket + 1, end_bucket): buckets[j].append(hits_per_bucket) if end_mod > 0: buckets[end_bucket].append(value * end_mod) newValues = [] for bucket in buckets: if bucket: newValues.append( sum(bucket) ) else: newValues.append(None) series.tags['hitcount'] = intervalString newName = 'hitcount(%s, "%s"%s)' % (series.name, intervalString, alignToInterval and ", true" or "") newSeries = series.copy(name=newName, start=newStart, step=interval, values=newValues) results.append(newSeries) return results hitcount.group = 'Transform' hitcount.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('intervalString', ParamTypes.interval, required=True, suggestions=['10min', '1h', '1d']), Param('alignToInterval', ParamTypes.boolean, default=False), ] def timeFunction(requestContext, name, step=60): """ Short Alias: time() Just returns the timestamp for each X value. T Example: .. code-block:: none &target=time("The.time.series") This would create a series named "The.time.series" that contains in Y the same value (in seconds) as X. Accepts optional second argument as 'step' parameter (default step is 60 sec) """ # TODO: align both startTime and endTime when creating the TimeSeries. delta = timedelta(seconds=step) when = requestContext["startTime"] values = [] while when < requestContext["endTime"]: values.append(time.mktime(when.timetuple())) when += delta series = TimeSeries(name, int(time.mktime(requestContext["startTime"].timetuple())), int(time.mktime(requestContext["endTime"].timetuple())), step, values, xFilesFactor=requestContext.get('xFilesFactor')) return [series] timeFunction.group = 'Transform' timeFunction.params = [ Param('name', ParamTypes.string, required=True), Param('step', ParamTypes.integer, default=60), ] def sinFunction(requestContext, name, amplitude=1, step=60): """ Short Alias: sin() Just returns the sine of the current time. The optional amplitude parameter changes the amplitude of the wave. Example: .. code-block:: none &target=sin("The.time.series", 2) This would create a series named "The.time.series" that contains sin(x)*2. Accepts optional second argument as 'amplitude' parameter (default amplitude is 1) Accepts optional third argument as 'step' parameter (default step is 60 sec) """ delta = timedelta(seconds=step) when = requestContext["startTime"] values = [] while when < requestContext["endTime"]: values.append(math.sin(time.mktime(when.timetuple()))*amplitude) when += delta return [TimeSeries(name, int(epoch(requestContext["startTime"])), int(epoch(requestContext["endTime"])), step, values, xFilesFactor=requestContext.get('xFilesFactor'))] sinFunction.group = 'Transform' sinFunction.params = [ Param('name', ParamTypes.string, required=True), Param('amplitude', ParamTypes.integer, default=1), Param('step', ParamTypes.integer, default=60), ] def removeEmptySeries(requestContext, seriesList, xFilesFactor=None): """ Takes one metric or a wildcard seriesList. Out of all metrics passed, draws only the metrics with not empty data Example: .. code-block:: none &target=removeEmptySeries(server*.instance*.threads.busy) Draws only live servers with not empty data. `xFilesFactor` follows the same semantics as in Whisper storage schemas. Setting it to 0 (the default) means that only a single value in the series needs to be non-null for it to be considered non-empty, setting it to 1 means that all values in the series must be non-null. A setting of 0.5 means that at least half the values in the series must be non-null. """ xFilesFactor = xFilesFactor if xFilesFactor is not None else 0 return [ series for series in seriesList if xffValues(series, xFilesFactor) ] removeEmptySeries.group = 'Filter Series' removeEmptySeries.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('xFilesFactor', ParamTypes.float, required=True), ] def unique(requestContext, *seriesLists): """ Takes an arbitrary number of seriesLists and returns unique series, filtered by name. Example: .. code-block:: none &target=unique(mostDeviant(server.*.disk_free,5),lowestCurrent(server.*.disk_free,5)) Draws servers with low disk space, and servers with highly deviant disk space, but never the same series twice. """ newList = [] seenNames = set() for seriesList in seriesLists: for series in seriesList: if series.name not in seenNames: seenNames.add(series.name) newList.append(series) return newList unique.group = 'Filter Series' unique.params = [ Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True), ] def randomWalkFunction(requestContext, name, step=60): """ Short Alias: randomWalk() Returns a random walk starting at 0. This is great for testing when there is no real data in whisper. Example: .. code-block:: none &target=randomWalk("The.time.series") This would create a series named "The.time.series" that contains points where x(t) == x(t-1)+random()-0.5, and x(0) == 0. Accepts optional second argument as 'step' parameter (default step is 60 sec) """ delta = timedelta(seconds=step) when = requestContext["startTime"] values = [] current = 0 while when < requestContext["endTime"]: values.append(current) current += random.random() - 0.5 when += delta return [TimeSeries(name, int(epoch(requestContext["startTime"])), int(epoch(requestContext["endTime"])), step, values, xFilesFactor=requestContext.get('xFilesFactor'))] randomWalkFunction.group = 'Special' randomWalkFunction.params = [ Param('name', ParamTypes.string, required=True), Param('step', ParamTypes.integer, default=60), ] def seriesByTag(requestContext, *tagExpressions): """ Returns a SeriesList of series matching all the specified tag expressions. Example: .. code-block:: none &target=seriesByTag("tag1=value1","tag2!=value2") Returns a seriesList of all series that have tag1 set to value1, AND do not have tag2 set to value2. Tags specifiers are strings, and may have the following formats: .. code-block:: none tag=spec tag value exactly matches spec tag!=spec tag value does not exactly match spec tag=~value tag value matches the regular expression spec tag!=~spec tag value does not match the regular expression spec Any tag spec that matches an empty value is considered to match series that don't have that tag. At least one tag spec must require a non-empty value. Regular expression conditions are treated as being anchored at the start of the value. See :ref:`querying tagged series ` for more detail. """ # the handling of seriesByTag is implemented in STORE.fetch seriesByTag.group = 'Special' seriesByTag.params = [ Param('tagExpressions', ParamTypes.string, required=True, multiple=True), ] def groupByTags(requestContext, seriesList, callback, *tags): """ Takes a serieslist and maps a callback to subgroups within as defined by multiple tags .. code-block:: none &target=seriesByTag("name=cpu")|groupByTags("average","dc") Would return multiple series which are each the result of applying the "averageSeries" function to groups joined on the specified tags resulting in a list of targets like .. code-block :: none averageSeries(seriesByTag("name=cpu","dc=dc1")),averageSeries(seriesByTag("name=cpu","dc=dc2")),... This function can be used with all aggregation functions supported by :py:func:`aggregate `: ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``, ``stddev``, ``range`` & ``multiply``. """ if STORE.tagdb is None: log.info('groupByTags called but no TagDB configured') return [] if not tags: raise ValueError("groupByTags(): no tags specified") # if all series have the same "name" tag use that for results, otherwise use the callback # if we're grouping by name, then the name is always used (see below) if 'name' not in tags: names = set([series.tags['name'] for series in seriesList]) name = list(names)[0] if len(names) == 1 else callback keys = [] metaSeries = {} for series in seriesList: # key is the metric path for the new series if 'name' not in tags: key = ';'.join([name] + sorted([tag + '=' + series.tags.get(tag, '') for tag in tags])) else: key = ';'.join([series.tags['name']] + sorted([tag + '=' + series.tags.get(tag, '') for tag in tags if tag != 'name'])) if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) for key in keys: if callback in SeriesFunctions: metaSeries[key] = SeriesFunctions[callback](requestContext, metaSeries[key])[0] else: metaSeries[key] = aggregate(requestContext, metaSeries[key], callback)[0] metaSeries[key].name = key metaSeries[key].pathExpression = key metaSeries[key].tags = STORE.tagdb.parse(key).tags return [metaSeries[key] for key in keys] groupByTags.group = 'Combine' groupByTags.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('callback', ParamTypes.aggFunc, required=True, options=aggFuncNames), Param('tags', ParamTypes.tag, required=True, multiple=True), ] def aliasByTags(requestContext, seriesList, *tags): """ Takes a seriesList and applies an alias derived from one or more tags and/or nodes .. code-block:: none &target=seriesByTag("name=cpu")|aliasByTags("server","name") This is an alias for :py:func:`aliasByNode `. """ return aliasByNode(requestContext, seriesList, *tags) aliasByTags.group = 'Alias' aliasByTags.params = [ Param('seriesList', ParamTypes.seriesList, required=True), Param('tags', ParamTypes.nodeOrTag, required=True, multiple=True), ] def events(requestContext, *tags): """ Returns the number of events at this point in time. Usable with drawAsInfinite. Example: .. code-block:: none &target=events("tag-one", "tag-two") &target=events("*") Returns all events tagged as "tag-one" and "tag-two" and the second one returns all events. """ step = 1 name = "events(\"" + "\", \"".join(tags) + "\")" if tags == ("*",): tags = None start_timestamp = epoch(requestContext["startTime"]) start_timestamp = start_timestamp - start_timestamp % step end_timestamp = epoch(requestContext["endTime"]) end_timestamp = end_timestamp - end_timestamp % step points = (end_timestamp - start_timestamp) // step events = models.Event.find_events(epoch_to_dt(start_timestamp), epoch_to_dt(end_timestamp), tags=tags) values = [None] * points for event in events: event_timestamp = epoch(event.when) value_offset = (event_timestamp - start_timestamp) // step if values[value_offset] is None: values[value_offset] = 1 else: values[value_offset] += 1 result_series = TimeSeries(name, start_timestamp, end_timestamp, step, values, 'sum', xFilesFactor=requestContext.get('xFilesFactor')) return [result_series] events.group = 'Special' events.params = [ Param('tags', ParamTypes.string, required=True, multiple=True), ] def minMax(requestContext, seriesList): """ Applies the popular min max normalization technique, which takes each point and applies the following normalization transformation to it: normalized = (point - min) / (max - min). Example: .. code-block:: none &target=minMax(Server.instance01.threads.busy) """ for series in seriesList: series.name = "minMax(%s)" % (series.name) series.pathExpression = series.name min_val = safeMin(series, default=0.0) max_val = safeMax(series, default=0.0) for i, val in enumerate(series): if series[i] is not None: try: series[i] = float(val - min_val) / (max_val - min_val) except ZeroDivisionError: series[i] = 0.0 return seriesList minMax.group = 'Transform' minMax.params = [ Param('seriesList', ParamTypes.seriesList, required=True), ] def pieAverage(requestContext, series): """Return the average""" return safeDiv(safeSum(series), safeLen(series)) pieAverage.group = 'Pie' pieAverage.params = [ Param('series', ParamTypes.series, required=True), ] def pieMaximum(requestContext, series): """Return the maximum""" return safeMax(series) pieMaximum.group = 'Pie' pieMaximum.params = [ Param('series', ParamTypes.series, required=True), ] def pieMinimum(requestContext, series): """Return the minimum""" return safeMin(series) pieMinimum.group = 'Pie' pieMinimum.params = [ Param('series', ParamTypes.series, required=True), ] PieFunctions = { 'average': pieAverage, 'maximum': pieMaximum, 'minimum': pieMinimum, } SeriesFunctions = { # Combine functions 'aggregate': aggregate, 'aggregateWithWildcards': aggregateWithWildcards, 'applyByNode': applyByNode, 'asPercent': asPercent, 'averageSeries': averageSeries, 'averageSeriesWithWildcards': averageSeriesWithWildcards, 'avg': averageSeries, 'countSeries': countSeries, 'diffSeries': diffSeries, 'divideSeries': divideSeries, 'divideSeriesLists': divideSeriesLists, 'group': group, 'groupByNode': groupByNode, 'groupByNodes' : groupByNodes, 'groupByTags': groupByTags, 'isNonNull': isNonNull, 'map': mapSeries, 'mapSeries': mapSeries, 'maxSeries': maxSeries, 'minSeries': minSeries, 'multiplySeries': multiplySeries, 'multiplySeriesWithWildcards': multiplySeriesWithWildcards, 'pct': asPercent, 'percentileOfSeries': percentileOfSeries, 'rangeOfSeries': rangeOfSeries, 'reduce': reduceSeries, 'reduceSeries': reduceSeries, 'stddevSeries': stddevSeries, 'sum': sumSeries, 'sumSeries': sumSeries, 'sumSeriesWithWildcards': sumSeriesWithWildcards, 'weightedAverage': weightedAverage, # Transform functions 'absolute': absolute, 'delay': delay, 'derivative': derivative, 'hitcount': hitcount, 'integral': integral, 'integralByInterval' : integralByInterval, 'interpolate': interpolate, 'invert': invert, 'keepLastValue': keepLastValue, 'log': logarithm, 'minMax': minMax, 'nonNegativeDerivative': nonNegativeDerivative, 'offset': offset, 'offsetToZero': offsetToZero, 'perSecond': perSecond, 'pow': pow, 'powSeries': powSeries, 'round': roundFunction, 'scale': scale, 'scaleToSeconds': scaleToSeconds, 'smartSummarize': smartSummarize, 'squareRoot': squareRoot, 'summarize': summarize, 'timeShift': timeShift, 'timeSlice': timeSlice, 'timeStack': timeStack, 'transformNull': transformNull, # Calculate functions 'aggregateLine': aggregateLine, 'exponentialMovingAverage': exponentialMovingAverage, 'holtWintersAberration': holtWintersAberration, 'holtWintersConfidenceArea': holtWintersConfidenceArea, 'holtWintersConfidenceBands': holtWintersConfidenceBands, 'holtWintersForecast': holtWintersForecast, 'linearRegression': linearRegression, 'movingAverage': movingAverage, 'movingMax': movingMax, 'movingMedian': movingMedian, 'movingMin': movingMin, 'movingSum': movingSum, 'movingWindow': movingWindow, 'nPercentile': nPercentile, 'stdev': stdev, # Series Filter functions 'averageAbove': averageAbove, 'averageBelow': averageBelow, 'averageOutsidePercentile': averageOutsidePercentile, 'currentAbove': currentAbove, 'currentBelow': currentBelow, 'exclude': exclude, 'filterSeries': filterSeries, 'grep': grep, 'highest': highest, 'highestAverage': highestAverage, 'highestCurrent': highestCurrent, 'highestMax': highestMax, 'limit': limit, 'lowest': lowest, 'lowestAverage': lowestAverage, 'lowestCurrent': lowestCurrent, 'maximumAbove': maximumAbove, 'maximumBelow': maximumBelow, 'minimumAbove': minimumAbove, 'minimumBelow': minimumBelow, 'mostDeviant': mostDeviant, 'removeBetweenPercentile': removeBetweenPercentile, 'removeEmptySeries': removeEmptySeries, 'unique': unique, 'useSeriesAbove': useSeriesAbove, # Data Filter functions 'removeAbovePercentile': removeAbovePercentile, 'removeAboveValue': removeAboveValue, 'removeBelowPercentile': removeBelowPercentile, 'removeBelowValue': removeBelowValue, # Sorting functions 'sortBy': sortBy, 'sortByMaxima': sortByMaxima, 'sortByMinima': sortByMinima, 'sortByName': sortByName, 'sortByTotal': sortByTotal, # Alias functions 'alias': alias, 'aliasByMetric': aliasByMetric, 'aliasByNode': aliasByNode, 'aliasByTags': aliasByTags, 'aliasQuery': aliasQuery, 'aliasSub': aliasSub, 'legendValue': legendValue, # Graph functions 'alpha': alpha, 'areaBetween': areaBetween, 'color': color, 'dashed': dashed, 'drawAsInfinite': drawAsInfinite, 'lineWidth': lineWidth, 'secondYAxis': secondYAxis, 'stacked': stacked, 'threshold': threshold, 'verticalLine' : verticalLine, # Special functions 'cactiStyle': cactiStyle, 'changed': changed, 'consolidateBy': consolidateBy, 'constantLine': constantLine, 'events': events, 'cumulative': cumulative, 'fallbackSeries': fallbackSeries, 'identity': identity, "randomWalk": randomWalkFunction, "randomWalkFunction": randomWalkFunction, 'setXFilesFactor': setXFilesFactor, "sin": sinFunction, "sinFunction": sinFunction, 'seriesByTag': seriesByTag, 'substr': substr, 'time': timeFunction, 'timeFunction': timeFunction, 'xFilesFactor': setXFilesFactor, } graphite-web-1.1.4/webapp/graphite/render/glyph.py0000644000000000000000000021566613343334667022122 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import math, itertools, re from datetime import datetime, timedelta from six.moves import range, zip from six.moves.urllib.parse import unquote_plus from six.moves.configparser import SafeConfigParser from django.conf import settings import pytz import six from graphite.render.datalib import TimeSeries from graphite.util import json, BytesIO try: import cairocffi as cairo except ImportError: import cairo INFINITY = float('inf') colorAliases = { 'black' : (0,0,0), 'white' : (255,255,255), 'blue' : (100,100,255), 'green' : (0,200,0), 'red' : (200,00,50), 'yellow' : (255,255,0), 'orange' : (255, 165, 0), 'purple' : (200,100,255), 'brown' : (150,100,50), 'cyan' : (0,255,255), 'aqua' : (0,150,150), 'gray' : (175,175,175), 'grey' : (175,175,175), 'magenta' : (255,0,255), 'pink' : (255,100,100), 'gold' : (200,200,0), 'rose' : (200,150,200), 'darkblue' : (0,0,255), 'darkgreen' : (0,255,0), 'darkred' : (255,0,0), 'darkgray' : (111,111,111), 'darkgrey' : (111,111,111), } # This gets overridden by graphTemplates.conf defaultGraphOptions = dict( background='white', foreground='black', majorline='rose', minorline='grey', linecolors='blue,green,red,purple,brown,yellow,aqua,grey,magenta,pink,gold,rose', fontname='Sans', fontsize=10, fontbold='false', fontitalic='false', ) # X-axis configurations (copied from rrdtool, this technique is evil & ugly but effective) SEC = 1 MIN = 60 HOUR = MIN * 60 DAY = HOUR * 24 WEEK = DAY * 7 MONTH = DAY * 31 YEAR = DAY * 365 # Set a flag to indicate whether the '%l' option can be used safely. # On Windows, in particular the %l option in strftime is not supported. # '%l' can also fail silently in Linux. # (It is not one of the documented Python formatters). try: if datetime.now().strftime("%a %l%p"): percent_l_supported = True else: percent_l_supported = False except ValueError as e: percent_l_supported = False DATE_FORMAT = settings.DATE_FORMAT xAxisConfigs = ( dict(seconds=0.00, minorGridUnit=SEC, minorGridStep=5, majorGridUnit=MIN, majorGridStep=1, labelUnit=SEC, labelStep=5, format="%H:%M:%S", maxInterval=10*MIN), dict(seconds=0.07, minorGridUnit=SEC, minorGridStep=10, majorGridUnit=MIN, majorGridStep=1, labelUnit=SEC, labelStep=10, format="%H:%M:%S", maxInterval=20*MIN), dict(seconds=0.14, minorGridUnit=SEC, minorGridStep=15, majorGridUnit=MIN, majorGridStep=1, labelUnit=SEC, labelStep=15, format="%H:%M:%S", maxInterval=30*MIN), dict(seconds=0.27, minorGridUnit=SEC, minorGridStep=30, majorGridUnit=MIN, majorGridStep=2, labelUnit=MIN, labelStep=1, format="%H:%M", maxInterval=2*HOUR), dict(seconds=0.5, minorGridUnit=MIN, minorGridStep=1, majorGridUnit=MIN, majorGridStep=2, labelUnit=MIN, labelStep=1, format="%H:%M", maxInterval=2*HOUR), dict(seconds=1.2, minorGridUnit=MIN, minorGridStep=1, majorGridUnit=MIN, majorGridStep=4, labelUnit=MIN, labelStep=2, format="%H:%M", maxInterval=3*HOUR), dict(seconds=2, minorGridUnit=MIN, minorGridStep=1, majorGridUnit=MIN, majorGridStep=10, labelUnit=MIN, labelStep=5, format="%H:%M", maxInterval=6*HOUR), dict(seconds=5, minorGridUnit=MIN, minorGridStep=2, majorGridUnit=MIN, majorGridStep=10, labelUnit=MIN, labelStep=10, format="%H:%M", maxInterval=12*HOUR), dict(seconds=10, minorGridUnit=MIN, minorGridStep=5, majorGridUnit=MIN, majorGridStep=20, labelUnit=MIN, labelStep=20, format="%H:%M", maxInterval=1*DAY), dict(seconds=30, minorGridUnit=MIN, minorGridStep=10, majorGridUnit=HOUR, majorGridStep=1, labelUnit=HOUR, labelStep=1, format="%H:%M", maxInterval=2*DAY), dict(seconds=60, minorGridUnit=MIN, minorGridStep=30, majorGridUnit=HOUR, majorGridStep=2, labelUnit=HOUR, labelStep=2, format="%H:%M", maxInterval=2*DAY), dict(seconds=100, minorGridUnit=HOUR, minorGridStep=2, majorGridUnit=HOUR, majorGridStep=4, labelUnit=HOUR, labelStep=4, format="%a %H:%M", maxInterval=6*DAY), dict(seconds=255, minorGridUnit=HOUR, minorGridStep=6, majorGridUnit=HOUR, majorGridStep=12, labelUnit=HOUR, labelStep=12, format=DATE_FORMAT + " %H:%M", maxInterval=10*DAY), dict(seconds=600, minorGridUnit=HOUR, minorGridStep=6, majorGridUnit=DAY, majorGridStep=1, labelUnit=DAY, labelStep=1, format=DATE_FORMAT, maxInterval=14*DAY), dict(seconds=1000, minorGridUnit=HOUR, minorGridStep=12, majorGridUnit=DAY, majorGridStep=1, labelUnit=DAY, labelStep=1, format=DATE_FORMAT, maxInterval=365*DAY), dict(seconds=2000, minorGridUnit=DAY, minorGridStep=1, majorGridUnit=DAY, majorGridStep=2, labelUnit=DAY, labelStep=2, format=DATE_FORMAT, maxInterval=365*DAY), dict(seconds=4000, minorGridUnit=DAY, minorGridStep=2, majorGridUnit=DAY, majorGridStep=4, labelUnit=DAY, labelStep=4, format=DATE_FORMAT, maxInterval=365*DAY), dict(seconds=8000, minorGridUnit=DAY, minorGridStep=3.5,majorGridUnit=DAY, majorGridStep=7, labelUnit=DAY, labelStep=7, format=DATE_FORMAT, maxInterval=365*DAY), dict(seconds=16000, minorGridUnit=DAY, minorGridStep=7, majorGridUnit=DAY, majorGridStep=14, labelUnit=DAY, labelStep=14, format=DATE_FORMAT, maxInterval=365*DAY), dict(seconds=32000, minorGridUnit=DAY, minorGridStep=15, majorGridUnit=DAY, majorGridStep=30, labelUnit=DAY, labelStep=30, format=DATE_FORMAT, maxInterval=365*DAY), dict(seconds=64000, minorGridUnit=DAY, minorGridStep=30, majorGridUnit=DAY, majorGridStep=60, labelUnit=DAY, labelStep=60, format=DATE_FORMAT + " %Y"), dict(seconds=100000,minorGridUnit=DAY, minorGridStep=60, majorGridUnit=DAY, majorGridStep=120,labelUnit=DAY, labelStep=120, format=DATE_FORMAT + " %Y"), dict(seconds=120000,minorGridUnit=DAY, minorGridStep=120,majorGridUnit=DAY, majorGridStep=240,labelUnit=DAY, labelStep=240, format=DATE_FORMAT + " %Y"), ) UnitSystems = { 'binary': ( ('Pi', 1024.0**5), ('Ti', 1024.0**4), ('Gi', 1024.0**3), ('Mi', 1024.0**2), ('Ki', 1024.0 )), 'si': ( ('P', 1000.0**5), ('T', 1000.0**4), ('G', 1000.0**3), ('M', 1000.0**2), ('k', 1000.0 )), 'sec': ( ('Y', 60*60*24*365), ('M', 60*60*24*30), ('D', 60*60*24), ('H', 60*60), ('m', 60)), 'msec': ( ('Y', 60*60*24*365*1000), ('M', 60*60*24*30*1000), ('D', 60*60*24*1000), ('H', 60*60*1000), ('m', 60*1000), ('s', 1000)), 'none' : [], } # We accept values fractionally outside of nominal limits, so that # rounding errors don't cause weird effects. Since our goal is to # create plots, and the maximum resolution of the plots is likely to # be less than 10000 pixels, errors smaller than this size shouldn't # create any visible effects. EPSILON = 0.0001 class GraphError(Exception): pass class _AxisTics: def __init__(self, minValue, maxValue, unitSystem=None): self.minValue = self.checkFinite(minValue, "data value") self.minValueSource = 'data' self.maxValue = self.checkFinite(maxValue, "data value") self.maxValueSource = 'data' self.unitSystem = unitSystem @staticmethod def checkFinite(value, name='value'): """Check that value is a finite number. If it is, return it. If not, raise GraphError describing the problem, using name in the error message. """ if math.isnan(value): raise GraphError('Encountered NaN %s' % (name,)) elif math.isinf(value): raise GraphError('Encountered infinite %s' % (name,)) return value @staticmethod def chooseDelta(x): """Choose a reasonable axis range given that one limit is x. Given that end of the axis range (i.e., minValue or maxValue) is x, choose a reasonable distance to the other limit. """ if abs(x) < 1.0e-9: return 1.0 else: return 0.1 * abs(x) def reconcileLimits(self): """If self.minValue is not less than self.maxValue, fix the problem. If self.minValue is not less than self.maxValue, adjust self.minValue and/or self.maxValue (depending on which was not specified explicitly by the user) to make self.minValue < self.maxValue. If the user specified both limits explicitly, then raise GraphError. """ if self.minValue < self.maxValue: # The limits are already OK. return minFixed = (self.minValueSource in ['min']) maxFixed = (self.maxValueSource in ['max', 'limit']) if minFixed and maxFixed: raise GraphError('The %s must be less than the %s' % (self.minValueSource, self.maxValueSource)) elif minFixed: self.maxValue = self.minValue + self.chooseDelta(self.minValue) elif maxFixed: self.minValue = self.maxValue - self.chooseDelta(self.maxValue) else: delta = self.chooseDelta(max(abs(self.minValue), abs(self.maxValue))) average = (self.minValue + self.maxValue) / 2.0 self.minValue = average - delta self.maxValue = average + delta def applySettings(self, axisMin=None, axisMax=None, axisLimit=None): """Apply the specified settings to this axis. Set self.minValue, self.minValueSource, self.maxValue, self.maxValueSource, and self.axisLimit reasonably based on the parameters provided. Arguments: axisMin -- a finite number, or None to choose a round minimum limit that includes all of the data. axisMax -- a finite number, 'max' to use the maximum value contained in the data, or None to choose a round maximum limit that includes all of the data. axisLimit -- a finite number to use as an upper limit on maxValue, or None to impose no upper limit. """ if axisMin is not None and not math.isnan(axisMin): self.minValueSource = 'min' self.minValue = self.checkFinite(axisMin, 'axis min') if axisMax == 'max': self.maxValueSource = 'extremum' elif axisMax is not None and not math.isnan(axisMax): self.maxValueSource = 'max' self.maxValue = self.checkFinite(axisMax, 'axis max') if axisLimit is None or math.isnan(axisLimit): self.axisLimit = None elif axisLimit < self.maxValue: self.maxValue = self.checkFinite(axisLimit, 'axis limit') self.maxValueSource = 'limit' # The limit has already been imposed, so there is no need to # remember it: self.axisLimit = None elif math.isinf(axisLimit): # It must be positive infinity, which is the same as no limit: self.axisLimit = None else: # We still need to remember axisLimit to avoid rounding top to # a value larger than axisLimit: self.axisLimit = axisLimit self.reconcileLimits() def makeLabel(self, value): """Create a label for the specified value. Create a label string containing the value and its units (if any), based on the values of self.step, self.span, and self.unitSystem. """ value, prefix = format_units(value, self.step, system=self.unitSystem) span, spanPrefix = format_units(self.span, self.step, system=self.unitSystem) if prefix: prefix += " " if value < 0.1: return "%g %s" % (float(value), prefix) elif value < 1.0: return "%.2f %s" % (float(value), prefix) if (span is not None and span > 10) or spanPrefix != prefix: if type(value) is float: return "%.1f %s" % (value, prefix) else: return "%d %s" % (int(value), prefix) elif span is not None and span > 3: return "%.1f %s" % (float(value), prefix) elif span is not None and span > 0.1: return "%.2f %s" % (float(value), prefix) else: return "%g %s" % (float(value), prefix) class _LinearAxisTics(_AxisTics): """Axis ticmarks with uniform spacing.""" def __init__(self, minValue, maxValue, unitSystem=None): _AxisTics.__init__(self, minValue, maxValue, unitSystem=unitSystem) self.step = None self.span = None self.binary = None def setStep(self, step): """Set the size of steps between ticmarks.""" self.step = self.checkFinite(float(step), 'axis step') def generateSteps(self, minStep): """Generate allowed steps with step >= minStep in increasing order.""" self.checkFinite(minStep) if self.binary: base = 2.0 mantissas = [1.0] exponent = math.floor(math.log(minStep, 2) - EPSILON) else: base = 10.0 mantissas = [1.0, 2.0, 5.0] exponent = math.floor(math.log10(minStep) - EPSILON) while True: multiplier = base ** exponent for mantissa in mantissas: value = mantissa * multiplier if value >= minStep * (1.0 - EPSILON): yield value exponent += 1 def computeSlop(self, step, divisor): """Compute the slop that would result from step and divisor. Return the slop, or None if this combination can't cover the full range. See chooseStep() for the definition of "slop". """ bottom = step * math.floor(self.minValue / float(step) + EPSILON) top = bottom + step * divisor if top >= self.maxValue - EPSILON * step: return max(top - self.maxValue, self.minValue - bottom) else: return None def chooseStep(self, divisors=None, binary=False): """Choose a nice, pretty size for the steps between axis labels. Our main constraint is that the number of divisions must be taken from the divisors list. We pick a number of divisions and a step size that minimizes the amount of whitespace ("slop") that would need to be included outside of the range [self.minValue, self.maxValue] if we were to push out the axis values to the next larger multiples of the step size. The minimum step that could possibly cover the variance satisfies minStep * max(divisors) >= variance or minStep = variance / max(divisors) It's not necessarily possible to cover the variance with a step that size, but we know that any smaller step definitely *cannot* cover it. So we can start there. For a sufficiently large step size, it is definitely possible to cover the variance, but at some point the slop will start growing. Let's define the slop to be slop = max(minValue - bottom, top - maxValue) Then for a given, step size, we know that slop >= (1/2) * (step * min(divisors) - variance) (the factor of 1/2 is for the best-case scenario that the slop is distributed equally on the two sides of the range). So suppose we already have a choice that yields bestSlop. Then there is no need to choose steps so large that the slop is guaranteed to be larger than bestSlop. Therefore, the maximum step size that we need to consider is maxStep = (2 * bestSlop + variance) / min(divisors) """ self.binary = binary if divisors is None: divisors = [4,5,6] else: for divisor in divisors: self.checkFinite(divisor, 'divisor') if divisor < 1: raise GraphError('Divisors must be greater than or equal to one') if self.minValue == self.maxValue: if self.minValue == 0.0: self.maxValue = 1.0 elif self.minValue < 0.0: self.minValue *= 1.1 self.maxValue *= 0.9 else: self.minValue *= 0.9 self.maxValue *= 1.1 variance = self.maxValue - self.minValue bestSlop = None bestStep = None for step in self.generateSteps(variance / float(max(divisors))): if bestSlop is not None and step * min(divisors) >= 2 * bestSlop + variance: break for divisor in divisors: slop = self.computeSlop(step, divisor) if slop is not None and (bestSlop is None or slop < bestSlop): bestSlop = slop bestStep = step self.step = bestStep def chooseLimits(self): if self.minValueSource == 'data': # Start labels at the greatest multiple of step <= minValue: self.bottom = self.step * math.floor(self.minValue / self.step + EPSILON) else: self.bottom = self.minValue if self.maxValueSource == 'data': # Extend the top of our graph to the lowest step multiple >= maxValue: self.top = self.step * math.ceil(self.maxValue / self.step - EPSILON) # ...but never exceed a user-specified limit: if self.axisLimit is not None and self.top > self.axisLimit + EPSILON * self.step: self.top = self.axisLimit else: self.top = self.maxValue self.span = self.top - self.bottom if self.span == 0: self.top += 1 self.span += 1 def getLabelValues(self): if self.step <= 0.0: raise GraphError('The step size must be positive') if self.span > 1000.0 * self.step: # This is insane. Pick something that won't cause trouble: self.chooseStep() values = [] start = self.step * math.ceil(self.bottom / self.step - EPSILON) i = 0 while True: value = start + i * self.step if value > self.top + EPSILON * self.step: break values.append(value) i += 1 return values class _LogAxisTics(_AxisTics): def __init__(self, minValue, maxValue, unitSystem=None, base=10.0): _AxisTics.__init__(self, minValue, maxValue, unitSystem=unitSystem) if base <= 1.0: raise GraphError('Logarithmic base must be greater than one') self.base = self.checkFinite(base, 'log base') self.step = None self.span = None def setStep(self, step): # step is ignored for Logarithmic tics: self.step = None def chooseStep(self, divisors=None, binary=False): # step is ignored for Logarithmic tics: self.step = None def chooseLimits(self): if self.minValue <= 0: raise GraphError('Logarithmic scale specified with a dataset with a ' 'minimum value less than or equal to zero') self.bottom = math.pow(self.base, math.floor(math.log(self.minValue, self.base))) self.top = math.pow(self.base, math.ceil(math.log(self.maxValue, self.base))) self.span = self.top - self.bottom if self.span == 0: self.top *= self.base self.span = self.top - self.bottom def getLabelValues(self): values = [] value = math.pow(self.base, math.ceil(math.log(self.bottom, self.base) - EPSILON)) while value < self.top * (1.0 + EPSILON): values.append(value) value *= self.base return values class Graph: customizable = ('width','height','margin','bgcolor','fgcolor', \ 'fontName','fontSize','fontBold','fontItalic', \ 'colorList','template','yAxisSide','outputFormat') def __init__(self,**params): self.params = params self.data = params['data'] self.dataLeft = [] self.dataRight = [] self.secondYAxis = False self.width = int( params.get('width',200) ) self.height = int( params.get('height',200) ) self.margin = int( params.get('margin',10) ) self.userTimeZone = params.get('tz') self.logBase = params.get('logBase', None) self.minorY = int(params.get('minorY', 1)) if self.logBase: if self.logBase == 'e': self.logBase = math.e elif self.logBase < 1: self.logBase = None params['logBase'] = None else: self.logBase = float(self.logBase) if self.margin < 0: self.margin = 10 self.setupCairo( params.get('outputFormat','png').lower() ) self.area = { 'xmin' : self.margin + 10, # Need extra room when the time is near the left edge 'xmax' : self.width - self.margin, 'ymin' : self.margin, 'ymax' : self.height - self.margin, } self.loadTemplate( params.get('template','default') ) opts = self.ctx.get_font_options() opts.set_antialias( cairo.ANTIALIAS_NONE ) self.ctx.set_font_options( opts ) self.foregroundColor = params.get('fgcolor',self.defaultForeground) self.backgroundColor = params.get('bgcolor',self.defaultBackground) self.setColor( self.backgroundColor ) self.drawRectangle( 0, 0, self.width, self.height ) if 'colorList' in params: colorList = unquote_plus( str(params['colorList']) ).split(',') else: colorList = self.defaultColorList self.colors = itertools.cycle( colorList ) self.drawGraph(**params) def setupCairo(self,outputFormat='png'): self.outputFormat = outputFormat if outputFormat == 'png': self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height) elif outputFormat == 'svg': self.surfaceData = BytesIO() self.surface = cairo.SVGSurface(self.surfaceData, self.width, self.height) elif outputFormat == 'pdf': self.surfaceData = BytesIO() self.surface = cairo.PDFSurface(self.surfaceData, self.width, self.height) res_x, res_y = self.surface.get_fallback_resolution() self.width = float(self.width / res_x) * 72 self.height = float(self.height / res_y) * 72 self.surface.set_size(self.width, self.height) self.ctx = cairo.Context(self.surface) def setColor(self, value, alpha=1.0, forceAlpha=False): if type(value) is tuple and len(value) == 3: r,g,b = value elif value in colorAliases: r,g,b = colorAliases[value] elif isinstance(value, six.string_types) and len(value) >= 6: s = value if s[0] == '#': s = s[1:] if s[0:3] == '%23': s = s[3:] r,g,b = ( int(s[0:2],base=16), int(s[2:4],base=16), int(s[4:6],base=16) ) if len(s) == 8 and not forceAlpha: alpha = float( int(s[6:8],base=16) ) / 255.0 elif isinstance(value, int) and len(str(value)) == 6: s = str(value) r,g,b = ( int(s[0:2],base=16), int(s[2:4],base=16), int(s[4:6],base=16) ) else: raise ValueError("Must specify an RGB 3-tuple, an html color string, or a known color alias!") r,g,b = [float(c) / 255.0 for c in (r,g,b)] self.ctx.set_source_rgba(r,g,b,alpha) def setFont(self, **params): p = self.defaultFontParams.copy() p.update(params) self.ctx.select_font_face(p['name'], p['italic'], p['bold']) self.ctx.set_font_size( float(p['size']) ) def getExtents(self,text=None,fontOptions={}): if fontOptions: self.setFont(**fontOptions) F = self.ctx.font_extents() extents = { 'maxHeight' : F[2], 'maxAscent' : F[0], 'maxDescent' : F[1] } if text: T = self.ctx.text_extents(text) extents['width'] = T[4] extents['height'] = T[3] return extents def drawRectangle(self, x, y, w, h, fill=True, dash=False): if not fill: o = self.ctx.get_line_width() / 2.0 #offset for borders so they are drawn as lines would be x += o y += o w -= o h -= o self.ctx.rectangle(x,y,w,h) if fill: self.ctx.fill() else: if dash: self.ctx.set_dash(dash,1) else: self.ctx.set_dash([],0) self.ctx.stroke() def drawText(self,text,x,y,font={},color={},align='left',valign='top',border=False,rotate=0): if font: self.setFont(**font) if color: self.setColor(**color) extents = self.getExtents(text) angle = math.radians(rotate) origMatrix = self.ctx.get_matrix() horizontal = { 'left' : 0, 'center' : extents['width'] / 2, 'right' : extents['width'], }[align.lower()] vertical = { 'top' : extents['maxAscent'], 'middle' : extents['maxHeight'] / 2 - extents['maxDescent'], 'bottom' : -extents['maxDescent'], 'baseline' : 0, }[valign.lower()] self.ctx.move_to(x,y) self.ctx.rel_move_to( math.sin(angle) * -vertical, math.cos(angle) * vertical) self.ctx.rotate(angle) self.ctx.rel_move_to( -horizontal, 0 ) bx, by = self.ctx.get_current_point() by -= extents['maxAscent'] self.ctx.text_path(text) self.ctx.fill() if border: self.drawRectangle(bx, by, extents['width'], extents['maxHeight'], fill=False) else: self.ctx.set_matrix(origMatrix) def drawTitle(self,text): self.encodeHeader('title') y = self.area['ymin'] x = self.width / 2 lineHeight = self.getExtents()['maxHeight'] for line in text.split('\n'): self.drawText(line, x, y, align='center') y += lineHeight if self.params.get('yAxisSide') == 'right': self.area['ymin'] = y else: self.area['ymin'] = y + self.margin def drawLegend(self, elements, unique=False): #elements is [ (name,color,rightSide), (name,color,rightSide), ... ] self.encodeHeader('legend') if unique: # remove duplicate names namesSeen = [] newElements = [] for e in elements: if e[0] not in namesSeen: namesSeen.append(e[0]) newElements.append(e) elements = newElements # Check if there's enough room to use two columns. rightSideLabels = False padding = 5 longestName = sorted([e[0] for e in elements],key=len)[-1] testSizeName = longestName + " " + longestName # Double it to check if there's enough room for 2 columns testExt = self.getExtents(testSizeName) testBoxSize = testExt['maxHeight'] - 1 testWidth = testExt['width'] + 2 * (testBoxSize + padding) if testWidth + 50 < self.width: rightSideLabels = True if(self.secondYAxis and rightSideLabels): extents = self.getExtents(longestName) padding = 5 boxSize = extents['maxHeight'] - 1 lineHeight = extents['maxHeight'] + 1 labelWidth = extents['width'] + 2 * (boxSize + padding) columns = max(1, math.floor( (self.width - self.area['xmin']) / labelWidth )) numRight = len([name for (name,color,rightSide) in elements if rightSide]) numberOfLines = max(len(elements) - numRight, numRight) columns = math.floor(columns / 2.0) if columns < 1: columns = 1 legendHeight = (max(1, (numberOfLines / columns)) * lineHeight) + padding self.area['ymax'] -= legendHeight #scoot the drawing area up to fit the legend self.ctx.set_line_width(1.0) x = self.area['xmin'] y = self.area['ymax'] + (2 * padding) n = 0 xRight = self.area['xmax'] - self.area['xmin'] yRight = y nRight = 0 for (name,color,rightSide) in elements: self.setColor( color ) if rightSide: nRight += 1 self.drawRectangle(xRight - padding,yRight,boxSize,boxSize) self.setColor( 'darkgrey' ) self.drawRectangle(xRight - padding,yRight,boxSize,boxSize,fill=False) self.setColor( self.foregroundColor ) self.drawText(name, xRight - boxSize, yRight, align='right') xRight -= labelWidth if nRight % columns == 0: xRight = self.area['xmax'] - self.area['xmin'] yRight += lineHeight else: n += 1 self.drawRectangle(x,y,boxSize,boxSize) self.setColor( 'darkgrey' ) self.drawRectangle(x,y,boxSize,boxSize,fill=False) self.setColor( self.foregroundColor ) self.drawText(name, x + boxSize + padding, y, align='left') x += labelWidth if n % columns == 0: x = self.area['xmin'] y += lineHeight else: extents = self.getExtents(longestName) boxSize = extents['maxHeight'] - 1 lineHeight = extents['maxHeight'] + 1 labelWidth = extents['width'] + 2 * (boxSize + padding) columns = math.floor( self.width / labelWidth ) if columns < 1: columns = 1 numberOfLines = math.ceil( float(len(elements)) / columns ) legendHeight = (numberOfLines * lineHeight) + padding self.area['ymax'] -= legendHeight #scoot the drawing area up to fit the legend self.ctx.set_line_width(1.0) x = self.area['xmin'] y = self.area['ymax'] + (2 * padding) for i,(name,color,rightSide) in enumerate(elements): if rightSide: self.setColor( color ) self.drawRectangle(x + labelWidth + padding,y,boxSize,boxSize) self.setColor( 'darkgrey' ) self.drawRectangle(x + labelWidth + padding,y,boxSize,boxSize,fill=False) self.setColor( self.foregroundColor ) self.drawText(name, x + labelWidth, y, align='right') x += labelWidth else: self.setColor( color ) self.drawRectangle(x,y,boxSize,boxSize) self.setColor( 'darkgrey' ) self.drawRectangle(x,y,boxSize,boxSize,fill=False) self.setColor( self.foregroundColor ) self.drawText(name, x + boxSize + padding, y, align='left') x += labelWidth if (i + 1) % columns == 0: x = self.area['xmin'] y += lineHeight def encodeHeader(self,text): self.ctx.save() self.setColor( self.backgroundColor ) self.ctx.move_to(-88,-88) # identifier for i, char in enumerate(text): self.ctx.line_to(-ord(char), -i-1) self.ctx.stroke() self.ctx.restore() def loadTemplate(self,template): conf = SafeConfigParser() if conf.read(settings.GRAPHTEMPLATES_CONF): defaults = defaultGraphOptions # If a graphTemplates.conf exists, read in # the values from it, but make sure that # all of the default values properly exist defaults.update(dict(conf.items('default'))) if template in conf.sections(): opts = dict( conf.items(template) ) else: opts = defaults else: opts = defaults = defaultGraphOptions self.defaultBackground = opts.get('background', defaults['background']) self.defaultForeground = opts.get('foreground', defaults['foreground']) self.defaultMajorGridLineColor = opts.get('majorline', defaults['majorline']) self.defaultMinorGridLineColor = opts.get('minorline', defaults['minorline']) self.defaultColorList = [c.strip() for c in opts.get('linecolors', defaults['linecolors']).split(',')] fontName = opts.get('fontname', defaults['fontname']) fontSize = float( opts.get('fontsize', defaults['fontsize']) ) fontBold = opts.get('fontbold', defaults['fontbold']).lower() == 'true' fontItalic = opts.get('fontitalic', defaults['fontitalic']).lower() == 'true' self.defaultFontParams = { 'name' : self.params.get('fontName',fontName), 'size' : int( self.params.get('fontSize',fontSize) ), 'bold' : self.params.get('fontBold',fontBold), 'italic' : self.params.get('fontItalic',fontItalic), } def output(self, fileObj): if self.outputFormat == 'png': self.surface.write_to_png(fileObj) elif self.outputFormat == 'pdf': self.surface.finish() pdfData = self.surfaceData.getvalue() self.surfaceData.close() fileObj.write(pdfData) else: if hasattr(self, 'startTime'): hasData = True metaData = { 'x': { 'start': self.startTime, 'end': self.endTime }, 'options': { 'lineWidth': self.lineWidth }, 'font': self.defaultFontParams, 'area': self.area, 'series': [] } if not self.secondYAxis: metaData['y'] = { 'top': self.yTop, 'bottom': self.yBottom, 'step': self.yStep, 'labels': self.yLabels, 'labelValues': self.yLabelValues } for series in self.data: if 'stacked' not in series.options: metaData['series'].append({ 'name': series.name, 'start': series.start, 'end': series.end, 'step': series.step, 'valuesPerPoint': series.valuesPerPoint, 'color': series.color, 'data': series, 'options': series.options }) else: hasData = False metaData = { } self.surface.finish() svgData = str(self.surfaceData.getvalue()) self.surfaceData.close() svgData = svgData.replace('pt"', 'px"', 2) # we expect height/width in pixels, not points svgData = svgData.replace('\n', '', 1) svgData = svgData.replace('\n\n wrappers instead def onHeaderPath(match): name = '' for char in re.findall(r'L -(\d+) -\d+', match.group(1)): name += chr(int(char)) return '' % name (svgData, subsMade) = re.subn(r'', onHeaderPath, svgData) # Replace the first with , and close out the last at the end svgData = svgData.replace(' 0: svgData += "" svgData = svgData.replace(' data-header="true"','') fileObj.write(svgData.encode('utf-8')) fileObj.write((""" """ % json.dumps(metaData)).encode('utf-8')) class LineGraph(Graph): customizable = Graph.customizable + \ ('title','vtitle','lineMode','lineWidth','hideLegend', \ 'hideAxes','minXStep','hideGrid','majorGridLineColor', \ 'minorGridLineColor','thickness','min','max', \ 'graphOnly','yMin','yMax','yLimit','yStep','areaMode', \ 'areaAlpha','drawNullAsZero','tz', 'yAxisSide','pieMode', \ 'yUnitSystem', 'logBase','yMinLeft','yMinRight','yMaxLeft', \ 'yMaxRight', 'yLimitLeft', 'yLimitRight', 'yStepLeft', \ 'yStepRight', 'rightWidth', 'rightColor', 'rightDashed', \ 'leftWidth', 'leftColor', 'leftDashed', 'xFormat', 'minorY', \ 'hideYAxis', 'uniqueLegend', 'vtitleRight', 'yDivisors', \ 'connectedLimit', 'hideXAxis', 'hideNullFromLegend') validLineModes = ('staircase','slope','connected') validAreaModes = ('none','first','all','stacked') validPieModes = ('maximum', 'minimum', 'average') def drawGraph(self,**params): # Make sure we've got datapoints to draw if self.data: startTime = min([series.start for series in self.data]) endTime = max([series.end for series in self.data]) timeRange = endTime - startTime else: timeRange = None if not timeRange: x = self.width / 2 y = self.height / 2 self.setColor('red') self.setFont(size=math.log(self.width * self.height) ) self.drawText("No Data", x, y, align='center') return # Determine if we're doing a 2 y-axis graph. for series in self.data: if 'secondYAxis' in series.options: self.dataRight.append(series) else: self.dataLeft.append(series) if len(self.dataRight) > 0: self.secondYAxis = True # API compatibility hacks if params.get('graphOnly',False): params['hideLegend'] = True params['hideGrid'] = True params['hideAxes'] = True params['hideXAxis'] = False params['hideYAxis'] = False params['yAxisSide'] = 'left' params['title'] = '' params['vtitle'] = '' params['margin'] = 0 params['tz'] = '' self.margin = 0 self.area['xmin'] = 0 self.area['xmax'] = self.width self.area['ymin'] = 0 self.area['ymax'] = self.height if 'yMin' not in params and 'min' in params: params['yMin'] = params['min'] if 'yMax' not in params and 'max' in params: params['yMax'] = params['max'] if 'lineWidth' not in params and 'thickness' in params: params['lineWidth'] = params['thickness'] if 'yAxisSide' not in params: params['yAxisSide'] = 'left' if 'yUnitSystem' not in params: params['yUnitSystem'] = 'si' else: params['yUnitSystem'] = six.text_type(params['yUnitSystem']).lower() if params['yUnitSystem'] not in UnitSystems: params['yUnitSystem'] = 'si' self.params = params # Don't do any of the special right y-axis stuff if we're drawing 2 y-axes. if self.secondYAxis: params['yAxisSide'] = 'left' # When Y Axis is labeled on the right, we subtract x-axis positions from the max, # instead of adding to the minimum if self.params.get('yAxisSide') == 'right': self.margin = self.width # Now to setup our LineGraph specific options self.lineWidth = float( params.get('lineWidth', 1.2) ) self.lineMode = params.get('lineMode','slope').lower() self.connectedLimit = params.get("connectedLimit", INFINITY) assert self.lineMode in self.validLineModes, "Invalid line mode!" self.areaMode = params.get('areaMode','none').lower() assert self.areaMode in self.validAreaModes, "Invalid area mode!" self.pieMode = params.get('pieMode', 'maximum').lower() assert self.pieMode in self.validPieModes, "Invalid pie mode!" # Line mode slope does not work (or even make sense) for series that have # only one datapoint. So if any series have one datapoint we force staircase mode. if self.lineMode == 'slope': for series in self.data: if len(series) == 1: self.lineMode = 'staircase' break if self.secondYAxis: for series in self.data: if 'secondYAxis' in series.options: if 'rightWidth' in params: series.options['lineWidth'] = params['rightWidth'] if 'rightDashed' in params: series.options['dashed'] = params['rightDashed'] if 'rightColor' in params: series.color = params['rightColor'] else: if 'leftWidth' in params: series.options['lineWidth'] = params['leftWidth'] if 'leftDashed' in params: series.options['dashed'] = params['leftDashed'] if 'leftColor' in params: series.color = params['leftColor'] for series in self.data: if not hasattr(series, 'color'): series.color = next(self.colors) titleSize = self.defaultFontParams['size'] + math.floor( math.log(self.defaultFontParams['size']) ) self.setFont( size=titleSize ) self.setColor( self.foregroundColor ) if params.get('title'): self.drawTitle( six.text_type( unquote_plus(params['title']) ) ) if params.get('vtitle'): self.drawVTitle( six.text_type( unquote_plus(params['vtitle']) ) ) if self.secondYAxis and params.get('vtitleRight'): self.drawVTitle( six.text_type( unquote_plus(params['vtitleRight']) ), rightAlign=True ) self.setFont() if not params.get('hideLegend', len(self.data) > settings.LEGEND_MAX_ITEMS): elements = [] for series in self.data: if series.name: if not (params.get('hideNullFromLegend', False) and all(v is None for v in list(series))): elements.append((unquote_plus(series.name),series.color,series.options.get('secondYAxis'))) if len(elements) > 0: self.drawLegend(elements, params.get('uniqueLegend', False)) # Setup axes, labels, and grid # First we adjust the drawing area size to fit X-axis labels if not self.params.get('hideAxes',False) and not self.params.get('hideXAxis', False): self.area['ymax'] -= self.getExtents()['maxAscent'] * 2 self.startTime = min([series.start for series in self.data]) if self.lineMode == 'staircase': self.endTime = max([series.end for series in self.data]) else: self.endTime = max([(series.end - series.step) for series in self.data]) self.timeRange = self.endTime - self.startTime # Now we consolidate our data points to fit in the currently estimated drawing area self.consolidateDataPoints() self.encodeHeader('axes') # Now its time to fully configure the Y-axis and determine the space required for Y-axis labels # Since we'll probably have to squeeze the drawing area to fit the Y labels, we may need to # reconsolidate our data points, which in turn means re-scaling the Y axis, this process will # repeat until we have accurate Y labels and enough space to fit our data points currentXMin = self.area['xmin'] currentXMax = self.area['xmax'] if self.secondYAxis: self.setupTwoYAxes() else: self.setupYAxis() while currentXMin != self.area['xmin'] or currentXMax != self.area['xmax']: #see if the Y-labels require more space self.consolidateDataPoints() #this can cause the Y values to change currentXMin = self.area['xmin'] #so let's keep track of the previous Y-label space requirements currentXMax = self.area['xmax'] if self.secondYAxis: #and recalculate their new requirements self.setupTwoYAxes() else: self.setupYAxis() # Now that our Y-axis is finalized, let's determine our X labels (this won't affect the drawing area) self.setupXAxis() if not self.params.get('hideAxes',False): self.drawLabels() if not self.params.get('hideGrid',False): #hideAxes implies hideGrid self.encodeHeader('grid') self.drawGridLines() # Finally, draw the graph lines self.encodeHeader('lines') self.drawLines() def drawVTitle(self, text, rightAlign=False): lineHeight = self.getExtents()['maxHeight'] if rightAlign: self.encodeHeader('vtitleRight') x = self.area['xmax'] - lineHeight y = self.height / 2 for line in text.split('\n'): self.drawText(line, x, y, align='center', valign='baseline', rotate=90) x -= lineHeight self.area['xmax'] = x - self.margin - lineHeight else: self.encodeHeader('vtitle') x = self.area['xmin'] + lineHeight y = self.height / 2 for line in text.split('\n'): self.drawText(line, x, y, align='center', valign='baseline', rotate=270) x += lineHeight self.area['xmin'] = x + self.margin + lineHeight def getYCoord(self, value, side=None): if "left" == side: yLabelValues = self.yLabelValuesL yTop = self.yTopL yBottom = self.yBottomL elif "right" == side: yLabelValues = self.yLabelValuesR yTop = self.yTopR yBottom = self.yBottomR else: yLabelValues = self.yLabelValues yTop = self.yTop yBottom = self.yBottom try: highestValue = max(yLabelValues) lowestValue = min(yLabelValues) except ValueError: highestValue = yTop lowestValue = yBottom pixelRange = self.area['ymax'] - self.area['ymin'] relativeValue = value - lowestValue valueRange = highestValue - lowestValue if self.logBase: if value <= 0: return None relativeValue = math.log(value, self.logBase) - math.log(lowestValue, self.logBase) valueRange = math.log(highestValue, self.logBase) - math.log(lowestValue, self.logBase) pixelToValueRatio = pixelRange / valueRange valueInPixels = pixelToValueRatio * relativeValue return self.area['ymax'] - valueInPixels def drawLines(self, width=None, dash=None, linecap='butt', linejoin='miter'): if not width: width = self.lineWidth self.ctx.set_line_width(width) originalWidth = width width = float(int(width) % 2) / 2 if dash: self.ctx.set_dash(dash,1) else: self.ctx.set_dash([],0) self.ctx.set_line_cap({ 'butt' : cairo.LINE_CAP_BUTT, 'round' : cairo.LINE_CAP_ROUND, 'square' : cairo.LINE_CAP_SQUARE, }[linecap]) self.ctx.set_line_join({ 'miter' : cairo.LINE_JOIN_MITER, 'round' : cairo.LINE_JOIN_ROUND, 'bevel' : cairo.LINE_JOIN_BEVEL, }[linejoin]) # check whether there is an stacked metric singleStacked = False for series in self.data: if 'stacked' in series.options: singleStacked = True if singleStacked: self.data = sort_stacked(self.data) # stack the values if self.areaMode == 'stacked' and not self.secondYAxis: #TODO Allow stacked area mode with secondYAxis total = [] for series in self.data: if 'drawAsInfinite' in series.options: continue series.options['stacked'] = True for i in range(len(series)): if len(total) <= i: total.append(0) if series[i] is not None: original = series[i] series[i] += total[i] total[i] += original elif self.areaMode == 'first': self.data[0].options['stacked'] = True elif self.areaMode == 'all': for series in self.data: if 'drawAsInfinite' not in series.options: series.options['stacked'] = True # apply alpha channel and create separate stroke series if self.params.get('areaAlpha'): try: alpha = float(self.params['areaAlpha']) except ValueError: alpha = 0.5 pass strokeSeries = [] for series in self.data: if 'stacked' in series.options: series.options['alpha'] = alpha newSeries = TimeSeries(series.name, series.start, series.end, series.step*series.valuesPerPoint, [x for x in series]) newSeries.xStep = series.xStep newSeries.color = series.color if 'secondYAxis' in series.options: newSeries.options['secondYAxis'] = True strokeSeries.append(newSeries) self.data += strokeSeries # setup the clip region self.ctx.set_line_width(1.0) self.ctx.rectangle(self.area['xmin'], self.area['ymin'], self.area['xmax'] - self.area['xmin'], self.area['ymax'] - self.area['ymin']) self.ctx.clip() self.ctx.set_line_width(originalWidth) # save clip to restore once stacked areas are drawn self.ctx.save() clipRestored = False for series in self.data: if 'stacked' not in series.options: # stacked areas are always drawn first. if this series is not stacked, we finished stacking. # reset the clip region so lines can show up on top of the stacked areas. if not clipRestored: clipRestored = True self.ctx.restore() if 'lineWidth' in series.options: self.ctx.set_line_width(series.options['lineWidth']) if 'dashed' in series.options: self.ctx.set_dash([ series.options['dashed'] ], 1) else: self.ctx.set_dash([], 0) # Shift the beginning of drawing area to the start of the series if the # graph itself has a larger range missingPoints = (series.start - self.startTime) / series.step startShift = series.xStep * (missingPoints / series.valuesPerPoint) x = float(self.area['xmin']) + startShift + (self.lineWidth / 2.0) y = float(self.area['ymin']) startX = x if series.options.get('invisible'): self.setColor( series.color, 0, True ) else: self.setColor( series.color, series.options.get('alpha') or 1.0 ) # The number of preceding datapoints that had a None value. consecutiveNones = 0 for index, value in enumerate(series): if value != value: # convert NaN to None value = None if value is None and self.params.get('drawNullAsZero'): value = 0.0 if value is None: if consecutiveNones == 0: self.ctx.line_to(x, y) if 'stacked' in series.options: #Close off and fill area before unknown interval if self.secondYAxis: if 'secondYAxis' in series.options: self.fillAreaAndClip(x, y, startX, self.getYCoord(0, "right")) else: self.fillAreaAndClip(x, y, startX, self.getYCoord(0, "left")) else: self.fillAreaAndClip(x, y, startX, self.getYCoord(0)) x += series.xStep consecutiveNones += 1 else: if self.secondYAxis: if 'secondYAxis' in series.options: y = self.getYCoord(value, "right") else: y = self.getYCoord(value, "left") else: y = self.getYCoord(value) if y is None: value = None elif y < 0: y = 0 if 'drawAsInfinite' in series.options and value > 0: self.ctx.move_to(x, self.area['ymax']) self.ctx.line_to(x, self.area['ymin']) self.ctx.stroke() x += series.xStep continue if consecutiveNones > 0: startX = x if self.lineMode == 'staircase': if consecutiveNones > 0: self.ctx.move_to(x, y) else: self.ctx.line_to(x, y) x += series.xStep self.ctx.line_to(x, y) elif self.lineMode == 'slope': if consecutiveNones > 0: self.ctx.move_to(x, y) self.ctx.line_to(x, y) x += series.xStep elif self.lineMode == 'connected': # If if the gap is larger than the connectedLimit or if this is the # first non-None datapoint in the series, start drawing from that datapoint. if consecutiveNones > self.connectedLimit or consecutiveNones == index: self.ctx.move_to(x, y) self.ctx.line_to(x, y) x += series.xStep consecutiveNones = 0 if 'stacked' in series.options: if self.lineMode == 'staircase': xPos = x else: xPos = x-series.xStep if self.secondYAxis: if 'secondYAxis' in series.options: areaYFrom = self.getYCoord(0, "right") else: areaYFrom = self.getYCoord(0, "left") else: areaYFrom = self.getYCoord(0) self.fillAreaAndClip(xPos, y, startX, areaYFrom) else: self.ctx.stroke() self.ctx.set_line_width(originalWidth) # return to the original line width if 'dash' in series.options: # if we changed the dash setting before, change it back now if dash: self.ctx.set_dash(dash,1) else: self.ctx.set_dash([],0) def fillAreaAndClip(self, x, y, startX=None, areaYFrom=None): startX = (startX or self.area['xmin']) areaYFrom = (areaYFrom or self.area['ymax']) pattern = self.ctx.copy_path() # fill self.ctx.line_to(x, areaYFrom) # bottom endX self.ctx.line_to(startX, areaYFrom) # bottom startX self.ctx.close_path() self.ctx.fill() # clip above y axis self.ctx.append_path(pattern) self.ctx.line_to(x, areaYFrom) # yZero endX self.ctx.line_to(self.area['xmax'], areaYFrom) # yZero right self.ctx.line_to(self.area['xmax'], self.area['ymin']) # top right self.ctx.line_to(self.area['xmin'], self.area['ymin']) # top left self.ctx.line_to(self.area['xmin'], areaYFrom) # yZero left self.ctx.line_to(startX, areaYFrom) # yZero startX # clip below y axis self.ctx.line_to(x, areaYFrom) # yZero endX self.ctx.line_to(self.area['xmax'], areaYFrom) # yZero right self.ctx.line_to(self.area['xmax'], self.area['ymax']) # bottom right self.ctx.line_to(self.area['xmin'], self.area['ymax']) # bottom left self.ctx.line_to(self.area['xmin'], areaYFrom) # yZero left self.ctx.line_to(startX, areaYFrom) # yZero startX self.ctx.close_path() self.ctx.clip() def consolidateDataPoints(self): numberOfPixels = self.graphWidth = self.area['xmax'] - self.area['xmin'] - (self.lineWidth + 1) for series in self.data: numberOfDataPoints = self.timeRange/series.step minXStep = float( self.params.get('minXStep',1.0) ) divisor = self.timeRange / series.step bestXStep = numberOfPixels / divisor if bestXStep < minXStep: drawableDataPoints = int( numberOfPixels / minXStep ) pointsPerPixel = math.ceil( float(numberOfDataPoints) / float(drawableDataPoints) ) series.consolidate(pointsPerPixel) series.xStep = (numberOfPixels * pointsPerPixel) / numberOfDataPoints else: series.xStep = bestXStep def _adjustLimits(self, minValue, maxValue, minName, maxName, limitName): if maxName in self.params and self.params[maxName] != 'max': maxValue = self.params[maxName] if limitName in self.params and self.params[limitName] < maxValue: maxValue = self.params[limitName] if minName in self.params: minValue = self.params[minName] if maxValue <= minValue: maxValue = minValue + 1 return (minValue, maxValue) def setupYAxis(self): (yMinValue, yMaxValue) = dataLimits(self.data, drawNullAsZero=self.params.get('drawNullAsZero'), stacked=(self.areaMode == 'stacked')) if self.logBase: yTics = _LogAxisTics(yMinValue, yMaxValue, unitSystem=self.params.get('yUnitSystem'), base=self.logBase) else: yTics = _LinearAxisTics(yMinValue, yMaxValue, unitSystem=self.params.get('yUnitSystem')) yTics.applySettings(axisMin=self.params.get('yMin'), axisMax=self.params.get('yMax'), axisLimit=self.params.get('yLimit')) if 'yStep' in self.params: yTics.setStep(self.params['yStep']) else: yDivisors = str(self.params.get('yDivisors', '4,5,6')) yDivisors = [int(d) for d in yDivisors.split(',')] binary = self.params.get('yUnitSystem') == 'binary' yTics.chooseStep(divisors=yDivisors, binary=binary) yTics.chooseLimits() # Copy the values we need back out of the yTics object: self.yStep = yTics.step self.yBottom = yTics.bottom self.yTop = yTics.top self.ySpan = yTics.span if not self.params.get('hideAxes', False): #Create and measure the Y-labels self.yLabelValues = yTics.getLabelValues() self.yLabels = [yTics.makeLabel(value) for value in self.yLabelValues] self.yLabelWidth = max([self.getExtents(label)['width'] for label in self.yLabels]) if not self.params.get('hideYAxis'): if self.params.get('yAxisSide') == 'left': # Scoot the graph over to the left just enough to fit the y-labels: xMin = self.margin + (self.yLabelWidth * 1.02) if self.area['xmin'] < xMin: self.area['xmin'] = xMin else: # Scoot the graph over to the right just enough to fit the y-labels: xMin = 0 xMax = self.margin - (self.yLabelWidth * 1.02) if self.area['xmax'] >= xMax: self.area['xmax'] = xMax else: self.yLabelValues = [] self.yLabels = [] self.yLabelWidth = 0.0 def setupTwoYAxes(self): drawNullAsZero = self.params.get('drawNullAsZero') stacked = (self.areaMode == 'stacked') (yMinValueL, yMaxValueL) = dataLimits(self.dataLeft, drawNullAsZero, stacked) (yMinValueR, yMaxValueR) = dataLimits(self.dataRight, drawNullAsZero, stacked) # TODO: Allow separate bases for L & R Axes. if self.logBase: yTicsL = _LogAxisTics(yMinValueL, yMaxValueL, unitSystem=self.params.get('yUnitSystem'), base=self.logBase) yTicsR = _LogAxisTics(yMinValueR, yMaxValueR, unitSystem=self.params.get('yUnitSystem'), base=self.logBase) else: yTicsL = _LinearAxisTics(yMinValueL, yMaxValueL, unitSystem=self.params.get('yUnitSystem')) yTicsR = _LinearAxisTics(yMinValueR, yMaxValueR, unitSystem=self.params.get('yUnitSystem')) yTicsL.applySettings(axisMin=self.params.get('yMinLeft'), axisMax=self.params.get('yMaxLeft'), axisLimit=self.params.get('yLimitLeft')) yTicsR.applySettings(axisMin=self.params.get('yMinRight'), axisMax=self.params.get('yMaxRight'), axisLimit=self.params.get('yLimitRight')) yDivisors = str(self.params.get('yDivisors', '4,5,6')) yDivisors = [int(d) for d in yDivisors.split(',')] binary = self.params.get('yUnitSystem') == 'binary' if 'yStepLeft' in self.params: yTicsL.setStep(self.params['yStepLeft']) else: yTicsL.chooseStep(divisors=yDivisors, binary=binary) if 'yStepRight' in self.params: yTicsR.setStep(self.params['yStepRight']) else: yTicsR.chooseStep(divisors=yDivisors, binary=binary) yTicsL.chooseLimits() yTicsR.chooseLimits() # Copy the values we need back out of the yTics objects: self.yStepL = yTicsL.step self.yBottomL = yTicsL.bottom self.yTopL = yTicsL.top self.ySpanL = yTicsL.span self.yStepR = yTicsR.step self.yBottomR = yTicsR.bottom self.yTopR = yTicsR.top self.ySpanR = yTicsR.span #Create and measure the Y-labels self.yLabelValuesL = yTicsL.getLabelValues() self.yLabelValuesR = yTicsR.getLabelValues() self.yLabelsL = [yTicsL.makeLabel(value) for value in self.yLabelValuesL] self.yLabelsR = [yTicsR.makeLabel(value) for value in self.yLabelValuesR] self.yLabelWidthL = max([self.getExtents(label)['width'] for label in self.yLabelsL]) self.yLabelWidthR = max([self.getExtents(label)['width'] for label in self.yLabelsR]) #scoot the graph over to the left just enough to fit the y-labels #xMin = self.margin + self.margin + (self.yLabelWidthL * 1.02) xMin = self.margin + (self.yLabelWidthL * 1.02) if self.area['xmin'] < xMin: self.area['xmin'] = xMin #scoot the graph over to the right just enough to fit the y-labels xMax = self.width - (self.yLabelWidthR * 1.02) if self.area['xmax'] >= xMax: self.area['xmax'] = xMax def setupXAxis(self): if self.userTimeZone: tzinfo = pytz.timezone(self.userTimeZone) else: tzinfo = pytz.timezone(settings.TIME_ZONE) self.start_dt = datetime.fromtimestamp(self.startTime, tzinfo) self.end_dt = datetime.fromtimestamp(self.endTime, tzinfo) secondsPerPixel = float(self.timeRange) / float(self.graphWidth) self.xScaleFactor = float(self.graphWidth) / float(self.timeRange) #pixels per second potential = [c for c in xAxisConfigs if c['seconds'] <= secondsPerPixel and c.get('maxInterval', self.timeRange + 1) >= self.timeRange] if potential: self.xConf = potential[-1] else: self.xConf = xAxisConfigs[-1] self.xLabelStep = self.xConf['labelUnit'] * self.xConf['labelStep'] self.xMinorGridStep = self.xConf['minorGridUnit'] * self.xConf['minorGridStep'] self.xMajorGridStep = self.xConf['majorGridUnit'] * self.xConf['majorGridStep'] def drawLabels(self): # Draw the Y-labels if not self.params.get('hideYAxis'): if not self.secondYAxis: for value,label in zip(self.yLabelValues,self.yLabels): if self.params.get('yAxisSide') == 'left': x = self.area['xmin'] - (self.yLabelWidth * 0.02) else: x = self.area['xmax'] + (self.yLabelWidth * 0.02) #Inverted for right side Y Axis y = self.getYCoord(value) if y is None: value = None elif y < 0: y = 0 if self.params.get('yAxisSide') == 'left': self.drawText(label, x, y, align='right', valign='middle') else: self.drawText(label, x, y, align='left', valign='middle') #Inverted for right side Y Axis else: # Draws a right side and a Left side axis for valueL,labelL in zip(self.yLabelValuesL,self.yLabelsL): xL = self.area['xmin'] - (self.yLabelWidthL * 0.02) yL = self.getYCoord(valueL, "left") if yL is None: value = None elif yL < 0: yL = 0 self.drawText(labelL, xL, yL, align='right', valign='middle') ### Right Side for valueR,labelR in zip(self.yLabelValuesR,self.yLabelsR): xR = self.area['xmax'] + (self.yLabelWidthR * 0.02) + 3 #Inverted for right side Y Axis yR = self.getYCoord(valueR, "right") if yR is None: valueR = None elif yR < 0: yR = 0 self.drawText(labelR, xR, yR, align='left', valign='middle') #Inverted for right side Y Axis if not self.params.get('hideXAxis'): (dt, x_label_delta) = find_x_times(self.start_dt, self.xConf['labelUnit'], self.xConf['labelStep']) # Draw the X-labels xFormat = self.params.get('xFormat', self.xConf['format']) while dt < self.end_dt: label = dt.strftime(xFormat) x = self.area['xmin'] + (toSeconds(dt - self.start_dt) * self.xScaleFactor) y = self.area['ymax'] + self.getExtents()['maxAscent'] self.drawText(label, x, y, align='center', valign='top') dt += x_label_delta def drawGridLines(self): # Not sure how to handle this for 2 y-axes # Just using the left side info for the grid. # Horizontal grid lines leftSide = self.area['xmin'] rightSide = self.area['xmax'] labels = [] if self.secondYAxis: labels = self.yLabelValuesL else: labels = self.yLabelValues for i, value in enumerate(labels): self.ctx.set_line_width(0.4) self.setColor( self.params.get('majorGridLineColor',self.defaultMajorGridLineColor) ) if self.secondYAxis: y = self.getYCoord(value,"left") else: y = self.getYCoord(value) if y is None or y < 0: continue self.ctx.move_to(leftSide, y) self.ctx.line_to(rightSide, y) self.ctx.stroke() # draw minor gridlines if this isn't the last label if self.minorY >= 1 and i < (len(labels) - 1): # in case graphite supports inverted Y axis now or someday (valueLower, valueUpper) = sorted((value, labels[i+1])) # each minor gridline is 1/minorY apart from the nearby gridlines. # we calculate that distance, for adding to the value in the loop. distance = ((valueUpper - valueLower) / float(1 + self.minorY)) # starting from the initial valueLower, we add the minor distance # for each minor gridline that we wish to draw, and then draw it. for minor in range(self.minorY): self.ctx.set_line_width(0.3) self.setColor( self.params.get('minorGridLineColor',self.defaultMinorGridLineColor) ) # the current minor gridline value is halfway between the current and next major gridline values value = (valueLower + ((1+minor) * distance)) if self.logBase: yTopFactor = self.logBase * self.logBase else: yTopFactor = 1 if self.secondYAxis: if value >= (yTopFactor * self.yTopL): continue else: if value >= (yTopFactor * self.yTop): continue if self.secondYAxis: y = self.getYCoord(value,"left") else: y = self.getYCoord(value) if y is None or y < 0: continue self.ctx.move_to(leftSide, y) self.ctx.line_to(rightSide, y) self.ctx.stroke() # Vertical grid lines top = self.area['ymin'] bottom = self.area['ymax'] # First we do the minor grid lines (majors will paint over them) self.ctx.set_line_width(0.25) self.setColor( self.params.get('minorGridLineColor',self.defaultMinorGridLineColor) ) (dt, x_minor_delta) = find_x_times(self.start_dt, self.xConf['minorGridUnit'], self.xConf['minorGridStep']) while dt < self.end_dt: x = self.area['xmin'] + (toSeconds(dt - self.start_dt) * self.xScaleFactor) if x < self.area['xmax']: self.ctx.move_to(x, bottom) self.ctx.line_to(x, top) self.ctx.stroke() dt += x_minor_delta # Now we do the major grid lines self.ctx.set_line_width(0.33) self.setColor( self.params.get('majorGridLineColor',self.defaultMajorGridLineColor) ) (dt, x_major_delta) = find_x_times(self.start_dt, self.xConf['majorGridUnit'], self.xConf['majorGridStep']) while dt < self.end_dt: x = self.area['xmin'] + (toSeconds(dt - self.start_dt) * self.xScaleFactor) if x < self.area['xmax']: self.ctx.move_to(x, bottom) self.ctx.line_to(x, top) self.ctx.stroke() dt += x_major_delta # Draw side borders for our graph area self.ctx.set_line_width(0.5) self.ctx.move_to(self.area['xmax'], bottom) self.ctx.line_to(self.area['xmax'], top) self.ctx.move_to(self.area['xmin'], bottom) self.ctx.line_to(self.area['xmin'], top) self.ctx.stroke() class PieGraph(Graph): customizable = Graph.customizable + \ ('title','valueLabels','valueLabelsMin','hideLegend','pieLabels','areaAlpha','valueLabelsColor') validValueLabels = ('none','number','percent') def drawGraph(self,**params): self.pieLabels = params.get('pieLabels', 'horizontal') self.total = sum( [t[1] for t in self.data] ) if not self.data: x = self.width / 2 y = self.height / 2 self.setColor('red') self.setFont(size=math.log(self.width * self.height) ) self.drawText("No Data", x, y, align='center') return if self.params.get('areaAlpha'): try: self.alpha = float(self.params['areaAlpha']) except ValueError: self.alpha = 1.0 pass else: self.alpha = 1.0 self.slices = [] for name,value in self.data: self.slices.append({ 'name' : name, 'value' : value, 'percent' : value / self.total, 'color' : next(self.colors), 'alpha' : self.alpha, }) titleSize = self.defaultFontParams['size'] + math.floor( math.log(self.defaultFontParams['size']) ) self.setFont( size=titleSize ) self.setColor( self.foregroundColor ) if params.get('title'): self.drawTitle( unquote_plus(params['title']) ) self.setFont() if not params.get('hideLegend',False): elements = [ (slice['name'],slice['color'],None) for slice in self.slices ] if len(elements) > 0: self.drawLegend(elements) self.drawSlices() if params.get('valueLabelsColor'): self.valueLabelsColor = params.get('valueLabelsColor') else: self.valueLabelsColor = 'black' self.valueLabelsMin = float( params.get('valueLabelsMin',5) ) self.valueLabels = params.get('valueLabels','percent') assert self.valueLabels in self.validValueLabels, \ "valueLabels=%s must be one of %s" % (self.valueLabels,self.validValueLabels) if self.valueLabels != 'none': self.drawLabels() def drawSlices(self): theta = 3.0 * math.pi / 2.0 halfX = (self.area['xmax'] - self.area['xmin']) / 2.0 halfY = (self.area['ymax'] - self.area['ymin']) / 2.0 self.x0 = x0 = self.area['xmin'] + halfX self.y0 = y0 = self.area['ymin'] + halfY self.radius = radius = min(halfX,halfY) * 0.95 for slice in self.slices: self.setColor( slice['color'], slice['alpha'] ) self.ctx.move_to(x0,y0) phi = theta + (2 * math.pi) * slice['percent'] self.ctx.arc( x0, y0, radius, theta, phi ) self.ctx.line_to(x0,y0) self.ctx.fill() slice['midAngle'] = (theta + phi) / 2.0 slice['midAngle'] %= 2.0 * math.pi theta = phi def drawLabels(self): self.setFont() self.setColor( self.valueLabelsColor ) for slice in self.slices: if self.valueLabels == 'percent': if (slice['percent'] * 100.0) < self.valueLabelsMin: continue label = "%%%.2f" % (slice['percent'] * 100.0) elif self.valueLabels == 'number': if slice['value'] < self.valueLabelsMin: continue if slice['value'] < 10 and slice['value'] != int(slice['value']): label = "%.2f" % slice['value'] else: label = six.text_type(int(slice['value'])) theta = slice['midAngle'] x = self.x0 + (self.radius / 2.0 * math.cos(theta)) y = self.y0 + (self.radius / 2.0 * math.sin(theta)) if self.pieLabels == 'rotated': if theta > (math.pi / 2.0) and theta <= (3.0 * math.pi / 2.0): theta -= math.pi self.drawText( label, x, y, align='center', valign='middle', rotate=math.degrees(theta) ) else: self.drawText( label, x, y, align='center', valign='middle') GraphTypes = { 'line' : LineGraph, 'pie' : PieGraph, } # Convenience functions def toSeconds(t): return (t.days * 86400) + t.seconds def safeArgs(args): """Iterate over valid, finite values in an iterable. Skip any items that are None, NaN, or infinite. """ return (arg for arg in args if arg is not None and not math.isnan(arg) and not math.isinf(arg)) def safeMin(args): args = list(safeArgs(args)) if args: return min(args) def safeMax(args): args = list(safeArgs(args)) if args: return max(args) def safeSum(values): return sum(safeArgs(values)) def any(args): for arg in args: if arg: return True return False def dataLimits(data, drawNullAsZero=False, stacked=False): """Return the range of values in data as (yMinValue, yMaxValue). data is an array of TimeSeries objects. """ missingValues = any(None in series for series in data) finiteData = [series for series in data if not series.options.get('drawAsInfinite')] yMinValue = safeMin(safeMin(series) for series in finiteData) if yMinValue is None: # This can only happen if there are no valid, non-infinite data. return (0.0, 1.0) if yMinValue > 0.0 and drawNullAsZero and missingValues: yMinValue = 0.0 if stacked: length = safeMin(len(series) for series in finiteData) sumSeries = [] for i in range(0, length): sumSeries.append( safeSum(series[i] for series in finiteData) ) yMaxValue = safeMax( sumSeries ) else: yMaxValue = safeMax(safeMax(series) for series in finiteData) if yMaxValue < 0.0 and drawNullAsZero and missingValues: yMaxValue = 0.0 return (yMinValue, yMaxValue) def sort_stacked(series_list): stacked = [s for s in series_list if 'stacked' in s.options] not_stacked = [s for s in series_list if 'stacked' not in s.options] return stacked + not_stacked def format_units(v, step=None, system='si', units=None): """Format the given value in standardized units. ``system`` is either 'binary' or 'si' For more info, see: http://en.wikipedia.org/wiki/SI_prefix http://en.wikipedia.org/wiki/Binary_prefix """ if v is None: return v, '' if step is None: condition = lambda size: abs(v) >= size else: condition = lambda size: abs(v) >= size and step >= size for prefix, size in UnitSystems[system]: if condition(size): v2 = v / size if (v2 - math.floor(v2)) < 0.00000000001 and v > 1: v2 = float(math.floor(v2)) if units: prefix = "%s%s" % (prefix, units) return v2, prefix if (v - math.floor(v)) < 0.00000000001 and v > 1 : v = float(math.floor(v)) if units: prefix = units else: prefix = '' return v, prefix def find_x_times(start_dt, unit, step): if not isinstance(start_dt, datetime): raise ValueError("Invalid start_dt: %s" % start_dt) if not isinstance(step, int) or not step > 0: if not isinstance(step, float) or unit != DAY or not step > 0.0: raise ValueError("Invalid step value: %s" % step) if unit == SEC: dt = start_dt.replace(second=start_dt.second - (start_dt.second % step)) x_delta = timedelta(seconds=step) elif unit == MIN: dt = start_dt.replace(second=0, minute=start_dt.minute - (start_dt.minute % step)) x_delta = timedelta(minutes=step) elif unit == HOUR: dt = start_dt.replace(second=0, minute=0, hour=start_dt.hour - (start_dt.hour % step)) x_delta = timedelta(hours=step) elif unit == DAY: dt = start_dt.replace(second=0, minute=0, hour=0) x_delta = timedelta(days=step) else: raise ValueError("Invalid unit: %s" % unit) while dt < start_dt: dt += x_delta return (dt, x_delta) graphite-web-1.1.4/webapp/graphite/render/attime.py0000644000000000000000000001263213343334667022246 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import pytz from datetime import datetime, timedelta, datetime as datetimetype from django.conf import settings months = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'] weekdays = ['sun','mon','tue','wed','thu','fri','sat'] SECONDS_STRING = 'seconds' MINUTES_STRING = 'minutes' HOURS_STRING = 'hours' DAYS_STRING = 'days' WEEKS_STRING = 'weeks' MONTHS_STRING = 'months' YEARS_STRING = 'years' def parseATTime(s, tzinfo=None, now=None): if tzinfo is None: tzinfo = pytz.timezone(settings.TIME_ZONE) if isinstance(s, datetimetype): if s.tzinfo: return s.astimezone(tzinfo) return tzinfo.localize(s) s = s.strip().lower().replace('_','').replace(',','').replace(' ','') if s.isdigit(): if len(s) == 8 and int(s[:4]) > 1900 and int(s[4:6]) < 13 and int(s[6:]) < 32: pass #Fall back because its not a timestamp, its YYYYMMDD form else: return datetime.fromtimestamp(int(s),tzinfo) if '+' in s: ref,offset = s.split('+',1) offset = '+' + offset elif '-' in s: ref,offset = s.split('-',1) offset = '-' + offset else: ref,offset = s,'' return tzinfo.normalize(parseTimeReference(ref, tzinfo, now) + parseTimeOffset(offset)) def parseTimeReference(ref, tzinfo=None, now=None): if tzinfo is None: tzinfo = pytz.timezone(settings.TIME_ZONE) if isinstance(ref, datetimetype): if ref.tzinfo: return ref.astimezone(tzinfo) return tzinfo.localize(ref) if now is None: now = datetime.now(tzinfo) else: now = parseATTime(now, tzinfo) if not ref or ref == 'now': return now rawRef = ref # Time-of-day reference i = ref.find(':') hour,minute = 0,0 if 0 < i < 3: hour = int( ref[:i] ) minute = int( ref[i+1:i+3] ) ref = ref[i+3:] if ref[:2] == 'am': ref = ref[2:] elif ref[:2] == 'pm': hour = (hour + 12) % 24 ref = ref[2:] # Xam or XXam i = ref.find('am') if 0 < i < 3: hour = int( ref[:i] ) ref = ref[i+2:] # Xpm or XXpm i = ref.find('pm') if 0 < i < 3: hour = (int( ref[:i] ) + 12) % 24 ref = ref[i+2:] if ref.startswith('noon'): hour,minute = 12,0 ref = ref[4:] elif ref.startswith('midnight'): hour,minute = 0,0 ref = ref[8:] elif ref.startswith('teatime'): hour,minute = 16,0 ref = ref[7:] refDate = now.replace(hour=hour,minute=minute,second=0,microsecond=0,tzinfo=None) # Day reference if ref in ('yesterday','today','tomorrow'): # yesterday, today, tomorrow if ref == 'yesterday': refDate -= timedelta(days=1) elif ref == 'tomorrow': refDate += timedelta(days=1) elif ref.count('/') == 2: # MM/DD/YY[YY] m,d,y = map(int,ref.split('/')) if y < 1900: y += 1900 if y < 1970: y += 100 refDate = datetime(year=y,month=m,day=d,hour=hour,minute=minute) elif len(ref) == 8 and ref.isdigit(): # YYYYMMDD refDate = datetime(year=int(ref[:4]),month=int(ref[4:6]),day=int(ref[6:8]),hour=hour,minute=minute) elif ref[:3] in months: # MonthName DayOfMonth d = None if ref[-2:].isdigit(): d = int(ref[-2:]) elif ref[-1:].isdigit(): d = int(ref[-1:]) else: raise Exception("Day of month required after month name") refDate = datetime(year=refDate.year,month=months.index(ref[:3]) + 1,day=d,hour=hour,minute=minute) elif ref[:3] in weekdays: # DayOfWeek (Monday, etc) todayDayName = refDate.strftime("%a").lower()[:3] today = weekdays.index( todayDayName ) twoWeeks = weekdays * 2 dayOffset = today - twoWeeks.index(ref[:3]) if dayOffset < 0: dayOffset += 7 refDate -= timedelta(days=dayOffset) elif ref: raise ValueError("Unknown day reference: %s" % rawRef) return tzinfo.localize(refDate) def parseTimeOffset(offset): if not offset: return timedelta() t = timedelta() if offset[0].isdigit(): sign = 1 else: try: sign = { '+' : 1, '-' : -1 }[offset[0]] except KeyError: raise KeyError('Invalid offset: %s' % offset) offset = offset[1:] while offset: i = 1 while offset[:i].isdigit() and i <= len(offset): i += 1 num = int(offset[:i-1]) offset = offset[i-1:] i = 1 while offset[:i].isalpha() and i <= len(offset): i += 1 unit = offset[:i-1] offset = offset[i-1:] unitString = getUnitString(unit) if unitString == MONTHS_STRING: unitString = DAYS_STRING num = num * 30 if unitString == YEARS_STRING: unitString = DAYS_STRING num = num * 365 t += timedelta(**{ unitString : sign * num}) return t def getUnitString(s): if s.startswith('s'): return SECONDS_STRING if s.startswith('min'): return MINUTES_STRING if s.startswith('h'): return HOURS_STRING if s.startswith('d'): return DAYS_STRING if s.startswith('w'): return WEEKS_STRING if s.startswith('mon'): return MONTHS_STRING if s.startswith('y'): return YEARS_STRING raise Exception("Invalid offset unit '%s'" % s) graphite-web-1.1.4/webapp/graphite/render/evaluator.py0000644000000000000000000001340513343334667022764 0ustar rootroot00000000000000import re import six from graphite.errors import NormalizeEmptyResultError from graphite.functions import SeriesFunction from graphite.render.grammar import grammar from graphite.render.datalib import fetchData, TimeSeries, prefetchData def evaluateTarget(requestContext, targets): if not isinstance(targets, list): targets = [targets] pathExpressions = extractPathExpressions(requestContext, targets) prefetchData(requestContext, pathExpressions) seriesList = [] for target in targets: if not target: continue if isinstance(target, six.string_types): if not target.strip(): continue target = grammar.parseString(target) result = evaluateTokens(requestContext, target) # we have to return a list of TimeSeries objects if isinstance(result, TimeSeries): seriesList.append(result) elif result: seriesList.extend(result) return seriesList def evaluateTokens(requestContext, tokens, replacements=None, pipedArg=None): if tokens.template: arglist = dict() if tokens.template.kwargs: arglist.update(dict([(kwarg.argname, evaluateScalarTokens(kwarg.args[0])) for kwarg in tokens.template.kwargs])) if tokens.template.args: arglist.update(dict([(str(i+1), evaluateScalarTokens(arg)) for i, arg in enumerate(tokens.template.args)])) if 'template' in requestContext: arglist.update(requestContext['template']) return evaluateTokens(requestContext, tokens.template, arglist) if tokens.expression: if tokens.expression.pipedCalls: # when the expression has piped calls, we pop the right-most call and pass the remaining # expression into it via pipedArg, to get the same result as a nested call rightMost = tokens.expression.pipedCalls.pop() return evaluateTokens(requestContext, rightMost, replacements, tokens) return evaluateTokens(requestContext, tokens.expression, replacements) if tokens.pathExpression: expression = tokens.pathExpression if replacements: for name in replacements: if expression == '$'+name: val = replacements[name] if not isinstance(val, six.string_types): return val elif re.match('^-?[\d.]+$', val): return float(val) else: return val else: expression = expression.replace('$'+name, str(replacements[name])) return fetchData(requestContext, expression) if tokens.call: if tokens.call.funcname == 'template': # if template propagates down here, it means the grammar didn't match the invocation # as tokens.template. this generally happens if you try to pass non-numeric/string args raise ValueError("invalid template() syntax, only string/numeric arguments are allowed") if tokens.call.funcname == 'seriesByTag': return fetchData(requestContext, tokens.call.raw) func = SeriesFunction(tokens.call.funcname) rawArgs = tokens.call.args or [] if pipedArg is not None: rawArgs.insert(0, pipedArg) args = [evaluateTokens(requestContext, arg, replacements) for arg in rawArgs] requestContext['args'] = rawArgs kwargs = dict([(kwarg.argname, evaluateTokens(requestContext, kwarg.args[0], replacements)) for kwarg in tokens.call.kwargs]) try: return func(requestContext, *args, **kwargs) except NormalizeEmptyResultError: return [] return evaluateScalarTokens(tokens) def evaluateScalarTokens(tokens): if tokens.number: if tokens.number.integer: return int(tokens.number.integer) if tokens.number.float: return float(tokens.number.float) if tokens.number.scientific: return float(tokens.number.scientific[0]) raise ValueError("unknown numeric type in target evaluator") if tokens.string: return tokens.string[1:-1] if tokens.boolean: return tokens.boolean[0] == 'true' if tokens.none: return None raise ValueError("unknown token in target evaluator") def extractPathExpressions(requestContext, targets): # Returns a list of unique pathExpressions found in the targets list pathExpressions = set() def extractPathExpression(requestContext, tokens, replacements=None): if tokens.template: arglist = dict() if tokens.template.kwargs: arglist.update(dict([(kwarg.argname, evaluateScalarTokens(kwarg.args[0])) for kwarg in tokens.template.kwargs])) if tokens.template.args: arglist.update(dict([(str(i+1), evaluateScalarTokens(arg)) for i, arg in enumerate(tokens.template.args)])) if 'template' in requestContext: arglist.update(requestContext['template']) extractPathExpression(requestContext, tokens.template, arglist) elif tokens.expression: extractPathExpression(requestContext, tokens.expression, replacements) if tokens.expression.pipedCalls: for token in tokens.expression.pipedCalls: extractPathExpression(requestContext, token, replacements) elif tokens.pathExpression: expression = tokens.pathExpression if replacements: for name in replacements: if expression != '$'+name: expression = expression.replace('$'+name, str(replacements[name])) pathExpressions.add(expression) elif tokens.call: # if we're prefetching seriesByTag, pass the entire call back as a path expression if tokens.call.funcname == 'seriesByTag': pathExpressions.add(tokens.call.raw) else: for a in tokens.call.args: extractPathExpression(requestContext, a, replacements) for target in targets: if not target: continue if isinstance(target, six.string_types): if not target.strip(): continue target = grammar.parseString(target) extractPathExpression(requestContext, target) return list(pathExpressions) graphite-web-1.1.4/webapp/graphite/render/datalib.py0000755000000000000000000002440613343334667022370 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from __future__ import division import collections import re import time import types from six import text_type from django.conf import settings from graphite.logger import log from graphite.storage import STORE from graphite.util import timebounds, logtime class TimeSeries(list): def __init__(self, name, start, end, step, values, consolidate='average', tags=None, xFilesFactor=None, pathExpression=None): list.__init__(self, values) self.name = name self.start = start self.end = end self.step = step self.consolidationFunc = consolidate self.valuesPerPoint = 1 self.options = {} self.pathExpression = pathExpression or name self.xFilesFactor = xFilesFactor if xFilesFactor is not None else settings.DEFAULT_XFILES_FACTOR if tags: self.tags = tags else: self.tags = {'name': name} # parse for tags if a tagdb is configured and name doesn't look like a function-wrapped name try: if STORE.tagdb and not re.match('^[a-z]+[(].+[)]$', name, re.IGNORECASE): self.tags = STORE.tagdb.parse(name).tags except Exception as err: # tags couldn't be parsed, just use "name" tag log.debug("Couldn't parse tags for %s: %s" % (name, err)) def __eq__(self, other): if not isinstance(other, TimeSeries): return False if hasattr(self, 'color'): if not hasattr(other, 'color') or (self.color != other.color): return False elif hasattr(other, 'color'): return False return ((self.name, self.start, self.end, self.step, self.consolidationFunc, self.valuesPerPoint, self.options, self.xFilesFactor) == (other.name, other.start, other.end, other.step, other.consolidationFunc, other.valuesPerPoint, other.options, other.xFilesFactor)) and list.__eq__(self, other) def __iter__(self): if self.valuesPerPoint > 1: return self.__consolidatingGenerator( list.__iter__(self) ) else: return list.__iter__(self) def consolidate(self, valuesPerPoint): self.valuesPerPoint = int(valuesPerPoint) __consolidation_functions = { 'sum': sum, 'average': lambda usable: sum(usable) / len(usable), 'max': max, 'min': min, 'first': lambda usable: usable[0], 'last': lambda usable: usable[-1], } def __consolidatingGenerator(self, gen): try: cf = self.__consolidation_functions[self.consolidationFunc] except KeyError: raise Exception("Invalid consolidation function: '%s'" % self.consolidationFunc) buf = [] # only the not-None values valcnt = 0 for x in gen: valcnt += 1 if x is not None: buf.append(x) if valcnt == self.valuesPerPoint: if buf and (len(buf) / self.valuesPerPoint) >= self.xFilesFactor: yield cf(buf) else: yield None buf = [] valcnt = 0 if valcnt > 0: if buf and (len(buf) / self.valuesPerPoint) >= self.xFilesFactor: yield cf(buf) else: yield None return def __repr__(self): return 'TimeSeries(name=%s, start=%s, end=%s, step=%s, valuesPerPoint=%s, consolidationFunc=%s, xFilesFactor=%s)' % ( self.name, self.start, self.end, self.step, self.valuesPerPoint, self.consolidationFunc, self.xFilesFactor) def getInfo(self): """Pickle-friendly representation of the series""" # make sure everything is unicode in python 2.x and 3.x return { text_type('name') : text_type(self.name), text_type('start') : self.start, text_type('end') : self.end, text_type('step') : self.step, text_type('values') : list(self), text_type('pathExpression') : text_type(self.pathExpression), text_type('valuesPerPoint') : self.valuesPerPoint, text_type('consolidationFunc'): text_type(self.consolidationFunc), text_type('xFilesFactor') : self.xFilesFactor, } def copy(self, name=None, start=None, end=None, step=None, values=None, consolidate=None, tags=None, xFilesFactor=None): return TimeSeries( name if name is not None else self.name, start if start is not None else self.start, end if end is not None else self.end, step if step is not None else self.step, values if values is not None else self.values, consolidate=consolidate if consolidate is not None else self.consolidationFunc, tags=tags if tags is not None else self.tags, xFilesFactor=xFilesFactor if xFilesFactor is not None else self.xFilesFactor ) def datapoints(self): timestamps = range(int(self.start), int(self.end) + 1, int(self.step * self.valuesPerPoint)) return list(zip(self, timestamps)) # Data retrieval API @logtime def fetchData(requestContext, pathExpr, timer=None): timer.set_msg("lookup and merge of \"%s\" took" % str(pathExpr)) seriesList = {} (startTime, endTime, now) = timebounds(requestContext) prefetched = requestContext.get('prefetched', {}).get((startTime, endTime, now), {}).get(pathExpr) if not prefetched: return [] return _merge_results(pathExpr, startTime, endTime, prefetched, seriesList, requestContext) def _merge_results(pathExpr, startTime, endTime, prefetched, seriesList, requestContext): log.debug("render.datalib.fetchData :: starting to merge") # Used as a cache to avoid recounting series None values below. series_best_nones = {} for path, results in prefetched: if not results: log.debug("render.datalib.fetchData :: no results for %s.fetch(%s, %s)" % (path, startTime, endTime)) continue try: (timeInfo, values) = results except ValueError as e: raise Exception("could not parse timeInfo/values from metric '%s': %s" % (path, e)) (start, end, step) = timeInfo series = TimeSeries(path, start, end, step, values, xFilesFactor=requestContext.get('xFilesFactor')) # hack to pass expressions through to render functions series.pathExpression = pathExpr if series.name in seriesList: # This counts the Nones in each series, and is unfortunately O(n) for each # series, which may be worth further optimization. The value of doing this # at all is to avoid the "flipping" effect of loading a graph multiple times # and having inconsistent data returned if one of the backing stores has # inconsistent data. This is imperfect as a validity test, but in practice # nicely keeps us using the "most complete" dataset available. Think of it # as a very weak CRDT resolver. candidate_nones = 0 if not settings.REMOTE_STORE_MERGE_RESULTS: candidate_nones = len( [val for val in values if val is None]) known = seriesList[series.name] # To avoid repeatedly recounting the 'Nones' in series we've already seen, # cache the best known count so far in a dict. if known.name in series_best_nones: known_nones = series_best_nones[known.name] else: known_nones = len([val for val in known if val is None]) series_best_nones[known.name] = known_nones if known_nones > candidate_nones and len(series): if settings.REMOTE_STORE_MERGE_RESULTS and len(series) == len(known): # This series has potential data that might be missing from # earlier series. Attempt to merge in useful data and update # the cache count. log.debug("Merging multiple TimeSeries for %s" % known.name) for i, j in enumerate(known): if j is None and series[i] is not None: known[i] = series[i] known_nones -= 1 # Store known_nones in our cache series_best_nones[known.name] = known_nones else: # Not merging data - # we've found a series better than what we've already seen. Update # the count cache and replace the given series in the array. series_best_nones[known.name] = candidate_nones seriesList[known.name] = series else: # If we looked at this series above, and it matched a 'known' # series already, then it's already in the series list (or ignored). # If not, append it here. seriesList[series.name] = series # Stabilize the order of the results by ordering the resulting series by name. # This returns the result ordering to the behavior observed pre PR#1010. return [seriesList[k] for k in sorted(seriesList)] def prefetchData(requestContext, pathExpressions): """Prefetch a bunch of path expressions and stores them in the context. The idea is that this will allow more batching than doing a query each time evaluateTarget() needs to fetch a path. All the prefetched data is stored in the requestContext, to be accessed later by fetchData. """ if not pathExpressions: return start = time.time() log.debug("Fetching data for [%s]" % (', '.join(pathExpressions))) (startTime, endTime, now) = timebounds(requestContext) prefetched = collections.defaultdict(list) for result in STORE.fetch(pathExpressions, startTime, endTime, now, requestContext): if result is None: continue prefetched[result['pathExpression']].append(( result['name'], ( result['time_info'], result['values'], ), )) # Several third-party readers including rrdtool and biggraphite return values in a # generator which can only be iterated on once. These must be converted to a list. for pathExpression, items in prefetched.items(): for i, (name, (time_info, values)) in enumerate(items): if isinstance(values, types.GeneratorType): prefetched[pathExpression][i] = (name, (time_info, list(values))) if not requestContext.get('prefetched'): requestContext['prefetched'] = {} requestContext['prefetched'][(startTime, endTime, now)] = prefetched log.rendering("Fetched data for [%s] in %fs" % (', '.join(pathExpressions), time.time() - start)) graphite-web-1.1.4/webapp/graphite/render/urls.py0000644000000000000000000000154713343334667021753 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/local/?$', views.renderLocalView, name='render_local'), url(r'^/~(?P[^/]+)/(?P[^/]+)/?$', views.renderMyGraphView, name='render_my_graph'), url(r'^/?$', views.renderView, name='render'), ] graphite-web-1.1.4/webapp/graphite/render/views.py0000755000000000000000000005031613343334667022124 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import csv import math import pytz import six.moves.http_client from datetime import datetime from time import time from random import shuffle from six.moves.urllib.parse import urlencode, urlsplit, urlunsplit from cgi import parse_qs from graphite.compat import HttpResponse from graphite.user_util import getProfileByUsername from graphite.util import json, unpickle, pickle, msgpack, BytesIO from graphite.storage import extractForwardHeaders from graphite.logger import log from graphite.render.evaluator import evaluateTarget from graphite.render.attime import parseATTime from graphite.functions import loadFunctions, PieFunction from graphite.render.hashing import hashRequest, hashData from graphite.render.glyph import GraphTypes from graphite.tags.models import Series, Tag, TagValue, SeriesTag # noqa # pylint: disable=unused-import from django.http import HttpResponseServerError, HttpResponseRedirect from django.template import Context, loader from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.utils.cache import add_never_cache_headers, patch_response_headers from six.moves import zip loadFunctions() def renderView(request): start = time() (graphOptions, requestOptions) = parseOptions(request) useCache = 'noCache' not in requestOptions cacheTimeout = requestOptions['cacheTimeout'] # TODO: Make that a namedtuple or a class. requestContext = { 'startTime' : requestOptions['startTime'], 'endTime' : requestOptions['endTime'], 'now': requestOptions['now'], 'localOnly' : requestOptions['localOnly'], 'template' : requestOptions['template'], 'tzinfo' : requestOptions['tzinfo'], 'forwardHeaders': requestOptions['forwardHeaders'], 'data' : [], 'prefetched' : {}, 'xFilesFactor' : requestOptions['xFilesFactor'], } data = requestContext['data'] response = None # First we check the request cache if useCache: requestKey = hashRequest(request) response = cache.get(requestKey) if response: log.cache('Request-Cache hit [%s]' % requestKey) log.rendering('Returned cached response in %.6f' % (time() - start)) return response log.cache('Request-Cache miss [%s]' % requestKey) # Now we prepare the requested data if requestOptions['graphType'] == 'pie': for target in requestOptions['targets']: if target.find(':') >= 0: try: name, value = target.split(':', 1) value = float(value) except ValueError: raise ValueError("Invalid target '%s'" % target) data.append((name, value)) else: seriesList = evaluateTarget(requestContext, target) for series in seriesList: func = PieFunction(requestOptions['pieMode']) data.append( (series.name, func(requestContext, series) or 0 )) elif requestOptions['graphType'] == 'line': # Let's see if at least our data is cached cachedData = None if useCache: targets = requestOptions['targets'] startTime = requestOptions['startTime'] endTime = requestOptions['endTime'] dataKey = hashData(targets, startTime, endTime, requestOptions['xFilesFactor']) cachedData = cache.get(dataKey) if cachedData: log.cache("Data-Cache hit [%s]" % dataKey) else: log.cache("Data-Cache miss [%s]" % dataKey) if cachedData is not None: requestContext['data'] = data = cachedData else: # Have to actually retrieve the data now targets = requestOptions['targets'] data.extend(evaluateTarget(requestContext, targets)) if useCache: cache.add(dataKey, data, cacheTimeout) renderStart = time() format = requestOptions.get('format') if format == 'csv': response = renderViewCsv(requestOptions, data) elif format == 'json': response = renderViewJson(requestOptions, data) elif format == 'dygraph': response = renderViewDygraph(requestOptions, data) elif format == 'rickshaw': response = renderViewRickshaw(requestOptions, data) elif format == 'raw': response = renderViewRaw(requestOptions, data) elif format == 'pickle': response = renderViewPickle(requestOptions, data) elif format == 'msgpack': response = renderViewMsgPack(requestOptions, data) # if response wasn't generated above, render a graph image if not response: format = 'image' renderStart = time() response = renderViewGraph(graphOptions, requestOptions, data) if useCache: cache.add(requestKey, response, cacheTimeout) patch_response_headers(response, cache_timeout=cacheTimeout) else: add_never_cache_headers(response) log.rendering('%s rendering time %6f' % (format, time() - renderStart)) log.rendering('Total request processing time %6f' % (time() - start)) return response def renderViewGraph(graphOptions, requestOptions, data): # We've got the data, now to render it graphOptions['data'] = data if settings.REMOTE_RENDERING: # Rendering on other machines is faster in some situations image = delegateRendering(requestOptions['graphType'], graphOptions, requestOptions['forwardHeaders']) else: image = doImageRender(requestOptions['graphClass'], graphOptions) if graphOptions['outputFormat'] == 'pdf': return buildResponse(image, 'application/x-pdf') if graphOptions['outputFormat'] == 'svg': if 'jsonp' in requestOptions: return HttpResponse( content="%s(%s)" % (requestOptions['jsonp'], json.dumps(image)), content_type='text/javascript') return buildResponse(image, 'image/svg+xml') return buildResponse(image, 'image/png') def renderViewCsv(requestOptions, data): response = HttpResponse(content_type='text/csv') writer = csv.writer(response, dialect='excel') for series in data: for i, value in enumerate(series): timestamp = datetime.fromtimestamp(series.start + (i * series.step), requestOptions['tzinfo']) writer.writerow((series.name, timestamp.strftime("%Y-%m-%d %H:%M:%S"), value)) return response def renderViewJson(requestOptions, data): series_data = [] if any(data): startTime = min([series.start for series in data]) endTime = max([series.end for series in data]) timeRange = endTime - startTime for series in data: if 'maxDataPoints' in requestOptions: maxDataPoints = requestOptions['maxDataPoints'] if maxDataPoints == 1: series.consolidate(len(series)) else: numberOfDataPoints = timeRange/series.step if maxDataPoints < numberOfDataPoints: valuesPerPoint = math.ceil(float(numberOfDataPoints) / float(maxDataPoints)) secondsPerPoint = int(valuesPerPoint * series.step) # Nudge start over a little bit so that the consolidation bands align with each call # removing 'jitter' seen when refreshing. nudge = secondsPerPoint + (series.start % series.step) - (series.start % secondsPerPoint) series.start = series.start + nudge valuesToLose = int(nudge/series.step) for r in range(1, valuesToLose): del series[0] series.consolidate(valuesPerPoint) datapoints = series.datapoints() if 'noNullPoints' in requestOptions: datapoints = [ point for point in datapoints if point[0] is not None and not math.isnan(point[0]) ] if not datapoints: continue series_data.append(dict(target=series.name, tags=series.tags, datapoints=datapoints)) output = json.dumps(series_data, indent=(2 if requestOptions.get('pretty') else None)).replace('None,', 'null,').replace('NaN,', 'null,').replace('Infinity,', '1e9999,') if 'jsonp' in requestOptions: response = HttpResponse( content="%s(%s)" % (requestOptions['jsonp'], output), content_type='text/javascript') else: response = HttpResponse( content=output, content_type='application/json') return response def renderViewDygraph(requestOptions, data): labels = ['Time'] output = '{}' if data: datapoints = [[ts] for ts in range(data[0].start, data[0].end, data[0].step)] for series in data: labels.append(series.name) for i, point in enumerate(series): if point is None: point = 'null' elif point == float('inf'): point = 'Infinity' elif point == float('-inf'): point = '-Infinity' elif math.isnan(point): point = 'null' datapoints[i].append(point) line_template = '[%%s000%s]' % ''.join([', %s'] * len(data)) lines = [line_template % tuple(points) for points in datapoints] output = '{"labels" : %s, "data" : [%s]}' % (json.dumps(labels), ', '.join(lines)) if 'jsonp' in requestOptions: response = HttpResponse( content="%s(%s)" % (requestOptions['jsonp'], output), content_type='text/javascript') else: response = HttpResponse( content=output, content_type='application/json') return response def renderViewRickshaw(requestOptions, data): series_data = [] for series in data: timestamps = range(series.start, series.end, series.step) datapoints = [{'x' : x, 'y' : y} for x, y in zip(timestamps, series)] series_data.append( dict(target=series.name, datapoints=datapoints) ) output = json.dumps(series_data, indent=(2 if requestOptions.get('pretty') else None)) if 'jsonp' in requestOptions: response = HttpResponse( content="%s(%s)" % (requestOptions['jsonp'], output), content_type='text/javascript') else: response = HttpResponse( content=output, content_type='application/json') return response def renderViewRaw(requestOptions, data): response = HttpResponse(content_type='text/plain') for series in data: response.write( "%s,%d,%d,%d|" % (series.name, series.start, series.end, series.step) ) response.write( ','.join(map(repr,series)) ) response.write('\n') return response def renderViewPickle(requestOptions, data): response = HttpResponse(content_type='application/pickle') seriesInfo = [series.getInfo() for series in data] pickle.dump(seriesInfo, response, protocol=-1) return response def renderViewMsgPack(requestOptions, data): response = HttpResponse(content_type='application/x-msgpack') seriesInfo = [series.getInfo() for series in data] msgpack.dump(seriesInfo, response, use_bin_type=True) return response def parseOptions(request): queryParams = request.GET.copy() queryParams.update(request.POST) # Start with some defaults graphOptions = {'width' : 330, 'height' : 250} requestOptions = {} graphType = queryParams.get('graphType','line') if graphType not in GraphTypes: raise AssertionError("Invalid graphType '%s', must be one of %s" % (graphType,list(GraphTypes))) graphClass = GraphTypes[graphType] # Fill in the requestOptions requestOptions['graphType'] = graphType requestOptions['graphClass'] = graphClass requestOptions['pieMode'] = queryParams.get('pieMode', 'average') cacheTimeout = int( queryParams.get('cacheTimeout', settings.DEFAULT_CACHE_DURATION) ) requestOptions['targets'] = [] requestOptions['forwardHeaders'] = extractForwardHeaders(request) # Extract the targets out of the queryParams mytargets = [] # Normal format: ?target=path.1&target=path.2 if len(queryParams.getlist('target')) > 0: mytargets = queryParams.getlist('target') # Rails/PHP/jQuery common practice format: ?target[]=path.1&target[]=path.2 elif len(queryParams.getlist('target[]')) > 0: mytargets = queryParams.getlist('target[]') # Collect the targets for target in mytargets: requestOptions['targets'].append(target) template = dict() for key, val in queryParams.items(): if key.startswith("template["): template[key[9:-1]] = val requestOptions['template'] = template if 'pickle' in queryParams: requestOptions['format'] = 'pickle' if 'rawData' in queryParams: requestOptions['format'] = 'raw' if 'format' in queryParams: requestOptions['format'] = queryParams['format'] if 'jsonp' in queryParams: requestOptions['jsonp'] = queryParams['jsonp'] requestOptions['pretty'] = bool(queryParams.get('pretty')) if 'noCache' in queryParams: requestOptions['noCache'] = True if 'maxDataPoints' in queryParams and queryParams['maxDataPoints'].isdigit(): requestOptions['maxDataPoints'] = int(queryParams['maxDataPoints']) if 'noNullPoints' in queryParams: requestOptions['noNullPoints'] = True requestOptions['localOnly'] = queryParams.get('local') == '1' # Fill in the graphOptions format = requestOptions.get('format') if format == 'svg': graphOptions['outputFormat'] = 'svg' elif format == 'pdf': graphOptions['outputFormat'] = 'pdf' else: graphOptions['outputFormat'] = 'png' for opt in graphClass.customizable: if opt in queryParams: val = queryParams[opt] if (val.isdigit() or (val.startswith('-') and val[1:].isdigit())) and 'color' not in opt.lower(): val = int(val) elif '.' in val and (val.replace('.','',1).isdigit() or (val.startswith('-') and val[1:].replace('.','',1).isdigit())): val = float(val) elif val.lower() in ('true','false'): val = val.lower() == 'true' elif val.lower() == 'default' or val == '': continue graphOptions[opt] = val tzinfo = pytz.timezone(settings.TIME_ZONE) if 'tz' in queryParams: try: tzinfo = pytz.timezone(queryParams['tz']) except pytz.UnknownTimeZoneError: pass requestOptions['tzinfo'] = tzinfo # Get the time interval for time-oriented graph types if graphType == 'line' or graphType == 'pie': if 'now' in queryParams: now = parseATTime(queryParams['now'], tzinfo) else: now = datetime.now(tzinfo) if 'until' in queryParams: untilTime = parseATTime(queryParams['until'], tzinfo, now) else: untilTime = now if 'from' in queryParams: fromTime = parseATTime(queryParams['from'], tzinfo, now) else: fromTime = parseATTime('-1d', tzinfo, now) startTime = min(fromTime, untilTime) endTime = max(fromTime, untilTime) assert startTime != endTime, "Invalid empty time range" requestOptions['startTime'] = startTime requestOptions['endTime'] = endTime timeRange = endTime - startTime queryTime = timeRange.days * 86400 + timeRange.seconds # convert the time delta to seconds if settings.DEFAULT_CACHE_POLICY and not queryParams.get('cacheTimeout'): timeouts = [timeout for period,timeout in settings.DEFAULT_CACHE_POLICY if period <= queryTime] cacheTimeout = max(timeouts or (0,)) requestOptions['now'] = now if cacheTimeout == 0: requestOptions['noCache'] = True requestOptions['cacheTimeout'] = cacheTimeout requestOptions['xFilesFactor'] = float( queryParams.get('xFilesFactor', settings.DEFAULT_XFILES_FACTOR) ) return (graphOptions, requestOptions) connectionPools = {} def connector_class_selector(https_support=False): return six.moves.http_client.HTTPSConnection if https_support else six.moves.http_client.HTTPConnection def delegateRendering(graphType, graphOptions, headers=None): if headers is None: headers = {} start = time() postData = graphType + '\n' + pickle.dumps(graphOptions) servers = settings.RENDERING_HOSTS[:] #make a copy so we can shuffle it safely shuffle(servers) connector_class = connector_class_selector(settings.INTRACLUSTER_HTTPS) for server in servers: start2 = time() try: # Get a connection try: pool = connectionPools[server] except KeyError: #happens the first time pool = connectionPools[server] = set() try: connection = pool.pop() except KeyError: #No available connections, have to make a new one connection = connector_class(server) connection.timeout = settings.REMOTE_RENDER_CONNECT_TIMEOUT # Send the request try: connection.request('POST','/render/local/', postData, headers) except six.moves.http_client.CannotSendRequest: connection = connector_class(server) #retry once connection.timeout = settings.REMOTE_RENDER_CONNECT_TIMEOUT connection.request('POST', '/render/local/', postData, headers) # Read the response try: # Python 2.7+, use buffering of HTTP responses response = connection.getresponse(buffering=True) except TypeError: # Python 2.6 and older response = connection.getresponse() assert response.status == 200, "Bad response code %d from %s" % (response.status,server) contentType = response.getheader('Content-Type') imageData = response.read() assert contentType == 'image/png', "Bad content type: \"%s\" from %s" % (contentType,server) assert imageData, "Received empty response from %s" % server # Wrap things up log.rendering('Remotely rendered image on %s in %.6f seconds' % (server,time() - start2)) log.rendering('Spent a total of %.6f seconds doing remote rendering work' % (time() - start)) pool.add(connection) return imageData except Exception: log.exception("Exception while attempting remote rendering request on %s" % server) log.rendering('Exception while remotely rendering on %s wasted %.6f' % (server,time() - start2)) continue def renderLocalView(request): try: start = time() reqParams = BytesIO(request.body) graphType = reqParams.readline().strip() optionsPickle = reqParams.read() reqParams.close() graphClass = GraphTypes[graphType] options = unpickle.loads(optionsPickle) image = doImageRender(graphClass, options) log.rendering("Delegated rendering request took %.6f seconds" % (time() - start)) response = buildResponse(image) add_never_cache_headers(response) return response except Exception: log.exception("Exception in graphite.render.views.rawrender") return HttpResponseServerError() def renderMyGraphView(request,username,graphName): profile = getProfileByUsername(username) if not profile: return errorPage("No such user '%s'" % username) try: graph = profile.mygraph_set.get(name=graphName) except ObjectDoesNotExist: return errorPage("User %s doesn't have a MyGraph named '%s'" % (username,graphName)) request_params = request.GET.copy() request_params.update(request.POST) if request_params: url_parts = urlsplit(graph.url) query_string = url_parts[3] if query_string: url_params = parse_qs(query_string) # Remove lists so that we can do an update() on the dict for param, value in url_params.items(): if isinstance(value, list) and param != 'target': url_params[param] = value[-1] url_params.update(request_params) # Handle 'target' being a list - we want duplicate &target params out of it url_param_pairs = [] for key,val in url_params.items(): if isinstance(val, list): for v in val: url_param_pairs.append( (key,v) ) else: url_param_pairs.append( (key,val) ) query_string = urlencode(url_param_pairs) url = urlunsplit(url_parts[:3] + (query_string,) + url_parts[4:]) else: url = graph.url return HttpResponseRedirect(url) def doImageRender(graphClass, graphOptions): pngData = BytesIO() t = time() img = graphClass(**graphOptions) img.output(pngData) log.rendering('Rendered PNG in %.6f seconds' % (time() - t)) imageData = pngData.getvalue() pngData.close() return imageData def buildResponse(imageData, content_type="image/png"): return HttpResponse(imageData, content_type=content_type) def errorPage(message): template = loader.get_template('500.html') context = Context(dict(message=message)) return HttpResponseServerError( template.render(context) ) graphite-web-1.1.4/webapp/graphite/__init__.py0000644000000000000000000000006513343334667021240 0ustar rootroot00000000000000# Two wrongs don't make a right, but three lefts do. graphite-web-1.1.4/webapp/graphite/whitelist/0000755000000000000000000000000013343335472021135 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/whitelist/__init__.py0000644000000000000000000000000013343334667023241 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/whitelist/urls.py0000644000000000000000000000144313343334667022503 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/add$', views.add, name='whitelist_add'), url(r'^/remove$', views.remove, name='whitelist_remove'), url(r'^/?$', views.show, name='whitelist_show'), ] graphite-web-1.1.4/webapp/graphite/whitelist/views.py0000644000000000000000000000371513343334667022657 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import os import pickle from random import randint from django.conf import settings from graphite.compat import HttpResponse from graphite.util import unpickle def add(request): metrics = set( request.POST['metrics'].split() ) whitelist = load_whitelist() new_whitelist = whitelist | metrics save_whitelist(new_whitelist) return HttpResponse(content_type="text/plain", content="OK") def remove(request): metrics = set( request.POST['metrics'].split() ) whitelist = load_whitelist() new_whitelist = whitelist - metrics save_whitelist(new_whitelist) return HttpResponse(content_type="text/plain", content="OK") def show(request): whitelist = load_whitelist() members = '\n'.join( sorted(whitelist) ) return HttpResponse(content_type="text/plain", content=members) def load_whitelist(): buffer = open(settings.WHITELIST_FILE, 'rb').read() whitelist = unpickle.loads(buffer) return whitelist def save_whitelist(whitelist): serialized = pickle.dumps(whitelist, protocol=-1) #do this instead of dump() to raise potential exceptions before open() tmpfile = '%s-%d' % (settings.WHITELIST_FILE, randint(0, 100000)) try: fh = open(tmpfile, 'wb') fh.write(serialized) fh.close() if os.path.exists(settings.WHITELIST_FILE): os.unlink(settings.WHITELIST_FILE) os.rename(tmpfile, settings.WHITELIST_FILE) finally: if os.path.exists(tmpfile): os.unlink(tmpfile) graphite-web-1.1.4/webapp/graphite/dashboard/0000755000000000000000000000000013343335472021050 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/dashboard/__init__.py0000644000000000000000000000000013343334667023154 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/dashboard/migrations/0000755000000000000000000000000013343335472023224 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/dashboard/migrations/__init__.py0000644000000000000000000000000013343334667025330 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/dashboard/migrations/0001_initial.py0000644000000000000000000000176113343334667025701 0ustar rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-14 11:22 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ('account', '0001_initial'), ] operations = [ migrations.CreateModel( name='Dashboard', fields=[ ('name', models.CharField(max_length=128, primary_key=True, serialize=False)), ('state', models.TextField()), ('owners', models.ManyToManyField(related_name='dashboards', to='account.Profile')), ], ), migrations.CreateModel( name='Template', fields=[ ('name', models.CharField(max_length=128, primary_key=True, serialize=False)), ('state', models.TextField()), ('owners', models.ManyToManyField(related_name='templates', to='account.Profile')), ], ), ] graphite-web-1.1.4/webapp/graphite/dashboard/admin.py0000644000000000000000000000016113343334667022515 0ustar rootroot00000000000000from django.contrib import admin from graphite.dashboard.models import Dashboard admin.site.register(Dashboard) graphite-web-1.1.4/webapp/graphite/dashboard/models.py0000644000000000000000000000256613343334667022723 0ustar rootroot00000000000000from django.db import models from graphite.account.models import Profile from graphite.util import json import six class Dashboard(models.Model): name = models.CharField(primary_key=True, max_length=128) owners = models.ManyToManyField(Profile, related_name='dashboards') state = models.TextField() __str__ = lambda self: "Dashboard [%s]" % self.name class Template(models.Model): class Admin: pass name = models.CharField(primary_key=True, max_length=128) owners = models.ManyToManyField(Profile, related_name='templates') state = models.TextField() __str__ = lambda self: "Template [%s]" % self.name def loadState(self, val): return self.state.replace('__VALUE__', val) def setState(self, state, key): #XXX Might not need this def replace_string(s): if isinstance(s, six.text_type): s = s.replace(key, '__VALUE__') return s def update_graph(graph): graph_opts = graph[1] graph_opts['target'] = [replace_string(s) for s in graph_opts['target']] return [replace_string(graph[0]), graph_opts, replace_string(graph[2])] # Parse JSON here and replace first five elements of target with __VALUE__ parsed_state = json.loads(state) for i, graph in enumerate(parsed_state['graphs']): parsed_state['graphs'][i] = update_graph(graph) self.state = json.dumps(parsed_state) graphite-web-1.1.4/webapp/graphite/dashboard/send_graph.py0000644000000000000000000000127313343334667023544 0ustar rootroot00000000000000from django.core.mail import EmailMessage def send_graph_email(subject, sender, recipients, attachments=None, body=None): """ :param str sender: sender's email address :param list recipients: list of recipient emails :param list attachments: list of triples of the form: (filename, content, mimetype). See the django docs https://docs.djangoproject.com/en/1.3/topics/email/#django.core.mail.EmailMessage """ attachments = attachments or [] msg = EmailMessage(subject=subject, from_email=sender, to=recipients, body=body, attachments=attachments) msg.send() graphite-web-1.1.4/webapp/graphite/dashboard/urls.py0000644000000000000000000000266713343334667022427 0ustar rootroot00000000000000from django.conf.urls import url from . import views urlpatterns = [ url(r'^/save/(?P[^/]+)$', views.save, name='dashboard_save'), url(r'^/save_template/(?P[^/]+)/(?P[^/]+)$', views.save_template, name='dashboard_save_template'), url(r'^/load/(?P[^/]+)$', views.load, name='dashboard_load'), url(r'^/load/(?P[^/]+)/(?P[^/]+)$', views.load_template, name='dashboard_load_template'), url(r'^/load_template/(?P[^/]+)/(?P[^/]+)$', views.load_template, name='dashboard_load_template'), url(r'^/delete/(?P[^/]+)$', views.delete, name='dashboard_delete'), url(r'^/create-temporary/?$', views.create_temporary, name='dashboard_create_temporary'), url(r'^/email$', views.email, name='dashboard_email'), url(r'^/find/?$', views.find, name='dashboard_find'), url(r'^/delete_template/(?P[^/]+)$', views.delete_template, name='dashboard_delete_template'), url(r'^/find_template/?$', views.find_template, name='dashboard_find_template'), url(r'^/login/?$', views.user_login, name='dashboard_login'), url(r'^/logout/?$', views.user_logout, name='dashboard_logout'), url(r'^/help/?$', views.help, name='dashboard_help'), url(r'^/(?P[^/]+)/(?P[^/]+)$', views.template, name='dashboard_template'), url(r'^/(?P[^/]+)$', views.dashboard, name='dashboard'), url(r'^/?$', views.dashboard, name='dashboard'), ] graphite-web-1.1.4/webapp/graphite/dashboard/views.py0000644000000000000000000003121213343334667022563 0ustar rootroot00000000000000import re import errno from os.path import getmtime from six.moves.urllib.parse import urlencode from six.moves.configparser import ConfigParser from django.shortcuts import render_to_response from django.http import QueryDict from django.conf import settings from django.contrib.auth import login, authenticate, logout from django.contrib.staticfiles import finders from django.utils.safestring import mark_safe from graphite.compat import HttpResponse from graphite.dashboard.models import Dashboard, Template from graphite.dashboard.send_graph import send_graph_email from graphite.render.views import renderView from graphite.util import json from graphite.user_util import isAuthenticated fieldRegex = re.compile(r'<([^>]+)>') defaultScheme = { 'name' : 'Everything', 'pattern' : '', 'fields' : [ dict(name='category', label='Category') ], } defaultUIConfig = { 'default_graph_width' : 400, 'default_graph_height' : 250, 'refresh_interval' : 60, 'autocomplete_delay' : 375, 'merge_hover_delay' : 700, 'theme' : 'default', } defaultKeyboardShortcuts = { 'toggle_toolbar' : 'ctrl-z', 'toggle_metrics_panel' : 'ctrl-space', 'erase_all_graphs' : 'alt-x', 'save_dashboard' : 'alt-s', 'completer_add_metrics' : 'alt-enter', 'completer_del_metrics' : 'alt-backspace', 'give_completer_focus' : 'shift-space', } ALL_PERMISSIONS = ['change', 'delete'] class DashboardConfig: def __init__(self): self.last_read = 0 self.schemes = [defaultScheme] self.ui_config = defaultUIConfig.copy() def check(self): if getmtime(settings.DASHBOARD_CONF) > self.last_read: self.load() def load(self): schemes = [defaultScheme] parser = ConfigParser() parser.read(settings.DASHBOARD_CONF) for option, default_value in defaultUIConfig.items(): if parser.has_option('ui', option): try: self.ui_config[option] = parser.getint('ui', option) except ValueError: self.ui_config[option] = parser.get('ui', option) else: self.ui_config[option] = default_value if parser.has_option('ui', 'automatic_variants'): self.ui_config['automatic_variants'] = parser.getboolean('ui', 'automatic_variants') else: self.ui_config['automatic_variants'] = True self.ui_config['keyboard_shortcuts'] = defaultKeyboardShortcuts.copy() if parser.has_section('keyboard-shortcuts'): self.ui_config['keyboard_shortcuts'].update( parser.items('keyboard-shortcuts') ) for section in parser.sections(): if section in ('ui', 'keyboard-shortcuts'): continue scheme = parser.get(section, 'scheme') fields = [] for match in fieldRegex.finditer(scheme): field = match.group(1) if parser.has_option(section, '%s.label' % field): label = parser.get(section, '%s.label' % field) else: label = field fields.append({ 'name' : field, 'label' : label }) schemes.append({ 'name' : section, 'pattern' : scheme, 'fields' : fields, }) self.schemes = schemes config = DashboardConfig() def dashboard(request, name=None): dashboard_conf_missing = False try: config.check() except OSError as e: if e.errno == errno.ENOENT: dashboard_conf_missing = True else: raise initialError = None debug = request.GET.get('debug', False) theme = request.GET.get('theme', config.ui_config['theme']) css_file = finders.find('css/dashboard-%s.css' % theme) if css_file is None: initialError = "Invalid theme '%s'" % theme theme = config.ui_config['theme'] context = { 'schemes_json': mark_safe(json.dumps(config.schemes)), 'ui_config_json': mark_safe(json.dumps(config.ui_config)), 'jsdebug': debug or settings.JAVASCRIPT_DEBUG, 'debug': debug, 'theme': theme, 'initialError': initialError, 'querystring': mark_safe(json.dumps(dict(request.GET.items()))), 'dashboard_conf_missing': dashboard_conf_missing, 'userName': '', 'permissions': mark_safe(json.dumps(getPermissions(request.user))), 'permissionsUnauthenticated': mark_safe(json.dumps(getPermissions(None))) } user = request.user if user: context['userName'] = user.username if name is not None: try: dashboard = Dashboard.objects.get(name=name) except Dashboard.DoesNotExist: context['initialError'] = "Dashboard '%s' does not exist." % name else: context['initialState'] = dashboard.state return render_to_response("dashboard.html", context) def template(request, name, val): template_conf_missing = False try: config.check() except OSError as e: if e.errno == errno.ENOENT: template_conf_missing = True else: raise initialError = None debug = request.GET.get('debug', False) theme = request.GET.get('theme', config.ui_config['theme']) css_file = finders.find('css/dashboard-%s.css' % theme) if css_file is None: initialError = "Invalid theme '%s'" % theme theme = config.ui_config['theme'] context = { 'schemes_json' : json.dumps(config.schemes), 'ui_config_json' : json.dumps(config.ui_config), 'jsdebug' : debug or settings.JAVASCRIPT_DEBUG, 'debug' : debug, 'theme' : theme, 'initialError' : initialError, 'querystring' : json.dumps( dict( request.GET.items() ) ), 'template_conf_missing' : template_conf_missing, 'userName': '', 'permissions': json.dumps(getPermissions(request.user)), 'permissionsUnauthenticated': json.dumps(getPermissions(None)) } user = request.user if user: context['userName'] = user.username try: template = Template.objects.get(name=name) except Template.DoesNotExist: context['initialError'] = "Template '%s' does not exist." % name else: state = json.loads(template.loadState(val)) state['name'] = '%s/%s' % (name, val) context['initialState'] = json.dumps(state) return render_to_response("dashboard.html", context) def getPermissions(user): """Return [change, delete] based on authorisation model and user privileges/groups""" if user and not isAuthenticated(user): user = None if not settings.DASHBOARD_REQUIRE_AUTHENTICATION: return ALL_PERMISSIONS # don't require login if not user: return [] # from here on, we have a user permissions = ALL_PERMISSIONS if settings.DASHBOARD_REQUIRE_PERMISSIONS: permissions = [permission for permission in ALL_PERMISSIONS if user.has_perm('dashboard.%s_dashboard' % permission)] editGroup = settings.DASHBOARD_REQUIRE_EDIT_GROUP if editGroup and len(user.groups.filter(name = editGroup)) == 0: permissions = [] return permissions def save(request, name): if 'change' not in getPermissions(request.user): return json_response( dict(error="Must be logged in with appropriate permissions to save") ) # Deserialize and reserialize as a validation step state = str( json.dumps( json.loads( request.POST['state'] ) ) ) try: dashboard = Dashboard.objects.get(name=name) except Dashboard.DoesNotExist: dashboard = Dashboard.objects.create(name=name, state=state) else: dashboard.state = state dashboard.save(); return json_response( dict(success=True) ) def save_template(request, name, key): if 'change' not in getPermissions(request.user): return json_response( dict(error="Must be logged in with appropriate permissions to save the template") ) # Deserialize and reserialize as a validation step state = str( json.dumps( json.loads( request.POST['state'] ) ) ) try: template = Template.objects.get(name=name) except Template.DoesNotExist: template = Template.objects.create(name=name) template.setState(state, key) template.save() else: template.setState(state, key) template.save(); return json_response( dict(success=True) ) def load(request, name): try: dashboard = Dashboard.objects.get(name=name) except Dashboard.DoesNotExist: return json_response( dict(error="Dashboard '%s' does not exist. " % name) ) return json_response( dict(state=json.loads(dashboard.state)) ) def load_template(request, name, val): try: template = Template.objects.get(name=name) except Template.DoesNotExist: return json_response( dict(error="Template '%s' does not exist. " % name) ) state = json.loads(template.loadState(val)) state['name'] = '%s/%s' % (name, val) return json_response( dict(state=state) ) def delete(request, name): if 'delete' not in getPermissions(request.user): return json_response( dict(error="Must be logged in with appropriate permissions to delete") ) try: dashboard = Dashboard.objects.get(name=name) except Dashboard.DoesNotExist: return json_response( dict(error="Dashboard '%s' does not exist. " % name) ) else: dashboard.delete() return json_response( dict(success=True) ) def delete_template(request, name): if 'delete' not in getPermissions(request.user): return json_response( dict(error="Must be logged in with appropriate permissions to delete the template") ) try: template = Template.objects.get(name=name) except Template.DoesNotExist: return json_response( dict(error="Template '%s' does not exist. " % name) ) else: template.delete() return json_response( dict(success=True) ) def find(request): queryParams = request.GET.copy() queryParams.update(request.POST) query = queryParams.get('query', False) query_terms = set( query.lower().split() ) results = [] # Find all dashboard names that contain each of our query terms as a substring for dashboard_name in Dashboard.objects.order_by('name').values_list('name', flat=True): name = dashboard_name.lower() if name.startswith('temporary-'): continue found = True # blank queries return everything for term in query_terms: if term in name: found = True else: found = False break if found: results.append( dict(name=dashboard_name) ) return json_response( dict(dashboards=results) ) def find_template(request): queryParams = request.GET.copy() queryParams.update(request.POST) query = queryParams.get('query', False) query_terms = set( query.lower().split() ) results = [] # Find all dashboard names that contain each of our query terms as a substring for template in Template.objects.all(): name = template.name.lower() found = True # blank queries return everything for term in query_terms: if term in name: found = True else: found = False break if found: results.append( dict(name=template.name) ) return json_response( dict(templates=results) ) def help(request): context = {} return render_to_response("dashboardHelp.html", context) def email(request): sender = request.POST['sender'] recipients = request.POST['recipients'].split() subject = request.POST['subject'] message = request.POST['message'] # these need to be passed to the render function in an HTTP request. graph_params = json.loads(request.POST['graph_params'], parse_int=str) target = QueryDict(urlencode({'target': graph_params.pop('target')})) graph_params = QueryDict(urlencode(graph_params)) new_post = request.POST.copy() new_post.update(graph_params) new_post.update(target) request.POST = new_post resp = renderView(request) img = resp.content if img: attachments = [('graph.png', img, 'image/png')] send_graph_email(subject, sender, recipients, attachments, message) return json_response(dict(success=True)) def create_temporary(request): state = str( json.dumps( json.loads( request.POST['state'] ) ) ) i = 0 while True: name = "temporary-%d" % i try: Dashboard.objects.get(name=name) except Dashboard.DoesNotExist: dashboard = Dashboard.objects.create(name=name, state=state) break else: i += 1 return json_response( dict(name=dashboard.name) ) def json_response(obj): return HttpResponse(content_type='application/json', content=json.dumps(obj)) def user_login(request): response = dict(errors={}, text={}, success=False, permissions=[]) user = authenticate(username=request.POST['username'], password=request.POST['password']) if user is not None: if user.is_active: login(request, user) response['success'] = True response['permissions'].extend(getPermissions(user)) else: response['errors']['reason'] = 'Account disabled.' else: response['errors']['reason'] = 'Username and/or password invalid.' return json_response(response) def user_logout(request): response = dict(errors={}, text={}, success=True) logout(request) return json_response(response) graphite-web-1.1.4/webapp/graphite/wsgi.py0000644000000000000000000000262513343334667020456 0ustar rootroot00000000000000import os import sys try: from importlib import import_module except ImportError: from django.utils.importlib import import_module os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'graphite.settings') # noqa from django.conf import settings from django.core.wsgi import get_wsgi_application application = get_wsgi_application() try: import whitenoise except ImportError: whitenoise = False else: whitenoise_version = tuple(map( int, getattr(whitenoise, '__version__', '0').split('.'))) # WhiteNoise < 2.0.1 does not support Python 2.6 if sys.version_info[:2] < (2, 7): if whitenoise_version < (2, 0, 1): whitenoise = False # Configure WhiteNoise >= 3.2 as middleware from app_settings.py # http://whitenoise.evans.io/en/stable/changelog.html#v4-0 if whitenoise_version >= (3, 2): whitenoise = False if whitenoise: from whitenoise.django import DjangoWhiteNoise application = DjangoWhiteNoise(application) prefix = "/".join((settings.URL_PREFIX.strip('/'), 'static')) for directory in settings.STATICFILES_DIRS: application.add_files(directory, prefix=prefix) for app_path in settings.INSTALLED_APPS: module = import_module(app_path) directory = os.path.join(os.path.dirname(module.__file__), 'static') if os.path.isdir(directory): application.add_files(directory, prefix=prefix) graphite-web-1.1.4/webapp/graphite/readers/0000755000000000000000000000000013343335472020546 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/readers/__init__.py0000644000000000000000000000103513343334667022663 0ustar rootroot00000000000000# Import some symbols to avoid breaking compatibility. from graphite.readers.utils import BaseReader, CarbonLink, merge_with_cache # noqa # pylint: disable=unused-import from graphite.readers.multi import MultiReader # noqa # pylint: disable=unused-import from graphite.readers.whisper import WhisperReader, GzippedWhisperReader # noqa # pylint: disable=unused-import from graphite.readers.ceres import CeresReader # noqa # pylint: disable=unused-import from graphite.readers.rrd import RRDReader # noqa # pylint: disable=unused-import graphite-web-1.1.4/webapp/graphite/readers/ceres.py0000644000000000000000000000203513343334667022226 0ustar rootroot00000000000000from __future__ import absolute_import from graphite.intervals import Interval, IntervalSet from graphite.readers.utils import merge_with_carbonlink, BaseReader try: import ceres except ImportError: ceres = False class CeresReader(BaseReader): __slots__ = ('ceres_node', 'real_metric_path') supported = bool(ceres) def __init__(self, ceres_node, real_metric_path): self.ceres_node = ceres_node self.real_metric_path = real_metric_path def get_intervals(self): intervals = [] for info in self.ceres_node.slice_info: (start, end, step) = info intervals.append(Interval(start, end)) return IntervalSet(intervals) def fetch(self, startTime, endTime): data = self.ceres_node.read(startTime, endTime) time_info = (data.startTime, data.endTime, data.timeStep) values = list(data.values) values = merge_with_carbonlink( self.real_metric_path, data.startTime, data.timeStep, values) return time_info, values graphite-web-1.1.4/webapp/graphite/readers/multi.py0000644000000000000000000000426113343334667022262 0ustar rootroot00000000000000import functools from graphite.intervals import IntervalSet from graphite.logger import log from graphite.readers.utils import BaseReader class MultiReader(BaseReader): __slots__ = ('nodes',) def __init__(self, nodes): self.nodes = nodes def get_intervals(self): interval_sets = [] for node in self.nodes: interval_sets.extend(node.intervals.intervals) return IntervalSet(sorted(interval_sets)) def fetch(self, startTime, endTime, now=None, requestContext=None): # Start the fetch on each node fetches = [] for n in self.nodes: try: fetches.append(n.fetch(startTime, endTime, now, requestContext)) except BaseException: log.exception("Failed to initiate subfetch for %s" % str(n)) results = [ r for r in fetches if r is not None ] if not results: raise Exception("All sub-fetches failed") return functools.reduce(self.merge, results) @staticmethod def merge(results1, results2): # Ensure results1 is finer than results2 if results1[0][2] > results2[0][2]: results1, results2 = results2, results1 time_info1, values1 = results1 time_info2, values2 = results2 start1, end1, step1 = time_info1 start2, end2, step2 = time_info2 step = step1 # finest step start = min(start1, start2) # earliest start end = max(end1, end2) # latest end time_info = (start, end, step) values = [] t = start while t < end: # Look for the finer precision value first if available i1 = (t - start1) // step1 if len(values1) > i1: v1 = values1[i1] else: v1 = None if v1 is None: i2 = (t - start2) // step2 if len(values2) > i2: v2 = values2[i2] else: v2 = None values.append(v2) else: values.append(v1) t += step return (time_info, values) graphite-web-1.1.4/webapp/graphite/readers/rrd.py0000644000000000000000000000605713343334667021724 0ustar rootroot00000000000000import sys import os import time import six # Use the built-in version of scandir/stat if possible, otherwise # use the scandir module version try: from os import scandir, stat # noqa # pylint: disable=unused-import except ImportError: from scandir import scandir, stat # noqa # pylint: disable=unused-import try: import rrdtool except ImportError: rrdtool = False from django.conf import settings from graphite.intervals import Interval, IntervalSet from graphite.readers.utils import BaseReader class RRDReader(BaseReader): supported = bool(rrdtool) @staticmethod def _convert_fs_path(fs_path): if isinstance(fs_path, six.text_type): fs_path = fs_path.encode(sys.getfilesystemencoding()) return os.path.realpath(fs_path) def __init__(self, fs_path, datasource_name): self.fs_path = RRDReader._convert_fs_path(fs_path) self.datasource_name = datasource_name def get_intervals(self): start = time.time() - self.get_retention(self.fs_path) end = max(stat(self.fs_path).st_mtime, start) return IntervalSet([Interval(start, end)]) def fetch(self, startTime, endTime): startString = time.strftime( "%H:%M_%Y%m%d+%Ss", time.localtime(startTime)) endString = time.strftime("%H:%M_%Y%m%d+%Ss", time.localtime(endTime)) if settings.FLUSHRRDCACHED: rrdtool.flushcached(self.fs_path, '--daemon', settings.FLUSHRRDCACHED) (timeInfo, columns, rows) = rrdtool.fetch( self.fs_path, settings.RRD_CF, '-s' + startString, '-e' + endString) colIndex = list(columns).index(self.datasource_name) rows.pop() # chop off the latest value because RRD returns crazy last values sometimes values = (row[colIndex] for row in rows) return (timeInfo, values) @staticmethod def get_datasources(fs_path): info = rrdtool.info(RRDReader._convert_fs_path(fs_path)) if 'ds' in info: return [datasource_name for datasource_name in info['ds']] else: ds_keys = [key for key in info if key.startswith('ds[')] datasources = set(key[3:].split(']')[0] for key in ds_keys) return list(datasources) @staticmethod def get_retention(fs_path): info = rrdtool.info(RRDReader._convert_fs_path(fs_path)) if 'rra' in info: rras = info['rra'] else: # Ugh, I like the old python-rrdtool api better.. rra_count = max([int(key[4]) for key in info if key.startswith('rra[')]) + 1 rras = [{}] * rra_count for i in range(rra_count): rras[i]['pdp_per_row'] = info['rra[%d].pdp_per_row' % i] rras[i]['rows'] = info['rra[%d].rows' % i] retention_points = 0 for rra in rras: points = rra['pdp_per_row'] * rra['rows'] if points > retention_points: retention_points = points return retention_points * info['step'] graphite-web-1.1.4/webapp/graphite/readers/whisper.py0000644000000000000000000000547513343334667022621 0ustar rootroot00000000000000from __future__ import absolute_import import time # Use the built-in version of scandir/stat if possible, otherwise # use the scandir module version try: from os import scandir, stat # noqa # pylint: disable=unused-import except ImportError: from scandir import scandir, stat # noqa # pylint: disable=unused-import try: import whisper except ImportError: whisper = False try: import gzip except ImportError: gzip = False from graphite.intervals import Interval, IntervalSet from graphite.logger import log from graphite.readers.utils import merge_with_carbonlink, BaseReader # The parser was replacing __readHeader with the __readHeader # which was not working. if bool(whisper): whisper__readHeader = whisper.__readHeader class WhisperReader(BaseReader): __slots__ = ('fs_path', 'real_metric_path') supported = bool(whisper) meta_info = None def __init__(self, fs_path, real_metric_path): self.fs_path = fs_path self.real_metric_path = real_metric_path def info(self): if not self.meta_info: self.meta_info = whisper.info(self.fs_path) return self.meta_info def get_raw_step(self): return self.info()['archives'][0]['secondsPerPoint'] def get_intervals(self): start = time.time() - self.info()['maxRetention'] end = max(stat(self.fs_path).st_mtime, start) return IntervalSet([Interval(start, end)]) def fetch_data(self, startTime, endTime, now=None): return whisper.fetch(self.fs_path, startTime, endTime, now=now) def fetch(self, startTime, endTime, now=None, requestContext=None): try: data = self.fetch_data(startTime, endTime, now=now) except IOError: log.exception("Failed fetch of whisper file '%s'" % self.fs_path) return None if not data: return None time_info, values = data (start, end, step) = time_info meta_info = self.info() aggregation_method = meta_info['aggregationMethod'] # Merge in data from carbon's cache values = merge_with_carbonlink( self.real_metric_path, start, step, values, aggregation_method, self.get_raw_step()) return time_info, values class GzippedWhisperReader(WhisperReader): supported = bool(whisper and gzip) def info(self): if not self.meta_info: fh = gzip.GzipFile(self.fs_path, 'rb') try: self.meta_info = whisper__readHeader(fh) # evil, but necessary. finally: fh.close() return self.meta_info def fetch_data(self, startTime, endTime, now=None): fh = gzip.GzipFile(self.fs_path, 'rb') try: return whisper.file_fetch(fh, startTime, endTime, now=now) finally: fh.close() graphite-web-1.1.4/webapp/graphite/readers/utils.py0000644000000000000000000000746413343334667022300 0ustar rootroot00000000000000import abc from graphite.logger import log class BaseReader(object): __metaclass__ = abc.ABCMeta supported = True @abc.abstractmethod def get_intervals(self): """Get the supported interval by a reader. Returns: IntervalSet(): set of supported intervals. """ @abc.abstractmethod def fetch(self, startTime, endTime, now=None, requestContext=None): """Fetches points for a given interval. Args: startTime: int endTime: int now: int requestContext: RequestContext Returns: (time_info, values) """ def merge_with_cache(cached_datapoints, start, step, values, func=None, raw_step=None): """Merge values with datapoints from a buffer/cache.""" consolidated = [] # Similar to the function in render/datalib:TimeSeries def consolidate(func, values): usable = [v for v in values if v is not None] if not usable: return None if func == 'sum': return sum(usable) if func == 'average': return float(sum(usable)) / len(usable) if func == 'max': return max(usable) if func == 'min': return min(usable) if func == 'last': return usable[-1] raise Exception("Invalid consolidation function: '%s'" % func) # if we have a raw_step, start by taking only the last data point for each interval to match what whisper will do if raw_step is not None and raw_step > 1: consolidated_dict = {} for (timestamp, value) in cached_datapoints: interval = timestamp - (timestamp % raw_step) consolidated_dict[interval] = value cached_datapoints = list(consolidated_dict.items()) # if we have a consolidation function and the step is not the default interval, consolidate to the requested step if func and step != raw_step: consolidated_dict = {} for (timestamp, value) in cached_datapoints: interval = timestamp - (timestamp % step) if interval in consolidated_dict: consolidated_dict[interval].append(value) else: consolidated_dict[interval] = [value] consolidated = [(i, consolidate(func, consolidated_dict[i])) for i in consolidated_dict] # otherwise just use the points else: consolidated = cached_datapoints for (interval, value) in consolidated: try: i = int(interval - start) // step if i < 0: # cached data point is earlier then the requested data point. # meaning we can definitely ignore the cache result. # note that we cannot rely on the 'except' # in this case since 'values[-n]=' # is equivalent to 'values[len(values) - n]=' continue values[i] = value except BaseException: pass return values def CarbonLink(): """Return a carbonlink instance.""" # Late import to avoid pulling out too many dependencies with # readers.py which is usually imported by plugins. from graphite.carbonlink import CarbonLink return CarbonLink() def merge_with_carbonlink(metric, start, step, values, aggregation_method=None, raw_step=None): """Get points from carbonlink and merge them with existing values.""" cached_datapoints = [] try: cached_datapoints = CarbonLink().query(metric) except BaseException: log.exception("Failed CarbonLink query '%s'" % metric) cached_datapoints = [] if isinstance(cached_datapoints, dict): cached_datapoints = list(cached_datapoints.items()) return merge_with_cache( cached_datapoints, start, step, values, func=aggregation_method, raw_step=raw_step) graphite-web-1.1.4/webapp/graphite/readers/remote.py0000644000000000000000000001221113343334667022415 0ustar rootroot00000000000000from traceback import format_exc from django.conf import settings from graphite.logger import log from graphite.readers.utils import BaseReader from graphite.util import unpickle, msgpack, BufferedHTTPReader import time class MeasuredReader(object): def __init__(self, reader): self.reader = reader self.bytes_read = 0 def read(self, amt=None): b = b'' try: if amt: b = self.reader.read(amt) else: b = self.reader.read() return b finally: self.bytes_read += len(b) class RemoteReader(BaseReader): __slots__ = ( 'finder', 'metric_path', 'intervals', 'bulk_query', ) def __init__(self, finder, node_info, bulk_query=None): self.finder = finder self.metric_path = node_info.get('path') or node_info.get('metric_path') self.intervals = node_info.get('intervals', []) self.bulk_query = set(bulk_query) if bulk_query else ( [self.metric_path] if self.metric_path else [] ) def __repr__(self): return '' % (id(self), self.finder.host, ','.join(self.bulk_query)) def get_intervals(self): return self.intervals def fetch(self, startTime, endTime, now=None, requestContext=None): for series in self.fetch_multi(startTime, endTime, now, requestContext): if series['name'] == self.metric_path: return (series['time_info'], series['values']) def fetch_multi(self, startTime, endTime, now=None, requestContext=None): if not self.bulk_query: return [] query_params = [ ('format', self.finder.params.get('format', 'pickle')), ('local', self.finder.params.get('local', '1')), ('noCache', '1'), ('from', int(startTime)), ('until', int(endTime)) ] for target in self.bulk_query: query_params.append(('target', target)) if now is not None: query_params.append(('now', int(now))) headers = requestContext.get('forwardHeaders') if requestContext else None retries = 1 # start counting at one to make log output and settings more readable while True: try: result = self.finder.request( '/render/', fields=query_params, headers=headers, timeout=settings.FETCH_TIMEOUT, ) break except Exception: if retries >= settings.MAX_FETCH_RETRIES: log.exception("Failed after %s attempts! Root cause:\n%s" % (settings.MAX_FETCH_RETRIES, format_exc())) raise else: log.exception("Got an exception when fetching data! Try: %i of %i. Root cause:\n%s" % (retries, settings.MAX_FETCH_RETRIES, format_exc())) retries += 1 data = self.deserialize(result) try: return [ { 'pathExpression': series.get('pathExpression', series['name']), 'name': series['name'], 'time_info': (series['start'], series['end'], series['step']), 'values': series['values'], } for series in data ] except Exception as err: self.finder.fail() log.exception( "RemoteReader[%s] Invalid render response from %s: %s" % (self.finder.host, result.url_full, repr(err))) raise Exception("Invalid render response from %s: %s" % (result.url_full, repr(err))) def deserialize(self, result): """ Based on configuration, either stream-deserialize a response in settings.REMOTE_BUFFER_SIZE chunks, or read the entire payload and use inline deserialization. :param result: an http response object :return: deserialized response payload from cluster server """ start = time.time() try: should_buffer = settings.REMOTE_BUFFER_SIZE > 0 measured_reader = MeasuredReader(BufferedHTTPReader(result, settings.REMOTE_BUFFER_SIZE)) if should_buffer: log.debug("Using streaming deserializer.") reader = BufferedHTTPReader(measured_reader, settings.REMOTE_BUFFER_SIZE) deserialized = self._deserialize_stream(reader, result.getheader('content-type')) return deserialized else: log.debug("Using inline deserializer for small payload") deserialized = self._deserialize_buffer(measured_reader.read(), result.getheader('content-type')) return deserialized except Exception as err: self.finder.fail() log.exception( "RemoteReader[%s] Error decoding render response from %s: %s" % (self.finder.host, result.url_full, err)) raise Exception("Error decoding render response from %s: %s" % (result.url_full, err)) finally: log.debug("Processed %d bytes in %f seconds." % (measured_reader.bytes_read, time.time() - start)) result.release_conn() @staticmethod def _deserialize_buffer(byte_buffer, content_type): if content_type == 'application/x-msgpack': data = msgpack.unpackb(byte_buffer, encoding='utf-8') else: data = unpickle.loads(byte_buffer) return data @staticmethod def _deserialize_stream(stream, content_type): if content_type == 'application/x-msgpack': data = msgpack.load(stream, encoding='utf-8') else: data = unpickle.load(stream) return data graphite-web-1.1.4/webapp/graphite/storage.py0000755000000000000000000004450613343334667021160 0ustar rootroot00000000000000from __future__ import absolute_import import os import random import sys import time import traceback import types from collections import defaultdict from copy import deepcopy from shutil import move from tempfile import mkstemp from django.conf import settings from django.core.cache import cache import six try: from importlib import import_module except ImportError: # python < 2.7 compatibility from django.utils.importlib import import_module from graphite.logger import log from graphite.node import LeafNode from graphite.intervals import Interval, IntervalSet from graphite.finders.utils import FindQuery, BaseFinder from graphite.readers import MultiReader from graphite.worker_pool.pool import get_pool, pool_exec, Job, PoolTimeoutError from graphite.render.grammar import grammar def get_finders(finder_path): module_name, class_name = finder_path.rsplit('.', 1) module = import_module(module_name) cls = getattr(module, class_name) if getattr(cls, 'factory', None): return cls.factory() # monkey patch so legacy finders will work finder = cls() if sys.version_info[0] >= 3: finder.fetch = types.MethodType(BaseFinder.fetch, finder) finder.find_multi = types.MethodType(BaseFinder.find_multi, finder) finder.get_index = types.MethodType(BaseFinder.get_index, finder) else: finder.fetch = types.MethodType(BaseFinder.fetch.__func__, finder) finder.find_multi = types.MethodType(BaseFinder.find_multi.__func__, finder) finder.get_index = types.MethodType(BaseFinder.get_index.__func__, finder) return [finder] def get_tagdb(tagdb_path): module_name, class_name = tagdb_path.rsplit('.', 1) module = import_module(module_name) return getattr(module, class_name)(settings, cache=cache, log=log) class Store(object): def __init__(self, finders=None, tagdb=None): if finders is None: finders = [] for finder_path in settings.STORAGE_FINDERS: finders.extend(get_finders(finder_path)) self.finders = finders if tagdb is None: tagdb = get_tagdb(settings.TAGDB or 'graphite.tags.base.DummyTagDB') self.tagdb = tagdb def get_finders(self, local=False): for finder in self.finders: # Support legacy finders by defaulting to 'disabled = False' if getattr(finder, 'disabled', False): continue # Support legacy finders by defaulting to 'local = True' if local and not getattr(finder, 'local', True): continue yield finder def pool_exec(self, jobs, timeout): if not jobs: return [] thread_count = 0 if settings.USE_WORKER_POOL: thread_count = min(len(self.finders), settings.POOL_MAX_WORKERS) return pool_exec(get_pool('finders', thread_count), jobs, timeout) def wait_jobs(self, jobs, timeout, context): if not jobs: return [] start = time.time() results = [] failed = [] done = 0 try: for job in self.pool_exec(jobs, timeout): elapsed = time.time() - start done += 1 if job.exception: failed.append(job) log.info("Exception during %s after %fs: %s" % ( job, elapsed, str(job.exception)) ) else: log.debug("Got a result for %s after %fs" % (job, elapsed)) results.append(job.result) except PoolTimeoutError: message = "Timed out after %fs for %s" % ( time.time() - start, context ) log.info(message) if done == 0: raise Exception(message) if len(failed) == done: message = "All requests failed for %s (%d)" % ( context, len(failed) ) for job in failed: message += "\n\n%s: %s: %s" % ( job, job.exception, '\n'.join(traceback.format_exception(*job.exception_info)) ) raise Exception(message) if len(results) < len(jobs) and settings.STORE_FAIL_ON_ERROR: message = "%s request(s) failed for %s (%d)" % ( len(jobs) - len(results), context, len(jobs) ) for job in failed: message += "\n\n%s: %s: %s" % ( job, job.exception, '\n'.join(traceback.format_exception(*job.exception_info)) ) raise Exception(message) return results def fetch(self, patterns, startTime, endTime, now, requestContext): # deduplicate patterns patterns = sorted(set(patterns)) if not patterns: return [] log.debug( 'graphite.storage.Store.fetch :: Starting fetch on all backends') jobs = [] tag_patterns = None pattern_aliases = defaultdict(list) for finder in self.get_finders(requestContext.get('localOnly')): # if the finder supports tags, just pass the patterns through if getattr(finder, 'tags', False): job = Job( finder.fetch, 'fetch for %s' % patterns, patterns, startTime, endTime, now=now, requestContext=requestContext ) jobs.append(job) continue # if we haven't resolved the seriesByTag calls, build resolved patterns and translation table if tag_patterns is None: tag_patterns, pattern_aliases = self._tag_patterns(patterns, requestContext) # dispatch resolved patterns to finder job = Job( finder.fetch, 'fetch for %s' % tag_patterns, tag_patterns, startTime, endTime, now=now, requestContext=requestContext ) jobs.append(job) done = 0 errors = 0 # Start fetches start = time.time() results = self.wait_jobs(jobs, settings.FETCH_TIMEOUT, 'fetch for %s' % str(patterns)) results = [i for l in results for i in l] # flatten # translate path expressions for responses from resolved seriesByTag patterns for result in results: if result['name'] == result['pathExpression'] and result['pathExpression'] in pattern_aliases: for pathExpr in pattern_aliases[result['pathExpression']]: newresult = deepcopy(result) newresult['pathExpression'] = pathExpr results.append(newresult) log.debug("Got all fetch results for %s in %fs" % (str(patterns), time.time() - start)) return results def _tag_patterns(self, patterns, requestContext): tag_patterns = [] pattern_aliases = defaultdict(list) for pattern in patterns: # if pattern isn't a seriesByTag call, just add it to the list if not pattern.startswith('seriesByTag('): tag_patterns.append(pattern) continue # perform the tagdb lookup exprs = tuple([ t.string[1:-1] for t in grammar.parseString(pattern).expression.call.args if t.string ]) taggedSeries = self.tagdb.find_series(exprs, requestContext=requestContext) if not taggedSeries: continue # add to translation table for path matching for series in taggedSeries: pattern_aliases[series].append(pattern) # add to list of resolved patterns tag_patterns.extend(taggedSeries) return sorted(set(tag_patterns)), pattern_aliases def get_index(self, requestContext=None): log.debug('graphite.storage.Store.get_index :: Starting get_index on all backends') if not requestContext: requestContext = {} context = 'get_index' jobs = [ Job(finder.get_index, context, requestContext=requestContext) for finder in self.get_finders(local=requestContext.get('localOnly')) ] start = time.time() results = self.wait_jobs(jobs, settings.FETCH_TIMEOUT, context) results = [i for l in results if l is not None for i in l] # flatten log.debug("Got all index results in %fs" % (time.time() - start)) return sorted(list(set(results))) def find(self, pattern, startTime=None, endTime=None, local=False, headers=None, leaves_only=False): query = FindQuery( pattern, startTime, endTime, local=local, headers=headers, leaves_only=leaves_only ) warn_threshold = settings.METRICS_FIND_WARNING_THRESHOLD fail_threshold = settings.METRICS_FIND_FAILURE_THRESHOLD matched_leafs = 0 for match in self._find(query): if isinstance(match, LeafNode): matched_leafs += 1 elif leaves_only: continue if matched_leafs > fail_threshold: raise Exception( ("Query %s yields too many results and failed " "(failure threshold is %d)") % (pattern, fail_threshold)) yield match if matched_leafs > warn_threshold: log.warning( ("Query %s yields large number of results up to %d " "(warning threshold is %d)") % ( pattern, matched_leafs, warn_threshold)) def _find(self, query): context = 'find %s' % query jobs = [ Job(finder.find_nodes, context, query) for finder in self.get_finders(query.local) ] # Group matching nodes by their path nodes_by_path = defaultdict(list) # Start finds start = time.time() results = self.wait_jobs(jobs, settings.FIND_TIMEOUT, context) for result in results: for node in result or []: nodes_by_path[node.path].append(node) log.debug("Got all find results for %s in %fs" % ( str(query), time.time() - start) ) return self._list_nodes(query, nodes_by_path) def _list_nodes(self, query, nodes_by_path): # Reduce matching nodes for each path to a minimal set found_branch_nodes = set() items = list(six.iteritems(nodes_by_path)) random.shuffle(items) for path, nodes in items: leaf_nodes = [] # First we dispense with the BranchNodes for node in nodes: if node.is_leaf: leaf_nodes.append(node) # TODO need to filter branch nodes based on requested # interval... how?!?!? elif node.path not in found_branch_nodes: yield node found_branch_nodes.add(node.path) leaf_node = self._merge_leaf_nodes(query, path, leaf_nodes) if leaf_node: yield leaf_node def _merge_leaf_nodes(self, query, path, leaf_nodes): """Get a single node from a list of leaf nodes.""" if not leaf_nodes: return None # Fast-path when there is a single node. if len(leaf_nodes) == 1: return leaf_nodes[0] # Calculate best minimal node set minimal_node_set = set() covered_intervals = IntervalSet([]) # If the query doesn't fall entirely within the FIND_TOLERANCE window # we disregard the window. This prevents unnecessary remote fetches # caused when carbon's cache skews node.intervals, giving the appearance # remote systems have data we don't have locally, which we probably # do. now = int(time.time()) tolerance_window = now - settings.FIND_TOLERANCE disregard_tolerance_window = query.interval.start < tolerance_window prior_to_window = Interval(float('-inf'), tolerance_window) def measure_of_added_coverage( node, drop_window=disregard_tolerance_window): relevant_intervals = node.intervals.intersect_interval( query.interval) if drop_window: relevant_intervals = relevant_intervals.intersect_interval( prior_to_window) return covered_intervals.union( relevant_intervals).size - covered_intervals.size nodes_remaining = list(leaf_nodes) # Prefer local nodes first (and do *not* drop the tolerance window) for node in leaf_nodes: if node.local and measure_of_added_coverage(node, False) > 0: nodes_remaining.remove(node) minimal_node_set.add(node) covered_intervals = covered_intervals.union(node.intervals) if settings.REMOTE_STORE_MERGE_RESULTS: remote_nodes = [n for n in nodes_remaining if not n.local] for node in remote_nodes: nodes_remaining.remove(node) minimal_node_set.add(node) covered_intervals = covered_intervals.union(node.intervals) else: while nodes_remaining: node_coverages = [(measure_of_added_coverage(n), n) for n in nodes_remaining] best_coverage, best_node = max(node_coverages) if best_coverage == 0: break nodes_remaining.remove(best_node) minimal_node_set.add(best_node) covered_intervals = covered_intervals.union( best_node.intervals) # Sometimes the requested interval falls within the caching window. # We include the most likely node if the gap is within # tolerance. if not minimal_node_set: def distance_to_requested_interval(node): if not node.intervals: return float('inf') latest = sorted( node.intervals, key=lambda i: i.end)[-1] distance = query.interval.start - latest.end return distance if distance >= 0 else float('inf') best_candidate = min( leaf_nodes, key=distance_to_requested_interval) if distance_to_requested_interval( best_candidate) <= settings.FIND_TOLERANCE: minimal_node_set.add(best_candidate) if not minimal_node_set: return None elif len(minimal_node_set) == 1: return minimal_node_set.pop() else: reader = MultiReader(minimal_node_set) return LeafNode(path, reader) def tagdb_auto_complete_tags(self, exprs, tagPrefix=None, limit=None, requestContext=None): log.debug( 'graphite.storage.Store.auto_complete_tags :: Starting lookup on all backends') if requestContext is None: requestContext = {} context = 'tags for %s %s' % (str(exprs), tagPrefix or '') jobs = [] use_tagdb = False for finder in self.get_finders(requestContext.get('localOnly')): if getattr(finder, 'tags', False): job = Job( finder.auto_complete_tags, context, exprs, tagPrefix=tagPrefix, limit=limit, requestContext=requestContext ) jobs.append(job) else: use_tagdb = True results = set() # if we're using the local tagdb then execute it (in the main thread # so that LocalDatabaseTagDB will work) if use_tagdb: results.update(self.tagdb.auto_complete_tags( exprs, tagPrefix=tagPrefix, limit=limit, requestContext=requestContext )) # Start fetches start = time.time() for result in self.wait_jobs(jobs, settings.FIND_TIMEOUT, context): results.update(result) # sort & limit results results = sorted(results) if limit: results = results[:int(limit)] log.debug("Got all autocomplete %s in %fs" % ( context, time.time() - start) ) return results def tagdb_auto_complete_values(self, exprs, tag, valuePrefix=None, limit=None, requestContext=None): log.debug( 'graphite.storage.Store.auto_complete_values :: Starting lookup on all backends') if requestContext is None: requestContext = {} context = 'values for %s %s %s' % (str(exprs), tag, valuePrefix or '') jobs = [] use_tagdb = False for finder in self.get_finders(requestContext.get('localOnly')): if getattr(finder, 'tags', False): job = Job( finder.auto_complete_values, context, exprs, tag, valuePrefix=valuePrefix, limit=limit, requestContext=requestContext ) jobs.append(job) else: use_tagdb = True # start finder jobs start = time.time() results = set() # if we're using the local tagdb then execute it (in the main thread # so that LocalDatabaseTagDB will work) if use_tagdb: results.update(self.tagdb.auto_complete_values( exprs, tag, valuePrefix=valuePrefix, limit=limit, requestContext=requestContext )) for result in self.wait_jobs(jobs, settings.FIND_TIMEOUT, context): results.update(result) # sort & limit results results = sorted(results) if limit: results = results[:int(limit)] log.debug("Got all autocomplete %s in %fs" % ( context, time.time() - start) ) return results def extractForwardHeaders(request): headers = {} for name in settings.REMOTE_STORE_FORWARD_HEADERS: value = request.META.get('HTTP_%s' % name.upper().replace('-', '_')) if value is not None: headers[name] = value return headers def write_index(index=None): if not index: index = settings.INDEX_FILE try: fd, tmp = mkstemp() try: tmp_index = os.fdopen(fd, 'wt') for metric in STORE.get_index(): tmp_index.write("{0}\n".format(metric)) finally: tmp_index.close() move(tmp, index) finally: try: os.unlink(tmp) except OSError: pass return None STORE = Store() graphite-web-1.1.4/webapp/graphite/middleware.py0000644000000000000000000000061413343334667021616 0ustar rootroot00000000000000from graphite.logger import log try: from django.utils.deprecation import MiddlewareMixin except ImportError: # Django < 1.10 MiddlewareMixin = object class LogExceptionsMiddleware(MiddlewareMixin): def process_exception(self, request, exception): log.exception('Exception encountered in <{0} {1}>'.format(request.method, request.build_absolute_uri())) return None graphite-web-1.1.4/webapp/graphite/carbonlink.py0000644000000000000000000001564113343334667021631 0ustar rootroot00000000000000import time import socket import struct import random from django.conf import settings from graphite.render.hashing import ConsistentHashRing from graphite.logger import log from graphite.util import load_module, unpickle from graphite.singleton import ThreadSafeSingleton try: import six.moves.cPickle as pickle except ImportError: import pickle def load_keyfunc(): if settings.CARBONLINK_HASHING_KEYFUNC: module_path, func_name = settings.CARBONLINK_HASHING_KEYFUNC.rsplit(':', 1) log.cache("Using keyfunc %s found in %s" % (str(func_name), str(module_path))) return load_module(module_path, member=func_name) else: return lambda x: x class CarbonLinkRequestError(Exception): pass class CarbonLinkPool(object): def __init__(self, hosts, timeout): self.hosts = [ (server, instance) for (server, port, instance) in hosts ] self.ports = dict( ((server, instance), port) for (server, port, instance) in hosts ) self.timeout = float(timeout) servers = set([server for (server, port, instance) in hosts]) if len(servers) < settings.REPLICATION_FACTOR: raise Exception("REPLICATION_FACTOR=%d cannot exceed servers=%d" % ( settings.REPLICATION_FACTOR, len(servers))) self.hash_ring = ConsistentHashRing( self.hosts, hash_type=settings.CARBONLINK_HASHING_TYPE) self.keyfunc = load_keyfunc() self.connections = {} self.last_failure = {} # Create a connection pool for each host for host in self.hosts: self.connections[host] = set() def select_host(self, metric): "Returns the carbon host that has data for the given metric" key = self.keyfunc(metric) nodes = [] servers = set() for node in self.hash_ring.get_nodes(key): (server, instance) = node if server in servers: continue servers.add(server) nodes.append(node) if len(servers) >= settings.REPLICATION_FACTOR: break available = [ n for n in nodes if self.is_available(n) ] return random.choice(available or nodes) def is_available(self, host): now = time.time() last_fail = self.last_failure.get(host, 0) return (now - last_fail) < settings.CARBONLINK_RETRY_DELAY def get_connection(self, host): # First try to take one out of the pool for this host (server, instance) = host port = self.ports[host] connectionPool = self.connections[host] try: return connectionPool.pop() except KeyError: pass #nothing left in the pool, gotta make a new connection log.cache("CarbonLink creating a new socket for %s" % str(host)) connection = socket.socket() connection.settimeout(self.timeout) try: connection.connect((server, port)) except socket.error: self.last_failure[host] = time.time() raise else: connection.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) return connection def query(self, metric): request = dict(type='cache-query', metric=metric) results = self.send_request(request) log.cache("CarbonLink cache-query request for %s returned %d datapoints" % ( metric, len(results['datapoints']))) return results['datapoints'] def get_metadata(self, metric, key): request = dict(type='get-metadata', metric=metric, key=key) results = self.send_request(request) log.cache("CarbonLink get-metadata request received for %s:%s" % (metric, key)) return results['value'] def set_metadata(self, metric, key, value): request = dict(type='set-metadata', metric=metric, key=key, value=value) results = self.send_request(request) log.cache("CarbonLink set-metadata request received for %s:%s" % (metric, key)) return results def send_request(self, request): metric = request['metric'] serialized_request = pickle.dumps(request, protocol=-1) len_prefix = struct.pack("!L", len(serialized_request)) request_packet = len_prefix + serialized_request result = {} result.setdefault('datapoints', []) if metric.startswith(settings.CARBON_METRIC_PREFIX): return self.send_request_to_all(request) if not self.hosts: log.cache("CarbonLink is not connected to any host. Returning empty nodes list") return result host = self.select_host(metric) conn = self.get_connection(host) log.cache("CarbonLink sending request for %s to %s" % (metric, str(host))) try: conn.sendall(request_packet) result = self.recv_response(conn) except Exception as e: self.last_failure[host] = time.time() log.cache("Exception getting data from cache %s: %s" % (str(host), e)) else: self.connections[host].add(conn) if 'error' in result: log.cache("Error getting data from cache: %s" % result['error']) raise CarbonLinkRequestError(result['error']) log.cache("CarbonLink finished receiving %s from %s" % (str(metric), str(host))) return result def send_request_to_all(self, request): metric = request['metric'] serialized_request = pickle.dumps(request, protocol=-1) len_prefix = struct.pack("!L", len(serialized_request)) request_packet = len_prefix + serialized_request results = {} results.setdefault('datapoints', {}) for host in self.hosts: conn = self.get_connection(host) log.cache("CarbonLink sending request for %s to %s" % (metric, str(host))) try: conn.sendall(request_packet) result = self.recv_response(conn) except Exception as e: self.last_failure[host] = time.time() log.cache("Exception getting data from cache %s: %s" % (str(host), e)) else: self.connections[host].add(conn) if 'error' in result: log.cache("Error getting data from cache %s: %s" % (str(host), result['error'])) else: if len(result['datapoints']) > 1: results['datapoints'].update(result['datapoints']) log.cache("CarbonLink finished receiving %s from %s" % (str(metric), str(host))) return results def recv_response(self, conn): len_prefix = self.recv_exactly(conn, 4) body_size = struct.unpack("!L", len_prefix)[0] body = self.recv_exactly(conn, body_size) return unpickle.loads(body) @staticmethod def recv_exactly(conn, num_bytes): buf = b'' while len(buf) < num_bytes: data = conn.recv(num_bytes - len(buf)) if not data: raise Exception("Connection lost") buf += data return buf @ThreadSafeSingleton class GlobalCarbonLinkPool(CarbonLinkPool): def __init__(self): hosts = [] for host in settings.CARBONLINK_HOSTS: parts = host.split(':') server = parts[0] port = int(parts[1]) if len(parts) > 2: instance = parts[2] else: instance = None hosts.append((server, int(port), instance)) timeout = settings.CARBONLINK_TIMEOUT CarbonLinkPool.__init__(self, hosts, timeout) def CarbonLink(): """Handy accessor for the global singleton.""" return GlobalCarbonLinkPool.instance() graphite-web-1.1.4/webapp/graphite/compat.py0000644000000000000000000000103313343334667020760 0ustar rootroot00000000000000 from django import VERSION from django.http import (HttpResponse as BaseHttpResponse, HttpResponseBadRequest as Base400) class ContentTypeMixin(object): def __init__(self, *args, **kwargs): if VERSION < (1, 5) and 'content_type' in kwargs: kwargs['mimetype'] = kwargs.pop('content_type') super(ContentTypeMixin, self).__init__(*args, **kwargs) class HttpResponse(ContentTypeMixin, BaseHttpResponse): pass class HttpResponseBadRequest(ContentTypeMixin, Base400): pass graphite-web-1.1.4/webapp/graphite/templates/0000755000000000000000000000000013343335472021117 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/templates/dashboard.html0000644000000000000000000000714413343334667023747 0ustar rootroot00000000000000{% load static staticfiles %} Graphite Dashboard
Drop To Merge
{% if jsdebug %} {% else %} {% endif %} graphite-web-1.1.4/webapp/graphite/templates/version.html0000644000000000000000000000001413343334667023472 0ustar rootroot00000000000000{{version}} graphite-web-1.1.4/webapp/graphite/templates/login.html0000644000000000000000000000300113343334667023114 0ustar rootroot00000000000000
{% if authenticationFailed %}

Authentication attempt failed, please make sure you entered your login and password correctly

{% endif %} {% if accountDisabled %}

Your account is disabled, please contact your site administrator.

{% endif %}
{% if nextPage %} {% endif %}
login
username
password
graphite-web-1.1.4/webapp/graphite/templates/editProfile.html0000644000000000000000000000174413343334667024266 0ustar rootroot00000000000000

Profile settings for {{profile.user.username}}

{% if nextPage %} {% endif %} Enable advanced/experimental UI features
graphite-web-1.1.4/webapp/graphite/templates/event.html0000644000000000000000000000151413343334667023134 0ustar rootroot00000000000000{% load staticfiles %} {{event.what}}

{{event.what}}

when{{event.when|date:"H:i:s D d M Y" }}
tags{{event.tags}}
data{{event.data}}
graphite-web-1.1.4/webapp/graphite/templates/browserHeader.html0000644000000000000000000000637713343334667024623 0ustar rootroot00000000000000{% load staticfiles %} Graphite Browser Header
graphite-web-1.1.4/webapp/graphite/templates/500.html0000644000000000000000000000113713343334667022320 0ustar rootroot00000000000000

Graphite encountered an unexpected error while handling your request.

Please contact your site administrator if the problem persists.


{{message}}
{% if stacktrace %}
{{stacktrace}}
{% endif %}
graphite-web-1.1.4/webapp/graphite/templates/dashboardHelp.html0000644000000000000000000000223113343334667024550 0ustar rootroot00000000000000
Shortcut Action
Ctrl-z Toggle visibility of the graph area toolbar
Ctrl-space Toggle visibility of the navigation panel
Alt-x Remove all graphs from the graph area
Alt-s Save the current dashboard
The following shortcuts are for the Completer UI mode only
Alt-Enter Add all matching metrics to the graph area
Alt-Backspace Remove all matching metrics from the graph area
Shift-space Put keyboard focus in the completer field
graphite-web-1.1.4/webapp/graphite/templates/composer.html0000644000000000000000000001123413343334667023642 0ustar rootroot00000000000000 {% load static staticfiles %} Graphite Composer {% if jsdebug %} {% else %} {% endif %} graphite-web-1.1.4/webapp/graphite/templates/browser.html0000644000000000000000000000221113343334667023471 0ustar rootroot00000000000000 Graphite Browser {% if target %} {% else %} {% endif %} graphite-web-1.1.4/webapp/graphite/templates/events.html0000644000000000000000000000267713343334667023332 0ustar rootroot00000000000000{% load staticfiles %} Events

graphite events

{% if events %} {% for event in events %} {% endfor %} {% else %}
No events. Add events using the admin interface or by posting (eg, curl -X POST {{ protocol }}://{{ site.domain }}{% url "events" %} -d '{"what": "Something Interesting", "tags" : "tag1"}') {% endif %}
whenwhattags
{{event.when|date:"H:i:s D d M Y" }} {{event.what}} ['{{ event.tags|join:"', '"}}']
graphite-web-1.1.4/webapp/graphite/version/0000755000000000000000000000000013343335472020606 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/version/__init__.py0000755000000000000000000000000013343334667022715 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/version/urls.py0000755000000000000000000000127713343334667022164 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/?$', views.index, name='version_index'), ] graphite-web-1.1.4/webapp/graphite/version/views.py0000755000000000000000000000032413343334667022324 0ustar rootroot00000000000000from django.shortcuts import render_to_response from graphite import settings def index(request): context = { 'version' : settings.WEBAPP_VERSION, } return render_to_response('version.html', context) graphite-web-1.1.4/webapp/graphite/composer/0000755000000000000000000000000013343335472020750 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/composer/__init__.py0000644000000000000000000000000013343334667023054 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/composer/urls.py0000644000000000000000000000146513343334667022322 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/send_email', views.send_email, name='composer_send_email'), url(r'^/mygraph', views.mygraph, name='composer_mygraph'), url(r'^/?$', views.composer, name='composer'), ] graphite-web-1.1.4/webapp/graphite/composer/views.py0000644000000000000000000001010513343334667022461 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import os from smtplib import SMTP from socket import gethostname from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage from six.moves.http_client import HTTPConnection from six.moves.urllib.parse import urlsplit from time import ctime, strftime from traceback import format_exc from graphite.user_util import getProfile from graphite.logger import log from graphite.account.models import MyGraph from django.shortcuts import render_to_response from django.http import HttpResponse from django.conf import settings from django.core.exceptions import ObjectDoesNotExist def composer(request): profile = getProfile(request) context = { 'queryString' : request.GET.urlencode().replace('+','%20'), 'showTarget' : request.GET.get('showTarget',''), 'user' : request.user, 'profile' : profile, 'showMyGraphs' : int( profile.user.username != 'default' ), 'searchEnabled' : int( os.access(settings.INDEX_FILE, os.R_OK) ), 'refreshInterval': settings.AUTO_REFRESH_INTERVAL, 'debug' : settings.DEBUG, 'jsdebug' : settings.DEBUG, } return render_to_response("composer.html",context) def mygraph(request): profile = getProfile(request, allowDefault=False) if not profile: return HttpResponse( "You are not logged in!" ) action = request.GET['action'] graphName = request.GET['graphName'] if not graphName: return HttpResponse("You must type in a graph name.") if action == 'save': url = request.GET['url'] try: existingGraph = profile.mygraph_set.get(name=graphName) existingGraph.url = url existingGraph.save() except ObjectDoesNotExist: try: newGraph = MyGraph(profile=profile,name=graphName,url=url) newGraph.save() except Exception: log.exception("Failed to create new MyGraph in /composer/mygraph/, graphName=%s" % graphName) return HttpResponse("Failed to save graph %s" % graphName) return HttpResponse("SAVED") elif action == 'delete': try: existingGraph = profile.mygraph_set.get(name=graphName) existingGraph.delete() except ObjectDoesNotExist: return HttpResponse("No such graph '%s'" % graphName) return HttpResponse("DELETED") else: return HttpResponse("Invalid operation '%s'" % action) def send_email(request): try: recipients = request.GET['to'].split(',') url = request.GET['url'] proto, server, path, query, frag = urlsplit(url) if query: path += '?' + query conn = HTTPConnection(server) conn.request('GET',path) try: # Python 2.7+, use buffering of HTTP responses resp = conn.getresponse(buffering=True) except TypeError: # Python 2.6 and older resp = conn.getresponse() assert resp.status == 200, "Failed HTTP response %s %s" % (resp.status, resp.reason) rawData = resp.read() conn.close() message = MIMEMultipart() message['Subject'] = "Graphite Image" message['To'] = ', '.join(recipients) message['From'] = 'composer@%s' % gethostname() text = MIMEText( "Image generated by the following graphite URL at %s\r\n\r\n%s" % (ctime(),url) ) image = MIMEImage( rawData ) image.add_header('Content-Disposition', 'attachment', filename="composer_" + strftime("%b%d_%I%M%p.png")) message.attach(text) message.attach(image) s = SMTP(settings.SMTP_SERVER) s.sendmail('composer@%s' % gethostname(),recipients,message.as_string()) s.quit() return HttpResponse( "OK" ) except Exception: return HttpResponse(format_exc()) graphite-web-1.1.4/webapp/graphite/singleton.py0000644000000000000000000000521713343334667021507 0ustar rootroot00000000000000# From https://github.com/reyoung/singleton from threading import Lock class Singleton(object): """ The Singleton class decorator. Like: from singleton.singleton import Singleton @Singleton class IntSingleton(object): def __init__(self): pass Use IntSingleton.instance() get the instance """ def __init__(self, cls): """ :param cls: decorator class type """ self.__cls = cls self.__instance = None def initialize(self, *args, **kwargs): """ Initialize singleton object if it has not been initialized :param args: class init parameters :param kwargs: class init parameters """ if not self.is_initialized(): self.__instance = self.__cls(*args, **kwargs) def is_initialized(self): """ :return: true if instance is initialized """ return self.__instance is not None def instance(self): """ Get singleton instance :return: instance object """ if not self.is_initialized(): self.initialize() return self.__instance def __call__(self, *args, **kwargs): """ Disable new instance of original class :raise TypeError: """ raise TypeError("Singletons must be access by instance") def __instancecheck__(self, inst): """ Helper for isinstance check """ return isinstance(inst, self.__cls) class ThreadSafeSingleton(object): def __init__(self, cls): self.__cls = cls self.__instance = None self.__mutex = Lock() def is_initialized(self): self.__mutex.acquire() try: return self.__instance is not None finally: self.__mutex.release() def initialize(self, *args, **kwargs): self.__mutex.acquire() try: if self.__instance is None: self.__instance = self.__cls(*args, **kwargs) finally: self.__mutex.release() def instance(self): self.__mutex.acquire() try: if self.__instance is None: self.__instance = self.__cls() return self.__instance finally: self.__mutex.release() def __call__(self, *args, **kwargs): """ Disable new instance of original class :raise TypeError: """ raise TypeError("Singletons must be access by instance") def __instancecheck__(self, inst): """ Helper for isinstance check """ return isinstance(inst, self.__cls) graphite-web-1.1.4/webapp/graphite/errors.py0000644000000000000000000000013613343334667021014 0ustar rootroot00000000000000class NormalizeEmptyResultError(Exception): # throw error for normalize() when empty pass graphite-web-1.1.4/webapp/graphite/worker_pool/0000755000000000000000000000000013343335472021463 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/worker_pool/__init__.py0000644000000000000000000000000013343334667023567 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/worker_pool/pool.py0000644000000000000000000000550513343334667023020 0ustar rootroot00000000000000import time import sys import six.moves.queue from threading import Lock from multiprocessing.pool import ThreadPool _init_lock = Lock() _pools = {} class Job(object): """A job to be executed by a pool The job accepts a function and arguments. When it is run, it will execute the function with the specified arguments. The return value of the function will be stored in the result property of the job. If the function raises an exception, it will be stored in the exception property of the job. """ __slots__ = ( 'func', 'description', 'args', 'kwargs', 'result', 'exception', 'exception_info', ) def __init__(self, func, description, *args, **kwargs): self.func = func self.args = args self.description = description self.kwargs = kwargs self.result = None self.exception = None def __str__(self): return self.description def run(self): try: self.result = self.func(*self.args, **self.kwargs) except Exception as e: self.exception_info = sys.exc_info() self.exception = e def get_pool(name="default", thread_count=1): """Get (and initialize) a Thread pool. If thread_count is 0, then None is returned. If the thread pool had already been initialized, thread_count will be ignored. """ if not thread_count: return None with _init_lock: pool = _pools.get(name, None) if pool is None: pool = ThreadPool(thread_count) _pools[name] = pool return pool def stop_pools(): with _init_lock: for name in list(_pools.keys()): pool = _pools.pop(name) pool.close() def stop_pool(name="default"): with _init_lock: _pools[name].close() del _pools[name] class PoolTimeoutError(Exception): pass def pool_exec(pool, jobs, timeout): """Execute a list of jobs, yielding each one as it completes. If a pool is specified then the jobs will be executed asynchronously, otherwise they are executed in order. If not all jobs have been executed after the specified timeout a PoolTimeoutError will be raised. When operating synchronously the timeout is checked before each job is run. """ start = time.time() deadline = start + timeout if pool: queue = six.moves.queue.Queue() def pool_executor(job): job.run() queue.put(job) for job in jobs: pool.apply_async(func=pool_executor, args=[job]) done = 0 total = len(jobs) while done < total: wait_time = max(0, deadline - time.time()) try: job = queue.get(True, wait_time) except six.moves.queue.Empty: raise PoolTimeoutError("Timed out after %fs" % (time.time() - start)) done += 1 yield job else: for job in jobs: if time.time() > deadline: raise PoolTimeoutError("Timed out after %fs" % (time.time() - start)) job.run() yield job graphite-web-1.1.4/webapp/graphite/events/0000755000000000000000000000000013343335472020425 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/events/__init__.py0000644000000000000000000000006513343334667022544 0ustar rootroot00000000000000# Two wrongs don't make a right, but three lefts do. graphite-web-1.1.4/webapp/graphite/events/migrations/0000755000000000000000000000000013343335472022601 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/events/migrations/__init__.py0000644000000000000000000000000013343334667024705 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/events/migrations/0001_initial.py0000644000000000000000000000140213343334667025246 0ustar rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-14 11:22 from __future__ import unicode_literals from django.db import migrations, models import tagging.fields class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Event', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('when', models.DateTimeField()), ('what', models.CharField(max_length=255)), ('data', models.TextField(blank=True)), ('tags', tagging.fields.TagField(blank=True, default=b'', max_length=255)), ], ), ] graphite-web-1.1.4/webapp/graphite/events/admin.py0000644000000000000000000000014613343334667022075 0ustar rootroot00000000000000from django.contrib import admin from graphite.events.models import Event admin.site.register(Event) graphite-web-1.1.4/webapp/graphite/events/models.py0000644000000000000000000000315513343334667022273 0ustar rootroot00000000000000import os from django.db import models from tagging.models import Tag from graphite.events.compat import ModelTaggedItemManager if os.environ.get('READTHEDOCS'): TagField = lambda *args, **kwargs: None else: from tagging.fields import TagField class Event(models.Model): when = models.DateTimeField() what = models.CharField(max_length=255) data = models.TextField(blank=True) tags = TagField(default="") def get_tags(self): return Tag.objects.get_for_object(self) def __str__(self): return "%s: %s" % (self.when, self.what) @staticmethod def find_events(time_from=None, time_until=None, tags=None, set_operation=None): if tags is not None: if set_operation == 'union': query = Event.tagged.with_any(tags) elif set_operation == 'intersection': query = Event.tagged.with_intersection(tags) else: query = Event.tagged.with_all(tags) else: query = Event.objects.all() if time_from is not None: query = query.filter(when__gte=time_from) if time_until is not None: query = query.filter(when__lte=time_until) result = list(query.order_by("when")) return result def as_dict(self): return dict( when=self.when, what=self.what, data=self.data, tags=self.tags.split(), id=self.id, ) # We use this rather than tagging.register() so that tags can be exposed # in the admin UI ModelTaggedItemManager().contribute_to_class(Event, 'tagged') graphite-web-1.1.4/webapp/graphite/events/compat.py0000644000000000000000000000077113343334667022274 0ustar rootroot00000000000000 from tagging.managers import (ModelTaggedItemManager as BaseModelTaggedItemManager, TaggedItem) class ContentTypeMixin(object): def with_intersection(self, tags, queryset=None): if queryset is None: return TaggedItem.objects.get_intersection_by_model(self.model, tags) else: return TaggedItem.objects.get_intersection_by_model(queryset, tags) class ModelTaggedItemManager(ContentTypeMixin, BaseModelTaggedItemManager): pass graphite-web-1.1.4/webapp/graphite/events/urls.py0000644000000000000000000000151013343334667021766 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/get_data?$', views.get_data, name='events_get_data'), url(r'^/(?P\d+)/?$', views.detail, name='events_detail'), url(r'^/?$', views.view_events, name='events'), ] graphite-web-1.1.4/webapp/graphite/events/views.py0000644000000000000000000000775413343334667022156 0ustar rootroot00000000000000import datetime import six try: from django.contrib.sites.requests import RequestSite except ImportError: # Django < 1.9 from django.contrib.sites.models import RequestSite from django.core.exceptions import ObjectDoesNotExist from django.core.serializers.json import DjangoJSONEncoder from django.forms.models import model_to_dict from django.shortcuts import render_to_response, get_object_or_404 from django.utils.timezone import now from graphite.util import json, epoch, epoch_to_dt, jsonResponse, HttpError, HttpResponse from graphite.events.models import Event from graphite.render.attime import parseATTime class EventEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return epoch(obj) return json.JSONEncoder.default(self, obj) def view_events(request): if request.method == 'GET': context = {'events': fetch(request), 'site': RequestSite(request), 'protocol': 'https' if request.is_secure() else 'http'} return render_to_response('events.html', context) else: return post_event(request) @jsonResponse(encoder=DjangoJSONEncoder) def jsonDetail(request, queryParams, event_id): try: e = Event.objects.get(id=event_id) e.tags = e.tags.split() return model_to_dict(e) except ObjectDoesNotExist: raise HttpError('Event matching query does not exist', status=404) def detail(request, event_id): if request.META.get('HTTP_ACCEPT') == 'application/json': return jsonDetail(request, event_id) e = get_object_or_404(Event, pk=event_id) context = {'event': e} return render_to_response('event.html', context) def post_event(request): if request.method == 'POST': event = json.loads(request.body) assert isinstance(event, dict) tags = event.get('tags') if tags is not None: if isinstance(tags, list): tags = ' '.join(tags) elif not isinstance(tags, six.string_types): return HttpResponse( json.dumps({'error': '"tags" must be an array or space-separated string'}), status=400) else: tags = None if 'when' in event: when = epoch_to_dt(event['when']) else: when = now() Event.objects.create( what=event.get('what'), tags=tags, when=when, data=event.get('data', ''), ) return HttpResponse(status=200) else: return HttpResponse(status=405) def get_data(request): query_params = request.GET.copy() query_params.update(request.POST) if 'jsonp' in query_params: response = HttpResponse( "%s(%s)" % (query_params.get('jsonp'), json.dumps(fetch(request), cls=EventEncoder)), content_type='text/javascript') else: response = HttpResponse( json.dumps(fetch(request), cls=EventEncoder), content_type='application/json') return response def fetch(request): if request.GET.get('from') is not None: time_from = parseATTime(request.GET['from']) else: time_from = epoch_to_dt(0) if request.GET.get('until') is not None: time_until = parseATTime(request.GET['until']) else: time_until = now() set_operation = request.GET.get('set') tags = request.GET.get('tags') if tags is not None: tags = request.GET.get('tags').split(' ') result = [] for x in Event.find_events(time_from, time_until, tags=tags, set_operation=set_operation): # django-tagging's with_intersection() returns matches with unknown tags # this is a workaround to ensure we only return positive matches if set_operation == 'intersection': if len(set(tags) & set(x.as_dict()['tags'])) == len(tags): result.append(x.as_dict()) else: result.append(x.as_dict()) return result graphite-web-1.1.4/webapp/graphite/settings.py0000644000000000000000000002402313343334714021332 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" # Django settings for graphite project. # DO NOT MODIFY THIS FILE DIRECTLY - use local_settings.py instead from __future__ import print_function import os import sys from os.path import abspath, dirname, join from warnings import warn from django import VERSION as DJANGO_VERSION try: from django.urls import reverse_lazy except ImportError: # Django < 1.10 from django.core.urlresolvers import reverse_lazy GRAPHITE_WEB_APP_SETTINGS_LOADED = False WEBAPP_VERSION = '1.1.4' DEBUG = False JAVASCRIPT_DEBUG = False DATE_FORMAT = '%m/%d' # Filesystem layout WEB_DIR = dirname( abspath(__file__) ) WEBAPP_DIR = dirname(WEB_DIR) GRAPHITE_ROOT = dirname(WEBAPP_DIR) # Initialize additional path variables # Defaults for these are set after local_settings is imported STATIC_ROOT = '' STATIC_URL = '/static/' URL_PREFIX = '' CONF_DIR = '' DASHBOARD_CONF = '' GRAPHTEMPLATES_CONF = '' STORAGE_DIR = '' WHITELIST_FILE = '' INDEX_FILE = '' LOG_DIR = '' CERES_DIR = '' WHISPER_DIR = '' RRD_DIR = '' STANDARD_DIRS = [] # Timeout settings FIND_TIMEOUT = None # default 3.0 see below FETCH_TIMEOUT = None # default 6.0 see below # Cluster settings CLUSTER_SERVERS = [] # Worker Pool USE_WORKER_POOL = True POOL_MAX_WORKERS = 10 # This settings control whether https is used to communicate between cluster members INTRACLUSTER_HTTPS = False REMOTE_FIND_TIMEOUT = None # Replaced by FIND_TIMEOUT REMOTE_FETCH_TIMEOUT = None # Replaced by FETCH_TIMEOUT REMOTE_RETRY_DELAY = 60.0 REMOTE_EXCLUDE_LOCAL = False STORE_FAIL_ON_ERROR = False REMOTE_STORE_MERGE_RESULTS = True REMOTE_STORE_FORWARD_HEADERS = [] REMOTE_STORE_USE_POST = False REMOTE_BUFFER_SIZE = 1024 * 1024 # Set to 0 to prevent streaming deserialization # Carbonlink settings CARBON_METRIC_PREFIX='carbon' CARBONLINK_HOSTS = ["127.0.0.1:7002"] CARBONLINK_TIMEOUT = 1.0 CARBONLINK_HASHING_KEYFUNC = None CARBONLINK_HASHING_TYPE = 'carbon_ch' CARBONLINK_RETRY_DELAY = 15 REPLICATION_FACTOR = 1 # Cache settings. MEMCACHE_HOSTS = [] MEMCACHE_KEY_PREFIX = '' MEMCACHE_OPTIONS = {} CACHES={} FIND_CACHE_DURATION = 300 FIND_TOLERANCE = 2 * FIND_CACHE_DURATION DEFAULT_CACHE_DURATION = 60 #metric data and graphs are cached for one minute by default DEFAULT_CACHE_POLICY = [] # this setting controls the default xFilesFactor used for query-time aggregration DEFAULT_XFILES_FACTOR = 0 # These can also be configured using: # https://docs.djangoproject.com/en/1.11/topics/logging/ LOG_RENDERING_PERFORMANCE = False LOG_CACHE_PERFORMANCE = False LOG_ROTATION = True LOG_ROTATION_COUNT = 1 LOG_FILE_INFO = 'info.log' LOG_FILE_EXCEPTION = 'exception.log' LOG_FILE_CACHE = 'cache.log' LOG_FILE_RENDERING = 'rendering.log' MAX_FETCH_RETRIES = 2 # This settings limit metrics find to prevent from too large query METRICS_FIND_WARNING_THRESHOLD = float('Inf') # Print a warning if more than X metrics are returned METRICS_FIND_FAILURE_THRESHOLD = float('Inf') # Fail if more than X metrics are returned #Remote rendering settings REMOTE_RENDERING = False #if True, rendering is delegated to RENDERING_HOSTS RENDERING_HOSTS = [] REMOTE_RENDER_CONNECT_TIMEOUT = 1.0 #Miscellaneous settings SMTP_SERVER = "localhost" DOCUMENTATION_VERSION = 'latest' if 'dev' in WEBAPP_VERSION else WEBAPP_VERSION DOCUMENTATION_URL = 'https://graphite.readthedocs.io/en/{}/'.format(DOCUMENTATION_VERSION) ALLOW_ANONYMOUS_CLI = True LEGEND_MAX_ITEMS = 10 RRD_CF = 'AVERAGE' STORAGE_FINDERS = ( 'graphite.finders.remote.RemoteFinder', 'graphite.finders.standard.StandardFinder', ) # TagDB settings TAGDB = 'graphite.tags.localdatabase.LocalDatabaseTagDB' TAGDB_CACHE_DURATION = 60 TAGDB_AUTOCOMPLETE_LIMIT = 100 TAGDB_REDIS_HOST = 'localhost' TAGDB_REDIS_PORT = 6379 TAGDB_REDIS_DB = 0 TAGDB_HTTP_URL = '' TAGDB_HTTP_USER = '' TAGDB_HTTP_PASSWORD = '' TAGDB_HTTP_AUTOCOMPLETE = False # Function plugins FUNCTION_PLUGINS = [] MIDDLEWARE = () if DJANGO_VERSION < (1, 10): MIDDLEWARE_CLASSES = MIDDLEWARE MAX_TAG_LENGTH = 50 AUTO_REFRESH_INTERVAL = 60 #Authentication settings USE_LDAP_AUTH = False LDAP_SERVER = "" # "ldapserver.mydomain.com" LDAP_PORT = 389 LDAP_USE_TLS = False LDAP_SEARCH_BASE = "" # "OU=users,DC=mydomain,DC=com" LDAP_BASE_USER = "" # "CN=some_readonly_account,DC=mydomain,DC=com" LDAP_BASE_PASS = "" # "my_password" LDAP_USER_QUERY = "" # "(username=%s)" For Active Directory use "(sAMAccountName=%s)" LDAP_URI = None LDAP_USER_DN_TEMPLATE = None #Set this to True to delegate authentication to the web server USE_REMOTE_USER_AUTHENTICATION = False REMOTE_USER_BACKEND = "" # Provide an alternate or subclassed backend REMOTE_USER_MIDDLEWARE = "" # Provide an alternate or subclassed middleware AUTHENTICATION_BACKENDS=[] # Django 1.5 requires this so we set a default but warn the user SECRET_KEY = 'UNSAFE_DEFAULT' # Django 1.5 requires this to be set. Here we default to prior behavior and allow all ALLOWED_HOSTS = [ '*' ] # Override to link a different URL for login (e.g. for django_openid_auth) LOGIN_URL = reverse_lazy('account_login') # Set the default timezone to UTC TIME_ZONE = 'UTC' # Set to True to require authentication to save or delete dashboards DASHBOARD_REQUIRE_AUTHENTICATION = False # Require Django change/delete permissions to save or delete dashboards. # NOTE: Requires DASHBOARD_REQUIRE_AUTHENTICATION to be set DASHBOARD_REQUIRE_PERMISSIONS = False # Name of a group to which the user must belong to save or delete dashboards. Alternative to # DASHBOARD_REQUIRE_PERMISSIONS, particularly useful when using only LDAP (without Admin app) # NOTE: Requires DASHBOARD_REQUIRE_AUTHENTICATION to be set DASHBOARD_REQUIRE_EDIT_GROUP = None DATABASES = None # If using rrdcached, set to the address or socket of the daemon FLUSHRRDCACHED = '' ## Load our local_settings try: from graphite.local_settings import * # noqa except ImportError: print("Could not import graphite.local_settings, using defaults!", file=sys.stderr) ## Load Django settings if they werent picked up in local_settings if not GRAPHITE_WEB_APP_SETTINGS_LOADED: from graphite.app_settings import * # noqa STATICFILES_DIRS = ( join(WEBAPP_DIR, 'content'), ) # Handle renamed timeout settings FIND_TIMEOUT = FIND_TIMEOUT or REMOTE_FIND_TIMEOUT or 3.0 FETCH_TIMEOUT = FETCH_TIMEOUT or REMOTE_FETCH_TIMEOUT or 6.0 ## Set config dependent on flags set in local_settings # Path configuration if not STATIC_ROOT: STATIC_ROOT = join(GRAPHITE_ROOT, 'static') if not CONF_DIR: CONF_DIR = os.environ.get('GRAPHITE_CONF_DIR', join(GRAPHITE_ROOT, 'conf')) if not DASHBOARD_CONF: DASHBOARD_CONF = join(CONF_DIR, 'dashboard.conf') if not GRAPHTEMPLATES_CONF: GRAPHTEMPLATES_CONF = join(CONF_DIR, 'graphTemplates.conf') if not STORAGE_DIR: STORAGE_DIR = os.environ.get('GRAPHITE_STORAGE_DIR', join(GRAPHITE_ROOT, 'storage')) if not WHITELIST_FILE: WHITELIST_FILE = join(STORAGE_DIR, 'lists', 'whitelist') if not INDEX_FILE: INDEX_FILE = join(STORAGE_DIR, 'index') if not LOG_DIR: LOG_DIR = join(STORAGE_DIR, 'log', 'webapp') if not WHISPER_DIR: WHISPER_DIR = join(STORAGE_DIR, 'whisper/') if not CERES_DIR: CERES_DIR = join(STORAGE_DIR, 'ceres/') if not RRD_DIR: RRD_DIR = join(STORAGE_DIR, 'rrd/') if not STANDARD_DIRS: try: import whisper # noqa if os.path.exists(WHISPER_DIR): STANDARD_DIRS.append(WHISPER_DIR) except ImportError: print("WARNING: whisper module could not be loaded, whisper support disabled", file=sys.stderr) try: import ceres # noqa if os.path.exists(CERES_DIR): STANDARD_DIRS.append(CERES_DIR) except ImportError: pass try: import rrdtool # noqa if os.path.exists(RRD_DIR): STANDARD_DIRS.append(RRD_DIR) except ImportError: pass if DATABASES is None: DATABASES = { 'default': { 'NAME': join(STORAGE_DIR, 'graphite.db'), 'ENGINE': 'django.db.backends.sqlite3', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', }, } # Handle URL prefix in static files handling if URL_PREFIX and not STATIC_URL.startswith(URL_PREFIX): STATIC_URL = '/{0}{1}'.format(URL_PREFIX.strip('/'), STATIC_URL) # Default sqlite db file # This is set here so that a user-set STORAGE_DIR is available if 'sqlite3' in DATABASES.get('default',{}).get('ENGINE','') \ and not DATABASES.get('default',{}).get('NAME'): DATABASES['default']['NAME'] = join(STORAGE_DIR, 'graphite.db') # Caching shortcuts if MEMCACHE_HOSTS: CACHES['default'] = { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': MEMCACHE_HOSTS, 'TIMEOUT': DEFAULT_CACHE_DURATION, 'KEY_PREFIX': MEMCACHE_KEY_PREFIX, 'OPTIONS': MEMCACHE_OPTIONS, } if not CACHES: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', }, } # Authentication shortcuts if USE_LDAP_AUTH and LDAP_URI is None: LDAP_URI = "ldap://%s:%d/" % (LDAP_SERVER, LDAP_PORT) if USE_REMOTE_USER_AUTHENTICATION or REMOTE_USER_BACKEND: if REMOTE_USER_MIDDLEWARE: MIDDLEWARE += (REMOTE_USER_MIDDLEWARE,) else: MIDDLEWARE += ('django.contrib.auth.middleware.RemoteUserMiddleware',) if DJANGO_VERSION < (1, 10): MIDDLEWARE_CLASSES = MIDDLEWARE if REMOTE_USER_BACKEND: AUTHENTICATION_BACKENDS.insert(0,REMOTE_USER_BACKEND) else: AUTHENTICATION_BACKENDS.insert(0,'django.contrib.auth.backends.RemoteUserBackend') if USE_LDAP_AUTH: AUTHENTICATION_BACKENDS.insert(0,'graphite.account.ldapBackend.LDAPBackend') if SECRET_KEY == 'UNSAFE_DEFAULT': warn('SECRET_KEY is set to an unsafe default. This should be set in local_settings.py for better security') USE_TZ = True graphite-web-1.1.4/webapp/graphite/finders/0000755000000000000000000000000013343335472020553 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/finders/__init__.py0000644000000000000000000000512413343334667022673 0ustar rootroot00000000000000import fnmatch import os.path import re EXPAND_BRACES_RE = re.compile(r'.*(\{.*?[^\\]?\})') def get_real_metric_path(absolute_path, metric_path): # Support symbolic links (real_metric_path ensures proper cache queries) real_absolute_path = os.path.realpath(absolute_path) if absolute_path != real_absolute_path: # replace left side base_fs_path that contains sym link with real fs path relative_fs_path = metric_path.replace('.', os.sep) real_absolute_path_no_ext, _ext = os.path.splitext(real_absolute_path) base_fs_path = os.path.dirname(real_absolute_path_no_ext[:-len(relative_fs_path)]) real_base_fs_path = os.path.realpath(base_fs_path) real_relative_fs_path = real_absolute_path[len(real_base_fs_path):].lstrip(os.sep) return fs_to_metric(real_relative_fs_path) return metric_path def fs_to_metric(path): dirpath = os.path.dirname(path) filename = os.path.basename(path) return os.path.join(dirpath, filename.split('.')[0]).replace(os.sep, '.') def extract_variants(pattern): """Extract the pattern variants (ie. {foo,bar}baz = foobaz or barbaz).""" v1, v2 = pattern.find('{'), pattern.find('}') if v1 > -1 and v2 > v1: variations = pattern[v1 + 1:v2].split(',') variants = [pattern[:v1] + v + pattern[v2 + 1:] for v in variations] return list({r for v in variants for r in extract_variants(v)}) else: return [pattern] def match_entries(entries, pattern): # First we check for pattern variants (ie. {foo,bar}baz = foobaz or barbaz) matching = [] for variant in expand_braces(pattern): matching.extend(fnmatch.filter(entries, variant)) return list(set(matching)) """ Brace expanding patch for python3 borrowed from: https://bugs.python.org/issue9584 """ def expand_braces(s): res = list() # Used instead of s.strip('{}') because strip is greedy. # We want to remove only ONE leading { and ONE trailing }, if both exist def remove_outer_braces(s): if s[0] == '{' and s[-1] == '}': return s[1:-1] return s m = EXPAND_BRACES_RE.search(s) if m is not None: sub = m.group(1) open_brace, close_brace = m.span(1) if ',' in sub: for pat in sub.strip('{}').split(','): res.extend(expand_braces( s[:open_brace] + pat + s[close_brace:])) else: res.extend(expand_braces( s[:open_brace] + remove_outer_braces(sub) + s[close_brace:])) else: res.append(s.replace('\\}', '}')) return list(set(res)) graphite-web-1.1.4/webapp/graphite/finders/ceres.py0000644000000000000000000000514513343334667022240 0ustar rootroot00000000000000from __future__ import absolute_import import os.path # Use the built-in version of walk if possible, otherwise # use the scandir module version try: from os import walk except ImportError: from scandir import walk from glob import glob from ceres import CeresTree, CeresNode from django.conf import settings from graphite.node import BranchNode, LeafNode from graphite.readers import CeresReader from graphite.finders.utils import BaseFinder from graphite.finders import get_real_metric_path, extract_variants from graphite.tags.utils import TaggedSeries class CeresFinder(BaseFinder): def __init__(self, directory=None): directory = directory or settings.CERES_DIR self.directory = directory self.tree = CeresTree(directory) def find_nodes(self, query): # translate query pattern if it is tagged tagged = not query.pattern.startswith('_tagged.') and ';' in query.pattern if tagged: # tagged series are stored in ceres using encoded names, so to retrieve them we need to # encode the query pattern using the same scheme used in carbon when they are written. variants = [ TaggedSeries.encode(query.pattern, hash_only=True), TaggedSeries.encode(query.pattern, hash_only=False), ] else: variants = extract_variants(query.pattern) for variant in variants: for fs_path in glob(self.tree.getFilesystemPath(variant)): metric_path = self.tree.getNodePath(fs_path) if CeresNode.isNodeDir(fs_path): ceres_node = self.tree.getNode(metric_path) if ceres_node.hasDataForInterval(query.startTime, query.endTime): real_metric_path = get_real_metric_path(fs_path, metric_path) reader = CeresReader(ceres_node, real_metric_path) # if we're finding by tag, return the proper metric path if tagged: metric_path = query.pattern yield LeafNode(metric_path, reader) elif os.path.isdir(fs_path): yield BranchNode(metric_path) def get_index(self, requestContext): matches = [] for root, _, files in walk(settings.CERES_DIR): root = root.replace(settings.CERES_DIR, '') for filename in files: if filename == '.ceres-node': matches.append(root) return sorted([ m.replace('/', '.').lstrip('.') for m in matches ]) graphite-web-1.1.4/webapp/graphite/finders/standard.py0000644000000000000000000002117313343334667022736 0ustar rootroot00000000000000import bisect import fnmatch import os from os.path import isdir, isfile, join, basename, splitext from django.conf import settings # Use the built-in version of scandir/walk if possible, otherwise # use the scandir module version try: from os import scandir, walk except ImportError: from scandir import scandir, walk from graphite.logger import log from graphite.node import BranchNode, LeafNode from graphite.readers import WhisperReader, GzippedWhisperReader, RRDReader from graphite.util import find_escaped_pattern_fields from graphite.finders.utils import BaseFinder from graphite.tags.utils import TaggedSeries from . import fs_to_metric, get_real_metric_path, match_entries, expand_braces class StandardFinder(BaseFinder): DATASOURCE_DELIMITER = '::RRD_DATASOURCE::' def __init__(self, directories=None): directories = directories or settings.STANDARD_DIRS self.directories = directories def find_nodes(self, query): clean_pattern = query.pattern.replace('\\', '') # translate query pattern if it is tagged tagged = not query.pattern.startswith('_tagged.') and ';' in query.pattern if tagged: # tagged series are stored in whisper using encoded names, so to retrieve them we need to # encode the query pattern using the same scheme used in carbon when they are written. encoded_paths = [ TaggedSeries.encode(query.pattern, sep=os.sep, hash_only=True), TaggedSeries.encode(query.pattern, sep=os.sep, hash_only=False), ] pattern_parts = clean_pattern.split('.') for root_dir in self.directories: if tagged: relative_paths = [] for pattern in encoded_paths: entries = [ pattern + '.wsp', pattern + '.wsp.gz', pattern + '.rrd', ] for entry in entries: if isfile(join(root_dir, entry)): relative_paths.append(entry) else: relative_paths = self._find_paths(root_dir, pattern_parts) for relative_path in relative_paths: if basename(relative_path).startswith('.'): continue if self.DATASOURCE_DELIMITER in basename(relative_path): (relative_path, datasource_pattern) = relative_path.rsplit( self.DATASOURCE_DELIMITER, 1) else: datasource_pattern = None absolute_path = join(root_dir, relative_path) metric_path = fs_to_metric(relative_path) real_metric_path = get_real_metric_path(absolute_path, metric_path) # if we're finding by tag, return the proper metric path if tagged: metric_path = query.pattern else: metric_path_parts = metric_path.split('.') for field_index in find_escaped_pattern_fields(query.pattern): metric_path_parts[field_index] = pattern_parts[field_index].replace('\\', '') metric_path = '.'.join(metric_path_parts) # Now we construct and yield an appropriate Node object if isdir(absolute_path): yield BranchNode(metric_path) elif absolute_path.endswith('.wsp') and WhisperReader.supported: reader = WhisperReader(absolute_path, real_metric_path) yield LeafNode(metric_path, reader) elif absolute_path.endswith('.wsp.gz') and GzippedWhisperReader.supported: reader = GzippedWhisperReader(absolute_path, real_metric_path) yield LeafNode(metric_path, reader) elif absolute_path.endswith('.rrd') and RRDReader.supported: if datasource_pattern is None: yield BranchNode(metric_path) else: for datasource_name in RRDReader.get_datasources(absolute_path): if match_entries([datasource_name], datasource_pattern): reader = RRDReader(absolute_path, datasource_name) yield LeafNode(metric_path + "." + datasource_name, reader) def _find_paths(self, current_dir, patterns): """Recursively generates absolute paths whose components underneath current_dir match the corresponding pattern in patterns""" raw_pattern = patterns[0] patterns = patterns[1:] for pattern in expand_braces(raw_pattern): has_wildcard = pattern.find('[') > -1 or pattern.find('*') > -1 or pattern.find('?') > -1 matching_subdirs = [] files = [] if has_wildcard: # this avoids os.listdir() for performance subdirs = [] try: for x in scandir(current_dir): if x.is_file(): files.append(x.name) if x.is_dir(): subdirs.append(x.name) except OSError as e: log.exception(e) if pattern == "**": matching_subdirs = map( lambda item: item[0][len(current_dir) + 1:], walk(current_dir) ) # if this is a terminal globstar, add a pattern for all files in subdirs if not patterns: patterns = ["*"] else: matching_subdirs = match_entries(subdirs, pattern) elif isdir(join(current_dir, pattern)): matching_subdirs.append(pattern) # the last pattern may apply to RRD data sources if len(patterns) == 1 and RRDReader.supported: if not has_wildcard: entries = [ pattern + ".rrd", ] rrd_files = [entry for entry in entries if isfile(join(current_dir, entry))] else: rrd_files = match_entries(files, pattern + ".rrd") if rrd_files: # let's assume it does datasource_pattern = patterns[0] for rrd_file in rrd_files: yield rrd_file + self.DATASOURCE_DELIMITER + datasource_pattern if patterns: # we've still got more directories to traverse for subdir in matching_subdirs: absolute_path = join(current_dir, subdir) for match in self._find_paths(absolute_path, patterns): yield join(subdir, match) else: # we've got the last pattern if not has_wildcard: entries = [ pattern + '.wsp', pattern + '.wsp.gz', pattern + '.rrd', ] matching_files = [entry for entry in entries if isfile(join(current_dir, entry))] else: matching_files = match_entries(files, pattern + '.*') for match in matching_files + matching_subdirs: yield match def get_index(self, requestContext): matches = [] for root, _, files in walk(settings.WHISPER_DIR): root = root.replace(settings.WHISPER_DIR, '') for base_name in files: if fnmatch.fnmatch(base_name, '*.wsp'): match = join(root, base_name).replace('.wsp', '').replace('/', '.').lstrip('.') bisect.insort_left(matches, match) # unlike 0.9.x, we're going to use os.walk with followlinks # since we require Python 2.7 and newer that supports it if RRDReader.supported: for root, _, files in walk(settings.RRD_DIR, followlinks=True): root = root.replace(settings.RRD_DIR, '') for base_name in files: if fnmatch.fnmatch(base_name, '*.rrd'): absolute_path = join(settings.RRD_DIR, root, base_name) base_name = splitext(base_name)[0] metric_path = join(root, base_name) rrd = RRDReader(absolute_path, metric_path) for datasource_name in rrd.get_datasources(absolute_path): match = join(metric_path, datasource_name).replace('.rrd', '').replace('/', '.').lstrip('.') if match not in matches: bisect.insort_left(matches, match) return matches graphite-web-1.1.4/webapp/graphite/finders/utils.py0000644000000000000000000001076413343334667022302 0ustar rootroot00000000000000"""Utility functions for finders.""" import time import abc from graphite.node import BranchNode, LeafNode # noqa from graphite.util import is_pattern from graphite.intervals import Interval class FindQuery(object): def __init__(self, pattern, startTime, endTime, local=False, headers=None, leaves_only=None): self.pattern = pattern self.startTime = startTime self.endTime = endTime self.isExact = is_pattern(pattern) self.interval = Interval( float('-inf') if startTime is None else startTime, float('inf') if endTime is None else endTime) self.local = local self.headers = headers self.leaves_only = leaves_only def __repr__(self): if self.startTime is None: startString = '*' else: startString = time.ctime(self.startTime) if self.endTime is None: endString = '*' else: endString = time.ctime(self.endTime) return '' % ( self.pattern, startString, endString) class BaseFinder(object): __metaclass__ = abc.ABCMeta # Set to False if this is a remote finder. local = True # set to True if this finder shouldn't be used disabled = False # set to True if this finder supports seriesByTag tags = False def __init__(self): """Initialize the finder.""" @abc.abstractmethod def find_nodes(self, query): """Get the list of nodes matching a query. Args: graphite.storage.FindQuery: the query to run. Returns: generator of Node """ def get_index(self, requestContext): """Get a list of all series Args: requestContext Returns: list of series """ query = FindQuery( '**', None, None, local=requestContext.get('localOnly'), headers=requestContext.get('forwardHeaders'), leaves_only=True, ) return sorted([node.path for node in self.find_nodes(query) if node.is_leaf]) # The methods below are fully optional and BaseFinder provides # a default implementation. They can be re-implemented by finders # that could provide a more efficient way of doing it. # # The API isn't fully finalized yet and can be subject to change # until it's documented. @classmethod def factory(cls): return [cls()] def find_multi(self, queries): """Executes multiple find queries. Works like multiple find_node(), this gives the ability for finders to parallelize the work. Returns: generator of (Node, query) """ for query in queries: for node in self.find_nodes(query): yield (node, query) def fetch(self, patterns, start_time, end_time, now=None, requestContext=None): """Fetch multiple patterns at once. This method is used to fetch multiple patterns at once, this allows alternate finders to do batching on their side when they can. Returns: an iterable of { 'pathExpression': pattern, 'path': node.path, 'time_info': time_info, 'values': values, } """ requestContext = requestContext or {} queries = [ FindQuery( pattern, start_time, end_time, local=requestContext.get('localOnly'), headers=requestContext.get('forwardHeaders'), leaves_only=True, ) for pattern in patterns ] results = [] for node, query in self.find_multi(queries): if not isinstance(node, LeafNode): continue result = node.fetch( start_time, end_time, now=now, requestContext=requestContext ) if result is None: continue time_info, values = result results.append({ 'pathExpression': query.pattern, 'path': node.path, 'name': node.path, 'time_info': time_info, 'values': values, }) return results def auto_complete_tags(self, exprs, tagPrefix=None, limit=None, requestContext=None): return [] def auto_complete_values(self, exprs, tag, valuePrefix=None, limit=None, requestContext=None): return [] graphite-web-1.1.4/webapp/graphite/finders/remote.py0000644000000000000000000002361213343334667022431 0ustar rootroot00000000000000import codecs import time from six.moves.urllib.parse import urlencode, urlsplit, parse_qs from django.conf import settings from django.core.cache import cache from graphite.http_pool import http from graphite.intervals import Interval, IntervalSet from graphite.logger import log from graphite.node import LeafNode, BranchNode from graphite.render.hashing import compactHash from graphite.util import unpickle, logtime, is_local_interface, json, msgpack, BufferedHTTPReader from graphite.finders.utils import BaseFinder from graphite.readers.remote import RemoteReader class RemoteFinder(BaseFinder): local = False @classmethod def factory(cls): finders = [] for host in settings.CLUSTER_SERVERS: if settings.REMOTE_EXCLUDE_LOCAL and is_local_interface(cls.parse_host(host)['host']): continue finders.append(cls(host)) return finders @staticmethod def parse_host(host): if host.startswith('http://') or host.startswith('https://'): parsed = urlsplit(host) else: scheme = 'https' if settings.INTRACLUSTER_HTTPS else 'http' parsed = urlsplit(scheme + '://' + host) return { 'host': parsed.netloc, 'url': '%s://%s%s' % (parsed.scheme, parsed.netloc, parsed.path), 'params': {key: value[-1] for (key, value) in parse_qs(parsed.query).items()}, } def __init__(self, host): parsed = self.parse_host(host) self.host = parsed['host'] self.url = parsed['url'] self.params = parsed['params'] self.last_failure = 0 self.tags = not self.params.get('noTags') @property def disabled(self): return time.time() - self.last_failure < settings.REMOTE_RETRY_DELAY def fail(self): self.last_failure = time.time() @logtime def find_nodes(self, query, timer=None): timer.set_msg( 'host: {host}, query: {query}'.format( host=self.host, query=query)) log.debug("RemoteFinder.find_nodes(host=%s, query=%s) called" % (self.host, query)) # prevent divide by 0 cacheTTL = settings.FIND_CACHE_DURATION or 1 if query.startTime: start = query.startTime - (query.startTime % cacheTTL) else: start = "" if query.endTime: end = query.endTime - (query.endTime % cacheTTL) else: end = "" cacheKey = "find:%s:%s:%s:%s" % (self.host, compactHash(query.pattern), start, end) results = cache.get(cacheKey) if results is not None: log.debug( "RemoteFinder.find_nodes(host=%s, query=%s) using cached result" % (self.host, query)) else: url = '/metrics/find/' query_params = [ ('local', self.params.get('local', '1')), ('format', self.params.get('format', 'pickle')), ('query', query.pattern), ] if query.startTime: query_params.append(('from', int(query.startTime))) if query.endTime: query_params.append(('until', int(query.endTime))) result = self.request( url, fields=query_params, headers=query.headers, timeout=settings.FIND_TIMEOUT) try: if result.getheader('content-type') == 'application/x-msgpack': results = msgpack.load(BufferedHTTPReader( result, buffer_size=settings.REMOTE_BUFFER_SIZE), encoding='utf-8') else: results = unpickle.load(BufferedHTTPReader( result, buffer_size=settings.REMOTE_BUFFER_SIZE)) except Exception as err: self.fail() log.exception( "RemoteFinder[%s] Error decoding find response from %s: %s" % (self.host, result.url_full, err)) raise Exception("Error decoding find response from %s: %s" % (result.url_full, err)) finally: result.release_conn() cache.set(cacheKey, results, settings.FIND_CACHE_DURATION) for node_info in results: # handle both 1.x and 0.9.x output path = node_info.get('path') or node_info.get('metric_path') is_leaf = node_info.get('is_leaf') or node_info.get('isLeaf') intervals = node_info.get('intervals') or [] if not isinstance(intervals, IntervalSet): intervals = IntervalSet( [Interval(interval[0], interval[1]) for interval in intervals]) node_info = { 'is_leaf': is_leaf, 'path': path, 'intervals': intervals, } if is_leaf: reader = RemoteReader(self, node_info) node = LeafNode(path, reader) else: node = BranchNode(path) node.local = False yield node def fetch(self, patterns, start_time, end_time, now=None, requestContext=None): reader = RemoteReader(self, {}, bulk_query=patterns) return reader.fetch_multi(start_time, end_time, now, requestContext) def get_index(self, requestContext): url = '/metrics/index.json' headers = requestContext.get('forwardHeaders') result = self.request( url, fields=[ ('local', self.params.get('local', '1')), ], headers=headers, timeout=settings.FIND_TIMEOUT) try: reader = codecs.getreader('utf-8') results = json.load(reader(result)) except Exception as err: self.fail() log.exception( "RemoteFinder[%s] Error decoding index response from %s: %s" % (self.host, result.url_full, err)) raise Exception("Error decoding index response from %s: %s" % (result.url_full, err)) finally: result.release_conn() return results def auto_complete_tags(self, exprs, tagPrefix=None, limit=None, requestContext=None): """ Return auto-complete suggestions for tags based on the matches for the specified expressions, optionally filtered by tag prefix """ if limit is None: limit = settings.TAGDB_AUTOCOMPLETE_LIMIT fields = [ ('tagPrefix', tagPrefix or ''), ('limit', str(limit)), ('local', self.params.get('local', '1')), ] for expr in exprs: fields.append(('expr', expr)) result = self.request( '/tags/autoComplete/tags', fields, headers=requestContext.get('forwardHeaders') if requestContext else None, timeout=settings.FIND_TIMEOUT) try: reader = codecs.getreader('utf-8') results = json.load(reader(result)) except Exception as err: self.fail() log.exception( "RemoteFinder[%s] Error decoding autocomplete tags response from %s: %s" % (self.host, result.url_full, err)) raise Exception("Error decoding autocomplete tags response from %s: %s" % (result.url_full, err)) finally: result.release_conn() return results def auto_complete_values(self, exprs, tag, valuePrefix=None, limit=None, requestContext=None): """ Return auto-complete suggestions for tags and values based on the matches for the specified expressions, optionally filtered by tag and/or value prefix """ if limit is None: limit = settings.TAGDB_AUTOCOMPLETE_LIMIT fields = [ ('tag', tag or ''), ('valuePrefix', valuePrefix or ''), ('limit', str(limit)), ('local', self.params.get('local', '1')), ] for expr in exprs: fields.append(('expr', expr)) result = self.request( '/tags/autoComplete/values', fields, headers=requestContext.get('forwardHeaders') if requestContext else None, timeout=settings.FIND_TIMEOUT) try: reader = codecs.getreader('utf-8') results = json.load(reader(result)) except Exception as err: self.fail() log.exception( "RemoteFinder[%s] Error decoding autocomplete values response from %s: %s" % (self.host, result.url_full, err)) raise Exception("Error decoding autocomplete values response from %s: %s" % (result.url_full, err)) finally: result.release_conn() return results def request(self, path, fields=None, headers=None, timeout=None): url = "%s%s" % (self.url, path) url_full = "%s?%s" % (url, urlencode(fields)) try: result = http.request( 'POST' if settings.REMOTE_STORE_USE_POST else 'GET', url, fields=fields, headers=headers, timeout=timeout, preload_content=False) except BaseException as err: self.fail() log.exception("RemoteFinder[%s] Error requesting %s: %s" % (self.host, url_full, err)) raise Exception("Error requesting %s: %s" % (url_full, err)) if result.status != 200: result.release_conn() self.fail() log.exception( "RemoteFinder[%s] Error response %d from %s" % (self.host, result.status, url_full)) raise Exception("Error response %d from %s" % (result.status, url_full)) result.url_full = url_full # reset last failure time so that retried fetches can re-enable a remote self.last_failure = 0 log.debug("RemoteFinder[%s] Fetched %s" % (self.host, url_full)) return result graphite-web-1.1.4/webapp/graphite/browser/0000755000000000000000000000000013343335472020604 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/browser/__init__.py0000644000000000000000000000000013343334667022710 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/browser/urls.py0000644000000000000000000000171413343334667022153 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/header/?$', views.header, name='browser_header'), url(r'^/search/?$', views.search, name='browser_search'), url(r'^/mygraph/?$', views.myGraphLookup, name='browser_my_graph'), url(r'^/usergraph/?$', views.userGraphLookup, name='browser_usergraph'), url(r'^/?$', views.browser, name='browser'), ] graphite-web-1.1.4/webapp/graphite/browser/views.py0000644000000000000000000002005713343334667022324 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import re from django.conf import settings from django.shortcuts import render_to_response from django.utils.safestring import mark_safe from django.utils.html import escape from graphite.account.models import Profile from graphite.compat import HttpResponse from graphite.user_util import getProfile, getProfileByUsername from graphite.util import json from graphite.logger import log from hashlib import md5 from six.moves.urllib.parse import urlencode, urlparse, parse_qsl def header(request): "View for the header frame of the browser UI" context = {} context['user'] = request.user context['profile'] = getProfile(request) context['documentation_url'] = settings.DOCUMENTATION_URL context['login_url'] = settings.LOGIN_URL return render_to_response("browserHeader.html", context) def browser(request): "View for the top-level frame of the browser UI" context = { 'queryString': mark_safe(request.GET.urlencode()), 'target': request.GET.get('target') } if context['queryString']: context['queryString'] = context['queryString'].replace('#','%23') if context['target']: context['target'] = context['target'].replace('#','%23') #js libs terminate a querystring on # return render_to_response("browser.html", context) def search(request): query = request.POST.get('query') if not query: return HttpResponse("") patterns = query.split() regexes = [re.compile(p,re.I) for p in patterns] def matches(s): for regex in regexes: if regex.search(s): return True return False results = [] index_file = open(settings.INDEX_FILE) for line in index_file: if matches(line): results.append( line.strip() ) if len(results) >= 100: break index_file.close() result_string = ','.join(results) return HttpResponse(result_string, content_type='text/plain') def myGraphLookup(request): "View for My Graphs navigation" profile = getProfile(request,allowDefault=False) assert profile nodes = [] leafNode = { 'allowChildren' : 0, 'expandable' : 0, 'leaf' : 1, } branchNode = { 'allowChildren' : 1, 'expandable' : 1, 'leaf' : 0, } try: path = request.GET.get('path', u'') if path: if path.endswith('.'): userpath_prefix = path else: userpath_prefix = path + '.' else: userpath_prefix = u"" matches = [ graph for graph in profile.mygraph_set.all().order_by('name') if graph.name.startswith(userpath_prefix) ] log.info( "myGraphLookup: username=%s, path=%s, userpath_prefix=%s, %ld graph to process" % (profile.user.username, path, userpath_prefix, len(matches)) ) branch_inserted = set() leaf_inserted = set() for graph in matches: #Now let's add the matching graph isBranch = False dotPos = graph.name.find( '.', len(userpath_prefix) ) if dotPos >= 0: isBranch = True name = graph.name[ len(userpath_prefix) : dotPos ] if name in branch_inserted: continue branch_inserted.add(name) else: name = graph.name[ len(userpath_prefix): ] if name in leaf_inserted: continue leaf_inserted.add(name) node = {'text': escape(name)} if isBranch: node.update({'id': userpath_prefix + name + '.'}) node.update(branchNode) else: m = md5() m.update(name.encode('utf-8')) # Sanitize target urlEscaped = str(graph.url) graphUrl = urlparse(urlEscaped) graphUrlParams = {} graphUrlParams['target'] = [] for param in parse_qsl(graphUrl.query): if param[0] != 'target': graphUrlParams[param[0]] = param[1] else: graphUrlParams[param[0]].append(escape(param[1])) urlEscaped = graphUrl._replace(query=urlencode(graphUrlParams, True)).geturl() node.update( { 'id' : str(userpath_prefix + m.hexdigest()), 'graphUrl' : urlEscaped } ) node.update(leafNode) nodes.append(node) except Exception: log.exception("browser.views.myGraphLookup(): could not complete request.") if not nodes: no_graphs = { 'text' : "No saved graphs", 'id' : 'no-click' } no_graphs.update(leafNode) nodes.append(no_graphs) return json_response(nodes, request) def userGraphLookup(request): "View for User Graphs navigation" user = request.GET.get('user') path = request.GET['path'] if user: username = user graphPath = path[len(username)+1:] elif '.' in path: username, graphPath = path.split('.', 1) else: username, graphPath = path, None nodes = [] branchNode = { 'allowChildren' : 1, 'expandable' : 1, 'leaf' : 0, } leafNode = { 'allowChildren' : 0, 'expandable' : 0, 'leaf' : 1, } try: if not username: profiles = Profile.objects.exclude(user__username='default').order_by('user__username') for profile in profiles: if profile.mygraph_set.count(): node = { 'text' : profile.user.username, 'id' : profile.user.username, } node.update(branchNode) nodes.append(node) else: profile = getProfileByUsername(username) assert profile, "No profile for username '%s'" % username if graphPath: prefix = graphPath.rstrip('.') + '.' else: prefix = '' matches = [ graph for graph in profile.mygraph_set.order_by('name') if graph.name.startswith(prefix) ] inserted = set() for graph in matches: relativePath = graph.name[ len(prefix): ] nodeName = relativePath.split('.')[0] if nodeName in inserted: continue inserted.add(nodeName) if '.' in relativePath: # branch node = { 'text' : escape(nodeName), 'id' : username + '.' + prefix + nodeName + '.', } node.update(branchNode) else: # leaf m = md5() m.update(nodeName.encode('utf-8')) # Sanitize target urlEscaped = str(graph.url) graphUrl = urlparse(urlEscaped) graphUrlParams = {} graphUrlParams['target'] = [] for param in parse_qsl(graphUrl.query): if param[0] != 'target': graphUrlParams[param[0]] = param[1] else: graphUrlParams[param[0]].append(escape(param[1])) urlEscaped = graphUrl._replace(query=urlencode(graphUrlParams, True)).geturl() node = { 'text' : escape(nodeName), 'id' : username + '.' + prefix + m.hexdigest(), 'graphUrl' : urlEscaped, } node.update(leafNode) nodes.append(node) except Exception: log.exception("browser.views.userLookup(): could not complete request for %s" % username) if not nodes: no_graphs = { 'text' : "No saved graphs", 'id' : 'no-click' } no_graphs.update(leafNode) nodes.append(no_graphs) nodes.sort(key=lambda node: node['allowChildren'], reverse = True) return json_response(nodes, request) def json_response(nodes, request=None): if request: jsonp = request.GET.get('jsonp', False) or request.POST.get('jsonp', False) else: jsonp = False #json = str(nodes) #poor man's json encoder for simple types json_data = json.dumps(nodes) if jsonp: response = HttpResponse("%s(%s)" % (jsonp, json_data), content_type="text/javascript") else: response = HttpResponse(json_data, content_type="application/json") response['Pragma'] = 'no-cache' response['Cache-Control'] = 'no-cache' return response graphite-web-1.1.4/webapp/graphite/url_shortener/0000755000000000000000000000000013343335472022014 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/url_shortener/__init__.py0000644000000000000000000000000013343334667024120 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/url_shortener/migrations/0000755000000000000000000000000013343335472024170 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/url_shortener/migrations/__init__.py0000644000000000000000000000000013343334667026274 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/url_shortener/migrations/0001_initial.py0000644000000000000000000000114313343334667026637 0ustar rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-14 11:22 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Link', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('url', models.TextField()), ('date_submitted', models.DateTimeField(auto_now_add=True)), ], ), ] graphite-web-1.1.4/webapp/graphite/url_shortener/models.py0000644000000000000000000000022313343334667023653 0ustar rootroot00000000000000from django.db import models class Link(models.Model): url = models.TextField() date_submitted = models.DateTimeField(auto_now_add=True) graphite-web-1.1.4/webapp/graphite/url_shortener/views.py0000644000000000000000000000202513343334667023527 0ustar rootroot00000000000000try: from django.urls import reverse except ImportError: # Django < 1.10 from django.core.urlresolvers import reverse from django.shortcuts import get_object_or_404 from django.http import HttpResponse, HttpResponsePermanentRedirect from graphite.url_shortener.baseconv import base62 from graphite.url_shortener.models import Link import re def follow(request, link_id): """Follow existing links""" key = base62.to_decimal(link_id) link = get_object_or_404(Link, pk=key) return HttpResponsePermanentRedirect(reverse('browser') + link.url) def shorten(request, path): if request.META.get('QUERY_STRING', None): path += '?' + request.META['QUERY_STRING'] # Remove _salt, _dc and _uniq to avoid creating many copies of the same URL path = re.sub('&_(uniq|salt|dc)=[0-9.]+', "", path) link, created = Link.objects.get_or_create(url=path) link_id = base62.from_decimal(link.id) url = reverse('follow', kwargs={'link_id': link_id}) return HttpResponse(url, content_type='text/plain') graphite-web-1.1.4/webapp/graphite/url_shortener/baseconv.py0000644000000000000000000000302013343334667024166 0ustar rootroot00000000000000""" Convert numbers from base 10 integers to base X strings and back again. Original: http://www.djangosnippets.org/snippets/1431/ Sample usage: >>> base20 = BaseConverter('0123456789abcdefghij') >>> base20.from_decimal(1234) '31e' >>> base20.to_decimal('31e') 1234 """ class BaseConverter(object): decimal_digits = "0123456789" def __init__(self, digits): self.digits = digits def from_decimal(self, i): return self.convert(i, self.decimal_digits, self.digits) def to_decimal(self, s): return int(self.convert(s, self.digits, self.decimal_digits)) def convert(number, fromdigits, todigits): # Based on http://code.activestate.com/recipes/111286/ if str(number)[0] == '-': number = str(number)[1:] neg = 1 else: neg = 0 # make an integer out of the number x = 0 for digit in str(number): x = x * len(fromdigits) + fromdigits.index(digit) # create the result in base 'len(todigits)' if x == 0: res = todigits[0] else: res = "" while x > 0: digit = x % len(todigits) res = todigits[digit] + res x = int(x / len(todigits)) if neg: res = '-' + res return res convert = staticmethod(convert) bin = BaseConverter('01') hexconv = BaseConverter('0123456789ABCDEF') base62 = BaseConverter( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz' ) graphite-web-1.1.4/webapp/graphite/account/0000755000000000000000000000000013343335472020555 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/account/ldapBackend.py0000644000000000000000000000445413343334667023333 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import ldap, traceback from django.conf import settings from django.contrib.auth.models import User class LDAPBackend: def authenticate(self, username=None, password=None): if settings.LDAP_USER_DN_TEMPLATE is not None: settings.LDAP_BASE_USER = settings.LDAP_USER_DN_TEMPLATE % {'username': username} settings.LDAP_BASE_PASS = password try: conn = ldap.initialize(settings.LDAP_URI) conn.protocol_version = ldap.VERSION3 if settings.LDAP_USE_TLS: conn.start_tls_s() conn.simple_bind_s( settings.LDAP_BASE_USER, settings.LDAP_BASE_PASS ) except ldap.LDAPError: traceback.print_exc() return None scope = ldap.SCOPE_SUBTREE filter = settings.LDAP_USER_QUERY % username returnFields = ['dn','mail'] try: resultID = conn.search( settings.LDAP_SEARCH_BASE, scope, filter, returnFields ) resultType, resultData = conn.result( resultID, 0 ) if len(resultData) != 1: # User does not exist return None userDN = resultData[0][0] try: userMail = resultData[0][1]['mail'][0].decode("utf-8") except Exception: userMail = "Unknown" conn.simple_bind_s(userDN,password) try: user = User.objects.get(username=username) except Exception: # First time login, not in django's database # To prevent login from django db user randomPasswd = User.objects.make_random_password(length=16) user = User.objects.create_user(username, userMail, randomPasswd) user.save() return user except ldap.INVALID_CREDENTIALS: traceback.print_exc() return None def get_user(self,user_id): try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None graphite-web-1.1.4/webapp/graphite/account/__init__.py0000644000000000000000000000000013343334667022661 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/account/migrations/0000755000000000000000000000000013343335472022731 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/account/migrations/__init__.py0000644000000000000000000000000013343334667025035 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/account/migrations/0001_initial.py0000644000000000000000000000560713343334667025411 0ustar rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-14 11:22 from __future__ import unicode_literals from django.conf import settings from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='MyGraph', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=64)), ('url', models.TextField()), ], ), migrations.CreateModel( name='Profile', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('history', models.TextField(default=b'')), ('advancedUI', models.BooleanField(default=False)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='Variable', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=64)), ('value', models.CharField(max_length=64)), ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Profile')), ], ), migrations.CreateModel( name='View', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=64)), ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Profile')), ], ), migrations.CreateModel( name='Window', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=64)), ('top', models.IntegerField()), ('left', models.IntegerField()), ('width', models.IntegerField()), ('height', models.IntegerField()), ('url', models.TextField()), ('interval', models.IntegerField(null=True)), ('view', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.View')), ], ), migrations.AddField( model_name='mygraph', name='profile', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Profile'), ), ] graphite-web-1.1.4/webapp/graphite/account/admin.py0000644000000000000000000000041013343334667022217 0ustar rootroot00000000000000from django.contrib import admin from graphite.account.models import Profile,MyGraph class MyGraphAdmin(admin.ModelAdmin): list_display = ('profile','name') list_filter = ('profile',) admin.site.register(Profile) admin.site.register(MyGraph, MyGraphAdmin) graphite-web-1.1.4/webapp/graphite/account/models.py0000644000000000000000000000324513343334667022423 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.db import models from django.contrib.auth import models as auth_models class Profile(models.Model): user = models.OneToOneField(auth_models.User, on_delete=models.CASCADE) history = models.TextField(default="") advancedUI = models.BooleanField(default=False) __str__ = lambda self: "Profile for %s" % self.user class Variable(models.Model): profile = models.ForeignKey(Profile, on_delete=models.CASCADE) name = models.CharField(max_length=64) value = models.CharField(max_length=64) class View(models.Model): profile = models.ForeignKey(Profile, on_delete=models.CASCADE) name = models.CharField(max_length=64) class Window(models.Model): view = models.ForeignKey(View, on_delete=models.CASCADE) name = models.CharField(max_length=64) top = models.IntegerField() left = models.IntegerField() width = models.IntegerField() height = models.IntegerField() url = models.TextField() interval = models.IntegerField(null=True) class MyGraph(models.Model): profile = models.ForeignKey(Profile, on_delete=models.CASCADE) name = models.CharField(max_length=64) url = models.TextField() graphite-web-1.1.4/webapp/graphite/account/urls.py0000644000000000000000000000160013343334667022116 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/login/?$', views.loginView, name='account_login'), url(r'^/logout/?$', views.logoutView, name='account_logout'), url(r'^/edit/?$', views.editProfile, name='account_edit'), url(r'^/update/?$', views.updateProfile, name='account_update'), ] graphite-web-1.1.4/webapp/graphite/account/views.py0000644000000000000000000000442513343334667022276 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.contrib.auth import authenticate, login, logout try: from django.urls import reverse except ImportError: # Django < 1.10 from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from graphite.user_util import getProfile, isAuthenticated def loginView(request): username = request.POST.get('username') password = request.POST.get('password') if request.method == 'GET': nextPage = request.GET.get('nextPage', reverse('browser')) else: nextPage = request.POST.get('nextPage', reverse('browser')) if username and password: user = authenticate(username=username,password=password) if user is None: return render_to_response("login.html",{'authenticationFailed' : True, 'nextPage' : nextPage}) elif not user.is_active: return render_to_response("login.html",{'accountDisabled' : True, 'nextPage' : nextPage}) else: login(request,user) return HttpResponseRedirect(nextPage) else: return render_to_response("login.html",{'nextPage' : nextPage}) def logoutView(request): nextPage = request.GET.get('nextPage', reverse('browser')) logout(request) return HttpResponseRedirect(nextPage) def editProfile(request): if not isAuthenticated(request.user): return HttpResponseRedirect(reverse('browser')) context = { 'profile' : getProfile(request) } return render_to_response("editProfile.html",context) def updateProfile(request): profile = getProfile(request,allowDefault=False) if profile: profile.advancedUI = request.POST.get('advancedUI','off') == 'on' profile.save() nextPage = request.POST.get('nextPage', reverse('browser')) return HttpResponseRedirect(nextPage) graphite-web-1.1.4/webapp/graphite/umsgpack.py0000644000000000000000000010537613343334667021326 0ustar rootroot00000000000000# u-msgpack-python v2.4.1 - v at sergeev.io # https://github.com/vsergeev/u-msgpack-python # # u-msgpack-python is a lightweight MessagePack serializer and deserializer # module, compatible with both Python 2 and 3, as well CPython and PyPy # implementations of Python. u-msgpack-python is fully compliant with the # latest MessagePack specification.com/msgpack/msgpack/blob/master/spec.md). In # particular, it supports the new binary, UTF-8 string, and application ext # types. # # MIT License # # Copyright (c) 2013-2016 vsergeev / Ivan (Vanya) A. Sergeev # # 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. # """ u-msgpack-python v2.4.1 - v at sergeev.io https://github.com/vsergeev/u-msgpack-python u-msgpack-python is a lightweight MessagePack serializer and deserializer module, compatible with both Python 2 and 3, as well CPython and PyPy implementations of Python. u-msgpack-python is fully compliant with the latest MessagePack specification.com/msgpack/msgpack/blob/master/spec.md). In particular, it supports the new binary, UTF-8 string, and application ext types. License: MIT """ import struct import collections import sys import io __version__ = "2.4.1" "Module version string" version = (2, 4, 1) "Module version tuple" ############################################################################## # Ext Class ############################################################################## # Extension type for application-defined types and data class Ext: """ The Ext class facilitates creating a serializable extension object to store an application-defined type and data byte array. """ def __init__(self, type, data): """ Construct a new Ext object. Args: type: application-defined type integer from 0 to 127 data: application-defined data byte array Raises: TypeError: Specified ext type is outside of 0 to 127 range. Example: >>> foo = umsgpack.Ext(0x05, b"\x01\x02\x03") >>> umsgpack.packb({u"special stuff": foo, u"awesome": True}) '\x82\xa7awesome\xc3\xadspecial stuff\xc7\x03\x05\x01\x02\x03' >>> bar = umsgpack.unpackb(_) >>> print(bar["special stuff"]) Ext Object (Type: 0x05, Data: 01 02 03) >>> """ # Application ext type should be 0 <= type <= 127 if not isinstance(type, int) or not (type >= 0 and type <= 127): raise TypeError("ext type out of range") # Check data is type bytes elif sys.version_info[0] == 3 and not isinstance(data, bytes): raise TypeError("ext data is not type \'bytes\'") elif sys.version_info[0] == 2 and not isinstance(data, str): raise TypeError("ext data is not type \'str\'") self.type = type self.data = data def __eq__(self, other): """ Compare this Ext object with another for equality. """ return (isinstance(other, self.__class__) and self.type == other.type and self.data == other.data) def __ne__(self, other): """ Compare this Ext object with another for inequality. """ return not self.__eq__(other) def __str__(self): """ String representation of this Ext object. """ s = "Ext Object (Type: 0x%02x, Data: " % self.type s += " ".join(["0x%02x" % ord(self.data[i:i + 1]) for i in xrange(min(len(self.data), 8))]) if len(self.data) > 8: s += " ..." s += ")" return s def __hash__(self): """ Provide a hash of this Ext object. """ return hash((self.type, self.data)) class InvalidString(bytes): """Subclass of bytes to hold invalid UTF-8 strings.""" pass ############################################################################## # Exceptions ############################################################################## # Base Exception classes class PackException(Exception): "Base class for exceptions encountered during packing." pass class UnpackException(Exception): "Base class for exceptions encountered during unpacking." pass # Packing error class UnsupportedTypeException(PackException): "Object type not supported for packing." pass # Unpacking error class InsufficientDataException(UnpackException): "Insufficient data to unpack the serialized object." pass class InvalidStringException(UnpackException): "Invalid UTF-8 string encountered during unpacking." pass class ReservedCodeException(UnpackException): "Reserved code encountered during unpacking." pass class UnhashableKeyException(UnpackException): """ Unhashable key encountered during map unpacking. The serialized map cannot be deserialized into a Python dictionary. """ pass class DuplicateKeyException(UnpackException): "Duplicate key encountered during map unpacking." pass # Backwards compatibility KeyNotPrimitiveException = UnhashableKeyException KeyDuplicateException = DuplicateKeyException ############################################################################# # Exported Functions and Glob ############################################################################# # Exported functions and variables, set up in __init() pack = None packb = None unpack = None unpackb = None dump = None dumps = None load = None loads = None compatibility = False """ Compatibility mode boolean. When compatibility mode is enabled, u-msgpack-python will serialize both unicode strings and bytes into the old "raw" msgpack type, and deserialize the "raw" msgpack type into bytes. This provides backwards compatibility with the old MessagePack specification. Example: >>> umsgpack.compatibility = True >>> >>> umsgpack.packb([u"some string", b"some bytes"]) b'\x92\xabsome string\xaasome bytes' >>> umsgpack.unpackb(_) [b'some string', b'some bytes'] >>> """ ############################################################################## # Packing ############################################################################## # You may notice struct.pack("B", obj) instead of the simpler chr(obj) in the # code below. This is to allow for seamless Python 2 and 3 compatibility, as # chr(obj) has a str return type instead of bytes in Python 3, and # struct.pack(...) has the right return type in both versions. def _pack_integer(obj, fp, options): if obj < 0: if obj >= -32: fp.write(struct.pack("b", obj)) elif obj >= -2**(8 - 1): fp.write(b"\xd0" + struct.pack("b", obj)) elif obj >= -2**(16 - 1): fp.write(b"\xd1" + struct.pack(">h", obj)) elif obj >= -2**(32 - 1): fp.write(b"\xd2" + struct.pack(">i", obj)) elif obj >= -2**(64 - 1): fp.write(b"\xd3" + struct.pack(">q", obj)) else: raise UnsupportedTypeException("huge signed int") else: if obj <= 127: fp.write(struct.pack("B", obj)) elif obj <= 2**8 - 1: fp.write(b"\xcc" + struct.pack("B", obj)) elif obj <= 2**16 - 1: fp.write(b"\xcd" + struct.pack(">H", obj)) elif obj <= 2**32 - 1: fp.write(b"\xce" + struct.pack(">I", obj)) elif obj <= 2**64 - 1: fp.write(b"\xcf" + struct.pack(">Q", obj)) else: raise UnsupportedTypeException("huge unsigned int") def _pack_nil(obj, fp, options): fp.write(b"\xc0") def _pack_boolean(obj, fp, options): fp.write(b"\xc3" if obj else b"\xc2") def _pack_float(obj, fp, options): float_precision = options.get('force_float_precision', _float_precision) if float_precision == "double": fp.write(b"\xcb" + struct.pack(">d", obj)) elif float_precision == "single": fp.write(b"\xca" + struct.pack(">f", obj)) else: raise ValueError("invalid float precision") def _pack_string(obj, fp, options): obj = obj.encode('utf-8') if len(obj) <= 31: fp.write(struct.pack("B", 0xa0 | len(obj)) + obj) elif len(obj) <= 2**8 - 1: fp.write(b"\xd9" + struct.pack("B", len(obj)) + obj) elif len(obj) <= 2**16 - 1: fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj) elif len(obj) <= 2**32 - 1: fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj) else: raise UnsupportedTypeException("huge string") def _pack_binary(obj, fp, options): if len(obj) <= 2**8 - 1: fp.write(b"\xc4" + struct.pack("B", len(obj)) + obj) elif len(obj) <= 2**16 - 1: fp.write(b"\xc5" + struct.pack(">H", len(obj)) + obj) elif len(obj) <= 2**32 - 1: fp.write(b"\xc6" + struct.pack(">I", len(obj)) + obj) else: raise UnsupportedTypeException("huge binary string") def _pack_oldspec_raw(obj, fp, options): if len(obj) <= 31: fp.write(struct.pack("B", 0xa0 | len(obj)) + obj) elif len(obj) <= 2**16 - 1: fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj) elif len(obj) <= 2**32 - 1: fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj) else: raise UnsupportedTypeException("huge raw string") def _pack_ext(obj, fp, options): if len(obj.data) == 1: fp.write(b"\xd4" + struct.pack("B", obj.type & 0xff) + obj.data) elif len(obj.data) == 2: fp.write(b"\xd5" + struct.pack("B", obj.type & 0xff) + obj.data) elif len(obj.data) == 4: fp.write(b"\xd6" + struct.pack("B", obj.type & 0xff) + obj.data) elif len(obj.data) == 8: fp.write(b"\xd7" + struct.pack("B", obj.type & 0xff) + obj.data) elif len(obj.data) == 16: fp.write(b"\xd8" + struct.pack("B", obj.type & 0xff) + obj.data) elif len(obj.data) <= 2**8 - 1: fp.write(b"\xc7" + struct.pack("BB", len(obj.data), obj.type & 0xff) + obj.data) elif len(obj.data) <= 2**16 - 1: fp.write(b"\xc8" + struct.pack(">HB", len(obj.data), obj.type & 0xff) + obj.data) elif len(obj.data) <= 2**32 - 1: fp.write(b"\xc9" + struct.pack(">IB", len(obj.data), obj.type & 0xff) + obj.data) else: raise UnsupportedTypeException("huge ext data") def _pack_array(obj, fp, options): if len(obj) <= 15: fp.write(struct.pack("B", 0x90 | len(obj))) elif len(obj) <= 2**16 - 1: fp.write(b"\xdc" + struct.pack(">H", len(obj))) elif len(obj) <= 2**32 - 1: fp.write(b"\xdd" + struct.pack(">I", len(obj))) else: raise UnsupportedTypeException("huge array") for e in obj: pack(e, fp, **options) def _pack_map(obj, fp, options): if len(obj) <= 15: fp.write(struct.pack("B", 0x80 | len(obj))) elif len(obj) <= 2**16 - 1: fp.write(b"\xde" + struct.pack(">H", len(obj))) elif len(obj) <= 2**32 - 1: fp.write(b"\xdf" + struct.pack(">I", len(obj))) else: raise UnsupportedTypeException("huge array") for k, v in obj.items(): pack(k, fp, **options) pack(v, fp, **options) ######################################## # Pack for Python 2, with 'unicode' type, 'str' type, and 'long' type def _pack2(obj, fp, **options): """ Serialize a Python object into MessagePack bytes. Args: obj: a Python object fp: a .write()-supporting file-like object Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping a custom type to a callable that packs an instance of the type into an Ext object force_float_precision (str): "single" to force packing floats as IEEE-754 single-precision floats, "double" to force packing floats as IEEE-754 double-precision floats. Returns: None. Raises: UnsupportedType(PackException): Object type not supported for packing. Example: >>> f = open('test.bin', 'wb') >>> umsgpack.pack({u"compact": True, u"schema": 0}, f) >>> """ global compatibility ext_handlers = options.get("ext_handlers") if obj is None: _pack_nil(obj, fp, options) elif ext_handlers and obj.__class__ in ext_handlers: _pack_ext(ext_handlers[obj.__class__](obj), fp, options) elif isinstance(obj, bool): _pack_boolean(obj, fp, options) elif isinstance(obj, int) or isinstance(obj, long): # noqa: F821 Py2 only _pack_integer(obj, fp, options) elif isinstance(obj, float): _pack_float(obj, fp, options) elif compatibility and isinstance(obj, unicode): # noqa: F821 Py2 only _pack_oldspec_raw(bytes(obj), fp, options) elif compatibility and isinstance(obj, bytes): _pack_oldspec_raw(obj, fp, options) elif isinstance(obj, unicode): # noqa: F821 Py2 only _pack_string(obj, fp, options) elif isinstance(obj, str): _pack_binary(obj, fp, options) elif isinstance(obj, list) or isinstance(obj, tuple): _pack_array(obj, fp, options) elif isinstance(obj, dict): _pack_map(obj, fp, options) elif isinstance(obj, Ext): _pack_ext(obj, fp, options) elif ext_handlers: # Linear search for superclass t = next((t for t in ext_handlers.keys() if isinstance(obj, t)), None) if t: _pack_ext(ext_handlers[t](obj), fp, options) else: raise UnsupportedTypeException( "unsupported type: %s" % str(type(obj))) else: raise UnsupportedTypeException("unsupported type: %s" % str(type(obj))) # Pack for Python 3, with unicode 'str' type, 'bytes' type, and no 'long' type def _pack3(obj, fp, **options): """ Serialize a Python object into MessagePack bytes. Args: obj: a Python object fp: a .write()-supporting file-like object Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping a custom type to a callable that packs an instance of the type into an Ext object force_float_precision (str): "single" to force packing floats as IEEE-754 single-precision floats, "double" to force packing floats as IEEE-754 double-precision floats. Returns: None. Raises: UnsupportedType(PackException): Object type not supported for packing. Example: >>> f = open('test.bin', 'wb') >>> umsgpack.pack({u"compact": True, u"schema": 0}, f) >>> """ global compatibility ext_handlers = options.get("ext_handlers") if obj is None: _pack_nil(obj, fp, options) elif ext_handlers and obj.__class__ in ext_handlers: _pack_ext(ext_handlers[obj.__class__](obj), fp, options) elif isinstance(obj, bool): _pack_boolean(obj, fp, options) elif isinstance(obj, int): _pack_integer(obj, fp, options) elif isinstance(obj, float): _pack_float(obj, fp, options) elif compatibility and isinstance(obj, str): _pack_oldspec_raw(obj.encode('utf-8'), fp, options) elif compatibility and isinstance(obj, bytes): _pack_oldspec_raw(obj, fp, options) elif isinstance(obj, str): _pack_string(obj, fp, options) elif isinstance(obj, bytes): _pack_binary(obj, fp, options) elif isinstance(obj, list) or isinstance(obj, tuple): _pack_array(obj, fp, options) elif isinstance(obj, dict): _pack_map(obj, fp, options) elif isinstance(obj, Ext): _pack_ext(obj, fp, options) elif ext_handlers: # Linear search for superclass t = next((t for t in ext_handlers.keys() if isinstance(obj, t)), None) if t: _pack_ext(ext_handlers[t](obj), fp, options) else: raise UnsupportedTypeException( "unsupported type: %s" % str(type(obj))) else: raise UnsupportedTypeException( "unsupported type: %s" % str(type(obj))) def _packb2(obj, **options): """ Serialize a Python object into MessagePack bytes. Args: obj: a Python object Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping a custom type to a callable that packs an instance of the type into an Ext object force_float_precision (str): "single" to force packing floats as IEEE-754 single-precision floats, "double" to force packing floats as IEEE-754 double-precision floats. Returns: A 'str' containing serialized MessagePack bytes. Raises: UnsupportedType(PackException): Object type not supported for packing. Example: >>> umsgpack.packb({u"compact": True, u"schema": 0}) '\x82\xa7compact\xc3\xa6schema\x00' >>> """ fp = io.BytesIO() _pack2(obj, fp, **options) return fp.getvalue() def _packb3(obj, **options): """ Serialize a Python object into MessagePack bytes. Args: obj: a Python object Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping a custom type to a callable that packs an instance of the type into an Ext object force_float_precision (str): "single" to force packing floats as IEEE-754 single-precision floats, "double" to force packing floats as IEEE-754 double-precision floats. Returns: A 'bytes' containing serialized MessagePack bytes. Raises: UnsupportedType(PackException): Object type not supported for packing. Example: >>> umsgpack.packb({u"compact": True, u"schema": 0}) b'\x82\xa7compact\xc3\xa6schema\x00' >>> """ fp = io.BytesIO() _pack3(obj, fp, **options) return fp.getvalue() ############################################################################# # Unpacking ############################################################################# def _read_except(fp, n): data = fp.read(n) if len(data) < n: raise InsufficientDataException() return data def _unpack_integer(code, fp, options): if (ord(code) & 0xe0) == 0xe0: return struct.unpack("b", code)[0] elif code == b'\xd0': return struct.unpack("b", _read_except(fp, 1))[0] elif code == b'\xd1': return struct.unpack(">h", _read_except(fp, 2))[0] elif code == b'\xd2': return struct.unpack(">i", _read_except(fp, 4))[0] elif code == b'\xd3': return struct.unpack(">q", _read_except(fp, 8))[0] elif (ord(code) & 0x80) == 0x00: return struct.unpack("B", code)[0] elif code == b'\xcc': return struct.unpack("B", _read_except(fp, 1))[0] elif code == b'\xcd': return struct.unpack(">H", _read_except(fp, 2))[0] elif code == b'\xce': return struct.unpack(">I", _read_except(fp, 4))[0] elif code == b'\xcf': return struct.unpack(">Q", _read_except(fp, 8))[0] raise Exception("logic error, not int: 0x%02x" % ord(code)) def _unpack_reserved(code, fp, options): if code == b'\xc1': raise ReservedCodeException( "encountered reserved code: 0x%02x" % ord(code)) raise Exception( "logic error, not reserved code: 0x%02x" % ord(code)) def _unpack_nil(code, fp, options): if code == b'\xc0': return None raise Exception("logic error, not nil: 0x%02x" % ord(code)) def _unpack_boolean(code, fp, options): if code == b'\xc2': return False elif code == b'\xc3': return True raise Exception("logic error, not boolean: 0x%02x" % ord(code)) def _unpack_float(code, fp, options): if code == b'\xca': return struct.unpack(">f", _read_except(fp, 4))[0] elif code == b'\xcb': return struct.unpack(">d", _read_except(fp, 8))[0] raise Exception("logic error, not float: 0x%02x" % ord(code)) def _unpack_string(code, fp, options): if (ord(code) & 0xe0) == 0xa0: length = ord(code) & ~0xe0 elif code == b'\xd9': length = struct.unpack("B", _read_except(fp, 1))[0] elif code == b'\xda': length = struct.unpack(">H", _read_except(fp, 2))[0] elif code == b'\xdb': length = struct.unpack(">I", _read_except(fp, 4))[0] else: raise Exception("logic error, not string: 0x%02x" % ord(code)) # Always return raw bytes in compatibility mode global compatibility if compatibility: return _read_except(fp, length) data = _read_except(fp, length) try: return bytes.decode(data, 'utf-8') except UnicodeDecodeError: if options.get("allow_invalid_utf8"): return InvalidString(data) raise InvalidStringException("unpacked string is invalid utf-8") def _unpack_binary(code, fp, options): if code == b'\xc4': length = struct.unpack("B", _read_except(fp, 1))[0] elif code == b'\xc5': length = struct.unpack(">H", _read_except(fp, 2))[0] elif code == b'\xc6': length = struct.unpack(">I", _read_except(fp, 4))[0] else: raise Exception("logic error, not binary: 0x%02x" % ord(code)) return _read_except(fp, length) def _unpack_ext(code, fp, options): if code == b'\xd4': length = 1 elif code == b'\xd5': length = 2 elif code == b'\xd6': length = 4 elif code == b'\xd7': length = 8 elif code == b'\xd8': length = 16 elif code == b'\xc7': length = struct.unpack("B", _read_except(fp, 1))[0] elif code == b'\xc8': length = struct.unpack(">H", _read_except(fp, 2))[0] elif code == b'\xc9': length = struct.unpack(">I", _read_except(fp, 4))[0] else: raise Exception("logic error, not ext: 0x%02x" % ord(code)) ext = Ext(ord(_read_except(fp, 1)), _read_except(fp, length)) # Unpack with ext handler, if we have one ext_handlers = options.get("ext_handlers") if ext_handlers and ext.type in ext_handlers: ext = ext_handlers[ext.type](ext) return ext def _unpack_array(code, fp, options): if (ord(code) & 0xf0) == 0x90: length = (ord(code) & ~0xf0) elif code == b'\xdc': length = struct.unpack(">H", _read_except(fp, 2))[0] elif code == b'\xdd': length = struct.unpack(">I", _read_except(fp, 4))[0] else: raise Exception("logic error, not array: 0x%02x" % ord(code)) return [_unpack(fp, options) for i in xrange(length)] def _deep_list_to_tuple(obj): if isinstance(obj, list): return tuple([_deep_list_to_tuple(e) for e in obj]) return obj def _unpack_map(code, fp, options): if (ord(code) & 0xf0) == 0x80: length = (ord(code) & ~0xf0) elif code == b'\xde': length = struct.unpack(">H", _read_except(fp, 2))[0] elif code == b'\xdf': length = struct.unpack(">I", _read_except(fp, 4))[0] else: raise Exception("logic error, not map: 0x%02x" % ord(code)) d = {} if not options.get('use_ordered_dict') \ else collections.OrderedDict() for _ in xrange(length): # Unpack key k = _unpack(fp, options) if isinstance(k, list): # Attempt to convert list into a hashable tuple k = _deep_list_to_tuple(k) elif not isinstance(k, collections.Hashable): raise UnhashableKeyException( "encountered unhashable key: %s, %s" % (str(k), str(type(k)))) elif k in d: raise DuplicateKeyException( "encountered duplicate key: %s, %s" % (str(k), str(type(k)))) # Unpack value v = _unpack(fp, options) try: d[k] = v except TypeError: raise UnhashableKeyException( "encountered unhashable key: %s" % str(k)) return d def _unpack(fp, options): code = _read_except(fp, 1) return _unpack_dispatch_table[code](code, fp, options) ######################################## def _unpack2(fp, **options): """ Deserialize MessagePack bytes into a Python object. Args: fp: a .read()-supporting file-like object Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext type to a callable that unpacks an instance of Ext into an object use_ordered_dict (bool): unpack maps into OrderedDict, instead of unordered dict (default False) allow_invalid_utf8 (bool): unpack invalid strings into instances of InvalidString, for access to the bytes (default False) Returns: A Python object. Raises: InsufficientDataException(UnpackException): Insufficient data to unpack the serialized object. InvalidStringException(UnpackException): Invalid UTF-8 string encountered during unpacking. ReservedCodeException(UnpackException): Reserved code encountered during unpacking. UnhashableKeyException(UnpackException): Unhashable key encountered during map unpacking. The serialized map cannot be deserialized into a Python dictionary. DuplicateKeyException(UnpackException): Duplicate key encountered during map unpacking. Example: >>> f = open('test.bin', 'rb') >>> umsgpack.unpackb(f) {u'compact': True, u'schema': 0} >>> """ return _unpack(fp, options) def _unpack3(fp, **options): """ Deserialize MessagePack bytes into a Python object. Args: fp: a .read()-supporting file-like object Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext type to a callable that unpacks an instance of Ext into an object use_ordered_dict (bool): unpack maps into OrderedDict, instead of unordered dict (default False) allow_invalid_utf8 (bool): unpack invalid strings into instances of InvalidString, for access to the bytes (default False) Returns: A Python object. Raises: InsufficientDataException(UnpackException): Insufficient data to unpack the serialized object. InvalidStringException(UnpackException): Invalid UTF-8 string encountered during unpacking. ReservedCodeException(UnpackException): Reserved code encountered during unpacking. UnhashableKeyException(UnpackException): Unhashable key encountered during map unpacking. The serialized map cannot be deserialized into a Python dictionary. DuplicateKeyException(UnpackException): Duplicate key encountered during map unpacking. Example: >>> f = open('test.bin', 'rb') >>> umsgpack.unpackb(f) {'compact': True, 'schema': 0} >>> """ return _unpack(fp, options) # For Python 2, expects a str object def _unpackb2(s, **options): """ Deserialize MessagePack bytes into a Python object. Args: s: a 'str' or 'bytearray' containing serialized MessagePack bytes Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext type to a callable that unpacks an instance of Ext into an object use_ordered_dict (bool): unpack maps into OrderedDict, instead of unordered dict (default False) allow_invalid_utf8 (bool): unpack invalid strings into instances of InvalidString, for access to the bytes (default False) Returns: A Python object. Raises: TypeError: Packed data type is neither 'str' nor 'bytearray'. InsufficientDataException(UnpackException): Insufficient data to unpack the serialized object. InvalidStringException(UnpackException): Invalid UTF-8 string encountered during unpacking. ReservedCodeException(UnpackException): Reserved code encountered during unpacking. UnhashableKeyException(UnpackException): Unhashable key encountered during map unpacking. The serialized map cannot be deserialized into a Python dictionary. DuplicateKeyException(UnpackException): Duplicate key encountered during map unpacking. Example: >>> umsgpack.unpackb(b'\x82\xa7compact\xc3\xa6schema\x00') {u'compact': True, u'schema': 0} >>> """ if not isinstance(s, (str, bytearray)): raise TypeError("packed data must be type 'str' or 'bytearray'") return _unpack(io.BytesIO(s), options) # For Python 3, expects a bytes object def _unpackb3(s, **options): """ Deserialize MessagePack bytes into a Python object. Args: s: a 'bytes' or 'bytearray' containing serialized MessagePack bytes Kwargs: ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext type to a callable that unpacks an instance of Ext into an object use_ordered_dict (bool): unpack maps into OrderedDict, instead of unordered dict (default False) allow_invalid_utf8 (bool): unpack invalid strings into instances of InvalidString, for access to the bytes (default False) Returns: A Python object. Raises: TypeError: Packed data type is neither 'bytes' nor 'bytearray'. InsufficientDataException(UnpackException): Insufficient data to unpack the serialized object. InvalidStringException(UnpackException): Invalid UTF-8 string encountered during unpacking. ReservedCodeException(UnpackException): Reserved code encountered during unpacking. UnhashableKeyException(UnpackException): Unhashable key encountered during map unpacking. The serialized map cannot be deserialized into a Python dictionary. DuplicateKeyException(UnpackException): Duplicate key encountered during map unpacking. Example: >>> umsgpack.unpackb(b'\x82\xa7compact\xc3\xa6schema\x00') {'compact': True, 'schema': 0} >>> """ if not isinstance(s, (bytes, bytearray)): raise TypeError("packed data must be type 'bytes' or 'bytearray'") return _unpack(io.BytesIO(s), options) ############################################################################# # Module Initialization ############################################################################# def __init(): global pack global packb global unpack global unpackb global dump global dumps global load global loads global compatibility global _float_precision global _unpack_dispatch_table global xrange # Compatibility mode for handling strings/bytes with the old specification compatibility = False # Auto-detect system float precision if sys.float_info.mant_dig == 53: _float_precision = "double" else: _float_precision = "single" # Map packb and unpackb to the appropriate version if sys.version_info[0] == 3: pack = _pack3 packb = _packb3 dump = _pack3 dumps = _packb3 unpack = _unpack3 unpackb = _unpackb3 load = _unpack3 loads = _unpackb3 xrange = range else: pack = _pack2 packb = _packb2 dump = _pack2 dumps = _packb2 unpack = _unpack2 unpackb = _unpackb2 load = _unpack2 loads = _unpackb2 # Build a dispatch table for fast lookup of unpacking function _unpack_dispatch_table = {} # Fix uint for code in range(0, 0x7f + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer # Fix map for code in range(0x80, 0x8f + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_map # Fix array for code in range(0x90, 0x9f + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_array # Fix str for code in range(0xa0, 0xbf + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_string # Nil _unpack_dispatch_table[b'\xc0'] = _unpack_nil # Reserved _unpack_dispatch_table[b'\xc1'] = _unpack_reserved # Boolean _unpack_dispatch_table[b'\xc2'] = _unpack_boolean _unpack_dispatch_table[b'\xc3'] = _unpack_boolean # Bin for code in range(0xc4, 0xc6 + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_binary # Ext for code in range(0xc7, 0xc9 + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_ext # Float _unpack_dispatch_table[b'\xca'] = _unpack_float _unpack_dispatch_table[b'\xcb'] = _unpack_float # Uint for code in range(0xcc, 0xcf + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer # Int for code in range(0xd0, 0xd3 + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer # Fixext for code in range(0xd4, 0xd8 + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_ext # String for code in range(0xd9, 0xdb + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_string # Array _unpack_dispatch_table[b'\xdc'] = _unpack_array _unpack_dispatch_table[b'\xdd'] = _unpack_array # Map _unpack_dispatch_table[b'\xde'] = _unpack_map _unpack_dispatch_table[b'\xdf'] = _unpack_map # Negative fixint for code in range(0xe0, 0xff + 1): _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer __init() graphite-web-1.1.4/webapp/graphite/metrics/0000755000000000000000000000000013343335472020567 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/metrics/__init__.py0000644000000000000000000000000013343334667022673 0ustar rootroot00000000000000graphite-web-1.1.4/webapp/graphite/metrics/urls.py0000644000000000000000000000206613343334667022137 0ustar rootroot00000000000000"""Copyright 2009 Chris Davis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from django.conf.urls import url from . import views urlpatterns = [ url(r'^/index\.json$', views.index_json, name='metrics_index'), url(r'^/find/?$', views.find_view, name='metrics_find'), url(r'^/expand/?$', views.expand_view, name='metrics_expand'), url(r'^/get-metadata/?$', views.get_metadata_view, name='metrics_get_metadata'), url(r'^/set-metadata/?$', views.set_metadata_view, name='metrics_set_metadata'), url(r'^/?$', views.find_view, name='metrics'), ] graphite-web-1.1.4/webapp/graphite/metrics/views.py0000644000000000000000000002603213343334667022306 0ustar rootroot00000000000000"""Copyright 2009 Chris Davis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" from functools import reduce import pytz from six import text_type from six.moves.urllib.parse import unquote_plus from datetime import datetime from django.conf import settings from graphite.carbonlink import CarbonLink from graphite.compat import HttpResponse, HttpResponseBadRequest from graphite.logger import log from graphite.render.attime import parseATTime from graphite.storage import STORE, extractForwardHeaders from graphite.user_util import getProfile from graphite.util import epoch, json, pickle, msgpack def index_json(request): queryParams = request.GET.copy() queryParams.update(request.POST) try: jsonp = queryParams.get('jsonp', False) requestContext = { 'localOnly': int( queryParams.get('local', 0) ), 'forwardHeaders': extractForwardHeaders(request), } matches = STORE.get_index(requestContext) except Exception: log.exception() return json_response_for(request, [], jsonp=jsonp, status=500) return json_response_for(request, matches, jsonp=jsonp) def find_view(request): "View for finding metrics matching a given pattern" queryParams = request.GET.copy() queryParams.update(request.POST) format = queryParams.get('format', 'treejson') leaves_only = int( queryParams.get('leavesOnly', 0) ) local_only = int( queryParams.get('local', 0) ) wildcards = int( queryParams.get('wildcards', 0) ) tzinfo = pytz.timezone(settings.TIME_ZONE) if 'tz' in queryParams: try: tzinfo = pytz.timezone(queryParams['tz']) except pytz.UnknownTimeZoneError: pass if 'now' in queryParams: now = parseATTime(queryParams['now'], tzinfo) else: now = datetime.now(tzinfo) if 'from' in queryParams and str(queryParams['from']) != '-1': fromTime = int(epoch(parseATTime(queryParams['from'], tzinfo, now))) else: fromTime = -1 if 'until' in queryParams and str(queryParams['from']) != '-1': untilTime = int(epoch(parseATTime(queryParams['until'], tzinfo, now))) else: untilTime = -1 nodePosition = int( queryParams.get('position', -1) ) jsonp = queryParams.get('jsonp', False) forward_headers = extractForwardHeaders(request) if fromTime == -1: fromTime = None if untilTime == -1: untilTime = None automatic_variants = int( queryParams.get('automatic_variants', 0) ) try: query = str(queryParams['query']) except KeyError: return HttpResponseBadRequest(content="Missing required parameter 'query'", content_type='text/plain') if query == '': return HttpResponseBadRequest(content="Required parameter 'query' is empty", content_type='text/plain') if '.' in query: base_path = query.rsplit('.', 1)[0] + '.' else: base_path = '' if format == 'completer': query = query.replace('..', '*.') if not query.endswith('*'): query += '*' if automatic_variants: query_parts = query.split('.') for i,part in enumerate(query_parts): if ',' in part and '{' not in part: query_parts[i] = '{%s}' % part query = '.'.join(query_parts) try: matches = list(STORE.find( query, fromTime, untilTime, local=local_only, headers=forward_headers, leaves_only=leaves_only, )) except Exception: log.exception() raise log.info('find_view query=%s local_only=%s matches=%d' % (query, local_only, len(matches))) matches.sort(key=lambda node: node.name) log.info("received remote find request: pattern=%s from=%s until=%s local_only=%s format=%s matches=%d" % (query, fromTime, untilTime, local_only, format, len(matches))) if format == 'treejson': profile = getProfile(request) content = tree_json(matches, base_path, wildcards=profile.advancedUI or wildcards) response = json_response_for(request, content, jsonp=jsonp) elif format == 'nodelist': content = nodes_by_position(matches, nodePosition) response = json_response_for(request, content, jsonp=jsonp) elif format == 'pickle': content = pickle_nodes(matches) response = HttpResponse(content, content_type='application/pickle') elif format == 'msgpack': content = msgpack_nodes(matches) response = HttpResponse(content, content_type='application/x-msgpack') elif format == 'json': content = json_nodes(matches) response = json_response_for(request, content, jsonp=jsonp) elif format == 'completer': results = [] for node in matches: node_info = dict(path=node.path, name=node.name, is_leaf=str(int(node.is_leaf))) if not node.is_leaf: node_info['path'] += '.' results.append(node_info) if len(results) > 1 and wildcards: wildcardNode = {'name' : '*'} results.append(wildcardNode) response = json_response_for(request, { 'metrics' : results }, jsonp=jsonp) else: return HttpResponseBadRequest( content="Invalid value for 'format' parameter", content_type='text/plain') response['Pragma'] = 'no-cache' response['Cache-Control'] = 'no-cache' return response def expand_view(request): "View for expanding a pattern into matching metric paths" queryParams = request.GET.copy() queryParams.update(request.POST) local_only = int( queryParams.get('local', 0) ) group_by_expr = int( queryParams.get('groupByExpr', 0) ) leaves_only = int( queryParams.get('leavesOnly', 0) ) jsonp = queryParams.get('jsonp', False) forward_headers = extractForwardHeaders(request) results = {} for query in queryParams.getlist('query'): results[query] = set() for node in STORE.find(query, local=local_only, headers=forward_headers): if node.is_leaf or not leaves_only: results[query].add( node.path ) # Convert our results to sorted lists because sets aren't json-friendly if group_by_expr: for query, matches in results.items(): results[query] = sorted(matches) else: results = sorted( reduce(set.union, results.values(), set()) ) result = { 'results' : results } response = json_response_for(request, result, jsonp=jsonp) response['Pragma'] = 'no-cache' response['Cache-Control'] = 'no-cache' return response def get_metadata_view(request): queryParams = request.GET.copy() queryParams.update(request.POST) key = queryParams.get('key') metrics = queryParams.getlist('metric') jsonp = queryParams.get('jsonp', False) results = {} for metric in metrics: try: results[metric] = CarbonLink.get_metadata(metric, key) except Exception: log.exception() results[metric] = dict(error="Unexpected error occurred in CarbonLink.get_metadata(%s, %s)" % (metric, key)) return json_response_for(request, results, jsonp=jsonp) def set_metadata_view(request): results = {} if request.method == 'GET': metric = request.GET['metric'] key = request.GET['key'] value = request.GET['value'] try: results[metric] = CarbonLink.set_metadata(metric, key, value) except Exception: log.exception() results[metric] = dict(error="Unexpected error occurred in CarbonLink.set_metadata(%s, %s)" % (metric, key)) elif request.method == 'POST': if request.META.get('CONTENT_TYPE') == 'application/json': operations = json.loads( request.body ) else: operations = json.loads( request.POST['operations'] ) for op in operations: metric = None try: metric, key, value = op['metric'], op['key'], op['value'] results[metric] = CarbonLink.set_metadata(metric, key, value) except Exception: log.exception() if metric: results[metric] = dict(error="Unexpected error occurred in bulk CarbonLink.set_metadata(%s)" % metric) else: results = dict(error='Invalid request method') return json_response_for(request, results) def tree_json(nodes, base_path, wildcards=False): results = [] branchNode = { 'allowChildren': 1, 'expandable': 1, 'leaf': 0, } leafNode = { 'allowChildren': 0, 'expandable': 0, 'leaf': 1, } #Add a wildcard node if appropriate if len(nodes) > 1 and wildcards: wildcardNode = {'text' : '*', 'id' : base_path + '*'} if any(not n.is_leaf for n in nodes): wildcardNode.update(branchNode) else: wildcardNode.update(leafNode) results.append(wildcardNode) found = set() results_leaf = [] results_branch = [] for node in nodes: #Now let's add the matching children if node.name in found: continue found.add(node.name) resultNode = { 'text' : unquote_plus(str(node.name)), 'id' : base_path + str(node.name), } if node.is_leaf: resultNode.update(leafNode) results_leaf.append(resultNode) else: resultNode.update(branchNode) results_branch.append(resultNode) results.extend(results_branch) results.extend(results_leaf) return results def nodes_by_position(matches, position): found = set() for metric in matches: nodes = metric.path.split('.') found.add(nodes[position]) results = { 'nodes' : sorted(found) } return results def pickle_nodes(nodes): nodes_info = [] for node in nodes: info = dict(path=node.path, is_leaf=node.is_leaf) if node.is_leaf: info['intervals'] = node.intervals nodes_info.append(info) return pickle.dumps(nodes_info, protocol=-1) def msgpack_nodes(nodes): nodes_info = [] # make sure everything is unicode in python 2.x and 3.x for node in nodes: info = { text_type('path'): text_type(node.path), text_type('is_leaf'): node.is_leaf, } if node.is_leaf: info[text_type('intervals')] = [interval.tuple for interval in node.intervals] nodes_info.append(info) return msgpack.dumps(nodes_info, use_bin_type=True) def json_nodes(nodes): nodes_info = [] for node in nodes: info = dict(path=node.path, is_leaf=node.is_leaf) if node.is_leaf: info['intervals'] = [{'start': i.start, 'end': i.end} for i in node.intervals] nodes_info.append(info) return sorted(nodes_info, key=lambda item: item['path']) def json_response_for(request, data, content_type='application/json', jsonp=False, **kwargs): accept = request.META.get('HTTP_ACCEPT', 'application/json') ensure_ascii = accept == 'application/json' pretty = bool(request.POST.get('pretty', request.GET.get('pretty'))) content = json.dumps(data, ensure_ascii=ensure_ascii, indent=(2 if pretty else None)) if jsonp: content = "%s(%s)" % (jsonp, content) content_type = 'text/javascript' if not ensure_ascii: content_type += ';charset=utf-8' return HttpResponse(content, content_type=content_type, **kwargs) graphite-web-1.1.4/webapp/graphite/local_settings.py.example0000644000000000000000000003641713343334667024157 0ustar rootroot00000000000000## Graphite local_settings.py # Edit this file to customize the default Graphite webapp settings # # Additional customizations to Django settings can be added to this file as well ##################################### # General Configuration # ##################################### # # Set this to a long, random unique string to use as a secret key for this # install. This key is used for salting of hashes used in auth tokens, # CRSF middleware, cookie storage, etc. This should be set identically among # instances if used behind a load balancer. #SECRET_KEY = 'UNSAFE_DEFAULT' # In Django 1.5+ set this to the list of hosts your graphite instances is # accessible as. See: # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-ALLOWED_HOSTS #ALLOWED_HOSTS = [ '*' ] # Set your local timezone (Django's default is America/Chicago) # If your graphs appear to be offset by a couple hours then this probably # needs to be explicitly set to your local timezone. #TIME_ZONE = 'America/Los_Angeles' # Set the default short date format. See strftime(3) for supported sequences. #DATE_FORMAT = '%m/%d' # Override this to provide documentation specific to your Graphite deployment #DOCUMENTATION_URL = "http://graphite.readthedocs.io/" # Logging # These can also be configured using Django's LOGGING: # https://docs.djangoproject.com/en/1.11/topics/logging/ #LOG_ROTATION = True #LOG_ROTATION_COUNT = 1 #LOG_RENDERING_PERFORMANCE = True #LOG_CACHE_PERFORMANCE = True # Filenames for log output, set to '-' to log to stderr #LOG_FILE_INFO = 'info.log' #LOG_FILE_EXCEPTION = 'exception.log' #LOG_FILE_CACHE = 'cache.log' #LOG_FILE_RENDERING = 'rendering.log' # Enable full debug page display on exceptions (Internal Server Error pages) #DEBUG = True # If using RRD files and rrdcached, set to the address or socket of the daemon #FLUSHRRDCACHED = 'unix:/var/run/rrdcached.sock' # This lists the memcached servers that will be used by this webapp. # If you have a cluster of webapps you should ensure all of them # have the *exact* same value for this setting. That will maximize cache # efficiency. Setting MEMCACHE_HOSTS to be empty will turn off use of # memcached entirely. # # You should not use the loopback address (127.0.0.1) here if using clustering # as every webapp in the cluster should use the exact same values to prevent # unneeded cache misses. Set to [] to disable caching of images and fetched data #MEMCACHE_HOSTS = ['10.10.10.10:11211', '10.10.10.11:11211', '10.10.10.12:11211'] # Metric data and graphs are cached for one minute by default. If defined, # DEFAULT_CACHE_POLICY is a list of tuples of minimum query time ranges mapped # to the cache duration for the results. This allows for larger queries to be # cached for longer periods of times. All times are in seconds. If the policy is # empty or undefined, all results will be cached for DEFAULT_CACHE_DURATION. #DEFAULT_CACHE_DURATION = 60 # Cache images and data for 1 minute #DEFAULT_CACHE_POLICY = [(0, 60), # default is 60 seconds # (7200, 120), # >= 2 hour queries are cached 2 minutes # (21600, 180)] # >= 6 hour queries are cached 3 minutes #MEMCACHE_KEY_PREFIX = 'graphite' # This lists the memcached options. Default is an empty dict. # Accepted options depend on the Memcached implementation and the Django version. # Until Django 1.10, options are used only for pylibmc. # Starting from 1.11, options are used for both python-memcached and pylibmc. #MEMCACHE_OPTIONS = { 'socket_timeout': 0.5 } # this setting controls the default xFilesFactor used for query-time aggregration DEFAULT_XFILES_FACTOR = 0 # Set URL_PREFIX when deploying graphite-web to a non-root location #URL_PREFIX = '/graphite' # Graphite uses Django Tagging to support tags in Events. By default each # tag is limited to 50 characters in length. #MAX_TAG_LENGTH = 50 # Interval for the Auto-Refresh feature in the Composer, measured in seconds. #AUTO_REFRESH_INTERVAL = 60 # Timeouts for find and render requests #FIND_TIMEOUT = 3.0 # Timeout for metric find requests #FETCH_TIMEOUT = 3.0 # Timeout to fetch series data ##################################### # Filesystem Paths # ##################################### # # Change only GRAPHITE_ROOT if your install is merely shifted from /opt/graphite # to somewhere else #GRAPHITE_ROOT = '/opt/graphite' # Most installs done outside of a separate tree such as /opt/graphite will # need to change these settings. Note that the default settings for each # of these is relative to GRAPHITE_ROOT. #CONF_DIR = '/opt/graphite/conf' #STORAGE_DIR = '/opt/graphite/storage' #STATIC_ROOT = '/opt/graphite/static' #LOG_DIR = '/opt/graphite/storage/log/webapp' #INDEX_FILE = '/opt/graphite/storage/index' # Search index file # To further or fully customize the paths, modify the following. Note that the # default settings for each of these are relative to CONF_DIR and STORAGE_DIR # ## Webapp config files #DASHBOARD_CONF = '/opt/graphite/conf/dashboard.conf' #GRAPHTEMPLATES_CONF = '/opt/graphite/conf/graphTemplates.conf' ## Data directories # # NOTE: If any directory is unreadable in STANDARD_DIRS it will break metric browsing # #CERES_DIR = '/opt/graphite/storage/ceres' #WHISPER_DIR = '/opt/graphite/storage/whisper' #RRD_DIR = '/opt/graphite/storage/rrd' # # Data directories using the "Standard" metrics finder (i.e. not Ceres) #STANDARD_DIRS = [WHISPER_DIR, RRD_DIR] # Default: set from the above variables ## Data finders # It is possible to use an alternate storage layer than the default, Whisper, # in order to accommodate specific needs. # See: http://graphite.readthedocs.io/en/latest/storage-backends.html # # STORAGE_FINDERS = ( # 'graphite.finders.remote.RemoteFinder', # 'graphite.finders.standard.StandardFinder', # 'graphite.finders.ceres.CeresFinder', # ) ##################################### # Email Configuration # ##################################### # # This is used for emailing rendered graphs. The default backend is SMTP. #EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # # To drop emails on the floor, enable the Dummy backend instead. #EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' #EMAIL_HOST = 'localhost' #EMAIL_PORT = 25 #EMAIL_HOST_USER = '' #EMAIL_HOST_PASSWORD = '' #EMAIL_USE_TLS = False ##################################### # Authentication Configuration # ##################################### # ## LDAP / ActiveDirectory authentication setup #USE_LDAP_AUTH = True #LDAP_SERVER = "ldap.mycompany.com" #LDAP_PORT = 389 #LDAP_USE_TLS = False ## Manual URI / query setup #LDAP_URI = "ldaps://ldap.mycompany.com:636" #LDAP_SEARCH_BASE = "OU=users,DC=mycompany,DC=com" #LDAP_BASE_USER = "CN=some_readonly_account,DC=mycompany,DC=com" #LDAP_BASE_PASS = "readonly_account_password" #LDAP_USER_QUERY = "(username=%s)" #For Active Directory use "(sAMAccountName=%s)" # User DN template to use for binding (and authentication) against the # LDAP server. %(username) is replaced with the username supplied at # graphite login. #LDAP_USER_DN_TEMPLATE = "CN=%(username)s,OU=users,DC=mycompany,DC=com" # If you want to further customize the ldap connection options you should # directly use ldap.set_option to set the ldap module's global options. # For example: # #import ldap #ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW) # Use ldap.OPT_X_TLS_DEMAND to force TLS #ldap.set_option(ldap.OPT_REFERRALS, 0) # Enable for Active Directory #ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, "/etc/ssl/ca") #ldap.set_option(ldap.OPT_X_TLS_CERTFILE, "/etc/ssl/mycert.pem") #ldap.set_option(ldap.OPT_X_TLS_KEYFILE, "/etc/ssl/mykey.pem") #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 65535) # To enable verbose debugging # See http://www.python-ldap.org/ for further details on these options. ## REMOTE_USER authentication. See: https://docs.djangoproject.com/en/dev/howto/auth-remote-user/ #USE_REMOTE_USER_AUTHENTICATION = True # Override the URL for the login link (e.g. for django_openid_auth) #LOGIN_URL = '/account/login' ############################### # Authorization for Dashboard # ############################### # By default, there is no security on dashboards - any user can add, change or delete them. # This section provides 3 different authorization models, of varying strictness. # If set to True, users must be logged in to save or delete dashboards. Defaults to False #DASHBOARD_REQUIRE_AUTHENTICATION = True # If set to the name of a user group, dashboards can be saved and deleted by any user in this # group. Groups can be set in the Django Admin app, or in LDAP. Defaults to None. # NOTE: Ignored if DASHBOARD_REQUIRE_AUTHENTICATION is not set #DASHBOARD_REQUIRE_EDIT_GROUP = 'dashboard-editors-group' # If set to True, dashboards can be saved or deleted by any user having the appropriate # (change or delete) permission (as set in the Django Admin app). Defaults to False # NOTE: Ignored if DASHBOARD_REQUIRE_AUTHENTICATION is not set #DASHBOARD_REQUIRE_PERMISSIONS = True ########################## # Database Configuration # ########################## # # By default sqlite is used. If you cluster multiple webapps you will need # to setup an external database (such as MySQL) and configure all of the webapp # instances to use the same database. Note that this database is only used to store # Django models such as saved graphs, dashboards, user preferences, etc. # Metric data is not stored here. # # DO NOT FORGET TO RUN MIGRATIONS AFTER SETTING UP A NEW DATABASE # http://graphite.readthedocs.io/en/latest/config-database-setup.html # # # The following built-in database engines are available: # django.db.backends.postgresql_psycopg2 # django.db.backends.mysql # django.db.backends.sqlite3 # django.db.backends.oracle # # The default is 'django.db.backends.sqlite3' with file 'graphite.db' # located in STORAGE_DIR # #DATABASES = { # 'default': { # 'NAME': '/opt/graphite/storage/graphite.db', # 'ENGINE': 'django.db.backends.sqlite3', # 'USER': '', # 'PASSWORD': '', # 'HOST': '', # 'PORT': '' # } #} # ######################### # Cluster Configuration # ######################### # # To avoid excessive DNS lookups you want to stick to using IP addresses only # in this entire section. # # This should list the IP address (and optionally port) of the webapp on each # remote server in the cluster. These servers must each have local access to # metric data. Note that the first server to return a match for a query will be # used. #CLUSTER_SERVERS = ["10.0.2.2:80", "10.0.2.3:80"] # Use a pool of worker threads to dispatch finder requests in parallel #USE_WORKER_POOL = True # Maximum number of worker threads for concurrent storage operations #POOL_MAX_WORKERS = 10 # This setting controls whether https is used to communicate between cluster members #INTRACLUSTER_HTTPS = False # Time before retrying a failed remote webapp #REMOTE_RETRY_DELAY = 60.0 # Fail all requests if any remote webapp call fails #STORE_FAIL_ON_ERROR = False # Try to detect when a cluster server is localhost and don't forward queries #REMOTE_EXCLUDE_LOCAL = False # Number of retries for a specific remote data fetch. #MAX_FETCH_RETRIES = 2 #FIND_CACHE_DURATION = 300 # Time to cache remote metric find results # If the query doesn't fall entirely within the FIND_TOLERANCE window # we disregard the window. This prevents unnecessary remote fetches # caused when carbon's cache skews node.intervals, giving the appearance # remote systems have data we don't have locally, which we probably do. #FIND_TOLERANCE = 2 * FIND_CACHE_DURATION #REMOTE_STORE_USE_POST = False # Use POST instead of GET for remote requests # Size of the buffer used for streaming remote cluster responses. Set to 0 to avoid streaming deserialization. #REMOTE_BUFFER_SIZE = 1024 * 1024 # During a rebalance of a consistent hash cluster, after a partition event on a replication > 1 cluster, # or in other cases we might receive multiple TimeSeries data for a metric key. Merge them together rather # that choosing the "most complete" one (pre-0.9.14 behaviour). #REMOTE_STORE_MERGE_RESULTS = True # Provide a list of HTTP headers that you want forwarded on from this host # when making a request to a remote webapp server in CLUSTER_SERVERS #REMOTE_STORE_FORWARD_HEADERS = [] # An iterable of HTTP header names ## Remote rendering settings # Set to True to enable rendering of Graphs on a remote webapp #REMOTE_RENDERING = True # List of IP (and optionally port) of the webapp on each remote server that # will be used for rendering. Note that each rendering host should have local # access to metric data or should have CLUSTER_SERVERS configured #RENDERING_HOSTS = [] #REMOTE_RENDER_CONNECT_TIMEOUT = 1.0 # If you are running multiple carbon-caches on this machine (typically behind # a relay using consistent hashing), you'll need to list the ip address, cache # query port, and instance name of each carbon-cache instance on the local # machine (NOT every carbon-cache in the entire cluster). The default cache # query port is 7002 and a common scheme is to use 7102 for instance b, 7202 # for instance c, etc. # If you're using consistent hashing, please keep an order of hosts the same as # order of DESTINATIONS in your relay - otherways you'll get cache misses. # # You *should* use 127.0.0.1 here in most cases. # #CARBONLINK_HOSTS = ["127.0.0.1:7002:a", "127.0.0.1:7102:b", "127.0.0.1:7202:c"] #CARBONLINK_TIMEOUT = 1.0 #CARBONLINK_RETRY_DELAY = 15 # Seconds to blacklist a failed remote server # # Type of metric hashing function. # The default `carbon_ch` is Graphite's traditional consistent-hashing implementation. # Alternatively, you can use `fnv1a_ch`, which supports the Fowler-Noll-Vo hash # function (FNV-1a) hash implementation offered by the carbon-c-relay project # https://github.com/grobian/carbon-c-relay # # Supported values: carbon_ch, fnv1a_ch # #CARBONLINK_HASHING_TYPE = 'carbon_ch' # A "keyfunc" is a user-defined python function that is given a metric name # and returns a string that should be used when hashing the metric name. # This is important when your hashing has to respect certain metric groupings. #CARBONLINK_HASHING_KEYFUNC = "/opt/graphite/bin/keyfuncs.py:my_keyfunc" # Prefix for internal carbon statistics. #CARBON_METRIC_PREFIX='carbon' # The replication factor to use with consistent hashing. # This should usually match the value configured in Carbon. #REPLICATION_FACTOR = 1 ##################################### # TagDB Settings # ##################################### # Tag Database #TAGDB = 'graphite.tags.localdatabase.LocalDatabaseTagDB' # Time to cache seriesByTag results #TAGDB_CACHE_DURATION = 60 # Autocomplete default result limit #TAGDB_AUTOCOMPLETE_LIMIT = 100 # Settings for Redis TagDB #TAGDB_REDIS_HOST = 'localhost' #TAGDB_REDIS_PORT = 6379 #TAGDB_REDIS_DB = 0 # Settings for HTTP TagDB #TAGDB_HTTP_URL = '' #TAGDB_HTTP_USER = '' #TAGDB_HTTP_PASSWORD = '' # Does the remote TagDB support autocomplete? #TAGDB_HTTP_AUTOCOMPLETE = False ##################################### # Function plugins # ##################################### # List of custom function plugin modules # See: http://graphite.readthedocs.io/en/latest/functions.html#function-plugins FUNCTION_PLUGINS = [] ##################################### # Additional Django Settings # ##################################### # Uncomment the following line for direct access to Django settings such as # MIDDLEWARE or APPS #from graphite.app_settings import * graphite-web-1.1.4/webapp/graphite/util.py0000644000000000000000000002357113343334667020465 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import imp import io import json as _json import socket import time import sys import calendar import pytz import six import traceback from datetime import datetime from functools import wraps from os.path import splitext, basename from django.conf import settings from django.utils.timezone import make_aware from graphite.compat import HttpResponse from graphite.logger import log # BytesIO is needed on py3 as StringIO does not operate on byte input anymore # We could use BytesIO on py2 as well but it is slower than StringIO if sys.version_info >= (3, 0): PY3 = True import pickle from io import BytesIO else: PY3 = False import cPickle as pickle from cStringIO import StringIO as BytesIO # use https://github.com/msgpack/msgpack-python if available try: import msgpack # NOQA # otherwise fall back to bundled https://github.com/vsergeev/u-msgpack-python except ImportError: import graphite.umsgpack as msgpack # NOQA def epoch(dt): """ Returns the epoch timestamp of a timezone-aware datetime object. """ if not dt.tzinfo: tb = traceback.extract_stack(None, 2) log.warning('epoch() called with non-timezone-aware datetime in %s at %s:%d' % (tb[0][2], tb[0][0], tb[0][1])) return calendar.timegm(make_aware(dt, pytz.timezone(settings.TIME_ZONE)).astimezone(pytz.utc).timetuple()) return calendar.timegm(dt.astimezone(pytz.utc).timetuple()) def epoch_to_dt(timestamp): """ Returns the timezone-aware datetime of an epoch timestamp. """ return make_aware(datetime.utcfromtimestamp(timestamp), pytz.utc) def timebounds(requestContext): startTime = int(epoch(requestContext['startTime'])) endTime = int(epoch(requestContext['endTime'])) now = int(epoch(requestContext['now'])) return (startTime, endTime, now) def is_local_interface(host): is_ipv6 = False if ':' not in host: pass elif host.count(':') == 1: host = host.split(':', 1)[0] else: is_ipv6 = True if host.find('[', 0, 2) != -1: last_bracket_position = host.rfind(']') last_colon_position = host.rfind(':') if last_colon_position > last_bracket_position: host = host.rsplit(':', 1)[0] host = host.strip('[]') try: if is_ipv6: sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) else: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind( (host, 0) ) except socket.error: return False finally: sock.close() return True def is_pattern(s): return '*' in s or '?' in s or '[' in s or '{' in s def is_escaped_pattern(s): for symbol in '*?[{': i = s.find(symbol) if i > 0: if s[i-1] == '\\': return True return False def find_escaped_pattern_fields(pattern_string): pattern_parts = pattern_string.split('.') for index,part in enumerate(pattern_parts): if is_escaped_pattern(part): yield index def load_module(module_path, member=None): module_name = splitext(basename(module_path))[0] module_file = open(module_path, 'U') description = ('.py', 'U', imp.PY_SOURCE) module = imp.load_module(module_name, module_file, module_path, description) if member: return getattr(module, member) else: return module def timestamp(dt): "Convert a datetime object into epoch time" return time.mktime(dt.timetuple()) def deltaseconds(timedelta): "Convert a timedelta object into seconds (same as timedelta.total_seconds() in Python 2.7+)" return (timedelta.microseconds + (timedelta.seconds + timedelta.days * 24 * 3600) * 10**6) / 10**6 # This whole song & dance is due to pickle being insecure # The SafeUnpickler classes were largely derived from # http://nadiana.com/python-pickle-insecure # This code also lives in carbon.util if not PY3: class SafeUnpickler(object): PICKLE_SAFE = { 'copy_reg': set(['_reconstructor']), '__builtin__': set(['object', 'list', 'set']), 'collections': set(['deque']), 'graphite.render.datalib': set(['TimeSeries']), 'graphite.intervals': set(['Interval', 'IntervalSet']), } @classmethod def find_class(cls, module, name): if not module in cls.PICKLE_SAFE: raise pickle.UnpicklingError('Attempting to unpickle unsafe module %s' % module) __import__(module) mod = sys.modules[module] if not name in cls.PICKLE_SAFE[module]: raise pickle.UnpicklingError('Attempting to unpickle unsafe class %s' % name) return getattr(mod, name) @classmethod def loads(cls, pickle_string): pickle_obj = pickle.Unpickler(BytesIO(pickle_string)) pickle_obj.find_global = cls.find_class return pickle_obj.load() @classmethod def load(cls, file): pickle_obj = pickle.Unpickler(file) pickle_obj.find_global = cls.find_class return pickle_obj.load() unpickle = SafeUnpickler else: class SafeUnpickler(pickle.Unpickler): PICKLE_SAFE = { 'copy_reg': set(['_reconstructor']), 'builtins': set(['object', 'list', 'set']), 'collections': set(['deque']), 'graphite.render.datalib': set(['TimeSeries']), 'graphite.intervals': set(['Interval', 'IntervalSet']), } def find_class(self, module, name): if not module in self.PICKLE_SAFE: raise pickle.UnpicklingError('Attempting to unpickle unsafe module %s' % module) __import__(module) mod = sys.modules[module] if not name in self.PICKLE_SAFE[module]: raise pickle.UnpicklingError('Attempting to unpickle unsafe class %s' % name) return getattr(mod, name) class unpickle(object): @staticmethod def loads(pickle_string): return SafeUnpickler(BytesIO(pickle_string)).load() @staticmethod def load(file): return SafeUnpickler(file).load() class json(object): JSONEncoder = _json.JSONEncoder JSONDecoder = _json.JSONDecoder @staticmethod def dump(*args, **kwargs): return _json.dump(*args, **kwargs) @staticmethod def dumps(*args, **kwargs): return _json.dumps(*args, **kwargs) @staticmethod def load(fp, *args, **kwargs): return _json.load(fp, *args, **kwargs) @staticmethod def loads(s, *args, **kwargs): if isinstance(s, six.binary_type): return _json.loads(s.decode('utf-8'), *args, **kwargs) return _json.loads(s, *args, **kwargs) class Timer(object): __slots__ = ('msg', 'name', 'start_time') def __init__(self, name): self.name = name self.msg = 'completed in' self.start_time = time.time() def set_msg(self, msg): self.msg = msg def set_name(self, name): self.name = name def stop(self): log.info( '{name} :: {msg} {sec:.6}s'.format( name=self.name, msg=self.msg, sec=time.time() - self.start_time, ) ) def logtime(f): @wraps(f) def wrapped_f(*args, **kwargs): timer = Timer(f.__module__ + '.' + f.__name__) kwargs['timer'] = timer try: return f(*args, **kwargs) except Exception: timer.msg = 'failed in' raise finally: timer.stop() return wrapped_f class BufferedHTTPReader(io.IOBase): def __init__(self, response, buffer_size=1048576): self.response = response self.buffer_size = buffer_size self.buffer = b'' self.pos = 0 def read(self, amt=None): if amt is None: return self.response.read() if len(self.buffer) - self.pos < amt: self.buffer = self.buffer[self.pos:] self.pos = 0 self.buffer += self.response.read(self.buffer_size) data = self.buffer[self.pos:self.pos + amt] self.pos += amt if self.pos >= len(self.buffer): self.pos = 0 self.buffer = b'' return data def jsonResponse(*args, **kwargs): encoder = kwargs.get('encoder') default = kwargs.get('default') def decorator(f): @wraps(f) def wrapped_f(request, *args, **kwargs): if request.method == 'GET': queryParams = request.GET.copy() elif request.method == 'POST': queryParams = request.GET.copy() queryParams.update(request.POST) else: queryParams = {} try: return _jsonResponse( f(request, queryParams, *args, **kwargs), queryParams, encoder=encoder, default=default) except ValueError as err: return _jsonError( str(err), queryParams, status=getattr(err, 'status', 400), encoder=encoder, default=default) except Exception as err: return _jsonError( str(err), queryParams, status=getattr(err, 'status', 500), encoder=encoder, default=default) return wrapped_f # used like @jsonResponse if args: return decorator(args[0]) # used like @jsonResponse(encoder=DjangoJSONEncoder) return decorator class HttpError(Exception): def __init__(self, message, status=500): super(HttpError, self).__init__(message) self.status=status def _jsonResponse(data, queryParams, status=200, encoder=None, default=None): if isinstance(data, HttpResponse): return data if not queryParams: queryParams = {} return HttpResponse( json.dumps( data, indent=(2 if queryParams.get('pretty') else None), sort_keys=bool(queryParams.get('pretty')), cls=encoder, default=default ) if data is not None else 'null', content_type='application/json', status=status ) def _jsonError(message, queryParams, status=500, encoder=None, default=None): return _jsonResponse( {'error': message}, queryParams, status=status, encoder=encoder, default=default) graphite-web-1.1.4/webapp/graphite/urls.py0000644000000000000000000000344213343334667020470 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License""" from django.conf import settings from django.conf.urls import include, url from django.contrib import admin from graphite.url_shortener.views import shorten, follow from graphite.browser.views import browser graphite_urls = [ url('^admin/', admin.site.urls), url('^render', include('graphite.render.urls')), url('^composer', include('graphite.composer.urls')), url('^metrics', include('graphite.metrics.urls')), url('^browser', include('graphite.browser.urls')), url('^account', include('graphite.account.urls')), url('^dashboard', include('graphite.dashboard.urls')), url('^whitelist', include('graphite.whitelist.urls')), url('^version', include('graphite.version.urls')), url('^events', include('graphite.events.urls')), url('^tags', include('graphite.tags.urls')), url('^functions', include('graphite.functions.urls')), url('^s/(?P.*)', shorten, name='shorten'), url('^S/(?P[a-zA-Z0-9]+)/?$', follow, name='follow'), url('^$', browser, name='browser'), ] if settings.URL_PREFIX.strip('/'): urlpatterns = [ url(r'^{0}/'.format(settings.URL_PREFIX.strip('/')), include(graphite_urls)), ] else: urlpatterns = graphite_urls handler500 = 'graphite.views.server_error' graphite-web-1.1.4/webapp/graphite/intervals.py0000644000000000000000000001023113343334667021504 0ustar rootroot00000000000000INFINITY = float('inf') NEGATIVE_INFINITY = -INFINITY class IntervalSet: __slots__ = ('intervals', 'size') def __init__(self, intervals, disjoint=False): self.intervals = intervals if not disjoint: self.intervals = union_overlapping(self.intervals) self.size = sum(i.size for i in self.intervals) def __repr__(self): return repr(self.intervals) def __iter__(self): return iter(self.intervals) def __len__(self): return len(self.intervals) def __getitem__(self, i): return self.intervals[i] def __nonzero__(self): return self.size != 0 def __sub__(self, other): return self.intersect( other.complement() ) def complement(self): complementary = [] cursor = NEGATIVE_INFINITY for interval in self.intervals: if cursor < interval.start: complementary.append( Interval(cursor, interval.start) ) cursor = interval.end if cursor < INFINITY: complementary.append( Interval(cursor, INFINITY) ) return IntervalSet(complementary, disjoint=True) def intersect(self, other): #XXX The last major bottleneck. Factorial-time hell. # Then again, this function is entirely unused... if (not self) or (not other): return IntervalSet([]) #earliest = max(self.intervals[0].start, other.intervals[0].start) #latest = min(self.intervals[-1].end, other.intervals[-1].end) #mine = [i for i in self.intervals if i.start >= earliest and i.end <= latest] #theirs = [i for i in other.intervals if i.start >= earliest and i.end <= latest] intersections = [x for x in (i.intersect(j) for i in self.intervals for j in other.intervals) if x] return IntervalSet(intersections, disjoint=True) def intersect_interval(self, interval): intersections = [x for x in (i.intersect(interval) for i in self.intervals) if x] return IntervalSet(intersections, disjoint=True) def union(self, other): return IntervalSet( sorted(self.intervals + other.intervals) ) class Interval: __slots__ = ('start', 'end', 'tuple', 'size') def __init__(self, start, end): if end - start < 0: raise ValueError("Invalid interval start=%s end=%s" % (start, end)) self.start = start self.end = end self.tuple = (start, end) self.size = self.end - self.start def __eq__(self, other): return self.tuple == other.tuple def __ne__(self, other): return self.tuple != other.tuple def __hash__(self): return hash( self.tuple ) def __lt__(self, other): return self.start < self.start def __le__(self, other): return self.start <= self.start def __gt__(self, other): return self.start > self.start def __ge__(self, other): return self.start >= self.start def __cmp__(self, other): return (self.start > other.start) - (self.start < other.start) def __len__(self): raise TypeError("len() doesn't support infinite values, use the 'size' attribute instead") def __nonzero__(self): # Python 2 return self.size != 0 def __bool__(self): # Python 3 return self.size != 0 def __repr__(self): return '' % str(self.tuple) def intersect(self, other): start = max(self.start, other.start) end = min(self.end, other.end) if end > start: return Interval(start, end) def overlaps(self, other): earlier = self if self.start <= other.start else other later = self if earlier is other else other return earlier.end >= later.start def union(self, other): if not self.overlaps(other): raise TypeError("Union of disjoint intervals is not an interval") start = min(self.start, other.start) end = max(self.end, other.end) return Interval(start, end) def union_overlapping(intervals): """Union any overlapping intervals in the given set.""" disjoint_intervals = [] for interval in intervals: if disjoint_intervals and disjoint_intervals[-1].overlaps(interval): disjoint_intervals[-1] = disjoint_intervals[-1].union(interval) else: disjoint_intervals.append(interval) return disjoint_intervals graphite-web-1.1.4/webapp/graphite/views.py0000644000000000000000000000047213343334667020640 0ustar rootroot00000000000000import traceback from django.http import HttpResponseServerError from django.template import loader def server_error(request, template_name='500.html'): template = loader.get_template(template_name) context = {'stacktrace' : traceback.format_exc()} return HttpResponseServerError(template.render(context)) graphite-web-1.1.4/webapp/graphite/http_pool.py0000644000000000000000000000013713343334667021511 0ustar rootroot00000000000000"""Shared urllib3 pool.""" import urllib3 http = urllib3.PoolManager(num_pools=10, maxsize=5) graphite-web-1.1.4/webapp/graphite/logger.py0000644000000000000000000000707413343334667020767 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" import os, logging from logging.handlers import TimedRotatingFileHandler as Rotater try: from logging import NullHandler except ImportError as ie: # py2.6 from logging import Handler class NullHandler(Handler): def emit(self, record): pass try: from logging import FileHandler, StreamHandler except ImportError as ie: # py2.6 from logging.handlers import FileHandler, StreamHandler from django.conf import settings class GraphiteLogger: def __init__(self): self.infoLogger = self._config_logger( settings.LOG_FILE_INFO, 'info', True, level = logging.DEBUG if settings.DEBUG else logging.INFO, ) self.exceptionLogger = self._config_logger( settings.LOG_FILE_EXCEPTION, 'exception', True, ) self.cacheLogger = self._config_logger( settings.LOG_FILE_CACHE, 'cache', settings.LOG_CACHE_PERFORMANCE, level = logging.DEBUG if settings.DEBUG else logging.INFO, ) self.renderingLogger = self._config_logger( settings.LOG_FILE_RENDERING, 'rendering', settings.LOG_RENDERING_PERFORMANCE, level = logging.DEBUG if settings.DEBUG else logging.INFO, ) @staticmethod def _config_logger(log_file_name, name, activate, level=None, when='midnight', backupCount=settings.LOG_ROTATION_COUNT): logger = logging.getLogger(name) if level is not None: logger.setLevel(level) if activate: # if want to log this one if log_file_name == '-': formatter = logging.Formatter( fmt='[%(asctime)s.%(msecs)03d] %(name)s %(levelname)s %(message)s', datefmt='%d/%b/%Y %H:%M:%S') handler = StreamHandler() else: formatter = logging.Formatter( fmt='%(asctime)s.%(msecs)03d :: %(message)s', datefmt='%Y-%m-%d,%H:%M:%S') log_file = os.path.join(settings.LOG_DIR, log_file_name) if settings.LOG_ROTATION: # if we want to rotate logs handler = Rotater(log_file, when=when, backupCount=backupCount) else: # let someone else, e.g. logrotate, rotate the logs handler = FileHandler(log_file) handler.setFormatter(formatter) logger.addHandler(handler) else: logger.addHandler(NullHandler()) return logger def info(self,msg,*args,**kwargs): return self.infoLogger.info(msg,*args,**kwargs) def debug(self,msg,*args,**kwargs): return self.infoLogger.debug(msg,*args,**kwargs) def warning(self,msg,*args,**kwargs): return self.infoLogger.warn(msg,*args,**kwargs) def exception(self,msg="Exception Caught",**kwargs): return self.exceptionLogger.exception(msg,**kwargs) def cache(self,msg,*args,**kwargs): return self.cacheLogger.info(msg,*args,**kwargs) def rendering(self,msg,*args,**kwargs): return self.renderingLogger.info(msg,*args,**kwargs) log = GraphiteLogger() # import-shared logger instance graphite-web-1.1.4/webapp/graphite/app_settings.py0000644000000000000000000000745013343334667022206 0ustar rootroot00000000000000"""Copyright 2008 Orbitz WorldWide Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" # Django settings for graphite project. # DO NOT MODIFY THIS FILE DIRECTLY - use local_settings.py instead from os.path import dirname, join, abspath from django import VERSION as DJANGO_VERSION try: import raven except ImportError: raven = None try: import whitenoise except ImportError: whitenoise = False else: whitenoise_version = tuple(map( int, getattr(whitenoise, '__version__', '0').split('.'))) # Configure WhiteNoise < 3.2 from wsgi.py # http://whitenoise.evans.io/en/stable/changelog.html#v4-0 if whitenoise_version < (3, 2): whitenoise = False #Django settings below, do not touch! APPEND_SLASH = False TEMPLATE_DEBUG = False SILENCED_SYSTEM_CHECKS = ['urls.W002'] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ join(dirname( abspath(__file__) ), 'templates') ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this # list if you haven't customized them: 'django.contrib.auth.context_processors.auth', 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', ], }, }, ] # Language code for this installation. All choices can be found here: # http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes # http://blogs.law.harvard.edu/tech/stories/storyReader$15 LANGUAGE_CODE = 'en-us' # Absolute path to the directory that holds media. MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. # Example: "http://media.lawrence.com" MEDIA_URL = '' MIDDLEWARE = ( 'graphite.middleware.LogExceptionsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.gzip.GZipMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) if whitenoise: MIDDLEWARE += ('whitenoise.middleware.WhiteNoiseMiddleware', ) # SessionAuthenticationMiddleware is enabled by default since 1.10 and # deprecated since 2.0 if DJANGO_VERSION < (1, 10): MIDDLEWARE_CLASSES = MIDDLEWARE + \ ('django.contrib.auth.middleware.SessionAuthenticationMiddleware',) ROOT_URLCONF = 'graphite.urls' INSTALLED_APPS = ( 'graphite.account', 'graphite.browser', 'graphite.composer', 'graphite.dashboard', 'graphite.events', 'graphite.functions', 'graphite.metrics', 'graphite.render', 'graphite.tags', 'graphite.url_shortener', 'graphite.whitelist', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.staticfiles', 'tagging', ) if raven is not None: INSTALLED_APPS = INSTALLED_APPS + ('raven.contrib.django.raven_compat',) AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend'] GRAPHITE_WEB_APP_SETTINGS_LOADED = True graphite-web-1.1.4/webapp/content/0000755000000000000000000000000013343335472016770 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/content/css/0000755000000000000000000000000013343335472017560 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/content/css/default/0000755000000000000000000000000013343335472021204 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/content/css/default/inspect.gif0000755000000000000000000000105413343334667023350 0ustar rootroot00000000000000GIF89atvЄ\~̧ov_⪠ȹ޶Gqs鬲{X򜦷߉ĘL籘w|ڐxyˈuutⴶȐjݽ!W,W >B+$7R8M"H 1 ;8@% KL99T! NI*@L;)SJ@@9F6:A#M: <&,?=..0U411/E2O-ΎWC5(O .?ft℠@;graphite-web-1.1.4/webapp/content/css/default/overlay.png0000755000000000000000000000540213343334667023404 0ustar rootroot00000000000000PNG  IHDR 2Ͻ pHYs   MiCCPPhotoshop ICC profilexڝSwX>eVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3gAMA|Q cHRMz%u0`:o_FIDATxb>@`b *B]*&<hIENDB`graphite-web-1.1.4/webapp/content/css/default/sizer.gif0000755000000000000000000000031113343334667023032 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, F!"p2 ,wm/ٮuC58T+HUu0h~8 ;graphite-web-1.1.4/webapp/content/css/default/close.gif0000755000000000000000000000176413343334667023020 0ustar rootroot00000000000000GIF89a#$)**+}%&ߴKMp=?n=?^78\67ܰ޴ZY&,KGu%*u&*LN 5:6:Q@AiUVhUVV@B?34=23MEF9-NFNFXQʈ⤚C8H=H=NBNC`X͊ߟ|%/%.%1'8/9/SIUKUKh]i_qhwod]UP𛓌b^_[n u v  9/1)801*.)B;1,JAKDQHZRcZe\aZ`Yc]c^ohpi($)$A;VQ%#&#(%)&$$7687#"{{{{vv͛877ʽvvv<<<!,% lRH5Q}##db@0!( @:0R4*Z֨BŊ .D3J,X\@tcq8dș"`08!?A$.ID4ZpCxⰁQF1H҇/mP"œ%a>0D+p09!CS(E";graphite-web-1.1.4/webapp/content/css/default/center_left.gif0000755000000000000000000000006413343334667024175 0ustar rootroot00000000000000GIF89a !,  g;graphite-web-1.1.4/webapp/content/css/default/top_left.gif0000755000000000000000000000054613343334667023524 0ustar rootroot00000000000000GIF89a !., @P8FÉ#9 % \PJ(\KP(&a6w.D=x(X0#  ###u$%&'()*+ ,-A;graphite-web-1.1.4/webapp/content/css/default/maximize.gif0000755000000000000000000000200013343334667023516 0ustar rootroot00000000000000GIF89a,(,+',ի~I[JdcXȬjmjkmkšcb7p67o5ɬKGJ[ILG,C*.E*QVP"MmJ7K8VuMr#?VpM$@$y$x&QN.P/iNjO*y*xXzE/xb6;:c7a<_;j,k.UVKIKfeh blbbf/N_^r*0s+FGOWXUikiemlzyzyyw|UwSz 00l/SX]x0ec==yGxF}ՃÜYYYXXXLLLKKKDDDCCC???!,I ӨQ6MȰN$)T%8h@@DaCDQʴA!R,"$:TxO 9B!(Q $0*,t1bHpp0䊖0f,!% #T8e )U҈ =Yc=j`(Ҍ'_Ad(/rfD"(Q: "T͇G !)RG >>$AbȰF;graphite-web-1.1.4/webapp/content/css/default/bottom_left.gif0000755000000000000000000000027313343334667024223 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, 8rB 窞A, 0˵z}Ǿ5\o_;0]ۯ ,v`48;graphite-web-1.1.4/webapp/content/css/default/bottom_mid.gif0000755000000000000000000000010413343334667024033 0ustar rootroot00000000000000GIF89a죣ttt!, 8"ϱH;graphite-web-1.1.4/webapp/content/css/default/center_right.gif0000755000000000000000000000006113343334667024355 0ustar rootroot00000000000000GIF89a!,D\;graphite-web-1.1.4/webapp/content/css/default/bottom_right.gif0000755000000000000000000000027313343334667024406 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, 8!"p2 ,wm/ٮu˩^EL -+}8 ;graphite-web-1.1.4/webapp/content/css/default/clear.gif0000755000000000000000000000176613343334667023003 0ustar rootroot00000000000000GIF89a#t -(-)1,'p#>8>8=8>8=7<6<6:5:4940+.**y&(s$&l"$i!#f Y?:@:<7;6:59483*w&"a94832-=883>872-~*.{*.y*3/:5'm".):3+s&>8?9>8.q*KEVPTLSKYQYRTMhbWNohbWskÅ_O~rw]B\B]Dv`Ԟ]AP:`Fp˚cGbGjNgMlxyjMmQsWpMw̍sOrN{X~\^bmΐȗפ;Y%ϔXq;fFx_DP&rZnWJJvyvdh!ww!@ָHcrMQWd%k-s8MͮDEEBB!,+ X@ 4@`0 ̠ n8Pi qA J D̀ l#2IdG 0j1#(AZȡC6e,ᑅKȁXDyA M䆕)R Dt\Ҥ"F%"@*TjO@HnjI(i#) *= V џ;z ha$I+;graphite-web-1.1.4/webapp/content/css/default/resize.gif0000755000000000000000000000021213343334667023177 0ustar rootroot00000000000000GIF89aՇ! ,7Ik8k}ݗ @( H)bb^7J7M{ gC0H;Btҭ";graphite-web-1.1.4/webapp/content/css/default/top_right.gif0000755000000000000000000000054513343334667023706 0ustar rootroot00000000000000GIF89a !/, @Cy 3* "T"*. l3z}Cd>+A+^hhH$$$$$%&'()*+!,.-A;graphite-web-1.1.4/webapp/content/css/default/bottom_right_resize.gif0000755000000000000000000000031113343334667025760 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, F!"p2 ,wm/ٮuC58T+HUu0h~8 ;graphite-web-1.1.4/webapp/content/css/default/top_mid.gif0000755000000000000000000000022513343334667023335 0ustar rootroot00000000000000GIF89a!,`q 0@D;graphite-web-1.1.4/webapp/content/css/default/minimize.gif0000755000000000000000000000177713343334667023540 0ustar rootroot00000000000000GIF89awTUX*-gjgj2j;?XKLb:>{úTBxa`Q燤dT_rnqqsz{R)srqr)`bbcqNMIjm "#,?MI",<ҍЍZ[]]]__ܕ-ٓ-{;|[aaA Rದ%d Q\ 'O\;graphite-web-1.1.4/webapp/content/css/default.css0000644000000000000000000000522313343334667021725 0ustar rootroot00000000000000.overlay_dialog { background-color: #666666; filter:alpha(opacity=60); -moz-opacity: 0.6; opacity: 0.6; } .overlay___invisible__ { background-color: #666666; filter:alpha(opacity=0); -moz-opacity: 0; opacity: 0; } .dialog_nw { width: 9px; height: 23px; background: transparent url(default/top_left.gif) no-repeat 0 0; } .dialog_n { background: transparent url(default/top_mid.gif) repeat-x 0 0; height: 23px; } .dialog_ne { width: 9px; height: 23px; background: transparent url(default/top_right.gif) no-repeat 0 0; } .dialog_e { width: 2px; background: transparent url(default/center_right.gif) repeat-y 0 0; } .dialog_w { width: 2px; background: transparent url(default/center_left.gif) repeat-y 0 0; } .dialog_sw { width: 9px; height: 19px; background: transparent url(default/bottom_left.gif) no-repeat 0 0; } .dialog_s { background: transparent url(default/bottom_mid.gif) repeat-x 0 0; height: 19px; } .dialog_se { width: 9px; height: 19px; background: transparent url(default/bottom_right.gif) no-repeat 0 0; } .dialog_sizer { width: 9px; height: 19px; background: transparent url(default/sizer.gif) no-repeat 0 0; cursor:se-resize; } .dialog_close { width: 14px; height: 14px; background: transparent url(default/close.gif) no-repeat 0 0; position:absolute; top:5px; left:8px; cursor:pointer; z-index:2000; } .dialog_minimize { width: 14px; height: 15px; background: transparent url(default/minimize.gif) no-repeat 0 0; position:absolute; top:5px; left:28px; cursor:pointer; z-index:2000; } .dialog_maximize { width: 14px; height: 15px; background: transparent url(default/maximize.gif) no-repeat 0 0; position:absolute; top:5px; left:49px; cursor:pointer; z-index:2000; } .dialog_title { float:left; height:14px; font-family: Tahoma, Arial, sans-serif; font-size:12px; text-align:center; width:100%; color:#000; } .dialog_content { overflow:auto; color: #DDD; font-family: Tahoma, Arial, sans-serif; font-size: 10px; /* background-color:#123; */ background:#5E5148; } .top_draggable, .bottom_draggable { cursor:move; } .status_bar { font-size:12px; } .status_bar input{ font-size:12px; } .wired_frame { display: block; position: absolute; border: 1px #000 dashed; } /* DO NOT CHANGE THESE VALUES*/ .dialog { display: block; position: absolute; } .dialog table.table_window { border-collapse: collapse; border-spacing: 0; width: 100%; margin: 0px; padding:0px; } .dialog table.table_window td , .dialog table.table_window th { padding: 0; } .dialog .title_window { -moz-user-select:none; } graphite-web-1.1.4/webapp/content/css/dashboard-default.css0000644000000000000000000000135713343334667023656 0ustar rootroot00000000000000/* This is the basic body style of the graph area */ .graph-area-body { color: white; background-color: black; } /* Metric Completer Settings */ .completer-input-field { font-size: 11pt; font-family: monospace; } /* This class is applied to each completer result row. */ .metric-result div { font-size: 11pt; font-family: monospace; } /* This class is applied to completer results that are already present in the graph area. */ .metric-toggled div { font-weight: bold; } /* Likewise this is applied to any metrics not in the graph area already. */ .metric-not-toggled div { } /* This applies to any results that are branch nodes, not actual metrics. */ .result-is-branch-node { background-color: rgb(240, 245, 255) !important; } graphite-web-1.1.4/webapp/content/css/table.css0000644000000000000000000000202613343334667021366 0ustar rootroot00000000000000body { color: #4f6b72; background: #E6EAE9; } a { color: #c75f3e; } .g_canvas { border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; background: #fff; color: #4f6b72; } #title { font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; color: #4f6b72; border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; border-top: 1px solid #C1DAD7; letter-spacing: 2px; text-transform: uppercase; text-align: left; padding: 6px 6px 6px 12px; background: #CAE8EA; } .styledtable th { font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; color: #4f6b72; border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; border-top: 1px solid #C1DAD7; letter-spacing: 2px; text-transform: uppercase; text-align: left; padding: 6px 6px 6px 12px; background: #CAE8EA; } .styledtable td { border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; background: #fff; padding: 6px 6px 6px 12px; color: #4f6b72; } graphite-web-1.1.4/webapp/content/css/darkX.css0000644000000000000000000000512013343334667021346 0ustar rootroot00000000000000.overlay_darkX { background-color: #85BBEF; filter:alpha(opacity=60); -moz-opacity: 0.6; opacity: 0.6; } .darkX_nw { background: transparent url(darkX/titlebar-left-focused.png) no-repeat 0 0; width:6px; height:21px; } .darkX_n { background: transparent url(darkX/titlebar-mid-focused.png) repeat-x 0 0; height:21px; } .darkX_ne { background: transparent url(darkX/titlebar-right-focused.png) no-repeat 0 0; width:6px; height:21px; } .darkX_w { background: transparent url(darkX/frame-left-focused.png) repeat-y top left; width:3px; } .darkX_e { background: transparent url(darkX/frame-right-focused.png) repeat-y top right; width:3px; } .darkX_sw { background: transparent url(darkX/frame-bottom-left-focused.png) no-repeat 0 0; width:5px; height:3px; } .darkX_s { background: transparent url(darkX/frame-bottom-mid-focused.png) repeat-x 0 0; height:3px; } .darkX_se, .darkX_sizer { background: transparent url(darkX/frame-bottom-right-focused.png) no-repeat 0 0; width:5px; height:3px; } .darkX_sizer { cursor:se-resize; } .darkX_close { width: 21px; height: 21px; background: transparent url(darkX/button-close-focused.png) no-repeat 0 0; position:absolute; top:0px; right:5px; cursor:pointer; z-index:1000; } .darkX_minimize { width: 21px; height: 21px; background: transparent url(darkX/button-minimize-focused.png) no-repeat 0 0; position:absolute; top:0px; right:26px; cursor:pointer; z-index:1000; } .darkX_maximize { width: 21px; height: 21px; background: transparent url(darkX/button-maximize-focused.png) no-repeat 0 0; position:absolute; top:0px; right:47px; cursor:pointer; z-index:1000; } .darkX_title { float:left; height:14px; font-size:12px; text-align:center; margin-top:2px; width:100%; color:#FFF; } .darkX_content { overflow:auto; color: #E6DF2A; font-family: Tahoma, Arial, sans-serif; font-size: 14px; background:#5E5148; } /* FOR IE */ * html .darkX_minimize { background-color: transparent; background-image: none; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/darkX/button-minimize-focused.png", sizingMethod="crop"); } * html .darkX_maximize { background-color: transparent; background-image: none; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/darkX/button-maximize-focused.png", sizingMethod="scale"); } * html .darkX_close { background-color: transparent; background-image: none; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/darkX/button-close-focused.png", sizingMethod="crop"); } graphite-web-1.1.4/webapp/content/css/dashboard-white.css0000644000000000000000000000135513343334667023350 0ustar rootroot00000000000000/* This is the basic body style of the graph area */ .graph-area-body { color: black; background-color: white; } /* Metric Completer Settings */ .completer-input-field { font-size: 9pt; font-family: monospace; } /* This class is applied to each completer result row. */ .metric-result div { font-size: 9pt; font-family: monospace; } /* This class is applied to completer results that are already present in the graph area. */ .metric-toggled div { font-weight: bold; } /* Likewise this is applied to any metrics not in the graph area already. */ .metric-not-toggled div { } /* This applies to any results that are branch nodes, not actual metrics. */ .result-is-branch-node { background-color: rgb(240, 245, 255) !important; } graphite-web-1.1.4/webapp/content/css/dashboard.css0000644000000000000000000000522313343334667022230 0ustar rootroot00000000000000/* You should not need to change stuff in this file. If something is missing from dashboard-default.css that belongs there, please let graphite-dev know. */ div.graph-container { float: left; margin: 7px; margin-right: 0; } div.graph-overlay { position: relative; visibility: hidden; } div.overlay-close-button { position: absolute; top: -5px; right: -5px; background-color: #faa; color: #922; font-family: monospace; font-size: 8pt; font-weight: bold; text-align: center; width: 15px; height: 15px; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; } img.graph-img { visibility: visible; padding: 3px; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; } img.graph-img-loading { visibility: visible; padding: 3px; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; background-color: rgb(140, 140, 255); } .graph-over img { background-color: rgb(140, 140, 255); } .graph-over div.overlay-close-button { visibility: visible; } .selectable, .selectable * { user-select: text !important; -moz-user-select: text !important; -khtml-user-select: text !important; -webkit-user-select: text !important; } .x-unselectable, .x-unselectable * { user-select: text !important; -moz-user-select: text !important; -khtml-user-select: text !important; -webkit-user-select: text !important; } .x-list-selected { background-color: rgb(180, 180, 220); } /* Customize the size of the split bar toggle button */ .x-layout-split { height: 10px; width: 10px; } .x-layout-cmini-west, .x-layout-cmini-east { width: 10px !important; } .x-layout-split-north .x-layout-mini { height: 10px; background-image: url(../img/mini-top2.gif); } .x-layout-split-south .x-layout-mini { height: 10px !important; background-image: url(../img/mini-bottom2.gif); } .x-layout-split-east .x-layout-mini { width: 10px !important; } .x-layout-cmini-north { height: 10px !important; } .x-layout-cmini-north .x-layout-mini { height: 10px !important; background-image: url(../img/mini-bottom2.gif); } .x-layout-cmini-south .x-layout-mini { height: 10px !important; background-image: url(../img/mini-top2.gif); } .x-layout-cmini-west .x-layout-mini { width: 10px !important; } /* DnD classes */ .x-dd-drop-ok { } .x-dd-drop-nodrop { } #merge { display: none; height: 16; text-align: center; background-color: rgb(200, 255, 200); color: rgb(20, 80, 20); font-size: 9pt; font-family: sans-serif; font-weight: bold; } graphite-web-1.1.4/webapp/content/css/darkX/0000755000000000000000000000000013343335472020631 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/content/css/darkX/frame-right-focused.png0000755000000000000000000000021613343334667025201 0ustar rootroot00000000000000PNG  IHDRqVbKGD pHYsiTStIME'0zIDATxcĀVvmq۾IENDB`graphite-web-1.1.4/webapp/content/css/darkX/titlebar-right-focused.png0000755000000000000000000000026413343334667025720 0ustar rootroot00000000000000PNG  IHDR*bKGD pHYs/tIME rAIDATxڵ˹@CQ3$P8plE 蓻?QwIf. Ub^T7!yIENDB`graphite-web-1.1.4/webapp/content/css/darkX/frame-left-focused.png0000755000000000000000000000021613343334667025016 0ustar rootroot00000000000000PNG  IHDRqVbKGD pHYsiTStIME'y0IDATxcTRT301 "9T]or(IENDB`graphite-web-1.1.4/webapp/content/css/darkX/frame-bottom-right-focused.png0000755000000000000000000000021613343334667026503 0ustar rootroot00000000000000PNG  IHDR[6bKGD pHYsiTStIME$$SoIDATxcπX0YXX0Qm eIENDB`graphite-web-1.1.4/webapp/content/css/darkX/button-close-focused.png0000755000000000000000000000161013343334667025411 0ustar rootroot00000000000000PNG  IHDRbKGD pHYs  d_tIME.6tEXtCommentCreated By roberTOѭIDATxkQϽsLf$ƤW21VhTuJDp!Oq'pVQ  [)m5I243וW^s١,˺!$ !V1<ϫwRJAE9i4N_Z\X\hZ78sJiZW ۳SٽsX:X- oəkvq߁uz,HvMv)#~$$ skzѺu5ex9o%@l8K# 330N>Ƿ&PC1ʺ8Tn&m540 E.PTСGŃTҪ ]dma203?/ !6ւ~/_}p7wmY |Q6cvbtMAe̲*?3Q,Y5!%Innmn/a6Jq^ypz̩瞞<vM0oꗮ^[tRLe;ڒQ/EiVh{zS+o@;ԵAiڵZ'01edo z oKjm? [T)T3@(B( @;Lڕu.eX==jѯ|yf+UA( H Hd0>3vr;HtkfClDHEA@&d<7bdX݁shTNYP@Ln B#$aH$=jiaaTga:})RY7jE\T/3"MR]=hD%@uMY|eO b";FnoɱD[-i}P]xӏ?L.~ v_uL#C_jkVNaя^~()%snwl! ;R 7xO/ƛ+wzPx[[7,zZ(j~8-TՒ7KIENDB`graphite-web-1.1.4/webapp/content/css/darkX/frame-bottom-mid-focused.png0000755000000000000000000000021613343334667026137 0ustar rootroot00000000000000PNG  IHDR[6bKGD pHYsiTStIME%$b.IDATxcπX0YXX0Qm eIENDB`graphite-web-1.1.4/webapp/content/css/darkX/button-maximize-focused.png0000755000000000000000000000156213343334667026135 0ustar rootroot00000000000000PNG  IHDRbKGD pHYs CfStIME0lpIDATxkW{wgd7q711 @$((>(T|AB> kB*"[) Ynh~ܹ>-}Ӆ=;)! @(h!,-Z4LP8K3ږ]복Z8xh1O^/;yxlt&66[7nW+mz;,ɔҏ^L1LV#c}H墲F6y8>%E81Rݟffb>O:)gaenj7:rl>{Hy=* ׾3raLdӔx[[3:Zx#B/@|/rO| i;hHΰGOLONN˖+YElV;K%s7IENDB`graphite-web-1.1.4/webapp/content/css/darkX/titlebar-left-focused.png0000755000000000000000000000026413343334667025535 0ustar rootroot00000000000000PNG  IHDR*bKGD pHYs/tIMEkVAIDATxڵ˹@CQ3$P8plE 蓻?QwIf. Ub^T7!yIENDB`graphite-web-1.1.4/webapp/content/css/darkX/frame-bottom-left-focused.png0000755000000000000000000000021613343334667026320 0ustar rootroot00000000000000PNG  IHDR[6bKGD pHYsiTStIME$_0`IDATxcπX0YXX0Qm eIENDB`graphite-web-1.1.4/webapp/content/css/darkX/titlebar-mid-focused.png0000755000000000000000000000025713343334667025356 0ustar rootroot00000000000000PNG  IHDR0SbKGD pHYs  tIME-[;', region: 'center' }, listeners: { activate: keepDataWindowOnTop, show: fitImageToWindow, resize: fitImageToWindow } }); // Tack on some convenience closures win.updateTimeDisplay = function (time) { var text; if (time.mode == 'date-range') { text = 'From ' + time.startDate.toLocaleString(); text += ' Until ' + time.endDate.toLocaleString(); text = text.replace(/:00 /g, ' '); // Strip out the seconds } else if (time.mode == 'recent') { text = 'Now showing the past ' + time.quantity + ' ' + time.units; } timeDisplay.getEl().dom.innerHTML = text; }; win.updateUI = function () { var toggled = Composer.url.getParam('autorefresh') ? true : false; Ext.getCmp('autorefresh_button').toggle(toggled); updateCheckItems(); }; win.getImage = function () { return Ext.getDom('image-viewer'); }; return win; } function toggleWindow(createFunc) { // Convenience for lazily creating toggled dialogs function toggler (button, e) { if (!button.window) { //First click, create the window button.window = createFunc(); } if (button.window.isVisible()) { button.window.hide(); } else { button.window.show(); } } return toggler; } function ifEnter(func) { // Convenience decorator for specialkey listener definitions return function (widget, e) { if (e.getCharCode() == Ext.EventObject.RETURN) { func(widget); } } } function keepDataWindowOnTop () { if (GraphDataWindow.window && GraphDataWindow.window.isVisible()) { GraphDataWindow.window.toFront(); } } function fitImageToWindow(win) { Composer.url.setParam('width', win.getInnerWidth()); Composer.url.setParam('height', win.getInnerHeight()); try { Composer.updateImage(); } catch (err) { //An exception gets thrown when the initial resize event //occurs prior to rendering the image viewer. Safe to ignore. } } /* Toolbar stuff */ function createToolbarButton(tip, icon, handler) { return new Ext.Toolbar.Button({ style: 'margin: 0 5px; background:transparent url(' + document.body.dataset.staticRoot + 'img/' + icon + ') no-repeat scroll 0% 50%', handler: handler, handleMouseEvents: false, text: '   ', listeners: { render: function (button) { button.el.toolTip = new Ext.ToolTip({ html: tip, showDelay: 100, dismissDelay: 10000, target: button.el }); } } }); } /* "Date Range" Calendar */ function createCalendarWindow() { // Start/End labels var style = 'font-family: tahoma,arial,verdana,sans-serif; font-size:11px;'; var startDateHeader = { html: '
Start Date
' }; var endDateHeader = { html: '
End Date
' }; // Date controls var startDateControl = new Ext.DatePicker({ id: 'start-date', maxDate: new Date() }); var endDateControl = new Ext.DatePicker({ id: 'end-date', maxDate: new Date() }); startDateControl.on('select', calendarSelectionMade); endDateControl.on('select', calendarSelectionMade); // Time controls var startTimeControl = new Ext.form.TimeField({ id: 'start-time', increment: 30, allowBlank: false, value: '12:00 AM', listeners: {select: calendarSelectionMade, specialkey: ifEnter(calendarSelectionMade)} }); var endTimeControl = new Ext.form.TimeField({ id: 'end-time', allowBlank: false, value: '11:59 PM', listeners: {select: calendarSelectionMade, specialkey: ifEnter(calendarSelectionMade)} }); var myWindow; var resizeStuff = function () { startTimeControl.setWidth( startDateControl.el.getWidth() ); endTimeControl.setWidth( endDateControl.el.getWidth() ); myWindow.setWidth( startDateControl.el.getWidth() + endDateControl.el.getWidth() + myWindow.getFrameWidth() ); //myWindow.setHeight( startDateControl.el.getHeight() + startTimeControl.el.getHeight() + myWindow.getFrameHeight() ); }; myWindow = new Ext.Window({ title: 'Select Date Range', layout: 'table', height: 300, width: 400, layoutConfig: { columns: 2 }, closeAction: 'hide', items: [ startDateHeader, endDateHeader, startDateControl, endDateControl, startTimeControl, endTimeControl ], listeners: {show: resizeStuff} }); return myWindow; } function calendarSelectionMade(datePicker, selectedDate) { var startDate = getCalendarSelection('start'); var endDate = getCalendarSelection('end'); Composer.url.setParam('from', asDateString(startDate) ); Composer.url.setParam('until', asDateString(endDate) ); Composer.updateImage(); Composer.window.updateTimeDisplay({ mode: 'date-range', startDate: startDate, endDate: endDate }); } function getCalendarSelection(which) { var myDate = Ext.getCmp(which + '-date').getValue(); var myTime = Ext.getCmp(which + '-time').getEl().dom.value; // Need to grab the raw textfield value, which may not be selected var myHour = myTime.match(/(\d+):/)[1]; var myMinute = myTime.match(/:(\d+)/)[1]; if (myTime.match(/\bAM\b/i) && myHour == '12') { myHour = 0; } if (myTime.match(/\bPM\b/i) && myHour != '12') { myHour = parseInt(myHour) + 12; } return myDate.add(Date.HOUR, myHour).add(Date.MINUTE, myMinute); } function asDateString(dateObj) { return dateObj.format('H:i_Ymd'); } /* Short url window */ function showShortUrl() { var showUrl = function(options, success, response) { if(success) { var win = new Ext.Window({ title: 'Graph URL', width: 600, height: 125, layout: 'border', modal: true, items: [ { xtype: 'label', region: 'north', style: 'text-align: center;', text: 'Short Direct URL to this graph' }, { xtype: 'textfield', region: 'center', value: window.location.origin + response.responseText, editable: false, style: 'text-align: center; font-size: large;', listeners: { focus: function (field) { field.selectText(); } } } ], buttonAlign: 'center', buttons: [ {text: 'Close', handler: function () { win.close(); } } ] }); win.show(); } } Ext.Ajax.request({ method: 'GET', url: document.body.dataset.baseUrl + 's/render/?' + Composer.url.queryString, callback: showUrl }); } /* "Recent Data" dialog */ function toggleWindow(createFunc) { function toggler (button, e) { if (!button.window) { //First click, create the window button.window = createFunc(); } if (button.window.isVisible()) { button.window.hide(); } else { button.window.show(); } } return toggler; } function createURLWindow() { var urlField = new Ext.form.TextField({ id: 'from-url', allowBlank: false, vtype: 'url', listeners: { change: urlChosen, specialkey: ifEnter(urlChosen) } }); return new Ext.Window({ title: 'Enter a URL to build graph from', layout: 'fit', height: 60, width: 450, closeAction: 'hide', items: [ urlField ] }); } function urlChosen() { var url = Ext.getCmp('from-url').getValue(); Composer.loadMyGraph('temp', decodeURIComponent(url)) } function createRecentWindow() { var quantityField = new Ext.form.NumberField({ id: 'time-quantity', grow: true, value: 24, listeners: {change: recentSelectionMade, specialkey: ifEnter(recentSelectionMade)} }); var unitSelector = new Ext.form.ComboBox({ id: 'time-units', editable: false, triggerAction: 'all', mode: 'local', store: ['minutes', 'hours', 'days', 'weeks', 'months', 'years'], width: 75, value: 'hours', listeners: {select: recentSelectionMade} }); return new Ext.Window({ title: 'Select a Recent Time Range', layout: 'table', height: 60, //there's gotta be a way to auto-size these windows! width: 235, layoutConfig: { columns: 3 }, closeAction: 'hide', items: [ { html: '
View the past
', style: 'border: none; background-color: rgb(223,232,246)' }, quantityField, unitSelector ] }); } function recentSelectionMade(combo, record, index) { var quantity = Ext.getCmp('time-quantity').getValue(); var units = Ext.getCmp('time-units').getValue(); var fromString = '-' + quantity + units; Composer.url.setParam('from', fromString); Composer.url.removeParam('until'); Composer.updateImage(); Composer.window.updateTimeDisplay({ mode: 'recent', quantity: quantity, units: units }); } /* "Save to MyGraphs" */ function saveMyGraph(button, e) { var myGraphName = ''; if (Composer.state.myGraphName) { myGraphName = Composer.state.myGraphName; var tmpArray = myGraphName.split('.'); if (tmpArray.length > 1) { tmpArray = tmpArray.slice(1, tmpArray.length); myGraphName = tmpArray.join('.'); } } Ext.MessageBox.prompt( 'Save to My Graphs', //title 'Please enter a name for your Graph', //prompt message function (button, text) { //handler if (button != 'ok') { return; } if (!text) { Ext.Msg.alert('You must enter a graph name!'); return; } if (text.charAt(text.length - 1) == '.') { Ext.Msg.alert('Graph names cannot end in a period.'); return; } //Save the name for future use and re-load the "My Graphs" tree Composer.state.myGraphName = text; //Send the request Ext.Ajax.request({ method: 'GET', url: document.body.dataset.baseUrl + 'composer/mygraph/', params: {action: 'save', graphName: text, url: Composer.url.getURL()}, callback: handleSaveMyGraphResponse }); }, this, //scope false, //multiline myGraphName ? myGraphName : '' //default value ); } function handleSaveMyGraphResponse(options, success, response) { var message; if (success) { Browser.trees.mygraphs.reload(); message = 'Graph saved successfully'; } else { message = 'There was an error saving your Graph, please try again later.'; } Ext.MessageBox.show({ title: 'Save to My Graphs - Result', msg: message, buttons: Ext.MessageBox.OK }); } function deleteMyGraph() { Ext.MessageBox.prompt( 'Delete a saved My Graph', //title 'Please enter the name of the My Graph you wish to delete', //prompt message function (button, text) { //handler if (button != 'ok') { return; } if (!text) { Ext.Msg.alert('Invalid My Graph name!'); return; } //Send the request Ext.Ajax.request({ method: 'GET', url: document.body.dataset.baseUrl + 'composer/mygraph/', params: {action: 'delete', graphName: text}, callback: function (options, success, response) { var message; if (success) { Browser.trees.mygraphs.reload(); message = 'Graph deleted successfully'; } else { message = 'There was an error performing the operation.'; } Ext.Msg.show({ title: 'Delete My Graph', msg: message, buttons: Ext.Msg.OK }); } }); }, this, //scope false, //multiline Composer.state.myGraphName ? Composer.state.myGraphName : '' //default value ); } /* Graph Data dialog */ var GraphDataWindow = { create: function () { var _this = this; this.targetList = new Ext.ListView({ store: TargetStore, multiSelect: true, emptyText: 'No graph targets', reserveScrollOffset: true, columnSort: false, hideHeaders: true, width: 385, height: 140, columns: [ {header: 'Graph Targets', width: 1.0, dataIndex: 'value'} ], listeners: { contextmenu: this.targetContextMenu, afterrender: this.targetChanged, selectionchange: this.targetChanged, dblclick: function (targetList, index, node, e) { targetList.select(index); this.editTarget(); }, scope: this } }); var targetsPanel = new Ext.Panel({ region: 'center', width: 400, height: 200, layout: 'fit', items: this.targetList }); var buttonPanel = new Ext.Panel({ region: 'east', width: 100, baseCls: 'x-window-mc', layout: { type: 'vbox', align: 'stretch' }, defaults: { xtype: 'button', disabled: true }, items: [ { text: 'Add', handler: this.addTarget.createDelegate(this), disabled: false }, { text: 'Edit', id: 'editTargetButton', handler: this.editTarget.createDelegate(this) }, { text: 'Remove', id: 'removeTargetButton', handler: this.removeTarget.createDelegate(this) }, { text: 'Move', id: 'moveButton', menuAlign: 'tr-tl', menu: { subMenuAlign: 'tr-tl', defaults: { defaultAlign: 'tr-tl' }, items: [ { text: 'Move Up', handler: this.moveTargetUp.createDelegate(this) }, { text: 'Move Down', handler: this.moveTargetDown.createDelegate(this) }, { text: 'Swap', handler: this.swapTargets.createDelegate(this), id: 'menuSwapTargets' } ] } }, { text: 'Apply Function', id: 'applyFunctionButton', menuAlign: 'tr-tl', menu: { subMenuAlign: 'tr-tl', defaults: { defaultAlign: 'tr-tl' }, items: createFunctionsMenu() } }, { text: 'Undo Function', handler: this.removeOuterCall.createDelegate(this), id: 'undoFunctionButton' } ] }); this.window = new Ext.Window({ title: 'Graph Data', height: 200, width: 600, closeAction: 'hide', layout: 'border', items: [ targetsPanel, buttonPanel ], listeners: { afterrender: function () { if (_this.targetList.getNodes().length > 0) { _this.targetList.select(0); } } } }); return this.window; }, targetChanged: function () { if (!this.targetList) { return; } // Ignore initial call var selected; try { selected = this.getSelectedTargets().length; } catch (e) { return; } if (selected == 0) { Ext.getCmp('editTargetButton').disable(); Ext.getCmp('removeTargetButton').disable(); Ext.getCmp('applyFunctionButton').disable(); Ext.getCmp('undoFunctionButton').disable(); Ext.getCmp('moveButton').disable(); } else { Ext.getCmp('editTargetButton').enable(); Ext.getCmp('removeTargetButton').enable(); Ext.getCmp('applyFunctionButton').enable(); Ext.getCmp('undoFunctionButton').enable(); Ext.getCmp('moveButton').enable(); } // Swap Targets if (selected == 2) Ext.getCmp('menuSwapTargets').enable(); else Ext.getCmp('menuSwapTargets').disable(); }, targetContextMenu: function (targetList, index, node, e) { /* Select the right-clicked row unless it is already selected */ if (! targetList.isSelected(index) ) { targetList.select(index); } var removeItem = {text: 'Remove', handler: this.removeTarget.createDelegate(this)}; var editItem = {text: 'Edit', handler: this.editTarget.createDelegate(this)}; var moveMenu = { text: 'Move', menu: [ { text: 'Move Up', handler: this.moveTargetUp.createDelegate(this) }, { text: 'Move Down', handler: this.moveTargetDown.createDelegate(this) }, { text: 'Swap', handler: this.swapTargets.createDelegate(this), disabled: true } ] }; if (this.getSelectedTargets().length == 0) { removeItem.disabled = true; editItem.disabled = true; moveMenu.disabled = true; } if (this.getSelectedTargets().length == 2) moveMenu.menu[2].disabled = false; var contextMenu = new Ext.menu.Menu({ items: [removeItem, editItem, moveMenu] }); contextMenu.showAt( e.getXY() ); e.stopEvent(); }, applyFuncToEach: function (funcName, extraArg) { var _this = this; function applyFunc() { Ext.each(_this.getSelectedTargets(), function (target) { var newTarget; if (extraArg) { newTarget = funcName + '(' + target + ',' + extraArg + ')'; } else { newTarget = funcName + '(' + target + ')'; } replaceTarget(target, newTarget); _this.targetList.select( TargetStore.findExact('value', newTarget), true); } ); Composer.syncTargetList(); Composer.updateImage(); } return applyFunc; }, applyFuncToEachWithInput: function (funcName, question, options) { if (options == null) { options = {}; } function applyFunc() { Ext.MessageBox.prompt( 'Input Required', //title question, //message function (button, inputValue) { //handler if (button == 'ok' && (options.allowBlank || inputValue != '')) { if (options.quote) { inputValue = '"' + inputValue + '"'; } applyFuncToEach(funcName, inputValue)(); } }, this, //scope false, //multiline '' //initial value ); } applyFunc = applyFunc.createDelegate(this); return applyFunc; }, applyFuncToAll: function (funcName) { function applyFunc() { var args = this.getSelectedTargets().join(','); var oldTargets = this.getSelectedTargets(); var firstTarget = oldTargets.shift(); var newTarget = funcName + '(' + args + ')'; // Insert new target where the first selected was replaceTarget(firstTarget,newTarget); Ext.each(oldTargets, function (target) { removeTarget(target); } ); Composer.syncTargetList(); Composer.updateImage(); this.targetList.select( TargetStore.findExact('value', newTarget), true); } applyFunc = applyFunc.createDelegate(this); return applyFunc; }, removeOuterCall: function () { /* It turns out that this is a big pain in the ass to do properly. * The following code is *almost* correct. It will fail if there is * an argument with a quoted parenthesis in it. Who cares... */ var _this = this; Ext.each(this.getSelectedTargets(), function (target) { var args = []; var i, c; var lastArg = 0; var depth = 0; var argString = target.replace(/^[^(]+\((.+)\)/, '$1'); //First we strip it down to just args for (i = 0; i < argString.length; i++) { switch (argString.charAt(i)) { case '(': depth += 1; break; case '{': depth += 1; break; case ')': depth -= 1; break; case '}': depth -= 1; break; case ',': if (depth > 0) { continue; } if (depth < 0) { Ext.Msg.alert('Malformed target, cannot remove outer call.'); return; } args.push( argString.substring(lastArg, i).replace(/^\s+/, '').replace(/\s+$/, '') ); lastArg = i + 1; break; } } args.push( argString.substring(lastArg, i) ); var firstIndex = indexOfTarget(target); removeTarget(target); args.reverse() Ext.each(args, function (arg) { if (!arg.match(/^([0123456789\.]+|".+"|'.*')$/)) { //Skip string and number literals insertTarget(firstIndex, arg); _this.targetList.select( TargetStore.findExact('value', arg), true); } }); Composer.syncTargetList(); Composer.updateImage(); } ); }, addTarget: function (target) { var metricCompleter; var win; metricCompleter = new MetricCompleter({ listeners: { specialkey: function (field, e) { if (e.getKey() == e.ENTER) { var target = metricCompleter.getValue(); addTarget(target); Composer.syncTargetList(); Composer.updateImage(); win.close(); e.stopEvent(); return false; } }, afterrender: function (field) { metricCompleter.focus('', 500); } } }); win = new Ext.Window({ title: 'Add a new Graph Target', id: 'addTargetWindow', modal: true, width: 400, height: 115, layout: { type: 'vbox', align:'stretch', pack: 'center' }, items: [ {xtype: 'label', text: 'Type the path of your new Graph Target.'}, metricCompleter ], buttonAlign: 'center', buttons: [ { xtype: 'button', text: 'OK', handler: function () { var target = metricCompleter.getValue(); addTarget(target); Composer.syncTargetList(); Composer.updateImage(); win.close(); } }, { xtype: 'button', text: 'Cancel', handler: function () { Ext.getCmp('addTargetWindow').close(); } } ] }); win.show(); }, removeTarget: function (item, e) { Ext.each(this.getSelectedTargets(), function (target) { removeTarget(target); }); Composer.syncTargetList(); Composer.updateImage(); }, editTarget: function (item, e) { var selected = this.targetList.getSelectedRecords(); if (selected.length != 1) { Ext.MessageBox.show({ title: 'Error', msg: 'You must select exactly one target to edit.', icon: Ext.MessageBox.ERROR, buttons: Ext.MessageBox.OK }); return; } var record = selected[0]; var metricCompleter; var win; metricCompleter = new MetricCompleter({ value: record.get('value'), listeners: { specialkey: function (field, e) { if (e.getKey() == e.ENTER) { var target = metricCompleter.getValue(); record.set('value', target); record.commit(); Composer.syncTargetList(); Composer.updateImage(); win.close(); e.stopEvent(); return false; } }, afterrender: function (field) { metricCompleter.focus('', 500); } } }); function editHandler () { var newValue = metricCompleter.getValue(); if (newValue != '') { record.set('value', newValue); record.commit(); Composer.syncTargetList(); Composer.updateImage(); } win.close(); } editHandler = editHandler.createDelegate(this); //dynamic scoping can really be a bitch win = new Ext.Window({ title: 'Edit Graph Target', id: 'editTargetWindow', modal: true, width: 400, height: 115, layout: { type: 'vbox', align:'stretch', pack: 'center' }, items: [ {xtype: 'label', text: 'Edit the path of your Graph Target.'}, metricCompleter ], buttonAlign: 'center', buttons: [ { xtype: 'button', text: 'OK', handler: editHandler }, { xtype: 'button', text: 'Cancel', handler: function () { win.close(); } } ] }); win.show(); }, moveTargetUp: function() { this._moveTarget(-1); }, moveTargetDown: function() { this._moveTarget(1); }, swapTargets: function() { this._swapTargets(); }, _moveTarget: function(direction) { var store = this.targetList.getStore(); var selectedRecords = this.targetList.getSelectedRecords(); // Don't move past boundaries var exit = false; Ext.each(selectedRecords, function(record) { var index = store.indexOf(record); if (direction == -1 && index == 0) { exit = true; return false; } else if (direction == 1 && index == store.getCount() - 1) { exit = true; return false; } }); if (exit) return; var newSelections = []; Ext.each(selectedRecords, function(recordA) { var indexA = store.indexOf( recordA ); var valueA = recordA.get('value'); var recordB = store.getAt( indexA + direction ); // swap recordA.set('value', recordB.get('value')); recordB.set('value', valueA); recordA.commit(); recordB.commit(); newSelections.push( indexA + direction ); }); Composer.syncTargetList(); Composer.updateImage(); this.targetList.select(newSelections); }, _swapTargets: function() { var selectedRecords = this.targetList.getSelectedRecords(); if (selectedRecords.length != 2) return; var recordA = selectedRecords[0]; var recordB = selectedRecords[1]; var valueA = recordA.get('value'); recordA.set('value', recordB.get('value')); recordB.set('value', valueA); recordA.commit(); recordB.commit(); Composer.syncTargetList(); Composer.updateImage(); }, addWlSelected: function (item, e) { Ext.Ajax.request({ url: document.body.dataset.baseUrl + 'whitelist/add', method: 'POST', success: function () { Ext.Msg.alert('Result', 'Successfully added metrics to whitelist.'); }, failure: function () { Ext.Msg.alert('Result', 'Failed to add metrics to whitelist.'); }, params: {metrics: this.getSelectedTargets().join('\n') } }); }, removeWlSelected: function (item, e) { Ext.Ajax.request({ url: document.body.dataset.baseUrl + 'whitelist/remove', method: 'POST', success: function () { Ext.Msg.alert('Result', 'Successfully removed metrics from whitelist.'); }, failure: function () { Ext.Msg.alert('Result', 'Failed to remove metrics from whitelist.'); }, params: {metrics: this.getSelectedTargets().join('\n') } }); }, getSelectedTargets: function () { var targets = []; Ext.each(this.targetList.getSelectedRecords(), function (record) { targets.push( record.get('value') ); }); return targets; } }; /* Yet another ghetto api hack */ var applyFuncToAll = GraphDataWindow.applyFuncToAll.createDelegate(GraphDataWindow); var applyFuncToEach = GraphDataWindow.applyFuncToEach.createDelegate(GraphDataWindow); var applyFuncToEachWithInput = GraphDataWindow.applyFuncToEachWithInput.createDelegate(GraphDataWindow); function createFunctionsMenu() { return [{ text: 'Combine', menu: [ {text: 'Sum', handler: applyFuncToAll('sumSeries')}, {text: 'Average', handler: applyFuncToAll('averageSeries')}, {text: 'Product', handler: applyFuncToAll('multiplySeries')}, {text: 'Min Values', handler: applyFuncToAll('minSeries')}, {text: 'Max Values', handler: applyFuncToAll('maxSeries')}, {text: 'Group', handler: applyFuncToAll('group')}, {text: 'Range', handler: applyFuncToAll('rangeOfSeries')}, {text: 'Count', handler: applyFuncToEach('countSeries')} ] }, { text: 'Transform', menu: [ {text: 'Scale', handler: applyFuncToEachWithInput('scale', 'Please enter a scale factor')}, {text: 'ScaleToSeconds', handler: applyFuncToEachWithInput('scaleToSeconds', 'Please enter a number of seconds to scale to')}, {text: 'Offset', handler: applyFuncToEachWithInput('offset', 'Please enter the value to offset Y-values by')}, {text: 'OffsetToZero', handler: applyFuncToEach('offsetToZero')}, {text: 'Interpolate', handler: applyFuncToEach('interpolate')}, {text: 'Derivative', handler: applyFuncToEach('derivative')}, {text: 'Power', handler: applyFuncToEachWithInput('pow', 'Please enter a power factor')}, {text: 'Power Series', handler: applyFuncToEachWithInput('powSeries', 'Please enter at least 2 series')}, {text: 'Square Root', handler: applyFuncToEach('squareRoot')}, {text: 'Time-adjusted Derivative', handler: applyFuncToEachWithInput('perSecond', 'Please enter a maximum value if this metric is a wrapping counter (or just leave this blank)', {allowBlank: true})}, {text: 'Delay', handler: applyFuncToEachWithInput('delay', 'Please enter the number of steps to delay')}, {text: 'Integral', handler: applyFuncToEach('integral')}, {text: 'Integral by Interval', handler: applyFuncToEachWithInput('integralByInterval', 'Integral this metric with a reset every ___ (examples: 1d, 1h, 10min)', {quote: true})}, {text: 'Percentile Values', handler: applyFuncToEachWithInput('percentileOfSeries', 'Please enter the percentile to use')}, {text: 'Non-negative Derivative', handler: applyFuncToEachWithInput('nonNegativeDerivative', 'Please enter a maximum value if this metric is a wrapping counter (or just leave this blank)', {allowBlank: true})}, {text: 'Log', handler: applyFuncToEachWithInput('log', 'Please enter a base')}, {text: 'Invert', handler: applyFuncToEach('invert')}, {text: 'Absolute Value', handler: applyFuncToEach('absolute')}, {text: 'timeShift', handler: applyFuncToEachWithInput('timeShift', 'Shift this metric ___ back in time (examples: 10min, 7d, 2w)', {quote: true})}, {text: 'timeSlice', handler: applyFuncToEachWithInput('timeSlice', 'Start showing metric at (example: 14:57 20150115)', {quote: true})}, {text: 'Summarize', handler: applyFuncToEachWithInput('summarize', 'Please enter a summary interval (examples: 10min, 1h, 7d)', {quote: true})}, {text: 'Hit Count', handler: applyFuncToEachWithInput('hitcount', 'Please enter a summary interval (examples: 10min, 1h, 7d)', {quote: true})} ] }, { text: 'Calculate', menu: [ {text: 'Moving Average', handler: applyFuncToEachWithInput('movingAverage', 'Moving average for the last ___ data points')}, {text: 'Moving Median', handler: applyFuncToEachWithInput('movingMedian', 'Moving median for the last ___ data points')}, {text: 'Moving Standard Deviation', handler: applyFuncToEachWithInput('stdev', 'Moving standard deviation for the last ___ data points')}, {text: 'Moving Sum', handler: applyFuncToEachWithInput('movingSum', 'Moving sum for the last ___ data points')}, {text: 'Moving Min', handler: applyFuncToEachWithInput('movingMin', 'Moving minimum for the last ___ data points')}, {text: 'Moving Max', handler: applyFuncToEachWithInput('movingMax', 'Moving maximum for the last ___ data points')}, {text: 'Holt-Winters Forecast', handler: applyFuncToEach('holtWintersForecast')}, {text: 'Holt-Winters Confidence Bands', handler: applyFuncToEach('holtWintersConfidenceBands')}, {text: 'Holt-Winters Aberration', handler: applyFuncToEach('holtWintersAberration')}, {text: 'Linear Regression', handler: applyFuncToEachWithInput('linearRegression', 'Start source of regression at (example: 14:57 20150115)', {quote: true})}, {text: 'As Percent', handler: applyFuncToEachWithInput('asPercent', 'Please enter the value that corresponds to 100% or leave blank to use the total', {allowBlank: true})}, {text: 'Difference (of 2 series)', handler: applyFuncToAll('diffSeries')}, {text: 'Ratio (of 2 series)', handler: applyFuncToAll('divideSeries')}, {text: 'Ratio (of series lists)', handler: applyFuncToAll('divideSeriesLists')}, {text: 'Exponential Moving Average', handler: applyFuncToEachWithInput('exponentialMovingAverage', 'EMA for the last __ data points')} ] }, { text: 'Filter', menu: [ { text: 'Data Filters', menu: [ {text: 'Remove Above Value', handler: applyFuncToEachWithInput('removeAboveValue', 'Set any values above ___ to None')}, {text: 'Remove Above Percentile', handler: applyFuncToEachWithInput('removeAbovePercentile', 'Set any values above the ___th percentile to None')}, {text: 'Remove Below Value', handler: applyFuncToEachWithInput('removeBelowValue', 'Set any values below ___ to None')}, {text: 'Remove Below Percentile', handler: applyFuncToEachWithInput('removeBelowPercentile', 'Set any values below the ___th percentile to None')} ] }, {text: 'Most Deviant', handler: applyFuncToEachWithInput('mostDeviant', 'Draw the ___ metrics with the highest standard deviation')}, {text: 'Highest Current Value', handler: applyFuncToEachWithInput('highestCurrent', 'Draw the ___ metrics with the highest current value')}, {text: 'Lowest Current Value', handler: applyFuncToEachWithInput('lowestCurrent', 'Draw the ___ metrics with the lowest current value')}, {text: 'Highest Maximum Value', handler: applyFuncToEachWithInput('highestMax', 'Draw the ___ metrics with the highest maximum value')}, {text: 'Nth Percentile Value', handler: applyFuncToEachWithInput('nPercentile', 'Draw the ___th Percentile for each metric.')}, {text: 'Remove Between Percentile', handler: applyFuncToEachWithInput('removeBetweenPercentile', 'Draw the metrics that have a value outside the ___th percentile of the average at a time')}, {text: 'Current Value Above', handler: applyFuncToEachWithInput('currentAbove', 'Draw all metrics whose current value is above ___')}, {text: 'Current Value Below', handler: applyFuncToEachWithInput('currentBelow', 'Draw all metrics whose current value is below ___')}, {text: 'Highest Average Value', handler: applyFuncToEachWithInput('highestAverage', 'Draw the ___ metrics with the highest average value')}, {text: 'Lowest Average Value', handler: applyFuncToEachWithInput('lowestAverage', 'Draw the ___ metrics with the lowest average value')}, {text: 'Average Value Above', handler: applyFuncToEachWithInput('averageAbove', 'Draw all metrics whose average value is above ___')}, {text: 'Average Value Below', handler: applyFuncToEachWithInput('averageBelow', 'Draw all metrics whose average value is below ___')}, {text: 'Average Outside Percentile Value', handler: applyFuncToEachWithInput('averageOutsidePercentile', 'Draw the metrics which average lies outside the ___th percentile of all averages')}, {text: 'Maximum Value Above', handler: applyFuncToEachWithInput('maximumAbove', 'Draw all metrics whose maximum value is above ___')}, {text: 'Maximum Value Below', handler: applyFuncToEachWithInput('maximumBelow', 'Draw all metrics whose maximum value is below ___')}, {text: 'Minimum Value Above', handler: applyFuncToEachWithInput('minimumAbove', 'Draw all metrics whose minimum value is above ___')}, {text: 'Minimum Value Below', handler: applyFuncToEachWithInput('minimumBelow', 'Draw all metrics whose minimum value is below ___')}, {text: 'sortByName', handler: applyFuncToEach('sortByName')}, {text: 'sortByTotal', handler: applyFuncToEach('sortByTotal')}, {text: 'sortByMaxima', handler: applyFuncToEach('sortByMaxima')}, {text: 'sortByMinima', handler: applyFuncToEach('sortByMinima')}, {text: 'limit', handler: applyFuncToEachWithInput('limit', 'Limit to first ___ of a list of metrics')}, {text: 'Exclude', handler: applyFuncToEachWithInput('exclude', 'Exclude metrics that match a regular expression')}, {text: 'Grep', handler: applyFuncToEachWithInput('grep', 'Exclude metrics that don\'t match a regular expression')}, {text: 'Remove Empty Series', handler: applyFuncToEachWithInput('removeEmptySeries', 'Removes series with no data from graph')}, {text: 'Remove Duplicates By Name', handler: applyFuncToAll('unique')} ] }, { text: 'Special', menu: [ {text: 'Set Legend Name', handler: applyFuncToEachWithInput('alias', 'Enter a legend label for this graph target', {quote: true})}, {text: 'Set Legend Name By Metric', handler: applyFuncToEach('aliasByMetric')}, {text: 'Set Legend Name By Node', handler: applyFuncToEachWithInput('aliasByNode', 'Enter the 0-indexed node to display')}, {text: 'Add Values to Legend Name', menu: [ {text: 'Cacti Style Legend', handler: applyFuncToEach('cactiStyle')}, {text: 'Last Value', handler: applyFuncToEach('legendValue', '"last"')}, {text: 'Average Value', handler: applyFuncToEach('legendValue', '"avg"')}, {text: 'Total Value', handler: applyFuncToEach('legendValue', '"total"')}, {text: 'Min Value', handler: applyFuncToEach('legendValue', '"min"')}, {text: 'Max Value', handler: applyFuncToEach('legendValue', '"max"')} ]}, {text: 'Color', handler: applyFuncToEachWithInput('color', 'Set the color for this graph target', {quote: true})}, {text: 'Alpha', handler: applyFuncToEachWithInput('alpha', 'Set the alpha (transparency) for this graph target (between 0.0 and 1.0)')}, {text: 'Consolidate By', menu: [ {text: 'Sum', handler: applyFuncToEach('consolidateBy', '"sum"')}, {text: 'Max', handler: applyFuncToEach('consolidateBy', '"max"')}, {text: 'Min', handler: applyFuncToEach('consolidateBy', '"min"')} ]}, {text: 'Draw non-zero As Infinite', handler: applyFuncToEach('drawAsInfinite')}, {text: 'Line Width', handler: applyFuncToEachWithInput('lineWidth', 'Please enter a line width for this graph target')}, {text: 'Dashed Line', handler: applyFuncToEach('dashed')}, {text: 'Keep Last Value', handler: applyFuncToEachWithInput('keepLastValue', 'Please enter the maximum number of "None" datapoints to overwrite, or leave empty for no limit. (default: empty)', {allowBlank: true})}, {text: 'Changed', handler: applyFuncToEach('changed')}, {text: 'Transform Nulls', handler: applyFuncToEachWithInput('transformNull', 'Please enter the value to transform null values to')}, {text: 'Count non-nulls', handler: applyFuncToAll('isNonNull')}, {text: 'Substring', handler: applyFuncToEachWithInput('substr', 'Enter a starting position')}, {text: 'Group', handler: applyFuncToAll('group')}, {text: 'Area Between', handler: applyFuncToEach('areaBetween')}, // {text: 'GroupByNode', handler: applyFuncToEachWithInput('group')}, // requires 2 parameters // {text: 'Add Threshold Line', handler: applyFuncToEachWithInput('threshold', 'Enter a threshold value')}, {text: 'Draw Stacked', handler: applyFuncToEach('stacked')}, {text: 'Draw in Second Y Axis', handler: applyFuncToEach('secondYAxis')}, {text: 'Aggregate Line', menu: [ {text: 'Avg', handler: applyFuncToEach('aggregateLine', '"avg"')}, {text: 'Max', handler: applyFuncToEach('aggregateLine', '"max"')}, {text: 'Min', handler: applyFuncToEach('aggregateLine', '"min"')} ] } ] } ]; } /* Auto-Refresh feature */ function toggleAutoRefresh(button, pressed) { //A closure makes this really simple var doRefresh = function () { Composer.updateImage(); //var interval = Math.min.apply(null, [context['interval'] for each (context in MetricContexts)] || [0]) || 60; var interval = GraphiteConfig.refreshInterval; button.timer = setTimeout(doRefresh, interval * 1000) } if (button.timer) { // AutoRefresh is on if (!pressed) { // The button was untoggled, turn off AutoRefresh clearTimeout(button.timer); button.timer = null; } } else { // AutoRefresh is off if (pressed) { // The button was toggled, turn on AutoRefresh doRefresh(); } } } /* Display Options Menu */ function createOptionsMenu() { var yAxisUnitMenu = new Ext.menu.Menu({ items: [ menuRadioItem('yUnit', 'Standard', 'yUnitSystem', 'si'), menuRadioItem('yUnit', 'Binary', 'yUnitSystem', 'binary'), menuRadioItem('yUnit', 'Seconds', 'yUnitSystem', 'sec'), menuRadioItem('yUnit', 'Milliseconds', 'yUnitSystem', 'msec'), menuRadioItem('yUnit', 'None', 'yUnitSystem', 'none') ] }); var yAxisSideMenu = new Ext.menu.Menu({ items: [ menuRadioItem('yAxis', 'Left', 'yAxisSide', 'left'), menuRadioItem('yAxis', 'Right', 'yAxisSide', 'right') ] }); var yAxisLeftMenu = new Ext.menu.Menu({ items: [ menuInputItem('Left Y Label', 'vtitle', 'Left Y Label', /^$/), menuInputItem('Left Y Minimum', 'yMinLeft'), menuInputItem('Left Y Maximum', 'yMaxLeft'), menuInputItem('Left Y Limit', 'yLimitLeft'), menuInputItem('Left Y Step', 'yStepLeft'), menuInputItem('Left Line Width', 'leftWidth'), menuInputItem('Left Line Color', 'leftColor'), menuInputItem('Left Line Dashed (length, in px)', 'leftDashed') ] }); var yAxisRightMenu = new Ext.menu.Menu({ items: [ menuInputItem('Right Y Label', 'vtitleRight', 'Right Y Label', /^$/), menuInputItem('Right Y Minimum', 'yMinRight'), menuInputItem('Right Y Maximum', 'yMaxRight'), menuInputItem('Right Y Limit', 'yLimitRight'), menuInputItem('Right Y Step', 'yStepRight'), menuInputItem('Right Line Width', 'rightWidth'), menuInputItem('Right Line Color', 'rightColor'), menuInputItem('Right Line Dashed (length, in px)', 'rightDashed') ] }); var SecondYAxisMenu = new Ext.menu.Menu({ items: [ {text: 'Left Y-Axis', menu: yAxisLeftMenu}, {text: 'Right Y-Axis', menu: yAxisRightMenu} ] }); var yAxisMenu = new Ext.menu.Menu({ items: [ menuInputItem('Label', 'vtitle', 'Y-Axis Label', /^$/), menuInputItem('Minimum', 'yMin'), menuInputItem('Maximum', 'yMax'), menuInputItem('Minor Lines', 'minorY', 'Enter the number of minor lines to draw', /^[a-zA-Z]/), menuInputItem('Logarithmic Scale', 'logBase', 'Enter the logarithmic base to use (ie. 10, e, etc...)'), menuInputItem('Step', 'yStep', 'Enter the Y-axis step to use (e.g. 0.2)'), menuInputItem('Divisors', 'yDivisors', 'Enter the target number of intermediate Y-axis values (e.g. 4,5,6)', /^[a-zA-Z]/), {text: 'Unit', menu: yAxisUnitMenu}, {text: 'Side', menu: yAxisSideMenu}, {text: 'Dual Y-Axis Options', menu: SecondYAxisMenu}, menuHelpItem('Dual Y-Axis Help', 'To select metrics to associate with the second (right-side) y-axis, go into the Graph Data dialog box, highlight a metric, click Apply Functions, Special, Second Y Axis.') ] }); var xAxisMenu = new Ext.menu.Menu({ items: [ menuInputItem('Time Format', 'xFormat', 'Enter the time format (see Python\'s datetime.strftime())', /^$/), menuInputItem('Timezone', 'tz', 'Enter the timezone to display (e.g. UTC or America/Chicago)', /^$/), menuInputItem('Point-width Consolidation Threshold', 'minXStep', 'Enter the closest number of pixels between points before consolidation') ] }); var areaMenu = new Ext.menu.Menu({ items: [ menuRadioItem('area', 'None', 'areaMode', ''), menuRadioItem('area', 'First Only', 'areaMode', 'first'), menuRadioItem('area', 'Stacked', 'areaMode', 'stacked'), menuRadioItem('area', 'All', 'areaMode', 'all') ] }); var lineMenu = new Ext.menu.Menu({ items: [ menuRadioItem('line', 'Slope Line (default)', 'lineMode', ''), menuRadioItem('line', 'Staircase Line', 'lineMode', 'staircase'), menuRadioItem('line', 'Connected Line', 'lineMode', 'connected'), menuInputItem('Connected Line Limit', 'connectedLimit', 'The number of consecutive None values to jump over when in connected line mode. (default: no limit, leave empty)'), menuCheckItem('Draw Null as Zero', 'drawNullAsZero') ] }); var fontFacesMenu = new Ext.menu.Menu({ items: [ menuRadioItem('fontFace', 'Sans', 'fontName', 'Sans'), menuRadioItem('fontFace', 'Times', 'fontName', 'Times'), menuRadioItem('fontFace', 'Courier', 'fontName', 'Courier'), menuRadioItem('fontFace', 'Helvetica', 'fontName', 'Helvetica') ] }); var fontMenu = new Ext.menu.Menu({ items: [ {text: 'Face', menu: fontFacesMenu}, { text: 'Style', menu: { items: [ menuCheckItem('Italics', 'fontItalic'), menuCheckItem('Bold', 'fontBold') ] } }, menuInputItem('Size', 'fontSize', 'Enter the font size in pt'), {text: 'Color', menu: createColorMenu('fgcolor')} ] }); var displayMenu = new Ext.menu.Menu({ items: [ {text: 'Font', menu: fontMenu}, { text: 'Color', menu: { items: [ menuInputItem('Line Colors', 'colorList', 'Enter an ordered list of comma-separated colors (name or hex values)', /^$/), {text: 'Background', menu: createColorMenu('bgcolor')}, {text: 'Major Grid Line', menu: createColorMenu('majorGridLineColor')}, {text: 'Minor Grid Line', menu: createColorMenu('minorGridLineColor')}, menuInputItem('Filled Area Alpha Value', 'areaAlpha', 'Enter the alpha value (between 0.0 and 1.0)') ] } }, { text: 'Graph Legend', menu: { items: [ menuRadioItem('legend', 'Hide If Too Many', 'hideLegend'), menuRadioItem('legend', 'Always Hide', 'hideLegend', 'true'), menuRadioItem('legend', 'Never Hide', 'hideLegend', 'false'), menuCheckItem('Hide Duplicate Items', 'uniqueLegend'), menuCheckItem('Hide Null Series', 'hideNullFromLegend') ] } }, menuInputItem('Line Thickness', 'lineWidth', 'Enter the line thickness in pixels'), menuInputItem('Margin', 'margin', 'Enter the margin width in pixels'), menuCheckItem('Graph Only', 'graphOnly'), menuCheckItem('Hide Axes', 'hideAxes'), menuCheckItem('Hide Y-Axis', 'hideYAxis'), menuCheckItem('Hide Grid', 'hideGrid'), menuInputItem('Apply Template', 'template', 'Enter the name of a template defined in graphTemplates.conf', /^$/) ] }); return { xtype: 'menu', items: [ menuInputItem('Graph Title', 'title', 'Graph Title', /^$/), {text: 'Display', menu: displayMenu}, {text: 'Line Mode', menu: lineMenu}, {text: 'Area Mode', menu: areaMenu}, {text: 'X-Axis', menu: xAxisMenu}, {text: 'Y-Axis', menu: yAxisMenu} ] }; } /* Graph Options API */ function updateGraph() { return Composer.updateImage(); } function getParam(param) { return Composer.url.getParam(param); } function setParam(param, value) { return Composer.url.setParam(param, value); } function removeParam(param) { return Composer.url.removeParam(param); } /* End of Graph Options API */ function createColorMenu(param) { var colorPicker = new Ext.menu.ColorMenu({hideOnClick: false}); colorPicker.on('select', function (palette, color) { setParam(param, color); updateGraph(); } ); return colorPicker; } function menuInputItem(name, param, question, regexp) { return new Ext.menu.Item({text: name, handler: paramPrompt(question || name, param, regexp)}); } function menuHelpItem(name, message) { return new Ext.menu.Item({text: name, handler: helpMessage(name, message)}); } function paramPrompt(question, param, regexp) { if(regexp == null) { regexp = /[^A-Za-z0-9_.\-]/; } return function (menuItem, e) { Ext.MessageBox.prompt( 'Input Required', question, function (button, value) { if (value.search(regexp) != -1) { Ext.Msg.alert('Input can only contain letters, numbers, underscores, or periods.'); return; } if (value.charAt(value.length - 1) == '.') { Ext.Msg.alert('Input cannot end in a period.'); return; } setParam(param, value); updateGraph(); }, this, //scope false, //multiline getParam(param) || '' //default value ); }; } function helpMessage(myTitle, myMessage) { return function (menuItem, e) { Ext.MessageBox.show( {title: myTitle, msg: myMessage, button: Ext.MessageBox.OK } ); }; } var checkItems = []; function menuCheckItem(name, param, paramValue) { var checkItem = new Ext.menu.CheckItem({text: name, param: param, hideOnClick: false}); checkItems.push(checkItem); //keep a list of each check item we create so we can update them later checkItem.on('checkchange', function (item, checked) { if (paramValue) { // Set param to a specific value if (checked) { setParam(param, paramValue); } else { // Remove the param if we're being unchecked removeParam(param); } } else { // Set the param to true/false setParam(param, checked.toString()); } updateGraph(); } ); return checkItem; } function menuRadioItem(groupName, name, param, paramValue ) { var selectItem = new Ext.menu.CheckItem({text: name, param: param, hideOnClick: false, group: groupName, checked: (paramValue ? false : true)}); selectItem.on('checkchange', function( item, clicked ) { if( paramValue ) { setParam(param, paramValue); } else { removeParam(param); } updateGraph(); } ); return selectItem; } function updateCheckItems() { Ext.each(checkItems, function (item) { var param = item.initialConfig.param; item.setChecked(getParam(param) ? true : false, true); } ); } graphite-web-1.1.4/webapp/content/js/composer.js0000644000000000000000000002025713343334667021604 0ustar rootroot00000000000000/* Copyright 2008 Orbitz WorldWide * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // From Ext library /*global Ext*/ // Defined in composer_widgets.js /*global createComposerWindow*/ // Defined in composer.html /*global addTarget getTargetRecord TargetStore*/ var RENDER_BASE_URL = window.location.protocol + '//' + window.location.host + document.body.dataset.baseUrl + 'render/?'; /* GraphiteComposer encapsulates a set of Ext UI Panels, * as well as a ParameterizedURL for the displayed graph. */ function GraphiteComposer () { this.url = new ParameterizedURL(RENDER_BASE_URL); this.state = {}; // For storing non-querystring state information this.window = createComposerWindow(this); } GraphiteComposer.prototype = { toggleTargetWithoutUpdate: function(target) { this.toggleTarget(target, false); }, toggleTarget: function (target, updateImage) { /* Add the given target to the graph if it does not exist, * otherwise remove it. * Optionally reload the image. (default = do update) */ var record = getTargetRecord(target); if (record) { TargetStore.remove(record); } else { // toggle it on addTarget(target); } this.syncTargetList(); // If the updateImage parameter is unspecified or true, reload the image. if(undefined == updateImage || true == updateImage) { this.updateImage(); } }, loadMyGraph: function (name, url) { this.state.myGraphName = name; this.loadURL(url); }, loadURL: function (url) { /* Take the targets out of the URL and add them to the store properly */ TargetStore.removeAll(); var tempUrl = new ParameterizedURL(RENDER_BASE_URL); tempUrl.copyQueryStringFromURL(url); var targets = tempUrl.getParamList('target'); tempUrl.removeParam('target'); this.url.setQueryString(tempUrl.queryString); /* Use ...WithoutUpdate here to avoid loading the image too soon. If * there are lots of targets, each modification would cause an extra * render. */ Ext.each(targets, this.toggleTargetWithoutUpdate, this); // Fit the image into the window this.url.setParam('width', this.window.getInnerWidth()); this.url.setParam('height', this.window.getInnerHeight()); // And don't forget to update the time display widget var from = this.url.getParam('from'); var until = this.url.getParam('until'); var timeInfo = {}; if (!from) { // No time interval specified, use the default timeInfo.mode = 'recent'; timeInfo.quantity = '24'; timeInfo.units = 'hours'; } else { var match = from.match(/^-(\d+)(\w+)/); if (match) { // Relative time timeInfo.mode = 'recent'; timeInfo.quantity = match[1]; timeInfo.units = match[2]; } else { // Absolute time timeInfo.mode = 'date-range'; timeInfo.startDate = this.parseDate(from); timeInfo.endDate = this.parseDate(until); } } this.window.updateTimeDisplay(timeInfo); this.window.updateUI(); this.updateImage(true); }, syncTargetList: function () { this.url.removeParam('target'); TargetStore.each(function (record) { this.url.addParam('target', record.data.value); }, this); }, updateImage: function (urlLoad) { /* Set the image's url to reflect this.url's current params */ var img = this.window.getImage(); if (img) { var now = new Date(); var unixTime = now.valueOf() / 1000; this.url.setParam('_salt', unixTime.toString() ); img.src = this.url.getURL(); if (this.topWindow && !urlLoad) { this.url.removeParam('_salt'); this.topWindow.history.pushState('', '', '?' + this.url.queryString); } } }, enableHistory: function (topWindow) { if (topWindow.history && topWindow.history.pushState) { this.topWindow = topWindow; var that = this; topWindow.onpopstate = function() { that.loadURL(topWindow.location.href); }; } }, parseDate: function(dateString) { // Format is HH:MM_YYYYMMDD var hour = dateString.substr(0,2); var minute = dateString.substr(3,2); var year = dateString.substr(6,4); var month = dateString.substr(10,2); var day = dateString.substr(12,14); month = parseInt(month) - 1; // Date assumes months are zero-indexed return new Date(year, month, day, hour, minute, 0, 0); } }; /* ParameterizedURL encapsulates a URL and * provides methods to access and modify the * query string parameters in a structured fashion. * This code should not be specific to Graphite or * the Composer in any way. */ function ParameterizedURL (baseURL) { this.baseURL = baseURL ? baseURL : ''; this.params = {}; this.queryString = ''; } ParameterizedURL.prototype = { /* Parameter access methods */ getParam: function (key) { /* Return the first value of this parameter */ var values = this.params[key]; return values ? values[0] : null; }, getParamList: function (key) { /* Return an array of all values for this parameter */ var values = this.params[key]; return values ? values : new Array(); }, getURL: function () { /* Return the current URL */ return this.baseURL + this.queryString; }, /* Parameter modification methods */ addParam: function (key, value) { /* Add a parameter value */ var values = this.getParamList(key); values.push(value); this.params[key] = values; this.syncQueryString(); }, removeParam: function (key, value) { /* Remove one or all values for a given parameter */ if (value == null) { //Remove all values this.params[key] = null; // in case it didn't exist, delete won't break now delete this.params[key]; } else { //Remove a specific value var newValues = this.getParamList(key); newValues.remove(value); if (newValues.length) { this.params[key] = newValues; } else { this.params[key] = null; delete this.params[key]; } } this.syncQueryString(); }, setParam: function (key, value) { /* Give the param only the given value */ this.params[key] = [value]; this.syncQueryString(); }, setParamList: function (key, values) { /* Give the param a given list of values */ this.params[key] = values; this.syncQueryString(); }, setParamHash: function (params) { /* Use the given params Hash (and update this.queryString to match) */ this.params = params; this.syncQueryString(); }, syncParams: function () { /* Set the value of this.params to reflect the parameters in this.queryString * Call this whenever you modify this.queryString */ var params = Ext.urlDecode( this.queryString ); Ext.iterate(params, function(key, value) { //We want all of our param values to be arrays if ( Ext.isString(value) ) { params[key] = [value]; } if (value == 'undefined' || value == undefined) { params[key] = null; delete params[key]; } }); this.params = params; }, setQueryString: function (qs) { /* Use the given query string (and update this.params to match) */ this.queryString = qs this.syncParams(); this.syncQueryString(); }, syncQueryString: function () { /* Set the value of this.queryString to reflect the parameters in this.params * Call this whenever you modify this.params */ this.queryString = Ext.urlEncode(this.params).replace(/#/,'%23'); }, copyQueryStringFromURL: function (url) { /* Make this object reflect the parameters of the given url */ var i = url.indexOf('?'); if (i == -1) { // No query string this.setParamHash({}); return; } var queryString = url.substr(i+1); this.setQueryString(queryString); } }; graphite-web-1.1.4/webapp/content/js/completer.js0000644000000000000000000000302313343334667021737 0ustar rootroot00000000000000// From Ext library /*global Ext*/ var MetricCompleter; MetricCompleter = Ext.extend(Ext.form.ComboBox, { displayField: 'path', listEmptyText: 'No matching metrics', mode: 'remote', hideTrigger: true, queryDelay: 100, queryParam: 'query', typeAhead: false, minChars: 1, initComponent: function () { var _this = this; var store = new Ext.data.JsonStore({ url: document.body.dataset.baseUrl + 'metrics/find/', root: 'metrics', fields: ['path', 'name'], baseParams: {format: 'completer'} }); var config = {store: store}; Ext.apply(this, config); Ext.apply(this.initialConfig, config); MetricCompleter.superclass.initComponent.call(this); this.addListener('beforequery', this.prepareQuery.createDelegate(this)); this.addListener('specialkey', this.onSpecialKey.createDelegate(this)); this.addListener('afterrender', function () { _this.getEl().addListener('specialkey', function (el, e) { _this.onSpecialKey(_this.getEl(), e); } ); } ); }, prepareQuery: function (queryEvent) { if (queryEvent.query.substr(-1) != '*') { queryEvent.query += '*'; } }, onSpecialKey: function (field, e) { if (e.getKey() == e.TAB) { // This was a pain in the ass to actually get it working right field.getEl().blur(); field.getEl().focus(50); field.doQuery( field.getValue() ); e.stopEvent(); return false; } } }); Ext.reg('metriccompleter', MetricCompleter); graphite-web-1.1.4/webapp/content/js/ext/0000755000000000000000000000000013343335472020204 5ustar rootroot00000000000000graphite-web-1.1.4/webapp/content/js/ext/ext-all.js0000644000000000000000000257127313343334667022136 0ustar rootroot00000000000000/* * Ext JS Library 3.4.0 * Copyright(c) 2006-2011 Sencha Inc. * licensing@sencha.com * http://www.sencha.com/license */ (function(){var h=Ext.util,j=Ext.each,g=true,i=false;h.Observable=function(){var k=this,l=k.events;if(k.listeners){k.on(k.listeners);delete k.listeners}k.events=l||{}};h.Observable.prototype={filterOptRe:/^(?:scope|delay|buffer|single)$/,fireEvent:function(){var k=Array.prototype.slice.call(arguments,0),m=k[0].toLowerCase(),n=this,l=g,p=n.events[m],s,o,r;if(n.eventsSuspended===g){if(o=n.eventQueue){o.push(k)}}else{if(typeof p=="object"){if(p.bubble){if(p.fire.apply(p,k.slice(1))===i){return i}r=n.getBubbleTarget&&n.getBubbleTarget();if(r&&r.enableBubble){s=r.events[m];if(!s||typeof s!="object"||!s.bubble){r.enableBubble(m)}return r.fireEvent.apply(r,k)}}else{k.shift();l=p.fire.apply(p,k)}}}return l},addListener:function(k,m,l,r){var n=this,q,s,p;if(typeof k=="object"){r=k;for(q in r){s=r[q];if(!n.filterOptRe.test(q)){n.addListener(q,s.fn||s,s.scope||r.scope,s.fn?s:r)}}}else{k=k.toLowerCase();p=n.events[k]||g;if(typeof p=="boolean"){n.events[k]=p=new h.Event(n,k)}p.addListener(m,l,typeof r=="object"?r:{})}},removeListener:function(k,m,l){var n=this.events[k.toLowerCase()];if(typeof n=="object"){n.removeListener(m,l)}},purgeListeners:function(){var m=this.events,k,l;for(l in m){k=m[l];if(typeof k=="object"){k.clearListeners()}}},addEvents:function(n){var m=this;m.events=m.events||{};if(typeof n=="string"){var k=arguments,l=k.length;while(l--){m.events[k[l]]=m.events[k[l]]||g}}else{Ext.applyIf(m.events,n)}},hasListener:function(k){var l=this.events[k.toLowerCase()];return typeof l=="object"&&l.listeners.length>0},suspendEvents:function(k){this.eventsSuspended=g;if(k&&!this.eventQueue){this.eventQueue=[]}},resumeEvents:function(){var k=this,l=k.eventQueue||[];k.eventsSuspended=i;delete k.eventQueue;j(l,function(m){k.fireEvent.apply(k,m)})}};var d=h.Observable.prototype;d.on=d.addListener;d.un=d.removeListener;h.Observable.releaseCapture=function(k){k.fireEvent=d.fireEvent};function e(l,m,k){return function(){if(m.target==arguments[0]){l.apply(k,Array.prototype.slice.call(arguments,0))}}}function b(n,p,k,m){k.task=new h.DelayedTask();return function(){k.task.delay(p.buffer,n,m,Array.prototype.slice.call(arguments,0))}}function c(m,n,l,k){return function(){n.removeListener(l,k);return m.apply(k,arguments)}}function a(n,p,k,m){return function(){var l=new h.DelayedTask(),o=Array.prototype.slice.call(arguments,0);if(!k.tasks){k.tasks=[]}k.tasks.push(l);l.delay(p.delay||10,function(){k.tasks.remove(l);n.apply(m,o)},m)}}h.Event=function(l,k){this.name=k;this.obj=l;this.listeners=[]};h.Event.prototype={addListener:function(o,n,m){var p=this,k;n=n||p.obj;if(!p.isListening(o,n)){k=p.createListener(o,n,m);if(p.firing){p.listeners=p.listeners.slice(0)}p.listeners.push(k)}},createListener:function(p,n,q){q=q||{};n=n||this.obj;var k={fn:p,scope:n,options:q},m=p;if(q.target){m=e(m,q,n)}if(q.delay){m=a(m,q,k,n)}if(q.single){m=c(m,this,p,n)}if(q.buffer){m=b(m,q,k,n)}k.fireFn=m;return k},findListener:function(o,n){var p=this.listeners,m=p.length,k;n=n||this.obj;while(m--){k=p[m];if(k){if(k.fn==o&&k.scope==n){return m}}}return -1},isListening:function(l,k){return this.findListener(l,k)!=-1},removeListener:function(r,q){var p,m,n,s=this,o=i;if((p=s.findListener(r,q))!=-1){if(s.firing){s.listeners=s.listeners.slice(0)}m=s.listeners[p];if(m.task){m.task.cancel();delete m.task}n=m.tasks&&m.tasks.length;if(n){while(n--){m.tasks[n].cancel()}delete m.tasks}s.listeners.splice(p,1);o=g}return o},clearListeners:function(){var n=this,k=n.listeners,m=k.length;while(m--){n.removeListener(k[m].fn,k[m].scope)}},fire:function(){var q=this,p=q.listeners,k=p.length,o=0,m;if(k>0){q.firing=g;var n=Array.prototype.slice.call(arguments,0);for(;o",i="",b=a+"",j=""+i,l=b+"",w=""+j;function h(B,D,C,E,A,y){var z=r.insertHtml(E,Ext.getDom(B),u(D));return C?Ext.get(z,true):z}function u(D){var z="",y,C,B,E;if(typeof D=="string"){z=D}else{if(Ext.isArray(D)){for(var A=0;A"}}}return z}function g(F,C,B,D){x.innerHTML=[C,B,D].join("");var y=-1,A=x,z;while(++y "'+D+'"'},insertBefore:function(y,A,z){return h(y,A,z,c)},insertAfter:function(y,A,z){return h(y,A,z,p,"nextSibling")},insertFirst:function(y,A,z){return h(y,A,z,n,"firstChild")},append:function(y,A,z){return h(y,A,z,q,"",true)},overwrite:function(y,A,z){y=Ext.getDom(y);y.innerHTML=u(A);return z?Ext.get(y.firstChild):y.firstChild},createHtml:u};return r}();Ext.Template=function(h){var j=this,c=arguments,e=[],d;if(Ext.isArray(h)){h=h.join("")}else{if(c.length>1){for(var g=0,b=c.length;g+~]\s?|\s|$)/,tagTokenRe=/^(#)?([\w\-\*]+)/,nthRe=/(\d*)n\+?(\d*)/,nthRe2=/\D/,isIE=window.ActiveXObject?true:false,key=30803;eval("var batch = 30803;");function child(parent,index){var i=0,n=parent.firstChild;while(n){if(n.nodeType==1){if(++i==index){return n}}n=n.nextSibling}return null}function next(n){while((n=n.nextSibling)&&n.nodeType!=1){}return n}function prev(n){while((n=n.previousSibling)&&n.nodeType!=1){}return n}function children(parent){var n=parent.firstChild,nodeIndex=-1,nextNode;while(n){nextNode=n.nextSibling;if(n.nodeType==3&&!nonSpace.test(n.nodeValue)){parent.removeChild(n)}else{n.nodeIndex=++nodeIndex}n=nextNode}return this}function byClassName(nodeSet,cls){if(!cls){return nodeSet}var result=[],ri=-1;for(var i=0,ci;ci=nodeSet[i];i++){if((" "+ci.className+" ").indexOf(cls)!=-1){result[++ri]=ci}}return result}function attrValue(n,attr){if(!n.tagName&&typeof n.length!="undefined"){n=n[0]}if(!n){return null}if(attr=="for"){return n.htmlFor}if(attr=="class"||attr=="className"){return n.className}return n.getAttribute(attr)||n[attr]}function getNodes(ns,mode,tagName){var result=[],ri=-1,cs;if(!ns){return result}tagName=tagName||"*";if(typeof ns.getElementsByTagName!="undefined"){ns=[ns]}if(!mode){for(var i=0,ni;ni=ns[i];i++){cs=ni.getElementsByTagName(tagName);for(var j=0,ci;ci=cs[j];j++){result[++ri]=ci}}}else{if(mode=="/"||mode==">"){var utag=tagName.toUpperCase();for(var i=0,ni,cn;ni=ns[i];i++){cn=ni.childNodes;for(var j=0,cj;cj=cn[j];j++){if(cj.nodeName==utag||cj.nodeName==tagName||tagName=="*"){result[++ri]=cj}}}}else{if(mode=="+"){var utag=tagName.toUpperCase();for(var i=0,n;n=ns[i];i++){while((n=n.nextSibling)&&n.nodeType!=1){}if(n&&(n.nodeName==utag||n.nodeName==tagName||tagName=="*")){result[++ri]=n}}}else{if(mode=="~"){var utag=tagName.toUpperCase();for(var i=0,n;n=ns[i];i++){while((n=n.nextSibling)){if(n.nodeName==utag||n.nodeName==tagName||tagName=="*"){result[++ri]=n}}}}}}}return result}function concat(a,b){if(b.slice){return a.concat(b)}for(var i=0,l=b.length;i1){return nodup(results)}return results},isXml:function(el){var docEl=(el?el.ownerDocument||el:0).documentElement;return docEl?docEl.nodeName!=="HTML":false},select:document.querySelectorAll?function(path,root,type){root=root||document;if(!Ext.DomQuery.isXml(root)){try{var cs=root.querySelectorAll(path);return Ext.toArray(cs)}catch(ex){}}return Ext.DomQuery.jsSelect.call(this,path,root,type)}:function(path,root,type){return Ext.DomQuery.jsSelect.call(this,path,root,type)},selectNode:function(path,root){return Ext.DomQuery.select(path,root)[0]},selectValue:function(path,root,defaultValue){path=path.replace(trimRe,"");if(!valueCache[path]){valueCache[path]=Ext.DomQuery.compile(path,"select")}var n=valueCache[path](root),v;n=n[0]?n[0]:n;if(typeof n.normalize=="function"){n.normalize()}v=(n&&n.firstChild?n.firstChild.nodeValue:null);return((v===null||v===undefined||v==="")?defaultValue:v)},selectNumber:function(path,root,defaultValue){var v=Ext.DomQuery.selectValue(path,root,defaultValue||0);return parseFloat(v)},is:function(el,ss){if(typeof el=="string"){el=document.getElementById(el)}var isArray=Ext.isArray(el),result=Ext.DomQuery.filter(isArray?el:[el],ss);return isArray?(result.length==el.length):(result.length>0)},filter:function(els,ss,nonMatches){ss=ss.replace(trimRe,"");if(!simpleCache[ss]){simpleCache[ss]=Ext.DomQuery.compile(ss,"simple")}var result=simpleCache[ss](els);return nonMatches?quickDiff(result,els):result},matchers:[{re:/^\.([\w\-]+)/,select:'n = byClassName(n, " {1} ");'},{re:/^\:([\w\-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,select:'n = byPseudo(n, "{1}", "{2}");'},{re:/^(?:([\[\{])(?:@)?([\w\-]+)\s?(?:(=|.=)\s?(["']?)(.*?)\4)?[\]\}])/,select:'n = byAttribute(n, "{2}", "{5}", "{3}", "{1}");'},{re:/^#([\w\-]+)/,select:'n = byId(n, "{1}");'},{re:/^@([\w\-]+)/,select:'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'}],operators:{"=":function(a,v){return a==v},"!=":function(a,v){return a!=v},"^=":function(a,v){return a&&a.substr(0,v.length)==v},"$=":function(a,v){return a&&a.substr(a.length-v.length)==v},"*=":function(a,v){return a&&a.indexOf(v)!==-1},"%=":function(a,v){return(a%v)==0},"|=":function(a,v){return a&&(a==v||a.substr(0,v.length+1)==v+"-")},"~=":function(a,v){return a&&(" "+a+" ").indexOf(" "+v+" ")!=-1}},pseudos:{"first-child":function(c){var r=[],ri=-1,n;for(var i=0,ci;ci=n=c[i];i++){while((n=n.previousSibling)&&n.nodeType!=1){}if(!n){r[++ri]=ci}}return r},"last-child":function(c){var r=[],ri=-1,n;for(var i=0,ci;ci=n=c[i];i++){while((n=n.nextSibling)&&n.nodeType!=1){}if(!n){r[++ri]=ci}}return r},"nth-child":function(c,a){var r=[],ri=-1,m=nthRe.exec(a=="even"&&"2n"||a=="odd"&&"2n+1"||!nthRe2.test(a)&&"n+"+a||a),f=(m[1]||1)-0,l=m[2]-0;for(var i=0,n;n=c[i];i++){var pn=n.parentNode;if(batch!=pn._batch){var j=0;for(var cn=pn.firstChild;cn;cn=cn.nextSibling){if(cn.nodeType==1){cn.nodeIndex=++j}}pn._batch=batch}if(f==1){if(l==0||n.nodeIndex==l){r[++ri]=n}}else{if((n.nodeIndex+l)%f==0){r[++ri]=n}}}return r},"only-child":function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(!prev(ci)&&!next(ci)){r[++ri]=ci}}return r},empty:function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var cns=ci.childNodes,j=0,cn,empty=true;while(cn=cns[j]){++j;if(cn.nodeType==1||cn.nodeType==3){empty=false;break}}if(empty){r[++ri]=ci}}return r},contains:function(c,v){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if((ci.textContent||ci.innerText||"").indexOf(v)!=-1){r[++ri]=ci}}return r},nodeValue:function(c,v){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(ci.firstChild&&ci.firstChild.nodeValue==v){r[++ri]=ci}}return r},checked:function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(ci.checked==true){r[++ri]=ci}}return r},not:function(c,ss){return Ext.DomQuery.filter(c,ss,true)},any:function(c,selectors){var ss=selectors.split("|"),r=[],ri=-1,s;for(var i=0,ci;ci=c[i];i++){for(var j=0;s=ss[j];j++){if(Ext.DomQuery.is(ci,s)){r[++ri]=ci;break}}}return r},odd:function(c){return this["nth-child"](c,"odd")},even:function(c){return this["nth-child"](c,"even")},nth:function(c,a){return c[a-1]||[]},first:function(c){return c[0]||[]},last:function(c){return c[c.length-1]||[]},has:function(c,ss){var s=Ext.DomQuery.select,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(s(ss,ci).length>0){r[++ri]=ci}}return r},next:function(c,ss){var is=Ext.DomQuery.is,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var n=next(ci);if(n&&is(n,ss)){r[++ri]=ci}}return r},prev:function(c,ss){var is=Ext.DomQuery.is,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var n=prev(ci);if(n&&is(n,ss)){r[++ri]=ci}}return r}}}}();Ext.query=Ext.DomQuery.select;Ext.util.DelayedTask=function(d,c,a){var e=this,g,b=function(){clearInterval(g);g=null;d.apply(c,a||[])};e.delay=function(i,k,j,h){e.cancel();d=k||d;c=j||c;a=h||a;g=setInterval(b,i)};e.cancel=function(){if(g){clearInterval(g);g=null}}};(function(){var h=document;Ext.Element=function(l,m){var n=typeof l=="string"?h.getElementById(l):l,o;if(!n){return null}o=n.id;if(!m&&o&&Ext.elCache[o]){return Ext.elCache[o].el}this.dom=n;this.id=o||Ext.id(n)};var d=Ext.DomHelper,e=Ext.Element,a=Ext.elCache;e.prototype={set:function(q,m){var n=this.dom,l,p,m=(m!==false)&&!!n.setAttribute;for(l in q){if(q.hasOwnProperty(l)){p=q[l];if(l=="style"){d.applyStyles(n,p)}else{if(l=="cls"){n.className=p}else{if(m){n.setAttribute(l,p)}else{n[l]=p}}}}}return this},defaultUnit:"px",is:function(l){return Ext.DomQuery.is(this.dom,l)},focus:function(o,n){var l=this,n=n||l.dom;try{if(Number(o)){l.focus.defer(o,null,[null,n])}else{n.focus()}}catch(m){}return l},blur:function(){try{this.dom.blur()}catch(l){}return this},getValue:function(l){var m=this.dom.value;return l?parseInt(m,10):m},addListener:function(l,o,n,m){Ext.EventManager.on(this.dom,l,o,n||this,m);return this},removeListener:function(l,n,m){Ext.EventManager.removeListener(this.dom,l,n,m||this);return this},removeAllListeners:function(){Ext.EventManager.removeAll(this.dom);return this},purgeAllListeners:function(){Ext.EventManager.purgeElement(this,true);return this},addUnits:function(l){if(l===""||l=="auto"||l===undefined){l=l||""}else{if(!isNaN(l)||!i.test(l)){l=l+(this.defaultUnit||"px")}}return l},load:function(m,n,l){Ext.Ajax.request(Ext.apply({params:n,url:m.url||m,callback:l,el:this.dom,indicatorText:m.indicatorText||""},Ext.isObject(m)?m:{}));return this},isBorderBox:function(){return Ext.isBorderBox||Ext.isForcedBorderBox||g[(this.dom.tagName||"").toLowerCase()]},remove:function(){var l=this,m=l.dom;if(m){delete l.dom;Ext.removeNode(m)}},hover:function(m,l,o,n){var p=this;p.on("mouseenter",m,o||p.dom,n);p.on("mouseleave",l,o||p.dom,n);return p},contains:function(l){return !l?false:Ext.lib.Dom.isAncestor(this.dom,l.dom?l.dom:l)},getAttributeNS:function(m,l){return this.getAttribute(l,m)},getAttribute:(function(){var p=document.createElement("table"),o=false,m="getAttribute" in p,l=/undefined|unknown/;if(m){try{p.getAttribute("ext:qtip")}catch(n){o=true}return function(q,s){var r=this.dom,t;if(r.getAttributeNS){t=r.getAttributeNS(s,q)||null}if(t==null){if(s){if(o&&r.tagName.toUpperCase()=="TABLE"){try{t=r.getAttribute(s+":"+q)}catch(u){t=""}}else{t=r.getAttribute(s+":"+q)}}else{t=r.getAttribute(q)||r[q]}}return t||""}}else{return function(q,s){var r=this.om,u,t;if(s){t=r[s+":"+q];u=l.test(typeof t)?undefined:t}else{u=r[q]}return u||""}}p=null})(),update:function(l){if(this.dom){this.dom.innerHTML=l}return this}};var k=e.prototype;e.addMethods=function(l){Ext.apply(k,l)};k.on=k.addListener;k.un=k.removeListener;k.autoBoxAdjust=true;var i=/\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,c;e.get=function(m){var l,p,o;if(!m){return null}if(typeof m=="string"){if(!(p=h.getElementById(m))){return null}if(a[m]&&a[m].el){l=a[m].el;l.dom=p}else{l=e.addToCache(new e(p))}return l}else{if(m.tagName){if(!(o=m.id)){o=Ext.id(m)}if(a[o]&&a[o].el){l=a[o].el;l.dom=m}else{l=e.addToCache(new e(m))}return l}else{if(m instanceof e){if(m!=c){if(Ext.isIE&&(m.id==undefined||m.id=="")){m.dom=m.dom}else{m.dom=h.getElementById(m.id)||m.dom}}return m}else{if(m.isComposite){return m}else{if(Ext.isArray(m)){return e.select(m)}else{if(m==h){if(!c){var n=function(){};n.prototype=e.prototype;c=new n();c.dom=h}return c}}}}}}return null};e.addToCache=function(l,m){m=m||l.id;a[m]={el:l,data:{},events:{}};return l};e.data=function(m,l,n){m=e.get(m);if(!m){return null}var o=a[m.id].data;if(arguments.length==2){return o[l]}else{return(o[l]=n)}};function j(){if(!Ext.enableGarbageCollector){clearInterval(e.collectorThreadId)}else{var l,n,q,p;for(l in a){p=a[l];if(p.skipGC){continue}n=p.el;q=n.dom;if(!q||!q.parentNode||(!q.offsetParent&&!h.getElementById(l))){if(Ext.enableListenerCollection){Ext.EventManager.removeAll(q)}delete a[l]}}if(Ext.isIE){var m={};for(l in a){m[l]=a[l]}a=Ext.elCache=m}}}e.collectorThreadId=setInterval(j,30000);var b=function(){};b.prototype=e.prototype;e.Flyweight=function(l){this.dom=l};e.Flyweight.prototype=new b();e.Flyweight.prototype.isFlyweight=true;e._flyweights={};e.fly=function(n,l){var m=null;l=l||"_global";if(n=Ext.getDom(n)){(e._flyweights[l]=e._flyweights[l]||new e.Flyweight()).dom=n;m=e._flyweights[l]}return m};Ext.get=e.get;Ext.fly=e.fly;var g=Ext.isStrict?{select:1}:{input:1,select:1,textarea:1};if(Ext.isIE||Ext.isGecko){g.button=1}})();Ext.Element.addMethods(function(){var d="parentNode",b="nextSibling",c="previousSibling",e=Ext.DomQuery,a=Ext.get;return{findParent:function(m,l,h){var j=this.dom,g=document.body,k=0,i;if(Ext.isGecko&&Object.prototype.toString.call(j)=="[object XULElement]"){return null}l=l||50;if(isNaN(l)){i=Ext.getDom(l);l=Number.MAX_VALUE}while(j&&j.nodeType==1&&k "+g,this.dom);return h?i:a(i)},parent:function(g,h){return this.matchNode(d,d,g,h)},next:function(g,h){return this.matchNode(b,b,g,h)},prev:function(g,h){return this.matchNode(c,c,g,h)},first:function(g,h){return this.matchNode(b,"firstChild",g,h)},last:function(g,h){return this.matchNode(c,"lastChild",g,h)},matchNode:function(h,k,g,i){var j=this.dom[k];while(j){if(j.nodeType==1&&(!g||e.is(j,g))){return !i?a(j):j}j=j[h]}return null}}}());Ext.Element.addMethods(function(){var c=Ext.getDom,a=Ext.get,b=Ext.DomHelper;return{appendChild:function(d){return a(d).appendTo(this)},appendTo:function(d){c(d).appendChild(this.dom);return this},insertBefore:function(d){(d=c(d)).parentNode.insertBefore(this.dom,d);return this},insertAfter:function(d){(d=c(d)).parentNode.insertBefore(this.dom,d.nextSibling);return this},insertFirst:function(e,d){e=e||{};if(e.nodeType||e.dom||typeof e=="string"){e=c(e);this.dom.insertBefore(e,this.dom.firstChild);return !d?a(e):e}else{return this.createChild(e,this.dom.firstChild,d)}},replace:function(d){d=a(d);this.insertBefore(d);d.remove();return this},replaceWith:function(d){var e=this;if(d.nodeType||d.dom||typeof d=="string"){d=c(d);e.dom.parentNode.insertBefore(d,e.dom)}else{d=b.insertBefore(e.dom,d)}delete Ext.elCache[e.id];Ext.removeNode(e.dom);e.id=Ext.id(e.dom=d);Ext.Element.addToCache(e.isFlyweight?new Ext.Element(e.dom):e);return e},createChild:function(e,d,g){e=e||{tag:"div"};return d?b.insertBefore(d,e,g!==true):b[!this.dom.firstChild?"overwrite":"append"](this.dom,e,g!==true)},wrap:function(d,e){var g=b.insertBefore(this.dom,d||{tag:"div"},!e);g.dom?g.dom.appendChild(this.dom):g.appendChild(this.dom);return g},insertHtml:function(e,g,d){var h=b.insertHtml(e,this.dom,g);return d?Ext.get(h):h}}}());Ext.Element.addMethods(function(){var A=Ext.supports,h={},x=/(-[a-z])/gi,s=document.defaultView,D=/alpha\(opacity=(.*)\)/i,l=/^\s+|\s+$/g,B=Ext.Element,u=/\s+/,b=/\w/g,d="padding",c="margin",y="border",t="-left",q="-right",w="-top",o="-bottom",j="-width",r=Math,z="hidden",e="isClipped",k="overflow",n="overflow-x",m="overflow-y",C="originalClip",i={l:y+t+j,r:y+q+j,t:y+w+j,b:y+o+j},g={l:d+t,r:d+q,t:d+w,b:d+o},a={l:c+t,r:c+q,t:c+w,b:c+o},E=Ext.Element.data;function p(F,G){return G.charAt(1).toUpperCase()}function v(F){return h[F]||(h[F]=F=="float"?(A.cssFloat?"cssFloat":"styleFloat"):F.replace(x,p))}return{adjustWidth:function(F){var G=this;var H=(typeof F=="number");if(H&&G.autoBoxAdjust&&!G.isBorderBox()){F-=(G.getBorderWidth("lr")+G.getPadding("lr"))}return(H&&F<0)?0:F},adjustHeight:function(F){var G=this;var H=(typeof F=="number");if(H&&G.autoBoxAdjust&&!G.isBorderBox()){F-=(G.getBorderWidth("tb")+G.getPadding("tb"))}return(H&&F<0)?0:F},addClass:function(J){var K=this,I,F,H,G=[];if(!Ext.isArray(J)){if(typeof J=="string"&&!this.hasClass(J)){K.dom.className+=" "+J}}else{for(I=0,F=J.length;I5?H.toLowerCase():G)},setStyle:function(I,H){var F,G;if(typeof I!="object"){F={};F[I]=H;I=F}for(G in I){H=I[G];G=="opacity"?this.setOpacity(H):this.dom.style[v(G)]=H}return this},setOpacity:function(G,F){var J=this,H=J.dom.style;if(!F||!J.anim){if(Ext.isIE){var I=G<1?"alpha(opacity="+G*100+")":"",K=H.filter.replace(D,"").replace(l,"");H.zoom=1;H.filter=K+(K.length>0?" ":"")+I}else{H.opacity=G}}else{J.anim({opacity:{to:G}},J.preanim(arguments,1),null,0.35,"easeIn")}return J},clearOpacity:function(){var F=this.dom.style;if(Ext.isIE){if(!Ext.isEmpty(F.filter)){F.filter=F.filter.replace(D,"").replace(l,"")}}else{F.opacity=F["-moz-opacity"]=F["-khtml-opacity"]=""}return this},getHeight:function(H){var G=this,J=G.dom,I=Ext.isIE&&G.isStyle("display","none"),F=r.max(J.offsetHeight,I?0:J.clientHeight)||0;F=!H?F:F-G.getBorderWidth("tb")-G.getPadding("tb");return F<0?0:F},getWidth:function(G){var H=this,J=H.dom,I=Ext.isIE&&H.isStyle("display","none"),F=r.max(J.offsetWidth,I?0:J.clientWidth)||0;F=!G?F:F-H.getBorderWidth("lr")-H.getPadding("lr");return F<0?0:F},setWidth:function(G,F){var H=this;G=H.adjustWidth(G);!F||!H.anim?H.dom.style.width=H.addUnits(G):H.anim({width:{to:G}},H.preanim(arguments,1));return H},setHeight:function(F,G){var H=this;F=H.adjustHeight(F);!G||!H.anim?H.dom.style.height=H.addUnits(F):H.anim({height:{to:F}},H.preanim(arguments,1));return H},getBorderWidth:function(F){return this.addStyles(F,i)},getPadding:function(F){return this.addStyles(F,g)},clip:function(){var F=this,G=F.dom;if(!E(G,e)){E(G,e,true);E(G,C,{o:F.getStyle(k),x:F.getStyle(n),y:F.getStyle(m)});F.setStyle(k,z);F.setStyle(n,z);F.setStyle(m,z)}return F},unclip:function(){var F=this,H=F.dom;if(E(H,e)){E(H,e,false);var G=E(H,C);if(G.o){F.setStyle(k,G.o)}if(G.x){F.setStyle(n,G.x)}if(G.y){F.setStyle(m,G.y)}}return F},addStyles:function(M,L){var J=0,K=M.match(b),I,H,G,F=K.length;for(G=0;Ga.clientHeight||a.scrollWidth>a.clientWidth},scrollTo:function(a,b){this.dom["scroll"+(/top/i.test(a)?"Top":"Left")]=b;return this},getScroll:function(){var i=this.dom,h=document,a=h.body,c=h.documentElement,b,g,e;if(i==h||i==a){if(Ext.isIE&&Ext.isStrict){b=c.scrollLeft;g=c.scrollTop}else{b=window.pageXOffset;g=window.pageYOffset}e={left:b||(a?a.scrollLeft:0),top:g||(a?a.scrollTop:0)}}else{e={left:i.scrollLeft,top:i.scrollTop}}return e}});Ext.Element.VISIBILITY=1;Ext.Element.DISPLAY=2;Ext.Element.OFFSETS=3;Ext.Element.ASCLASS=4;Ext.Element.visibilityCls="x-hide-nosize";Ext.Element.addMethods(function(){var e=Ext.Element,p="opacity",j="visibility",g="display",d="hidden",n="offsets",k="asclass",m="none",a="nosize",b="originalDisplay",c="visibilityMode",h="isVisible",i=e.data,l=function(r){var q=i(r,b);if(q===undefined){i(r,b,q="")}return q},o=function(r){var q=i(r,c);if(q===undefined){i(r,c,q=1)}return q};return{originalDisplay:"",visibilityMode:1,setVisibilityMode:function(q){i(this.dom,c,q);return this},animate:function(r,t,s,u,q){this.anim(r,{duration:t,callback:s,easing:u},q);return this},anim:function(t,u,r,w,s,q){r=r||"run";u=u||{};var v=this,x=Ext.lib.Anim[r](v.dom,t,(u.duration||w)||0.35,(u.easing||s)||"easeOut",function(){if(q){q.call(v)}if(u.callback){u.callback.call(u.scope||v,v,u)}},v);u.anim=x;return x},preanim:function(q,r){return !q[r]?false:(typeof q[r]=="object"?q[r]:{duration:q[r+1],callback:q[r+2],easing:q[r+3]})},isVisible:function(){var q=this,s=q.dom,r=i(s,h);if(typeof r=="boolean"){return r}r=!q.isStyle(j,d)&&!q.isStyle(g,m)&&!((o(s)==e.ASCLASS)&&q.hasClass(q.visibilityCls||e.visibilityCls));i(s,h,r);return r},setVisible:function(t,q){var w=this,r,y,x,v,u=w.dom,s=o(u);if(typeof q=="string"){switch(q){case g:s=e.DISPLAY;break;case j:s=e.VISIBILITY;break;case n:s=e.OFFSETS;break;case a:case k:s=e.ASCLASS;break}w.setVisibilityMode(s);q=false}if(!q||!w.anim){if(s==e.ASCLASS){w[t?"removeClass":"addClass"](w.visibilityCls||e.visibilityCls)}else{if(s==e.DISPLAY){return w.setDisplayed(t)}else{if(s==e.OFFSETS){if(!t){w.hideModeStyles={position:w.getStyle("position"),top:w.getStyle("top"),left:w.getStyle("left")};w.applyStyles({position:"absolute",top:"-10000px",left:"-10000px"})}else{w.applyStyles(w.hideModeStyles||{position:"",top:"",left:""});delete w.hideModeStyles}}else{w.fixDisplay();u.style.visibility=t?"visible":d}}}}else{if(t){w.setOpacity(0.01);w.setVisible(true)}w.anim({opacity:{to:(t?1:0)}},w.preanim(arguments,1),null,0.35,"easeIn",function(){t||w.setVisible(false).setOpacity(1)})}i(u,h,t);return w},hasMetrics:function(){var q=this.dom;return this.isVisible()||(o(q)==e.VISIBILITY)},toggle:function(q){var r=this;r.setVisible(!r.isVisible(),r.preanim(arguments,0));return r},setDisplayed:function(q){if(typeof q=="boolean"){q=q?l(this.dom):m}this.setStyle(g,q);return this},fixDisplay:function(){var q=this;if(q.isStyle(g,m)){q.setStyle(j,d);q.setStyle(g,l(this.dom));if(q.isStyle(g,m)){q.setStyle(g,"block")}}},hide:function(q){if(typeof q=="string"){this.setVisible(false,q);return this}this.setVisible(false,this.preanim(arguments,0));return this},show:function(q){if(typeof q=="string"){this.setVisible(true,q);return this}this.setVisible(true,this.preanim(arguments,0));return this}}}());(function(){var y=null,A=undefined,k=true,t=false,j="setX",h="setY",a="setXY",n="left",l="bottom",s="top",m="right",q="height",g="width",i="points",w="hidden",z="absolute",u="visible",e="motion",o="position",r="easeOut",d=new Ext.Element.Flyweight(),v={},x=function(B){return B||{}},p=function(B){d.dom=B;d.id=Ext.id(B);return d},c=function(B){if(!v[B]){v[B]=[]}return v[B]},b=function(C,B){v[C]=B};Ext.enableFx=k;Ext.Fx={switchStatements:function(C,D,B){return D.apply(this,B[C])},slideIn:function(H,E){E=x(E);var J=this,G=J.dom,M=G.style,O,B,L,D,C,M,I,N,K,F;H=H||"t";J.queueFx(E,function(){O=p(G).getXY();p(G).fixDisplay();B=p(G).getFxRestore();L={x:O[0],y:O[1],0:O[0],1:O[1],width:G.offsetWidth,height:G.offsetHeight};L.right=L.x+L.width;L.bottom=L.y+L.height;p(G).setWidth(L.width).setHeight(L.height);D=p(G).fxWrap(B.pos,E,w);M.visibility=u;M.position=z;function P(){p(G).fxUnwrap(D,B.pos,E);M.width=B.width;M.height=B.height;p(G).afterFx(E)}N={to:[L.x,L.y]};K={to:L.width};F={to:L.height};function Q(U,R,V,S,X,Z,ac,ab,aa,W,T){var Y={};p(U).setWidth(V).setHeight(S);if(p(U)[X]){p(U)[X](Z)}R[ac]=R[ab]="0";if(aa){Y.width=aa}if(W){Y.height=W}if(T){Y.points=T}return Y}I=p(G).switchStatements(H.toLowerCase(),Q,{t:[D,M,L.width,0,y,y,n,l,y,F,y],l:[D,M,0,L.height,y,y,m,s,K,y,y],r:[D,M,L.width,L.height,j,L.right,n,s,y,y,N],b:[D,M,L.width,L.height,h,L.bottom,n,s,y,F,N],tl:[D,M,0,0,y,y,m,l,K,F,N],bl:[D,M,0,0,h,L.y+L.height,m,s,K,F,N],br:[D,M,0,0,a,[L.right,L.bottom],n,s,K,F,N],tr:[D,M,0,0,j,L.x+L.width,n,l,K,F,N]});M.visibility=u;p(D).show();arguments.callee.anim=p(D).fxanim(I,E,e,0.5,r,P)});return J},slideOut:function(F,D){D=x(D);var H=this,E=H.dom,K=E.style,L=H.getXY(),C,B,I,J,G={to:0};F=F||"t";H.queueFx(D,function(){B=p(E).getFxRestore();I={x:L[0],y:L[1],0:L[0],1:L[1],width:E.offsetWidth,height:E.offsetHeight};I.right=I.x+I.width;I.bottom=I.y+I.height;p(E).setWidth(I.width).setHeight(I.height);C=p(E).fxWrap(B.pos,D,u);K.visibility=u;K.position=z;p(C).setWidth(I.width).setHeight(I.height);function M(){D.useDisplay?p(E).setDisplayed(t):p(E).hide();p(E).fxUnwrap(C,B.pos,D);K.width=B.width;K.height=B.height;p(E).afterFx(D)}function N(O,W,U,X,S,V,R,T,Q){var P={};O[W]=O[U]="0";P[X]=S;if(V){P[V]=R}if(T){P[T]=Q}return P}J=p(E).switchStatements(F.toLowerCase(),N,{t:[K,n,l,q,G],l:[K,m,s,g,G],r:[K,n,s,g,G,i,{to:[I.right,I.y]}],b:[K,n,s,q,G,i,{to:[I.x,I.bottom]}],tl:[K,m,l,g,G,q,G],bl:[K,m,s,g,G,q,G,i,{to:[I.x,I.bottom]}],br:[K,n,s,g,G,q,G,i,{to:[I.x+I.width,I.bottom]}],tr:[K,n,l,g,G,q,G,i,{to:[I.right,I.y]}]});arguments.callee.anim=p(C).fxanim(J,D,e,0.5,r,M)});return H},puff:function(H){H=x(H);var F=this,G=F.dom,C=G.style,D,B,E;F.queueFx(H,function(){D=p(G).getWidth();B=p(G).getHeight();p(G).clearOpacity();p(G).show();E=p(G).getFxRestore();function I(){H.useDisplay?p(G).setDisplayed(t):p(G).hide();p(G).clearOpacity();p(G).setPositioning(E.pos);C.width=E.width;C.height=E.height;C.fontSize="";p(G).afterFx(H)}arguments.callee.anim=p(G).fxanim({width:{to:p(G).adjustWidth(D*2)},height:{to:p(G).adjustHeight(B*2)},points:{by:[-D*0.5,-B*0.5]},opacity:{to:0},fontSize:{to:200,unit:"%"}},H,e,0.5,r,I)});return F},switchOff:function(F){F=x(F);var D=this,E=D.dom,B=E.style,C;D.queueFx(F,function(){p(E).clearOpacity();p(E).clip();C=p(E).getFxRestore();function G(){F.useDisplay?p(E).setDisplayed(t):p(E).hide();p(E).clearOpacity();p(E).setPositioning(C.pos);B.width=C.width;B.height=C.height;p(E).afterFx(F)}p(E).fxanim({opacity:{to:0.3}},y,y,0.1,y,function(){p(E).clearOpacity();(function(){p(E).fxanim({height:{to:1},points:{by:[0,p(E).getHeight()*0.5]}},F,e,0.3,"easeIn",G)}).defer(100)})});return D},highlight:function(D,H){H=x(H);var F=this,G=F.dom,B=H.attr||"backgroundColor",C={},E;F.queueFx(H,function(){p(G).clearOpacity();p(G).show();function I(){G.style[B]=E;p(G).afterFx(H)}E=G.style[B];C[B]={from:D||"ffff9c",to:H.endColor||p(G).getColor(B)||"ffffff"};arguments.callee.anim=p(G).fxanim(C,H,"color",1,"easeIn",I)});return F},frame:function(B,E,H){H=x(H);var D=this,G=D.dom,C,F;D.queueFx(H,function(){B=B||"#C3DAF9";if(B.length==6){B="#"+B}E=E||1;p(G).show();var L=p(G).getXY(),J={x:L[0],y:L[1],0:L[0],1:L[1],width:G.offsetWidth,height:G.offsetHeight},I=function(){C=p(document.body||document.documentElement).createChild({style:{position:z,"z-index":35000,border:"0px solid "+B}});return C.queueFx({},K)};arguments.callee.anim={isAnimated:true,stop:function(){E=0;C.stopFx()}};function K(){var M=Ext.isBorderBox?2:1;F=C.anim({top:{from:J.y,to:J.y-20},left:{from:J.x,to:J.x-20},borderWidth:{from:0,to:10},opacity:{from:1,to:0},height:{from:J.height,to:J.height+20*M},width:{from:J.width,to:J.width+20*M}},{duration:H.duration||1,callback:function(){C.remove();--E>0?I():p(G).afterFx(H)}});arguments.callee.anim={isAnimated:true,stop:function(){F.stop()}}}I()});return D},pause:function(D){var C=this.dom,B;this.queueFx({},function(){B=setTimeout(function(){p(C).afterFx({})},D*1000);arguments.callee.anim={isAnimated:true,stop:function(){clearTimeout(B);p(C).afterFx({})}}});return this},fadeIn:function(D){D=x(D);var B=this,C=B.dom,E=D.endOpacity||1;B.queueFx(D,function(){p(C).setOpacity(0);p(C).fixDisplay();C.style.visibility=u;arguments.callee.anim=p(C).fxanim({opacity:{to:E}},D,y,0.5,r,function(){if(E==1){p(C).clearOpacity()}p(C).afterFx(D)})});return B},fadeOut:function(E){E=x(E);var C=this,D=C.dom,B=D.style,F=E.endOpacity||0;C.queueFx(E,function(){arguments.callee.anim=p(D).fxanim({opacity:{to:F}},E,y,0.5,r,function(){if(F==0){Ext.Element.data(D,"visibilityMode")==Ext.Element.DISPLAY||E.useDisplay?B.display="none":B.visibility=w;p(D).clearOpacity()}p(D).afterFx(E)})});return C},scale:function(B,C,D){this.shift(Ext.apply({},D,{width:B,height:C}));return this},shift:function(D){D=x(D);var C=this.dom,B={};this.queueFx(D,function(){for(var E in D){if(D[E]!=A){B[E]={to:D[E]}}}B.width?B.width.to=p(C).adjustWidth(D.width):B;B.height?B.height.to=p(C).adjustWidth(D.height):B;if(B.x||B.y||B.xy){B.points=B.xy||{to:[B.x?B.x.to:p(C).getX(),B.y?B.y.to:p(C).getY()]}}arguments.callee.anim=p(C).fxanim(B,D,e,0.35,r,function(){p(C).afterFx(D)})});return this},ghost:function(E,C){C=x(C);var G=this,D=G.dom,J=D.style,H={opacity:{to:0},points:{}},K=H.points,B,I,F;E=E||"b";G.queueFx(C,function(){B=p(D).getFxRestore();I=p(D).getWidth();F=p(D).getHeight();function L(){C.useDisplay?p(D).setDisplayed(t):p(D).hide();p(D).clearOpacity();p(D).setPositioning(B.pos);J.width=B.width;J.height=B.height;p(D).afterFx(C)}K.by=p(D).switchStatements(E.toLowerCase(),function(N,M){return[N,M]},{t:[0,-F],l:[-I,0],r:[I,0],b:[0,F],tl:[-I,-F],bl:[-I,F],br:[I,F],tr:[I,-F]});arguments.callee.anim=p(D).fxanim(H,C,e,0.5,r,L)});return G},syncFx:function(){var B=this;B.fxDefaults=Ext.apply(B.fxDefaults||{},{block:t,concurrent:k,stopFx:t});return B},sequenceFx:function(){var B=this;B.fxDefaults=Ext.apply(B.fxDefaults||{},{block:t,concurrent:t,stopFx:t});return B},nextFx:function(){var B=c(this.dom.id)[0];if(B){B.call(this)}},hasActiveFx:function(){return c(this.dom.id)[0]},stopFx:function(B){var C=this,E=C.dom.id;if(C.hasActiveFx()){var D=c(E)[0];if(D&&D.anim){if(D.anim.isAnimated){b(E,[D]);D.anim.stop(B!==undefined?B:k)}else{b(E,[])}}}return C},beforeFx:function(B){if(this.hasActiveFx()&&!B.concurrent){if(B.stopFx){this.stopFx();return k}return t}return k},hasFxBlock:function(){var B=c(this.dom.id);return B&&B[0]&&B[0].block},queueFx:function(E,B){var C=p(this.dom);if(!C.hasFxBlock()){Ext.applyIf(E,C.fxDefaults);if(!E.concurrent){var D=C.beforeFx(E);B.block=E.block;c(C.dom.id).push(B);if(D){C.nextFx()}}else{B.call(C)}}return C},fxWrap:function(H,F,D){var E=this.dom,C,B;if(!F.wrap||!(C=Ext.getDom(F.wrap))){if(F.fixPosition){B=p(E).getXY()}var G=document.createElement("div");G.style.visibility=D;C=E.parentNode.insertBefore(G,E);p(C).setPositioning(H);if(p(C).isStyle(o,"static")){p(C).position("relative")}p(E).clearPositioning("auto");p(C).clip();C.appendChild(E);if(B){p(C).setXY(B)}}return C},fxUnwrap:function(C,F,E){var D=this.dom;p(D).clearPositioning();p(D).setPositioning(F);if(!E.wrap){var B=p(C).dom.parentNode;B.insertBefore(D,C);p(C).remove()}},getFxRestore:function(){var B=this.dom.style;return{pos:this.getPositioning(),width:B.width,height:B.height}},afterFx:function(C){var B=this.dom,D=B.id;if(C.afterStyle){p(B).setStyle(C.afterStyle)}if(C.afterCls){p(B).addClass(C.afterCls)}if(C.remove==k){p(B).remove()}if(C.callback){C.callback.call(C.scope,p(B))}if(!C.concurrent){c(D).shift();p(B).nextFx()}},fxanim:function(E,F,C,G,D,B){C=C||"run";F=F||{};var H=Ext.lib.Anim[C](this.dom,E,(F.duration||G)||0.35,(F.easing||D)||r,B,this);F.anim=H;return H}};Ext.Fx.resize=Ext.Fx.scale;Ext.Element.addMethods(Ext.Fx)})();Ext.CompositeElementLite=function(b,a){this.elements=[];this.add(b,a);this.el=new Ext.Element.Flyweight()};Ext.CompositeElementLite.prototype={isComposite:true,getElement:function(a){var b=this.el;b.dom=a;b.id=a.id;return b},transformElement:function(a){return Ext.getDom(a)},getCount:function(){return this.elements.length},add:function(d,b){var e=this,g=e.elements;if(!d){return this}if(typeof d=="string"){d=Ext.Element.selectorFunction(d,b)}else{if(d.isComposite){d=d.elements}else{if(!Ext.isIterable(d)){d=[d]}}}for(var c=0,a=d.length;c-1){c=Ext.getDom(c);if(a){g=this.elements[b];g.parentNode.insertBefore(c,g);Ext.removeNode(g)}this.elements.splice(b,1,c)}return this},clear:function(){this.elements=[]}};Ext.CompositeElementLite.prototype.on=Ext.CompositeElementLite.prototype.addListener;Ext.CompositeElementLite.importElementMethods=function(){var c,b=Ext.Element.prototype,a=Ext.CompositeElementLite.prototype;for(c in b){if(typeof b[c]=="function"){(function(d){a[d]=a[d]||function(){return this.invoke(d,arguments)}}).call(a,c)}}};Ext.CompositeElementLite.importElementMethods();if(Ext.DomQuery){Ext.Element.selectorFunction=Ext.DomQuery.select}Ext.Element.select=function(a,b){var c;if(typeof a=="string"){c=Ext.Element.selectorFunction(a,b)}else{if(a.length!==undefined){c=a}else{throw"Invalid selector"}}return new Ext.CompositeElementLite(c)};Ext.select=Ext.Element.select;(function(){var b="beforerequest",e="requestcomplete",d="requestexception",h=undefined,c="load",i="POST",a="GET",g=window;Ext.data.Connection=function(j){Ext.apply(this,j);this.addEvents(b,e,d);Ext.data.Connection.superclass.constructor.call(this)};Ext.extend(Ext.data.Connection,Ext.util.Observable,{timeout:30000,autoAbort:false,disableCaching:true,disableCachingParam:"_dc",request:function(n){var s=this;if(s.fireEvent(b,s,n)){if(n.el){if(!Ext.isEmpty(n.indicatorText)){s.indicatorText='
'+n.indicatorText+"
"}if(s.indicatorText){Ext.getDom(n.el).innerHTML=s.indicatorText}n.success=(Ext.isFunction(n.success)?n.success:function(){}).createInterceptor(function(o){Ext.getDom(n.el).innerHTML=o.responseText})}var l=n.params,k=n.url||s.url,j,q={success:s.handleResponse,failure:s.handleFailure,scope:s,argument:{options:n},timeout:Ext.num(n.timeout,s.timeout)},m,t;if(Ext.isFunction(l)){l=l.call(n.scope||g,n)}l=Ext.urlEncode(s.extraParams,Ext.isObject(l)?Ext.urlEncode(l):l);if(Ext.isFunction(k)){k=k.call(n.scope||g,n)}if((m=Ext.getDom(n.form))){k=k||m.action;if(n.isUpload||(/multipart\/form-data/i.test(m.getAttribute("enctype")))){return s.doFormUpload.call(s,n,l,k)}t=Ext.lib.Ajax.serializeForm(m);l=l?(l+"&"+t):t}j=n.method||s.method||((l||n.xmlData||n.jsonData)?i:a);if(j===a&&(s.disableCaching&&n.disableCaching!==false)||n.disableCaching===true){var r=n.disableCachingParam||s.disableCachingParam;k=Ext.urlAppend(k,r+"="+(new Date().getTime()))}n.headers=Ext.applyIf(n.headers||{},s.defaultHeaders||{});if(n.autoAbort===true||s.autoAbort){s.abort()}if((j==a||n.xmlData||n.jsonData)&&l){k=Ext.urlAppend(k,l);l=""}return(s.transId=Ext.lib.Ajax.request(j,k,q,l,n))}else{return n.callback?n.callback.apply(n.scope,[n,h,h]):null}},isLoading:function(j){return j?Ext.lib.Ajax.isCallInProgress(j):!!this.transId},abort:function(j){if(j||this.isLoading()){Ext.lib.Ajax.abort(j||this.transId)}},handleResponse:function(j){this.transId=false;var k=j.argument.options;j.argument=k?k.argument:null;this.fireEvent(e,this,j,k);if(k.success){k.success.call(k.scope,j,k)}if(k.callback){k.callback.call(k.scope,k,true,j)}},handleFailure:function(j,l){this.transId=false;var k=j.argument.options;j.argument=k?k.argument:null;this.fireEvent(d,this,j,k,l);if(k.failure){k.failure.call(k.scope,j,k)}if(k.callback){k.callback.call(k.scope,k,false,j)}},doFormUpload:function(q,j,k){var l=Ext.id(),v=document,r=v.createElement("iframe"),m=Ext.getDom(q.form),u=[],t,p="multipart/form-data",n={target:m.target,method:m.method,encoding:m.encoding,enctype:m.enctype,action:m.action};Ext.fly(r).set({id:l,name:l,cls:"x-hidden",src:Ext.SSL_SECURE_URL});v.body.appendChild(r);if(Ext.isIE){document.frames[l].name=l}Ext.fly(m).set({target:l,method:i,enctype:p,encoding:p,action:k||n.action});Ext.iterate(Ext.urlDecode(j,false),function(w,o){t=v.createElement("input");Ext.fly(t).set({type:"hidden",value:o,name:w});m.appendChild(t);u.push(t)});function s(){var x=this,w={responseText:"",responseXML:null,argument:q.argument},A,z;try{A=r.contentWindow.document||r.contentDocument||g.frames[l].document;if(A){if(A.body){if(/textarea/i.test((z=A.body.firstChild||{}).tagName)){w.responseText=z.value}else{w.responseText=A.body.innerHTML}}w.responseXML=A.XMLDocument||A}}catch(y){}Ext.EventManager.removeListener(r,c,s,x);x.fireEvent(e,x,w,q);function o(D,C,B){if(Ext.isFunction(D)){D.apply(C,B)}}o(q.success,q.scope,[w,q]);o(q.callback,q.scope,[q,true,w]);if(!x.debugUploads){setTimeout(function(){Ext.removeNode(r)},100)}}Ext.EventManager.on(r,c,s,this);m.submit();Ext.fly(m).set(n);Ext.each(u,function(o){Ext.removeNode(o)})}})})();Ext.Ajax=new Ext.data.Connection({autoAbort:false,serializeForm:function(a){return Ext.lib.Ajax.serializeForm(a)}});Ext.util.JSON=new (function(){var useHasOwn=!!{}.hasOwnProperty,isNative=function(){var useNative=null;return function(){if(useNative===null){useNative=Ext.USE_NATIVE_JSON&&window.JSON&&JSON.toString()=="[object JSON]"}return useNative}}(),pad=function(n){return n<10?"0"+n:n},doDecode=function(json){return json?eval("("+json+")"):""},doEncode=function(o){if(!Ext.isDefined(o)||o===null){return"null"}else{if(Ext.isArray(o)){return encodeArray(o)}else{if(Ext.isDate(o)){return Ext.util.JSON.encodeDate(o)}else{if(Ext.isString(o)){return encodeString(o)}else{if(typeof o=="number"){return isFinite(o)?String(o):"null"}else{if(Ext.isBoolean(o)){return String(o)}else{var a=["{"],b,i,v;for(i in o){if(!o.getElementsByTagName){if(!useHasOwn||o.hasOwnProperty(i)){v=o[i];switch(typeof v){case"undefined":case"function":case"unknown":break;default:if(b){a.push(",")}a.push(doEncode(i),":",v===null?"null":doEncode(v));b=true}}}}a.push("}");return a.join("")}}}}}}},m={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},encodeString=function(s){if(/["\\\x00-\x1f]/.test(s)){return'"'+s.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];if(c){return c}c=b.charCodeAt();return"\\u00"+Math.floor(c/16).toString(16)+(c%16).toString(16)})+'"'}return'"'+s+'"'},encodeArray=function(o){var a=["["],b,i,l=o.length,v;for(i=0;i
';e.body.appendChild(g);d=g.lastChild;if((c=e.defaultView)){if(c.getComputedStyle(g.firstChild.firstChild,null).marginRight!="0px"){b.correctRightMargin=false}if(c.getComputedStyle(d,null).backgroundColor!="transparent"){b.correctTransparentColor=false}}b.cssFloat=!!d.style.cssFloat;e.body.removeChild(g)};if(Ext.isReady){a()}else{Ext.onReady(a)}})();Ext.EventObject=function(){var b=Ext.lib.Event,c=/(dbl)?click/,a={3:13,63234:37,63235:39,63232:38,63233:40,63276:33,63277:34,63272:46,63273:36,63275:35},d=Ext.isIE?{1:0,4:1,2:2}:{0:0,1:1,2:2};Ext.EventObjectImpl=function(g){if(g){this.setEvent(g.browserEvent||g)}};Ext.EventObjectImpl.prototype={setEvent:function(h){var g=this;if(h==g||(h&&h.browserEvent)){return h}g.browserEvent=h;if(h){g.button=h.button?d[h.button]:(h.which?h.which-1:-1);if(c.test(h.type)&&g.button==-1){g.button=0}g.type=h.type;g.shiftKey=h.shiftKey;g.ctrlKey=h.ctrlKey||h.metaKey||false;g.altKey=h.altKey;g.keyCode=h.keyCode;g.charCode=h.charCode;g.target=b.getTarget(h);g.xy=b.getXY(h)}else{g.button=-1;g.shiftKey=false;g.ctrlKey=false;g.altKey=false;g.keyCode=0;g.charCode=0;g.target=null;g.xy=[0,0]}return g},stopEvent:function(){var e=this;if(e.browserEvent){if(e.browserEvent.type=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.fire(e)}b.stopEvent(e.browserEvent)}},preventDefault:function(){if(this.browserEvent){b.preventDefault(this.browserEvent)}},stopPropagation:function(){var e=this;if(e.browserEvent){if(e.browserEvent.type=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.fire(e)}b.stopPropagation(e.browserEvent)}},getCharCode:function(){return this.charCode||this.keyCode},getKey:function(){return this.normalizeKey(this.keyCode||this.charCode)},normalizeKey:function(e){return Ext.isSafari?(a[e]||e):e},getPageX:function(){return this.xy[0]},getPageY:function(){return this.xy[1]},getXY:function(){return this.xy},getTarget:function(g,h,e){return g?Ext.fly(this.target).findParent(g,h,e):(e?Ext.get(this.target):this.target)},getRelatedTarget:function(){return this.browserEvent?b.getRelatedTarget(this.browserEvent):null},getWheelDelta:function(){var g=this.browserEvent;var h=0;if(g.wheelDelta){h=g.wheelDelta/120}else{if(g.detail){h=-g.detail/3}}return h},within:function(h,i,e){if(h){var g=this[i?"getRelatedTarget":"getTarget"]();return g&&((e?(g==Ext.getDom(h)):false)||Ext.fly(h).contains(g))}return false}};return new Ext.EventObjectImpl()}();Ext.Loader=Ext.apply({},{load:function(j,i,k,c){var k=k||this,g=document.getElementsByTagName("head")[0],b=document.createDocumentFragment(),a=j.length,h=0,e=this;var l=function(m){g.appendChild(e.buildScriptTag(j[m],d))};var d=function(){h++;if(a==h&&typeof i=="function"){i.call(k)}else{if(c===true){l(h)}}};if(c===true){l.call(this,0)}else{Ext.each(j,function(n,m){b.appendChild(this.buildScriptTag(n,d))},this);g.appendChild(b)}},buildScriptTag:function(b,c){var a=document.createElement("script");a.type="text/javascript";a.src=b;if(a.readyState){a.onreadystatechange=function(){if(a.readyState=="loaded"||a.readyState=="complete"){a.onreadystatechange=null;c()}}}else{a.onload=c}return a}});Ext.ns("Ext.grid","Ext.list","Ext.dd","Ext.tree","Ext.form","Ext.menu","Ext.state","Ext.layout.boxOverflow","Ext.app","Ext.ux","Ext.chart","Ext.direct","Ext.slider");Ext.apply(Ext,function(){var c=Ext,a=0,b=null;return{emptyFn:function(){},BLANK_IMAGE_URL:Ext.isIE6||Ext.isIE7||Ext.isAir?"http://www.extjs.com/s.gif":"data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",extendX:function(d,e){return Ext.extend(d,e(d.prototype))},getDoc:function(){return Ext.get(document)},num:function(e,d){e=Number(Ext.isEmpty(e)||Ext.isArray(e)||typeof e=="boolean"||(typeof e=="string"&&e.trim().length==0)?NaN:e);return isNaN(e)?d:e},value:function(g,d,e){return Ext.isEmpty(g,e)?d:g},escapeRe:function(d){return d.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1")},sequence:function(h,d,g,e){h[d]=h[d].createSequence(g,e)},addBehaviors:function(i){if(!Ext.isReady){Ext.onReady(function(){Ext.addBehaviors(i)})}else{var e={},h,d,g;for(d in i){if((h=d.split("@"))[1]){g=h[0];if(!e[g]){e[g]=Ext.select(g)}e[g].on(h[1],i[d])}}e=null}},getScrollBarWidth:function(g){if(!Ext.isReady){return 0}if(g===true||b===null){var i=Ext.getBody().createChild('
'),h=i.child("div",true);var e=h.offsetWidth;i.setStyle("overflow",(Ext.isWebKit||Ext.isGecko)?"auto":"scroll");var d=h.offsetWidth;i.remove();b=e-d+2}return b},combine:function(){var g=arguments,e=g.length,j=[];for(var h=0;hh?1:-1};Ext.each(d,function(h){g=e(g,h)==1?g:h});return g},mean:function(d){return d.length>0?Ext.sum(d)/d.length:undefined},sum:function(d){var e=0;Ext.each(d,function(g){e+=g});return e},partition:function(d,e){var g=[[],[]];Ext.each(d,function(j,k,h){g[(e&&e(j,k,h))||(!e&&j)?0:1].push(j)});return g},invoke:function(d,e){var h=[],g=Array.prototype.slice.call(arguments,2);Ext.each(d,function(j,k){if(j&&typeof j[e]=="function"){h.push(j[e].apply(j,g))}else{h.push(undefined)}});return h},pluck:function(d,g){var e=[];Ext.each(d,function(h){e.push(h[g])});return e},zip:function(){var n=Ext.partition(arguments,function(i){return typeof i!="function"}),k=n[0],m=n[1][0],d=Ext.max(Ext.pluck(k,"length")),h=[];for(var l=0;l=a.left&&b.right<=a.right&&b.top>=a.top&&b.bottom<=a.bottom)},getArea:function(){var a=this;return((a.bottom-a.top)*(a.right-a.left))},intersect:function(h){var g=this,d=Math.max(g.top,h.top),e=Math.min(g.right,h.right),a=Math.min(g.bottom,h.bottom),c=Math.max(g.left,h.left);if(a>=d&&e>=c){return new Ext.lib.Region(d,e,a,c)}},union:function(h){var g=this,d=Math.min(g.top,h.top),e=Math.max(g.right,h.right),a=Math.max(g.bottom,h.bottom),c=Math.min(g.left,h.left);return new Ext.lib.Region(d,e,a,c)},constrainTo:function(b){var a=this;a.top=a.top.constrain(b.top,b.bottom);a.bottom=a.bottom.constrain(b.top,b.bottom);a.left=a.left.constrain(b.left,b.right);a.right=a.right.constrain(b.left,b.right);return a},adjust:function(d,c,a,g){var e=this;e.top+=d;e.left+=c;e.right+=g;e.bottom+=a;return e}};Ext.lib.Region.getRegion=function(e){var h=Ext.lib.Dom.getXY(e),d=h[1],g=h[0]+e.offsetWidth,a=h[1]+e.offsetHeight,c=h[0];return new Ext.lib.Region(d,g,a,c)};Ext.lib.Point=function(a,c){if(Ext.isArray(a)){c=a[1];a=a[0]}var b=this;b.x=b.right=b.left=b[0]=a;b.y=b.top=b.bottom=b[1]=c};Ext.lib.Point.prototype=new Ext.lib.Region();Ext.apply(Ext.DomHelper,function(){var e,a="afterbegin",h="afterend",i="beforebegin",d="beforeend",b=/tag|children|cn|html$/i;function g(m,p,n,q,l,j){m=Ext.getDom(m);var k;if(e.useDom){k=c(p,null);if(j){m.appendChild(k)}else{(l=="firstChild"?m:m.parentNode).insertBefore(k,m[l]||m)}}else{k=Ext.DomHelper.insertHtml(q,m,Ext.DomHelper.createHtml(p))}return n?Ext.get(k,true):k}function c(j,r){var k,u=document,p,s,m,t;if(Ext.isArray(j)){k=u.createDocumentFragment();for(var q=0,n=j.length;q0){return setTimeout(d,c)}d();return 0},createSequence:function(c,b,a){if(!Ext.isFunction(b)){return c}else{return function(){var d=c.apply(this||window,arguments);b.apply(a||this||window,arguments);return d}}}};Ext.defer=Ext.util.Functions.defer;Ext.createInterceptor=Ext.util.Functions.createInterceptor;Ext.createSequence=Ext.util.Functions.createSequence;Ext.createDelegate=Ext.util.Functions.createDelegate;Ext.apply(Ext.util.Observable.prototype,function(){function a(j){var i=(this.methodEvents=this.methodEvents||{})[j],d,c,g,h=this;if(!i){this.methodEvents[j]=i={};i.originalFn=this[j];i.methodName=j;i.before=[];i.after=[];var b=function(l,k,e){if((c=l.apply(k||h,e))!==undefined){if(typeof c=="object"){if(c.returnValue!==undefined){d=c.returnValue}else{d=c}g=!!c.cancel}else{if(c===false){g=true}else{d=c}}}};this[j]=function(){var l=Array.prototype.slice.call(arguments,0),k;d=c=undefined;g=false;for(var m=0,e=i.before.length;m=525:!((Ext.isGecko&&!Ext.isWindows)||Ext.isOpera);return{_unload:function(){Ext.EventManager.un(window,"resize",this.fireWindowResize,this);c.call(Ext.EventManager)},doResizeEvent:function(){var m=a.getViewHeight(),l=a.getViewWidth();if(h!=m||i!=l){d.fire(i=l,h=m)}},onWindowResize:function(n,m,l){if(!d){d=new Ext.util.Event();k=new Ext.util.DelayedTask(this.doResizeEvent);Ext.EventManager.on(window,"resize",this.fireWindowResize,this)}d.addListener(n,m,l)},fireWindowResize:function(){if(d){k.delay(100)}},onTextResize:function(o,n,l){if(!g){g=new Ext.util.Event();var m=new Ext.Element(document.createElement("div"));m.dom.className="x-text-resize";m.dom.innerHTML="X";m.appendTo(document.body);b=m.dom.offsetHeight;setInterval(function(){if(m.dom.offsetHeight!=b){g.fire(b,b=m.dom.offsetHeight)}},this.textResizeInterval)}g.addListener(o,n,l)},removeResizeListener:function(m,l){if(d){d.removeListener(m,l)}},fireResize:function(){if(d){d.fire(a.getViewWidth(),a.getViewHeight())}},textResizeInterval:50,ieDeferSrc:false,getKeyEvent:function(){return e?"keydown":"keypress"},useKeydown:e}}());Ext.EventManager.on=Ext.EventManager.addListener;Ext.apply(Ext.EventObjectImpl.prototype,{BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,RETURN:13,SHIFT:16,CTRL:17,CONTROL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGEUP:33,PAGE_DOWN:34,PAGEDOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,isNavKeyPress:function(){var b=this,a=this.normalizeKey(b.keyCode);return(a>=33&&a<=40)||a==b.RETURN||a==b.TAB||a==b.ESC},isSpecialKey:function(){var a=this.normalizeKey(this.keyCode);return(this.type=="keypress"&&this.ctrlKey)||this.isNavKeyPress()||(a==this.BACKSPACE)||(a>=16&&a<=20)||(a>=44&&a<=46)},getPoint:function(){return new Ext.lib.Point(this.xy[0],this.xy[1])},hasModifier:function(){return((this.ctrlKey||this.altKey)||this.shiftKey)}});Ext.Element.addMethods({swallowEvent:function(a,b){var d=this;function c(g){g.stopPropagation();if(b){g.preventDefault()}}if(Ext.isArray(a)){Ext.each(a,function(g){d.on(g,c)});return d}d.on(a,c);return d},relayEvent:function(a,b){this.on(a,function(c){b.fireEvent(a,c)})},clean:function(b){var d=this,e=d.dom,g=e.firstChild,c=-1;if(Ext.Element.data(e,"isCleaned")&&b!==true){return d}while(g){var a=g.nextSibling;if(g.nodeType==3&&!(/\S/.test(g.nodeValue))){e.removeChild(g)}else{g.nodeIndex=++c}g=a}Ext.Element.data(e,"isCleaned",true);return d},load:function(){var a=this.getUpdater();a.update.apply(a,arguments);return this},getUpdater:function(){return this.updateManager||(this.updateManager=new Ext.Updater(this))},update:function(html,loadScripts,callback){if(!this.dom){return this}html=html||"";if(loadScripts!==true){this.dom.innerHTML=html;if(typeof callback=="function"){callback()}return this}var id=Ext.id(),dom=this.dom;html+='';Ext.lib.Event.onAvailable(id,function(){var DOC=document,hd=DOC.getElementsByTagName("head")[0],re=/(?:]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,srcRe=/\ssrc=([\'\"])(.*?)\1/i,typeRe=/\stype=([\'\"])(.*?)\1/i,match,attrs,srcMatch,typeMatch,el,s;while((match=re.exec(html))){attrs=match[1];srcMatch=attrs?attrs.match(srcRe):false;if(srcMatch&&srcMatch[2]){s=DOC.createElement("script");s.src=srcMatch[2];typeMatch=attrs.match(typeRe);if(typeMatch&&typeMatch[2]){s.type=typeMatch[2]}hd.appendChild(s)}else{if(match[2]&&match[2].length>0){if(window.execScript){window.execScript(match[2])}else{window.eval(match[2])}}}}el=DOC.getElementById(id);if(el){Ext.removeNode(el)}if(typeof callback=="function"){callback()}});dom.innerHTML=html.replace(/(?:)((\n|\r|.)*?)(?:<\/script>)/ig,"");return this},removeAllListeners:function(){this.removeAnchor();Ext.EventManager.removeAll(this.dom);return this},createProxy:function(a,e,d){a=(typeof a=="object")?a:{tag:"div",cls:a};var c=this,b=e?Ext.DomHelper.append(e,a,true):Ext.DomHelper.insertBefore(c.dom,a,true);if(d&&c.setBox&&c.getBox){b.setBox(c.getBox())}return b}});Ext.Element.prototype.getUpdateManager=Ext.Element.prototype.getUpdater;Ext.Element.addMethods({getAnchorXY:function(e,l,q){e=(e||"tl").toLowerCase();q=q||{};var k=this,b=k.dom==document.body||k.dom==document,n=q.width||b?Ext.lib.Dom.getViewWidth():k.getWidth(),i=q.height||b?Ext.lib.Dom.getViewHeight():k.getHeight(),p,a=Math.round,c=k.getXY(),m=k.getScroll(),j=b?m.left:!l?c[0]:0,g=b?m.top:!l?c[1]:0,d={c:[a(n*0.5),a(i*0.5)],t:[a(n*0.5),0],l:[0,a(i*0.5)],r:[n,a(i*0.5)],b:[a(n*0.5),i],tl:[0,0],bl:[0,i],br:[n,i],tr:[n,0]};p=d[e];return[p[0]+j,p[1]+g]},anchorTo:function(b,h,c,a,k,l){var i=this,e=i.dom,j=!Ext.isEmpty(k),d=function(){Ext.fly(e).alignTo(b,h,c,a);Ext.callback(l,Ext.fly(e))},g=this.getAnchor();this.removeAnchor();Ext.apply(g,{fn:d,scroll:j});Ext.EventManager.onWindowResize(d,null);if(j){Ext.EventManager.on(window,"scroll",d,null,{buffer:!isNaN(k)?k:50})}d.call(i);return i},removeAnchor:function(){var b=this,a=this.getAnchor();if(a&&a.fn){Ext.EventManager.removeResizeListener(a.fn);if(a.scroll){Ext.EventManager.un(window,"scroll",a.fn)}delete a.fn}return b},getAnchor:function(){var b=Ext.Element.data,c=this.dom;if(!c){return}var a=b(c,"_anchor");if(!a){a=b(c,"_anchor",{})}return a},getAlignToXY:function(g,A,B){g=Ext.get(g);if(!g||!g.dom){throw"Element.alignToXY with an element that doesn't exist"}B=B||[0,0];A=(!A||A=="?"?"tl-bl?":(!(/-/).test(A)&&A!==""?"tl-"+A:A||"tl-bl")).toLowerCase();var K=this,H=K.dom,M,L,n,l,s,F,v,t=Ext.lib.Dom.getViewWidth()-10,G=Ext.lib.Dom.getViewHeight()-10,b,i,j,k,u,z,N=document,J=N.documentElement,q=N.body,E=(J.scrollLeft||q.scrollLeft||0)+5,D=(J.scrollTop||q.scrollTop||0)+5,I=false,e="",a="",C=A.match(/^([a-z]+)-([a-z]+)(\?)?$/);if(!C){throw"Element.alignTo with an invalid alignment "+A}e=C[1];a=C[2];I=!!C[3];M=K.getAnchorXY(e,true);L=g.getAnchorXY(a,false);n=L[0]-M[0]+B[0];l=L[1]-M[1]+B[1];if(I){s=K.getWidth();F=K.getHeight();v=g.getRegion();b=e.charAt(0);i=e.charAt(e.length-1);j=a.charAt(0);k=a.charAt(a.length-1);u=((b=="t"&&j=="b")||(b=="b"&&j=="t"));z=((i=="r"&&k=="l")||(i=="l"&&k=="r"));if(n+s>t+E){n=z?v.left-s:t+E-s}if(nG+D){l=u?v.top-F:G+D-F}if(lB){p=B-q;m=true}if((o+C)>g){o=g-C;m=true}if(p"+String.format(Ext.Element.boxMarkup,c)+"
"));Ext.DomQuery.selectNode("."+c+"-mc",d.dom).appendChild(this.dom);return d},setSize:function(e,c,d){var g=this;if(typeof e=="object"){c=e.height;e=e.width}e=g.adjustWidth(e);c=g.adjustHeight(c);if(!d||!g.anim){g.dom.style.width=g.addUnits(e);g.dom.style.height=g.addUnits(c)}else{g.anim({width:{to:e},height:{to:c}},g.preanim(arguments,2))}return g},getComputedHeight:function(){var d=this,c=Math.max(d.dom.offsetHeight,d.dom.clientHeight);if(!c){c=parseFloat(d.getStyle("height"))||0;if(!d.isBorderBox()){c+=d.getFrameWidth("tb")}}return c},getComputedWidth:function(){var c=Math.max(this.dom.offsetWidth,this.dom.clientWidth);if(!c){c=parseFloat(this.getStyle("width"))||0;if(!this.isBorderBox()){c+=this.getFrameWidth("lr")}}return c},getFrameWidth:function(d,c){return c&&this.isBorderBox()?0:(this.getPadding(d)+this.getBorderWidth(d))},addClassOnOver:function(c){this.hover(function(){Ext.fly(this,a).addClass(c)},function(){Ext.fly(this,a).removeClass(c)});return this},addClassOnFocus:function(c){this.on("focus",function(){Ext.fly(this,a).addClass(c)},this.dom);this.on("blur",function(){Ext.fly(this,a).removeClass(c)},this.dom);return this},addClassOnClick:function(c){var d=this.dom;this.on("mousedown",function(){Ext.fly(d,a).addClass(c);var g=Ext.getDoc(),e=function(){Ext.fly(d,a).removeClass(c);g.removeListener("mouseup",e)};g.on("mouseup",e)});return this},getViewSize:function(){var g=document,h=this.dom,c=(h==g||h==g.body);if(c){var e=Ext.lib.Dom;return{width:e.getViewWidth(),height:e.getViewHeight()}}else{return{width:h.clientWidth,height:h.clientHeight}}},getStyleSize:function(){var j=this,c,i,l=document,m=this.dom,e=(m==l||m==l.body),g=m.style;if(e){var k=Ext.lib.Dom;return{width:k.getViewWidth(),height:k.getViewHeight()}}if(g.width&&g.width!="auto"){c=parseFloat(g.width);if(j.isBorderBox()){c-=j.getFrameWidth("lr")}}if(g.height&&g.height!="auto"){i=parseFloat(g.height);if(j.isBorderBox()){i-=j.getFrameWidth("tb")}}return{width:c||j.getWidth(true),height:i||j.getHeight(true)}},getSize:function(c){return{width:this.getWidth(c),height:this.getHeight(c)}},repaint:function(){var c=this.dom;this.addClass("x-repaint");setTimeout(function(){Ext.fly(c).removeClass("x-repaint")},1);return this},unselectable:function(){this.dom.unselectable="on";return this.swallowEvent("selectstart",true).applyStyles("-moz-user-select:none;-khtml-user-select:none;").addClass("x-unselectable")},getMargins:function(d){var e=this,c,g={t:"top",l:"left",r:"right",b:"bottom"},h={};if(!d){for(c in e.margins){h[g[c]]=parseFloat(e.getStyle(e.margins[c]))||0}return h}else{return e.addStyles.call(e,d,e.margins)}}}}());Ext.Element.addMethods({setBox:function(e,g,b){var d=this,a=e.width,c=e.height;if((g&&!d.autoBoxAdjust)&&!d.isBorderBox()){a-=(d.getBorderWidth("lr")+d.getPadding("lr"));c-=(d.getBorderWidth("tb")+d.getPadding("tb"))}d.setBounds(e.x,e.y,a,c,d.animTest.call(d,arguments,b,2));return d},getBox:function(j,p){var m=this,v,e,o,d=m.getBorderWidth,q=m.getPadding,g,a,u,n;if(!p){v=m.getXY()}else{e=parseInt(m.getStyle("left"),10)||0;o=parseInt(m.getStyle("top"),10)||0;v=[e,o]}var c=m.dom,s=c.offsetWidth,i=c.offsetHeight,k;if(!j){k={x:v[0],y:v[1],0:v[0],1:v[1],width:s,height:i}}else{g=d.call(m,"l")+q.call(m,"l");a=d.call(m,"r")+q.call(m,"r");u=d.call(m,"t")+q.call(m,"t");n=d.call(m,"b")+q.call(m,"b");k={x:v[0]+g,y:v[1]+u,0:v[0]+g,1:v[1]+u,width:s-(g+a),height:i-(u+n)}}k.right=k.x+k.width;k.bottom=k.y+k.height;return k},move:function(j,b,c){var g=this,m=g.getXY(),k=m[0],i=m[1],d=[k-b,i],l=[k+b,i],h=[k,i-b],a=[k,i+b],e={l:d,left:d,r:l,right:l,t:h,top:h,up:h,b:a,bottom:a,down:a};j=j.toLowerCase();g.moveTo(e[j][0],e[j][1],g.animTest.call(g,arguments,c,2))},setLeftTop:function(d,c){var b=this,a=b.dom.style;a.left=b.addUnits(d);a.top=b.addUnits(c);return b},getRegion:function(){return Ext.lib.Dom.getRegion(this.dom)},setBounds:function(b,g,d,a,c){var e=this;if(!c||!e.anim){e.setSize(d,a);e.setLocation(b,g)}else{e.anim({points:{to:[b,g]},width:{to:e.adjustWidth(d)},height:{to:e.adjustHeight(a)}},e.preanim(arguments,4),"motion")}return e},setRegion:function(b,a){return this.setBounds(b.left,b.top,b.right-b.left,b.bottom-b.top,this.animTest.call(this,arguments,a,1))}});Ext.Element.addMethods({scrollTo:function(b,d,a){var e=/top/i.test(b),c=this,g=c.dom,h;if(!a||!c.anim){h="scroll"+(e?"Top":"Left");g[h]=d}else{h="scroll"+(e?"Left":"Top");c.anim({scroll:{to:e?[g[h],d]:[d,g[h]]}},c.preanim(arguments,2),"scroll")}return c},scrollIntoView:function(e,i){var p=Ext.getDom(e)||Ext.getBody().dom,h=this.dom,g=this.getOffsetsTo(p),k=g[0]+p.scrollLeft,u=g[1]+p.scrollTop,q=u+h.offsetHeight,d=k+h.offsetWidth,a=p.clientHeight,m=parseInt(p.scrollTop,10),s=parseInt(p.scrollLeft,10),j=m+a,n=s+p.clientWidth;if(h.offsetHeight>a||uj){p.scrollTop=q-a}}p.scrollTop=p.scrollTop;if(i!==false){if(h.offsetWidth>p.clientWidth||kn){p.scrollLeft=d-p.clientWidth}}p.scrollLeft=p.scrollLeft}return this},scrollChildIntoView:function(b,a){Ext.fly(b,"_scrollChildIntoView").scrollIntoView(this,a)},scroll:function(m,b,d){if(!this.isScrollable()){return false}var e=this.dom,g=e.scrollLeft,p=e.scrollTop,n=e.scrollWidth,k=e.scrollHeight,i=e.clientWidth,a=e.clientHeight,c=false,o,j={l:Math.min(g+b,n-i),r:o=Math.max(g-b,0),t:Math.max(p-b,0),b:Math.min(p+b,k-a)};j.d=j.b;j.u=j.t;m=m.substr(0,1);if((o=j[m])>-1){c=true;this.scrollTo(m=="l"||m=="r"?"left":"top",o,this.preanim(arguments,2))}return c}});Ext.Element.addMethods(function(){var d="visibility",b="display",a="hidden",h="none",c="x-masked",g="x-masked-relative",e=Ext.Element.data;return{isVisible:function(i){var j=!this.isStyle(d,a)&&!this.isStyle(b,h),k=this.dom.parentNode;if(i!==true||!j){return j}while(k&&!(/^body/i.test(k.tagName))){if(!Ext.fly(k,"_isVisible").isVisible()){return false}k=k.parentNode}return true},isDisplayed:function(){return !this.isStyle(b,h)},enableDisplayMode:function(i){this.setVisibilityMode(Ext.Element.DISPLAY);if(!Ext.isEmpty(i)){e(this.dom,"originalDisplay",i)}return this},mask:function(j,n){var p=this,l=p.dom,o=Ext.DomHelper,m="ext-el-mask-msg",i,q;if(!/^body/i.test(l.tagName)&&p.getStyle("position")=="static"){p.addClass(g)}if(i=e(l,"maskMsg")){i.remove()}if(i=e(l,"mask")){i.remove()}q=o.append(l,{cls:"ext-el-mask"},true);e(l,"mask",q);p.addClass(c);q.setDisplayed(true);if(typeof j=="string"){var k=o.append(l,{cls:m,cn:{tag:"div"}},true);e(l,"maskMsg",k);k.dom.className=n?m+" "+n:m;k.dom.firstChild.innerHTML=j;k.setDisplayed(true);k.center(p)}if(Ext.isIE&&!(Ext.isIE7&&Ext.isStrict)&&p.getStyle("height")=="auto"){q.setSize(undefined,p.getHeight())}return q},unmask:function(){var k=this,l=k.dom,i=e(l,"mask"),j=e(l,"maskMsg");if(i){if(j){j.remove();e(l,"maskMsg",undefined)}i.remove();e(l,"mask",undefined);k.removeClass([c,g])}},isMasked:function(){var i=e(this.dom,"mask");return i&&i.isVisible()},createShim:function(){var i=document.createElement("iframe"),j;i.frameBorder="0";i.className="ext-shim";i.src=Ext.SSL_SECURE_URL;j=Ext.get(this.dom.parentNode.insertBefore(i,this.dom));j.autoBoxAdjust=false;return j}}}());Ext.Element.addMethods({addKeyListener:function(b,d,c){var a;if(typeof b!="object"||Ext.isArray(b)){a={key:b,fn:d,scope:c}}else{a={key:b.key,shift:b.shift,ctrl:b.ctrl,alt:b.alt,fn:d,scope:c}}return new Ext.KeyMap(this,a)},addKeyMap:function(a){return new Ext.KeyMap(this,a)}});Ext.CompositeElementLite.importElementMethods();Ext.apply(Ext.CompositeElementLite.prototype,{addElements:function(c,a){if(!c){return this}if(typeof c=="string"){c=Ext.Element.selectorFunction(c,a)}var b=this.elements;Ext.each(c,function(d){b.push(Ext.get(d))});return this},first:function(){return this.item(0)},last:function(){return this.item(this.getCount()-1)},contains:function(a){return this.indexOf(a)!=-1},removeElement:function(d,e){var c=this,a=this.elements,b;Ext.each(d,function(g){if((b=(a[g]||a[g=c.indexOf(g)]))){if(e){if(b.dom){b.remove()}else{Ext.removeNode(b)}}a.splice(g,1)}});return this}});Ext.CompositeElement=Ext.extend(Ext.CompositeElementLite,{constructor:function(b,a){this.elements=[];this.add(b,a)},getElement:function(a){return a},transformElement:function(a){return Ext.get(a)}});Ext.Element.select=function(a,d,b){var c;if(typeof a=="string"){c=Ext.Element.selectorFunction(a,b)}else{if(a.length!==undefined){c=a}else{throw"Invalid selector"}}return(d===true)?new Ext.CompositeElement(c):new Ext.CompositeElementLite(c)};Ext.select=Ext.Element.select;Ext.UpdateManager=Ext.Updater=Ext.extend(Ext.util.Observable,function(){var b="beforeupdate",d="update",c="failure";function a(h){var i=this;i.transaction=null;if(h.argument.form&&h.argument.reset){try{h.argument.form.reset()}catch(j){}}if(i.loadScripts){i.renderer.render(i.el,h,i,g.createDelegate(i,[h]))}else{i.renderer.render(i.el,h,i);g.call(i,h)}}function g(h,i,j){this.fireEvent(i||d,this.el,h);if(Ext.isFunction(h.argument.callback)){h.argument.callback.call(h.argument.scope,this.el,Ext.isEmpty(j)?true:false,h,h.argument.options)}}function e(h){g.call(this,h,c,!!(this.transaction=null))}return{constructor:function(i,h){var j=this;i=Ext.get(i);if(!h&&i.updateManager){return i.updateManager}j.el=i;j.defaultUrl=null;j.addEvents(b,d,c);Ext.apply(j,Ext.Updater.defaults);j.transaction=null;j.refreshDelegate=j.refresh.createDelegate(j);j.updateDelegate=j.update.createDelegate(j);j.formUpdateDelegate=(j.formUpdate||function(){}).createDelegate(j);j.renderer=j.renderer||j.getDefaultRenderer();Ext.Updater.superclass.constructor.call(j)},setRenderer:function(h){this.renderer=h},getRenderer:function(){return this.renderer},getDefaultRenderer:function(){return new Ext.Updater.BasicRenderer()},setDefaultUrl:function(h){this.defaultUrl=h},getEl:function(){return this.el},update:function(i,n,p,l){var k=this,h,j;if(k.fireEvent(b,k.el,i,n)!==false){if(Ext.isObject(i)){h=i;i=h.url;n=n||h.params;p=p||h.callback;l=l||h.discardUrl;j=h.scope;if(!Ext.isEmpty(h.nocache)){k.disableCaching=h.nocache}if(!Ext.isEmpty(h.text)){k.indicatorText='
'+h.text+"
"}if(!Ext.isEmpty(h.scripts)){k.loadScripts=h.scripts}if(!Ext.isEmpty(h.timeout)){k.timeout=h.timeout}}k.showLoading();if(!l){k.defaultUrl=i}if(Ext.isFunction(i)){i=i.call(k)}var m=Ext.apply({},{url:i,params:(Ext.isFunction(n)&&j)?n.createDelegate(j):n,success:a,failure:e,scope:k,callback:undefined,timeout:(k.timeout*1000),disableCaching:k.disableCaching,argument:{options:h,url:i,form:null,callback:p,scope:j||window,params:n}},h);k.transaction=Ext.Ajax.request(m)}},formUpdate:function(k,h,j,l){var i=this;if(i.fireEvent(b,i.el,k,h)!==false){if(Ext.isFunction(h)){h=h.call(i)}k=Ext.getDom(k);i.transaction=Ext.Ajax.request({form:k,url:h,success:a,failure:e,scope:i,timeout:(i.timeout*1000),argument:{url:h,form:k,callback:l,reset:j}});i.showLoading.defer(1,i)}},startAutoRefresh:function(i,j,l,m,h){var k=this;if(h){k.update(j||k.defaultUrl,l,m,true)}if(k.autoRefreshProcId){clearInterval(k.autoRefreshProcId)}k.autoRefreshProcId=setInterval(k.update.createDelegate(k,[j||k.defaultUrl,l,m,true]),i*1000)},stopAutoRefresh:function(){if(this.autoRefreshProcId){clearInterval(this.autoRefreshProcId);delete this.autoRefreshProcId}},isAutoRefreshing:function(){return !!this.autoRefreshProcId},showLoading:function(){if(this.showLoadIndicator){this.el.dom.innerHTML=this.indicatorText}},abort:function(){if(this.transaction){Ext.Ajax.abort(this.transaction)}},isUpdating:function(){return this.transaction?Ext.Ajax.isLoading(this.transaction):false},refresh:function(h){if(this.defaultUrl){this.update(this.defaultUrl,null,h,true)}}}}());Ext.Updater.defaults={timeout:30,disableCaching:false,showLoadIndicator:true,indicatorText:'
Loading...
',loadScripts:false,sslBlankUrl:Ext.SSL_SECURE_URL};Ext.Updater.updateElement=function(d,c,e,b){var a=Ext.get(d).getUpdater();Ext.apply(a,b);a.update(c,e,b?b.callback:null)};Ext.Updater.BasicRenderer=function(){};Ext.Updater.BasicRenderer.prototype={render:function(c,a,b,d){c.update(a.responseText,b.loadScripts,d)}};(function(){Date.useStrict=false;function b(d){var c=Array.prototype.slice.call(arguments,1);return d.replace(/\{(\d+)\}/g,function(e,g){return c[g]})}Date.formatCodeToRegex=function(d,c){var e=Date.parseCodes[d];if(e){e=typeof e=="function"?e():e;Date.parseCodes[d]=e}return e?Ext.applyIf({c:e.c?b(e.c,c||"{0}"):e.c},e):{g:0,c:null,s:Ext.escapeRe(d)}};var a=Date.formatCodeToRegex;Ext.apply(Date,{parseFunctions:{"M$":function(d,c){var e=new RegExp("\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/");var g=(d||"").match(e);return g?new Date(((g[1]||"")+g[2])*1):null}},parseRegexes:[],formatFunctions:{"M$":function(){return"\\/Date("+this.getTime()+")\\/"}},y2kYear:50,MILLI:"ms",SECOND:"s",MINUTE:"mi",HOUR:"h",DAY:"d",MONTH:"mo",YEAR:"y",defaults:{},dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNumbers:{Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11},getShortMonthName:function(c){return Date.monthNames[c].substring(0,3)},getShortDayName:function(c){return Date.dayNames[c].substring(0,3)},getMonthNumber:function(c){return Date.monthNumbers[c.substring(0,1).toUpperCase()+c.substring(1,3).toLowerCase()]},formatContainsHourInfo:(function(){var d=/(\\.)/g,c=/([gGhHisucUOPZ]|M\$)/;return function(e){return c.test(e.replace(d,""))}})(),formatCodes:{d:"String.leftPad(this.getDate(), 2, '0')",D:"Date.getShortDayName(this.getDay())",j:"this.getDate()",l:"Date.dayNames[this.getDay()]",N:"(this.getDay() ? this.getDay() : 7)",S:"this.getSuffix()",w:"this.getDay()",z:"this.getDayOfYear()",W:"String.leftPad(this.getWeekOfYear(), 2, '0')",F:"Date.monthNames[this.getMonth()]",m:"String.leftPad(this.getMonth() + 1, 2, '0')",M:"Date.getShortMonthName(this.getMonth())",n:"(this.getMonth() + 1)",t:"this.getDaysInMonth()",L:"(this.isLeapYear() ? 1 : 0)",o:"(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",Y:"String.leftPad(this.getFullYear(), 4, '0')",y:"('' + this.getFullYear()).substring(2, 4)",a:"(this.getHours() < 12 ? 'am' : 'pm')",A:"(this.getHours() < 12 ? 'AM' : 'PM')",g:"((this.getHours() % 12) ? this.getHours() % 12 : 12)",G:"this.getHours()",h:"String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",H:"String.leftPad(this.getHours(), 2, '0')",i:"String.leftPad(this.getMinutes(), 2, '0')",s:"String.leftPad(this.getSeconds(), 2, '0')",u:"String.leftPad(this.getMilliseconds(), 3, '0')",O:"this.getGMTOffset()",P:"this.getGMTOffset(true)",T:"this.getTimezone()",Z:"(this.getTimezoneOffset() * -60)",c:function(){for(var k="Y-m-dTH:i:sP",h=[],g=0,d=k.length;g= 0 && y >= 0){","v = new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);","v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);","}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){","v = null;","}else{","v = new Date(y < 100 ? 100 : y, m, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);","}","}","}","if(v){","if(zz != null){","v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);","}else if(o){","v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));","}","}","return v;"].join("\n");return function(m){var e=Date.parseRegexes.length,o=1,g=[],l=[],k=false,d="",j=0,h,n;for(;j Date.y2kYear ? 1900 + ty : 2000 + ty;\n",s:"(\\d{1,2})"},a:function(){return a("A")},A:{calcLast:true,g:1,c:"if (/(am)/i.test(results[{0}])) {\nif (!h || h == 12) { h = 0; }\n} else { if (!h || h < 12) { h = (h || 0) + 12; }}",s:"(AM|PM|am|pm)"},g:function(){return a("G")},G:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{1,2})"},h:function(){return a("H")},H:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},i:{g:1,c:"i = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},s:{g:1,c:"s = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},u:{g:1,c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",s:"(\\d+)"},O:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),","mn = o.substring(3,5) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+-]\\d{4})"},P:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),","mn = o.substring(4,6) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+-]\\d{2}:\\d{2})"},T:{g:0,c:null,s:"[A-Z]{1,4}"},Z:{g:1,c:"zz = results[{0}] * 1;\nzz = (-43200 <= zz && zz <= 50400)? zz : null;\n",s:"([+-]?\\d{1,5})"},c:function(){var e=[],c=[a("Y",1),a("m",2),a("d",3),a("h",4),a("i",5),a("s",6),{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"},{c:["if(results[8]) {","if(results[8] == 'Z'){","zz = 0;","}else if (results[8].indexOf(':') > -1){",a("P",8).c,"}else{",a("O",8).c,"}","}"].join("\n")}];for(var g=0,d=c.length;g0?"-":"+")+String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset())/60),2,"0")+(a?":":"")+String.leftPad(Math.abs(this.getTimezoneOffset()%60),2,"0")},getDayOfYear:function(){var b=0,e=this.clone(),a=this.getMonth(),c;for(c=0,e.setDate(1),e.setMonth(0);c28){a=Math.min(a,this.getFirstDateOfMonth().add("mo",c).getLastDateOfMonth().getDate())}e.setDate(a);e.setMonth(this.getMonth()+c);break;case Date.YEAR:e.setFullYear(this.getFullYear()+c);break}return e},between:function(c,a){var b=this.getTime();return c.getTime()<=b&&b<=a.getTime()}});Date.prototype.format=Date.prototype.dateFormat;if(Ext.isSafari&&(navigator.userAgent.match(/WebKit\/(\d+)/)[1]||NaN)<420){Ext.apply(Date.prototype,{_xMonth:Date.prototype.setMonth,_xDate:Date.prototype.setDate,setMonth:function(a){if(a<=-1){var d=Math.ceil(-a),c=Math.ceil(d/12),b=(d%12)?12-d%12:0;this.setFullYear(this.getFullYear()-c);return this._xMonth(b)}else{return this._xMonth(a)}},setDate:function(a){return this.setTime(this.getTime()-(this.getDate()-a)*86400000)}})}Ext.util.MixedCollection=function(b,a){this.items=[];this.map={};this.keys=[];this.length=0;this.addEvents("clear","add","replace","remove","sort");this.allowFunctions=b===true;if(a){this.getKey=a}Ext.util.MixedCollection.superclass.constructor.call(this)};Ext.extend(Ext.util.MixedCollection,Ext.util.Observable,{allowFunctions:false,add:function(b,c){if(arguments.length==1){c=arguments[0];b=this.getKey(c)}if(typeof b!="undefined"&&b!==null){var a=this.map[b];if(typeof a!="undefined"){return this.replace(b,c)}this.map[b]=c}this.length++;this.items.push(c);this.keys.push(b);this.fireEvent("add",this.length-1,c,b);return c},getKey:function(a){return a.id},replace:function(c,d){if(arguments.length==1){d=arguments[0];c=this.getKey(d)}var a=this.map[c];if(typeof c=="undefined"||c===null||typeof a=="undefined"){return this.add(c,d)}var b=this.indexOfKey(c);this.items[b]=d;this.map[c]=d;this.fireEvent("replace",c,a,d);return d},addAll:function(e){if(arguments.length>1||Ext.isArray(e)){var b=arguments.length>1?arguments:e;for(var d=0,a=b.length;d=this.length){return this.add(b,c)}this.length++;this.items.splice(a,0,c);if(typeof b!="undefined"&&b!==null){this.map[b]=c}this.keys.splice(a,0,b);this.fireEvent("add",a,c,b);return c},remove:function(a){return this.removeAt(this.indexOf(a))},removeAt:function(a){if(a=0){this.length--;var c=this.items[a];this.items.splice(a,1);var b=this.keys[a];if(typeof b!="undefined"){delete this.map[b]}this.keys.splice(a,1);this.fireEvent("remove",c,b);return c}return false},removeKey:function(a){return this.removeAt(this.indexOfKey(a))},getCount:function(){return this.length},indexOf:function(a){return this.items.indexOf(a)},indexOfKey:function(a){return this.keys.indexOf(a)},item:function(b){var a=this.map[b],c=a!==undefined?a:(typeof b=="number")?this.items[b]:undefined;return typeof c!="function"||this.allowFunctions?c:null},itemAt:function(a){return this.items[a]},key:function(a){return this.map[a]},contains:function(a){return this.indexOf(a)!=-1},containsKey:function(a){return typeof this.map[a]!="undefined"},clear:function(){this.length=0;this.items=[];this.keys=[];this.map={};this.fireEvent("clear")},first:function(){return this.items[0]},last:function(){return this.items[this.length-1]},_sort:function(k,a,j){var d,e,b=String(a).toUpperCase()=="DESC"?-1:1,h=[],l=this.keys,g=this.items;j=j||function(i,c){return i-c};for(d=0,e=g.length;de?1:(g=a;c--){d[d.length]=b[c]}}return d},filter:function(c,b,d,a){if(Ext.isEmpty(b,false)){return this.clone()}b=this.createValueMatcher(b,d,a);return this.filterBy(function(e){return e&&b.test(e[c])})},filterBy:function(g,e){var h=new Ext.util.MixedCollection();h.getKey=this.getKey;var b=this.keys,d=this.items;for(var c=0,a=d.length;c]+>/gi,stripScriptsRe=/(?:)((\n|\r|.)*?)(?:<\/script>)/ig,nl2brRe=/\r?\n/g;return{ellipsis:function(value,len,word){if(value&&value.length>len){if(word){var vs=value.substr(0,len-2),index=Math.max(vs.lastIndexOf(" "),vs.lastIndexOf("."),vs.lastIndexOf("!"),vs.lastIndexOf("?"));if(index==-1||index<(len-15)){return value.substr(0,len-3)+"..."}else{return vs.substr(0,index)+"..."}}else{return value.substr(0,len-3)+"..."}}return value},undef:function(value){return value!==undefined?value:""},defaultValue:function(value,defaultValue){return value!==undefined&&value!==""?value:defaultValue},htmlEncode:function(value){return !value?value:String(value).replace(/&/g,"&").replace(/>/g,">").replace(/").replace(/</g,"<").replace(/"/g,'"').replace(/&/g,"&")},trim:function(value){return String(value).replace(trimRe,"")},substr:function(value,start,length){return String(value).substr(start,length)},lowercase:function(value){return String(value).toLowerCase()},uppercase:function(value){return String(value).toUpperCase()},capitalize:function(value){return !value?value:value.charAt(0).toUpperCase()+value.substr(1).toLowerCase()},call:function(value,fn){if(arguments.length>2){var args=Array.prototype.slice.call(arguments,2);args.unshift(value);return eval(fn).apply(window,args)}else{return eval(fn).call(window,value)}},usMoney:function(v){v=(Math.round((v-0)*100))/100;v=(v==Math.floor(v))?v+".00":((v*10==Math.floor(v*10))?v+"0":v);v=String(v);var ps=v.split("."),whole=ps[0],sub=ps[1]?"."+ps[1]:".00",r=/(\d+)(\d{3})/;while(r.test(whole)){whole=whole.replace(r,"$1,$2")}v=whole+sub;if(v.charAt(0)=="-"){return"-$"+v.substr(1)}return"$"+v},date:function(v,format){if(!v){return""}if(!Ext.isDate(v)){v=new Date(Date.parse(v))}return v.dateFormat(format||"m/d/Y")},dateRenderer:function(format){return function(v){return Ext.util.Format.date(v,format)}},stripTags:function(v){return !v?v:String(v).replace(stripTagsRE,"")},stripScripts:function(v){return !v?v:String(v).replace(stripScriptsRe,"")},fileSize:function(size){if(size<1024){return size+" bytes"}else{if(size<1048576){return(Math.round(((size*10)/1024))/10)+" KB"}else{return(Math.round(((size*10)/1048576))/10)+" MB"}}},math:function(){var fns={};return function(v,a){if(!fns[a]){fns[a]=new Function("v","return v "+a+";")}return fns[a](v)}}(),round:function(value,precision){var result=Number(value);if(typeof precision=="number"){precision=Math.pow(10,precision);result=Math.round(value*precision)/precision}return result},number:function(v,format){if(!format){return v}v=Ext.num(v,NaN);if(isNaN(v)){return""}var comma=",",dec=".",i18n=false,neg=v<0;v=Math.abs(v);if(format.substr(format.length-2)=="/i"){format=format.substr(0,format.length-2);i18n=true;comma=".";dec=","}var hasComma=format.indexOf(comma)!=-1,psplit=(i18n?format.replace(/[^\d\,]/g,""):format.replace(/[^\d\.]/g,"")).split(dec);if(1")}}}();Ext.XTemplate=function(){Ext.XTemplate.superclass.constructor.apply(this,arguments);var y=this,j=y.html,q=/]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,d=/^]*?for="(.*?)"/,v=/^]*?if="(.*?)"/,x=/^]*?exec="(.*?)"/,r,p=0,k=[],o="values",w="parent",l="xindex",n="xcount",e="return ",c="with(values){ ";j=["",j,""].join("");while((r=j.match(q))){var b=r[0].match(d),a=r[0].match(v),A=r[0].match(x),g=null,h=null,t=null,z=b&&b[1]?b[1]:"";if(a){g=a&&a[1]?a[1]:null;if(g){h=new Function(o,w,l,n,c+e+(Ext.util.Format.htmlDecode(g))+"; }")}}if(A){g=A&&A[1]?A[1]:null;if(g){t=new Function(o,w,l,n,c+(Ext.util.Format.htmlDecode(g))+"; }")}}if(z){switch(z){case".":z=new Function(o,w,c+e+o+"; }");break;case"..":z=new Function(o,w,c+e+w+"; }");break;default:z=new Function(o,w,c+e+z+"; }")}}k.push({id:p,target:z,exec:t,test:h,body:r[1]||""});j=j.replace(r[0],"{xtpl"+p+"}");++p}for(var u=k.length-1;u>=0;--u){y.compileTpl(k[u])}y.master=k[k.length-1];y.tpls=k};Ext.extend(Ext.XTemplate,Ext.Template,{re:/\{([\w\-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,codeRe:/\{\[((?:\\\]|.|\n)*?)\]\}/g,applySubTemplate:function(a,k,j,d,c){var h=this,g,m=h.tpls[a],l,b=[];if((m.test&&!m.test.call(h,k,j,d,c))||(m.exec&&m.exec.call(h,k,j,d,c))){return""}l=m.target?m.target.call(h,k,j):k;g=l.length;j=m.target?k:j;if(m.target&&Ext.isArray(l)){for(var e=0,g=l.length;e=0;--g){d[k[g].selectorText.toLowerCase()]=k[g]}}catch(i){}},getRules:function(h){if(d===null||h){d={};var k=c.styleSheets;for(var j=0,g=k.length;j=37&&a<=40){b.stopEvent()}},destroy:function(){this.disable()},enable:function(){if(this.disabled){if(Ext.isSafari2){this.el.on("keyup",this.stopKeyUp,this)}this.el.on(this.isKeydown()?"keydown":"keypress",this.relay,this);this.disabled=false}},disable:function(){if(!this.disabled){if(Ext.isSafari2){this.el.un("keyup",this.stopKeyUp,this)}this.el.un(this.isKeydown()?"keydown":"keypress",this.relay,this);this.disabled=true}},setDisabled:function(a){this[a?"disable":"enable"]()},isKeydown:function(){return this.forceKeyDown||Ext.EventManager.useKeydown}};Ext.KeyMap=function(c,b,a){this.el=Ext.get(c);this.eventName=a||"keydown";this.bindings=[];if(b){this.addBinding(b)}this.enable()};Ext.KeyMap.prototype={stopEvent:false,addBinding:function(b){if(Ext.isArray(b)){Ext.each(b,function(j){this.addBinding(j)},this);return}var k=b.key,g=b.fn||b.handler,l=b.scope;if(b.stopEvent){this.stopEvent=b.stopEvent}if(typeof k=="string"){var h=[];var e=k.toUpperCase();for(var c=0,d=e.length;c2)?a[2]:null;var h=(i>3)?a[3]:"/";var d=(i>4)?a[4]:null;var g=(i>5)?a[5]:false;document.cookie=c+"="+escape(e)+((b===null)?"":("; expires="+b.toGMTString()))+((h===null)?"":("; path="+h))+((d===null)?"":("; domain="+d))+((g===true)?"; secure":"")},get:function(d){var b=d+"=";var g=b.length;var a=document.cookie.length;var e=0;var c=0;while(e0){return this.ownerCt.items.itemAt(a-1)}}return null},getBubbleTarget:function(){return this.ownerCt}});Ext.reg("component",Ext.Component);Ext.Action=Ext.extend(Object,{constructor:function(a){this.initialConfig=a;this.itemId=a.itemId=(a.itemId||a.id||Ext.id());this.items=[]},isAction:true,setText:function(a){this.initialConfig.text=a;this.callEach("setText",[a])},getText:function(){return this.initialConfig.text},setIconClass:function(a){this.initialConfig.iconCls=a;this.callEach("setIconClass",[a])},getIconClass:function(){return this.initialConfig.iconCls},setDisabled:function(a){this.initialConfig.disabled=a;this.callEach("setDisabled",[a])},enable:function(){this.setDisabled(false)},disable:function(){this.setDisabled(true)},isDisabled:function(){return this.initialConfig.disabled},setHidden:function(a){this.initialConfig.hidden=a;this.callEach("setVisible",[!a])},show:function(){this.setHidden(false)},hide:function(){this.setHidden(true)},isHidden:function(){return this.initialConfig.hidden},setHandler:function(b,a){this.initialConfig.handler=b;this.initialConfig.scope=a;this.callEach("setHandler",[b,a])},each:function(b,a){Ext.each(this.items,b,a)},callEach:function(e,b){var d=this.items;for(var c=0,a=d.length;cj+o.left){k=j-l-c;g=true}if((i+e)>d+o.top){i=d-e-c;g=true}if(k=m){i=m-e-5}}n=[k,i];this.storeXY(n);a.setXY.call(this,n);this.sync()}}return this},getConstrainOffset:function(){return this.shadowOffset},isVisible:function(){return this.visible},showAction:function(){this.visible=true;if(this.useDisplay===true){this.setDisplayed("")}else{if(this.lastXY){a.setXY.call(this,this.lastXY)}else{if(this.lastLT){a.setLeftTop.call(this,this.lastLT[0],this.lastLT[1])}}}},hideAction:function(){this.visible=false;if(this.useDisplay===true){this.setDisplayed(false)}else{this.setLeftTop(-10000,-10000)}},setVisible:function(i,h,k,l,j){if(i){this.showAction()}if(h&&i){var g=function(){this.sync(true);if(l){l()}}.createDelegate(this);a.setVisible.call(this,true,true,k,g,j)}else{if(!i){this.hideUnders(true)}var g=l;if(h){g=function(){this.hideAction();if(l){l()}}.createDelegate(this)}a.setVisible.call(this,i,h,k,g,j);if(i){this.sync(true)}else{if(!h){this.hideAction()}}}return this},storeXY:function(c){delete this.lastLT;this.lastXY=c},storeLeftTop:function(d,c){delete this.lastXY;this.lastLT=[d,c]},beforeFx:function(){this.beforeAction();return Ext.Layer.superclass.beforeFx.apply(this,arguments)},afterFx:function(){Ext.Layer.superclass.afterFx.apply(this,arguments);this.sync(this.isVisible())},beforeAction:function(){if(!this.updating&&this.shadow){this.shadow.hide()}},setLeft:function(c){this.storeLeftTop(c,this.getTop(true));a.setLeft.apply(this,arguments);this.sync();return this},setTop:function(c){this.storeLeftTop(this.getLeft(true),c);a.setTop.apply(this,arguments);this.sync();return this},setLeftTop:function(d,c){this.storeLeftTop(d,c);a.setLeftTop.apply(this,arguments);this.sync();return this},setXY:function(j,h,k,l,i){this.fixDisplay();this.beforeAction();this.storeXY(j);var g=this.createCB(l);a.setXY.call(this,j,h,k,g,i);if(!h){g()}return this},createCB:function(e){var d=this;return function(){d.constrainXY();d.sync(true);if(e){e()}}},setX:function(g,h,j,k,i){this.setXY([g,this.getY()],h,j,k,i);return this},setY:function(k,g,i,j,h){this.setXY([this.getX(),k],g,i,j,h);return this},setSize:function(j,k,i,m,n,l){this.beforeAction();var g=this.createCB(n);a.setSize.call(this,j,k,i,m,g,l);if(!i){g()}return this},setWidth:function(i,h,k,l,j){this.beforeAction();var g=this.createCB(l);a.setWidth.call(this,i,h,k,g,j);if(!h){g()}return this},setHeight:function(j,i,l,m,k){this.beforeAction();var g=this.createCB(m);a.setHeight.call(this,j,i,l,g,k);if(!i){g()}return this},setBounds:function(o,m,p,i,n,k,l,j){this.beforeAction();var g=this.createCB(l);if(!n){this.storeXY([o,m]);a.setXY.call(this,[o,m]);a.setSize.call(this,p,i,n,k,g,j);g()}else{a.setBounds.call(this,o,m,p,i,n,k,g,j)}return this},setZIndex:function(c){this.zindex=c;this.setStyle("z-index",c+2);if(this.shadow){this.shadow.setZIndex(c+1)}if(this.shim){this.shim.setStyle("z-index",c)}return this}})})();Ext.Shadow=function(d){Ext.apply(this,d);if(typeof this.mode!="string"){this.mode=this.defaultMode}var e=this.offset,c={h:0},b=Math.floor(this.offset/2);switch(this.mode.toLowerCase()){case"drop":c.w=0;c.l=c.t=e;c.t-=1;if(Ext.isIE){c.l-=this.offset+b;c.t-=this.offset+b;c.w-=b;c.h-=b;c.t+=1}break;case"sides":c.w=(e*2);c.l=-e;c.t=e-1;if(Ext.isIE){c.l-=(this.offset-b);c.t-=this.offset+b;c.l+=1;c.w-=(this.offset-b)*2;c.w-=b+1;c.h-=1}break;case"frame":c.w=c.h=(e*2);c.l=c.t=-e;c.t+=1;c.h-=2;if(Ext.isIE){c.l-=(this.offset-b);c.t-=(this.offset-b);c.l+=1;c.w-=(this.offset+b+1);c.h-=(this.offset+b);c.h+=1}break}this.adjusts=c};Ext.Shadow.prototype={offset:4,defaultMode:"drop",show:function(a){a=Ext.get(a);if(!this.el){this.el=Ext.Shadow.Pool.pull();if(this.el.dom.nextSibling!=a.dom){this.el.insertBefore(a)}}this.el.setStyle("z-index",this.zIndex||parseInt(a.getStyle("z-index"),10)-1);if(Ext.isIE){this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")"}this.realign(a.getLeft(true),a.getTop(true),a.getWidth(),a.getHeight());this.el.dom.style.display="block"},isVisible:function(){return this.el?true:false},realign:function(b,r,q,g){if(!this.el){return}var n=this.adjusts,k=this.el.dom,u=k.style,i=0,p=(q+n.w),e=(g+n.h),j=p+"px",o=e+"px",m,c;u.left=(b+n.l)+"px";u.top=(r+n.t)+"px";if(u.width!=j||u.height!=o){u.width=j;u.height=o;if(!Ext.isIE){m=k.childNodes;c=Math.max(0,(p-12))+"px";m[0].childNodes[1].style.width=c;m[1].childNodes[1].style.width=c;m[2].childNodes[1].style.width=c;m[1].style.height=Math.max(0,(e-12))+"px"}}},hide:function(){if(this.el){this.el.dom.style.display="none";Ext.Shadow.Pool.push(this.el);delete this.el}},setZIndex:function(a){this.zIndex=a;if(this.el){this.el.setStyle("z-index",a)}}};Ext.Shadow.Pool=function(){var b=[],a=Ext.isIE?'
':'
';return{pull:function(){var c=b.shift();if(!c){c=Ext.get(Ext.DomHelper.insertHtml("beforeBegin",document.body.firstChild,a));c.autoBoxAdjust=false}return c},push:function(c){b.push(c)}}}();Ext.BoxComponent=Ext.extend(Ext.Component,{initComponent:function(){Ext.BoxComponent.superclass.initComponent.call(this);this.addEvents("resize","move")},boxReady:false,deferHeight:false,setSize:function(b,d){if(typeof b=="object"){d=b.height;b=b.width}if(Ext.isDefined(b)&&Ext.isDefined(this.boxMinWidth)&&(bthis.boxMaxWidth)){b=this.boxMaxWidth}if(Ext.isDefined(d)&&Ext.isDefined(this.boxMaxHeight)&&(d>this.boxMaxHeight)){d=this.boxMaxHeight}if(!this.boxReady){this.width=b;this.height=d;return this}if(this.cacheSizes!==false&&this.lastSize&&this.lastSize.width==b&&this.lastSize.height==d){return this}this.lastSize={width:b,height:d};var c=this.adjustSize(b,d),g=c.width,a=c.height,e;if(g!==undefined||a!==undefined){e=this.getResizeEl();if(!this.deferHeight&&g!==undefined&&a!==undefined){e.setSize(g,a)}else{if(!this.deferHeight&&a!==undefined){e.setHeight(a)}else{if(g!==undefined){e.setWidth(g)}}}this.onResize(g,a,b,d);this.fireEvent("resize",this,g,a,b,d)}return this},setWidth:function(a){return this.setSize(a)},setHeight:function(a){return this.setSize(undefined,a)},getSize:function(){return this.getResizeEl().getSize()},getWidth:function(){return this.getResizeEl().getWidth()},getHeight:function(){return this.getResizeEl().getHeight()},getOuterSize:function(){var a=this.getResizeEl();return{width:a.getWidth()+a.getMargins("lr"),height:a.getHeight()+a.getMargins("tb")}},getPosition:function(a){var b=this.getPositionEl();if(a===true){return[b.getLeft(true),b.getTop(true)]}return this.xy||b.getXY()},getBox:function(a){var c=this.getPosition(a);var b=this.getSize();b.x=c[0];b.y=c[1];return b},updateBox:function(a){this.setSize(a.width,a.height);this.setPagePosition(a.x,a.y);return this},getResizeEl:function(){return this.resizeEl||this.el},setAutoScroll:function(a){if(this.rendered){this.getContentTarget().setOverflow(a?"auto":"")}this.autoScroll=a;return this},setPosition:function(a,g){if(a&&typeof a[1]=="number"){g=a[1];a=a[0]}this.x=a;this.y=g;if(!this.boxReady){return this}var b=this.adjustPosition(a,g);var e=b.x,d=b.y;var c=this.getPositionEl();if(e!==undefined||d!==undefined){if(e!==undefined&&d!==undefined){c.setLeftTop(e,d)}else{if(e!==undefined){c.setLeft(e)}else{if(d!==undefined){c.setTop(d)}}}this.onPosition(e,d);this.fireEvent("move",this,e,d)}return this},setPagePosition:function(a,c){if(a&&typeof a[1]=="number"){c=a[1];a=a[0]}this.pageX=a;this.pageY=c;if(!this.boxReady){return}if(a===undefined||c===undefined){return}var b=this.getPositionEl().translatePoints(a,c);this.setPosition(b.left,b.top);return this},afterRender:function(){Ext.BoxComponent.superclass.afterRender.call(this);if(this.resizeEl){this.resizeEl=Ext.get(this.resizeEl)}if(this.positionEl){this.positionEl=Ext.get(this.positionEl)}this.boxReady=true;Ext.isDefined(this.autoScroll)&&this.setAutoScroll(this.autoScroll);this.setSize(this.width,this.height);if(this.x||this.y){this.setPosition(this.x,this.y)}else{if(this.pageX||this.pageY){this.setPagePosition(this.pageX,this.pageY)}}},syncSize:function(){delete this.lastSize;this.setSize(this.autoWidth?undefined:this.getResizeEl().getWidth(),this.autoHeight?undefined:this.getResizeEl().getHeight());return this},onResize:function(d,b,a,c){},onPosition:function(a,b){},adjustSize:function(a,b){if(this.autoWidth){a="auto"}if(this.autoHeight){b="auto"}return{width:a,height:b}},adjustPosition:function(a,b){return{x:a,y:b}}});Ext.reg("box",Ext.BoxComponent);Ext.Spacer=Ext.extend(Ext.BoxComponent,{autoEl:"div"});Ext.reg("spacer",Ext.Spacer);Ext.SplitBar=function(c,e,b,d,a){this.el=Ext.get(c,true);this.el.dom.unselectable="on";this.resizingEl=Ext.get(e,true);this.orientation=b||Ext.SplitBar.HORIZONTAL;this.minSize=0;this.maxSize=2000;this.animate=false;this.useShim=false;this.shim=null;if(!a){this.proxy=Ext.SplitBar.createProxy(this.orientation)}else{this.proxy=Ext.get(a).dom}this.dd=new Ext.dd.DDProxy(this.el.dom.id,"XSplitBars",{dragElId:this.proxy.id});this.dd.b4StartDrag=this.onStartProxyDrag.createDelegate(this);this.dd.endDrag=this.onEndProxyDrag.createDelegate(this);this.dragSpecs={};this.adapter=new Ext.SplitBar.BasicLayoutAdapter();this.adapter.init(this);if(this.orientation==Ext.SplitBar.HORIZONTAL){this.placement=d||(this.el.getX()>this.resizingEl.getX()?Ext.SplitBar.LEFT:Ext.SplitBar.RIGHT);this.el.addClass("x-splitbar-h")}else{this.placement=d||(this.el.getY()>this.resizingEl.getY()?Ext.SplitBar.TOP:Ext.SplitBar.BOTTOM);this.el.addClass("x-splitbar-v")}this.addEvents("resize","moved","beforeresize","beforeapply");Ext.SplitBar.superclass.constructor.call(this)};Ext.extend(Ext.SplitBar,Ext.util.Observable,{onStartProxyDrag:function(a,e){this.fireEvent("beforeresize",this);this.overlay=Ext.DomHelper.append(document.body,{cls:"x-drag-overlay",html:" "},true);this.overlay.unselectable();this.overlay.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true));this.overlay.show();Ext.get(this.proxy).setDisplayed("block");var c=this.adapter.getElementSize(this);this.activeMinSize=this.getMinimumSize();this.activeMaxSize=this.getMaximumSize();var d=c-this.activeMinSize;var b=Math.max(this.activeMaxSize-c,0);if(this.orientation==Ext.SplitBar.HORIZONTAL){this.dd.resetConstraints();this.dd.setXConstraint(this.placement==Ext.SplitBar.LEFT?d:b,this.placement==Ext.SplitBar.LEFT?b:d,this.tickSize);this.dd.setYConstraint(0,0)}else{this.dd.resetConstraints();this.dd.setXConstraint(0,0);this.dd.setYConstraint(this.placement==Ext.SplitBar.TOP?d:b,this.placement==Ext.SplitBar.TOP?b:d,this.tickSize)}this.dragSpecs.startSize=c;this.dragSpecs.startPoint=[a,e];Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd,a,e)},onEndProxyDrag:function(c){Ext.get(this.proxy).setDisplayed(false);var b=Ext.lib.Event.getXY(c);if(this.overlay){Ext.destroy(this.overlay);delete this.overlay}var a;if(this.orientation==Ext.SplitBar.HORIZONTAL){a=this.dragSpecs.startSize+(this.placement==Ext.SplitBar.LEFT?b[0]-this.dragSpecs.startPoint[0]:this.dragSpecs.startPoint[0]-b[0])}else{a=this.dragSpecs.startSize+(this.placement==Ext.SplitBar.TOP?b[1]-this.dragSpecs.startPoint[1]:this.dragSpecs.startPoint[1]-b[1])}a=Math.min(Math.max(a,this.activeMinSize),this.activeMaxSize);if(a!=this.dragSpecs.startSize){if(this.fireEvent("beforeapply",this,a)!==false){this.adapter.setElementSize(this,a);this.fireEvent("moved",this,a);this.fireEvent("resize",this,a)}}},getAdapter:function(){return this.adapter},setAdapter:function(a){this.adapter=a;this.adapter.init(this)},getMinimumSize:function(){return this.minSize},setMinimumSize:function(a){this.minSize=a},getMaximumSize:function(){return this.maxSize},setMaximumSize:function(a){this.maxSize=a},setCurrentSize:function(b){var a=this.animate;this.animate=false;this.adapter.setElementSize(this,b);this.animate=a},destroy:function(a){Ext.destroy(this.shim,Ext.get(this.proxy));this.dd.unreg();if(a){this.el.remove()}this.purgeListeners()}});Ext.SplitBar.createProxy=function(b){var c=new Ext.Element(document.createElement("div"));document.body.appendChild(c.dom);c.unselectable();var a="x-splitbar-proxy";c.addClass(a+" "+(b==Ext.SplitBar.HORIZONTAL?a+"-h":a+"-v"));return c.dom};Ext.SplitBar.BasicLayoutAdapter=function(){};Ext.SplitBar.BasicLayoutAdapter.prototype={init:function(a){},getElementSize:function(a){if(a.orientation==Ext.SplitBar.HORIZONTAL){return a.resizingEl.getWidth()}else{return a.resizingEl.getHeight()}},setElementSize:function(b,a,c){if(b.orientation==Ext.SplitBar.HORIZONTAL){if(!b.animate){b.resizingEl.setWidth(a);if(c){c(b,a)}}else{b.resizingEl.setWidth(a,true,0.1,c,"easeOut")}}else{if(!b.animate){b.resizingEl.setHeight(a);if(c){c(b,a)}}else{b.resizingEl.setHeight(a,true,0.1,c,"easeOut")}}}};Ext.SplitBar.AbsoluteLayoutAdapter=function(a){this.basic=new Ext.SplitBar.BasicLayoutAdapter();this.container=Ext.get(a)};Ext.SplitBar.AbsoluteLayoutAdapter.prototype={init:function(a){this.basic.init(a)},getElementSize:function(a){return this.basic.getElementSize(a)},setElementSize:function(b,a,c){this.basic.setElementSize(b,a,this.moveSplitter.createDelegate(this,[b]))},moveSplitter:function(a){var b=Ext.SplitBar;switch(a.placement){case b.LEFT:a.el.setX(a.resizingEl.getRight());break;case b.RIGHT:a.el.setStyle("right",(this.container.getWidth()-a.resizingEl.getLeft())+"px");break;case b.TOP:a.el.setY(a.resizingEl.getBottom());break;case b.BOTTOM:a.el.setY(a.resizingEl.getTop()-a.el.getHeight());break}}};Ext.SplitBar.VERTICAL=1;Ext.SplitBar.HORIZONTAL=2;Ext.SplitBar.LEFT=1;Ext.SplitBar.RIGHT=2;Ext.SplitBar.TOP=3;Ext.SplitBar.BOTTOM=4;Ext.Container=Ext.extend(Ext.BoxComponent,{bufferResize:50,autoDestroy:true,forceLayout:false,defaultType:"panel",resizeEvent:"resize",bubbleEvents:["add","remove"],initComponent:function(){Ext.Container.superclass.initComponent.call(this);this.addEvents("afterlayout","beforeadd","beforeremove","add","remove");var a=this.items;if(a){delete this.items;this.add(a)}},initItems:function(){if(!this.items){this.items=new Ext.util.MixedCollection(false,this.getComponentId);this.getLayout()}},setLayout:function(a){if(this.layout&&this.layout!=a){this.layout.setContainer(null)}this.layout=a;this.initItems();a.setContainer(this)},afterRender:function(){Ext.Container.superclass.afterRender.call(this);if(!this.layout){this.layout="auto"}if(Ext.isObject(this.layout)&&!this.layout.layout){this.layoutConfig=this.layout;this.layout=this.layoutConfig.type}if(Ext.isString(this.layout)){this.layout=new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig)}this.setLayout(this.layout);if(this.activeItem!==undefined&&this.layout.setActiveItem){var a=this.activeItem;delete this.activeItem;this.layout.setActiveItem(a)}if(!this.ownerCt){this.doLayout(false,true)}if(this.monitorResize===true){Ext.EventManager.onWindowResize(this.doLayout,this,[false])}},getLayoutTarget:function(){return this.el},getComponentId:function(a){return a.getItemId()},add:function(b){this.initItems();var e=arguments.length>1;if(e||Ext.isArray(b)){var a=[];Ext.each(e?arguments:b,function(h){a.push(this.add(h))},this);return a}var g=this.lookupComponent(this.applyDefaults(b));var d=this.items.length;if(this.fireEvent("beforeadd",this,g,d)!==false&&this.onBeforeAdd(g)!==false){this.items.add(g);g.onAdded(this,d);this.onAdd(g);this.fireEvent("add",this,g,d)}return g},onAdd:function(a){},onAdded:function(a,b){this.ownerCt=a;this.initRef();this.cascade(function(d){d.initRef()});this.fireEvent("added",this,a,b)},insert:function(e,b){var d=arguments,h=d.length,a=[],g,j;this.initItems();if(h>2){for(g=h-1;g>=1;--g){a.push(this.insert(e,d[g]))}return a}j=this.lookupComponent(this.applyDefaults(b));e=Math.min(e,this.items.length);if(this.fireEvent("beforeadd",this,j,e)!==false&&this.onBeforeAdd(j)!==false){if(j.ownerCt==this){this.items.remove(j)}this.items.insert(e,j);j.onAdded(this,e);this.onAdd(j);this.fireEvent("add",this,j,e)}return j},applyDefaults:function(b){var a=this.defaults;if(a){if(Ext.isFunction(a)){a=a.call(this,b)}if(Ext.isString(b)){b=Ext.ComponentMgr.get(b);Ext.apply(b,a)}else{if(!b.events){Ext.applyIf(b.isAction?b.initialConfig:b,a)}else{Ext.apply(b,a)}}}return b},onBeforeAdd:function(a){if(a.ownerCt){a.ownerCt.remove(a,false)}if(this.hideBorders===true){a.border=(a.border===true)}},remove:function(a,b){this.initItems();var d=this.getComponent(a);if(d&&this.fireEvent("beforeremove",this,d)!==false){this.doRemove(d,b);this.fireEvent("remove",this,d)}return d},onRemove:function(a){},doRemove:function(e,d){var b=this.layout,a=b&&this.rendered;if(a){b.onRemove(e)}this.items.remove(e);e.onRemoved();this.onRemove(e);if(d===true||(d!==false&&this.autoDestroy)){e.destroy()}if(a){b.afterRemove(e)}},removeAll:function(c){this.initItems();var e,g=[],b=[];this.items.each(function(h){g.push(h)});for(var d=0,a=g.length;d','','
','
',"");a.disableFormats=true;return a.compile()})(),destroy:function(){if(this.resizeTask&&this.resizeTask.cancel){this.resizeTask.cancel()}if(this.container){this.container.un(this.container.resizeEvent,this.onResize,this)}if(!Ext.isEmpty(this.targetCls)){var a=this.container.getLayoutTarget();if(a){a.removeClass(this.targetCls)}}}});Ext.layout.AutoLayout=Ext.extend(Ext.layout.ContainerLayout,{type:"auto",monitorResize:true,onLayout:function(d,g){Ext.layout.AutoLayout.superclass.onLayout.call(this,d,g);var e=this.getRenderedItems(d),a=e.length,b,h;for(b=0;b0){b.setSize(a)}}});Ext.Container.LAYOUTS.fit=Ext.layout.FitLayout;Ext.layout.CardLayout=Ext.extend(Ext.layout.FitLayout,{deferredRender:false,layoutOnCardChange:false,renderHidden:true,type:"card",setActiveItem:function(d){var a=this.activeItem,b=this.container;d=b.getComponent(d);if(d&&a!=d){if(a){a.hide();if(a.hidden!==true){return false}a.fireEvent("deactivate",a)}var c=d.doLayout&&(this.layoutOnCardChange||!d.rendered);this.activeItem=d;delete d.deferLayout;d.show();this.layout();if(c){d.doLayout()}d.fireEvent("activate",d)}},renderAll:function(a,b){if(this.deferredRender){this.renderItem(this.activeItem,undefined,b)}else{Ext.layout.CardLayout.superclass.renderAll.call(this,a,b)}}});Ext.Container.LAYOUTS.card=Ext.layout.CardLayout;Ext.layout.AnchorLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"anchor",defaultAnchor:"100%",parseAnchorRE:/^(r|right|b|bottom)$/i,getLayoutTargetSize:function(){var b=this.container.getLayoutTarget(),a={};if(b){a=b.getViewSize();if(Ext.isIE&&Ext.isStrict&&a.width==0){a=b.getStyleSize()}a.width-=b.getPadding("lr");a.height-=b.getPadding("tb")}return a},onLayout:function(m,w){Ext.layout.AnchorLayout.superclass.onLayout.call(this,m,w);var p=this.getLayoutTargetSize(),k=p.width,o=p.height,q=w.getStyle("overflow"),n=this.getRenderedItems(m),t=n.length,g=[],j,a,v,l,h,c,e,d,u=0,s,b;if(k<20&&o<20){return}if(m.anchorSize){if(typeof m.anchorSize=="number"){a=m.anchorSize}else{a=m.anchorSize.width;v=m.anchorSize.height}}else{a=m.initialConfig.width;v=m.initialConfig.height}for(s=0;s ');b.disableFormats=true;b.compile();Ext.layout.BorderLayout.Region.prototype.toolTemplate=b}this.collapsedEl=this.targetEl.createChild({cls:"x-layout-collapsed x-layout-collapsed-"+this.position,id:this.panel.id+"-xcollapsed"});this.collapsedEl.enableDisplayMode("block");if(this.collapseMode=="mini"){this.collapsedEl.addClass("x-layout-cmini-"+this.position);this.miniCollapsedEl=this.collapsedEl.createChild({cls:"x-layout-mini x-layout-mini-"+this.position,html:" "});this.miniCollapsedEl.addClassOnOver("x-layout-mini-over");this.collapsedEl.addClassOnOver("x-layout-collapsed-over");this.collapsedEl.on("click",this.onExpandClick,this,{stopEvent:true})}else{if(this.collapsible!==false&&!this.hideCollapseTool){var a=this.expandToolEl=this.toolTemplate.append(this.collapsedEl.dom,{id:"expand-"+this.position},true);a.addClassOnOver("x-tool-expand-"+this.position+"-over");a.on("click",this.onExpandClick,this,{stopEvent:true})}if(this.floatable!==false||this.titleCollapse){this.collapsedEl.addClassOnOver("x-layout-collapsed-over");this.collapsedEl.on("click",this[this.floatable?"collapseClick":"onExpandClick"],this)}}}return this.collapsedEl},onExpandClick:function(a){if(this.isSlid){this.panel.expand(false)}else{this.panel.expand()}},onCollapseClick:function(a){this.panel.collapse()},beforeCollapse:function(c,a){this.lastAnim=a;if(this.splitEl){this.splitEl.hide()}this.getCollapsedEl().show();var b=this.panel.getEl();this.originalZIndex=b.getStyle("z-index");b.setStyle("z-index",100);this.isCollapsed=true;this.layout.layout()},onCollapse:function(a){this.panel.el.setStyle("z-index",1);if(this.lastAnim===false||this.panel.animCollapse===false){this.getCollapsedEl().dom.style.visibility="visible"}else{this.getCollapsedEl().slideIn(this.panel.slideAnchor,{duration:0.2})}this.state.collapsed=true;this.panel.saveState()},beforeExpand:function(a){if(this.isSlid){this.afterSlideIn()}var b=this.getCollapsedEl();this.el.show();if(this.position=="east"||this.position=="west"){this.panel.setSize(undefined,b.getHeight())}else{this.panel.setSize(b.getWidth(),undefined)}b.hide();b.dom.style.visibility="hidden";this.panel.el.setStyle("z-index",this.floatingZIndex)},onExpand:function(){this.isCollapsed=false;if(this.splitEl){this.splitEl.show()}this.layout.layout();this.panel.el.setStyle("z-index",this.originalZIndex);this.state.collapsed=false;this.panel.saveState()},collapseClick:function(a){if(this.isSlid){a.stopPropagation();this.slideIn()}else{a.stopPropagation();this.slideOut()}},onHide:function(){if(this.isCollapsed){this.getCollapsedEl().hide()}else{if(this.splitEl){this.splitEl.hide()}}},onShow:function(){if(this.isCollapsed){this.getCollapsedEl().show()}else{if(this.splitEl){this.splitEl.show()}}},isVisible:function(){return !this.panel.hidden},getMargins:function(){return this.isCollapsed&&this.cmargins?this.cmargins:this.margins},getSize:function(){return this.isCollapsed?this.getCollapsedEl().getSize():this.panel.getSize()},setPanel:function(a){this.panel=a},getMinWidth:function(){return this.minWidth},getMinHeight:function(){return this.minHeight},applyLayoutCollapsed:function(a){var b=this.getCollapsedEl();b.setLeftTop(a.x,a.y);b.setSize(a.width,a.height)},applyLayout:function(a){if(this.isCollapsed){this.applyLayoutCollapsed(a)}else{this.panel.setPosition(a.x,a.y);this.panel.setSize(a.width,a.height)}},beforeSlide:function(){this.panel.beforeEffect()},afterSlide:function(){this.panel.afterEffect()},initAutoHide:function(){if(this.autoHide!==false){if(!this.autoHideHd){this.autoHideSlideTask=new Ext.util.DelayedTask(this.slideIn,this);this.autoHideHd={mouseout:function(a){if(!a.within(this.el,true)){this.autoHideSlideTask.delay(500)}},mouseover:function(a){this.autoHideSlideTask.cancel()},scope:this}}this.el.on(this.autoHideHd);this.collapsedEl.on(this.autoHideHd)}},clearAutoHide:function(){if(this.autoHide!==false){this.el.un("mouseout",this.autoHideHd.mouseout);this.el.un("mouseover",this.autoHideHd.mouseover);this.collapsedEl.un("mouseout",this.autoHideHd.mouseout);this.collapsedEl.un("mouseover",this.autoHideHd.mouseover)}},clearMonitor:function(){Ext.getDoc().un("click",this.slideInIf,this)},slideOut:function(){if(this.isSlid||this.el.hasActiveFx()){return}this.isSlid=true;var b=this.panel.tools,c,a;if(b&&b.toggle){b.toggle.hide()}this.el.show();a=this.panel.collapsed;this.panel.collapsed=false;if(this.position=="east"||this.position=="west"){c=this.panel.deferHeight;this.panel.deferHeight=false;this.panel.setSize(undefined,this.collapsedEl.getHeight());this.panel.deferHeight=c}else{this.panel.setSize(this.collapsedEl.getWidth(),undefined)}this.panel.collapsed=a;this.restoreLT=[this.el.dom.style.left,this.el.dom.style.top];this.el.alignTo(this.collapsedEl,this.getCollapseAnchor());this.el.setStyle("z-index",this.floatingZIndex+2);this.panel.el.replaceClass("x-panel-collapsed","x-panel-floating");if(this.animFloat!==false){this.beforeSlide();this.el.slideIn(this.getSlideAnchor(),{callback:function(){this.afterSlide();this.initAutoHide();Ext.getDoc().on("click",this.slideInIf,this)},scope:this,block:true})}else{this.initAutoHide();Ext.getDoc().on("click",this.slideInIf,this)}},afterSlideIn:function(){this.clearAutoHide();this.isSlid=false;this.clearMonitor();this.el.setStyle("z-index","");this.panel.el.replaceClass("x-panel-floating","x-panel-collapsed");this.el.dom.style.left=this.restoreLT[0];this.el.dom.style.top=this.restoreLT[1];var a=this.panel.tools;if(a&&a.toggle){a.toggle.show()}},slideIn:function(a){if(!this.isSlid||this.el.hasActiveFx()){Ext.callback(a);return}this.isSlid=false;if(this.animFloat!==false){this.beforeSlide();this.el.slideOut(this.getSlideAnchor(),{callback:function(){this.el.hide();this.afterSlide();this.afterSlideIn();Ext.callback(a)},scope:this,block:true})}else{this.el.hide();this.afterSlideIn()}},slideInIf:function(a){if(!a.within(this.el)){this.slideIn()}},anchors:{west:"left",east:"right",north:"top",south:"bottom"},sanchors:{west:"l",east:"r",north:"t",south:"b"},canchors:{west:"tl-tr",east:"tr-tl",north:"tl-bl",south:"bl-tl"},getAnchor:function(){return this.anchors[this.position]},getCollapseAnchor:function(){return this.canchors[this.position]},getSlideAnchor:function(){return this.sanchors[this.position]},getAlignAdj:function(){var a=this.cmargins;switch(this.position){case"west":return[0,0];break;case"east":return[0,0];break;case"north":return[0,0];break;case"south":return[0,0];break}},getExpandAdj:function(){var b=this.collapsedEl,a=this.cmargins;switch(this.position){case"west":return[-(a.right+b.getWidth()+a.left),0];break;case"east":return[a.right+b.getWidth()+a.left,0];break;case"north":return[0,-(a.top+a.bottom+b.getHeight())];break;case"south":return[0,a.top+a.bottom+b.getHeight()];break}},destroy:function(){if(this.autoHideSlideTask&&this.autoHideSlideTask.cancel){this.autoHideSlideTask.cancel()}Ext.destroyMembers(this,"miniCollapsedEl","collapsedEl","expandToolEl")}};Ext.layout.BorderLayout.SplitRegion=function(b,a,c){Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this,b,a,c);this.applyLayout=this.applyFns[c]};Ext.extend(Ext.layout.BorderLayout.SplitRegion,Ext.layout.BorderLayout.Region,{splitTip:"Drag to resize.",collapsibleSplitTip:"Drag to resize. Double click to hide.",useSplitTips:false,splitSettings:{north:{orientation:Ext.SplitBar.VERTICAL,placement:Ext.SplitBar.TOP,maxFn:"getVMaxSize",minProp:"minHeight",maxProp:"maxHeight"},south:{orientation:Ext.SplitBar.VERTICAL,placement:Ext.SplitBar.BOTTOM,maxFn:"getVMaxSize",minProp:"minHeight",maxProp:"maxHeight"},east:{orientation:Ext.SplitBar.HORIZONTAL,placement:Ext.SplitBar.RIGHT,maxFn:"getHMaxSize",minProp:"minWidth",maxProp:"maxWidth"},west:{orientation:Ext.SplitBar.HORIZONTAL,placement:Ext.SplitBar.LEFT,maxFn:"getHMaxSize",minProp:"minWidth",maxProp:"maxWidth"}},applyFns:{west:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;this.panel.setPosition(c.x,c.y);var a=d.offsetWidth;b.left=(c.x+c.width-a)+"px";b.top=(c.y)+"px";b.height=Math.max(0,c.height)+"px";this.panel.setSize(c.width-a,c.height)},east:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;var a=d.offsetWidth;this.panel.setPosition(c.x+a,c.y);b.left=(c.x)+"px";b.top=(c.y)+"px";b.height=Math.max(0,c.height)+"px";this.panel.setSize(c.width-a,c.height)},north:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;var a=d.offsetHeight;this.panel.setPosition(c.x,c.y);b.left=(c.x)+"px";b.top=(c.y+c.height-a)+"px";b.width=Math.max(0,c.width)+"px";this.panel.setSize(c.width,c.height-a)},south:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;var a=d.offsetHeight;this.panel.setPosition(c.x,c.y+a);b.left=(c.x)+"px";b.top=(c.y)+"px";b.width=Math.max(0,c.width)+"px";this.panel.setSize(c.width,c.height-a)}},render:function(a,c){Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this,a,c);var d=this.position;this.splitEl=a.createChild({cls:"x-layout-split x-layout-split-"+d,html:" ",id:this.panel.id+"-xsplit"});if(this.collapseMode=="mini"){this.miniSplitEl=this.splitEl.createChild({cls:"x-layout-mini x-layout-mini-"+d,html:" "});this.miniSplitEl.addClassOnOver("x-layout-mini-over");this.miniSplitEl.on("click",this.onCollapseClick,this,{stopEvent:true})}var b=this.splitSettings[d];this.split=new Ext.SplitBar(this.splitEl.dom,c.el,b.orientation);this.split.tickSize=this.tickSize;this.split.placement=b.placement;this.split.getMaximumSize=this[b.maxFn].createDelegate(this);this.split.minSize=this.minSize||this[b.minProp];this.split.on("beforeapply",this.onSplitMove,this);this.split.useShim=this.useShim===true;this.maxSize=this.maxSize||this[b.maxProp];if(c.hidden){this.splitEl.hide()}if(this.useSplitTips){this.splitEl.dom.title=this.collapsible?this.collapsibleSplitTip:this.splitTip}if(this.collapsible){this.splitEl.on("dblclick",this.onCollapseClick,this)}},getSize:function(){if(this.isCollapsed){return this.collapsedEl.getSize()}var a=this.panel.getSize();if(this.position=="north"||this.position=="south"){a.height+=this.splitEl.dom.offsetHeight}else{a.width+=this.splitEl.dom.offsetWidth}return a},getHMaxSize:function(){var b=this.maxSize||10000;var a=this.layout.center;return Math.min(b,(this.el.getWidth()+a.el.getWidth())-a.getMinWidth())},getVMaxSize:function(){var b=this.maxSize||10000;var a=this.layout.center;return Math.min(b,(this.el.getHeight()+a.el.getHeight())-a.getMinHeight())},onSplitMove:function(b,a){var c=this.panel.getSize();this.lastSplitSize=a;if(this.position=="north"||this.position=="south"){this.panel.setSize(c.width,a);this.state.height=a}else{this.panel.setSize(a,c.height);this.state.width=a}this.layout.layout();this.panel.saveState();return false},getSplitBar:function(){return this.split},destroy:function(){Ext.destroy(this.miniSplitEl,this.split,this.splitEl);Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this)}});Ext.Container.LAYOUTS.border=Ext.layout.BorderLayout;Ext.layout.FormLayout=Ext.extend(Ext.layout.AnchorLayout,{labelSeparator:":",trackLabels:true,type:"form",onRemove:function(d){Ext.layout.FormLayout.superclass.onRemove.call(this,d);if(this.trackLabels){d.un("show",this.onFieldShow,this);d.un("hide",this.onFieldHide,this)}var b=d.getPositionEl(),a=d.getItemCt&&d.getItemCt();if(d.rendered&&a){if(b&&b.dom){b.insertAfter(a)}Ext.destroy(a);Ext.destroyMembers(d,"label","itemCt");if(d.customItemCt){Ext.destroyMembers(d,"getItemCt","customItemCt")}}},setContainer:function(a){Ext.layout.FormLayout.superclass.setContainer.call(this,a);if(a.labelAlign){a.addClass("x-form-label-"+a.labelAlign)}if(a.hideLabels){Ext.apply(this,{labelStyle:"display:none",elementStyle:"padding-left:0;",labelAdjust:0})}else{this.labelSeparator=Ext.isDefined(a.labelSeparator)?a.labelSeparator:this.labelSeparator;a.labelWidth=a.labelWidth||100;if(Ext.isNumber(a.labelWidth)){var b=Ext.isNumber(a.labelPad)?a.labelPad:5;Ext.apply(this,{labelAdjust:a.labelWidth+b,labelStyle:"width:"+a.labelWidth+"px;",elementStyle:"padding-left:"+(a.labelWidth+b)+"px"})}if(a.labelAlign=="top"){Ext.apply(this,{labelStyle:"width:auto;",labelAdjust:0,elementStyle:"padding-left:0;"})}}},isHide:function(a){return a.hideLabel||this.container.hideLabels},onFieldShow:function(a){a.getItemCt().removeClass("x-hide-"+a.hideMode);if(a.isComposite){a.doLayout()}},onFieldHide:function(a){a.getItemCt().addClass("x-hide-"+a.hideMode)},getLabelStyle:function(e){var b="",c=[this.labelStyle,e];for(var d=0,a=c.length;d=b)||(this.cells[c]&&this.cells[c][a])){if(b&&a>=b){c++;a=0}else{a++}}return[a,c]},renderItem:function(e,a,d){if(!this.table){this.table=d.createChild(Ext.apply({tag:"table",cls:"x-table-layout",cellspacing:0,cn:{tag:"tbody"}},this.tableAttrs),null,true)}if(e&&!e.rendered){e.render(this.getNextCell(e));this.configureItem(e)}else{if(e&&!this.isValidParent(e,d)){var b=this.getNextCell(e);b.insertBefore(e.getPositionEl().dom,null);e.container=Ext.get(b);this.configureItem(e)}}},isValidParent:function(b,a){return b.getPositionEl().up("table",5).dom.parentNode===(a.dom||a)},destroy:function(){delete this.table;Ext.layout.TableLayout.superclass.destroy.call(this)}});Ext.Container.LAYOUTS.table=Ext.layout.TableLayout;Ext.layout.AbsoluteLayout=Ext.extend(Ext.layout.AnchorLayout,{extraCls:"x-abs-layout-item",type:"absolute",onLayout:function(a,b){b.position();this.paddingLeft=b.getPadding("l");this.paddingTop=b.getPadding("t");Ext.layout.AbsoluteLayout.superclass.onLayout.call(this,a,b)},adjustWidthAnchor:function(b,a){return b?b-a.getPosition(true)[0]+this.paddingLeft:b},adjustHeightAnchor:function(b,a){return b?b-a.getPosition(true)[1]+this.paddingTop:b}});Ext.Container.LAYOUTS.absolute=Ext.layout.AbsoluteLayout;Ext.layout.BoxLayout=Ext.extend(Ext.layout.ContainerLayout,{defaultMargins:{left:0,top:0,right:0,bottom:0},padding:"0",pack:"start",monitorResize:true,type:"box",scrollOffset:0,extraCls:"x-box-item",targetCls:"x-box-layout-ct",innerCls:"x-box-inner",constructor:function(a){Ext.layout.BoxLayout.superclass.constructor.call(this,a);if(Ext.isString(this.defaultMargins)){this.defaultMargins=this.parseMargins(this.defaultMargins)}var d=this.overflowHandler;if(typeof d=="string"){d={type:d}}var c="none";if(d&&d.type!=undefined){c=d.type}var b=Ext.layout.boxOverflow[c];if(b[this.type]){b=b[this.type]}this.overflowHandler=new b(this,d)},onLayout:function(b,h){Ext.layout.BoxLayout.superclass.onLayout.call(this,b,h);var d=this.getLayoutTargetSize(),i=this.getVisibleItems(b),c=this.calculateChildBoxes(i,d),g=c.boxes,j=c.meta;if(d.width>0){var k=this.overflowHandler,a=j.tooNarrow?"handleOverflow":"clearOverflow";var e=k[a](c,d);if(e){if(e.targetSize){d=e.targetSize}if(e.recalculate){i=this.getVisibleItems(b);c=this.calculateChildBoxes(i,d);g=c.boxes}}}this.layoutTargetLastSize=d;this.childBoxCache=c;this.updateInnerCtSize(d,c);this.updateChildBoxes(g);this.handleTargetOverflow(d,b,h)},updateChildBoxes:function(c){for(var b=0,e=c.length;b(None)',constructor:function(a){Ext.layout.boxOverflow.Menu.superclass.constructor.apply(this,arguments);this.menuItems=[]},createInnerElements:function(){if(!this.afterCt){this.afterCt=this.layout.innerCt.insertSibling({cls:this.afterCls},"before")}},clearOverflow:function(a,g){var e=g.width+(this.afterCt?this.afterCt.getWidth():0),b=this.menuItems;this.hideTrigger();for(var c=0,d=b.length;ci.width;return l}},handleOverflow:function(d,h){this.showTrigger();var k=h.width-this.afterCt.getWidth(),l=d.boxes,e=0,r=false;for(var o=0,c=l.length;o=0;j--){var q=l[j].component,p=l[j].left+l[j].width;if(p>=k){this.menuItems.unshift({component:q,width:l[j].width});q.hide()}else{break}}}if(this.menuItems.length==0){this.hideTrigger()}return{targetSize:{height:h.height,width:k},recalculate:r}}});Ext.layout.boxOverflow.menu.hbox=Ext.layout.boxOverflow.HorizontalMenu;Ext.layout.boxOverflow.Scroller=Ext.extend(Ext.layout.boxOverflow.None,{animateScroll:true,scrollIncrement:100,wheelIncrement:3,scrollRepeatInterval:400,scrollDuration:0.4,beforeCls:"x-strip-left",afterCls:"x-strip-right",scrollerCls:"x-strip-scroller",beforeScrollerCls:"x-strip-scroller-left",afterScrollerCls:"x-strip-scroller-right",createWheelListener:function(){this.layout.innerCt.on({scope:this,mousewheel:function(a){a.stopEvent();this.scrollBy(a.getWheelDelta()*this.wheelIncrement*-1,false)}})},handleOverflow:function(a,b){this.createInnerElements();this.showScrollers()},clearOverflow:function(){this.hideScrollers()},showScrollers:function(){this.createScrollers();this.beforeScroller.show();this.afterScroller.show();this.updateScrollButtons()},hideScrollers:function(){if(this.beforeScroller!=undefined){this.beforeScroller.hide();this.afterScroller.hide()}},createScrollers:function(){if(!this.beforeScroller&&!this.afterScroller){var a=this.beforeCt.createChild({cls:String.format("{0} {1} ",this.scrollerCls,this.beforeScrollerCls)});var b=this.afterCt.createChild({cls:String.format("{0} {1}",this.scrollerCls,this.afterScrollerCls)});a.addClassOnOver(this.beforeScrollerCls+"-hover");b.addClassOnOver(this.afterScrollerCls+"-hover");a.setVisibilityMode(Ext.Element.DISPLAY);b.setVisibilityMode(Ext.Element.DISPLAY);this.beforeRepeater=new Ext.util.ClickRepeater(a,{interval:this.scrollRepeatInterval,handler:this.scrollLeft,scope:this});this.afterRepeater=new Ext.util.ClickRepeater(b,{interval:this.scrollRepeatInterval,handler:this.scrollRight,scope:this});this.beforeScroller=a;this.afterScroller=b}},destroy:function(){Ext.destroy(this.beforeScroller,this.afterScroller,this.beforeRepeater,this.afterRepeater,this.beforeCt,this.afterCt)},scrollBy:function(b,a){this.scrollTo(this.getScrollPosition()+b,a)},getItem:function(a){if(Ext.isString(a)){a=Ext.getCmp(a)}else{if(Ext.isNumber(a)){a=this.items[a]}}return a},getScrollAnim:function(){return{duration:this.scrollDuration,callback:this.updateScrollButtons,scope:this}},updateScrollButtons:function(){if(this.beforeScroller==undefined||this.afterScroller==undefined){return}var d=this.atExtremeBefore()?"addClass":"removeClass",c=this.atExtremeAfter()?"addClass":"removeClass",a=this.beforeScrollerCls+"-disabled",b=this.afterScrollerCls+"-disabled";this.beforeScroller[d](a);this.afterScroller[c](b);this.scrolling=false},atExtremeBefore:function(){return this.getScrollPosition()===0},scrollLeft:function(a){this.scrollBy(-this.scrollIncrement,a)},scrollRight:function(a){this.scrollBy(this.scrollIncrement,a)},scrollToItem:function(d,b){d=this.getItem(d);if(d!=undefined){var a=this.getItemVisibility(d);if(!a.fullyVisible){var c=d.getBox(true,true),e=c.x;if(a.hiddenRight){e-=(this.layout.innerCt.getWidth()-c.width)}this.scrollTo(e,b)}}},getItemVisibility:function(e){var d=this.getItem(e).getBox(true,true),a=d.x,c=d.x+d.width,g=this.getScrollPosition(),b=this.layout.innerCt.getWidth()+g;return{hiddenLeft:ab,fullyVisible:a>g&&c=this.getMaxScrollBottom()}});Ext.layout.boxOverflow.scroller.vbox=Ext.layout.boxOverflow.VerticalScroller;Ext.layout.boxOverflow.HorizontalScroller=Ext.extend(Ext.layout.boxOverflow.Scroller,{handleOverflow:function(a,b){Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this,arguments);return{targetSize:{height:b.height,width:b.width-(this.beforeCt.getWidth()+this.afterCt.getWidth())}}},createInnerElements:function(){var a=this.layout.innerCt;if(!this.beforeCt){this.afterCt=a.insertSibling({cls:this.afterCls},"before");this.beforeCt=a.insertSibling({cls:this.beforeCls},"before");this.createWheelListener()}},scrollTo:function(a,b){var d=this.getScrollPosition(),c=a.constrain(0,this.getMaxScrollRight());if(c!=d&&!this.scrolling){if(b==undefined){b=this.animateScroll}this.layout.innerCt.scrollTo("left",c,b?this.getScrollAnim():false);if(b){this.scrolling=true}else{this.scrolling=false;this.updateScrollButtons()}}},getScrollPosition:function(){return parseInt(this.layout.innerCt.dom.scrollLeft,10)||0},getMaxScrollRight:function(){return this.layout.innerCt.dom.scrollWidth-this.layout.innerCt.getWidth()},atExtremeAfter:function(){return this.getScrollPosition()>=this.getMaxScrollRight()}});Ext.layout.boxOverflow.scroller.hbox=Ext.layout.boxOverflow.HorizontalScroller;Ext.layout.HBoxLayout=Ext.extend(Ext.layout.BoxLayout,{align:"top",type:"hbox",calculateChildBoxes:function(r,b){var F=r.length,R=this.padding,D=R.top,U=R.left,y=D+R.bottom,O=U+R.right,a=b.width-this.scrollOffset,e=b.height,o=Math.max(0,e-y),P=this.pack=="start",W=this.pack=="center",A=this.pack=="end",L=0,Q=0,T=0,l=0,X=0,H=[],k,J,M,V,w,j,S,I,c,x,q,N;for(S=0;Sa;var n=Math.max(0,a-L-O);if(p){for(S=0;S0){var C=[];for(var E=0,v=F;Ei.available?1:-1});for(var S=0,v=C.length;S0){I.top=D+q+(z/2)}}U+=I.width+w.right}return{boxes:H,meta:{maxHeight:Q,nonFlexWidth:L,desiredWidth:l,minimumWidth:X,shortfall:l-a,tooNarrow:p}}}});Ext.Container.LAYOUTS.hbox=Ext.layout.HBoxLayout;Ext.layout.VBoxLayout=Ext.extend(Ext.layout.BoxLayout,{align:"left",type:"vbox",calculateChildBoxes:function(o,b){var E=o.length,R=this.padding,C=R.top,V=R.left,x=C+R.bottom,O=V+R.right,a=b.width-this.scrollOffset,c=b.height,K=Math.max(0,a-O),P=this.pack=="start",X=this.pack=="center",z=this.pack=="end",k=0,u=0,U=0,L=0,m=0,G=[],h,I,N,W,t,g,T,H,S,w,n,d,r;for(T=0;Tc;var q=Math.max(0,(c-k-x));if(l){for(T=0,r=E;T0){var J=[];for(var D=0,r=E;Di.available?1:-1});for(var T=0,r=J.length;T0){H.left=V+w+(y/2)}}C+=H.height+t.bottom}return{boxes:G,meta:{maxWidth:u,nonFlexHeight:k,desiredHeight:L,minimumHeight:m,shortfall:L-c,tooNarrow:l}}}});Ext.Container.LAYOUTS.vbox=Ext.layout.VBoxLayout;Ext.layout.ToolbarLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"toolbar",triggerWidth:18,noItemsMenuText:'
(None)
',lastOverflow:false,tableHTML:['',"","",'",'","","","
','',"",'',"","
","
','',"","","","","","","
",'',"",'',"","
","
",'',"",'',"","
","
","
"].join(""),onLayout:function(e,j){if(!this.leftTr){var h=e.buttonAlign=="center"?"center":"left";j.addClass("x-toolbar-layout-ct");j.insertHtml("beforeEnd",String.format(this.tableHTML,h));this.leftTr=j.child("tr.x-toolbar-left-row",true);this.rightTr=j.child("tr.x-toolbar-right-row",true);this.extrasTr=j.child("tr.x-toolbar-extras-row",true);if(this.hiddenItem==undefined){this.hiddenItems=[]}}var k=e.buttonAlign=="right"?this.rightTr:this.leftTr,l=e.items.items,d=0;for(var b=0,g=l.length,m;b=0&&(d=e[a]);a--){if(!d.firstChild){b.removeChild(d)}}},insertCell:function(e,b,a){var d=document.createElement("td");d.className="x-toolbar-cell";b.insertBefore(d,b.childNodes[a]||null);return d},hideItem:function(a){this.hiddenItems.push(a);a.xtbHidden=true;a.xtbWidth=a.getPositionEl().dom.parentNode.offsetWidth;a.hide()},unhideItem:function(a){a.show();a.xtbHidden=false;this.hiddenItems.remove(a)},getItemWidth:function(a){return a.hidden?(a.xtbWidth||0):a.getPositionEl().dom.parentNode.offsetWidth},fitToSize:function(k){if(this.container.enableOverflow===false){return}var b=k.dom.clientWidth,j=k.dom.firstChild.offsetWidth,m=b-this.triggerWidth,a=this.lastWidth||0,c=this.hiddenItems,e=c.length!=0,n=b>=a;this.lastWidth=b;if(j>b||(e&&n)){var l=this.container.items.items,h=l.length,d=0,o;for(var g=0;gm){if(!(o.hidden||o.xtbHidden)){this.hideItem(o)}}else{if(o.xtbHidden){this.unhideItem(o)}}}}}e=c.length!=0;if(e){this.initMore();if(!this.lastOverflow){this.container.fireEvent("overflowchange",this.container,true);this.lastOverflow=true}}else{if(this.more){this.clearMenu();this.more.destroy();delete this.more;if(this.lastOverflow){this.container.fireEvent("overflowchange",this.container,false);this.lastOverflow=false}}}},createMenuConfig:function(c,a){var b=Ext.apply({},c.initialConfig),d=c.toggleGroup;Ext.copyTo(b,c,["iconCls","icon","itemId","disabled","handler","scope","menu"]);Ext.apply(b,{text:c.overflowText||c.text,hideOnClick:a});if(d||c.enableToggle){Ext.apply(b,{group:d,checked:c.pressed,listeners:{checkchange:function(g,e){c.toggle(e)}}})}delete b.ownerCt;delete b.xtype;delete b.id;return b},addComponentToMenu:function(b,a){if(a instanceof Ext.Toolbar.Separator){b.add("-")}else{if(Ext.isFunction(a.isXType)){if(a.isXType("splitbutton")){b.add(this.createMenuConfig(a,true))}else{if(a.isXType("button")){b.add(this.createMenuConfig(a,!a.menu))}else{if(a.isXType("buttongroup")){a.items.each(function(c){this.addComponentToMenu(b,c)},this)}}}}}},clearMenu:function(){var a=this.moreMenu;if(a&&a.items){a.items.each(function(b){delete b.menu})}},beforeMoreShow:function(h){var b=this.container.items.items,a=b.length,g,e;var c=function(j,i){return j.isXType("buttongroup")&&!(i instanceof Ext.Toolbar.Separator)};this.clearMenu();h.removeAll();for(var d=0;d','','{altText}',"","")}if(g&&!g.rendered){if(Ext.isNumber(b)){b=e.dom.childNodes[b]}var d=this.getItemArgs(g);g.render(g.positionEl=b?this.itemTpl.insertBefore(b,d,true):this.itemTpl.append(e,d,true));g.positionEl.menuItemId=g.getItemId();if(!d.isMenuItem&&d.needsIcon){g.positionEl.addClass("x-menu-list-item-indent")}this.configureItem(g)}else{if(g&&!this.isValidParent(g,e)){if(Ext.isNumber(b)){b=e.dom.childNodes[b]}e.dom.insertBefore(g.getActionEl().dom,b||null)}}},getItemArgs:function(d){var a=d instanceof Ext.menu.Item,b=!(a||d instanceof Ext.menu.Separator);return{isMenuItem:a,needsIcon:b&&(d.icon||d.iconCls),icon:d.icon||Ext.BLANK_IMAGE_URL,iconCls:"x-menu-item-icon "+(d.iconCls||""),itemId:"x-menu-el-"+d.id,itemCls:"x-menu-list-item ",altText:d.altText||""}},isValidParent:function(b,a){return b.el.up("li.x-menu-list-item",5).dom.parentNode===(a.dom||a)},onLayout:function(a,b){Ext.layout.MenuLayout.superclass.onLayout.call(this,a,b);this.doAutoSize()},doAutoSize:function(){var c=this.container,a=c.width;if(c.floating){if(a){c.setWidth(a)}else{if(Ext.isIE){c.setWidth(Ext.isStrict&&(Ext.isIE7||Ext.isIE8||Ext.isIE9)?"auto":c.minWidth);var d=c.getEl(),b=d.dom.offsetWidth;c.setWidth(c.getLayoutTarget().getWidth()+d.getFrameWidth("lr"))}}}}});Ext.Container.LAYOUTS.menu=Ext.layout.MenuLayout;Ext.Viewport=Ext.extend(Ext.Container,{initComponent:function(){Ext.Viewport.superclass.initComponent.call(this);document.getElementsByTagName("html")[0].className+=" x-viewport";this.el=Ext.getBody();this.el.setHeight=Ext.emptyFn;this.el.setWidth=Ext.emptyFn;this.el.setSize=Ext.emptyFn;this.el.dom.scroll="no";this.allowDomMove=false;this.autoWidth=true;this.autoHeight=true;Ext.EventManager.onWindowResize(this.fireResize,this);this.renderTo=this.el},fireResize:function(a,b){this.fireEvent("resize",this,a,b,a,b)}});Ext.reg("viewport",Ext.Viewport);Ext.Panel=Ext.extend(Ext.Container,{baseCls:"x-panel",collapsedCls:"x-panel-collapsed",maskDisabled:true,animCollapse:Ext.enableFx,headerAsText:true,buttonAlign:"right",collapsed:false,collapseFirst:true,minButtonWidth:75,elements:"body",preventBodyReset:false,padding:undefined,resizeEvent:"bodyresize",toolTarget:"header",collapseEl:"bwrap",slideAnchor:"t",disabledClass:"",deferHeight:true,expandDefaults:{duration:0.25},collapseDefaults:{duration:0.25},initComponent:function(){Ext.Panel.superclass.initComponent.call(this);this.addEvents("bodyresize","titlechange","iconchange","collapse","expand","beforecollapse","beforeexpand","beforeclose","close","activate","deactivate");if(this.unstyled){this.baseCls="x-plain"}this.toolbars=[];if(this.tbar){this.elements+=",tbar";this.topToolbar=this.createToolbar(this.tbar);this.tbar=null}if(this.bbar){this.elements+=",bbar";this.bottomToolbar=this.createToolbar(this.bbar);this.bbar=null}if(this.header===true){this.elements+=",header";this.header=null}else{if(this.headerCfg||(this.title&&this.header!==false)){this.elements+=",header"}}if(this.footerCfg||this.footer===true){this.elements+=",footer";this.footer=null}if(this.buttons){this.fbar=this.buttons;this.buttons=null}if(this.fbar){this.createFbar(this.fbar)}if(this.autoLoad){this.on("render",this.doAutoLoad,this,{delay:10})}},createFbar:function(b){var a=this.minButtonWidth;this.elements+=",footer";this.fbar=this.createToolbar(b,{buttonAlign:this.buttonAlign,toolbarCls:"x-panel-fbar",enableOverflow:false,defaults:function(d){return{minWidth:d.minWidth||a}}});this.fbar.items.each(function(d){d.minWidth=d.minWidth||this.minButtonWidth},this);this.buttons=this.fbar.items.items},createToolbar:function(b,c){var a;if(Ext.isArray(b)){b={items:b}}a=b.events?Ext.apply(b,c):this.createComponent(Ext.apply({},b,c),"toolbar");this.toolbars.push(a);return a},createElement:function(a,c){if(this[a]){c.appendChild(this[a].dom);return}if(a==="bwrap"||this.elements.indexOf(a)!=-1){if(this[a+"Cfg"]){this[a]=Ext.fly(c).createChild(this[a+"Cfg"])}else{var b=document.createElement("div");b.className=this[a+"Cls"];this[a]=Ext.get(c.appendChild(b))}if(this[a+"CssClass"]){this[a].addClass(this[a+"CssClass"])}if(this[a+"Style"]){this[a].applyStyles(this[a+"Style"])}}},onRender:function(g,e){Ext.Panel.superclass.onRender.call(this,g,e);this.createClasses();var a=this.el,h=a.dom,k,i;if(this.collapsible&&!this.hideCollapseTool){this.tools=this.tools?this.tools.slice(0):[];this.tools[this.collapseFirst?"unshift":"push"]({id:"toggle",handler:this.toggleCollapse,scope:this})}if(this.tools){i=this.tools;this.elements+=(this.header!==false)?",header":""}this.tools={};a.addClass(this.baseCls);if(h.firstChild){this.header=a.down("."+this.headerCls);this.bwrap=a.down("."+this.bwrapCls);var j=this.bwrap?this.bwrap:a;this.tbar=j.down("."+this.tbarCls);this.body=j.down("."+this.bodyCls);this.bbar=j.down("."+this.bbarCls);this.footer=j.down("."+this.footerCls);this.fromMarkup=true}if(this.preventBodyReset===true){a.addClass("x-panel-reset")}if(this.cls){a.addClass(this.cls)}if(this.buttons){this.elements+=",footer"}if(this.frame){a.insertHtml("afterBegin",String.format(Ext.Element.boxMarkup,this.baseCls));this.createElement("header",h.firstChild.firstChild.firstChild);this.createElement("bwrap",h);k=this.bwrap.dom;var c=h.childNodes[1],b=h.childNodes[2];k.appendChild(c);k.appendChild(b);var l=k.firstChild.firstChild.firstChild;this.createElement("tbar",l);this.createElement("body",l);this.createElement("bbar",l);this.createElement("footer",k.lastChild.firstChild.firstChild);if(!this.footer){this.bwrap.dom.lastChild.className+=" x-panel-nofooter"}this.ft=Ext.get(this.bwrap.dom.lastChild);this.mc=Ext.get(l)}else{this.createElement("header",h);this.createElement("bwrap",h);k=this.bwrap.dom;this.createElement("tbar",k);this.createElement("body",k);this.createElement("bbar",k);this.createElement("footer",k);if(!this.header){this.body.addClass(this.bodyCls+"-noheader");if(this.tbar){this.tbar.addClass(this.tbarCls+"-noheader")}}}if(Ext.isDefined(this.padding)){this.body.setStyle("padding",this.body.addUnits(this.padding))}if(this.border===false){this.el.addClass(this.baseCls+"-noborder");this.body.addClass(this.bodyCls+"-noborder");if(this.header){this.header.addClass(this.headerCls+"-noborder")}if(this.footer){this.footer.addClass(this.footerCls+"-noborder")}if(this.tbar){this.tbar.addClass(this.tbarCls+"-noborder")}if(this.bbar){this.bbar.addClass(this.bbarCls+"-noborder")}}if(this.bodyBorder===false){this.body.addClass(this.bodyCls+"-noborder")}this.bwrap.enableDisplayMode("block");if(this.header){this.header.unselectable();if(this.headerAsText){this.header.dom.innerHTML=''+this.header.dom.innerHTML+"";if(this.iconCls){this.setIconClass(this.iconCls)}}}if(this.floating){this.makeFloating(this.floating)}if(this.collapsible&&this.titleCollapse&&this.header){this.mon(this.header,"click",this.toggleCollapse,this);this.header.setStyle("cursor","pointer")}if(i){this.addTool.apply(this,i)}if(this.fbar){this.footer.addClass("x-panel-btns");this.fbar.ownerCt=this;this.fbar.render(this.footer);this.footer.createChild({cls:"x-clear"})}if(this.tbar&&this.topToolbar){this.topToolbar.ownerCt=this;this.topToolbar.render(this.tbar)}if(this.bbar&&this.bottomToolbar){this.bottomToolbar.ownerCt=this;this.bottomToolbar.render(this.bbar)}},setIconClass:function(b){var a=this.iconCls;this.iconCls=b;if(this.rendered&&this.header){if(this.frame){this.header.addClass("x-panel-icon");this.header.replaceClass(a,this.iconCls)}else{var e=this.header,c=e.child("img.x-panel-inline-icon");if(c){Ext.fly(c).replaceClass(a,this.iconCls)}else{var d=e.child("span."+this.headerTextCls);if(d){Ext.DomHelper.insertBefore(d.dom,{tag:"img",alt:"",src:Ext.BLANK_IMAGE_URL,cls:"x-panel-inline-icon "+this.iconCls})}}}}this.fireEvent("iconchange",this,b,a)},makeFloating:function(a){this.floating=true;this.el=new Ext.Layer(Ext.apply({},a,{shadow:Ext.isDefined(this.shadow)?this.shadow:"sides",shadowOffset:this.shadowOffset,constrain:false,shim:this.shim===false?false:undefined}),this.el)},getTopToolbar:function(){return this.topToolbar},getBottomToolbar:function(){return this.bottomToolbar},getFooterToolbar:function(){return this.fbar},addButton:function(a,c,b){if(!this.fbar){this.createFbar([])}if(c){if(Ext.isString(a)){a={text:a}}a=Ext.apply({handler:c,scope:b},a)}return this.fbar.add(a)},addTool:function(){if(!this.rendered){if(!this.tools){this.tools=[]}Ext.each(arguments,function(a){this.tools.push(a)},this);return}if(!this[this.toolTarget]){return}if(!this.toolTemplate){var h=new Ext.Template('
 
');h.disableFormats=true;h.compile();Ext.Panel.prototype.toolTemplate=h}for(var g=0,d=arguments,c=d.length;g0){Ext.each(this.toolbars,function(c){c.doLayout(undefined,a)});this.syncHeight()}},syncHeight:function(){var b=this.toolbarHeight,c=this.body,a=this.lastSize.height,d;if(this.autoHeight||!Ext.isDefined(a)||a=="auto"){return}if(b!=this.getToolbarHeight()){b=Math.max(0,a-this.getFrameHeight());c.setHeight(b);d=c.getSize();this.toolbarHeight=this.getToolbarHeight();this.onBodyResize(d.width,d.height)}},onShow:function(){if(this.floating){return this.el.show()}Ext.Panel.superclass.onShow.call(this)},onHide:function(){if(this.floating){return this.el.hide()}Ext.Panel.superclass.onHide.call(this)},createToolHandler:function(c,a,d,b){return function(g){c.removeClass(d);if(a.stopEvent!==false){g.stopEvent()}if(a.handler){a.handler.call(a.scope||c,g,c,b,a)}}},afterRender:function(){if(this.floating&&!this.hidden){this.el.show()}if(this.title){this.setTitle(this.title)}Ext.Panel.superclass.afterRender.call(this);if(this.collapsed){this.collapsed=false;this.collapse(false)}this.initEvents()},getKeyMap:function(){if(!this.keyMap){this.keyMap=new Ext.KeyMap(this.el,this.keys)}return this.keyMap},initEvents:function(){if(this.keys){this.getKeyMap()}if(this.draggable){this.initDraggable()}if(this.toolbars.length>0){Ext.each(this.toolbars,function(a){a.doLayout();a.on({scope:this,afterlayout:this.syncHeight,remove:this.syncHeight})},this);this.syncHeight()}},initDraggable:function(){this.dd=new Ext.Panel.DD(this,Ext.isBoolean(this.draggable)?null:this.draggable)},beforeEffect:function(a){if(this.floating){this.el.beforeAction()}if(a!==false){this.el.addClass("x-panel-animated")}},afterEffect:function(a){this.syncShadow();this.el.removeClass("x-panel-animated")},createEffect:function(c,b,d){var e={scope:d,block:true};if(c===true){e.callback=b;return e}else{if(!c.callback){e.callback=b}else{e.callback=function(){b.call(d);Ext.callback(c.callback,c.scope)}}}return Ext.applyIf(e,c)},collapse:function(b){if(this.collapsed||this.el.hasFxBlock()||this.fireEvent("beforecollapse",this,b)===false){return}var a=b===true||(b!==false&&this.animCollapse);this.beforeEffect(a);this.onCollapse(a,b);return this},onCollapse:function(a,b){if(a){this[this.collapseEl].slideOut(this.slideAnchor,Ext.apply(this.createEffect(b||true,this.afterCollapse,this),this.collapseDefaults))}else{this[this.collapseEl].hide(this.hideMode);this.afterCollapse(false)}},afterCollapse:function(a){this.collapsed=true;this.el.addClass(this.collapsedCls);if(a!==false){this[this.collapseEl].hide(this.hideMode)}this.afterEffect(a);this.cascade(function(b){if(b.lastSize){b.lastSize={width:undefined,height:undefined}}});this.fireEvent("collapse",this)},expand:function(b){if(!this.collapsed||this.el.hasFxBlock()||this.fireEvent("beforeexpand",this,b)===false){return}var a=b===true||(b!==false&&this.animCollapse);this.el.removeClass(this.collapsedCls);this.beforeEffect(a);this.onExpand(a,b);return this},onExpand:function(a,b){if(a){this[this.collapseEl].slideIn(this.slideAnchor,Ext.apply(this.createEffect(b||true,this.afterExpand,this),this.expandDefaults))}else{this[this.collapseEl].show(this.hideMode);this.afterExpand(false)}},afterExpand:function(a){this.collapsed=false;if(a!==false){this[this.collapseEl].show(this.hideMode)}this.afterEffect(a);if(this.deferLayout){delete this.deferLayout;this.doLayout(true)}this.fireEvent("expand",this)},toggleCollapse:function(a){this[this.collapsed?"expand":"collapse"](a);return this},onDisable:function(){if(this.rendered&&this.maskDisabled){this.el.mask()}Ext.Panel.superclass.onDisable.call(this)},onEnable:function(){if(this.rendered&&this.maskDisabled){this.el.unmask()}Ext.Panel.superclass.onEnable.call(this)},onResize:function(g,d,c,e){var a=g,b=d;if(Ext.isDefined(a)||Ext.isDefined(b)){if(!this.collapsed){if(Ext.isNumber(a)){this.body.setWidth(a=this.adjustBodyWidth(a-this.getFrameWidth()))}else{if(a=="auto"){a=this.body.setWidth("auto").dom.offsetWidth}else{a=this.body.dom.offsetWidth}}if(this.tbar){this.tbar.setWidth(a);if(this.topToolbar){this.topToolbar.setSize(a)}}if(this.bbar){this.bbar.setWidth(a);if(this.bottomToolbar){this.bottomToolbar.setSize(a);if(Ext.isIE){this.bbar.setStyle("position","static");this.bbar.setStyle("position","")}}}if(this.footer){this.footer.setWidth(a);if(this.fbar){this.fbar.setSize(Ext.isIE?(a-this.footer.getFrameWidth("lr")):"auto")}}if(Ext.isNumber(b)){b=Math.max(0,b-this.getFrameHeight());this.body.setHeight(b)}else{if(b=="auto"){this.body.setHeight(b)}}if(this.disabled&&this.el._mask){this.el._mask.setSize(this.el.dom.clientWidth,this.el.getHeight())}}else{this.queuedBodySize={width:a,height:b};if(!this.queuedExpand&&this.allowQueuedExpand!==false){this.queuedExpand=true;this.on("expand",function(){delete this.queuedExpand;this.onResize(this.queuedBodySize.width,this.queuedBodySize.height)},this,{single:true})}}this.onBodyResize(a,b)}this.syncShadow();Ext.Panel.superclass.onResize.call(this,g,d,c,e)},onBodyResize:function(a,b){this.fireEvent("bodyresize",this,a,b)},getToolbarHeight:function(){var a=0;if(this.rendered){Ext.each(this.toolbars,function(b){a+=b.getHeight()},this)}return a},adjustBodyHeight:function(a){return a},adjustBodyWidth:function(a){return a},onPosition:function(){this.syncShadow()},getFrameWidth:function(){var b=this.el.getFrameWidth("lr")+this.bwrap.getFrameWidth("lr");if(this.frame){var a=this.bwrap.dom.firstChild;b+=(Ext.fly(a).getFrameWidth("l")+Ext.fly(a.firstChild).getFrameWidth("r"));b+=this.mc.getFrameWidth("lr")}return b},getFrameHeight:function(){var a=this.el.getFrameWidth("tb")+this.bwrap.getFrameWidth("tb");a+=(this.tbar?this.tbar.getHeight():0)+(this.bbar?this.bbar.getHeight():0);if(this.frame){a+=this.el.dom.firstChild.offsetHeight+this.ft.dom.offsetHeight+this.mc.getFrameWidth("tb")}else{a+=(this.header?this.header.getHeight():0)+(this.footer?this.footer.getHeight():0)}return a},getInnerWidth:function(){return this.getSize().width-this.getFrameWidth()},getInnerHeight:function(){return this.body.getHeight()},syncShadow:function(){if(this.floating){this.el.sync(true)}},getLayoutTarget:function(){return this.body},getContentTarget:function(){return this.body},setTitle:function(b,a){this.title=b;if(this.header&&this.headerAsText){this.header.child("span").update(b)}if(a){this.setIconClass(a)}this.fireEvent("titlechange",this,b);return this},getUpdater:function(){return this.body.getUpdater()},load:function(){var a=this.body.getUpdater();a.update.apply(a,arguments);return this},beforeDestroy:function(){Ext.Panel.superclass.beforeDestroy.call(this);if(this.header){this.header.removeAllListeners()}if(this.tools){for(var a in this.tools){Ext.destroy(this.tools[a])}}if(this.toolbars.length>0){Ext.each(this.toolbars,function(b){b.un("afterlayout",this.syncHeight,this);b.un("remove",this.syncHeight,this)},this)}if(Ext.isArray(this.buttons)){while(this.buttons.length){Ext.destroy(this.buttons[0])}}if(this.rendered){Ext.destroy(this.ft,this.header,this.footer,this.tbar,this.bbar,this.body,this.mc,this.bwrap,this.dd);if(this.fbar){Ext.destroy(this.fbar,this.fbar.el)}}Ext.destroy(this.toolbars)},createClasses:function(){this.headerCls=this.baseCls+"-header";this.headerTextCls=this.baseCls+"-header-text";this.bwrapCls=this.baseCls+"-bwrap";this.tbarCls=this.baseCls+"-tbar";this.bodyCls=this.baseCls+"-body";this.bbarCls=this.baseCls+"-bbar";this.footerCls=this.baseCls+"-footer"},createGhost:function(a,e,b){var d=document.createElement("div");d.className="x-panel-ghost "+(a?a:"");if(this.header){d.appendChild(this.el.dom.firstChild.cloneNode(true))}Ext.fly(d.appendChild(document.createElement("ul"))).setHeight(this.bwrap.getHeight());d.style.width=this.el.dom.offsetWidth+"px";if(!b){this.container.dom.appendChild(d)}else{Ext.getDom(b).appendChild(d)}if(e!==false&&this.el.useShim!==false){var c=new Ext.Layer({shadow:false,useDisplay:true,constrain:false},d);c.show();return c}else{return new Ext.Element(d)}},doAutoLoad:function(){var a=this.body.getUpdater();if(this.renderer){a.setRenderer(this.renderer)}a.update(Ext.isObject(this.autoLoad)?this.autoLoad:{url:this.autoLoad})},getTool:function(a){return this.tools[a]}});Ext.reg("panel",Ext.Panel);Ext.Editor=function(b,a){if(b.field){this.field=Ext.create(b.field,"textfield");a=Ext.apply({},b);delete a.field}else{this.field=b}Ext.Editor.superclass.constructor.call(this,a)};Ext.extend(Ext.Editor,Ext.Component,{allowBlur:true,value:"",alignment:"c-c?",offsets:[0,0],shadow:"frame",constrain:false,swallowKeys:true,completeOnEnter:true,cancelOnEsc:true,updateEl:false,initComponent:function(){Ext.Editor.superclass.initComponent.call(this);this.addEvents("beforestartedit","startedit","beforecomplete","complete","canceledit","specialkey")},onRender:function(b,a){this.el=new Ext.Layer({shadow:this.shadow,cls:"x-editor",parentEl:b,shim:this.shim,shadowOffset:this.shadowOffset||4,id:this.id,constrain:this.constrain});if(this.zIndex){this.el.setZIndex(this.zIndex)}this.el.setStyle("overflow",Ext.isGecko?"auto":"hidden");if(this.field.msgTarget!="title"){this.field.msgTarget="qtip"}this.field.inEditor=true;this.mon(this.field,{scope:this,blur:this.onBlur,specialkey:this.onSpecialKey});if(this.field.grow){this.mon(this.field,"autosize",this.el.sync,this.el,{delay:1})}this.field.render(this.el).show();this.field.getEl().dom.name="";if(this.swallowKeys){this.field.el.swallowEvent(["keypress","keydown"])}},onSpecialKey:function(g,d){var b=d.getKey(),a=this.completeOnEnter&&b==d.ENTER,c=this.cancelOnEsc&&b==d.ESC;if(a||c){d.stopEvent();if(a){this.completeEdit()}else{this.cancelEdit()}if(g.triggerBlur){g.triggerBlur()}}this.fireEvent("specialkey",g,d)},startEdit:function(b,c){if(this.editing){this.completeEdit()}this.boundEl=Ext.get(b);var a=c!==undefined?c:this.boundEl.dom.innerHTML;if(!this.rendered){this.render(this.parentEl||document.body)}if(this.fireEvent("beforestartedit",this,this.boundEl,a)!==false){this.startValue=a;this.field.reset();this.field.setValue(a);this.realign(true);this.editing=true;this.show()}},doAutoSize:function(){if(this.autoSize){var b=this.boundEl.getSize(),a=this.field.getSize();switch(this.autoSize){case"width":this.setSize(b.width,a.height);break;case"height":this.setSize(a.width,b.height);break;case"none":this.setSize(a.width,a.height);break;default:this.setSize(b.width,b.height)}}},setSize:function(a,b){delete this.field.lastSize;this.field.setSize(a,b);if(this.el){if(Ext.isGecko2||Ext.isOpera||(Ext.isIE7&&Ext.isStrict)){this.el.setSize(a,b)}this.el.sync()}},realign:function(a){if(a===true){this.doAutoSize()}this.el.alignTo(this.boundEl,this.alignment,this.offsets)},completeEdit:function(a){if(!this.editing){return}if(this.field.assertValue){this.field.assertValue()}var b=this.getValue();if(!this.field.isValid()){if(this.revertInvalid!==false){this.cancelEdit(a)}return}if(String(b)===String(this.startValue)&&this.ignoreNoChange){this.hideEdit(a);return}if(this.fireEvent("beforecomplete",this,b,this.startValue)!==false){b=this.getValue();if(this.updateEl&&this.boundEl){this.boundEl.update(b)}this.hideEdit(a);this.fireEvent("complete",this,b,this.startValue)}},onShow:function(){this.el.show();if(this.hideEl!==false){this.boundEl.hide()}this.field.show().focus(false,true);this.fireEvent("startedit",this.boundEl,this.startValue)},cancelEdit:function(a){if(this.editing){var b=this.getValue();this.setValue(this.startValue);this.hideEdit(a);this.fireEvent("canceledit",this,b,this.startValue)}},hideEdit:function(a){if(a!==true){this.editing=false;this.hide()}},onBlur:function(){if(this.allowBlur===true&&this.editing&&this.selectSameEditor!==true){this.completeEdit()}},onHide:function(){if(this.editing){this.completeEdit();return}this.field.blur();if(this.field.collapse){this.field.collapse()}this.el.hide();if(this.hideEl!==false){this.boundEl.show()}},setValue:function(a){this.field.setValue(a)},getValue:function(){return this.field.getValue()},beforeDestroy:function(){Ext.destroyMembers(this,"field");delete this.parentEl;delete this.boundEl}});Ext.reg("editor",Ext.Editor);Ext.ColorPalette=Ext.extend(Ext.Component,{itemCls:"x-color-palette",value:null,clickEvent:"click",ctype:"Ext.ColorPalette",allowReselect:false,colors:["000000","993300","333300","003300","003366","000080","333399","333333","800000","FF6600","808000","008000","008080","0000FF","666699","808080","FF0000","FF9900","99CC00","339966","33CCCC","3366FF","800080","969696","FF00FF","FFCC00","FFFF00","00FF00","00FFFF","00CCFF","993366","C0C0C0","FF99CC","FFCC99","FFFF99","CCFFCC","CCFFFF","99CCFF","CC99FF","FFFFFF"],initComponent:function(){Ext.ColorPalette.superclass.initComponent.call(this);this.addEvents("select");if(this.handler){this.on("select",this.handler,this.scope,true)}},onRender:function(b,a){this.autoEl={tag:"div",cls:this.itemCls};Ext.ColorPalette.superclass.onRender.call(this,b,a);var c=this.tpl||new Ext.XTemplate(' ');c.overwrite(this.el,this.colors);this.mon(this.el,this.clickEvent,this.handleClick,this,{delegate:"a"});if(this.clickEvent!="click"){this.mon(this.el,"click",Ext.emptyFn,this,{delegate:"a",preventDefault:true})}},afterRender:function(){Ext.ColorPalette.superclass.afterRender.call(this);if(this.value){var a=this.value;this.value=null;this.select(a,true)}},handleClick:function(b,a){b.preventDefault();if(!this.disabled){var d=a.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];this.select(d.toUpperCase())}},select:function(b,a){b=b.replace("#","");if(b!=this.value||this.allowReselect){var c=this.el;if(this.value){c.child("a.color-"+this.value).removeClass("x-color-palette-sel")}c.child("a.color-"+b).addClass("x-color-palette-sel");this.value=b;if(a!==true){this.fireEvent("select",this,b)}}}});Ext.reg("colorpalette",Ext.ColorPalette);Ext.DatePicker=Ext.extend(Ext.BoxComponent,{todayText:"Today",okText:" OK ",cancelText:"Cancel",todayTip:"{0} (Spacebar)",minText:"This date is before the minimum date",maxText:"This date is after the maximum date",format:"m/d/y",disabledDaysText:"Disabled",disabledDatesText:"Disabled",monthNames:Date.monthNames,dayNames:Date.dayNames,nextText:"Next Month (Control+Right)",prevText:"Previous Month (Control+Left)",monthYearText:"Choose a month (Control+Up/Down to move years)",startDay:0,showToday:true,focusOnSelect:true,initHour:12,initComponent:function(){Ext.DatePicker.superclass.initComponent.call(this);this.value=this.value?this.value.clearTime(true):new Date().clearTime();this.addEvents("select");if(this.handler){this.on("select",this.handler,this.scope||this)}this.initDisabledDays()},initDisabledDays:function(){if(!this.disabledDatesRE&&this.disabledDates){var b=this.disabledDates,a=b.length-1,c="(?:";Ext.each(b,function(g,e){c+=Ext.isDate(g)?"^"+Ext.escapeRe(g.dateFormat(this.format))+"$":b[e];if(e!=a){c+="|"}},this);this.disabledDatesRE=new RegExp(c+")")}},setDisabledDates:function(a){if(Ext.isArray(a)){this.disabledDates=a;this.disabledDatesRE=null}else{this.disabledDatesRE=a}this.initDisabledDays();this.update(this.value,true)},setDisabledDays:function(a){this.disabledDays=a;this.update(this.value,true)},setMinDate:function(a){this.minDate=a;this.update(this.value,true)},setMaxDate:function(a){this.maxDate=a;this.update(this.value,true)},setValue:function(a){this.value=a.clearTime(true);this.update(this.value)},getValue:function(){return this.value},focus:function(){this.update(this.activeDate)},onEnable:function(a){Ext.DatePicker.superclass.onEnable.call(this);this.doDisabled(false);this.update(a?this.value:this.activeDate);if(Ext.isIE){this.el.repaint()}},onDisable:function(){Ext.DatePicker.superclass.onDisable.call(this);this.doDisabled(true);if(Ext.isIE&&!Ext.isIE8){Ext.each([].concat(this.textNodes,this.el.query("th span")),function(a){Ext.fly(a).repaint()})}},doDisabled:function(a){this.keyNav.setDisabled(a);this.prevRepeater.setDisabled(a);this.nextRepeater.setDisabled(a);if(this.showToday){this.todayKeyListener.setDisabled(a);this.todayBtn.setDisabled(a)}},onRender:function(e,b){var a=['','','",this.showToday?'':"",'
  
'],c=this.dayNames,h;for(h=0;h<7;h++){var k=this.startDay+h;if(k>6){k=k-7}a.push("")}a[a.length]="";for(h=0;h<42;h++){if(h%7===0&&h!==0){a[a.length]=""}a[a.length]=''}a.push("
",c[k].substr(0,1),"
');var j=document.createElement("div");j.className="x-date-picker";j.innerHTML=a.join("");e.dom.insertBefore(j,b);this.el=Ext.get(j);this.eventEl=Ext.get(j.firstChild);this.prevRepeater=new Ext.util.ClickRepeater(this.el.child("td.x-date-left a"),{handler:this.showPrevMonth,scope:this,preventDefault:true,stopDefault:true});this.nextRepeater=new Ext.util.ClickRepeater(this.el.child("td.x-date-right a"),{handler:this.showNextMonth,scope:this,preventDefault:true,stopDefault:true});this.monthPicker=this.el.down("div.x-date-mp");this.monthPicker.enableDisplayMode("block");this.keyNav=new Ext.KeyNav(this.eventEl,{left:function(d){if(d.ctrlKey){this.showPrevMonth()}else{this.update(this.activeDate.add("d",-1))}},right:function(d){if(d.ctrlKey){this.showNextMonth()}else{this.update(this.activeDate.add("d",1))}},up:function(d){if(d.ctrlKey){this.showNextYear()}else{this.update(this.activeDate.add("d",-7))}},down:function(d){if(d.ctrlKey){this.showPrevYear()}else{this.update(this.activeDate.add("d",7))}},pageUp:function(d){this.showNextMonth()},pageDown:function(d){this.showPrevMonth()},enter:function(d){d.stopPropagation();return true},scope:this});this.el.unselectable();this.cells=this.el.select("table.x-date-inner tbody td");this.textNodes=this.el.query("table.x-date-inner tbody span");this.mbtn=new Ext.Button({text:" ",tooltip:this.monthYearText,renderTo:this.el.child("td.x-date-middle",true)});this.mbtn.el.child("em").addClass("x-btn-arrow");if(this.showToday){this.todayKeyListener=this.eventEl.addKeyListener(Ext.EventObject.SPACE,this.selectToday,this);var g=(new Date()).dateFormat(this.format);this.todayBtn=new Ext.Button({renderTo:this.el.child("td.x-date-bottom",true),text:String.format(this.todayText,g),tooltip:String.format(this.todayTip,g),handler:this.selectToday,scope:this})}this.mon(this.eventEl,"mousewheel",this.handleMouseWheel,this);this.mon(this.eventEl,"click",this.handleDateClick,this,{delegate:"a.x-date-date"});this.mon(this.mbtn,"click",this.showMonthPicker,this);this.onEnable(true)},createMonthPicker:function(){if(!this.monthPicker.dom.firstChild){var a=[''];for(var b=0;b<6;b++){a.push('",'",b===0?'':'')}a.push('","
',Date.getShortMonthName(b),"',Date.getShortMonthName(b+6),"
");this.monthPicker.update(a.join(""));this.mon(this.monthPicker,"click",this.onMonthClick,this);this.mon(this.monthPicker,"dblclick",this.onMonthDblClick,this);this.mpMonths=this.monthPicker.select("td.x-date-mp-month");this.mpYears=this.monthPicker.select("td.x-date-mp-year");this.mpMonths.each(function(c,d,e){e+=1;if((e%2)===0){c.dom.xmonth=5+Math.round(e*0.5)}else{c.dom.xmonth=Math.round((e-1)*0.5)}})}},showMonthPicker:function(){if(!this.disabled){this.createMonthPicker();var a=this.el.getSize();this.monthPicker.setSize(a);this.monthPicker.child("table").setSize(a);this.mpSelMonth=(this.activeDate||this.value).getMonth();this.updateMPMonth(this.mpSelMonth);this.mpSelYear=(this.activeDate||this.value).getFullYear();this.updateMPYear(this.mpSelYear);this.monthPicker.slideIn("t",{duration:0.2})}},updateMPYear:function(e){this.mpyear=e;var c=this.mpYears.elements;for(var b=1;b<=10;b++){var d=c[b-1],a;if((b%2)===0){a=e+Math.round(b*0.5);d.firstChild.innerHTML=a;d.xyear=a}else{a=e-(5-Math.round(b*0.5));d.firstChild.innerHTML=a;d.xyear=a}this.mpYears.item(b-1)[a==this.mpSelYear?"addClass":"removeClass"]("x-date-mp-sel")}},updateMPMonth:function(a){this.mpMonths.each(function(b,c,d){b[b.dom.xmonth==a?"addClass":"removeClass"]("x-date-mp-sel")})},selectMPMonth:function(a){},onMonthClick:function(g,b){g.stopEvent();var c=new Ext.Element(b),a;if(c.is("button.x-date-mp-cancel")){this.hideMonthPicker()}else{if(c.is("button.x-date-mp-ok")){var h=new Date(this.mpSelYear,this.mpSelMonth,(this.activeDate||this.value).getDate());if(h.getMonth()!=this.mpSelMonth){h=new Date(this.mpSelYear,this.mpSelMonth,1).getLastDateOfMonth()}this.update(h);this.hideMonthPicker()}else{if((a=c.up("td.x-date-mp-month",2))){this.mpMonths.removeClass("x-date-mp-sel");a.addClass("x-date-mp-sel");this.mpSelMonth=a.dom.xmonth}else{if((a=c.up("td.x-date-mp-year",2))){this.mpYears.removeClass("x-date-mp-sel");a.addClass("x-date-mp-sel");this.mpSelYear=a.dom.xyear}else{if(c.is("a.x-date-mp-prev")){this.updateMPYear(this.mpyear-10)}else{if(c.is("a.x-date-mp-next")){this.updateMPYear(this.mpyear+10)}}}}}}},onMonthDblClick:function(d,b){d.stopEvent();var c=new Ext.Element(b),a;if((a=c.up("td.x-date-mp-month",2))){this.update(new Date(this.mpSelYear,a.dom.xmonth,(this.activeDate||this.value).getDate()));this.hideMonthPicker()}else{if((a=c.up("td.x-date-mp-year",2))){this.update(new Date(a.dom.xyear,this.mpSelMonth,(this.activeDate||this.value).getDate()));this.hideMonthPicker()}}},hideMonthPicker:function(a){if(this.monthPicker){if(a===true){this.monthPicker.hide()}else{this.monthPicker.slideOut("t",{duration:0.2})}}},showPrevMonth:function(a){this.update(this.activeDate.add("mo",-1))},showNextMonth:function(a){this.update(this.activeDate.add("mo",1))},showPrevYear:function(){this.update(this.activeDate.add("y",-1))},showNextYear:function(){this.update(this.activeDate.add("y",1))},handleMouseWheel:function(a){a.stopEvent();if(!this.disabled){var b=a.getWheelDelta();if(b>0){this.showPrevMonth()}else{if(b<0){this.showNextMonth()}}}},handleDateClick:function(b,a){b.stopEvent();if(!this.disabled&&a.dateValue&&!Ext.fly(a.parentNode).hasClass("x-date-disabled")){this.cancelFocus=this.focusOnSelect===false;this.setValue(new Date(a.dateValue));delete this.cancelFocus;this.fireEvent("select",this,this.value)}},selectToday:function(){if(this.todayBtn&&!this.todayBtn.disabled){this.setValue(new Date().clearTime());this.fireEvent("select",this,this.value)}},update:function(G,A){if(this.rendered){var a=this.activeDate,p=this.isVisible();this.activeDate=G;if(!A&&a&&this.el){var o=G.getTime();if(a.getMonth()==G.getMonth()&&a.getFullYear()==G.getFullYear()){this.cells.removeClass("x-date-selected");this.cells.each(function(d){if(d.dom.firstChild.dateValue==o){d.addClass("x-date-selected");if(p&&!this.cancelFocus){Ext.fly(d.dom.firstChild).focus(50)}return false}},this);return}}var k=G.getDaysInMonth(),q=G.getFirstDateOfMonth(),g=q.getDay()-this.startDay;if(g<0){g+=7}k+=g;var B=G.add("mo",-1),h=B.getDaysInMonth()-g,e=this.cells.elements,r=this.textNodes,D=(new Date(B.getFullYear(),B.getMonth(),h,this.initHour)),C=new Date().clearTime().getTime(),v=G.clearTime(true).getTime(),u=this.minDate?this.minDate.clearTime(true):Number.NEGATIVE_INFINITY,y=this.maxDate?this.maxDate.clearTime(true):Number.POSITIVE_INFINITY,F=this.disabledDatesRE,s=this.disabledDatesText,I=this.disabledDays?this.disabledDays.join(""):false,E=this.disabledDaysText,z=this.format;if(this.showToday){var m=new Date().clearTime(),c=(my||(F&&z&&F.test(m.dateFormat(z)))||(I&&I.indexOf(m.getDay())!=-1));if(!this.disabled){this.todayBtn.setDisabled(c);this.todayKeyListener[c?"disable":"enable"]()}}var l=function(J,d){d.title="";var i=D.clearTime(true).getTime();d.firstChild.dateValue=i;if(i==C){d.className+=" x-date-today";d.title=J.todayText}if(i==v){d.className+=" x-date-selected";if(p){Ext.fly(d.firstChild).focus(50)}}if(iy){d.className=" x-date-disabled";d.title=J.maxText;return}if(I){if(I.indexOf(D.getDay())!=-1){d.title=E;d.className=" x-date-disabled"}}if(F&&z){var w=D.dateFormat(z);if(F.test(w)){d.title=s.replace("%0",w);d.className=" x-date-disabled"}}};var x=0;for(;x=a.value){d=a.value}}c.setValue(b,d,false);c.fireEvent("drag",c,g,this)},getNewValue:function(){var a=this.slider,b=a.innerEl.translatePoints(this.tracker.getXY());return Ext.util.Format.round(a.reverseValue(b.left),a.decimalPrecision)},onDragEnd:function(c){var a=this.slider,b=this.value;this.el.removeClass("x-slider-thumb-drag");this.dragging=false;a.fireEvent("dragend",a,c);if(this.dragStartValue!=b){a.fireEvent("changecomplete",a,b,this)}},destroy:function(){Ext.destroyMembers(this,"tracker","el")}});Ext.slider.MultiSlider=Ext.extend(Ext.BoxComponent,{vertical:false,minValue:0,maxValue:100,decimalPrecision:0,keyIncrement:1,increment:0,clickRange:[5,15],clickToChange:true,animate:true,constrainThumbs:true,topThumbZIndex:10000,initComponent:function(){if(!Ext.isDefined(this.value)){this.value=this.minValue}this.thumbs=[];Ext.slider.MultiSlider.superclass.initComponent.call(this);this.keyIncrement=Math.max(this.increment,this.keyIncrement);this.addEvents("beforechange","change","changecomplete","dragstart","drag","dragend");if(this.values==undefined||Ext.isEmpty(this.values)){this.values=[0]}var a=this.values;for(var b=0;bthis.clickRange[0]&&c.top=c){d+=c}else{if(a*2<-c){d-=c}}}return d.constrain(this.minValue,this.maxValue)},afterRender:function(){Ext.slider.MultiSlider.superclass.afterRender.apply(this,arguments);for(var c=0;ce?e:c.value}this.syncThumb()},setValue:function(d,c,b,g){var a=this.thumbs[d],e=a.el;c=this.normalizeValue(c);if(c!==a.value&&this.fireEvent("beforechange",this,c,a.value,a)!==false){a.value=c;if(this.rendered){this.moveThumb(d,this.translateValue(c),b!==false);this.fireEvent("change",this,c,a);if(g){this.fireEvent("changecomplete",this,c,a)}}}},translateValue:function(a){var b=this.getRatio();return(a*b)-(this.minValue*b)-this.halfThumb},reverseValue:function(b){var a=this.getRatio();return(b+(this.minValue*a))/a},moveThumb:function(d,c,b){var a=this.thumbs[d].el;if(!b||this.animate===false){a.setLeft(c)}else{a.shift({left:c,stopFx:true,duration:0.35})}},focus:function(){this.focusEl.focus(10)},onResize:function(c,e){var b=this.thumbs,a=b.length,d=0;for(;dthis.clickRange[0]&&c.left','
','
','
',"
 
","
","
",'
',"
 
","
","
","");this.el=a?c.insertBefore(a,{cls:this.baseCls},true):c.append(d,{cls:this.baseCls},true);if(this.id){this.el.dom.id=this.id}var b=this.el.dom.firstChild;this.progressBar=Ext.get(b.firstChild);if(this.textEl){this.textEl=Ext.get(this.textEl);delete this.textTopEl}else{this.textTopEl=Ext.get(this.progressBar.dom.firstChild);var e=Ext.get(b.childNodes[1]);this.textTopEl.setStyle("z-index",99).addClass("x-hidden");this.textEl=new Ext.CompositeElement([this.textTopEl.dom.firstChild,e.dom.firstChild]);this.textEl.setWidth(b.offsetWidth)}this.progressBar.setHeight(b.offsetHeight)},afterRender:function(){Ext.ProgressBar.superclass.afterRender.call(this);if(this.value){this.updateProgress(this.value,this.text)}else{this.updateText(this.text)}},updateProgress:function(c,d,b){this.value=c||0;if(d){this.updateText(d)}if(this.rendered&&!this.isDestroyed){var a=Math.floor(c*this.el.dom.firstChild.offsetWidth);this.progressBar.setWidth(a,b===true||(b!==false&&this.animate));if(this.textTopEl){this.textTopEl.removeClass("x-hidden").setWidth(a)}}this.fireEvent("update",this,c,d);return this},wait:function(b){if(!this.waitTimer){var a=this;b=b||{};this.updateText(b.text);this.waitTimer=Ext.TaskMgr.start({run:function(c){var d=b.increment||10;c-=1;this.updateProgress(((((c+d)%d)+1)*(100/d))*0.01,null,b.animate)},interval:b.interval||1000,duration:b.duration,onStop:function(){if(b.fn){b.fn.apply(b.scope||this)}this.reset()},scope:a})}return this},isWaiting:function(){return this.waitTimer!==null},updateText:function(a){this.text=a||" ";if(this.rendered){this.textEl.update(this.text)}return this},syncProgressBar:function(){if(this.value){this.updateProgress(this.value,this.text)}return this},setSize:function(a,c){Ext.ProgressBar.superclass.setSize.call(this,a,c);if(this.textTopEl){var b=this.el.dom.firstChild;this.textEl.setSize(b.offsetWidth,b.offsetHeight)}this.syncProgressBar();return this},reset:function(a){this.updateProgress(0);if(this.textTopEl){this.textTopEl.addClass("x-hidden")}this.clearTimer();if(a===true){this.hide()}return this},clearTimer:function(){if(this.waitTimer){this.waitTimer.onStop=null;Ext.TaskMgr.stop(this.waitTimer);this.waitTimer=null}},onDestroy:function(){this.clearTimer();if(this.rendered){if(this.textEl.isComposite){this.textEl.clear()}Ext.destroyMembers(this,"textEl","progressBar","textTopEl")}Ext.ProgressBar.superclass.onDestroy.call(this)}});Ext.reg("progress",Ext.ProgressBar);(function(){var a=Ext.EventManager;var b=Ext.lib.Dom;Ext.dd.DragDrop=function(e,c,d){if(e){this.init(e,c,d)}};Ext.dd.DragDrop.prototype={id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true},moveOnly:false,unlock:function(){this.locked=false},isTarget:true,padding:null,_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,b4StartDrag:function(c,d){},startDrag:function(c,d){},b4Drag:function(c){},onDrag:function(c){},onDragEnter:function(c,d){},b4DragOver:function(c){},onDragOver:function(c,d){},b4DragOut:function(c){},onDragOut:function(c,d){},b4DragDrop:function(c){},onDragDrop:function(c,d){},onInvalidDrop:function(c){},b4EndDrag:function(c){},endDrag:function(c){},b4MouseDown:function(c){},onMouseDown:function(c){},onMouseUp:function(c){},onAvailable:function(){},defaultPadding:{left:0,right:0,top:0,bottom:0},constrainTo:function(j,h,o){if(Ext.isNumber(h)){h={left:h,right:h,top:h,bottom:h}}h=h||this.defaultPadding;var l=Ext.get(this.getEl()).getBox(),d=Ext.get(j),n=d.getScroll(),k,e=d.dom;if(e==document.body){k={x:n.left,y:n.top,width:Ext.lib.Dom.getViewWidth(),height:Ext.lib.Dom.getViewHeight()}}else{var m=d.getXY();k={x:m[0],y:m[1],width:e.clientWidth,height:e.clientHeight}}var i=l.y-k.y,g=l.x-k.x;this.resetConstraints();this.setXConstraint(g-(h.left||0),k.width-g-l.width-(h.right||0),this.xTickSize);this.setYConstraint(i-(h.top||0),k.height-i-l.height-(h.bottom||0),this.yTickSize)},getEl:function(){if(!this._domRef){this._domRef=Ext.getDom(this.id)}return this._domRef},getDragEl:function(){return Ext.getDom(this.dragElId)},init:function(e,c,d){this.initTarget(e,c,d);a.on(this.id,"mousedown",this.handleMouseDown,this)},initTarget:function(e,c,d){this.config=d||{};this.DDM=Ext.dd.DDM;this.groups={};if(typeof e!=="string"){e=Ext.id(e)}this.id=e;this.addToGroup((c)?c:"default");this.handleElId=e;this.setDragElId(e);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig();this.handleOnAvailable()},applyConfig:function(){this.padding=this.config.padding||[0,0,0,0];this.isTarget=(this.config.isTarget!==false);this.maintainOffset=(this.config.maintainOffset);this.primaryButtonOnly=(this.config.primaryButtonOnly!==false)},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable()},setPadding:function(e,c,g,d){if(!c&&0!==c){this.padding=[e,e,e,e]}else{if(!g&&0!==g){this.padding=[e,c,e,c]}else{this.padding=[e,c,g,d]}}},setInitPosition:function(g,e){var h=this.getEl();if(!this.DDM.verifyEl(h)){return}var d=g||0;var c=e||0;var i=b.getXY(h);this.initPageX=i[0]-d;this.initPageY=i[1]-c;this.lastPageX=i[0];this.lastPageY=i[1];this.setStartPosition(i)},setStartPosition:function(d){var c=d||b.getXY(this.getEl());this.deltaSetXY=null;this.startPageX=c[0];this.startPageY=c[1]},addToGroup:function(c){this.groups[c]=true;this.DDM.regDragDrop(this,c)},removeFromGroup:function(c){if(this.groups[c]){delete this.groups[c]}this.DDM.removeDDFromGroup(this,c)},setDragElId:function(c){this.dragElId=c},setHandleElId:function(c){if(typeof c!=="string"){c=Ext.id(c)}this.handleElId=c;this.DDM.regHandle(this.id,c)},setOuterHandleElId:function(c){if(typeof c!=="string"){c=Ext.id(c)}a.on(c,"mousedown",this.handleMouseDown,this);this.setHandleElId(c);this.hasOuterHandles=true},unreg:function(){a.un(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this)},destroy:function(){this.unreg()},isLocked:function(){return(this.DDM.isLocked()||this.locked)},handleMouseDown:function(g,d){if(this.primaryButtonOnly&&g.button!=0){return}if(this.isLocked()){return}this.DDM.refreshCache(this.groups);var c=new Ext.lib.Point(Ext.lib.Event.getPageX(g),Ext.lib.Event.getPageY(g));if(!this.hasOuterHandles&&!this.DDM.isOverTarget(c,this)){}else{if(this.clickValidator(g)){this.setStartPosition();this.b4MouseDown(g);this.onMouseDown(g);this.DDM.handleMouseDown(g,this);this.DDM.stopEvent(g)}else{}}},clickValidator:function(d){var c=d.getTarget();return(this.isValidHandleChild(c)&&(this.id==this.handleElId||this.DDM.handleWasClicked(c,this.id)))},addInvalidHandleType:function(c){var d=c.toUpperCase();this.invalidHandleTypes[d]=d},addInvalidHandleId:function(c){if(typeof c!=="string"){c=Ext.id(c)}this.invalidHandleIds[c]=c},addInvalidHandleClass:function(c){this.invalidHandleClasses.push(c)},removeInvalidHandleType:function(c){var d=c.toUpperCase();delete this.invalidHandleTypes[d]},removeInvalidHandleId:function(c){if(typeof c!=="string"){c=Ext.id(c)}delete this.invalidHandleIds[c]},removeInvalidHandleClass:function(d){for(var e=0,c=this.invalidHandleClasses.length;e=this.minX;d=d-c){if(!e[d]){this.xTicks[this.xTicks.length]=d;e[d]=true}}for(d=this.initPageX;d<=this.maxX;d=d+c){if(!e[d]){this.xTicks[this.xTicks.length]=d;e[d]=true}}this.xTicks.sort(this.DDM.numericSort)},setYTicks:function(g,c){this.yTicks=[];this.yTickSize=c;var e={};for(var d=this.initPageY;d>=this.minY;d=d-c){if(!e[d]){this.yTicks[this.yTicks.length]=d;e[d]=true}}for(d=this.initPageY;d<=this.maxY;d=d+c){if(!e[d]){this.yTicks[this.yTicks.length]=d;e[d]=true}}this.yTicks.sort(this.DDM.numericSort)},setXConstraint:function(e,d,c){this.leftConstraint=e;this.rightConstraint=d;this.minX=this.initPageX-e;this.maxX=this.initPageX+d;if(c){this.setXTicks(this.initPageX,c)}this.constrainX=true},clearConstraints:function(){this.constrainX=false;this.constrainY=false;this.clearTicks()},clearTicks:function(){this.xTicks=null;this.yTicks=null;this.xTickSize=0;this.yTickSize=0},setYConstraint:function(c,e,d){this.topConstraint=c;this.bottomConstraint=e;this.minY=this.initPageY-c;this.maxY=this.initPageY+e;if(d){this.setYTicks(this.initPageY,d)}this.constrainY=true},resetConstraints:function(){if(this.initPageX||this.initPageX===0){var d=(this.maintainOffset)?this.lastPageX-this.initPageX:0;var c=(this.maintainOffset)?this.lastPageY-this.initPageY:0;this.setInitPosition(d,c)}else{this.setInitPosition()}if(this.constrainX){this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize)}if(this.constrainY){this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize)}},getTick:function(k,g){if(!g){return k}else{if(g[0]>=k){return g[0]}else{for(var d=0,c=g.length;d=k){var j=k-g[d];var h=g[e]-k;return(h>j)?g[d]:g[e]}}return g[g.length-1]}}},toString:function(){return("DragDrop "+this.id)}}})();if(!Ext.dd.DragDropMgr){Ext.dd.DragDropMgr=function(){var a=Ext.EventManager;return{ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,init:function(){this.initialized=true},POINT:0,INTERSECT:1,mode:0,_execOnAll:function(d,c){for(var e in this.ids){for(var b in this.ids[e]){var g=this.ids[e][b];if(!this.isTypeOfDD(g)){continue}g[d].apply(g,c)}}},_onLoad:function(){this.init();a.on(document,"mouseup",this.handleMouseUp,this,true);a.on(document,"mousemove",this.handleMouseMove,this,true);a.on(window,"unload",this._onUnload,this,true);a.on(window,"resize",this._onResize,this,true)},_onResize:function(b){this._execOnAll("resetConstraints",[])},lock:function(){this.locked=true},unlock:function(){this.locked=false},isLocked:function(){return this.locked},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:350,dragThreshMet:false,clickTimeout:null,startX:0,startY:0,regDragDrop:function(c,b){if(!this.initialized){this.init()}if(!this.ids[b]){this.ids[b]={}}this.ids[b][c.id]=c},removeDDFromGroup:function(d,b){if(!this.ids[b]){this.ids[b]={}}var c=this.ids[b];if(c&&c[d.id]){delete c[d.id]}},_remove:function(c){for(var b in c.groups){if(b&&this.ids[b]&&this.ids[b][c.id]){delete this.ids[b][c.id]}}delete this.handleIds[c.id]},regHandle:function(c,b){if(!this.handleIds[c]){this.handleIds[c]={}}this.handleIds[c][b]=b},isDragDrop:function(b){return(this.getDDById(b))?true:false},getRelated:function(h,c){var g=[];for(var e in h.groups){for(var d in this.ids[e]){var b=this.ids[e][d];if(!this.isTypeOfDD(b)){continue}if(!c||b.isTarget){g[g.length]=b}}}return g},isLegalTarget:function(g,e){var c=this.getRelated(g,true);for(var d=0,b=c.length;dthis.clickPixelThresh||b>this.clickPixelThresh){this.startDrag(this.startX,this.startY)}}if(this.dragThreshMet){this.dragCurrent.b4Drag(d);this.dragCurrent.onDrag(d);if(!this.dragCurrent.moveOnly){this.fireEvents(d,false)}}this.stopEvent(d);return true},fireEvents:function(n,o){var q=this.dragCurrent;if(!q||q.isLocked()){return}var r=n.getPoint();var b=[];var g=[];var l=[];var j=[];var d=[];for(var h in this.dragOvers){var c=this.dragOvers[h];if(!this.isTypeOfDD(c)){continue}if(!this.isOverTarget(r,c,this.mode)){g.push(c)}b[h]=true;delete this.dragOvers[h]}for(var p in q.groups){if("string"!=typeof p){continue}for(h in this.ids[p]){var k=this.ids[p][h];if(!this.isTypeOfDD(k)){continue}if(k.isTarget&&!k.isLocked()&&((k!=q)||(q.ignoreSelf===false))){if(this.isOverTarget(r,k,this.mode)){if(o){j.push(k)}else{if(!b[k.id]){d.push(k)}else{l.push(k)}this.dragOvers[k.id]=k}}}}}if(this.mode){if(g.length){q.b4DragOut(n,g);q.onDragOut(n,g)}if(d.length){q.onDragEnter(n,d)}if(l.length){q.b4DragOver(n,l);q.onDragOver(n,l)}if(j.length){q.b4DragDrop(n,j);q.onDragDrop(n,j)}}else{var m=0;for(h=0,m=g.length;h2000){}else{setTimeout(b._addListeners,10);if(document&&document.body){b._timeoutCount+=1}}}},handleWasClicked:function(b,d){if(this.isHandle(d,b.id)){return true}else{var c=b.parentNode;while(c){if(this.isHandle(d,c.id)){return true}else{c=c.parentNode}}}return false}}}();Ext.dd.DDM=Ext.dd.DragDropMgr;Ext.dd.DDM._addListeners()}Ext.dd.DD=function(c,a,b){if(c){this.init(c,a,b)}};Ext.extend(Ext.dd.DD,Ext.dd.DragDrop,{scroll:true,autoOffset:function(c,b){var a=c-this.startPageX;var d=b-this.startPageY;this.setDelta(a,d)},setDelta:function(b,a){this.deltaX=b;this.deltaY=a},setDragElPos:function(c,b){var a=this.getDragEl();this.alignElWithMouse(a,c,b)},alignElWithMouse:function(c,h,g){var e=this.getTargetCoord(h,g);var b=c.dom?c:Ext.fly(c,"_dd");if(!this.deltaSetXY){var i=[e.x,e.y];b.setXY(i);var d=b.getLeft(true);var a=b.getTop(true);this.deltaSetXY=[d-e.x,a-e.y]}else{b.setLeftTop(e.x+this.deltaSetXY[0],e.y+this.deltaSetXY[1])}this.cachePosition(e.x,e.y);this.autoScroll(e.x,e.y,c.offsetHeight,c.offsetWidth);return e},cachePosition:function(b,a){if(b){this.lastPageX=b;this.lastPageY=a}else{var c=Ext.lib.Dom.getXY(this.getEl());this.lastPageX=c[0];this.lastPageY=c[1]}},autoScroll:function(l,k,e,m){if(this.scroll){var n=Ext.lib.Dom.getViewHeight();var b=Ext.lib.Dom.getViewWidth();var p=this.DDM.getScrollTop();var d=this.DDM.getScrollLeft();var j=e+k;var o=m+l;var i=(n+p-k-this.deltaY);var g=(b+d-l-this.deltaX);var c=40;var a=(document.all)?80:30;if(j>n&&i0&&k-pb&&g0&&l-dthis.maxX){a=this.maxX}}if(this.constrainY){if(dthis.maxY){d=this.maxY}}a=this.getTick(a,this.xTicks);d=this.getTick(d,this.yTicks);return{x:a,y:d}},applyConfig:function(){Ext.dd.DD.superclass.applyConfig.call(this);this.scroll=(this.config.scroll!==false)},b4MouseDown:function(a){this.autoOffset(a.getPageX(),a.getPageY())},b4Drag:function(a){this.setDragElPos(a.getPageX(),a.getPageY())},toString:function(){return("DD "+this.id)}});Ext.dd.DDProxy=function(c,a,b){if(c){this.init(c,a,b);this.initFrame()}};Ext.dd.DDProxy.dragElId="ygddfdiv";Ext.extend(Ext.dd.DDProxy,Ext.dd.DD,{resizeFrame:true,centerFrame:false,createFrame:function(){var b=this;var a=document.body;if(!a||!a.firstChild){setTimeout(function(){b.createFrame()},50);return}var d=this.getDragEl();if(!d){d=document.createElement("div");d.id=this.dragElId;var c=d.style;c.position="absolute";c.visibility="hidden";c.cursor="move";c.border="2px solid #aaa";c.zIndex=999;a.insertBefore(d,a.firstChild)}},initFrame:function(){this.createFrame()},applyConfig:function(){Ext.dd.DDProxy.superclass.applyConfig.call(this);this.resizeFrame=(this.config.resizeFrame!==false);this.centerFrame=(this.config.centerFrame);this.setDragElId(this.config.dragElId||Ext.dd.DDProxy.dragElId)},showFrame:function(e,d){var c=this.getEl();var a=this.getDragEl();var b=a.style;this._resizeProxy();if(this.centerFrame){this.setDelta(Math.round(parseInt(b.width,10)/2),Math.round(parseInt(b.height,10)/2))}this.setDragElPos(e,d);Ext.fly(a).show()},_resizeProxy:function(){if(this.resizeFrame){var a=this.getEl();Ext.fly(this.getDragEl()).setSize(a.offsetWidth,a.offsetHeight)}},b4MouseDown:function(b){var a=b.getPageX();var c=b.getPageY();this.autoOffset(a,c);this.setDragElPos(a,c)},b4StartDrag:function(a,b){this.showFrame(a,b)},b4EndDrag:function(a){Ext.fly(this.getDragEl()).hide()},endDrag:function(c){var b=this.getEl();var a=this.getDragEl();a.style.visibility="";this.beforeMove();b.style.visibility="hidden";Ext.dd.DDM.moveToEl(b,a);a.style.visibility="hidden";b.style.visibility="";this.afterDrag()},beforeMove:function(){},afterDrag:function(){},toString:function(){return("DDProxy "+this.id)}});Ext.dd.DDTarget=function(c,a,b){if(c){this.initTarget(c,a,b)}};Ext.extend(Ext.dd.DDTarget,Ext.dd.DragDrop,{getDragEl:Ext.emptyFn,isValidHandleChild:Ext.emptyFn,startDrag:Ext.emptyFn,endDrag:Ext.emptyFn,onDrag:Ext.emptyFn,onDragDrop:Ext.emptyFn,onDragEnter:Ext.emptyFn,onDragOut:Ext.emptyFn,onDragOver:Ext.emptyFn,onInvalidDrop:Ext.emptyFn,onMouseDown:Ext.emptyFn,onMouseUp:Ext.emptyFn,setXConstraint:Ext.emptyFn,setYConstraint:Ext.emptyFn,resetConstraints:Ext.emptyFn,clearConstraints:Ext.emptyFn,clearTicks:Ext.emptyFn,setInitPosition:Ext.emptyFn,setDragElId:Ext.emptyFn,setHandleElId:Ext.emptyFn,setOuterHandleElId:Ext.emptyFn,addInvalidHandleClass:Ext.emptyFn,addInvalidHandleId:Ext.emptyFn,addInvalidHandleType:Ext.emptyFn,removeInvalidHandleClass:Ext.emptyFn,removeInvalidHandleId:Ext.emptyFn,removeInvalidHandleType:Ext.emptyFn,toString:function(){return("DDTarget "+this.id)}});Ext.dd.DragTracker=Ext.extend(Ext.util.Observable,{active:false,tolerance:5,autoStart:false,constructor:function(a){Ext.apply(this,a);this.addEvents("mousedown","mouseup","mousemove","dragstart","dragend","drag");this.dragRegion=new Ext.lib.Region(0,0,0,0);if(this.el){this.initEl(this.el)}Ext.dd.DragTracker.superclass.constructor.call(this,a)},initEl:function(a){this.el=Ext.get(a);a.on("mousedown",this.onMouseDown,this,this.delegate?{delegate:this.delegate}:undefined)},destroy:function(){this.el.un("mousedown",this.onMouseDown,this);delete this.el},onMouseDown:function(b,a){if(this.fireEvent("mousedown",this,b)!==false&&this.onBeforeStart(b)!==false){this.startXY=this.lastXY=b.getXY();this.dragTarget=this.delegate?a:this.el.dom;if(this.preventDefault!==false){b.preventDefault()}Ext.getDoc().on({scope:this,mouseup:this.onMouseUp,mousemove:this.onMouseMove,selectstart:this.stopSelect});if(this.autoStart){this.timer=this.triggerStart.defer(this.autoStart===true?1000:this.autoStart,this,[b])}}},onMouseMove:function(d,c){if(this.active&&Ext.isIE&&!d.browserEvent.button){d.preventDefault();this.onMouseUp(d);return}d.preventDefault();var b=d.getXY(),a=this.startXY;this.lastXY=b;if(!this.active){if(Math.abs(a[0]-b[0])>this.tolerance||Math.abs(a[1]-b[1])>this.tolerance){this.triggerStart(d)}else{return}}this.fireEvent("mousemove",this,d);this.onDrag(d);this.fireEvent("drag",this,d)},onMouseUp:function(c){var b=Ext.getDoc(),a=this.active;b.un("mousemove",this.onMouseMove,this);b.un("mouseup",this.onMouseUp,this);b.un("selectstart",this.stopSelect,this);c.preventDefault();this.clearStart();this.active=false;delete this.elRegion;this.fireEvent("mouseup",this,c);if(a){this.onEnd(c);this.fireEvent("dragend",this,c)}},triggerStart:function(a){this.clearStart();this.active=true;this.onStart(a);this.fireEvent("dragstart",this,a)},clearStart:function(){if(this.timer){clearTimeout(this.timer);delete this.timer}},stopSelect:function(a){a.stopEvent();return false},onBeforeStart:function(a){},onStart:function(a){},onDrag:function(a){},onEnd:function(a){},getDragTarget:function(){return this.dragTarget},getDragCt:function(){return this.el},getXY:function(a){return a?this.constrainModes[a].call(this,this.lastXY):this.lastXY},getOffset:function(c){var b=this.getXY(c),a=this.startXY;return[a[0]-b[0],a[1]-b[1]]},constrainModes:{point:function(b){if(!this.elRegion){this.elRegion=this.getDragCt().getRegion()}var a=this.dragRegion;a.left=b[0];a.top=b[1];a.right=b[0];a.bottom=b[1];a.constrainTo(this.elRegion);return[a.left,a.top]}}});Ext.dd.ScrollManager=function(){var c=Ext.dd.DragDropMgr;var e={};var b=null;var i={};var h=function(l){b=null;a()};var j=function(){if(c.dragCurrent){c.refreshCache(c.dragCurrent.groups)}};var d=function(){if(c.dragCurrent){var l=Ext.dd.ScrollManager;var m=i.el.ddScrollConfig?i.el.ddScrollConfig.increment:l.increment;if(!l.animate){if(i.el.scroll(i.dir,m)){j()}}else{i.el.scroll(i.dir,m,true,l.animDuration,j)}}};var a=function(){if(i.id){clearInterval(i.id)}i.id=0;i.el=null;i.dir=""};var g=function(m,l){a();i.el=m;i.dir=l;var o=m.ddScrollConfig?m.ddScrollConfig.ddGroup:undefined,n=(m.ddScrollConfig&&m.ddScrollConfig.frequency)?m.ddScrollConfig.frequency:Ext.dd.ScrollManager.frequency;if(o===undefined||c.dragCurrent.ddGroup==o){i.id=setInterval(d,n)}};var k=function(o,q){if(q||!c.dragCurrent){return}var s=Ext.dd.ScrollManager;if(!b||b!=c.dragCurrent){b=c.dragCurrent;s.refreshCache()}var t=Ext.lib.Event.getXY(o);var u=new Ext.lib.Point(t[0],t[1]);for(var m in e){var n=e[m],l=n._region;var p=n.ddScrollConfig?n.ddScrollConfig:s;if(l&&l.contains(u)&&n.isScrollable()){if(l.bottom-u.y<=p.vthresh){if(i.el!=n){g(n,"down")}return}else{if(l.right-u.x<=p.hthresh){if(i.el!=n){g(n,"left")}return}else{if(u.y-l.top<=p.vthresh){if(i.el!=n){g(n,"up")}return}else{if(u.x-l.left<=p.hthresh){if(i.el!=n){g(n,"right")}return}}}}}}a()};c.fireEvents=c.fireEvents.createSequence(k,c);c.stopDrag=c.stopDrag.createSequence(h,c);return{register:function(n){if(Ext.isArray(n)){for(var m=0,l=n.length;m]+>/gi,asText:function(a){return String(a).replace(this.stripTagsRE,"")},asUCText:function(a){return String(a).toUpperCase().replace(this.stripTagsRE,"")},asUCString:function(a){return String(a).toUpperCase()},asDate:function(a){if(!a){return 0}if(Ext.isDate(a)){return a.getTime()}return Date.parse(String(a))},asFloat:function(a){var b=parseFloat(String(a).replace(/,/g,""));return isNaN(b)?0:b},asInt:function(a){var b=parseInt(String(a).replace(/,/g,""),10);return isNaN(b)?0:b}};Ext.data.Record=function(a,b){this.id=(b||b===0)?b:Ext.data.Record.id(this);this.data=a||{}};Ext.data.Record.create=function(e){var c=Ext.extend(Ext.data.Record,{});var d=c.prototype;d.fields=new Ext.util.MixedCollection(false,function(g){return g.name});for(var b=0,a=e.length;b-1){a.join(null);this.data.removeAt(b)}if(this.pruneModifiedRecords){this.modified.remove(a)}if(this.snapshot){this.snapshot.remove(a)}if(b>-1){this.fireEvent("remove",this,a,b)}},removeAt:function(a){this.remove(this.getAt(a))},removeAll:function(b){var a=[];this.each(function(c){a.push(c)});this.clearData();if(this.snapshot){this.snapshot.clear()}if(this.pruneModifiedRecords){this.modified=[]}if(b!==true){this.fireEvent("clear",this,a)}},onClear:function(b,a){Ext.each(a,function(d,c){this.destroyRecord(this,d,c)},this)},insert:function(d,c){var e,a,b;c=[].concat(c);for(e=0,a=c.length;e=0;d--){if(b[d].phantom===true){var a=b.splice(d,1).shift();if(a.isValid()){g.push(a)}}else{if(!b[d].isValid()){b.splice(d,1)}}}if(g.length){h.push(["create",g])}if(b.length){h.push(["update",b])}}j=h.length;if(j){e=++this.batchCounter;for(d=0;d=0;b--){this.modified.splice(this.modified.indexOf(a[b]),1)}}else{this.modified.splice(this.modified.indexOf(a),1)}},reMap:function(b){if(Ext.isArray(b)){for(var d=0,a=b.length;d=0;c--){this.insert(b[c].lastIndex,b[c])}}},handleException:function(a){Ext.handleError(a)},reload:function(a){this.load(Ext.applyIf(a||{},this.lastOptions))},loadRecords:function(b,l,h){var e,g;if(this.isDestroyed===true){return}if(!b||h===false){if(h!==false){this.fireEvent("load",this,[],l)}if(l.callback){l.callback.call(l.scope||this,[],l,false,b)}return}var a=b.records,j=b.totalRecords||a.length;if(!l||l.add!==true){if(this.pruneModifiedRecords){this.modified=[]}for(e=0,g=a.length;e-1){this.doUpdate(d)}else{k.push(d);++c}}this.totalLength=Math.max(j,this.data.length+c);this.add(k)}this.fireEvent("load",this,a,l);if(l.callback){l.callback.call(l.scope||this,a,l,true)}},loadData:function(c,a){var b=this.reader.readRecords(c);this.loadRecords(b,{add:a},true)},getCount:function(){return this.data.length||0},getTotalCount:function(){return this.totalLength||0},getSortState:function(){return this.sortInfo},applySort:function(){if((this.sortInfo||this.multiSortInfo)&&!this.remoteSort){this.sortData()}},sortData:function(){var a=this.hasMultiSort?this.multiSortInfo:this.sortInfo,k=a.direction||"ASC",h=a.sorters,c=[];if(!this.hasMultiSort){h=[{direction:k,field:a.field}]}for(var d=0,b=h.length;d1){for(var p=1,o=c.length;ph?1:(i=0;b--){if(Ext.isArray(c)){this.realize(a.splice(b,1).shift(),c.splice(b,1).shift())}else{this.realize(a.splice(b,1).shift(),c)}}}else{if(Ext.isArray(c)&&c.length==1){c=c.shift()}if(!this.isData(c)){throw new Ext.data.DataReader.Error("realize",a)}a.phantom=false;a._phid=a.id;a.id=this.getId(c);a.data=c;a.commit();a.store.reMap(a)}},update:function(a,c){if(Ext.isArray(a)){for(var b=a.length-1;b>=0;b--){if(Ext.isArray(c)){this.update(a.splice(b,1).shift(),c.splice(b,1).shift())}else{this.update(a.splice(b,1).shift(),c)}}}else{if(Ext.isArray(c)&&c.length==1){c=c.shift()}if(this.isData(c)){a.data=Ext.apply(a.data,c)}a.commit()}},extractData:function(k,a){var j=(this instanceof Ext.data.JsonReader)?"json":"node";var c=[];if(this.isData(k)&&!(this instanceof Ext.data.XmlReader)){k=[k]}var h=this.recordType.prototype.fields,o=h.items,m=h.length,c=[];if(a===true){var l=this.recordType;for(var e=0;e=0){return new Function("obj","return obj"+(b>0?".":"")+c)}return function(d){return d[c]}}}(),extractValues:function(h,d,a){var g,c={};for(var e=0;e<\u003fxml version="{version}" encoding="{encoding}"\u003f><{documentRoot}><{name}>{value}<{root}><{parent.record}><{name}>{value}',render:function(b,c,a){c=this.toArray(c);b.xmlData=this.tpl.applyTemplate({version:this.xmlVersion,encoding:this.xmlEncoding,documentRoot:(c.length>0||this.forceDocumentRoot===true)?this.documentRoot:false,record:this.meta.record,root:this.root,baseParams:c,records:(Ext.isArray(a[0]))?a:[a]})},createRecord:function(a){return this.toArray(this.toHash(a))},updateRecord:function(a){return this.toArray(this.toHash(a))},destroyRecord:function(b){var a={};a[this.meta.idProperty]=b.id;return this.toArray(a)}});Ext.data.XmlReader=function(a,b){a=a||{};Ext.applyIf(a,{idProperty:a.idProperty||a.idPath||a.id,successProperty:a.successProperty||a.success});Ext.data.XmlReader.superclass.constructor.call(this,a,b||a.fields)};Ext.extend(Ext.data.XmlReader,Ext.data.DataReader,{read:function(a){var b=a.responseXML;if(!b){throw {message:"XmlReader.read: XML Document not available"}}return this.readRecords(b)},readRecords:function(d){this.xmlData=d;var a=d.documentElement||d,c=Ext.DomQuery,g=0,e=true;if(this.meta.totalProperty){g=this.getTotal(a,0)}if(this.meta.successProperty){e=this.getSuccess(a)}var b=this.extractData(c.select(this.meta.record,a),true);return{success:e,records:b,totalRecords:g||b.length}},readResponse:function(g,b){var e=Ext.DomQuery,h=b.responseXML,a=h.documentElement||h;var c=new Ext.data.Response({action:g,success:this.getSuccess(a),message:this.getMessage(a),data:this.extractData(e.select(this.meta.record,a)||e.select(this.meta.root,a),false),raw:h});if(Ext.isEmpty(c.success)){throw new Ext.data.DataReader.Error("successProperty-response",this.meta.successProperty)}if(g===Ext.data.Api.actions.create){var d=Ext.isDefined(c.data);if(d&&Ext.isEmpty(c.data)){throw new Ext.data.JsonReader.Error("root-empty",this.meta.root)}else{if(!d){throw new Ext.data.JsonReader.Error("root-undefined-response",this.meta.root)}}}return c},getSuccess:function(){return true},buildExtractors:function(){if(this.ef){return}var l=this.meta,h=this.recordType,e=h.prototype.fields,k=e.items,j=e.length;if(l.totalProperty){this.getTotal=this.createAccessor(l.totalProperty)}if(l.successProperty){this.getSuccess=this.createAccessor(l.successProperty)}if(l.messageProperty){this.getMessage=this.createAccessor(l.messageProperty)}this.getRoot=function(g){return(!Ext.isEmpty(g[this.meta.record]))?g[this.meta.record]:g[this.meta.root]};if(l.idPath||l.idProperty){var d=this.createAccessor(l.idPath||l.idProperty);this.getId=function(g){var i=d(g)||g.id;return(i===undefined||i==="")?null:i}}else{this.getId=function(){return null}}var c=[];for(var b=0;b0&&c[0].field==this.groupField){c.shift()}this.groupField=e;this.groupDir=d;this.applyGroupField();var b=function(){this.fireEvent("groupchange",this,this.getGroupState())};if(this.groupOnSort){this.sort(e,d);b.call(this);return}if(this.remoteGroup){this.on("load",b,this,{single:true});this.reload()}else{this.sort(c);b.call(this)}},sort:function(h,c){if(this.remoteSort){return Ext.data.GroupingStore.superclass.sort.call(this,h,c)}var g=[];if(Ext.isArray(arguments[0])){g=arguments[0]}else{if(h==undefined){g=this.sortInfo?[this.sortInfo]:[]}else{var e=this.fields.get(h);if(!e){return false}var b=e.name,a=this.sortInfo||null,d=this.sortToggle?this.sortToggle[b]:null;if(!c){if(a&&a.field==b){c=(this.sortToggle[b]||"ASC").toggle("ASC","DESC")}else{c=e.sortDir}}this.sortToggle[b]=c;this.sortInfo={field:b,direction:c};g=[this.sortInfo]}}if(this.groupField){g.unshift({direction:this.groupDir,field:this.groupField})}return this.multiSort.call(this,g,c)},applyGroupField:function(){if(this.remoteGroup){if(!this.baseParams){this.baseParams={}}Ext.apply(this.baseParams,{groupBy:this.groupField,groupDir:this.groupDir});var a=this.lastOptions;if(a&&a.params){a.params.groupDir=this.groupDir;delete a.params.groupBy}}},applyGrouping:function(a){if(this.groupField!==false){this.groupBy(this.groupField,true,this.groupDir);return true}else{if(a===true){this.fireEvent("datachanged",this)}return false}},getGroupState:function(){return this.groupOnSort&&this.groupField!==false?(this.sortInfo?this.sortInfo.field:undefined):this.groupField}});Ext.reg("groupingstore",Ext.data.GroupingStore);Ext.data.DirectProxy=function(a){Ext.apply(this,a);if(typeof this.paramOrder=="string"){this.paramOrder=this.paramOrder.split(/[\s,|]/)}Ext.data.DirectProxy.superclass.constructor.call(this,a)};Ext.extend(Ext.data.DirectProxy,Ext.data.DataProxy,{paramOrder:undefined,paramsAsHash:true,directFn:undefined,doRequest:function(b,c,a,e,k,l,n){var j=[],h=this.api[b]||this.directFn;switch(b){case Ext.data.Api.actions.create:j.push(a.jsonData);break;case Ext.data.Api.actions.read:if(h.directCfg.method.len>0){if(this.paramOrder){for(var d=0,g=this.paramOrder.length;d1){for(var d=0,b=c.length;d0){this.doSend(a==1?this.callBuffer[0]:this.callBuffer);this.callBuffer=[]}},queueTransaction:function(a){if(a.form){this.processForm(a);return}this.callBuffer.push(a);if(this.enableBuffer){if(!this.callTask){this.callTask=new Ext.util.DelayedTask(this.combineAndSend,this)}this.callTask.delay(Ext.isNumber(this.enableBuffer)?this.enableBuffer:10)}else{this.combineAndSend()}},doCall:function(i,a,b){var h=null,e=b[a.len],g=b[a.len+1];if(a.len!==0){h=b.slice(0,a.len)}var d=new Ext.Direct.Transaction({provider:this,args:b,action:i,method:a.name,data:h,cb:g&&Ext.isFunction(e)?e.createDelegate(g):e});if(this.fireEvent("beforecall",this,d,a)!==false){Ext.Direct.addTransaction(d);this.queueTransaction(d);this.fireEvent("call",this,d,a)}},doForm:function(j,b,g,i,e){var d=new Ext.Direct.Transaction({provider:this,action:j,method:b.name,args:[g,i,e],cb:e&&Ext.isFunction(i)?i.createDelegate(e):i,isForm:true});if(this.fireEvent("beforecall",this,d,b)!==false){Ext.Direct.addTransaction(d);var a=String(g.getAttribute("enctype")).toLowerCase()=="multipart/form-data",h={extTID:d.tid,extAction:j,extMethod:b.name,extType:"rpc",extUpload:String(a)};Ext.apply(d,{form:Ext.getDom(g),isUpload:a,params:i&&Ext.isObject(i.params)?Ext.apply(h,i.params):h});this.fireEvent("call",this,d,b);this.processForm(d)}},processForm:function(a){Ext.Ajax.request({url:this.url,params:a.params,callback:this.onData,scope:this,form:a.form,isUpload:a.isUpload,ts:a})},createMethod:function(d,a){var b;if(!a.formHandler){b=function(){this.doCall(d,a,Array.prototype.slice.call(arguments,0))}.createDelegate(this)}else{b=function(e,g,c){this.doForm(d,a,e,g,c)}.createDelegate(this)}b.directCfg={action:d,method:a};return b},getTransaction:function(a){return a&&a.tid?Ext.Direct.getTransaction(a.tid):null},doCallback:function(c,g){var d=g.status?"success":"failure";if(c&&c.cb){var b=c.cb,a=Ext.isDefined(g.result)?g.result:g.data;if(Ext.isFunction(b)){b(a,g)}else{Ext.callback(b[d],b.scope,[a,g]);Ext.callback(b.callback,b.scope,[a,g])}}}});Ext.Direct.PROVIDERS.remoting=Ext.direct.RemotingProvider;Ext.Resizable=Ext.extend(Ext.util.Observable,{constructor:function(d,e){this.el=Ext.get(d);if(e&&e.wrap){e.resizeChild=this.el;this.el=this.el.wrap(typeof e.wrap=="object"?e.wrap:{cls:"xresizable-wrap"});this.el.id=this.el.dom.id=e.resizeChild.id+"-rzwrap";this.el.setStyle("overflow","hidden");this.el.setPositioning(e.resizeChild.getPositioning());e.resizeChild.clearPositioning();if(!e.width||!e.height){var g=e.resizeChild.getSize();this.el.setSize(g.width,g.height)}if(e.pinned&&!e.adjustments){e.adjustments="auto"}}this.proxy=this.el.createProxy({tag:"div",cls:"x-resizable-proxy",id:this.el.id+"-rzproxy"},Ext.getBody());this.proxy.unselectable();this.proxy.enableDisplayMode("block");Ext.apply(this,e);if(this.pinned){this.disableTrackOver=true;this.el.addClass("x-resizable-pinned")}var k=this.el.getStyle("position");if(k!="absolute"&&k!="fixed"){this.el.setStyle("position","relative")}if(!this.handles){this.handles="s,e,se";if(this.multiDirectional){this.handles+=",n,w"}}if(this.handles=="all"){this.handles="n s e w ne nw se sw"}var o=this.handles.split(/\s*?[,;]\s*?| /);var c=Ext.Resizable.positions;for(var j=0,l=o.length;j0){if(a>(e/2)){d=c+(e-a)}else{d=c-a}}return Math.max(b,d)},resizeElement:function(){var a=this.proxy.getBox();if(this.updateBox){this.el.setBox(a,false,this.animate,this.duration,null,this.easing)}else{this.el.setSize(a.width,a.height,this.animate,this.duration,null,this.easing)}this.updateChildSize();if(!this.dynamic){this.proxy.hide()}if(this.draggable&&this.constrainTo){this.dd.resetConstraints();this.dd.constrainTo(this.constrainTo)}return a},constrain:function(b,c,a,d){if(b-cd){c=b-d}}return c},onMouseMove:function(z){if(this.enabled&&this.activeHandle){try{if(this.resizeRegion&&!this.resizeRegion.contains(z.getPoint())){return}var t=this.curSize||this.startBox,l=this.startBox.x,k=this.startBox.y,c=l,b=k,m=t.width,u=t.height,d=m,o=u,n=this.minWidth,A=this.minHeight,s=this.maxWidth,D=this.maxHeight,i=this.widthIncrement,a=this.heightIncrement,B=z.getXY(),r=-(this.startPoint[0]-Math.max(this.minX,B[0])),p=-(this.startPoint[1]-Math.max(this.minY,B[1])),j=this.activeHandle.position,E,g;switch(j){case"east":m+=r;m=Math.min(Math.max(n,m),s);break;case"south":u+=p;u=Math.min(Math.max(A,u),D);break;case"southeast":m+=r;u+=p;m=Math.min(Math.max(n,m),s);u=Math.min(Math.max(A,u),D);break;case"north":p=this.constrain(u,p,A,D);k+=p;u-=p;break;case"west":r=this.constrain(m,r,n,s);l+=r;m-=r;break;case"northeast":m+=r;m=Math.min(Math.max(n,m),s);p=this.constrain(u,p,A,D);k+=p;u-=p;break;case"northwest":r=this.constrain(m,r,n,s);p=this.constrain(u,p,A,D);k+=p;u-=p;l+=r;m-=r;break;case"southwest":r=this.constrain(m,r,n,s);u+=p;u=Math.min(Math.max(A,u),D);l+=r;m-=r;break}var q=this.snap(m,i,n);var C=this.snap(u,a,A);if(q!=m||C!=u){switch(j){case"northeast":k-=C-u;break;case"north":k-=C-u;break;case"southwest":l-=q-m;break;case"west":l-=q-m;break;case"northwest":l-=q-m;k-=C-u;break}m=q;u=C}if(this.preserveRatio){switch(j){case"southeast":case"east":u=o*(m/d);u=Math.min(Math.max(A,u),D);m=d*(u/o);break;case"south":m=d*(u/o);m=Math.min(Math.max(n,m),s);u=o*(m/d);break;case"northeast":m=d*(u/o);m=Math.min(Math.max(n,m),s);u=o*(m/d);break;case"north":E=m;m=d*(u/o);m=Math.min(Math.max(n,m),s);u=o*(m/d);l+=(E-m)/2;break;case"southwest":u=o*(m/d);u=Math.min(Math.max(A,u),D);E=m;m=d*(u/o);l+=E-m;break;case"west":g=u;u=o*(m/d);u=Math.min(Math.max(A,u),D);k+=(g-u)/2;E=m;m=d*(u/o);l+=E-m;break;case"northwest":E=m;g=u;u=o*(m/d);u=Math.min(Math.max(A,u),D);m=d*(u/o);k+=g-u;l+=E-m;break}}this.proxy.setBounds(l,k,m,u);if(this.dynamic){this.resizeElement()}}catch(v){}}},handleOver:function(){if(this.enabled){this.el.addClass("x-resizable-over")}},handleOut:function(){if(!this.resizing){this.el.removeClass("x-resizable-over")}},getEl:function(){return this.el},getResizeChild:function(){return this.resizeChild},destroy:function(b){Ext.destroy(this.dd,this.overlay,this.proxy);this.overlay=null;this.proxy=null;var c=Ext.Resizable.positions;for(var a in c){if(typeof c[a]!="function"&&this[c[a]]){this[c[a]].destroy()}}if(b){this.el.update("");Ext.destroy(this.el);this.el=null}this.purgeListeners()},syncHandleHeight:function(){var a=this.el.getHeight(true);if(this.west){this.west.el.setHeight(a)}if(this.east){this.east.el.setHeight(a)}}});Ext.Resizable.positions={n:"north",s:"south",e:"east",w:"west",se:"southeast",sw:"southwest",nw:"northwest",ne:"northeast"};Ext.Resizable.Handle=Ext.extend(Object,{constructor:function(d,g,c,e,a){if(!this.tpl){var b=Ext.DomHelper.createTemplate({tag:"div",cls:"x-resizable-handle x-resizable-handle-{0}"});b.compile();Ext.Resizable.Handle.prototype.tpl=b}this.position=g;this.rz=d;this.el=this.tpl.append(d.el.dom,[this.position],true);this.el.unselectable();if(e){this.el.setOpacity(0)}if(!Ext.isEmpty(a)){this.el.addClass(a)}this.el.on("mousedown",this.onMouseDown,this);if(!c){this.el.on({scope:this,mouseover:this.onMouseOver,mouseout:this.onMouseOut})}},afterResize:function(a){},onMouseDown:function(a){this.rz.onMouseDown(this,a)},onMouseOver:function(a){this.rz.handleOver(this,a)},onMouseOut:function(a){this.rz.handleOut(this,a)},destroy:function(){Ext.destroy(this.el);this.el=null}});Ext.Window=Ext.extend(Ext.Panel,{baseCls:"x-window",resizable:true,draggable:true,closable:true,closeAction:"close",constrain:false,constrainHeader:false,plain:false,minimizable:false,maximizable:false,minHeight:100,minWidth:200,expandOnShow:true,showAnimDuration:0.25,hideAnimDuration:0.25,collapsible:false,initHidden:undefined,hidden:true,elements:"header,body",frame:true,floating:true,initComponent:function(){this.initTools();Ext.Window.superclass.initComponent.call(this);this.addEvents("resize","maximize","minimize","restore");if(Ext.isDefined(this.initHidden)){this.hidden=this.initHidden}if(this.hidden===false){this.hidden=true;this.show()}},getState:function(){return Ext.apply(Ext.Window.superclass.getState.call(this)||{},this.getBox(true))},onRender:function(b,a){Ext.Window.superclass.onRender.call(this,b,a);if(this.plain){this.el.addClass("x-window-plain")}this.focusEl=this.el.createChild({tag:"a",href:"#",cls:"x-dlg-focus",tabIndex:"-1",html:" "});this.focusEl.swallowEvent("click",true);this.proxy=this.el.createProxy("x-window-proxy");this.proxy.enableDisplayMode("block");if(this.modal){this.mask=this.container.createChild({cls:"ext-el-mask"},this.el.dom);this.mask.enableDisplayMode("block");this.mask.hide();this.mon(this.mask,"click",this.focus,this)}if(this.maximizable){this.mon(this.header,"dblclick",this.toggleMaximize,this)}},initEvents:function(){Ext.Window.superclass.initEvents.call(this);if(this.animateTarget){this.setAnimateTarget(this.animateTarget)}if(this.resizable){this.resizer=new Ext.Resizable(this.el,{minWidth:this.minWidth,minHeight:this.minHeight,handles:this.resizeHandles||"all",pinned:true,resizeElement:this.resizerAction,handleCls:"x-window-handle"});this.resizer.window=this;this.mon(this.resizer,"beforeresize",this.beforeResize,this)}if(this.draggable){this.header.addClass("x-window-draggable")}this.mon(this.el,"mousedown",this.toFront,this);this.manager=this.manager||Ext.WindowMgr;this.manager.register(this);if(this.maximized){this.maximized=false;this.maximize()}if(this.closable){var a=this.getKeyMap();a.on(27,this.onEsc,this);a.disable()}},initDraggable:function(){this.dd=new Ext.Window.DD(this)},onEsc:function(a,b){if(this.activeGhost){this.unghost()}b.stopEvent();this[this.closeAction]()},beforeDestroy:function(){if(this.rendered){this.hide();this.clearAnchor();Ext.destroy(this.focusEl,this.resizer,this.dd,this.proxy,this.mask)}Ext.Window.superclass.beforeDestroy.call(this)},onDestroy:function(){if(this.manager){this.manager.unregister(this)}Ext.Window.superclass.onDestroy.call(this)},initTools:function(){if(this.minimizable){this.addTool({id:"minimize",handler:this.minimize.createDelegate(this,[])})}if(this.maximizable){this.addTool({id:"maximize",handler:this.maximize.createDelegate(this,[])});this.addTool({id:"restore",handler:this.restore.createDelegate(this,[]),hidden:true})}if(this.closable){this.addTool({id:"close",handler:this[this.closeAction].createDelegate(this,[])})}},resizerAction:function(){var a=this.proxy.getBox();this.proxy.hide();this.window.handleResize(a);return a},beforeResize:function(){this.resizer.minHeight=Math.max(this.minHeight,this.getFrameHeight()+40);this.resizer.minWidth=Math.max(this.minWidth,this.getFrameWidth()+40);this.resizeBox=this.el.getBox()},updateHandles:function(){if(Ext.isIE&&this.resizer){this.resizer.syncHandleHeight();this.el.repaint()}},handleResize:function(b){var a=this.resizeBox;if(a.x!=b.x||a.y!=b.y){this.updateBox(b)}else{this.setSize(b);if(Ext.isIE6&&Ext.isStrict){this.doLayout()}}this.focus();this.updateHandles();this.saveState()},focus:function(){var e=this.focusEl,a=this.defaultButton,c=typeof a,d,b;if(Ext.isDefined(a)){if(Ext.isNumber(a)&&this.fbar){e=this.fbar.items.get(a)}else{if(Ext.isString(a)){e=Ext.getCmp(a)}else{e=a}}d=e.getEl();b=Ext.getDom(this.container);if(d&&b){if(b!=document.body&&!Ext.lib.Region.getRegion(b).contains(Ext.lib.Region.getRegion(d.dom))){return}}}e=e||this.focusEl;e.focus.defer(10,e)},setAnimateTarget:function(a){a=Ext.get(a);this.animateTarget=a},beforeShow:function(){delete this.el.lastXY;delete this.el.lastLT;if(this.x===undefined||this.y===undefined){var a=this.el.getAlignToXY(this.container,"c-c");var b=this.el.translatePoints(a[0],a[1]);this.x=this.x===undefined?b.left:this.x;this.y=this.y===undefined?b.top:this.y}this.el.setLeftTop(this.x,this.y);if(this.expandOnShow){this.expand(false)}if(this.modal){Ext.getBody().addClass("x-body-masked");this.mask.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true));this.mask.show()}},show:function(c,a,b){if(!this.rendered){this.render(Ext.getBody())}if(this.hidden===false){this.toFront();return this}if(this.fireEvent("beforeshow",this)===false){return this}if(a){this.on("show",a,b,{single:true})}this.hidden=false;if(Ext.isDefined(c)){this.setAnimateTarget(c)}this.beforeShow();if(this.animateTarget){this.animShow()}else{this.afterShow()}return this},afterShow:function(b){if(this.isDestroyed){return false}this.proxy.hide();this.el.setStyle("display","block");this.el.show();if(this.maximized){this.fitContainer()}if(Ext.isMac&&Ext.isGecko2){this.cascade(this.setAutoScroll)}if(this.monitorResize||this.modal||this.constrain||this.constrainHeader){Ext.EventManager.onWindowResize(this.onWindowResize,this)}this.doConstrain();this.doLayout();if(this.keyMap){this.keyMap.enable()}this.toFront();this.updateHandles();if(b&&(Ext.isIE||Ext.isWebKit)){var a=this.getSize();this.onResize(a.width,a.height)}this.onShow();this.fireEvent("show",this)},animShow:function(){this.proxy.show();this.proxy.setBox(this.animateTarget.getBox());this.proxy.setOpacity(0);var a=this.getBox();this.el.setStyle("display","none");this.proxy.shift(Ext.apply(a,{callback:this.afterShow.createDelegate(this,[true],false),scope:this,easing:"easeNone",duration:this.showAnimDuration,opacity:0.5}))},hide:function(c,a,b){if(this.hidden||this.fireEvent("beforehide",this)===false){return this}if(a){this.on("hide",a,b,{single:true})}this.hidden=true;if(c!==undefined){this.setAnimateTarget(c)}if(this.modal){this.mask.hide();Ext.getBody().removeClass("x-body-masked")}if(this.animateTarget){this.animHide()}else{this.el.hide();this.afterHide()}return this},afterHide:function(){this.proxy.hide();if(this.monitorResize||this.modal||this.constrain||this.constrainHeader){Ext.EventManager.removeResizeListener(this.onWindowResize,this)}if(this.keyMap){this.keyMap.disable()}this.onHide();this.fireEvent("hide",this)},animHide:function(){this.proxy.setOpacity(0.5);this.proxy.show();var a=this.getBox(false);this.proxy.setBox(a);this.el.hide();this.proxy.shift(Ext.apply(this.animateTarget.getBox(),{callback:this.afterHide,scope:this,duration:this.hideAnimDuration,easing:"easeNone",opacity:0}))},onShow:Ext.emptyFn,onHide:Ext.emptyFn,onWindowResize:function(){if(this.maximized){this.fitContainer()}if(this.modal){this.mask.setSize("100%","100%");var a=this.mask.dom.offsetHeight;this.mask.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true))}this.doConstrain()},doConstrain:function(){if(this.constrain||this.constrainHeader){var b;if(this.constrain){b={right:this.el.shadowOffset,left:this.el.shadowOffset,bottom:this.el.shadowOffset}}else{var a=this.getSize();b={right:-(a.width-100),bottom:-(a.height-25+this.el.getConstrainOffset())}}var c=this.el.getConstrainToXY(this.container,true,b);if(c){this.setPosition(c[0],c[1])}}},ghost:function(a){var c=this.createGhost(a);var b=this.getBox(true);c.setLeftTop(b.x,b.y);c.setWidth(b.width);this.el.hide();this.activeGhost=c;return c},unghost:function(b,a){if(!this.activeGhost){return}if(b!==false){this.el.show();this.focus.defer(10,this);if(Ext.isMac&&Ext.isGecko2){this.cascade(this.setAutoScroll)}}if(a!==false){this.setPosition(this.activeGhost.getLeft(true),this.activeGhost.getTop(true))}this.activeGhost.hide();this.activeGhost.remove();delete this.activeGhost},minimize:function(){this.fireEvent("minimize",this);return this},close:function(){if(this.fireEvent("beforeclose",this)!==false){if(this.hidden){this.doClose()}else{this.hide(null,this.doClose,this)}}},doClose:function(){this.fireEvent("close",this);this.destroy()},maximize:function(){if(!this.maximized){this.expand(false);this.restoreSize=this.getSize();this.restorePos=this.getPosition(true);if(this.maximizable){this.tools.maximize.hide();this.tools.restore.show()}this.maximized=true;this.el.disableShadow();if(this.dd){this.dd.lock()}if(this.collapsible){this.tools.toggle.hide()}this.el.addClass("x-window-maximized");this.container.addClass("x-window-maximized-ct");this.setPosition(0,0);this.fitContainer();this.fireEvent("maximize",this)}return this},restore:function(){if(this.maximized){var a=this.tools;this.el.removeClass("x-window-maximized");if(a.restore){a.restore.hide()}if(a.maximize){a.maximize.show()}this.setPosition(this.restorePos[0],this.restorePos[1]);this.setSize(this.restoreSize.width,this.restoreSize.height);delete this.restorePos;delete this.restoreSize;this.maximized=false;this.el.enableShadow(true);if(this.dd){this.dd.unlock()}if(this.collapsible&&a.toggle){a.toggle.show()}this.container.removeClass("x-window-maximized-ct");this.doConstrain();this.fireEvent("restore",this)}return this},toggleMaximize:function(){return this[this.maximized?"restore":"maximize"]()},fitContainer:function(){var a=this.container.getViewSize(false);this.setSize(a.width,a.height)},setZIndex:function(a){if(this.modal){this.mask.setStyle("z-index",a)}this.el.setZIndex(++a);a+=5;if(this.resizer){this.resizer.proxy.setStyle("z-index",++a)}this.lastZIndex=a},alignTo:function(b,a,c){var d=this.el.getAlignToXY(b,a,c);this.setPagePosition(d[0],d[1]);return this},anchorTo:function(c,e,d,b){this.clearAnchor();this.anchorTarget={el:c,alignment:e,offsets:d};Ext.EventManager.onWindowResize(this.doAnchor,this);var a=typeof b;if(a!="undefined"){Ext.EventManager.on(window,"scroll",this.doAnchor,this,{buffer:a=="number"?b:50})}return this.doAnchor()},doAnchor:function(){var a=this.anchorTarget;this.alignTo(a.el,a.alignment,a.offsets);return this},clearAnchor:function(){if(this.anchorTarget){Ext.EventManager.removeResizeListener(this.doAnchor,this);Ext.EventManager.un(window,"scroll",this.doAnchor,this);delete this.anchorTarget}return this},toFront:function(a){if(this.manager.bringToFront(this)){if(!a||!a.getTarget().focus){this.focus()}}return this},setActive:function(a){if(a){if(!this.maximized){this.el.enableShadow(true)}this.fireEvent("activate",this)}else{this.el.disableShadow();this.fireEvent("deactivate",this)}},toBack:function(){this.manager.sendToBack(this);return this},center:function(){var a=this.el.getAlignToXY(this.container,"c-c");this.setPagePosition(a[0],a[1]);return this}});Ext.reg("window",Ext.Window);Ext.Window.DD=Ext.extend(Ext.dd.DD,{constructor:function(a){this.win=a;Ext.Window.DD.superclass.constructor.call(this,a.el.id,"WindowDD-"+a.id);this.setHandleElId(a.header.id);this.scroll=false},moveOnly:true,headerOffsets:[100,25],startDrag:function(){var a=this.win;this.proxy=a.ghost(a.initialConfig.cls);if(a.constrain!==false){var c=a.el.shadowOffset;this.constrainTo(a.container,{right:c,left:c,bottom:c})}else{if(a.constrainHeader!==false){var b=this.proxy.getSize();this.constrainTo(a.container,{right:-(b.width-this.headerOffsets[0]),bottom:-(b.height-this.headerOffsets[1])})}}},b4Drag:Ext.emptyFn,onDrag:function(a){this.alignElWithMouse(this.proxy,a.getPageX(),a.getPageY())},endDrag:function(a){this.win.unghost();this.win.saveState()}});Ext.WindowGroup=function(){var g={};var d=[];var e=null;var c=function(j,i){return(!j._lastAccess||j._lastAccess0){l.sort(c);var k=l[0].manager.zseed;for(var m=0;m=0;--j){if(!d[j].hidden){b(d[j]);return}}b(null)};return{zseed:9000,register:function(i){if(i.manager){i.manager.unregister(i)}i.manager=this;g[i.id]=i;d.push(i);i.on("hide",a)},unregister:function(i){delete i.manager;delete g[i.id];i.un("hide",a);d.remove(i)},get:function(i){return typeof i=="object"?i:g[i]},bringToFront:function(i){i=this.get(i);if(i!=e){i._lastAccess=new Date().getTime();h();return true}return false},sendToBack:function(i){i=this.get(i);i._lastAccess=-(new Date().getTime());h();return i},hideAll:function(){for(var i in g){if(g[i]&&typeof g[i]!="function"&&g[i].isVisible()){g[i].hide()}}},getActive:function(){return e},getBy:function(l,k){var m=[];for(var j=d.length-1;j>=0;--j){var n=d[j];if(l.call(k||n,n)!==false){m.push(n)}}return m},each:function(j,i){for(var k in g){if(g[k]&&typeof g[k]!="function"){if(j.call(i||g[k],g[k])===false){return}}}}}};Ext.WindowMgr=new Ext.WindowGroup();Ext.MessageBox=function(){var u,b,q,t,h,l,s,a,n,p,j,g,r,v,o,i="",d="",m=["ok","yes","no","cancel"];var c=function(x){r[x].blur();if(u.isVisible()){u.hide();w();Ext.callback(b.fn,b.scope||window,[x,v.dom.value,b],1)}};var w=function(){if(b&&b.cls){u.el.removeClass(b.cls)}n.reset()};var e=function(z,x,y){if(b&&b.closable!==false){u.hide();w()}if(y){y.stopEvent()}};var k=function(x){var z=0,y;if(!x){Ext.each(m,function(A){r[A].hide()});return z}u.footer.dom.style.display="";Ext.iterate(r,function(A,B){y=x[A];if(y){B.show();B.setText(Ext.isString(y)?y:Ext.MessageBox.buttonText[A]);z+=B.getEl().getWidth()+15}else{B.hide()}});return z};return{getDialog:function(x){if(!u){var z=[];r={};Ext.each(m,function(A){z.push(r[A]=new Ext.Button({text:this.buttonText[A],handler:c.createCallback(A),hideMode:"offsets"}))},this);u=new Ext.Window({autoCreate:true,title:x,resizable:false,constrain:true,constrainHeader:true,minimizable:false,maximizable:false,stateful:false,modal:true,shim:true,buttonAlign:"center",width:400,height:100,minHeight:80,plain:true,footer:true,closable:true,close:function(){if(b&&b.buttons&&b.buttons.no&&!b.buttons.cancel){c("no")}else{c("cancel")}},fbar:new Ext.Toolbar({items:z,enableOverflow:false})});u.render(document.body);u.getEl().addClass("x-window-dlg");q=u.mask;h=u.body.createChild({html:'

'});j=Ext.get(h.dom.firstChild);var y=h.dom.childNodes[1];l=Ext.get(y.firstChild);s=Ext.get(y.childNodes[2].firstChild);s.enableDisplayMode();s.addKeyListener([10,13],function(){if(u.isVisible()&&b&&b.buttons){if(b.buttons.ok){c("ok")}else{if(b.buttons.yes){c("yes")}}}});a=Ext.get(y.childNodes[2].childNodes[1]);a.enableDisplayMode();n=new Ext.ProgressBar({renderTo:h});h.createChild({cls:"x-clear"})}return u},updateText:function(A){if(!u.isVisible()&&!b.width){u.setSize(this.maxWidth,100)}l.update(A?A+" ":" ");var y=d!=""?(j.getWidth()+j.getMargins("lr")):0,C=l.getWidth()+l.getMargins("lr"),z=u.getFrameWidth("lr"),B=u.body.getFrameWidth("lr"),x;x=Math.max(Math.min(b.width||y+C+z+B,b.maxWidth||this.maxWidth),Math.max(b.minWidth||this.minWidth,o||0));if(b.prompt===true){v.setWidth(x-y-z-B)}if(b.progress===true||b.wait===true){n.setSize(x-y-z-B)}if(Ext.isIE&&x==o){x+=4}l.update(A||" ");u.setSize(x,"auto").center();return this},updateProgress:function(y,x,z){n.updateProgress(y,x);if(z){this.updateText(z)}return this},isVisible:function(){return u&&u.isVisible()},hide:function(){var x=u?u.activeGhost:null;if(this.isVisible()||x){u.hide();w();if(x){u.unghost(false,false)}}return this},show:function(A){if(this.isVisible()){this.hide()}b=A;var B=this.getDialog(b.title||" ");B.setTitle(b.title||" ");var x=(b.closable!==false&&b.progress!==true&&b.wait!==true);B.tools.close.setDisplayed(x);v=s;b.prompt=b.prompt||(b.multiline?true:false);if(b.prompt){if(b.multiline){s.hide();a.show();a.setHeight(Ext.isNumber(b.multiline)?b.multiline:this.defaultTextHeight);v=a}else{s.show();a.hide()}}else{s.hide();a.hide()}v.dom.value=b.value||"";if(b.prompt){B.focusEl=v}else{var z=b.buttons;var y=null;if(z&&z.ok){y=r.ok}else{if(z&&z.yes){y=r.yes}}if(y){B.focusEl=y}}if(Ext.isDefined(b.iconCls)){B.setIconClass(b.iconCls)}this.setIcon(Ext.isDefined(b.icon)?b.icon:i);o=k(b.buttons);n.setVisible(b.progress===true||b.wait===true);this.updateProgress(0,b.progressText);this.updateText(b.msg);if(b.cls){B.el.addClass(b.cls)}B.proxyDrag=b.proxyDrag===true;B.modal=b.modal!==false;B.mask=b.modal!==false?q:false;if(!B.isVisible()){document.body.appendChild(u.el.dom);B.setAnimateTarget(b.animEl);B.on("show",function(){if(x===true){B.keyMap.enable()}else{B.keyMap.disable()}},this,{single:true});B.show(b.animEl)}if(b.wait===true){n.wait(b.waitConfig)}return this},setIcon:function(x){if(!u){i=x;return}i=undefined;if(x&&x!=""){j.removeClass("x-hidden");j.replaceClass(d,x);h.addClass("x-dlg-icon");d=x}else{j.replaceClass(d,"x-hidden");h.removeClass("x-dlg-icon");d=""}return this},progress:function(z,y,x){this.show({title:z,msg:y,buttons:false,progress:true,closable:false,minWidth:this.minProgressWidth,progressText:x});return this},wait:function(z,y,x){this.show({title:y,msg:z,buttons:false,closable:false,wait:true,modal:true,minWidth:this.minProgressWidth,waitConfig:x});return this},alert:function(A,z,y,x){this.show({title:A,msg:z,buttons:this.OK,fn:y,scope:x,minWidth:this.minWidth});return this},confirm:function(A,z,y,x){this.show({title:A,msg:z,buttons:this.YESNO,fn:y,scope:x,icon:this.QUESTION,minWidth:this.minWidth});return this},prompt:function(C,B,z,y,x,A){this.show({title:C,msg:B,buttons:this.OKCANCEL,fn:z,minWidth:this.minPromptWidth,scope:y,prompt:true,multiline:x,value:A});return this},OK:{ok:true},CANCEL:{cancel:true},OKCANCEL:{ok:true,cancel:true},YESNO:{yes:true,no:true},YESNOCANCEL:{yes:true,no:true,cancel:true},INFO:"ext-mb-info",WARNING:"ext-mb-warning",QUESTION:"ext-mb-question",ERROR:"ext-mb-error",defaultTextHeight:75,maxWidth:600,minWidth:100,minProgressWidth:250,minPromptWidth:250,buttonText:{ok:"OK",cancel:"Cancel",yes:"Yes",no:"No"}}}();Ext.Msg=Ext.MessageBox;Ext.dd.PanelProxy=Ext.extend(Object,{constructor:function(a,b){this.panel=a;this.id=this.panel.id+"-ddproxy";Ext.apply(this,b)},insertProxy:true,setStatus:Ext.emptyFn,reset:Ext.emptyFn,update:Ext.emptyFn,stop:Ext.emptyFn,sync:Ext.emptyFn,getEl:function(){return this.ghost},getGhost:function(){return this.ghost},getProxy:function(){return this.proxy},hide:function(){if(this.ghost){if(this.proxy){this.proxy.remove();delete this.proxy}this.panel.el.dom.style.display="";this.ghost.remove();delete this.ghost}},show:function(){if(!this.ghost){this.ghost=this.panel.createGhost(this.panel.initialConfig.cls,undefined,Ext.getBody());this.ghost.setXY(this.panel.el.getXY());if(this.insertProxy){this.proxy=this.panel.el.insertSibling({cls:"x-panel-dd-spacer"});this.proxy.setSize(this.panel.getSize())}this.panel.el.dom.style.display="none"}},repair:function(b,c,a){this.hide();if(typeof c=="function"){c.call(a||this)}},moveProxy:function(a,b){if(this.proxy){a.insertBefore(this.proxy.dom,b)}}});Ext.Panel.DD=Ext.extend(Ext.dd.DragSource,{constructor:function(b,a){this.panel=b;this.dragData={panel:b};this.proxy=new Ext.dd.PanelProxy(b,a);Ext.Panel.DD.superclass.constructor.call(this,b.el,a);var d=b.header,c=b.body;if(d){this.setHandleElId(d.id);c=b.header}c.setStyle("cursor","move");this.scroll=false},showFrame:Ext.emptyFn,startDrag:Ext.emptyFn,b4StartDrag:function(a,b){this.proxy.show()},b4MouseDown:function(b){var a=b.getPageX(),c=b.getPageY();this.autoOffset(a,c)},onInitDrag:function(a,b){this.onStartDrag(a,b);return true},createFrame:Ext.emptyFn,getDragEl:function(a){return this.proxy.ghost.dom},endDrag:function(a){this.proxy.hide();this.panel.saveState()},autoOffset:function(a,b){a-=this.startPageX;b-=this.startPageY;this.setDelta(a,b)}});Ext.state.Provider=Ext.extend(Ext.util.Observable,{constructor:function(){this.addEvents("statechange");this.state={};Ext.state.Provider.superclass.constructor.call(this)},get:function(b,a){return typeof this.state[b]=="undefined"?a:this.state[b]},clear:function(a){delete this.state[a];this.fireEvent("statechange",this,a,null)},set:function(a,b){this.state[a]=b;this.fireEvent("statechange",this,a,b)},decodeValue:function(b){var e=/^(a|n|d|b|s|o|e)\:(.*)$/,h=e.exec(unescape(b)),d,c,a,g;if(!h||!h[1]){return}c=h[1];a=h[2];switch(c){case"e":return null;case"n":return parseFloat(a);case"d":return new Date(Date.parse(a));case"b":return(a=="1");case"a":d=[];if(a!=""){Ext.each(a.split("^"),function(i){d.push(this.decodeValue(i))},this)}return d;case"o":d={};if(a!=""){Ext.each(a.split("^"),function(i){g=i.split("=");d[g[0]]=this.decodeValue(g[1])},this)}return d;default:return a}},encodeValue:function(c){var b,g="",e=0,a,d;if(c==null){return"e:1"}else{if(typeof c=="number"){b="n:"+c}else{if(typeof c=="boolean"){b="b:"+(c?"1":"0")}else{if(Ext.isDate(c)){b="d:"+c.toGMTString()}else{if(Ext.isArray(c)){for(a=c.length;e-1){var e=this.isSelected(b),c=this.all.elements[b],d=this.bufferRender([a],b)[0];this.all.replaceElement(b,d,true);if(e){this.selected.replaceElement(c,d);this.all.item(b).addClass(this.selectedClass)}this.updateIndexes(b,b)}},onAdd:function(g,d,e){if(this.all.getCount()===0){this.refresh();return}var c=this.bufferRender(d,e),h,b=this.all.elements;if(e0){if(!b){this.selected.removeClass(this.selectedClass)}this.selected.clear();this.last=false;if(!a){this.fireEvent("selectionchange",this,this.selected.elements)}}},isSelected:function(a){return this.selected.contains(this.getNode(a))},deselect:function(a){if(this.isSelected(a)){a=this.getNode(a);this.selected.removeElement(a);if(this.last==a.viewIndex){this.last=false}Ext.fly(a).removeClass(this.selectedClass);this.fireEvent("selectionchange",this,this.selected.elements)}},select:function(d,g,b){if(Ext.isArray(d)){if(!g){this.clearSelections(true)}for(var c=0,a=d.length;c=a&&d[c];c--){b.push(d[c])}}return b},indexOf:function(a){a=this.getNode(a);if(Ext.isNumber(a.viewIndex)){return a.viewIndex}return this.all.indexOf(a)},onBeforeLoad:function(){if(this.loadingText){this.clearSelections(false,true);this.getTemplateTarget().update('
'+this.loadingText+"
");this.all.clear()}},onDestroy:function(){this.all.clear();this.selected.clear();Ext.DataView.superclass.onDestroy.call(this);this.bindStore(null)}});Ext.DataView.prototype.setStore=Ext.DataView.prototype.bindStore;Ext.reg("dataview",Ext.DataView);Ext.list.ListView=Ext.extend(Ext.DataView,{itemSelector:"dl",selectedClass:"x-list-selected",overClass:"x-list-over",scrollOffset:undefined,columnResize:true,columnSort:true,maxColumnWidth:Ext.isIE?99:100,initComponent:function(){if(this.columnResize){this.colResizer=new Ext.list.ColumnResizer(this.colResizer);this.colResizer.init(this)}if(this.columnSort){this.colSorter=new Ext.list.Sorter(this.columnSort);this.colSorter.init(this)}if(!this.internalTpl){this.internalTpl=new Ext.XTemplate('
','','
',"{header}","
","
",'
',"
",'
',"
")}if(!this.tpl){this.tpl=new Ext.XTemplate('',"
",'','
',' class="{cls}">',"{[values.tpl.apply(parent)]}","
","
",'
',"
","
")}var l=this.columns,h=0,k=0,m=l.length,b=[];for(var g=0;gthis.maxColumnWidth){n.width-=(h-this.maxColumnWidth)/100}k++}b.push(n)}l=this.columns=b;if(k10)){b.style.width=d;g.style.width=d}else{b.style.width=c+"px";g.style.width=c+"px";setTimeout(function(){if((a.offsetWidth-a.clientWidth)>10){b.style.width=d;g.style.width=d}},10)}}if(Ext.isNumber(e)){a.style.height=Math.max(0,e-g.parentNode.offsetHeight)+"px"}},updateIndexes:function(){Ext.list.ListView.superclass.updateIndexes.apply(this,arguments);this.verifyInternalSize()},findHeaderIndex:function(g){g=g.dom||g;var a=g.parentNode,d=a.parentNode.childNodes,b=0,e;for(;e=d[b];b++){if(e==a){return b}}return -1},setHdWidths:function(){var d=this.innerHd.dom.getElementsByTagName("div"),c=0,b=this.columns,a=b.length;for(;c','','{text}',"");d.disableFormats=true;d.compile();Ext.TabPanel.prototype.itemTpl=d}this.items.each(this.initTab,this)},afterRender:function(){Ext.TabPanel.superclass.afterRender.call(this);if(this.autoTabs){this.readTabs(false)}if(this.activeTab!==undefined){var a=Ext.isObject(this.activeTab)?this.activeTab:this.items.get(this.activeTab);delete this.activeTab;this.setActiveTab(a)}},initEvents:function(){Ext.TabPanel.superclass.initEvents.call(this);this.mon(this.strip,{scope:this,mousedown:this.onStripMouseDown,contextmenu:this.onStripContextMenu});if(this.enableTabScroll){this.mon(this.strip,"mousewheel",this.onWheel,this)}},findTargets:function(c){var b=null,a=c.getTarget("li:not(.x-tab-edge)",this.strip);if(a){b=this.getComponent(a.id.split(this.idDelimiter)[1]);if(b.disabled){return{close:null,item:null,el:null}}}return{close:c.getTarget(".x-tab-strip-close",this.strip),item:b,el:a}},onStripMouseDown:function(b){if(b.button!==0){return}b.preventDefault();var a=this.findTargets(b);if(a.close){if(a.item.fireEvent("beforeclose",a.item)!==false){a.item.fireEvent("close",a.item);this.remove(a.item)}return}if(a.item&&a.item!=this.activeTab){this.setActiveTab(a.item)}},onStripContextMenu:function(b){b.preventDefault();var a=this.findTargets(b);if(a.item){this.fireEvent("contextmenu",this,a.item,b)}},readTabs:function(d){if(d===true){this.items.each(function(h){this.remove(h)},this)}var c=this.el.query(this.autoTabSelector);for(var b=0,a=c.length;b0){this.setActiveTab(0)}else{this.setActiveTab(null)}}}if(!this.destroying){this.delegateUpdates()}},onBeforeShowItem:function(a){if(a!=this.activeTab){this.setActiveTab(a);return false}},onItemDisabled:function(b){var a=this.getTabEl(b);if(a){Ext.fly(a).addClass("x-item-disabled")}this.stack.remove(b)},onItemEnabled:function(b){var a=this.getTabEl(b);if(a){Ext.fly(a).removeClass("x-item-disabled")}},onItemTitleChanged:function(b){var a=this.getTabEl(b);if(a){Ext.fly(a).child("span.x-tab-strip-text",true).innerHTML=b.title}},onItemIconChanged:function(d,a,c){var b=this.getTabEl(d);if(b){b=Ext.get(b);b.child("span.x-tab-strip-text").replaceClass(c,a);b[Ext.isEmpty(a)?"removeClass":"addClass"]("x-tab-with-icon")}},getTabEl:function(a){var b=this.getComponent(a);return b?b.tabEl:null},onResize:function(){Ext.TabPanel.superclass.onResize.apply(this,arguments);this.delegateUpdates()},beginUpdate:function(){this.suspendUpdates=true},endUpdate:function(){this.suspendUpdates=false;this.delegateUpdates()},hideTabStripItem:function(b){b=this.getComponent(b);var a=this.getTabEl(b);if(a){a.style.display="none";this.delegateUpdates()}this.stack.remove(b)},unhideTabStripItem:function(b){b=this.getComponent(b);var a=this.getTabEl(b);if(a){a.style.display="";this.delegateUpdates()}},delegateUpdates:function(){var a=this.rendered;if(this.suspendUpdates){return}if(this.resizeTabs&&a){this.autoSizeTabs()}if(this.enableTabScroll&&a){this.autoScrollTabs()}},autoSizeTabs:function(){var h=this.items.length,b=this.tabPosition!="bottom"?"header":"footer",c=this[b].dom.offsetWidth,a=this[b].dom.clientWidth;if(!this.resizeTabs||h<1||!a){return}var k=Math.max(Math.min(Math.floor((a-4)/h)-this.tabMargin,this.tabWidth),this.minTabWidth);this.lastTabWidth=k;var m=this.strip.query("li:not(.x-tab-edge)");for(var e=0,j=m.length;e20?c:20);if(!this.scrolling){if(!this.scrollLeft){this.createScrollers()}else{this.scrollLeft.show();this.scrollRight.show()}}this.scrolling=true;if(i>(a-c)){e.scrollLeft=a-c}else{this.scrollToTab(this.activeTab,false)}this.updateScrollButtons()}},createScrollers:function(){this.pos.addClass("x-tab-scrolling-"+this.tabPosition);var c=this.stripWrap.dom.offsetHeight;var a=this.pos.insertFirst({cls:"x-tab-scroller-left"});a.setHeight(c);a.addClassOnOver("x-tab-scroller-left-over");this.leftRepeater=new Ext.util.ClickRepeater(a,{interval:this.scrollRepeatInterval,handler:this.onScrollLeft,scope:this});this.scrollLeft=a;var b=this.pos.insertFirst({cls:"x-tab-scroller-right"});b.setHeight(c);b.addClassOnOver("x-tab-scroller-right-over");this.rightRepeater=new Ext.util.ClickRepeater(b,{interval:this.scrollRepeatInterval,handler:this.onScrollRight,scope:this});this.scrollRight=b},getScrollWidth:function(){return this.edge.getOffsetsTo(this.stripWrap)[0]+this.getScrollPos()},getScrollPos:function(){return parseInt(this.stripWrap.dom.scrollLeft,10)||0},getScrollArea:function(){return parseInt(this.stripWrap.dom.clientWidth,10)||0},getScrollAnim:function(){return{duration:this.scrollDuration,callback:this.updateScrollButtons,scope:this}},getScrollIncrement:function(){return this.scrollIncrement||(this.resizeTabs?this.lastTabWidth+2:100)},scrollToTab:function(e,a){if(!e){return}var c=this.getTabEl(e),h=this.getScrollPos(),d=this.getScrollArea(),g=Ext.fly(c).getOffsetsTo(this.stripWrap)[0]+h,b=g+c.offsetWidth;if(g(h+d)){this.scrollTo(b-d,a)}}},scrollTo:function(b,a){this.stripWrap.scrollTo("left",b,a?this.getScrollAnim():false);if(!a){this.updateScrollButtons()}},onWheel:function(g){var h=g.getWheelDelta()*this.wheelIncrement*-1;g.stopEvent();var i=this.getScrollPos(),c=i+h,a=this.getScrollWidth()-this.getScrollArea();var b=Math.max(0,Math.min(a,c));if(b!=i){this.scrollTo(b,false)}},onScrollRight:function(){var a=this.getScrollWidth()-this.getScrollArea(),c=this.getScrollPos(),b=Math.min(a,c+this.getScrollIncrement());if(b!=c){this.scrollTo(b,this.animScroll)}},onScrollLeft:function(){var b=this.getScrollPos(),a=Math.max(0,b-this.getScrollIncrement());if(a!=b){this.scrollTo(a,this.animScroll)}},updateScrollButtons:function(){var a=this.getScrollPos();this.scrollLeft[a===0?"addClass":"removeClass"]("x-tab-scroller-left-disabled");this.scrollRight[a>=(this.getScrollWidth()-this.getScrollArea())?"addClass":"removeClass"]("x-tab-scroller-right-disabled")},beforeDestroy:function(){Ext.destroy(this.leftRepeater,this.rightRepeater);this.deleteMembers("strip","edge","scrollLeft","scrollRight","stripWrap");this.activeTab=null;Ext.TabPanel.superclass.beforeDestroy.apply(this)}});Ext.reg("tabpanel",Ext.TabPanel);Ext.TabPanel.prototype.activate=Ext.TabPanel.prototype.setActiveTab;Ext.TabPanel.AccessStack=function(){var a=[];return{add:function(b){a.push(b);if(a.length>10){a.shift()}},remove:function(e){var d=[];for(var c=0,b=a.length;c','  ','  ','  ',"");Ext.Button.buttonTemplate.compile()}this.template=Ext.Button.buttonTemplate}var b,d=this.getTemplateArgs();if(a){b=this.template.insertBefore(a,d,true)}else{b=this.template.append(c,d,true)}this.btnEl=b.child(this.buttonSelector);this.mon(this.btnEl,{scope:this,focus:this.onFocus,blur:this.onBlur});this.initButtonEl(b,this.btnEl);Ext.ButtonToggleMgr.register(this)},initButtonEl:function(b,c){this.el=b;this.setIcon(this.icon);this.setText(this.text);this.setIconClass(this.iconCls);if(Ext.isDefined(this.tabIndex)){c.dom.tabIndex=this.tabIndex}if(this.tooltip){this.setTooltip(this.tooltip,true)}if(this.handleMouseEvents){this.mon(b,{scope:this,mouseover:this.onMouseOver,mousedown:this.onMouseDown})}if(this.menu){this.mon(this.menu,{scope:this,show:this.onMenuShow,hide:this.onMenuHide})}if(this.repeat){var a=new Ext.util.ClickRepeater(b,Ext.isObject(this.repeat)?this.repeat:{});this.mon(a,"click",this.onRepeatClick,this)}else{this.mon(b,this.clickEvent,this.onClick,this)}},afterRender:function(){Ext.Button.superclass.afterRender.call(this);this.useSetClass=true;this.setButtonClass();this.doc=Ext.getDoc();this.doAutoWidth()},setIconClass:function(a){this.iconCls=a;if(this.el){this.btnEl.dom.className="";this.btnEl.addClass(["x-btn-text",a||""]);this.setButtonClass()}return this},setTooltip:function(b,a){if(this.rendered){if(!a){this.clearTip()}if(Ext.isObject(b)){Ext.QuickTips.register(Ext.apply({target:this.btnEl.id},b));this.tooltip=b}else{this.btnEl.dom[this.tooltipType]=b}}else{this.tooltip=b}return this},clearTip:function(){if(Ext.isObject(this.tooltip)){Ext.QuickTips.unregister(this.btnEl)}},beforeDestroy:function(){if(this.rendered){this.clearTip()}if(this.menu&&this.destroyMenu!==false){Ext.destroy(this.btnEl,this.menu)}Ext.destroy(this.repeater)},onDestroy:function(){if(this.rendered){this.doc.un("mouseover",this.monitorMouseOver,this);this.doc.un("mouseup",this.onMouseUp,this);delete this.doc;delete this.btnEl;Ext.ButtonToggleMgr.unregister(this)}Ext.Button.superclass.onDestroy.call(this)},doAutoWidth:function(){if(this.autoWidth!==false&&this.el&&this.text&&this.width===undefined){this.el.setWidth("auto");if(Ext.isIE7&&Ext.isStrict){var a=this.btnEl;if(a&&a.getWidth()>20){a.clip();a.setWidth(Ext.util.TextMetrics.measure(a,this.text).width+a.getFrameWidth("lr"))}}if(this.minWidth){if(this.el.getWidth()a}else{return c.getPageY()>this.btnEl.getRegion().bottom}},onClick:function(b,a){b.preventDefault();if(!this.disabled){if(this.isClickOnArrow(b)){if(this.menu&&!this.menu.isVisible()&&!this.ignoreNextClick){this.showMenu()}this.fireEvent("arrowclick",this,b);if(this.arrowHandler){this.arrowHandler.call(this.scope||this,this,b)}}else{this.doToggle();this.fireEvent("click",this,b);if(this.handler){this.handler.call(this.scope||this,this,b)}}}},isMenuTriggerOver:function(a){return this.menu&&a.target.tagName==this.arrowSelector},isMenuTriggerOut:function(b,a){return this.menu&&b.target.tagName!=this.arrowSelector}});Ext.reg("splitbutton",Ext.SplitButton);Ext.CycleButton=Ext.extend(Ext.SplitButton,{getItemText:function(a){if(a&&this.showText===true){var b="";if(this.prependText){b+=this.prependText}b+=a.text;return b}return undefined},setActiveItem:function(c,a){if(!Ext.isObject(c)){c=this.menu.getComponent(c)}if(c){if(!this.rendered){this.text=this.getItemText(c);this.iconCls=c.iconCls}else{var b=this.getItemText(c);if(b){this.setText(b)}this.setIconClass(c.iconCls)}this.activeItem=c;if(!c.checked){c.setChecked(true,a)}if(this.forceIcon){this.setIconClass(this.forceIcon)}if(!a){this.fireEvent("change",this,c)}}},getActiveItem:function(){return this.activeItem},initComponent:function(){this.addEvents("change");if(this.changeHandler){this.on("change",this.changeHandler,this.scope||this);delete this.changeHandler}this.itemCount=this.items.length;this.menu={cls:"x-cycle-menu",items:[]};var a=0;Ext.each(this.items,function(c,b){Ext.apply(c,{group:c.group||this.id,itemIndex:b,checkHandler:this.checkHandler,scope:this,checked:c.checked||false});this.menu.items.push(c);if(c.checked){a=b}},this);Ext.CycleButton.superclass.initComponent.call(this);this.on("click",this.toggleSelected,this);this.setActiveItem(a,true)},checkHandler:function(a,b){if(b){this.setActiveItem(a)}},toggleSelected:function(){var a=this.menu;a.render();if(!a.hasLayout){a.doLayout()}var d,b;for(var c=1;c"){b=new a.Fill()}else{b=new a.TextItem(b)}}}this.applyDefaults(b)}else{if(b.isFormField||b.render){b=this.createComponent(b)}else{if(b.tag){b=new a.Item({autoEl:b})}else{if(b.tagName){b=new a.Item({el:b})}else{if(Ext.isObject(b)){b=b.xtype?this.createComponent(b):this.constructButton(b)}}}}}return b},applyDefaults:function(e){if(!Ext.isString(e)){e=Ext.Toolbar.superclass.applyDefaults.call(this,e);var b=this.internalDefaults;if(e.events){Ext.applyIf(e.initialConfig,b);Ext.apply(e,b)}else{Ext.applyIf(e,b)}}return e},addSeparator:function(){return this.add(new a.Separator())},addSpacer:function(){return this.add(new a.Spacer())},addFill:function(){this.add(new a.Fill())},addElement:function(b){return this.addItem(new a.Item({el:b}))},addItem:function(b){return this.add.apply(this,arguments)},addButton:function(c){if(Ext.isArray(c)){var e=[];for(var d=0,b=c.length;d");this.items.push(this.displayItem=new a.TextItem({}))}Ext.PagingToolbar.superclass.initComponent.call(this);this.addEvents("change","beforechange");this.on("afterlayout",this.onFirstLayout,this,{single:true});this.cursor=0;this.bindStore(this.store,true)},onFirstLayout:function(){if(this.dsLoaded){this.onLoad.apply(this,this.dsLoaded)}},updateInfo:function(){if(this.displayItem){var b=this.store.getCount();var c=b==0?this.emptyMsg:String.format(this.displayMsg,this.cursor+1,this.cursor+b,this.store.getTotalCount());this.displayItem.setText(c)}},onLoad:function(b,e,j){if(!this.rendered){this.dsLoaded=[b,e,j];return}var g=this.getParams();this.cursor=(j.params&&j.params[g.start])?j.params[g.start]:0;var i=this.getPageData(),c=i.activePage,h=i.pages;this.afterTextItem.setText(String.format(this.afterPageText,i.pages));this.inputItem.setValue(c);this.first.setDisabled(c==1);this.prev.setDisabled(c==1);this.next.setDisabled(c==h);this.last.setDisabled(c==h);this.refresh.enable();this.updateInfo();this.fireEvent("change",this,i)},getPageData:function(){var b=this.store.getTotalCount();return{total:b,activePage:Math.ceil((this.cursor+this.pageSize)/this.pageSize),pages:b=1&g<=j.pages){i.setValue(g)}}}}}},getParams:function(){return this.paramNames||this.store.paramNames},beforeLoad:function(){if(this.rendered&&this.refresh){this.refresh.disable()}},doLoad:function(d){var c={},b=this.getParams();c[b.start]=d;c[b.limit]=this.pageSize;if(this.fireEvent("beforechange",this,c)!==false){this.store.load({params:c})}},moveFirst:function(){this.doLoad(0)},movePrevious:function(){this.doLoad(Math.max(0,this.cursor-this.pageSize))},moveNext:function(){this.doLoad(this.cursor+this.pageSize)},moveLast:function(){var c=this.store.getTotalCount(),b=c%this.pageSize;this.doLoad(b?(c-b):c-this.pageSize)},doRefresh:function(){this.doLoad(this.cursor)},bindStore:function(c,d){var b;if(!d&&this.store){if(c!==this.store&&this.store.autoDestroy){this.store.destroy()}else{this.store.un("beforeload",this.beforeLoad,this);this.store.un("load",this.onLoad,this);this.store.un("exception",this.onLoadError,this)}if(!c){this.store=null}}if(c){c=Ext.StoreMgr.lookup(c);c.on({scope:this,beforeload:this.beforeLoad,load:this.onLoad,exception:this.onLoadError});b=true}this.store=c;if(b){this.onLoad(c,null,{})}},unbind:function(b){this.bindStore(null)},bind:function(b){this.bindStore(b)},onDestroy:function(){this.bindStore(null);Ext.PagingToolbar.superclass.onDestroy.call(this)}})})();Ext.reg("paging",Ext.PagingToolbar);Ext.History=(function(){var e,c;var k=false;var d;function g(){var l=location.href,m=l.indexOf("#"),n=m>=0?l.substr(m+1):null;if(Ext.isGecko){n=decodeURIComponent(n)}return n}function a(){c.value=d}function h(l){d=l;Ext.History.fireEvent("change",l)}function i(m){var l=['
',Ext.util.Format.htmlEncode(m),"
"].join("");try{var o=e.contentWindow.document;o.open();o.write(l);o.close();return true}catch(n){return false}}function b(){if(!e.contentWindow||!e.contentWindow.document){setTimeout(b,10);return}var o=e.contentWindow.document;var m=o.getElementById("state");var l=m?m.innerText:null;var n=g();setInterval(function(){o=e.contentWindow.document;m=o.getElementById("state");var q=m?m.innerText:null;var p=g();if(q!==l){l=q;h(l);location.hash=l;n=l;a()}else{if(p!==n){n=p;i(p)}}},50);k=true;Ext.History.fireEvent("ready",Ext.History)}function j(){d=c.value?c.value:g();if(Ext.isIE){b()}else{var l=g();setInterval(function(){var m=g();if(m!==l){l=m;h(l);a()}},50);k=true;Ext.History.fireEvent("ready",Ext.History)}}return{fieldId:"x-history-field",iframeId:"x-history-frame",events:{},init:function(m,l){if(k){Ext.callback(m,l,[this]);return}if(!Ext.isReady){Ext.onReady(function(){Ext.History.init(m,l)});return}c=Ext.getDom(Ext.History.fieldId);if(Ext.isIE){e=Ext.getDom(Ext.History.iframeId)}this.addEvents("ready","change");if(m){this.on("ready",m,l,{single:true})}j()},add:function(l,m){if(m!==false){if(this.getToken()==l){return true}}if(Ext.isIE){return i(l)}else{location.hash=l;return true}},back:function(){history.go(-1)},forward:function(){history.go(1)},getToken:function(){return k?d:g()}}})();Ext.apply(Ext.History,new Ext.util.Observable());Ext.Tip=Ext.extend(Ext.Panel,{minWidth:40,maxWidth:300,shadow:"sides",defaultAlign:"tl-bl?",autoRender:true,quickShowInterval:250,frame:true,hidden:true,baseCls:"x-tip",floating:{shadow:true,shim:true,useDisplay:true,constrain:false},autoHeight:true,closeAction:"hide",initComponent:function(){Ext.Tip.superclass.initComponent.call(this);if(this.closable&&!this.title){this.elements+=",header"}},afterRender:function(){Ext.Tip.superclass.afterRender.call(this);if(this.closable){this.addTool({id:"close",handler:this[this.closeAction],scope:this})}},showAt:function(a){Ext.Tip.superclass.show.call(this);if(this.measureWidth!==false&&(!this.initialConfig||typeof this.initialConfig.width!="number")){this.doAutoWidth()}if(this.constrainPosition){a=this.el.adjustForConstraints(a)}this.setPagePosition(a[0],a[1])},doAutoWidth:function(a){a=a||0;var b=this.body.getTextWidth();if(this.title){b=Math.max(b,this.header.child("span").getTextWidth(this.title))}b+=this.getFrameWidth()+(this.closable?20:0)+this.body.getPadding("lr")+a;this.setWidth(b.constrain(this.minWidth,this.maxWidth));if(Ext.isIE7&&!this.repainted){this.el.repaint();this.repainted=true}},showBy:function(a,b){if(!this.rendered){this.render(Ext.getBody())}this.showAt(this.el.getAlignToXY(a,b||this.defaultAlign))},initDraggable:function(){this.dd=new Ext.Tip.DD(this,typeof this.draggable=="boolean"?null:this.draggable);this.header.addClass("x-tip-draggable")}});Ext.reg("tip",Ext.Tip);Ext.Tip.DD=function(b,a){Ext.apply(this,a);this.tip=b;Ext.Tip.DD.superclass.constructor.call(this,b.el.id,"WindowDD-"+b.id);this.setHandleElId(b.header.id);this.scroll=false};Ext.extend(Ext.Tip.DD,Ext.dd.DD,{moveOnly:true,scroll:false,headerOffsets:[100,25],startDrag:function(){this.tip.el.disableShadow()},endDrag:function(a){this.tip.el.enableShadow(true)}});Ext.ToolTip=Ext.extend(Ext.Tip,{showDelay:500,hideDelay:200,dismissDelay:5000,trackMouse:false,anchorToTarget:true,anchorOffset:0,targetCounter:0,constrainPosition:false,initComponent:function(){Ext.ToolTip.superclass.initComponent.call(this);this.lastActive=new Date();this.initTarget(this.target);this.origAnchor=this.anchor},onRender:function(b,a){Ext.ToolTip.superclass.onRender.call(this,b,a);this.anchorCls="x-tip-anchor-"+this.getAnchorPosition();this.anchorEl=this.el.createChild({cls:"x-tip-anchor "+this.anchorCls})},afterRender:function(){Ext.ToolTip.superclass.afterRender.call(this);this.anchorEl.setStyle("z-index",this.el.getZIndex()+1).setVisibilityMode(Ext.Element.DISPLAY)},initTarget:function(c){var a;if((a=Ext.get(c))){if(this.target){var b=Ext.get(this.target);this.mun(b,"mouseover",this.onTargetOver,this);this.mun(b,"mouseout",this.onTargetOut,this);this.mun(b,"mousemove",this.onMouseMove,this)}this.mon(a,{mouseover:this.onTargetOver,mouseout:this.onTargetOut,mousemove:this.onMouseMove,scope:this});this.target=a}if(this.anchor){this.anchorTarget=this.target}},onMouseMove:function(b){var a=this.delegate?b.getTarget(this.delegate):this.triggerElement=true;if(a){this.targetXY=b.getXY();if(a===this.triggerElement){if(!this.hidden&&this.trackMouse){this.setPagePosition(this.getTargetXY())}}else{this.hide();this.lastActive=new Date(0);this.onTargetOver(b)}}else{if(!this.closable&&this.isVisible()){this.hide()}}},getTargetXY:function(){if(this.delegate){this.anchorTarget=this.triggerElement}if(this.anchor){this.targetCounter++;var c=this.getOffsets(),l=(this.anchorToTarget&&!this.trackMouse)?this.el.getAlignToXY(this.anchorTarget,this.getAnchorAlign()):this.targetXY,a=Ext.lib.Dom.getViewWidth()-5,h=Ext.lib.Dom.getViewHeight()-5,i=document.documentElement,e=document.body,k=(i.scrollLeft||e.scrollLeft||0)+5,j=(i.scrollTop||e.scrollTop||0)+5,b=[l[0]+c[0],l[1]+c[1]],g=this.getSize();this.anchorEl.removeClass(this.anchorCls);if(this.targetCounter<2){if(b[0]a){if(this.anchorToTarget){this.defaultAlign="r-l";if(this.mouseOffset){this.mouseOffset[0]*=-1}}this.anchor="right";return this.getTargetXY()}if(b[1]h){if(this.anchorToTarget){this.defaultAlign="b-t";if(this.mouseOffset){this.mouseOffset[1]*=-1}}this.anchor="bottom";return this.getTargetXY()}}this.anchorCls="x-tip-anchor-"+this.getAnchorPosition();this.anchorEl.addClass(this.anchorCls);this.targetCounter=0;return b}else{var d=this.getMouseOffset();return[this.targetXY[0]+d[0],this.targetXY[1]+d[1]]}},getMouseOffset:function(){var a=this.anchor?[0,0]:[15,18];if(this.mouseOffset){a[0]+=this.mouseOffset[0];a[1]+=this.mouseOffset[1]}return a},getAnchorPosition:function(){if(this.anchor){this.tipAnchor=this.anchor.charAt(0)}else{var a=this.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);if(!a){throw"AnchorTip.defaultAlign is invalid"}this.tipAnchor=a[1].charAt(0)}switch(this.tipAnchor){case"t":return"top";case"b":return"bottom";case"r":return"right"}return"left"},getAnchorAlign:function(){switch(this.anchor){case"top":return"tl-bl";case"left":return"tl-tr";case"right":return"tr-tl";default:return"bl-tl"}},getOffsets:function(){var b,a=this.getAnchorPosition().charAt(0);if(this.anchorToTarget&&!this.trackMouse){switch(a){case"t":b=[0,9];break;case"b":b=[0,-13];break;case"r":b=[-13,0];break;default:b=[9,0];break}}else{switch(a){case"t":b=[-15-this.anchorOffset,30];break;case"b":b=[-19-this.anchorOffset,-13-this.el.dom.offsetHeight];break;case"r":b=[-15-this.el.dom.offsetWidth,-13-this.anchorOffset];break;default:b=[25,-13-this.anchorOffset];break}}var c=this.getMouseOffset();b[0]+=c[0];b[1]+=c[1];return b},onTargetOver:function(b){if(this.disabled||b.within(this.target.dom,true)){return}var a=b.getTarget(this.delegate);if(a){this.triggerElement=a;this.clearTimer("hide");this.targetXY=b.getXY();this.delayShow()}},delayShow:function(){if(this.hidden&&!this.showTimer){if(this.lastActive.getElapsed()=c){d=c-b-5}}return{x:a,y:d}},beforeDestroy:function(){this.clearTimers();Ext.destroy(this.anchorEl);delete this.anchorEl;delete this.target;delete this.anchorTarget;delete this.triggerElement;Ext.ToolTip.superclass.beforeDestroy.call(this)},onDestroy:function(){Ext.getDoc().un("mousedown",this.onDocMouseDown,this);Ext.ToolTip.superclass.onDestroy.call(this)}});Ext.reg("tooltip",Ext.ToolTip);Ext.QuickTip=Ext.extend(Ext.ToolTip,{interceptTitles:false,tagConfig:{namespace:"ext",attribute:"qtip",width:"qwidth",target:"target",title:"qtitle",hide:"hide",cls:"qclass",align:"qalign",anchor:"anchor"},initComponent:function(){this.target=this.target||Ext.getDoc();this.targets=this.targets||{};Ext.QuickTip.superclass.initComponent.call(this)},register:function(e){var h=Ext.isArray(e)?e:arguments;for(var g=0,a=h.length;g1){var d=function(i,h){if(i&&h){var j=h.findChild(a,b);if(j){j.select();if(g){g(true,j)}}else{if(g){g(false,j)}}}else{if(g){g(false,j)}}};this.expandPath(c.join(this.pathSeparator),a,d)}else{this.root.select();if(g){g(true,this.root)}}},getTreeEl:function(){return this.body},onRender:function(b,a){Ext.tree.TreePanel.superclass.onRender.call(this,b,a);this.el.addClass("x-tree");this.innerCt=this.body.createChild({tag:"ul",cls:"x-tree-root-ct "+(this.useArrows?"x-tree-arrows":this.lines?"x-tree-lines":"x-tree-no-lines")})},initEvents:function(){Ext.tree.TreePanel.superclass.initEvents.call(this);if(this.containerScroll){Ext.dd.ScrollManager.register(this.body)}if((this.enableDD||this.enableDrop)&&!this.dropZone){this.dropZone=new Ext.tree.TreeDropZone(this,this.dropConfig||{ddGroup:this.ddGroup||"TreeDD",appendOnly:this.ddAppendOnly===true})}if((this.enableDD||this.enableDrag)&&!this.dragZone){this.dragZone=new Ext.tree.TreeDragZone(this,this.dragConfig||{ddGroup:this.ddGroup||"TreeDD",scroll:this.ddScroll})}this.getSelectionModel().init(this)},afterRender:function(){Ext.tree.TreePanel.superclass.afterRender.call(this);this.renderRoot()},beforeDestroy:function(){if(this.rendered){Ext.dd.ScrollManager.unregister(this.body);Ext.destroy(this.dropZone,this.dragZone)}this.destroyRoot();Ext.destroy(this.loader);this.nodeHash=this.root=this.loader=null;Ext.tree.TreePanel.superclass.beforeDestroy.call(this)},destroyRoot:function(){if(this.root&&this.root.destroy){this.root.destroy(true)}}});Ext.tree.TreePanel.nodeTypes={};Ext.reg("treepanel",Ext.tree.TreePanel);Ext.tree.TreeEventModel=function(a){this.tree=a;this.tree.on("render",this.initEvents,this)};Ext.tree.TreeEventModel.prototype={initEvents:function(){var a=this.tree;if(a.trackMouseOver!==false){a.mon(a.innerCt,{scope:this,mouseover:this.delegateOver,mouseout:this.delegateOut})}a.mon(a.getTreeEl(),{scope:this,click:this.delegateClick,dblclick:this.delegateDblClick,contextmenu:this.delegateContextMenu})},getNode:function(b){var a;if(a=b.getTarget(".x-tree-node-el",10)){var c=Ext.fly(a,"_treeEvents").getAttribute("tree-node-id","ext");if(c){return this.tree.getNodeById(c)}}return null},getNodeTarget:function(b){var a=b.getTarget(".x-tree-node-icon",1);if(!a){a=b.getTarget(".x-tree-node-el",6)}return a},delegateOut:function(b,a){if(!this.beforeEvent(b)){return}if(b.getTarget(".x-tree-ec-icon",1)){var c=this.getNode(b);this.onIconOut(b,c);if(c==this.lastEcOver){delete this.lastEcOver}}if((a=this.getNodeTarget(b))&&!b.within(a,true)){this.onNodeOut(b,this.getNode(b))}},delegateOver:function(b,a){if(!this.beforeEvent(b)){return}if(Ext.isGecko&&!this.trackingDoc){Ext.getBody().on("mouseover",this.trackExit,this);this.trackingDoc=true}if(this.lastEcOver){this.onIconOut(b,this.lastEcOver);delete this.lastEcOver}if(b.getTarget(".x-tree-ec-icon",1)){this.lastEcOver=this.getNode(b);this.onIconOver(b,this.lastEcOver)}if(a=this.getNodeTarget(b)){this.onNodeOver(b,this.getNode(b))}},trackExit:function(a){if(this.lastOverNode){if(this.lastOverNode.ui&&!a.within(this.lastOverNode.ui.getEl())){this.onNodeOut(a,this.lastOverNode)}delete this.lastOverNode;Ext.getBody().un("mouseover",this.trackExit,this);this.trackingDoc=false}},delegateClick:function(b,a){if(this.beforeEvent(b)){if(b.getTarget("input[type=checkbox]",1)){this.onCheckboxClick(b,this.getNode(b))}else{if(b.getTarget(".x-tree-ec-icon",1)){this.onIconClick(b,this.getNode(b))}else{if(this.getNodeTarget(b)){this.onNodeClick(b,this.getNode(b))}}}}else{this.checkContainerEvent(b,"click")}},delegateDblClick:function(b,a){if(this.beforeEvent(b)){if(this.getNodeTarget(b)){this.onNodeDblClick(b,this.getNode(b))}}else{this.checkContainerEvent(b,"dblclick")}},delegateContextMenu:function(b,a){if(this.beforeEvent(b)){if(this.getNodeTarget(b)){this.onNodeContextMenu(b,this.getNode(b))}}else{this.checkContainerEvent(b,"contextmenu")}},checkContainerEvent:function(b,a){if(this.disabled){b.stopEvent();return false}this.onContainerEvent(b,a)},onContainerEvent:function(b,a){this.tree.fireEvent("container"+a,this.tree,b)},onNodeClick:function(b,a){a.ui.onClick(b)},onNodeOver:function(b,a){this.lastOverNode=a;a.ui.onOver(b)},onNodeOut:function(b,a){a.ui.onOut(b)},onIconOver:function(b,a){a.ui.addClass("x-tree-ec-over")},onIconOut:function(b,a){a.ui.removeClass("x-tree-ec-over")},onIconClick:function(b,a){a.ui.ecClick(b)},onCheckboxClick:function(b,a){a.ui.onCheckChange(b)},onNodeDblClick:function(b,a){a.ui.onDblClick(b)},onNodeContextMenu:function(b,a){a.ui.onContextMenu(b)},beforeEvent:function(b){var a=this.getNode(b);if(this.disabled||!a||!a.ui){b.stopEvent();return false}return true},disable:function(){this.disabled=true},enable:function(){this.disabled=false}};Ext.tree.DefaultSelectionModel=Ext.extend(Ext.util.Observable,{constructor:function(a){this.selNode=null;this.addEvents("selectionchange","beforeselect");Ext.apply(this,a);Ext.tree.DefaultSelectionModel.superclass.constructor.call(this)},init:function(a){this.tree=a;a.mon(a.getTreeEl(),"keydown",this.onKeyDown,this);a.on("click",this.onNodeClick,this)},onNodeClick:function(a,b){this.select(a)},select:function(c,a){if(!Ext.fly(c.ui.wrap).isVisible()&&a){return a.call(this,c)}var b=this.selNode;if(c==b){c.ui.onSelectedChange(true)}else{if(this.fireEvent("beforeselect",this,c,b)!==false){if(b&&b.ui){b.ui.onSelectedChange(false)}this.selNode=c;c.ui.onSelectedChange(true);this.fireEvent("selectionchange",this,c,b)}}return c},unselect:function(b,a){if(this.selNode==b){this.clearSelections(a)}},clearSelections:function(a){var b=this.selNode;if(b){b.ui.onSelectedChange(false);this.selNode=null;if(a!==true){this.fireEvent("selectionchange",this,null)}}return b},getSelectedNode:function(){return this.selNode},isSelected:function(a){return this.selNode==a},selectPrevious:function(a){if(!(a=a||this.selNode||this.lastSelNode)){return null}var c=a.previousSibling;if(c){if(!c.isExpanded()||c.childNodes.length<1){return this.select(c,this.selectPrevious)}else{var b=c.lastChild;while(b&&b.isExpanded()&&Ext.fly(b.ui.wrap).isVisible()&&b.childNodes.length>0){b=b.lastChild}return this.select(b,this.selectPrevious)}}else{if(a.parentNode&&(this.tree.rootVisible||!a.parentNode.isRoot)){return this.select(a.parentNode,this.selectPrevious)}}return null},selectNext:function(b){if(!(b=b||this.selNode||this.lastSelNode)){return null}if(b.firstChild&&b.isExpanded()&&Ext.fly(b.ui.wrap).isVisible()){return this.select(b.firstChild,this.selectNext)}else{if(b.nextSibling){return this.select(b.nextSibling,this.selectNext)}else{if(b.parentNode){var a=null;b.parentNode.bubble(function(){if(this.nextSibling){a=this.getOwnerTree().selModel.select(this.nextSibling,this.selectNext);return false}});return a}}}return null},onKeyDown:function(c){var b=this.selNode||this.lastSelNode;var d=this;if(!b){return}var a=c.getKey();switch(a){case c.DOWN:c.stopEvent();this.selectNext();break;case c.UP:c.stopEvent();this.selectPrevious();break;case c.RIGHT:c.preventDefault();if(b.hasChildNodes()){if(!b.isExpanded()){b.expand()}else{if(b.firstChild){this.select(b.firstChild,c)}}}break;case c.LEFT:c.preventDefault();if(b.hasChildNodes()&&b.isExpanded()){b.collapse()}else{if(b.parentNode&&(this.tree.rootVisible||b.parentNode!=this.tree.getRootNode())){this.select(b.parentNode,c)}}break}}});Ext.tree.MultiSelectionModel=Ext.extend(Ext.util.Observable,{constructor:function(a){this.selNodes=[];this.selMap={};this.addEvents("selectionchange");Ext.apply(this,a);Ext.tree.MultiSelectionModel.superclass.constructor.call(this)},init:function(a){this.tree=a;a.mon(a.getTreeEl(),"keydown",this.onKeyDown,this);a.on("click",this.onNodeClick,this)},onNodeClick:function(a,b){if(b.ctrlKey&&this.isSelected(a)){this.unselect(a)}else{this.select(a,b,b.ctrlKey)}},select:function(a,c,b){if(b!==true){this.clearSelections(true)}if(this.isSelected(a)){this.lastSelNode=a;return a}this.selNodes.push(a);this.selMap[a.id]=a;this.lastSelNode=a;a.ui.onSelectedChange(true);this.fireEvent("selectionchange",this,this.selNodes);return a},unselect:function(b){if(this.selMap[b.id]){b.ui.onSelectedChange(false);var c=this.selNodes;var a=c.indexOf(b);if(a!=-1){this.selNodes.splice(a,1)}delete this.selMap[b.id];this.fireEvent("selectionchange",this,this.selNodes)}},clearSelections:function(b){var d=this.selNodes;if(d.length>0){for(var c=0,a=d.length;c0},isExpandable:function(){return this.attributes.expandable||this.hasChildNodes()},appendChild:function(e){var g=false;if(Ext.isArray(e)){g=e}else{if(arguments.length>1){g=arguments}}if(g){for(var d=0,a=g.length;d0){var g=d?function(){e.apply(d,arguments)}:e;c.sort(g);for(var b=0;b
','',this.indentMarkup,"",'','',g?('':"/>")):"",'',e.text,"
",'',""].join("");if(l!==true&&e.nextSibling&&(b=e.nextSibling.ui.getEl())){this.wrap=Ext.DomHelper.insertHtml("beforeBegin",b,d)}else{this.wrap=Ext.DomHelper.insertHtml("beforeEnd",j,d)}this.elNode=this.wrap.childNodes[0];this.ctNode=this.wrap.childNodes[1];var i=this.elNode.childNodes;this.indentNode=i[0];this.ecNode=i[1];this.iconNode=i[2];var h=3;if(g){this.checkbox=i[3];this.checkbox.defaultChecked=this.checkbox.checked;h++}this.anchor=i[h];this.textNode=i[h].firstChild},getHref:function(a){return Ext.isEmpty(a)?(Ext.isGecko?"":"#"):a},getAnchor:function(){return this.anchor},getTextEl:function(){return this.textNode},getIconEl:function(){return this.iconNode},isChecked:function(){return this.checkbox?this.checkbox.checked:false},updateExpandIcon:function(){if(this.rendered){var g=this.node,d,c,a=g.isLast()?"x-tree-elbow-end":"x-tree-elbow",e=g.hasChildNodes();if(e||g.attributes.expandable){if(g.expanded){a+="-minus";d="x-tree-node-collapsed";c="x-tree-node-expanded"}else{a+="-plus";d="x-tree-node-expanded";c="x-tree-node-collapsed"}if(this.wasLeaf){this.removeClass("x-tree-node-leaf");this.wasLeaf=false}if(this.c1!=d||this.c2!=c){Ext.fly(this.elNode).replaceClass(d,c);this.c1=d;this.c2=c}}else{if(!this.wasLeaf){Ext.fly(this.elNode).replaceClass("x-tree-node-expanded","x-tree-node-collapsed");delete this.c1;delete this.c2;this.wasLeaf=true}}var b="x-tree-ec-icon "+a;if(this.ecc!=b){this.ecNode.className=b;this.ecc=b}}},onIdChange:function(a){if(this.rendered){this.elNode.setAttribute("ext:tree-node-id",a)}},getChildIndent:function(){if(!this.childIndent){var a=[],b=this.node;while(b){if(!b.isRoot||(b.isRoot&&b.ownerTree.rootVisible)){if(!b.isLast()){a.unshift('')}else{a.unshift('')}}b=b.parentNode}this.childIndent=a.join("")}return this.childIndent},renderIndent:function(){if(this.rendered){var a="",b=this.node.parentNode;if(b){a=b.ui.getChildIndent()}if(this.indentMarkup!=a){this.indentNode.innerHTML=a;this.indentMarkup=a}this.updateExpandIcon()}},destroy:function(){if(this.elNode){Ext.dd.Registry.unregister(this.elNode.id)}Ext.each(["textnode","anchor","checkbox","indentNode","ecNode","iconNode","elNode","ctNode","wrap","holder"],function(a){if(this[a]){Ext.fly(this[a]).remove();delete this[a]}},this);delete this.node}});Ext.tree.RootTreeNodeUI=Ext.extend(Ext.tree.TreeNodeUI,{render:function(){if(!this.rendered){var a=this.node.ownerTree.innerCt.dom;this.node.expanded=true;a.innerHTML='
';this.wrap=this.ctNode=a.firstChild}},collapse:Ext.emptyFn,expand:Ext.emptyFn});Ext.tree.TreeLoader=function(a){this.baseParams={};Ext.apply(this,a);this.addEvents("beforeload","load","loadexception");Ext.tree.TreeLoader.superclass.constructor.call(this);if(Ext.isString(this.paramOrder)){this.paramOrder=this.paramOrder.split(/[\s,|]/)}};Ext.extend(Ext.tree.TreeLoader,Ext.util.Observable,{uiProviders:{},clearOnLoad:true,paramOrder:undefined,paramsAsHash:false,nodeParameter:"node",directFn:undefined,load:function(b,c,a){if(this.clearOnLoad){while(b.firstChild){b.removeChild(b.firstChild)}}if(this.doPreload(b)){this.runCallback(c,a||b,[b])}else{if(this.directFn||this.dataUrl||this.url){this.requestData(b,c,a||b)}}},doPreload:function(d){if(d.attributes.children){if(d.childNodes.length<1){var c=d.attributes.children;d.beginUpdate();for(var b=0,a=c.length;b-1){c=[]}for(var d=0,a=b.length;dp){return e?-1:1}}return 0}},doSort:function(a){a.sort(this.sortFn)},updateSort:function(a,b){if(b.childrenRendered){this.doSort.defer(1,this,[b])}},updateSortParent:function(a){var b=a.parentNode;if(b&&b.childrenRendered){this.doSort.defer(1,this,[b])}}});if(Ext.dd.DropZone){Ext.tree.TreeDropZone=function(a,b){this.allowParentInsert=b.allowParentInsert||false;this.allowContainerDrop=b.allowContainerDrop||false;this.appendOnly=b.appendOnly||false;Ext.tree.TreeDropZone.superclass.constructor.call(this,a.getTreeEl(),b);this.tree=a;this.dragOverData={};this.lastInsertClass="x-tree-no-status"};Ext.extend(Ext.tree.TreeDropZone,Ext.dd.DropZone,{ddGroup:"TreeDD",expandDelay:1000,expandNode:function(a){if(a.hasChildNodes()&&!a.isExpanded()){a.expand(false,null,this.triggerCacheRefresh.createDelegate(this))}},queueExpand:function(a){this.expandProcId=this.expandNode.defer(this.expandDelay,this,[a])},cancelExpand:function(){if(this.expandProcId){clearTimeout(this.expandProcId);this.expandProcId=false}},isValidDropPoint:function(a,k,i,d,c){if(!a||!c){return false}var g=a.node;var h=c.node;if(!(g&&g.isTarget&&k)){return false}if(k=="append"&&g.allowChildren===false){return false}if((k=="above"||k=="below")&&(g.parentNode&&g.parentNode.allowChildren===false)){return false}if(h&&(g==h||h.contains(g))){return false}var b=this.dragOverData;b.tree=this.tree;b.target=g;b.data=c;b.point=k;b.source=i;b.rawEvent=d;b.dropNode=h;b.cancel=false;var j=this.tree.fireEvent("nodedragover",b);return b.cancel===false&&j!==false},getDropPoint:function(h,g,l){var m=g.node;if(m.isRoot){return m.allowChildren!==false?"append":false}var c=g.ddel;var o=Ext.lib.Dom.getY(c),j=o+c.offsetHeight;var i=Ext.lib.Event.getPageY(h);var k=m.allowChildren===false||m.isLeaf();if(this.appendOnly||m.parentNode.allowChildren===false){return k?false:"append"}var d=false;if(!this.allowParentInsert){d=m.hasChildNodes()&&m.isExpanded()}var a=(j-o)/(k?2:3);if(i>=o&&i<(o+a)){return"above"}else{if(!d&&(k||i>=j-a&&i<=j)){return"below"}else{return"append"}}},onNodeEnter:function(d,a,c,b){this.cancelExpand()},onContainerOver:function(a,c,b){if(this.allowContainerDrop&&this.isValidDropPoint({ddel:this.tree.getRootNode().ui.elNode,node:this.tree.getRootNode()},"append",a,c,b)){return this.dropAllowed}return this.dropNotAllowed},onNodeOver:function(b,i,h,g){var k=this.getDropPoint(h,b,i);var c=b.node;if(!this.expandProcId&&k=="append"&&c.hasChildNodes()&&!b.node.isExpanded()){this.queueExpand(c)}else{if(k!="append"){this.cancelExpand()}}var d=this.dropNotAllowed;if(this.isValidDropPoint(b,k,i,h,g)){if(k){var a=b.ddel;var j;if(k=="above"){d=b.node.isFirst()?"x-tree-drop-ok-above":"x-tree-drop-ok-between";j="x-tree-drag-insert-above"}else{if(k=="below"){d=b.node.isLast()?"x-tree-drop-ok-below":"x-tree-drop-ok-between";j="x-tree-drag-insert-below"}else{d="x-tree-drop-ok-append";j="x-tree-drag-append"}}if(this.lastInsertClass!=j){Ext.fly(a).replaceClass(this.lastInsertClass,j);this.lastInsertClass=j}}}return d},onNodeOut:function(d,a,c,b){this.cancelExpand();this.removeDropIndicators(d)},onNodeDrop:function(i,b,h,d){var a=this.getDropPoint(h,i,b);var g=i.node;g.ui.startDrop();if(!this.isValidDropPoint(i,a,b,h,d)){g.ui.endDrop();return false}var c=d.node||(b.getTreeNode?b.getTreeNode(d,g,a,h):null);return this.processDrop(g,d,a,b,h,c)},onContainerDrop:function(a,g,c){if(this.allowContainerDrop&&this.isValidDropPoint({ddel:this.tree.getRootNode().ui.elNode,node:this.tree.getRootNode()},"append",a,g,c)){var d=this.tree.getRootNode();d.ui.startDrop();var b=c.node||(a.getTreeNode?a.getTreeNode(c,d,"append",g):null);return this.processDrop(d,c,"append",a,g,b)}return false},processDrop:function(j,h,b,a,i,d){var g={tree:this.tree,target:j,data:h,point:b,source:a,rawEvent:i,dropNode:d,cancel:!d,dropStatus:false};var c=this.tree.fireEvent("beforenodedrop",g);if(c===false||g.cancel===true||!g.dropNode){j.ui.endDrop();return g.dropStatus}j=g.target;if(b=="append"&&!j.isExpanded()){j.expand(false,null,function(){this.completeDrop(g)}.createDelegate(this))}else{this.completeDrop(g)}return true},completeDrop:function(h){var d=h.dropNode,e=h.point,c=h.target;if(!Ext.isArray(d)){d=[d]}var g;for(var b=0,a=d.length;bd.offsetLeft){e.scrollLeft=d.offsetLeft}var a=Math.min(this.maxWidth,(e.clientWidth>20?e.clientWidth:e.offsetWidth)-Math.max(0,d.offsetLeft-e.scrollLeft)-5);this.setSize(a,"")},triggerEdit:function(a,c){this.completeEdit();if(a.attributes.editable!==false){this.editNode=a;if(this.tree.autoScroll){Ext.fly(a.ui.getEl()).scrollIntoView(this.tree.body)}var b=a.text||"";if(!Ext.isGecko&&Ext.isEmpty(a.text)){a.setText(" ")}this.autoEditTimer=this.startEdit.defer(this.editDelay,this,[a.ui.textNode,b]);return false}},bindScroll:function(){this.tree.getTreeEl().on("scroll",this.cancelEdit,this)},beforeNodeClick:function(a,b){clearTimeout(this.autoEditTimer);if(this.tree.getSelectionModel().isSelected(a)){b.stopEvent();return this.triggerEdit(a)}},onNodeDblClick:function(a,b){clearTimeout(this.autoEditTimer)},updateNode:function(a,b){this.tree.getTreeEl().un("scroll",this.cancelEdit,this);this.editNode.setText(b)},onHide:function(){Ext.tree.TreeEditor.superclass.onHide.call(this);if(this.editNode){this.editNode.ui.focus.defer(50,this.editNode.ui)}},onSpecialKey:function(c,b){var a=b.getKey();if(a==b.ESC){b.stopEvent();this.cancelEdit()}else{if(a==b.ENTER&&!b.hasModifier()){b.stopEvent();this.completeEdit()}}},onDestroy:function(){clearTimeout(this.autoEditTimer);Ext.tree.TreeEditor.superclass.onDestroy.call(this);var a=this.tree;a.un("beforeclick",this.beforeNodeClick,this);a.un("dblclick",this.onNodeDblClick,this)}}); /* SWFObject v2.2 is released under the MIT License */ var swfobject=function(){var E="undefined",s="object",T="Shockwave Flash",X="ShockwaveFlash.ShockwaveFlash",r="application/x-shockwave-flash",S="SWFObjectExprInst",y="onreadystatechange",P=window,k=document,u=navigator,U=false,V=[i],p=[],O=[],J=[],m,R,F,C,K=false,a=false,o,H,n=true,N=function(){var ab=typeof k.getElementById!=E&&typeof k.getElementsByTagName!=E&&typeof k.createElement!=E,ai=u.userAgent.toLowerCase(),Z=u.platform.toLowerCase(),af=Z?(/win/).test(Z):/win/.test(ai),ad=Z?(/mac/).test(Z):/mac/.test(ai),ag=/webkit/.test(ai)?parseFloat(ai.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,Y=!+"\v1",ah=[0,0,0],ac=null;if(typeof u.plugins!=E&&typeof u.plugins[T]==s){ac=u.plugins[T].description;if(ac&&!(typeof u.mimeTypes!=E&&u.mimeTypes[r]&&!u.mimeTypes[r].enabledPlugin)){U=true;Y=false;ac=ac.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ah[0]=parseInt(ac.replace(/^(.*)\..*$/,"$1"),10);ah[1]=parseInt(ac.replace(/^.*\.(.*)\s.*$/,"$1"),10);ah[2]=/[a-zA-Z]/.test(ac)?parseInt(ac.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof P.ActiveXObject!=E){try{var ae=new ActiveXObject(X);if(ae){ac=ae.GetVariable("$version");if(ac){Y=true;ac=ac.split(" ")[1].split(",");ah=[parseInt(ac[0],10),parseInt(ac[1],10),parseInt(ac[2],10)]}}}catch(aa){}}}return{w3:ab,pv:ah,wk:ag,ie:Y,win:af,mac:ad}}(),l=function(){if(!N.w3){return}if((typeof k.readyState!=E&&k.readyState=="complete")||(typeof k.readyState==E&&(k.getElementsByTagName("body")[0]||k.body))){g()}if(!K){if(typeof k.addEventListener!=E){k.addEventListener("DOMContentLoaded",g,false)}if(N.ie&&N.win){k.attachEvent(y,function(){if(k.readyState=="complete"){k.detachEvent(y,arguments.callee);g()}});if(P==top){(function(){if(K){return}try{k.documentElement.doScroll("left")}catch(Y){setTimeout(arguments.callee,0);return}g()})()}}if(N.wk){(function(){if(K){return}if(!(/loaded|complete/).test(k.readyState)){setTimeout(arguments.callee,0);return}g()})()}t(g)}}();function g(){if(K){return}try{var aa=k.getElementsByTagName("body")[0].appendChild(D("span"));aa.parentNode.removeChild(aa)}catch(ab){return}K=true;var Y=V.length;for(var Z=0;Z0){for(var ag=0;ag0){var af=c(Z);if(af){if(G(p[ag].swfVersion)&&!(N.wk&&N.wk<312)){x(Z,true);if(ac){ab.success=true;ab.ref=A(Z);ac(ab)}}else{if(p[ag].expressInstall&&B()){var aj={};aj.data=p[ag].expressInstall;aj.width=af.getAttribute("width")||"0";aj.height=af.getAttribute("height")||"0";if(af.getAttribute("class")){aj.styleclass=af.getAttribute("class")}if(af.getAttribute("align")){aj.align=af.getAttribute("align")}var ai={};var Y=af.getElementsByTagName("param");var ad=Y.length;for(var ae=0;ae'}}ab.outerHTML='"+ag+"";O[O.length]=aj.id;Y=c(aj.id)}else{var aa=D(s);aa.setAttribute("type",r);for(var ad in aj){if(aj[ad]!=Object.prototype[ad]){if(ad.toLowerCase()=="styleclass"){aa.setAttribute("class",aj[ad])}else{if(ad.toLowerCase()!="classid"){aa.setAttribute(ad,aj[ad])}}}}for(var ac in ah){if(ah[ac]!=Object.prototype[ac]&&ac.toLowerCase()!="movie"){e(aa,ac,ah[ac])}}ab.parentNode.replaceChild(aa,ab);Y=aa}}return Y}function e(aa,Y,Z){var ab=D("param");ab.setAttribute("name",Y);ab.setAttribute("value",Z);aa.appendChild(ab)}function z(Z){var Y=c(Z);if(Y&&Y.nodeName=="OBJECT"){if(N.ie&&N.win){Y.style.display="none";(function(){if(Y.readyState==4){b(Z)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.removeChild(Y)}}}function b(aa){var Z=c(aa);if(Z){for(var Y in Z){if(typeof Z[Y]=="function"){Z[Y]=null}}Z.parentNode.removeChild(Z)}}function c(aa){var Y=null;try{Y=k.getElementById(aa)}catch(Z){}return Y}function D(Y){return k.createElement(Y)}function j(aa,Y,Z){aa.attachEvent(Y,Z);J[J.length]=[aa,Y,Z]}function G(aa){var Z=N.pv,Y=aa.split(".");Y[0]=parseInt(Y[0],10);Y[1]=parseInt(Y[1],10)||0;Y[2]=parseInt(Y[2],10)||0;return(Z[0]>Y[0]||(Z[0]==Y[0]&&Z[1]>Y[1])||(Z[0]==Y[0]&&Z[1]==Y[1]&&Z[2]>=Y[2]))?true:false}function w(ad,Z,ae,ac){if(N.ie&&N.mac){return}var ab=k.getElementsByTagName("head")[0];if(!ab){return}var Y=(ae&&typeof ae=="string")?ae:"screen";if(ac){o=null;H=null}if(!o||H!=Y){var aa=D("style");aa.setAttribute("type","text/css");aa.setAttribute("media",Y);o=ab.appendChild(aa);if(N.ie&&N.win&&typeof k.styleSheets!=E&&k.styleSheets.length>0){o=k.styleSheets[k.styleSheets.length-1]}H=Y}if(N.ie&&N.win){if(o&&typeof o.addRule==s){o.addRule(ad,Z)}}else{if(o&&typeof k.createTextNode!=E){o.appendChild(k.createTextNode(ad+" {"+Z+"}"))}}}function x(aa,Y){if(!n){return}var Z=Y?"visible":"hidden";if(K&&c(aa)){c(aa).style.visibility=Z}else{w("#"+aa,"visibility:"+Z)}}function M(Z){var aa=/[\\\"<>\.;]/;var Y=aa.exec(Z)!=null;return Y&&typeof encodeURIComponent!=E?encodeURIComponent(Z):Z}var d=function(){if(N.ie&&N.win){window.attachEvent("onunload",function(){var ad=J.length;for(var ac=0;ac0){for(h=0;h-1&&e.position=="left"){e.position="bottom"}return e},onDestroy:function(){Ext.chart.CartesianChart.superclass.onDestroy.call(this);Ext.each(this.labelFn,function(a){this.removeFnProxy(a)},this)}});Ext.reg("cartesianchart",Ext.chart.CartesianChart);Ext.chart.LineChart=Ext.extend(Ext.chart.CartesianChart,{type:"line"});Ext.reg("linechart",Ext.chart.LineChart);Ext.chart.ColumnChart=Ext.extend(Ext.chart.CartesianChart,{type:"column"});Ext.reg("columnchart",Ext.chart.ColumnChart);Ext.chart.StackedColumnChart=Ext.extend(Ext.chart.CartesianChart,{type:"stackcolumn"});Ext.reg("stackedcolumnchart",Ext.chart.StackedColumnChart);Ext.chart.BarChart=Ext.extend(Ext.chart.CartesianChart,{type:"bar"});Ext.reg("barchart",Ext.chart.BarChart);Ext.chart.StackedBarChart=Ext.extend(Ext.chart.CartesianChart,{type:"stackbar"});Ext.reg("stackedbarchart",Ext.chart.StackedBarChart);Ext.chart.Axis=function(a){Ext.apply(this,a)};Ext.chart.Axis.prototype={type:null,orientation:"horizontal",reverse:false,labelFunction:null,hideOverlappingLabels:true,labelSpacing:2};Ext.chart.NumericAxis=Ext.extend(Ext.chart.Axis,{type:"numeric",minimum:NaN,maximum:NaN,majorUnit:NaN,minorUnit:NaN,snapToUnits:true,alwaysShowZero:true,scale:"linear",roundMajorUnit:true,calculateByLabelSize:true,position:"left",adjustMaximumByMajorUnit:true,adjustMinimumByMajorUnit:true});Ext.chart.TimeAxis=Ext.extend(Ext.chart.Axis,{type:"time",minimum:null,maximum:null,majorUnit:NaN,majorTimeUnit:null,minorUnit:NaN,minorTimeUnit:null,snapToUnits:true,stackingEnabled:false,calculateByLabelSize:true});Ext.chart.CategoryAxis=Ext.extend(Ext.chart.Axis,{type:"category",categoryNames:null,calculateCategoryCount:false});Ext.chart.Series=function(a){Ext.apply(this,a)};Ext.chart.Series.prototype={type:null,displayName:null};Ext.chart.CartesianSeries=Ext.extend(Ext.chart.Series,{xField:null,yField:null,showInLegend:true,axis:"primary"});Ext.chart.ColumnSeries=Ext.extend(Ext.chart.CartesianSeries,{type:"column"});Ext.chart.LineSeries=Ext.extend(Ext.chart.CartesianSeries,{type:"line"});Ext.chart.BarSeries=Ext.extend(Ext.chart.CartesianSeries,{type:"bar"});Ext.chart.PieSeries=Ext.extend(Ext.chart.Series,{type:"pie",dataField:null,categoryField:null});Ext.menu.Menu=Ext.extend(Ext.Container,{minWidth:120,shadow:"sides",subMenuAlign:"tl-tr?",defaultAlign:"tl-bl?",allowOtherMenus:false,ignoreParentClicks:false,enableScrolling:true,maxHeight:null,scrollIncrement:24,showSeparator:true,defaultOffsets:[0,0],plain:false,floating:true,zIndex:15000,hidden:true,layout:"menu",hideMode:"offsets",scrollerHeight:8,autoLayout:true,defaultType:"menuitem",bufferResize:false,initComponent:function(){if(Ext.isArray(this.initialConfig)){Ext.apply(this,{items:this.initialConfig})}this.addEvents("click","mouseover","mouseout","itemclick");Ext.menu.MenuMgr.register(this);if(this.floating){Ext.EventManager.onWindowResize(this.hide,this)}else{if(this.initialConfig.hidden!==false){this.hidden=false}this.internalDefaults={hideOnClick:false}}Ext.menu.Menu.superclass.initComponent.call(this);if(this.autoLayout){var a=this.doLayout.createDelegate(this,[]);this.on({add:a,remove:a})}},getLayoutTarget:function(){return this.ul},onRender:function(b,a){if(!b){b=Ext.getBody()}var c={id:this.getId(),cls:"x-menu "+((this.floating)?"x-menu-floating x-layer ":"")+(this.cls||"")+(this.plain?" x-menu-plain":"")+(this.showSeparator?"":" x-menu-nosep"),style:this.style,cn:[{tag:"a",cls:"x-menu-focus",href:"#",onclick:"return false;",tabIndex:"-1"},{tag:"ul",cls:"x-menu-list"}]};if(this.floating){this.el=new Ext.Layer({shadow:this.shadow,dh:c,constrain:false,parentEl:b,zindex:this.zIndex})}else{this.el=b.createChild(c)}Ext.menu.Menu.superclass.onRender.call(this,b,a);if(!this.keyNav){this.keyNav=new Ext.menu.MenuNav(this)}this.focusEl=this.el.child("a.x-menu-focus");this.ul=this.el.child("ul.x-menu-list");this.mon(this.ul,{scope:this,click:this.onClick,mouseover:this.onMouseOver,mouseout:this.onMouseOut});if(this.enableScrolling){this.mon(this.el,{scope:this,delegate:".x-menu-scroller",click:this.onScroll,mouseover:this.deactivateActive})}},findTargetItem:function(b){var a=b.getTarget(".x-menu-list-item",this.ul,true);if(a&&a.menuItemId){return this.items.get(a.menuItemId)}},onClick:function(b){var a=this.findTargetItem(b);if(a){if(a.isFormField){this.setActiveItem(a)}else{if(a instanceof Ext.menu.BaseItem){if(a.menu&&this.ignoreParentClicks){a.expandMenu();b.preventDefault()}else{if(a.onClick){a.onClick(b);this.fireEvent("click",this,a,b)}}}}}},setActiveItem:function(a,b){if(a!=this.activeItem){this.deactivateActive();if((this.activeItem=a).isFormField){a.focus()}else{a.activate(b)}}else{if(b){a.expandMenu()}}},deactivateActive:function(){var b=this.activeItem;if(b){if(b.isFormField){if(b.collapse){b.collapse()}}else{b.deactivate()}delete this.activeItem}},tryActivate:function(g,e){var b=this.items;for(var c=g,a=b.length;c>=0&&c=a.scrollHeight){this.onScrollerOut(null,b)}},onScrollerIn:function(d,b){var a=this.ul.dom,c=Ext.fly(b).is(".x-menu-scroller-top");if(c?a.scrollTop>0:a.scrollTop+this.activeMaxc){b=c;a=i-h}else{if(bb&&b>0){this.activeMax=b-this.scrollerHeight*2-this.el.getFrameWidth("tb")-Ext.num(this.el.shadowOffset,0);this.ul.setHeight(this.activeMax);this.createScrollers();this.el.select(".x-menu-scroller").setDisplayed("")}else{this.ul.setHeight(d);this.el.select(".x-menu-scroller").setDisplayed("none")}this.ul.dom.scrollTop=0;return a},createScrollers:function(){if(!this.scroller){this.scroller={pos:0,top:this.el.insertFirst({tag:"div",cls:"x-menu-scroller x-menu-scroller-top",html:" "}),bottom:this.el.createChild({tag:"div",cls:"x-menu-scroller x-menu-scroller-bottom",html:" "})};this.scroller.top.hover(this.onScrollerIn,this.onScrollerOut,this);this.scroller.topRepeater=new Ext.util.ClickRepeater(this.scroller.top,{listeners:{click:this.onScroll.createDelegate(this,[null,this.scroller.top],false)}});this.scroller.bottom.hover(this.onScrollerIn,this.onScrollerOut,this);this.scroller.bottomRepeater=new Ext.util.ClickRepeater(this.scroller.bottom,{listeners:{click:this.onScroll.createDelegate(this,[null,this.scroller.bottom],false)}})}},onLayout:function(){if(this.isVisible()){if(this.enableScrolling){this.constrainScroll(this.el.getTop())}if(this.floating){this.el.sync()}}},focus:function(){if(!this.hidden){this.doFocus.defer(50,this)}},doFocus:function(){if(!this.hidden){this.focusEl.focus()}},hide:function(a){if(!this.isDestroyed){this.deepHide=a;Ext.menu.Menu.superclass.hide.call(this);delete this.deepHide}},onHide:function(){Ext.menu.Menu.superclass.onHide.call(this);this.deactivateActive();if(this.el&&this.floating){this.el.hide()}var a=this.parentMenu;if(this.deepHide===true&&a){if(a.floating){a.hide(true)}else{a.deactivateActive()}}},lookupComponent:function(a){if(Ext.isString(a)){a=(a=="separator"||a=="-")?new Ext.menu.Separator():new Ext.menu.TextItem(a);this.applyDefaults(a)}else{if(Ext.isObject(a)){a=this.getMenuItem(a)}else{if(a.tagName||a.el){a=new Ext.BoxComponent({el:a})}}}return a},applyDefaults:function(b){if(!Ext.isString(b)){b=Ext.menu.Menu.superclass.applyDefaults.call(this,b);var a=this.internalDefaults;if(a){if(b.events){Ext.applyIf(b.initialConfig,a);Ext.apply(b,a)}else{Ext.applyIf(b,a)}}}return b},getMenuItem:function(a){a.ownerCt=this;if(!a.isXType){if(!a.xtype&&Ext.isBoolean(a.checked)){return new Ext.menu.CheckItem(a)}return Ext.create(a,this.defaultType)}return a},addSeparator:function(){return this.add(new Ext.menu.Separator())},addElement:function(a){return this.add(new Ext.menu.BaseItem({el:a}))},addItem:function(a){return this.add(a)},addMenuItem:function(a){return this.add(this.getMenuItem(a))},addText:function(a){return this.add(new Ext.menu.TextItem(a))},onDestroy:function(){Ext.EventManager.removeResizeListener(this.hide,this);var a=this.parentMenu;if(a&&a.activeChild==this){delete a.activeChild}delete this.parentMenu;Ext.menu.Menu.superclass.onDestroy.call(this);Ext.menu.MenuMgr.unregister(this);if(this.keyNav){this.keyNav.disable()}var b=this.scroller;if(b){Ext.destroy(b.topRepeater,b.bottomRepeater,b.top,b.bottom)}Ext.destroy(this.el,this.focusEl,this.ul)}});Ext.reg("menu",Ext.menu.Menu);Ext.menu.MenuNav=Ext.extend(Ext.KeyNav,function(){function a(d,c){if(!c.tryActivate(c.items.indexOf(c.activeItem)-1,-1)){c.tryActivate(c.items.length-1,-1)}}function b(d,c){if(!c.tryActivate(c.items.indexOf(c.activeItem)+1,1)){c.tryActivate(0,1)}}return{constructor:function(c){Ext.menu.MenuNav.superclass.constructor.call(this,c.el);this.scope=this.menu=c},doRelay:function(g,d){var c=g.getKey();if(this.menu.activeItem&&this.menu.activeItem.isFormField&&c!=g.TAB){return false}if(!this.menu.activeItem&&g.isNavKeyPress()&&c!=g.SPACE&&c!=g.RETURN){this.menu.tryActivate(0,1);return false}return d.call(this.scope||this,g,this.menu)},tab:function(d,c){d.stopEvent();if(d.shiftKey){a(d,c)}else{b(d,c)}},up:a,down:b,right:function(d,c){if(c.activeItem){c.activeItem.expandMenu(true)}},left:function(d,c){c.hide();if(c.parentMenu&&c.parentMenu.activeItem){c.parentMenu.activeItem.activate()}},enter:function(d,c){if(c.activeItem){d.stopPropagation();c.activeItem.onClick(d);c.fireEvent("click",this,c.activeItem);return true}}}}());Ext.menu.MenuMgr=function(){var h,e,b,d={},a=false,l=new Date();function n(){h={};e=new Ext.util.MixedCollection();b=Ext.getDoc().addKeyListener(27,j);b.disable()}function j(){if(e&&e.length>0){var o=e.clone();o.each(function(p){p.hide()});return true}return false}function g(o){e.remove(o);if(e.length<1){b.disable();Ext.getDoc().un("mousedown",m);a=false}}function k(o){var p=e.last();l=new Date();e.add(o);if(!a){b.enable();Ext.getDoc().on("mousedown",m);a=true}if(o.parentMenu){o.getEl().setZIndex(parseInt(o.parentMenu.getEl().getStyle("z-index"),10)+3);o.parentMenu.activeChild=o}else{if(p&&!p.isDestroyed&&p.isVisible()){o.getEl().setZIndex(parseInt(p.getEl().getStyle("z-index"),10)+3)}}}function c(o){if(o.activeChild){o.activeChild.hide()}if(o.autoHideTimer){clearTimeout(o.autoHideTimer);delete o.autoHideTimer}}function i(o){var p=o.parentMenu;if(!p&&!o.allowOtherMenus){j()}else{if(p&&p.activeChild){p.activeChild.hide()}}}function m(o){if(l.getElapsed()>50&&e.length>0&&!o.getTarget(".x-menu")){j()}}return{hideAll:function(){return j()},register:function(o){if(!h){n()}h[o.id]=o;o.on({beforehide:c,hide:g,beforeshow:i,show:k})},get:function(o){if(typeof o=="string"){if(!h){return null}return h[o]}else{if(o.events){return o}else{if(typeof o.length=="number"){return new Ext.menu.Menu({items:o})}else{return Ext.create(o,"menu")}}}},unregister:function(o){delete h[o.id];o.un("beforehide",c);o.un("hide",g);o.un("beforeshow",i);o.un("show",k)},registerCheckable:function(o){var p=o.group;if(p){if(!d[p]){d[p]=[]}d[p].push(o)}},unregisterCheckable:function(o){var p=o.group;if(p){d[p].remove(o)}},onCheckChange:function(q,r){if(q.group&&r){var t=d[q.group],p=0,o=t.length,s;for(;p',' target="{hrefTarget}"',"",">",'{altText}','{text}',"")}var c=this.getTemplateArgs();this.el=b?this.itemTpl.insertBefore(b,c,true):this.itemTpl.append(d,c,true);this.iconEl=this.el.child("img.x-menu-item-icon");this.textEl=this.el.child(".x-menu-item-text");if(!this.href){this.mon(this.el,"click",Ext.emptyFn,null,{preventDefault:true})}Ext.menu.Item.superclass.onRender.call(this,d,b)},getTemplateArgs:function(){return{id:this.id,cls:this.itemCls+(this.menu?" x-menu-item-arrow":"")+(this.cls?" "+this.cls:""),href:this.href||"#",hrefTarget:this.hrefTarget,icon:this.icon||Ext.BLANK_IMAGE_URL,iconCls:this.iconCls||"",text:this.itemText||this.text||" ",altText:this.altText||""}},setText:function(a){this.text=a||" ";if(this.rendered){this.textEl.update(this.text);this.parentMenu.layout.doAutoSize()}},setIconClass:function(a){var b=this.iconCls;this.iconCls=a;if(this.rendered){this.iconEl.replaceClass(b,this.iconCls)}},beforeDestroy:function(){clearTimeout(this.showTimer);clearTimeout(this.hideTimer);if(this.menu){delete this.menu.ownerCt;this.menu.destroy()}Ext.menu.Item.superclass.beforeDestroy.call(this)},handleClick:function(a){if(!this.href){a.stopEvent()}Ext.menu.Item.superclass.handleClick.apply(this,arguments)},activate:function(a){if(Ext.menu.Item.superclass.activate.apply(this,arguments)){this.focus();if(a){this.expandMenu()}}return true},shouldDeactivate:function(a){if(Ext.menu.Item.superclass.shouldDeactivate.call(this,a)){if(this.menu&&this.menu.isVisible()){return !this.menu.getEl().getRegion().contains(a.getPoint())}return true}return false},deactivate:function(){Ext.menu.Item.superclass.deactivate.apply(this,arguments);this.hideMenu()},expandMenu:function(a){if(!this.disabled&&this.menu){clearTimeout(this.hideTimer);delete this.hideTimer;if(!this.menu.isVisible()&&!this.showTimer){this.showTimer=this.deferExpand.defer(this.showDelay,this,[a])}else{if(this.menu.isVisible()&&a){this.menu.tryActivate(0,1)}}}},deferExpand:function(a){delete this.showTimer;this.menu.show(this.container,this.parentMenu.subMenuAlign||"tl-tr?",this.parentMenu);if(a){this.menu.tryActivate(0,1)}},hideMenu:function(){clearTimeout(this.showTimer);delete this.showTimer;if(!this.hideTimer&&this.menu&&this.menu.isVisible()){this.hideTimer=this.deferHide.defer(this.hideDelay,this)}},deferHide:function(){delete this.hideTimer;if(this.menu.over){this.parentMenu.setActiveItem(this,false)}else{this.menu.hide()}}});Ext.reg("menuitem",Ext.menu.Item);Ext.menu.CheckItem=Ext.extend(Ext.menu.Item,{itemCls:"x-menu-item x-menu-check-item",groupClass:"x-menu-group-item",checked:false,ctype:"Ext.menu.CheckItem",initComponent:function(){Ext.menu.CheckItem.superclass.initComponent.call(this);this.addEvents("beforecheckchange","checkchange");if(this.checkHandler){this.on("checkchange",this.checkHandler,this.scope)}Ext.menu.MenuMgr.registerCheckable(this)},onRender:function(a){Ext.menu.CheckItem.superclass.onRender.apply(this,arguments);if(this.group){this.el.addClass(this.groupClass)}if(this.checked){this.checked=false;this.setChecked(true,true)}},destroy:function(){Ext.menu.MenuMgr.unregisterCheckable(this);Ext.menu.CheckItem.superclass.destroy.apply(this,arguments)},setChecked:function(b,a){var c=a===true;if(this.checked!=b&&(c||this.fireEvent("beforecheckchange",this,b)!==false)){Ext.menu.MenuMgr.onCheckChange(this,b);if(this.container){this.container[b?"addClass":"removeClass"]("x-menu-item-checked")}this.checked=b;if(!c){this.fireEvent("checkchange",this,b)}}},handleClick:function(a){if(!this.disabled&&!(this.checked&&this.group)){this.setChecked(!this.checked)}Ext.menu.CheckItem.superclass.handleClick.apply(this,arguments)}});Ext.reg("menucheckitem",Ext.menu.CheckItem);Ext.menu.DateMenu=Ext.extend(Ext.menu.Menu,{enableScrolling:false,hideOnClick:true,pickerId:null,cls:"x-date-menu",initComponent:function(){this.on("beforeshow",this.onBeforeShow,this);if(this.strict=(Ext.isIE7&&Ext.isStrict)){this.on("show",this.onShow,this,{single:true,delay:20})}Ext.apply(this,{plain:true,showSeparator:false,items:this.picker=new Ext.DatePicker(Ext.applyIf({internalRender:this.strict||!Ext.isIE,ctCls:"x-menu-date-item",id:this.pickerId},this.initialConfig))});this.picker.purgeListeners();Ext.menu.DateMenu.superclass.initComponent.call(this);this.relayEvents(this.picker,["select"]);this.on("show",this.picker.focus,this.picker);this.on("select",this.menuHide,this);if(this.handler){this.on("select",this.handler,this.scope||this)}},menuHide:function(){if(this.hideOnClick){this.hide(true)}},onBeforeShow:function(){if(this.picker){this.picker.hideMonthPicker(true)}},onShow:function(){var a=this.picker.getEl();a.setWidth(a.getWidth())}});Ext.reg("datemenu",Ext.menu.DateMenu);Ext.menu.ColorMenu=Ext.extend(Ext.menu.Menu,{enableScrolling:false,hideOnClick:true,cls:"x-color-menu",paletteId:null,initComponent:function(){Ext.apply(this,{plain:true,showSeparator:false,items:this.palette=new Ext.ColorPalette(Ext.applyIf({id:this.paletteId},this.initialConfig))});this.palette.purgeListeners();Ext.menu.ColorMenu.superclass.initComponent.call(this);this.relayEvents(this.palette,["select"]);this.on("select",this.menuHide,this);if(this.handler){this.on("select",this.handler,this.scope||this)}},menuHide:function(){if(this.hideOnClick){this.hide(true)}}});Ext.reg("colormenu",Ext.menu.ColorMenu);Ext.form.Field=Ext.extend(Ext.BoxComponent,{invalidClass:"x-form-invalid",invalidText:"The value in this field is invalid",focusClass:"x-form-focus",validationEvent:"keyup",validateOnBlur:true,validationDelay:250,defaultAutoCreate:{tag:"input",type:"text",size:"20",autocomplete:"off"},fieldClass:"x-form-field",msgTarget:"qtip",msgFx:"normal",readOnly:false,disabled:false,submitValue:true,isFormField:true,msgDisplay:"",hasFocus:false,initComponent:function(){Ext.form.Field.superclass.initComponent.call(this);this.addEvents("focus","blur","specialkey","change","invalid","valid")},getName:function(){return this.rendered&&this.el.dom.name?this.el.dom.name:this.name||this.id||""},onRender:function(c,a){if(!this.el){var b=this.getAutoCreate();if(!b.name){b.name=this.name||this.id}if(this.inputType){b.type=this.inputType}this.autoEl=b}Ext.form.Field.superclass.onRender.call(this,c,a);if(this.submitValue===false){this.el.dom.removeAttribute("name")}var d=this.el.dom.type;if(d){if(d=="password"){d="text"}this.el.addClass("x-form-"+d)}if(this.readOnly){this.setReadOnly(true)}if(this.tabIndex!==undefined){this.el.dom.setAttribute("tabIndex",this.tabIndex)}this.el.addClass([this.fieldClass,this.cls])},getItemCt:function(){return this.itemCt},initValue:function(){if(this.value!==undefined){this.setValue(this.value)}else{if(!Ext.isEmpty(this.el.dom.value)&&this.el.dom.value!=this.emptyText){this.setValue(this.el.dom.value)}}this.originalValue=this.getValue()},isDirty:function(){if(this.disabled||!this.rendered){return false}return String(this.getValue())!==String(this.originalValue)},setReadOnly:function(a){if(this.rendered){this.el.dom.readOnly=a}this.readOnly=a},afterRender:function(){Ext.form.Field.superclass.afterRender.call(this);this.initEvents();this.initValue()},fireKey:function(a){if(a.isSpecialKey()){this.fireEvent("specialkey",this,a)}},reset:function(){this.setValue(this.originalValue);this.clearInvalid()},initEvents:function(){this.mon(this.el,Ext.EventManager.getKeyEvent(),this.fireKey,this);this.mon(this.el,"focus",this.onFocus,this);this.mon(this.el,"blur",this.onBlur,this,this.inEditor?{buffer:10}:null)},preFocus:Ext.emptyFn,onFocus:function(){this.preFocus();if(this.focusClass){this.el.addClass(this.focusClass)}if(!this.hasFocus){this.hasFocus=true;this.startValue=this.getValue();this.fireEvent("focus",this)}},beforeBlur:Ext.emptyFn,onBlur:function(){this.beforeBlur();if(this.focusClass){this.el.removeClass(this.focusClass)}this.hasFocus=false;if(this.validationEvent!==false&&(this.validateOnBlur||this.validationEvent=="blur")){this.validate()}var a=this.getValue();if(String(a)!==String(this.startValue)){this.fireEvent("change",this,a,this.startValue)}this.fireEvent("blur",this);this.postBlur()},postBlur:Ext.emptyFn,isValid:function(a){if(this.disabled){return true}var c=this.preventMark;this.preventMark=a===true;var b=this.validateValue(this.processValue(this.getRawValue()),a);this.preventMark=c;return b},validate:function(){if(this.disabled||this.validateValue(this.processValue(this.getRawValue()))){this.clearInvalid();return true}return false},processValue:function(a){return a},validateValue:function(b){var a=this.getErrors(b)[0];if(a==undefined){return true}else{this.markInvalid(a);return false}},getErrors:function(){return[]},getActiveError:function(){return this.activeError||""},markInvalid:function(c){if(this.rendered&&!this.preventMark){c=c||this.invalidText;var a=this.getMessageHandler();if(a){a.mark(this,c)}else{if(this.msgTarget){this.el.addClass(this.invalidClass);var b=Ext.getDom(this.msgTarget);if(b){b.innerHTML=c;b.style.display=this.msgDisplay}}}}this.setActiveError(c)},clearInvalid:function(){if(this.rendered&&!this.preventMark){this.el.removeClass(this.invalidClass);var a=this.getMessageHandler();if(a){a.clear(this)}else{if(this.msgTarget){this.el.removeClass(this.invalidClass);var b=Ext.getDom(this.msgTarget);if(b){b.innerHTML="";b.style.display="none"}}}}this.unsetActiveError()},setActiveError:function(b,a){this.activeError=b;if(a!==true){this.fireEvent("invalid",this,b)}},unsetActiveError:function(a){delete this.activeError;if(a!==true){this.fireEvent("valid",this)}},getMessageHandler:function(){return Ext.form.MessageTargets[this.msgTarget]},getErrorCt:function(){return this.el.findParent(".x-form-element",5,true)||this.el.findParent(".x-form-field-wrap",5,true)},alignErrorEl:function(){this.errorEl.setWidth(this.getErrorCt().getWidth(true)-20)},alignErrorIcon:function(){this.errorIcon.alignTo(this.el,"tl-tr",[2,0])},getRawValue:function(){var a=this.rendered?this.el.getValue():Ext.value(this.value,"");if(a===this.emptyText){a=""}return a},getValue:function(){if(!this.rendered){return this.value}var a=this.el.getValue();if(a===this.emptyText||a===undefined){a=""}return a},setRawValue:function(a){return this.rendered?(this.el.dom.value=(Ext.isEmpty(a)?"":a)):""},setValue:function(a){this.value=a;if(this.rendered){this.el.dom.value=(Ext.isEmpty(a)?"":a);this.validate()}return this},append:function(a){this.setValue([this.getValue(),a].join(""))}});Ext.form.MessageTargets={qtip:{mark:function(a,b){a.el.addClass(a.invalidClass);a.el.dom.qtip=b;a.el.dom.qclass="x-form-invalid-tip";if(Ext.QuickTips){Ext.QuickTips.enable()}},clear:function(a){a.el.removeClass(a.invalidClass);a.el.dom.qtip=""}},title:{mark:function(a,b){a.el.addClass(a.invalidClass);a.el.dom.title=b},clear:function(a){a.el.dom.title=""}},under:{mark:function(b,c){b.el.addClass(b.invalidClass);if(!b.errorEl){var a=b.getErrorCt();if(!a){b.el.dom.title=c;return}b.errorEl=a.createChild({cls:"x-form-invalid-msg"});b.on("resize",b.alignErrorEl,b);b.on("destroy",function(){Ext.destroy(this.errorEl)},b)}b.alignErrorEl();b.errorEl.update(c);Ext.form.Field.msgFx[b.msgFx].show(b.errorEl,b)},clear:function(a){a.el.removeClass(a.invalidClass);if(a.errorEl){Ext.form.Field.msgFx[a.msgFx].hide(a.errorEl,a)}else{a.el.dom.title=""}}},side:{mark:function(b,c){b.el.addClass(b.invalidClass);if(!b.errorIcon){var a=b.getErrorCt();if(!a){b.el.dom.title=c;return}b.errorIcon=a.createChild({cls:"x-form-invalid-icon"});if(b.ownerCt){b.ownerCt.on("afterlayout",b.alignErrorIcon,b);b.ownerCt.on("expand",b.alignErrorIcon,b)}b.on("resize",b.alignErrorIcon,b);b.on("destroy",function(){Ext.destroy(this.errorIcon)},b)}b.alignErrorIcon();b.errorIcon.dom.qtip=c;b.errorIcon.dom.qclass="x-form-invalid-tip";b.errorIcon.show()},clear:function(a){a.el.removeClass(a.invalidClass);if(a.errorIcon){a.errorIcon.dom.qtip="";a.errorIcon.hide()}else{a.el.dom.title=""}}}};Ext.form.Field.msgFx={normal:{show:function(a,b){a.setDisplayed("block")},hide:function(a,b){a.setDisplayed(false).update("")}},slide:{show:function(a,b){a.slideIn("t",{stopFx:true})},hide:function(a,b){a.slideOut("t",{stopFx:true,useDisplay:true})}},slideRight:{show:function(a,b){a.fixDisplay();a.alignTo(b.el,"tl-tr");a.slideIn("l",{stopFx:true})},hide:function(a,b){a.slideOut("l",{stopFx:true,useDisplay:true})}}};Ext.reg("field",Ext.form.Field);Ext.form.TextField=Ext.extend(Ext.form.Field,{grow:false,growMin:30,growMax:800,vtype:null,maskRe:null,disableKeyFilter:false,allowBlank:true,minLength:0,maxLength:Number.MAX_VALUE,minLengthText:"The minimum length for this field is {0}",maxLengthText:"The maximum length for this field is {0}",selectOnFocus:false,blankText:"This field is required",validator:null,regex:null,regexText:"",emptyText:null,emptyClass:"x-form-empty-field",initComponent:function(){Ext.form.TextField.superclass.initComponent.call(this);this.addEvents("autosize","keydown","keyup","keypress")},initEvents:function(){Ext.form.TextField.superclass.initEvents.call(this);if(this.validationEvent=="keyup"){this.validationTask=new Ext.util.DelayedTask(this.validate,this);this.mon(this.el,"keyup",this.filterValidation,this)}else{if(this.validationEvent!==false&&this.validationEvent!="blur"){this.mon(this.el,this.validationEvent,this.validate,this,{buffer:this.validationDelay})}}if(this.selectOnFocus||this.emptyText){this.mon(this.el,"mousedown",this.onMouseDown,this);if(this.emptyText){this.applyEmptyText()}}if(this.maskRe||(this.vtype&&this.disableKeyFilter!==true&&(this.maskRe=Ext.form.VTypes[this.vtype+"Mask"]))){this.mon(this.el,"keypress",this.filterKeys,this)}if(this.grow){this.mon(this.el,"keyup",this.onKeyUpBuffered,this,{buffer:50});this.mon(this.el,"click",this.autoSize,this)}if(this.enableKeyEvents){this.mon(this.el,{scope:this,keyup:this.onKeyUp,keydown:this.onKeyDown,keypress:this.onKeyPress})}},onMouseDown:function(a){if(!this.hasFocus){this.mon(this.el,"mouseup",Ext.emptyFn,this,{single:true,preventDefault:true})}},processValue:function(a){if(this.stripCharsRe){var b=a.replace(this.stripCharsRe,"");if(b!==a){this.setRawValue(b);return b}}return a},filterValidation:function(a){if(!a.isNavKeyPress()){this.validationTask.delay(this.validationDelay)}},onDisable:function(){Ext.form.TextField.superclass.onDisable.call(this);if(Ext.isIE){this.el.dom.unselectable="on"}},onEnable:function(){Ext.form.TextField.superclass.onEnable.call(this);if(Ext.isIE){this.el.dom.unselectable=""}},onKeyUpBuffered:function(a){if(this.doAutoSize(a)){this.autoSize()}},doAutoSize:function(a){return !a.isNavKeyPress()},onKeyUp:function(a){this.fireEvent("keyup",this,a)},onKeyDown:function(a){this.fireEvent("keydown",this,a)},onKeyPress:function(a){this.fireEvent("keypress",this,a)},reset:function(){Ext.form.TextField.superclass.reset.call(this);this.applyEmptyText()},applyEmptyText:function(){if(this.rendered&&this.emptyText&&this.getRawValue().length<1&&!this.hasFocus){this.setRawValue(this.emptyText);this.el.addClass(this.emptyClass)}},preFocus:function(){var a=this.el,b;if(this.emptyText){if(a.dom.value==this.emptyText){this.setRawValue("");b=true}a.removeClass(this.emptyClass)}if(this.selectOnFocus||b){a.dom.select()}},postBlur:function(){this.applyEmptyText()},filterKeys:function(b){if(b.ctrlKey){return}var a=b.getKey();if(Ext.isGecko&&(b.isNavKeyPress()||a==b.BACKSPACE||(a==b.DELETE&&b.button==-1))){return}var c=String.fromCharCode(b.getCharCode());if(!Ext.isGecko&&b.isSpecialKey()&&!c){return}if(!this.maskRe.test(c)){b.stopEvent()}},setValue:function(a){if(this.emptyText&&this.el&&!Ext.isEmpty(a)){this.el.removeClass(this.emptyClass)}Ext.form.TextField.superclass.setValue.apply(this,arguments);this.applyEmptyText();this.autoSize();return this},getErrors:function(a){var d=Ext.form.TextField.superclass.getErrors.apply(this,arguments);a=Ext.isDefined(a)?a:this.processValue(this.getRawValue());if(Ext.isFunction(this.validator)){var c=this.validator(a);if(c!==true){d.push(c)}}if(a.length<1||a===this.emptyText){if(this.allowBlank){return d}else{d.push(this.blankText)}}if(!this.allowBlank&&(a.length<1||a===this.emptyText)){d.push(this.blankText)}if(a.lengththis.maxLength){d.push(String.format(this.maxLengthText,this.maxLength))}if(this.vtype){var b=Ext.form.VTypes;if(!b[this.vtype](a,this)){d.push(this.vtypeText||b[this.vtype+"Text"])}}if(this.regex&&!this.regex.test(a)){d.push(this.regexText)}return d},selectText:function(h,a){var c=this.getRawValue();var e=false;if(c.length>0){h=h===undefined?0:h;a=a===undefined?c.length:a;var g=this.el.dom;if(g.setSelectionRange){g.setSelectionRange(h,a)}else{if(g.createTextRange){var b=g.createTextRange();b.moveStart("character",h);b.moveEnd("character",a-c.length);b.select()}}e=Ext.isGecko||Ext.isOpera}else{e=true}if(e){this.focus()}},autoSize:function(){if(!this.grow||!this.rendered){return}if(!this.metrics){this.metrics=Ext.util.TextMetrics.createInstance(this.el)}var c=this.el;var b=c.dom.value;var e=document.createElement("div");e.appendChild(document.createTextNode(b));b=e.innerHTML;Ext.removeNode(e);e=null;b+=" ";var a=Math.min(this.growMax,Math.max(this.metrics.getWidth(b)+10,this.growMin));this.el.setWidth(a);this.fireEvent("autosize",this,a)},onDestroy:function(){if(this.validationTask){this.validationTask.cancel();this.validationTask=null}Ext.form.TextField.superclass.onDestroy.call(this)}});Ext.reg("textfield",Ext.form.TextField);Ext.form.TriggerField=Ext.extend(Ext.form.TextField,{defaultAutoCreate:{tag:"input",type:"text",size:"16",autocomplete:"off"},hideTrigger:false,editable:true,readOnly:false,wrapFocusClass:"x-trigger-wrap-focus",autoSize:Ext.emptyFn,monitorTab:true,deferHeight:true,mimicing:false,actionMode:"wrap",defaultTriggerWidth:17,onResize:function(a,c){Ext.form.TriggerField.superclass.onResize.call(this,a,c);var b=this.getTriggerWidth();if(Ext.isNumber(a)){this.el.setWidth(a-b)}this.wrap.setWidth(this.el.getWidth()+b)},getTriggerWidth:function(){var a=this.trigger.getWidth();if(!this.hideTrigger&&!this.readOnly&&a===0){a=this.defaultTriggerWidth}return a},alignErrorIcon:function(){if(this.wrap){this.errorIcon.alignTo(this.wrap,"tl-tr",[2,0])}},onRender:function(b,a){this.doc=Ext.isIE?Ext.getBody():Ext.getDoc();Ext.form.TriggerField.superclass.onRender.call(this,b,a);this.wrap=this.el.wrap({cls:"x-form-field-wrap x-form-field-trigger-wrap"});this.trigger=this.wrap.createChild(this.triggerConfig||{tag:"img",src:Ext.BLANK_IMAGE_URL,alt:"",cls:"x-form-trigger "+this.triggerClass});this.initTrigger();if(!this.width){this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth())}this.resizeEl=this.positionEl=this.wrap},getWidth:function(){return(this.el.getWidth()+this.trigger.getWidth())},updateEditState:function(){if(this.rendered){if(this.readOnly){this.el.dom.readOnly=true;this.el.addClass("x-trigger-noedit");this.mun(this.el,"click",this.onTriggerClick,this);this.trigger.setDisplayed(false)}else{if(!this.editable){this.el.dom.readOnly=true;this.el.addClass("x-trigger-noedit");this.mon(this.el,"click",this.onTriggerClick,this)}else{this.el.dom.readOnly=false;this.el.removeClass("x-trigger-noedit");this.mun(this.el,"click",this.onTriggerClick,this)}this.trigger.setDisplayed(!this.hideTrigger)}this.onResize(this.width||this.wrap.getWidth())}},setHideTrigger:function(a){if(a!=this.hideTrigger){this.hideTrigger=a;this.updateEditState()}},setEditable:function(a){if(a!=this.editable){this.editable=a;this.updateEditState()}},setReadOnly:function(a){if(a!=this.readOnly){this.readOnly=a;this.updateEditState()}},afterRender:function(){Ext.form.TriggerField.superclass.afterRender.call(this);this.updateEditState()},initTrigger:function(){this.mon(this.trigger,"click",this.onTriggerClick,this,{preventDefault:true});this.trigger.addClassOnOver("x-form-trigger-over");this.trigger.addClassOnClick("x-form-trigger-click")},onDestroy:function(){Ext.destroy(this.trigger,this.wrap);if(this.mimicing){this.doc.un("mousedown",this.mimicBlur,this)}delete this.doc;Ext.form.TriggerField.superclass.onDestroy.call(this)},onFocus:function(){Ext.form.TriggerField.superclass.onFocus.call(this);if(!this.mimicing){this.wrap.addClass(this.wrapFocusClass);this.mimicing=true;this.doc.on("mousedown",this.mimicBlur,this,{delay:10});if(this.monitorTab){this.on("specialkey",this.checkTab,this)}}},checkTab:function(a,b){if(b.getKey()==b.TAB){this.triggerBlur()}},onBlur:Ext.emptyFn,mimicBlur:function(a){if(!this.isDestroyed&&!this.wrap.contains(a.target)&&this.validateBlur(a)){this.triggerBlur()}},triggerBlur:function(){this.mimicing=false;this.doc.un("mousedown",this.mimicBlur,this);if(this.monitorTab&&this.el){this.un("specialkey",this.checkTab,this)}Ext.form.TriggerField.superclass.onBlur.call(this);if(this.wrap){this.wrap.removeClass(this.wrapFocusClass)}},beforeBlur:Ext.emptyFn,validateBlur:function(a){return true},onTriggerClick:Ext.emptyFn});Ext.form.TwinTriggerField=Ext.extend(Ext.form.TriggerField,{initComponent:function(){Ext.form.TwinTriggerField.superclass.initComponent.call(this);this.triggerConfig={tag:"span",cls:"x-form-twin-triggers",cn:[{tag:"img",src:Ext.BLANK_IMAGE_URL,alt:"",cls:"x-form-trigger "+this.trigger1Class},{tag:"img",src:Ext.BLANK_IMAGE_URL,alt:"",cls:"x-form-trigger "+this.trigger2Class}]}},getTrigger:function(a){return this.triggers[a]},afterRender:function(){Ext.form.TwinTriggerField.superclass.afterRender.call(this);var c=this.triggers,b=0,a=c.length;for(;b")}}d.innerHTML=a;b=Math.min(this.growMax,Math.max(d.offsetHeight,this.growMin));if(b!=this.lastHeight){this.lastHeight=b;this.el.setHeight(b);this.fireEvent("autosize",this,b)}}});Ext.reg("textarea",Ext.form.TextArea);Ext.form.NumberField=Ext.extend(Ext.form.TextField,{fieldClass:"x-form-field x-form-num-field",allowDecimals:true,decimalSeparator:".",decimalPrecision:2,allowNegative:true,minValue:Number.NEGATIVE_INFINITY,maxValue:Number.MAX_VALUE,minText:"The minimum value for this field is {0}",maxText:"The maximum value for this field is {0}",nanText:"{0} is not a valid number",baseChars:"0123456789",autoStripChars:false,initEvents:function(){var a=this.baseChars+"";if(this.allowDecimals){a+=this.decimalSeparator}if(this.allowNegative){a+="-"}a=Ext.escapeRe(a);this.maskRe=new RegExp("["+a+"]");if(this.autoStripChars){this.stripCharsRe=new RegExp("[^"+a+"]","gi")}Ext.form.NumberField.superclass.initEvents.call(this)},getErrors:function(b){var c=Ext.form.NumberField.superclass.getErrors.apply(this,arguments);b=Ext.isDefined(b)?b:this.processValue(this.getRawValue());if(b.length<1){return c}b=String(b).replace(this.decimalSeparator,".");if(isNaN(b)){c.push(String.format(this.nanText,b))}var a=this.parseValue(b);if(athis.maxValue){c.push(String.format(this.maxText,this.maxValue))}return c},getValue:function(){return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)))},setValue:function(a){a=Ext.isNumber(a)?a:parseFloat(String(a).replace(this.decimalSeparator,"."));a=this.fixPrecision(a);a=isNaN(a)?"":String(a).replace(".",this.decimalSeparator);return Ext.form.NumberField.superclass.setValue.call(this,a)},setMinValue:function(a){this.minValue=Ext.num(a,Number.NEGATIVE_INFINITY)},setMaxValue:function(a){this.maxValue=Ext.num(a,Number.MAX_VALUE)},parseValue:function(a){a=parseFloat(String(a).replace(this.decimalSeparator,"."));return isNaN(a)?"":a},fixPrecision:function(b){var a=isNaN(b);if(!this.allowDecimals||this.decimalPrecision==-1||a||!b){return a?"":b}return parseFloat(parseFloat(b).toFixed(this.decimalPrecision))},beforeBlur:function(){var a=this.parseValue(this.getRawValue());if(!Ext.isEmpty(a)){this.setValue(a)}}});Ext.reg("numberfield",Ext.form.NumberField);Ext.form.DateField=Ext.extend(Ext.form.TriggerField,{format:"m/d/Y",altFormats:"m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j",disabledDaysText:"Disabled",disabledDatesText:"Disabled",minText:"The date in this field must be equal to or after {0}",maxText:"The date in this field must be equal to or before {0}",invalidText:"{0} is not a valid date - it must be in the format {1}",triggerClass:"x-form-date-trigger",showToday:true,startDay:0,defaultAutoCreate:{tag:"input",type:"text",size:"10",autocomplete:"off"},initTime:"12",initTimeFormat:"H",safeParse:function(b,c){if(Date.formatContainsHourInfo(c)){return Date.parseDate(b,c)}else{var a=Date.parseDate(b+" "+this.initTime,c+" "+this.initTimeFormat);if(a){return a.clearTime()}}},initComponent:function(){Ext.form.DateField.superclass.initComponent.call(this);this.addEvents("select");if(Ext.isString(this.minValue)){this.minValue=this.parseDate(this.minValue)}if(Ext.isString(this.maxValue)){this.maxValue=this.parseDate(this.maxValue)}this.disabledDatesRE=null;this.initDisabledDays()},initEvents:function(){Ext.form.DateField.superclass.initEvents.call(this);this.keyNav=new Ext.KeyNav(this.el,{down:function(a){this.onTriggerClick()},scope:this,forceKeyDown:true})},initDisabledDays:function(){if(this.disabledDates){var b=this.disabledDates,a=b.length-1,c="(?:";Ext.each(b,function(g,e){c+=Ext.isDate(g)?"^"+Ext.escapeRe(g.dateFormat(this.format))+"$":b[e];if(e!=a){c+="|"}},this);this.disabledDatesRE=new RegExp(c+")")}},setDisabledDates:function(a){this.disabledDates=a;this.initDisabledDays();if(this.menu){this.menu.picker.setDisabledDates(this.disabledDatesRE)}},setDisabledDays:function(a){this.disabledDays=a;if(this.menu){this.menu.picker.setDisabledDays(a)}},setMinValue:function(a){this.minValue=(Ext.isString(a)?this.parseDate(a):a);if(this.menu){this.menu.picker.setMinDate(this.minValue)}},setMaxValue:function(a){this.maxValue=(Ext.isString(a)?this.parseDate(a):a);if(this.menu){this.menu.picker.setMaxDate(this.maxValue)}},getErrors:function(e){var h=Ext.form.DateField.superclass.getErrors.apply(this,arguments);e=this.formatDate(e||this.processValue(this.getRawValue()));if(e.length<1){return h}var c=e;e=this.parseDate(e);if(!e){h.push(String.format(this.invalidText,c,this.format));return h}var g=e.getTime();if(this.minValue&&gthis.maxValue.clearTime().getTime()){h.push(String.format(this.maxText,this.formatDate(this.maxValue)))}if(this.disabledDays){var a=e.getDay();for(var b=0;b
{'+this.displayField+"}
"}this.view=new Ext.DataView({applyTo:this.innerList,tpl:this.tpl,singleSelect:true,selectedClass:this.selectedClass,itemSelector:this.itemSelector||"."+a+"-item",emptyText:this.listEmptyText,deferEmptyText:false});this.mon(this.view,{containerclick:this.onViewClick,click:this.onViewClick,scope:this});this.bindStore(this.store,true);if(this.resizable){this.resizer=new Ext.Resizable(this.list,{pinned:true,handles:"se"});this.mon(this.resizer,"resize",function(g,d,e){this.maxHeight=e-this.handleHeight-this.list.getFrameWidth("tb")-this.assetHeight;this.listWidth=d;this.innerList.setWidth(d-this.list.getFrameWidth("lr"));this.restrictHeight()},this);this[this.pageSize?"footer":"innerList"].setStyle("margin-bottom",this.handleHeight+"px")}}},getListParent:function(){return document.body},getStore:function(){return this.store},bindStore:function(a,b){if(this.store&&!b){if(this.store!==a&&this.store.autoDestroy){this.store.destroy()}else{this.store.un("beforeload",this.onBeforeLoad,this);this.store.un("load",this.onLoad,this);this.store.un("exception",this.collapse,this)}if(!a){this.store=null;if(this.view){this.view.bindStore(null)}if(this.pageTb){this.pageTb.bindStore(null)}}}if(a){if(!b){this.lastQuery=null;if(this.pageTb){this.pageTb.bindStore(a)}}this.store=Ext.StoreMgr.lookup(a);this.store.on({scope:this,beforeload:this.onBeforeLoad,load:this.onLoad,exception:this.collapse});if(this.view){this.view.bindStore(a)}}},reset:function(){if(this.clearFilterOnReset&&this.mode=="local"){this.store.clearFilter()}Ext.form.ComboBox.superclass.reset.call(this)},initEvents:function(){Ext.form.ComboBox.superclass.initEvents.call(this);this.keyNav=new Ext.KeyNav(this.el,{up:function(a){this.inKeyMode=true;this.selectPrev()},down:function(a){if(!this.isExpanded()){this.onTriggerClick()}else{this.inKeyMode=true;this.selectNext()}},enter:function(a){this.onViewClick()},esc:function(a){this.collapse()},tab:function(a){if(this.forceSelection===true){this.collapse()}else{this.onViewClick(false)}return true},scope:this,doRelay:function(c,b,a){if(a=="down"||this.scope.isExpanded()){var d=Ext.KeyNav.prototype.doRelay.apply(this,arguments);if(!Ext.isIE&&Ext.EventManager.useKeydown){this.scope.fireKey(c)}return d}return true},forceKeyDown:true,defaultEventAction:"stopEvent"});this.queryDelay=Math.max(this.queryDelay||10,this.mode=="local"?10:250);this.dqTask=new Ext.util.DelayedTask(this.initQuery,this);if(this.typeAhead){this.taTask=new Ext.util.DelayedTask(this.onTypeAhead,this)}if(!this.enableKeyEvents){this.mon(this.el,"keyup",this.onKeyUp,this)}},onDestroy:function(){if(this.dqTask){this.dqTask.cancel();this.dqTask=null}this.bindStore(null);Ext.destroy(this.resizer,this.view,this.pageTb,this.list);Ext.destroyMembers(this,"hiddenField");Ext.form.ComboBox.superclass.onDestroy.call(this)},fireKey:function(a){if(!this.isExpanded()){Ext.form.ComboBox.superclass.fireKey.call(this,a)}},onResize:function(a,b){Ext.form.ComboBox.superclass.onResize.apply(this,arguments);if(!isNaN(a)&&this.isVisible()&&this.list){this.doResize(a)}else{this.bufferSize=a}},doResize:function(a){if(!Ext.isDefined(this.listWidth)){var b=Math.max(a,this.minListWidth);this.list.setWidth(b);this.innerList.setWidth(b-this.list.getFrameWidth("lr"))}},onEnable:function(){Ext.form.ComboBox.superclass.onEnable.apply(this,arguments);if(this.hiddenField){this.hiddenField.disabled=false}},onDisable:function(){Ext.form.ComboBox.superclass.onDisable.apply(this,arguments);if(this.hiddenField){this.hiddenField.disabled=true}},onBeforeLoad:function(){if(!this.hasFocus){return}this.innerList.update(this.loadingText?'
'+this.loadingText+"
":"");this.restrictHeight();this.selectedIndex=-1},onLoad:function(){if(!this.hasFocus){return}if(this.store.getCount()>0||this.listEmptyText){this.expand();this.restrictHeight();if(this.lastQuery==this.allQuery){if(this.editable){this.el.dom.select()}if(this.autoSelect!==false&&!this.selectByValue(this.value,true)){this.select(0,true)}}else{if(this.autoSelect!==false){this.selectNext()}if(this.typeAhead&&this.lastKey!=Ext.EventObject.BACKSPACE&&this.lastKey!=Ext.EventObject.DELETE){this.taTask.delay(this.typeAheadDelay)}}}else{this.collapse()}},onTypeAhead:function(){if(this.store.getCount()>0){var b=this.store.getAt(0);var c=b.data[this.displayField];var a=c.length;var d=this.getRawValue().length;if(d!=a){this.setRawValue(c);this.selectText(d,c.length)}}},assertValue:function(){var b=this.getRawValue(),a;if(this.valueField&&Ext.isDefined(this.value)){a=this.findRecord(this.valueField,this.value)}if(!a||a.get(this.displayField)!=b){a=this.findRecord(this.displayField,b)}if(!a&&this.forceSelection){if(b.length>0&&b!=this.emptyText){this.el.dom.value=Ext.value(this.lastSelectionText,"");this.applyEmptyText()}else{this.clearValue()}}else{if(a&&this.valueField){if(this.value==b){return}b=a.get(this.valueField||this.displayField)}this.setValue(b)}},onSelect:function(a,b){if(this.fireEvent("beforeselect",this,a,b)!==false){this.setValue(a.data[this.valueField||this.displayField]);this.collapse();this.fireEvent("select",this,a,b)}},getName:function(){var a=this.hiddenField;return a&&a.name?a.name:this.hiddenName||Ext.form.ComboBox.superclass.getName.call(this)},getValue:function(){if(this.valueField){return Ext.isDefined(this.value)?this.value:""}else{return Ext.form.ComboBox.superclass.getValue.call(this)}},clearValue:function(){if(this.hiddenField){this.hiddenField.value=""}this.setRawValue("");this.lastSelectionText="";this.applyEmptyText();this.value=""},setValue:function(a){var c=a;if(this.valueField){var b=this.findRecord(this.valueField,a);if(b){c=b.data[this.displayField]}else{if(Ext.isDefined(this.valueNotFoundText)){c=this.valueNotFoundText}}}this.lastSelectionText=c;if(this.hiddenField){this.hiddenField.value=Ext.value(a,"")}Ext.form.ComboBox.superclass.setValue.call(this,c);this.value=a;return this},findRecord:function(c,b){var a;if(this.store.getCount()>0){this.store.each(function(d){if(d.data[c]==b){a=d;return false}})}return a},onViewMove:function(b,a){this.inKeyMode=false},onViewOver:function(d,b){if(this.inKeyMode){return}var c=this.view.findItemFromChild(b);if(c){var a=this.view.indexOf(c);this.select(a,false)}},onViewClick:function(b){var a=this.view.getSelectedIndexes()[0],c=this.store,d=c.getAt(a);if(d){this.onSelect(d,a)}else{this.collapse()}if(b!==false){this.el.focus()}},restrictHeight:function(){this.innerList.dom.style.height="";var b=this.innerList.dom,e=this.list.getFrameWidth("tb")+(this.resizable?this.handleHeight:0)+this.assetHeight,c=Math.max(b.clientHeight,b.offsetHeight,b.scrollHeight),a=this.getPosition()[1]-Ext.getBody().getScroll().top,g=Ext.lib.Dom.getViewHeight()-a-this.getSize().height,d=Math.max(a,g,this.minHeight||0)-this.list.shadowOffset-e-5;c=Math.min(c,d,this.maxHeight);this.innerList.setHeight(c);this.list.beginUpdate();this.list.setHeight(c+e);this.list.alignTo.apply(this.list,[this.el].concat(this.listAlign));this.list.endUpdate()},isExpanded:function(){return this.list&&this.list.isVisible()},selectByValue:function(a,c){if(!Ext.isEmpty(a,true)){var b=this.findRecord(this.valueField||this.displayField,a);if(b){this.select(this.store.indexOf(b),c);return true}}return false},select:function(a,c){this.selectedIndex=a;this.view.select(a);if(c!==false){var b=this.view.getNode(a);if(b){this.innerList.scrollChildIntoView(b,false)}}},selectNext:function(){var a=this.store.getCount();if(a>0){if(this.selectedIndex==-1){this.select(0)}else{if(this.selectedIndex0){if(this.selectedIndex==-1){this.select(0)}else{if(this.selectedIndex!==0){this.select(this.selectedIndex-1)}}}},onKeyUp:function(b){var a=b.getKey();if(this.editable!==false&&this.readOnly!==true&&(a==b.BACKSPACE||!b.isSpecialKey())){this.lastKey=a;this.dqTask.delay(this.queryDelay)}Ext.form.ComboBox.superclass.onKeyUp.call(this,b)},validateBlur:function(){return !this.list||!this.list.isVisible()},initQuery:function(){this.doQuery(this.getRawValue())},beforeBlur:function(){this.assertValue()},postBlur:function(){Ext.form.ComboBox.superclass.postBlur.call(this);this.collapse();this.inKeyMode=false},doQuery:function(c,b){c=Ext.isEmpty(c)?"":c;var a={query:c,forceAll:b,combo:this,cancel:false};if(this.fireEvent("beforequery",a)===false||a.cancel){return false}c=a.query;b=a.forceAll;if(b===true||(c.length>=this.minChars)){if(this.lastQuery!==c){this.lastQuery=c;if(this.mode=="local"){this.selectedIndex=-1;if(b){this.store.clearFilter()}else{this.store.filter(this.displayField,c)}this.onLoad()}else{this.store.baseParams[this.queryParam]=c;this.store.load({params:this.getParams(c)});this.expand()}}else{this.selectedIndex=-1;this.onLoad()}}},getParams:function(a){var b={},c=this.store.paramNames;if(this.pageSize){b[c.start]=0;b[c.limit]=this.pageSize}return b},collapse:function(){if(!this.isExpanded()){return}this.list.hide();Ext.getDoc().un("mousewheel",this.collapseIf,this);Ext.getDoc().un("mousedown",this.collapseIf,this);this.fireEvent("collapse",this)},collapseIf:function(a){if(!this.isDestroyed&&!a.within(this.wrap)&&!a.within(this.list)){this.collapse()}},expand:function(){if(this.isExpanded()||!this.hasFocus){return}if(this.title||this.pageSize){this.assetHeight=0;if(this.title){this.assetHeight+=this.header.getHeight()}if(this.pageSize){this.assetHeight+=this.footer.getHeight()}}if(this.bufferSize){this.doResize(this.bufferSize);delete this.bufferSize}this.list.alignTo.apply(this.list,[this.el].concat(this.listAlign));this.list.setZIndex(this.getZIndex());this.list.show();if(Ext.isGecko2){this.innerList.setOverflow("auto")}this.mon(Ext.getDoc(),{scope:this,mousewheel:this.collapseIf,mousedown:this.collapseIf});this.fireEvent("expand",this)},onTriggerClick:function(){if(this.readOnly||this.disabled){return}if(this.isExpanded()){this.collapse();this.el.focus()}else{this.onFocus({});if(this.triggerAction=="all"){this.doQuery(this.allQuery,true)}else{this.doQuery(this.getRawValue())}this.el.focus()}}});Ext.reg("combo",Ext.form.ComboBox);Ext.form.Checkbox=Ext.extend(Ext.form.Field,{focusClass:undefined,fieldClass:"x-form-field",checked:false,boxLabel:" ",defaultAutoCreate:{tag:"input",type:"checkbox",autocomplete:"off"},actionMode:"wrap",initComponent:function(){Ext.form.Checkbox.superclass.initComponent.call(this);this.addEvents("check")},onResize:function(){Ext.form.Checkbox.superclass.onResize.apply(this,arguments);if(!this.boxLabel&&!this.fieldLabel){this.el.alignTo(this.wrap,"c-c")}},initEvents:function(){Ext.form.Checkbox.superclass.initEvents.call(this);this.mon(this.el,{scope:this,click:this.onClick,change:this.onClick})},markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn,onRender:function(b,a){Ext.form.Checkbox.superclass.onRender.call(this,b,a);if(this.inputValue!==undefined){this.el.dom.value=this.inputValue}this.wrap=this.el.wrap({cls:"x-form-check-wrap"});if(this.boxLabel){this.wrap.createChild({tag:"label",htmlFor:this.el.id,cls:"x-form-cb-label",html:this.boxLabel})}if(this.checked){this.setValue(true)}else{this.checked=this.el.dom.checked}if(Ext.isIE&&!Ext.isStrict){this.wrap.repaint()}this.resizeEl=this.positionEl=this.wrap},onDestroy:function(){Ext.destroy(this.wrap);Ext.form.Checkbox.superclass.onDestroy.call(this)},initValue:function(){this.originalValue=this.getValue()},getValue:function(){if(this.rendered){return this.el.dom.checked}return this.checked},onClick:function(){if(this.el.dom.checked!=this.checked){this.setValue(this.el.dom.checked)}},setValue:function(a){var c=this.checked,b=this.inputValue;if(a===false){this.checked=false}else{this.checked=(a===true||a==="true"||a=="1"||(b?a==b:String(a).toLowerCase()=="on"))}if(this.rendered){this.el.dom.checked=this.checked;this.el.dom.defaultChecked=this.checked}if(c!=this.checked){this.fireEvent("check",this,this.checked);if(this.handler){this.handler.call(this.scope||this,this,this.checked)}}return this}});Ext.reg("checkbox",Ext.form.Checkbox);Ext.form.CheckboxGroup=Ext.extend(Ext.form.Field,{columns:"auto",vertical:false,allowBlank:true,blankText:"You must select at least one item in this group",defaultType:"checkbox",groupCls:"x-form-check-group",initComponent:function(){this.addEvents("change");this.on("change",this.validate,this);Ext.form.CheckboxGroup.superclass.initComponent.call(this)},onRender:function(j,g){if(!this.el){var p={autoEl:{id:this.id},cls:this.groupCls,layout:"column",renderTo:j,bufferResize:false};var a={xtype:"container",defaultType:this.defaultType,layout:"form",defaults:{hideLabel:true,anchor:"100%"}};if(this.items[0].items){Ext.apply(p,{layoutConfig:{columns:this.items.length},defaults:this.defaults,items:this.items});for(var e=0,m=this.items.length;e0&&e%r==0){o++}if(this.items[e].fieldLabel){this.items[e].hideLabel=false}n[o].items.push(this.items[e])}}else{for(var e=0,m=this.items.length;e-1){b.setValue(true)}})},getBox:function(b){var a=null;this.eachItem(function(c){if(b==c||c.dataIndex==b||c.id==b||c.getName()==b){a=c;return false}});return a},getValue:function(){var a=[];this.eachItem(function(b){if(b.checked){a.push(b)}});return a},eachItem:function(b,a){if(this.items&&this.items.each){this.items.each(b,a||this)}},getRawValue:Ext.emptyFn,setRawValue:Ext.emptyFn});Ext.reg("checkboxgroup",Ext.form.CheckboxGroup);Ext.form.CompositeField=Ext.extend(Ext.form.Field,{defaultMargins:"0 5 0 0",skipLastItemMargin:true,isComposite:true,combineErrors:true,labelConnector:", ",initComponent:function(){var g=[],b=this.items,e;for(var d=0,c=b.length;d")},sortErrors:function(){var a=this.items;this.fieldErrors.sort("ASC",function(g,d){var c=function(b){return function(i){return i.getName()==b}};var h=a.findIndexBy(c(g.field)),e=a.findIndexBy(c(d.field));return h1){var a=this.getBox(c);if(a){a.setValue(b);if(a.checked){this.eachItem(function(d){if(d!==a){d.setValue(false)}})}}}else{this.setValueForItem(c)}},setValueForItem:function(a){a=String(a).split(",")[0];this.eachItem(function(b){b.setValue(a==b.inputValue)})},fireChecked:function(){if(!this.checkTask){this.checkTask=new Ext.util.DelayedTask(this.bufferChecked,this)}this.checkTask.delay(10)},bufferChecked:function(){var a=null;this.eachItem(function(b){if(b.checked){a=b;return false}});this.fireEvent("change",this,a)},onDestroy:function(){if(this.checkTask){this.checkTask.cancel();this.checkTask=null}Ext.form.RadioGroup.superclass.onDestroy.call(this)}});Ext.reg("radiogroup",Ext.form.RadioGroup);Ext.form.Hidden=Ext.extend(Ext.form.Field,{inputType:"hidden",shouldLayout:false,onRender:function(){Ext.form.Hidden.superclass.onRender.apply(this,arguments)},initEvents:function(){this.originalValue=this.getValue()},setSize:Ext.emptyFn,setWidth:Ext.emptyFn,setHeight:Ext.emptyFn,setPosition:Ext.emptyFn,setPagePosition:Ext.emptyFn,markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn});Ext.reg("hidden",Ext.form.Hidden);Ext.form.BasicForm=Ext.extend(Ext.util.Observable,{constructor:function(b,a){Ext.apply(this,a);if(Ext.isString(this.paramOrder)){this.paramOrder=this.paramOrder.split(/[\s,|]/)}this.items=new Ext.util.MixedCollection(false,function(c){return c.getItemId()});this.addEvents("beforeaction","actionfailed","actioncomplete");if(b){this.initEl(b)}Ext.form.BasicForm.superclass.constructor.call(this)},timeout:30,paramOrder:undefined,paramsAsHash:false,waitTitle:"Please Wait...",activeAction:null,trackResetOnLoad:false,initEl:function(a){this.el=Ext.get(a);this.id=this.el.id||Ext.id();if(!this.standardSubmit){this.el.on("submit",this.onSubmit,this)}this.el.addClass("x-form")},getEl:function(){return this.el},onSubmit:function(a){a.stopEvent()},destroy:function(a){if(a!==true){this.items.each(function(b){Ext.destroy(b)});Ext.destroy(this.el)}this.items.clear();this.purgeListeners()},isValid:function(){var a=true;this.items.each(function(b){if(!b.validate()){a=false}});return a},isDirty:function(){var a=false;this.items.each(function(b){if(b.isDirty()){a=true;return false}});return a},doAction:function(b,a){if(Ext.isString(b)){b=new Ext.form.Action.ACTION_TYPES[b](this,a)}if(this.fireEvent("beforeaction",this,b)!==false){this.beforeAction(b);b.run.defer(100,b)}return this},submit:function(b){b=b||{};if(this.standardSubmit){var a=b.clientValidation===false||this.isValid();if(a){var c=this.el.dom;if(this.url&&Ext.isEmpty(c.action)){c.action=this.url}c.submit()}return a}var d=String.format("{0}submit",this.api?"direct":"");this.doAction(d,b);return this},load:function(a){var b=String.format("{0}load",this.api?"direct":"");this.doAction(b,a);return this},updateRecord:function(b){b.beginEdit();var a=b.fields,d,c;a.each(function(e){d=this.findField(e.name);if(d){c=d.getValue();if(Ext.type(c)!==false&&c.getGroupValue){c=c.getGroupValue()}else{if(d.eachItem){c=[];d.eachItem(function(g){c.push(g.getValue())})}}b.set(e.name,c)}},this);b.endEdit();return this},loadRecord:function(a){this.setValues(a.data);return this},beforeAction:function(a){this.items.each(function(c){if(c.isFormField&&c.syncValue){c.syncValue()}});var b=a.options;if(b.waitMsg){if(this.waitMsgTarget===true){this.el.mask(b.waitMsg,"x-mask-loading")}else{if(this.waitMsgTarget){this.waitMsgTarget=Ext.get(this.waitMsgTarget);this.waitMsgTarget.mask(b.waitMsg,"x-mask-loading")}else{Ext.MessageBox.wait(b.waitMsg,b.waitTitle||this.waitTitle)}}}},afterAction:function(a,c){this.activeAction=null;var b=a.options;if(b.waitMsg){if(this.waitMsgTarget===true){this.el.unmask()}else{if(this.waitMsgTarget){this.waitMsgTarget.unmask()}else{Ext.MessageBox.updateProgress(1);Ext.MessageBox.hide()}}}if(c){if(b.reset){this.reset()}Ext.callback(b.success,b.scope,[this,a]);this.fireEvent("actioncomplete",this,a)}else{Ext.callback(b.failure,b.scope,[this,a]);this.fireEvent("actionfailed",this,a)}},findField:function(c){var b=this.items.get(c);if(!Ext.isObject(b)){var a=function(d){if(d.isFormField){if(d.dataIndex==c||d.id==c||d.getName()==c){b=d;return false}else{if(d.isComposite){return d.items.each(a)}else{if(d instanceof Ext.form.CheckboxGroup&&d.rendered){return d.eachItem(a)}}}}};this.items.each(a)}return b||null},markInvalid:function(h){if(Ext.isArray(h)){for(var c=0,a=h.length;c':">"),c,"")}return d.join("")},createToolbar:function(e){var c=[];var a=Ext.QuickTips&&Ext.QuickTips.isEnabled();function d(j,h,i){return{itemId:j,cls:"x-btn-icon",iconCls:"x-edit-"+j,enableToggle:h!==false,scope:e,handler:i||e.relayBtnCmd,clickEvent:"mousedown",tooltip:a?e.buttonTips[j]||undefined:undefined,overflowText:e.buttonTips[j].title||undefined,tabIndex:-1}}if(this.enableFont&&!Ext.isSafari2){var g=new Ext.Toolbar.Item({autoEl:{tag:"select",cls:"x-font-select",html:this.createFontOptions()}});c.push(g,"-")}if(this.enableFormat){c.push(d("bold"),d("italic"),d("underline"))}if(this.enableFontSize){c.push("-",d("increasefontsize",false,this.adjustFont),d("decreasefontsize",false,this.adjustFont))}if(this.enableColors){c.push("-",{itemId:"forecolor",cls:"x-btn-icon",iconCls:"x-edit-forecolor",clickEvent:"mousedown",tooltip:a?e.buttonTips.forecolor||undefined:undefined,tabIndex:-1,menu:new Ext.menu.ColorMenu({allowReselect:true,focus:Ext.emptyFn,value:"000000",plain:true,listeners:{scope:this,select:function(i,h){this.execCmd("forecolor",Ext.isWebKit||Ext.isIE?"#"+h:h);this.deferFocus()}},clickEvent:"mousedown"})},{itemId:"backcolor",cls:"x-btn-icon",iconCls:"x-edit-backcolor",clickEvent:"mousedown",tooltip:a?e.buttonTips.backcolor||undefined:undefined,tabIndex:-1,menu:new Ext.menu.ColorMenu({focus:Ext.emptyFn,value:"FFFFFF",plain:true,allowReselect:true,listeners:{scope:this,select:function(i,h){if(Ext.isGecko){this.execCmd("useCSS",false);this.execCmd("hilitecolor",h);this.execCmd("useCSS",true);this.deferFocus()}else{this.execCmd(Ext.isOpera?"hilitecolor":"backcolor",Ext.isWebKit||Ext.isIE?"#"+h:h);this.deferFocus()}}},clickEvent:"mousedown"})})}if(this.enableAlignments){c.push("-",d("justifyleft"),d("justifycenter"),d("justifyright"))}if(!Ext.isSafari2){if(this.enableLinks){c.push("-",d("createlink",false,this.createLink))}if(this.enableLists){c.push("-",d("insertorderedlist"),d("insertunorderedlist"))}if(this.enableSourceEdit){c.push("-",d("sourceedit",true,function(h){this.toggleSourceEdit(!this.sourceEditMode)}))}}var b=new Ext.Toolbar({renderTo:this.wrap.dom.firstChild,items:c});if(g){this.fontSelect=g.el;this.mon(this.fontSelect,"change",function(){var h=this.fontSelect.dom.value;this.relayCmd("fontname",h);this.deferFocus()},this)}this.mon(b.el,"click",function(h){h.preventDefault()});this.tb=b;this.tb.doLayout()},onDisable:function(){this.wrap.mask();Ext.form.HtmlEditor.superclass.onDisable.call(this)},onEnable:function(){this.wrap.unmask();Ext.form.HtmlEditor.superclass.onEnable.call(this)},setReadOnly:function(b){Ext.form.HtmlEditor.superclass.setReadOnly.call(this,b);if(this.initialized){if(Ext.isIE){this.getEditorBody().contentEditable=!b}else{this.setDesignMode(!b)}var a=this.getEditorBody();if(a){a.style.cursor=this.readOnly?"default":"text"}this.disableItems(b)}},getDocMarkup:function(){var a=Ext.fly(this.iframe).getHeight()-this.iframePad*2;return String.format('',this.iframePad,a)},getEditorBody:function(){var a=this.getDoc();return a.body||a.documentElement},getDoc:function(){return Ext.isIE?this.getWin().document:(this.iframe.contentDocument||this.getWin().document)},getWin:function(){return Ext.isIE?this.iframe.contentWindow:window.frames[this.iframe.name]},onRender:function(b,a){Ext.form.HtmlEditor.superclass.onRender.call(this,b,a);this.el.dom.style.border="0 none";this.el.dom.setAttribute("tabIndex",-1);this.el.addClass("x-hidden");if(Ext.isIE){this.el.applyStyles("margin-top:-1px;margin-bottom:-1px;")}this.wrap=this.el.wrap({cls:"x-html-editor-wrap",cn:{cls:"x-html-editor-tb"}});this.createToolbar(this);this.disableItems(true);this.tb.doLayout();this.createIFrame();if(!this.width){var c=this.el.getSize();this.setSize(c.width,this.height||c.height)}this.resizeEl=this.positionEl=this.wrap},createIFrame:function(){var a=document.createElement("iframe");a.name=Ext.id();a.frameBorder="0";a.style.overflow="auto";a.src=Ext.SSL_SECURE_URL;this.wrap.dom.appendChild(a);this.iframe=a;this.monitorTask=Ext.TaskMgr.start({run:this.checkDesignMode,scope:this,interval:100})},initFrame:function(){Ext.TaskMgr.stop(this.monitorTask);var b=this.getDoc();this.win=this.getWin();b.open();b.write(this.getDocMarkup());b.close();var a={run:function(){var c=this.getDoc();if(c.body||c.readyState=="complete"){Ext.TaskMgr.stop(a);this.setDesignMode(true);this.initEditor.defer(10,this)}},interval:10,duration:10000,scope:this};Ext.TaskMgr.start(a)},checkDesignMode:function(){if(this.wrap&&this.wrap.dom.offsetWidth){var a=this.getDoc();if(!a){return}if(!a.editorInitialized||this.getDesignMode()!="on"){this.initFrame()}}},setDesignMode:function(b){var a=this.getDoc();if(a){if(this.readOnly){b=false}a.designMode=(/on|true/i).test(String(b).toLowerCase())?"on":"off"}},getDesignMode:function(){var a=this.getDoc();if(!a){return""}return String(a.designMode).toLowerCase()},disableItems:function(a){if(this.fontSelect){this.fontSelect.dom.disabled=a}this.tb.items.each(function(b){if(b.getItemId()!="sourceedit"){b.setDisabled(a)}})},onResize:function(b,c){Ext.form.HtmlEditor.superclass.onResize.apply(this,arguments);if(this.el&&this.iframe){if(Ext.isNumber(b)){var e=b-this.wrap.getFrameWidth("lr");this.el.setWidth(e);this.tb.setWidth(e);this.iframe.style.width=Math.max(e,0)+"px"}if(Ext.isNumber(c)){var a=c-this.wrap.getFrameWidth("tb")-this.tb.el.getHeight();this.el.setHeight(a);this.iframe.style.height=Math.max(a,0)+"px";var d=this.getEditorBody();if(d){d.style.height=Math.max((a-(this.iframePad*2)),0)+"px"}}}},toggleSourceEdit:function(b){var d,a;if(b===undefined){b=!this.sourceEditMode}this.sourceEditMode=b===true;var c=this.tb.getComponent("sourceedit");if(c.pressed!==this.sourceEditMode){c.toggle(this.sourceEditMode);if(!c.xtbHidden){return}}if(this.sourceEditMode){this.previousSize=this.getSize();d=Ext.get(this.iframe).getHeight();this.disableItems(true);this.syncValue();this.iframe.className="x-hidden";this.el.removeClass("x-hidden");this.el.dom.removeAttribute("tabIndex");this.el.focus();this.el.dom.style.height=d+"px"}else{a=parseInt(this.el.dom.style.height,10);if(this.initialized){this.disableItems(this.readOnly)}this.pushValue();this.iframe.className="";this.el.addClass("x-hidden");this.el.dom.setAttribute("tabIndex",-1);this.deferFocus();this.setSize(this.previousSize);delete this.previousSize;this.iframe.style.height=a+"px"}this.fireEvent("editmodechange",this,this.sourceEditMode)},createLink:function(){var a=prompt(this.createLinkText,this.defaultLinkValue);if(a&&a!="http://"){this.relayCmd("createlink",a)}},initEvents:function(){this.originalValue=this.getValue()},markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn,setValue:function(a){Ext.form.HtmlEditor.superclass.setValue.call(this,a);this.pushValue();return this},cleanHtml:function(a){a=String(a);if(Ext.isWebKit){a=a.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi,"")}if(a.charCodeAt(0)==this.defaultValue.replace(/\D/g,"")){a=a.substring(1)}return a},syncValue:function(){if(this.initialized){var d=this.getEditorBody();var c=d.innerHTML;if(Ext.isWebKit){var b=d.getAttribute("style");var a=b.match(/text-align:(.*?);/i);if(a&&a[1]){c='
'+c+"
"}}c=this.cleanHtml(c);if(this.fireEvent("beforesync",this,c)!==false){this.el.dom.value=c;this.fireEvent("sync",this,c)}}},getValue:function(){this[this.sourceEditMode?"pushValue":"syncValue"]();return Ext.form.HtmlEditor.superclass.getValue.call(this)},pushValue:function(){if(this.initialized){var a=this.el.dom.value;if(!this.activated&&a.length<1){a=this.defaultValue}if(this.fireEvent("beforepush",this,a)!==false){this.getEditorBody().innerHTML=a;if(Ext.isGecko){this.setDesignMode(false);this.setDesignMode(true)}this.fireEvent("push",this,a)}}},deferFocus:function(){this.focus.defer(10,this)},focus:function(){if(this.win&&!this.sourceEditMode){this.win.focus()}else{this.el.focus()}},initEditor:function(){try{var c=this.getEditorBody(),a=this.el.getStyles("font-size","font-family","background-image","background-repeat","background-color","color"),g,b;a["background-attachment"]="fixed";c.bgProperties="fixed";Ext.DomHelper.applyStyles(c,a);g=this.getDoc();if(g){try{Ext.EventManager.removeAll(g)}catch(d){}}b=this.onEditorEvent.createDelegate(this);Ext.EventManager.on(g,{mousedown:b,dblclick:b,click:b,keyup:b,buffer:100});if(Ext.isGecko){Ext.EventManager.on(g,"keypress",this.applyCommand,this)}if(Ext.isIE||Ext.isWebKit||Ext.isOpera){Ext.EventManager.on(g,"keydown",this.fixKeys,this)}g.editorInitialized=true;this.initialized=true;this.pushValue();this.setReadOnly(this.readOnly);this.fireEvent("initialize",this)}catch(d){}},beforeDestroy:function(){if(this.monitorTask){Ext.TaskMgr.stop(this.monitorTask)}if(this.rendered){Ext.destroy(this.tb);var b=this.getDoc();if(b){try{Ext.EventManager.removeAll(b);for(var c in b){delete b[c]}}catch(a){}}if(this.wrap){this.wrap.dom.innerHTML="";this.wrap.remove()}}Ext.form.HtmlEditor.superclass.beforeDestroy.call(this)},onFirstFocus:function(){this.activated=true;this.disableItems(this.readOnly);if(Ext.isGecko){this.win.focus();var a=this.win.getSelection();if(!a.focusNode||a.focusNode.nodeType!=3){var b=a.getRangeAt(0);b.selectNodeContents(this.getEditorBody());b.collapse(true);this.deferFocus()}try{this.execCmd("useCSS",true);this.execCmd("styleWithCSS",false)}catch(c){}}this.fireEvent("activate",this)},adjustFont:function(b){var d=b.getItemId()=="increasefontsize"?1:-1,c=this.getDoc(),a=parseInt(c.queryCommandValue("FontSize")||2,10);if((Ext.isSafari&&!Ext.isSafari2)||Ext.isChrome||Ext.isAir){if(a<=10){a=1+d}else{if(a<=13){a=2+d}else{if(a<=16){a=3+d}else{if(a<=18){a=4+d}else{if(a<=24){a=5+d}else{a=6+d}}}}}a=a.constrain(1,6)}else{if(Ext.isSafari){d*=2}a=Math.max(1,a+d)+(Ext.isSafari?"px":0)}this.execCmd("FontSize",a)},onEditorEvent:function(a){this.updateToolbar()},updateToolbar:function(){if(this.readOnly){return}if(!this.activated){this.onFirstFocus();return}var b=this.tb.items.map,c=this.getDoc();if(this.enableFont&&!Ext.isSafari2){var a=(c.queryCommandValue("FontName")||this.defaultFont).toLowerCase();if(a!=this.fontSelect.dom.value){this.fontSelect.dom.value=a}}if(this.enableFormat){b.bold.toggle(c.queryCommandState("bold"));b.italic.toggle(c.queryCommandState("italic"));b.underline.toggle(c.queryCommandState("underline"))}if(this.enableAlignments){b.justifyleft.toggle(c.queryCommandState("justifyleft"));b.justifycenter.toggle(c.queryCommandState("justifycenter"));b.justifyright.toggle(c.queryCommandState("justifyright"))}if(!Ext.isSafari2&&this.enableLists){b.insertorderedlist.toggle(c.queryCommandState("insertorderedlist"));b.insertunorderedlist.toggle(c.queryCommandState("insertunorderedlist"))}Ext.menu.MenuMgr.hideAll();this.syncValue()},relayBtnCmd:function(a){this.relayCmd(a.getItemId())},relayCmd:function(b,a){(function(){this.focus();this.execCmd(b,a);this.updateToolbar()}).defer(10,this)},execCmd:function(b,a){var c=this.getDoc();c.execCommand(b,false,a===undefined?null:a);this.syncValue()},applyCommand:function(b){if(b.ctrlKey){var d=b.getCharCode(),a;if(d>0){d=String.fromCharCode(d);switch(d){case"b":a="bold";break;case"i":a="italic";break;case"u":a="underline";break}if(a){this.win.focus();this.execCmd(a);this.deferFocus();b.preventDefault()}}}},insertAtCursor:function(c){if(!this.activated){return}if(Ext.isIE){this.win.focus();var b=this.getDoc(),a=b.selection.createRange();if(a){a.pasteHTML(c);this.syncValue();this.deferFocus()}}else{this.win.focus();this.execCmd("InsertHTML",c);this.deferFocus()}},fixKeys:function(){if(Ext.isIE){return function(g){var a=g.getKey(),d=this.getDoc(),b;if(a==g.TAB){g.stopEvent();b=d.selection.createRange();if(b){b.collapse(true);b.pasteHTML("    ");this.deferFocus()}}else{if(a==g.ENTER){b=d.selection.createRange();if(b){var c=b.parentElement();if(!c||c.tagName.toLowerCase()!="li"){g.stopEvent();b.pasteHTML("
");b.collapse(false);b.select()}}}}}}else{if(Ext.isOpera){return function(b){var a=b.getKey();if(a==b.TAB){b.stopEvent();this.win.focus();this.execCmd("InsertHTML","    ");this.deferFocus()}}}else{if(Ext.isWebKit){return function(b){var a=b.getKey();if(a==b.TAB){b.stopEvent();this.execCmd("InsertText","\t");this.deferFocus()}else{if(a==b.ENTER){b.stopEvent();this.execCmd("InsertHtml","

");this.deferFocus()}}}}}}}(),getToolbar:function(){return this.tb},buttonTips:{bold:{title:"Bold (Ctrl+B)",text:"Make the selected text bold.",cls:"x-html-editor-tip"},italic:{title:"Italic (Ctrl+I)",text:"Make the selected text italic.",cls:"x-html-editor-tip"},underline:{title:"Underline (Ctrl+U)",text:"Underline the selected text.",cls:"x-html-editor-tip"},increasefontsize:{title:"Grow Text",text:"Increase the font size.",cls:"x-html-editor-tip"},decreasefontsize:{title:"Shrink Text",text:"Decrease the font size.",cls:"x-html-editor-tip"},backcolor:{title:"Text Highlight Color",text:"Change the background color of the selected text.",cls:"x-html-editor-tip"},forecolor:{title:"Font Color",text:"Change the color of the selected text.",cls:"x-html-editor-tip"},justifyleft:{title:"Align Text Left",text:"Align text to the left.",cls:"x-html-editor-tip"},justifycenter:{title:"Center Text",text:"Center text in the editor.",cls:"x-html-editor-tip"},justifyright:{title:"Align Text Right",text:"Align text to the right.",cls:"x-html-editor-tip"},insertunorderedlist:{title:"Bullet List",text:"Start a bulleted list.",cls:"x-html-editor-tip"},insertorderedlist:{title:"Numbered List",text:"Start a numbered list.",cls:"x-html-editor-tip"},createlink:{title:"Hyperlink",text:"Make the selected text a hyperlink.",cls:"x-html-editor-tip"},sourceedit:{title:"Source Edit",text:"Switch to source editing mode.",cls:"x-html-editor-tip"}}});Ext.reg("htmleditor",Ext.form.HtmlEditor);Ext.form.TimeField=Ext.extend(Ext.form.ComboBox,{minValue:undefined,maxValue:undefined,minText:"The time in this field must be equal to or after {0}",maxText:"The time in this field must be equal to or before {0}",invalidText:"{0} is not a valid time",format:"g:i A",altFormats:"g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",increment:15,mode:"local",triggerAction:"all",typeAhead:false,initDate:"1/1/2008",initDateFormat:"j/n/Y",initComponent:function(){if(Ext.isDefined(this.minValue)){this.setMinValue(this.minValue,true)}if(Ext.isDefined(this.maxValue)){this.setMaxValue(this.maxValue,true)}if(!this.store){this.generateStore(true)}Ext.form.TimeField.superclass.initComponent.call(this)},setMinValue:function(b,a){this.setLimit(b,true,a);return this},setMaxValue:function(b,a){this.setLimit(b,false,a);return this},generateStore:function(b){var c=this.minValue||new Date(this.initDate).clearTime(),a=this.maxValue||new Date(this.initDate).clearTime().add("mi",(24*60)-1),d=[];while(c<=a){d.push(c.dateFormat(this.format));c=c.add("mi",this.increment)}this.bindStore(d,b)},setLimit:function(b,g,a){var e;if(Ext.isString(b)){e=this.parseDate(b)}else{if(Ext.isDate(b)){e=b}}if(e){var c=new Date(this.initDate).clearTime();c.setHours(e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds());this[g?"minValue":"maxValue"]=c;if(!a){this.generateStore()}}},getValue:function(){var a=Ext.form.TimeField.superclass.getValue.call(this);return this.formatDate(this.parseDate(a))||""},setValue:function(a){return Ext.form.TimeField.superclass.setValue.call(this,this.formatDate(this.parseDate(a)))},validateValue:Ext.form.DateField.prototype.validateValue,formatDate:Ext.form.DateField.prototype.formatDate,parseDate:function(h){if(!h||Ext.isDate(h)){return h}var j=this.initDate+" ",g=this.initDateFormat+" ",b=Date.parseDate(j+h,g+this.format),c=this.altFormats;if(!b&&c){if(!this.altFormatsArray){this.altFormatsArray=c.split("|")}for(var e=0,d=this.altFormatsArray,a=d.length;e=0){if(!d){c=g-1}d=false;while(c>=0){if(e.call(j||this,k,c,i)===true){return[k,c]}c--}k--}}else{if(c>=g){k++;d=false}while(k','
','
','
','
{header}
',"
",'
',"
",'
','
{body}
','',"
","
",'
 
','
 
',""),headerTpl:new Ext.Template('',"",'{cells}',"","
"),bodyTpl:new Ext.Template("{rows}"),cellTpl:new Ext.Template('','
{value}
',""),initTemplates:function(){var c=this.templates||{},d,b,g=new Ext.Template('','
',this.grid.enableHdMenu?'':"","{value}",'',"
",""),a=['','','
{body}
',"",""].join(""),e=['',"","{cells}",this.enableRowBody?a:"","","
"].join("");Ext.applyIf(c,{hcell:g,cell:this.cellTpl,body:this.bodyTpl,header:this.headerTpl,master:this.masterTpl,row:new Ext.Template('
'+e+"
"),rowInner:new Ext.Template(e)});for(b in c){d=c[b];if(d&&Ext.isFunction(d.compile)&&!d.compiled){d.disableFormats=true;d.compile()}}this.templates=c;this.colRe=new RegExp("x-grid3-td-([^\\s]+)","")},fly:function(a){if(!this._flyweight){this._flyweight=new Ext.Element.Flyweight(document.body)}this._flyweight.dom=a;return this._flyweight},getEditorParent:function(){return this.scroller.dom},initElements:function(){var b=Ext.Element,d=Ext.get(this.grid.getGridEl().dom.firstChild),e=new b(d.child("div.x-grid3-viewport")),c=new b(e.child("div.x-grid3-header")),a=new b(e.child("div.x-grid3-scroller"));if(this.grid.hideHeaders){c.setDisplayed(false)}if(this.forceFit){a.setStyle("overflow-x","hidden")}Ext.apply(this,{el:d,mainWrap:e,scroller:a,mainHd:c,innerHd:c.child("div.x-grid3-header-inner").dom,mainBody:new b(b.fly(a).child("div.x-grid3-body")),focusEl:new b(b.fly(a).child("a")),resizeMarker:new b(d.child("div.x-grid3-resize-marker")),resizeProxy:new b(d.child("div.x-grid3-resize-proxy"))});this.focusEl.swallowEvent("click",true)},getRows:function(){return this.hasRows()?this.mainBody.dom.childNodes:[]},findCell:function(a){if(!a){return false}return this.fly(a).findParent(this.cellSelector,this.cellSelectorDepth)},findCellIndex:function(d,c){var b=this.findCell(d),a;if(b){a=this.fly(b).hasClass(c);if(!c||a){return this.getCellIndex(b)}}return false},getCellIndex:function(b){if(b){var a=b.className.match(this.colRe);if(a&&a[1]){return this.cm.getIndexById(a[1])}}return false},findHeaderCell:function(b){var a=this.findCell(b);return a&&this.fly(a).hasClass(this.hdCls)?a:null},findHeaderIndex:function(a){return this.findCellIndex(a,this.hdCls)},findRow:function(a){if(!a){return false}return this.fly(a).findParent(this.rowSelector,this.rowSelectorDepth)},findRowIndex:function(a){var b=this.findRow(a);return b?b.rowIndex:false},findRowBody:function(a){if(!a){return false}return this.fly(a).findParent(this.rowBodySelector,this.rowBodySelectorDepth)},getRow:function(a){return this.getRows()[a]},getCell:function(b,a){return Ext.fly(this.getRow(b)).query(this.cellSelector)[a]},getHeaderCell:function(a){return this.mainHd.dom.getElementsByTagName("td")[a]},addRowClass:function(b,a){var c=this.getRow(b);if(c){this.fly(c).addClass(a)}},removeRowClass:function(c,a){var b=this.getRow(c);if(b){this.fly(b).removeClass(a)}},removeRow:function(a){Ext.removeNode(this.getRow(a));this.syncFocusEl(a)},removeRows:function(c,a){var b=this.mainBody.dom,d;for(d=c;d<=a;d++){Ext.removeNode(b.childNodes[c])}this.syncFocusEl(c)},getScrollState:function(){var a=this.scroller.dom;return{left:a.scrollLeft,top:a.scrollTop}},restoreScroll:function(a){var b=this.scroller.dom;b.scrollLeft=a.left;b.scrollTop=a.top},scrollToTop:function(){var a=this.scroller.dom;a.scrollTop=0;a.scrollLeft=0},syncScroll:function(){this.syncHeaderScroll();var a=this.scroller.dom;this.grid.fireEvent("bodyscroll",a.scrollLeft,a.scrollTop)},syncHeaderScroll:function(){var a=this.innerHd,b=this.scroller.dom.scrollLeft;a.scrollLeft=b;a.scrollLeft=b},updateSortIcon:function(d,c){var a=this.sortClasses,b=a[c=="DESC"?1:0],e=this.mainHd.select("td").removeClass(a);e.item(d).addClass(b)},updateAllColumnWidths:function(){var e=this.getTotalWidth(),k=this.cm.getColumnCount(),m=this.getRows(),g=m.length,b=[],l,a,h,d,c;for(d=0;d=this.ds.getCount()){return null}d=(d!==undefined?d:0);var c=this.getRow(h),b=this.cm,e=b.getColumnCount(),a;if(!(g===false&&d===0)){while(dm){n.scrollTop=q-a}}if(e!==false){var l=parseInt(h.offsetLeft,10),j=l+h.offsetWidth,i=parseInt(n.scrollLeft,10),b=i+n.clientWidth;if(lb){n.scrollLeft=j-n.clientWidth}}}return this.getResolvedXY(r)},insertRows:function(a,i,e,h){var d=a.getCount()-1;if(!h&&i===0&&e>=d){this.fireEvent("beforerowsinserted",this,i,e);this.refresh();this.fireEvent("rowsinserted",this,i,e)}else{if(!h){this.fireEvent("beforerowsinserted",this,i,e)}var b=this.renderRows(i,e),g=this.getRow(i);if(g){if(i===0){Ext.fly(this.getRow(0)).removeClass(this.firstRowCls)}Ext.DomHelper.insertHtml("beforeBegin",g,b)}else{var c=this.getRow(d-1);if(c){Ext.fly(c).removeClass(this.lastRowCls)}Ext.DomHelper.insertHtml("beforeEnd",this.mainBody.dom,b)}if(!h){this.processRows(i);this.fireEvent("rowsinserted",this,i,e)}else{if(i===0||i>=d){Ext.fly(this.getRow(i)).addClass(i===0?this.firstRowCls:this.lastRowCls)}}}this.syncFocusEl(i)},deleteRows:function(a,c,b){if(a.getRowCount()<1){this.refresh()}else{this.fireEvent("beforerowsdeleted",this,c,b);this.removeRows(c,b);this.processRows(c);this.fireEvent("rowsdeleted",this,c,b)}},getColumnStyle:function(b,d){var a=this.cm,g=a.config,c=d?"":g[b].css||"",e=g[b].align;c+=String.format("width: {0};",this.getColumnWidth(b));if(a.isHidden(b)){c+="display: none; "}if(e){c+=String.format("text-align: {0};",e)}return c},getColumnWidth:function(b){var c=this.cm.getColumnWidth(b),a=this.borderWidth;if(Ext.isNumber(c)){if(Ext.isBorderBox||(Ext.isWebKit&&!Ext.isSafari2)){return c+"px"}else{return Math.max(c-a,0)+"px"}}else{return c}},getTotalWidth:function(){return this.cm.getTotalWidth()+"px"},fitColumns:function(g,j,h){var a=this.grid,l=this.cm,s=l.getTotalWidth(false),q=this.getGridInnerWidth(),r=q-s,c=[],o=0,n=0,u,d,p;if(q<20||r===0){return false}var e=l.getColumnCount(true),m=l.getColumnCount(false),b=e-(Ext.isNumber(h)?1:0);if(b===0){b=1;h=undefined}for(p=0;pq){var t=(b==e)?o:h,k=Math.max(1,l.getColumnWidth(t)-(s-q));l.setColumnWidth(t,k,true)}if(g!==true){this.updateAllColumnWidths()}return true},autoExpand:function(k){var a=this.grid,i=this.cm,e=this.getGridInnerWidth(),c=i.getTotalWidth(false),g=a.autoExpandColumn;if(!this.userResized&&g){if(e!=c){var j=i.getIndexById(g),b=i.getColumnWidth(j),h=e-c+b,d=Math.min(Math.max(h,a.autoExpandMin),a.autoExpandMax);if(b!=d){i.setColumnWidth(j,d,true);if(k!==true){this.updateColumnWidth(j,d)}}}}},getGridInnerWidth:function(){return this.grid.getGridEl().getWidth(true)-this.getScrollOffset()},getColumnData:function(){var e=[],c=this.cm,g=c.getColumnCount(),a=this.ds.fields,d,b;for(d=0;d'+this.emptyText+"")}},updateHeaderSortState:function(){var b=this.ds.getSortState();if(!b){return}if(!this.sortState||(this.sortState.field!=b.field||this.sortState.direction!=b.direction)){this.grid.fireEvent("sortchange",this.grid,b)}this.sortState=b;var c=this.cm.findColumnIndex(b.field);if(c!=-1){var a=b.direction;this.updateSortIcon(c,a)}},clearHeaderSortState:function(){if(!this.sortState){return}this.grid.fireEvent("sortchange",this.grid,null);this.mainHd.select("td").removeClass(this.sortClasses);delete this.sortState},destroy:function(){var j=this,a=j.grid,d=a.getGridEl(),i=j.dragZone,g=j.splitZone,h=j.columnDrag,e=j.columnDrop,k=j.scrollToTopTask,c,b;if(k&&k.cancel){k.cancel()}Ext.destroyMembers(j,"colMenu","hmenu");j.initData(null,null);j.purgeListeners();Ext.fly(j.innerHd).un("click",j.handleHdDown,j);if(a.enableColumnMove){c=h.dragData;b=h.proxy;Ext.destroy(h.el,b.ghost,b.el,e.el,e.proxyTop,e.proxyBottom,c.ddel,c.header);if(b.anim){Ext.destroy(b.anim)}delete b.ghost;delete c.ddel;delete c.header;h.destroy();delete Ext.dd.DDM.locationCache[h.id];delete h._domRef;delete e.proxyTop;delete e.proxyBottom;e.destroy();delete Ext.dd.DDM.locationCache["gridHeader"+d.id];delete e._domRef;delete Ext.dd.DDM.ids[e.ddGroup]}if(g){g.destroy();delete g._domRef;delete Ext.dd.DDM.ids["gridSplitters"+d.id]}Ext.fly(j.innerHd).removeAllListeners();Ext.removeNode(j.innerHd);delete j.innerHd;Ext.destroy(j.el,j.mainWrap,j.mainHd,j.scroller,j.mainBody,j.focusEl,j.resizeMarker,j.resizeProxy,j.activeHdBtn,j._flyweight,i,g);delete a.container;if(i){i.destroy()}Ext.dd.DDM.currentTarget=null;delete Ext.dd.DDM.locationCache[d.id];Ext.EventManager.removeResizeListener(j.onWindowResize,j)},onDenyColumnHide:function(){},render:function(){if(this.autoFill){var a=this.grid.ownerCt;if(a&&a.getLayout()){a.on("afterlayout",function(){this.fitColumns(true,true);this.updateHeaders();this.updateHeaderSortState()},this,{single:true})}}else{if(this.forceFit){this.fitColumns(true,false)}else{if(this.grid.autoExpandColumn){this.autoExpand(true)}}}this.grid.getGridEl().dom.innerHTML=this.renderUI();this.afterRenderUI()},initData:function(a,e){var b=this;if(b.ds){var d=b.ds;d.un("add",b.onAdd,b);d.un("load",b.onLoad,b);d.un("clear",b.onClear,b);d.un("remove",b.onRemove,b);d.un("update",b.onUpdate,b);d.un("datachanged",b.onDataChange,b);if(d!==a&&d.autoDestroy){d.destroy()}}if(a){a.on({scope:b,load:b.onLoad,add:b.onAdd,remove:b.onRemove,update:b.onUpdate,clear:b.onClear,datachanged:b.onDataChange})}if(b.cm){var c=b.cm;c.un("configchange",b.onColConfigChange,b);c.un("widthchange",b.onColWidthChange,b);c.un("headerchange",b.onHeaderChange,b);c.un("hiddenchange",b.onHiddenChange,b);c.un("columnmoved",b.onColumnMove,b)}if(e){delete b.lastViewWidth;e.on({scope:b,configchange:b.onColConfigChange,widthchange:b.onColWidthChange,headerchange:b.onHeaderChange,hiddenchange:b.onHiddenChange,columnmoved:b.onColumnMove})}b.ds=a;b.cm=e},onDataChange:function(){this.refresh(true);this.updateHeaderSortState();this.syncFocusEl(0)},onClear:function(){this.refresh();this.syncFocusEl(0)},onUpdate:function(b,a){this.refreshRow(a)},onAdd:function(b,a,c){this.insertRows(b,c,c+(a.length-1))},onRemove:function(b,a,c,d){if(d!==true){this.fireEvent("beforerowremoved",this,c,a)}this.removeRow(c);if(d!==true){this.processRows(c);this.applyEmptyText();this.fireEvent("rowremoved",this,c,a)}},onLoad:function(){if(Ext.isGecko){if(!this.scrollToTopTask){this.scrollToTopTask=new Ext.util.DelayedTask(this.scrollToTop,this)}this.scrollToTopTask.delay(1)}else{this.scrollToTop()}},onColWidthChange:function(a,b,c){this.updateColumnWidth(b,c)},onHeaderChange:function(a,b,c){this.updateHeaders()},onHiddenChange:function(a,b,c){this.updateColumnHidden(b,c)},onColumnMove:function(a,c,b){this.indexMap=null;this.refresh(true);this.restoreScroll(this.getScrollState());this.afterMove(b);this.grid.fireEvent("columnmove",c,b)},onColConfigChange:function(){delete this.lastViewWidth;this.indexMap=null;this.refresh(true)},initUI:function(a){a.on("headerclick",this.onHeaderClick,this)},initEvents:Ext.emptyFn,onHeaderClick:function(b,a){if(this.headersDisabled||!this.cm.isSortable(a)){return}b.stopEditing(true);b.store.sort(this.cm.getDataIndex(a))},onRowOver:function(b,a){var c=this.findRowIndex(a);if(c!==false){this.addRowClass(c,this.rowOverCls)}},onRowOut:function(b,a){var c=this.findRowIndex(a);if(c!==false&&!b.within(this.getRow(c),true)){this.removeRowClass(c,this.rowOverCls)}},onRowSelect:function(a){this.addRowClass(a,this.selectedRowClass)},onRowDeselect:function(a){this.removeRowClass(a,this.selectedRowClass)},onCellSelect:function(c,b){var a=this.getCell(c,b);if(a){this.fly(a).addClass("x-grid3-cell-selected")}},onCellDeselect:function(c,b){var a=this.getCell(c,b);if(a){this.fly(a).removeClass("x-grid3-cell-selected")}},handleWheel:function(a){a.stopPropagation()},onColumnSplitterMoved:function(a,b){this.userResized=true;this.grid.colModel.setColumnWidth(a,b,true);if(this.forceFit){this.fitColumns(true,false,a);this.updateAllColumnWidths()}else{this.updateColumnWidth(a,b);this.syncHeaderScroll()}this.grid.fireEvent("columnresize",a,b)},beforeColMenuShow:function(){var b=this.cm,d=b.getColumnCount(),a=this.colMenu,c;a.removeAll();for(c=0;c0){if(!this.cm.isHidden(a-1)){return a}a--}return undefined},handleHdOver:function(c,b){var d=this.findHeaderCell(b);if(d&&!this.headersDisabled){var a=this.fly(d);this.activeHdRef=b;this.activeHdIndex=this.getCellIndex(d);this.activeHdRegion=a.getRegion();if(!this.isMenuDisabled(this.activeHdIndex,a)){a.addClass("x-grid3-hd-over");this.activeHdBtn=a.child(".x-grid3-hd-btn");if(this.activeHdBtn){this.activeHdBtn.dom.style.height=(d.firstChild.offsetHeight-1)+"px"}}}},handleHdOut:function(b,a){var c=this.findHeaderCell(a);if(c&&(!Ext.isIE||!b.within(c,true))){this.activeHdRef=null;this.fly(c).removeClass("x-grid3-hd-over");c.style.cursor=""}},isMenuDisabled:function(a,b){return this.cm.isMenuDisabled(a)},hasRows:function(){var a=this.mainBody.dom.firstChild;return a&&a.nodeType==1&&a.className!="x-grid-empty"},isHideableColumn:function(a){return !a.hidden},bind:function(a,b){this.initData(a,b)}});Ext.grid.GridView.SplitDragZone=Ext.extend(Ext.dd.DDProxy,{constructor:function(a,b){this.grid=a;this.view=a.getView();this.marker=this.view.resizeMarker;this.proxy=this.view.resizeProxy;Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this,b,"gridSplitters"+this.grid.getGridEl().id,{dragElId:Ext.id(this.proxy.dom),resizeFrame:false});this.scroll=false;this.hw=this.view.splitHandleWidth||5},b4StartDrag:function(a,e){this.dragHeadersDisabled=this.view.headersDisabled;this.view.headersDisabled=true;var d=this.view.mainWrap.getHeight();this.marker.setHeight(d);this.marker.show();this.marker.alignTo(this.view.getHeaderCell(this.cellIndex),"tl-tl",[-2,0]);this.proxy.setHeight(d);var b=this.cm.getColumnWidth(this.cellIndex),c=Math.max(b-this.grid.minColumnWidth,0);this.resetConstraints();this.setXConstraint(c,1000);this.setYConstraint(0,0);this.minX=a-c;this.maxX=a+1000;this.startPos=a;Ext.dd.DDProxy.prototype.b4StartDrag.call(this,a,e)},allowHeaderDrag:function(a){return true},handleMouseDown:function(a){var h=this.view.findHeaderCell(a.getTarget());if(h&&this.allowHeaderDrag(a)){var k=this.view.fly(h).getXY(),c=k[0],i=a.getXY(),b=i[0],g=h.offsetWidth,d=false;if((b-c)<=this.hw){d=-1}else{if((c+g)-b<=this.hw){d=0}}if(d!==false){this.cm=this.grid.colModel;var j=this.view.getCellIndex(h);if(d==-1){if(j+d<0){return}while(this.cm.isHidden(j+d)){--d;if(j+d<0){return}}}this.cellIndex=j+d;this.split=h.dom;if(this.cm.isResizable(this.cellIndex)&&!this.cm.isFixed(this.cellIndex)){Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this,arguments)}}else{if(this.view.columnDrag){this.view.columnDrag.callHandleMouseDown(a)}}}},endDrag:function(g){this.marker.hide();var a=this.view,c=Math.max(this.minX,g.getPageX()),d=c-this.startPos,b=this.dragHeadersDisabled;a.onColumnSplitterMoved(this.cellIndex,this.cm.getColumnWidth(this.cellIndex)+d);setTimeout(function(){a.headersDisabled=b},50)},autoOffset:function(){this.setDelta(0,0)}});Ext.grid.PivotGridView=Ext.extend(Ext.grid.GridView,{colHeaderCellCls:"grid-hd-group-cell",title:"",getColumnHeaders:function(){return this.grid.topAxis.buildHeaders()},getRowHeaders:function(){return this.grid.leftAxis.buildHeaders()},renderRows:function(a,t){var b=this.grid,o=b.extractData(),p=o.length,g=this.templates,s=b.renderer,h=typeof s=="function",w=this.getCellCls,n=typeof w=="function",d=g.cell,x=g.row,k=[],q={},c="width:"+this.getGridInnerWidth()+"px;",l,r,e,v,m;a=a||0;t=Ext.isDefined(t)?t:p-1;for(v=0;v','
','
','
{title}
','
','
',"
",'
',"
",'
','
','
{body}
','',"
","
",'
 
','
 
',""),initTemplates:function(){Ext.grid.PivotGridView.superclass.initTemplates.apply(this,arguments);var a=this.templates||{};if(!a.gcell){a.gcell=new Ext.XTemplate('','
',this.grid.enableHdMenu?'':"","{value}","
","")}this.templates=a;this.hrowRe=new RegExp("ux-grid-hd-group-row-(\\d+)","")},initElements:function(){Ext.grid.PivotGridView.superclass.initElements.apply(this,arguments);this.rowHeadersEl=new Ext.Element(this.scroller.child("div.x-grid3-row-headers"));this.headerTitleEl=new Ext.Element(this.mainHd.child("div.x-grid3-header-title"))},getGridInnerWidth:function(){var a=Ext.grid.PivotGridView.superclass.getGridInnerWidth.apply(this,arguments);return a-this.getTotalRowHeaderWidth()},getTotalRowHeaderWidth:function(){var d=this.getRowHeaders(),c=d.length,b=0,a;for(a=0;a0&&d>0){h=h||o.data[a[g-1].dataIndex]!=l[d-1].data[a[g-1].dataIndex]}if(h){s.push({header:q,span:p,start:b});b+=p;p=0}if(k){s.push({header:n,span:p+1,start:b});b+=p;p=0}q=n;p++}c.push({items:s,width:e.width||this.defaultHeaderWidth});q=undefined}return c}});Ext.grid.HeaderDragZone=Ext.extend(Ext.dd.DragZone,{maxDragWidth:120,constructor:function(a,c,b){this.grid=a;this.view=a.getView();this.ddGroup="gridHeader"+this.grid.getGridEl().id;Ext.grid.HeaderDragZone.superclass.constructor.call(this,c);if(b){this.setHandleElId(Ext.id(c));this.setOuterHandleElId(Ext.id(b))}this.scroll=false},getDragData:function(c){var a=Ext.lib.Event.getTarget(c),b=this.view.findHeaderCell(a);if(b){return{ddel:b.firstChild,header:b}}return false},onInitDrag:function(a){this.dragHeadersDisabled=this.view.headersDisabled;this.view.headersDisabled=true;var b=this.dragData.ddel.cloneNode(true);b.id=Ext.id();b.style.width=Math.min(this.dragData.header.offsetWidth,this.maxDragWidth)+"px";this.proxy.update(b);return true},afterValidDrop:function(){this.completeDrop()},afterInvalidDrop:function(){this.completeDrop()},completeDrop:function(){var a=this.view,b=this.dragHeadersDisabled;setTimeout(function(){a.headersDisabled=b},50)}});Ext.grid.HeaderDropZone=Ext.extend(Ext.dd.DropZone,{proxyOffsets:[-4,-9],fly:Ext.Element.fly,constructor:function(a,c,b){this.grid=a;this.view=a.getView();this.proxyTop=Ext.DomHelper.append(document.body,{cls:"col-move-top",html:" "},true);this.proxyBottom=Ext.DomHelper.append(document.body,{cls:"col-move-bottom",html:" "},true);this.proxyTop.hide=this.proxyBottom.hide=function(){this.setLeftTop(-100,-100);this.setStyle("visibility","hidden")};this.ddGroup="gridHeader"+this.grid.getGridEl().id;Ext.grid.HeaderDropZone.superclass.constructor.call(this,a.getGridEl().dom)},getTargetFromEvent:function(c){var a=Ext.lib.Event.getTarget(c),b=this.view.findCellIndex(a);if(b!==false){return this.view.getHeaderCell(b)}},nextVisible:function(c){var b=this.view,a=this.grid.colModel;c=c.nextSibling;while(c){if(!a.isHidden(b.getCellIndex(c))){return c}c=c.nextSibling}return null},prevVisible:function(c){var b=this.view,a=this.grid.colModel;c=c.prevSibling;while(c){if(!a.isHidden(b.getCellIndex(c))){return c}c=c.prevSibling}return null},positionIndicator:function(d,k,j){var a=Ext.lib.Event.getPageX(j),g=Ext.lib.Dom.getRegion(k.firstChild),c,i,b=g.top+this.proxyOffsets[1];if((g.right-a)<=(g.right-g.left)/2){c=g.right+this.view.borderWidth;i="after"}else{c=g.left;i="before"}if(this.grid.colModel.isFixed(this.view.getCellIndex(k))){return false}c+=this.proxyOffsets[0];this.proxyTop.setLeftTop(c,b);this.proxyTop.show();if(!this.bottomOffset){this.bottomOffset=this.view.mainHd.getHeight()}this.proxyBottom.setLeftTop(c,b+this.proxyTop.dom.offsetHeight+this.bottomOffset);this.proxyBottom.show();return i},onNodeEnter:function(d,a,c,b){if(b.header!=d){this.positionIndicator(b.header,d,c)}},onNodeOver:function(g,b,d,c){var a=false;if(c.header!=g){a=this.positionIndicator(c.header,g,d)}if(!a){this.proxyTop.hide();this.proxyBottom.hide()}return a?this.dropAllowed:this.dropNotAllowed},onNodeOut:function(d,a,c,b){this.proxyTop.hide();this.proxyBottom.hide()},onNodeDrop:function(b,m,g,c){var d=c.header;if(d!=b){var k=this.grid.colModel,j=Ext.lib.Event.getPageX(g),a=Ext.lib.Dom.getRegion(b.firstChild),o=(a.right-j)<=((a.right-a.left)/2)?"after":"before",i=this.view.getCellIndex(d),l=this.view.getCellIndex(b);if(o=="after"){l++}if(i=0&&this.config[a].resizable!==false&&this.config[a].fixed!==true},setHidden:function(a,b){var d=this.config[a];if(d.hidden!==b){d.hidden=b;this.totalWidth=null;this.fireEvent("hiddenchange",this,a,b)}},setEditor:function(a,b){this.config[a].setEditor(b)},destroy:function(){var b=this.config.length,a=0;for(;a0},isSelected:function(a){var b=Ext.isNumber(a)?this.grid.store.getAt(a):a;return(b&&this.selections.key(b.id)?true:false)},isIdSelected:function(a){return(this.selections.key(a)?true:false)},handleMouseDown:function(d,i,h){if(h.button!==0||this.isLocked()){return}var a=this.grid.getView();if(h.shiftKey&&!this.singleSelect&&this.last!==false){var c=this.last;this.selectRange(c,i,h.ctrlKey);this.last=c;a.focusRow(i)}else{var b=this.isSelected(i);if(h.ctrlKey&&b){this.deselectRow(i)}else{if(!b||this.getCount()>1){this.selectRow(i,h.ctrlKey||h.shiftKey);a.focusRow(i)}}}},selectRows:function(c,d){if(!d){this.clearSelections()}for(var b=0,a=c.length;b=a;c--){this.selectRow(c,true)}}},deselectRange:function(c,b,a){if(this.isLocked()){return}for(var d=c;d<=b;d++){this.deselectRow(d,a)}},selectRow:function(b,d,a){if(this.isLocked()||(b<0||b>=this.grid.store.getCount())||(d&&this.isSelected(b))){return}var c=this.grid.store.getAt(b);if(c&&this.fireEvent("beforerowselect",this,b,d,c)!==false){if(!d||this.singleSelect){this.clearSelections()}this.selections.add(c);this.last=this.lastActive=b;if(!a){this.grid.getView().onRowSelect(b)}if(!this.silent){this.fireEvent("rowselect",this,b,c);this.fireEvent("selectionchange",this)}}},deselectRow:function(b,a){if(this.isLocked()){return}if(this.last==b){this.last=false}if(this.lastActive==b){this.lastActive=false}var c=this.grid.store.getAt(b);if(c){this.selections.remove(c);if(!a){this.grid.getView().onRowDeselect(b)}this.fireEvent("rowdeselect",this,b,c);this.fireEvent("selectionchange",this)}},acceptsNav:function(c,b,a){return !a.isHidden(b)&&a.isCellEditable(b,c)},onEditorKey:function(n,l){var d=l.getKey(),h,i=this.grid,p=i.lastEdit,j=i.activeEditor,b=l.shiftKey,o,p,a,m;if(d==l.TAB){l.stopEvent();j.completeEdit();if(b){h=i.walkCells(j.row,j.col-1,-1,this.acceptsNav,this)}else{h=i.walkCells(j.row,j.col+1,1,this.acceptsNav,this)}}else{if(d==l.ENTER){if(this.moveEditorOnEnter!==false){if(b){h=i.walkCells(p.row-1,p.col,-1,this.acceptsNav,this)}else{h=i.walkCells(p.row+1,p.col,1,this.acceptsNav,this)}}}}if(h){a=h[0];m=h[1];this.onEditorSelect(a,p.row);if(i.isEditor&&i.editing){o=i.activeEditor;if(o&&o.field.triggerBlur){o.field.triggerBlur()}}i.startEditing(a,m)}},onEditorSelect:function(b,a){if(a!=b){this.selectRow(b)}},destroy:function(){Ext.destroy(this.rowNav);this.rowNav=null;Ext.grid.RowSelectionModel.superclass.destroy.call(this)}});Ext.grid.Column=Ext.extend(Ext.util.Observable,{isColumn:true,constructor:function(b){Ext.apply(this,b);if(Ext.isString(this.renderer)){this.renderer=Ext.util.Format[this.renderer]}else{if(Ext.isObject(this.renderer)){this.scope=this.renderer.scope;this.renderer=this.renderer.fn}}if(!this.scope){this.scope=this}var a=this.editor;delete this.editor;this.setEditor(a);this.addEvents("click","contextmenu","dblclick","mousedown");Ext.grid.Column.superclass.constructor.call(this)},processEvent:function(b,d,c,g,a){return this.fireEvent(b,this,c,g,d)},destroy:function(){if(this.setEditor){this.setEditor(null)}this.purgeListeners()},renderer:function(a){return a},getEditor:function(a){return this.editable!==false?this.editor:null},setEditor:function(b){var a=this.editor;if(a){if(a.gridEditor){a.gridEditor.destroy();delete a.gridEditor}else{a.destroy()}}this.editor=null;if(b){if(!b.isXType){b=Ext.create(b,"textfield")}this.editor=b}},getCellEditor:function(b){var a=this.getEditor(b);if(a){if(!a.startEdit){if(!a.gridEditor){a.gridEditor=new Ext.grid.GridEditor(a)}a=a.gridEditor}}return a}});Ext.grid.BooleanColumn=Ext.extend(Ext.grid.Column,{trueText:"true",falseText:"false",undefinedText:" ",constructor:function(a){Ext.grid.BooleanColumn.superclass.constructor.call(this,a);var c=this.trueText,d=this.falseText,b=this.undefinedText;this.renderer=function(e){if(e===undefined){return b}if(!e||e==="false"){return d}return c}}});Ext.grid.NumberColumn=Ext.extend(Ext.grid.Column,{format:"0,000.00",constructor:function(a){Ext.grid.NumberColumn.superclass.constructor.call(this,a);this.renderer=Ext.util.Format.numberRenderer(this.format)}});Ext.grid.DateColumn=Ext.extend(Ext.grid.Column,{format:"m/d/Y",constructor:function(a){Ext.grid.DateColumn.superclass.constructor.call(this,a);this.renderer=Ext.util.Format.dateRenderer(this.format)}});Ext.grid.TemplateColumn=Ext.extend(Ext.grid.Column,{constructor:function(a){Ext.grid.TemplateColumn.superclass.constructor.call(this,a);var b=(!Ext.isPrimitive(this.tpl)&&this.tpl.compile)?this.tpl:new Ext.XTemplate(this.tpl);this.renderer=function(d,e,c){return b.apply(c.data)};this.tpl=b}});Ext.grid.ActionColumn=Ext.extend(Ext.grid.Column,{header:" ",actionIdRe:/x-action-col-(\d+)/,altText:"",constructor:function(b){var g=this,c=b.items||(g.items=[g]),a=c.length,d,e;Ext.grid.ActionColumn.superclass.constructor.call(g,b);g.renderer=function(h,i){h=Ext.isFunction(b.renderer)?b.renderer.apply(this,arguments)||"":"";i.css+=" x-action-col-cell";for(d=0;d"}return h}},destroy:function(){delete this.items;delete this.renderer;return Ext.grid.ActionColumn.superclass.destroy.apply(this,arguments)},processEvent:function(c,i,d,j,b){var a=i.getTarget().className.match(this.actionIdRe),h,g;if(a&&(h=this.items[parseInt(a[1],10)])){if(c=="click"){(g=h.handler||this.handler)&&g.call(h.scope||this.scope||this,d,j,b,h,i)}else{if((c=="mousedown")&&(h.stopSelection!==false)){return false}}}return Ext.grid.ActionColumn.superclass.processEvent.apply(this,arguments)}});Ext.grid.Column.types={gridcolumn:Ext.grid.Column,booleancolumn:Ext.grid.BooleanColumn,numbercolumn:Ext.grid.NumberColumn,datecolumn:Ext.grid.DateColumn,templatecolumn:Ext.grid.TemplateColumn,actioncolumn:Ext.grid.ActionColumn};Ext.grid.RowNumberer=Ext.extend(Object,{header:"",width:23,sortable:false,constructor:function(a){Ext.apply(this,a);if(this.rowspan){this.renderer=this.renderer.createDelegate(this)}},fixed:true,hideable:false,menuDisabled:true,dataIndex:"",id:"numberer",rowspan:undefined,renderer:function(b,c,a,d){if(this.rowspan){c.cellAttr='rowspan="'+this.rowspan+'"'}return d+1}});Ext.grid.CheckboxSelectionModel=Ext.extend(Ext.grid.RowSelectionModel,{header:'
 
',width:20,sortable:false,menuDisabled:true,fixed:true,hideable:false,dataIndex:"",id:"checker",isColumn:true,constructor:function(){Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this,arguments);if(this.checkOnly){this.handleMouseDown=Ext.emptyFn}},initEvents:function(){Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);this.grid.on("render",function(){Ext.fly(this.grid.getView().innerHd).on("mousedown",this.onHdMouseDown,this)},this)},processEvent:function(b,d,c,g,a){if(b=="mousedown"){this.onMouseDown(d,d.getTarget());return false}else{return Ext.grid.Column.prototype.processEvent.apply(this,arguments)}},onMouseDown:function(c,b){if(c.button===0&&b.className=="x-grid3-row-checker"){c.stopEvent();var d=c.getTarget(".x-grid3-row");if(d){var a=d.rowIndex;if(this.isSelected(a)){this.deselectRow(a)}else{this.selectRow(a,true);this.grid.getView().focusRow(a)}}}},onHdMouseDown:function(c,a){if(a.className=="x-grid3-hd-checker"){c.stopEvent();var b=Ext.fly(a.parentNode);var d=b.hasClass("x-grid3-hd-checker-on");if(d){b.removeClass("x-grid3-hd-checker-on");this.clearSelections()}else{b.addClass("x-grid3-hd-checker-on");this.selectAll()}}},renderer:function(b,c,a){return'
 
'},onEditorSelect:function(b,a){if(a!=b&&!this.checkOnly){this.selectRow(b)}}});Ext.grid.CellSelectionModel=Ext.extend(Ext.grid.AbstractSelectionModel,{constructor:function(a){Ext.apply(this,a);this.selection=null;this.addEvents("beforecellselect","cellselect","selectionchange");Ext.grid.CellSelectionModel.superclass.constructor.call(this)},initEvents:function(){this.grid.on("cellmousedown",this.handleMouseDown,this);this.grid.on(Ext.EventManager.getKeyEvent(),this.handleKeyDown,this);this.grid.getView().on({scope:this,refresh:this.onViewChange,rowupdated:this.onRowUpdated,beforerowremoved:this.clearSelections,beforerowsinserted:this.clearSelections});if(this.grid.isEditor){this.grid.on("beforeedit",this.beforeEdit,this)}},beforeEdit:function(a){this.select(a.row,a.column,false,true,a.record)},onRowUpdated:function(a,b,c){if(this.selection&&this.selection.record==c){a.onCellSelect(b,this.selection.cell[1])}},onViewChange:function(){this.clearSelections(true)},getSelectedCell:function(){return this.selection?this.selection.cell:null},clearSelections:function(b){var a=this.selection;if(a){if(b!==true){this.grid.view.onCellDeselect(a.cell[0],a.cell[1])}this.selection=null;this.fireEvent("selectionchange",this,null)}},hasSelection:function(){return this.selection?true:false},handleMouseDown:function(b,d,a,c){if(c.button!==0||this.isLocked()){return}this.select(d,a)},select:function(g,c,b,e,d){if(this.fireEvent("beforecellselect",this,g,c)!==false){this.clearSelections();d=d||this.grid.store.getAt(g);this.selection={record:d,cell:[g,c]};if(!b){var a=this.grid.getView();a.onCellSelect(g,c);if(e!==true){a.focusCell(g,c)}}this.fireEvent("cellselect",this,g,c);this.fireEvent("selectionchange",this,this.selection)}},isSelectable:function(c,b,a){return !a.isHidden(b)},onEditorKey:function(b,a){if(a.getKey()==a.TAB){this.handleKeyDown(a)}},handleKeyDown:function(j){if(!j.isNavKeyPress()){return}var d=j.getKey(),i=this.grid,p=this.selection,b=this,m=function(g,c,e){return i.walkCells(g,c,e,i.isEditor&&i.editing?b.acceptsNav:b.isSelectable,b)},o,h,a,l,n;switch(d){case j.ESC:case j.PAGE_UP:case j.PAGE_DOWN:break;default:j.stopEvent();break}if(!p){o=m(0,0,1);if(o){this.select(o[0],o[1])}return}o=p.cell;a=o[0];l=o[1];switch(d){case j.TAB:if(j.shiftKey){h=m(a,l-1,-1)}else{h=m(a,l+1,1)}break;case j.DOWN:h=m(a+1,l,1);break;case j.UP:h=m(a-1,l,-1);break;case j.RIGHT:h=m(a,l+1,1);break;case j.LEFT:h=m(a,l-1,-1);break;case j.ENTER:if(i.isEditor&&!i.editing){i.startEditing(a,l);return}break}if(h){a=h[0];l=h[1];this.select(a,l);if(i.isEditor&&i.editing){n=i.activeEditor;if(n&&n.field.triggerBlur){n.field.triggerBlur()}i.startEditing(a,l)}}},acceptsNav:function(c,b,a){return !a.isHidden(b)&&a.isCellEditable(b,c)}});Ext.grid.EditorGridPanel=Ext.extend(Ext.grid.GridPanel,{clicksToEdit:2,forceValidation:false,isEditor:true,detectEdit:false,autoEncode:false,trackMouseOver:false,initComponent:function(){Ext.grid.EditorGridPanel.superclass.initComponent.call(this);if(!this.selModel){this.selModel=new Ext.grid.CellSelectionModel()}this.activeEditor=null;this.addEvents("beforeedit","afteredit","validateedit")},initEvents:function(){Ext.grid.EditorGridPanel.superclass.initEvents.call(this);this.getGridEl().on("mousewheel",this.stopEditing.createDelegate(this,[true]),this);this.on("columnresize",this.stopEditing,this,[true]);if(this.clicksToEdit==1){this.on("cellclick",this.onCellDblClick,this)}else{var a=this.getView();if(this.clicksToEdit=="auto"&&a.mainBody){a.mainBody.on("mousedown",this.onAutoEditClick,this)}this.on("celldblclick",this.onCellDblClick,this)}},onResize:function(){Ext.grid.EditorGridPanel.superclass.onResize.apply(this,arguments);var a=this.activeEditor;if(this.editing&&a){a.realign(true)}},onCellDblClick:function(b,c,a){this.startEditing(c,a)},onAutoEditClick:function(c,b){if(c.button!==0){return}var g=this.view.findRowIndex(b),a=this.view.findCellIndex(b);if(g!==false&&a!==false){this.stopEditing();if(this.selModel.getSelectedCell){var d=this.selModel.getSelectedCell();if(d&&d[0]===g&&d[1]===a){this.startEditing(g,a)}}else{if(this.selModel.isSelected(g)){this.startEditing(g,a)}}}},onEditComplete:function(b,d,a){this.editing=false;this.lastActiveEditor=this.activeEditor;this.activeEditor=null;var c=b.record,h=this.colModel.getDataIndex(b.col);d=this.postEditValue(d,a,c,h);if(this.forceValidation===true||String(d)!==String(a)){var g={grid:this,record:c,field:h,originalValue:a,value:d,row:b.row,column:b.col,cancel:false};if(this.fireEvent("validateedit",g)!==false&&!g.cancel&&String(d)!==String(a)){c.set(h,g.value);delete g.cancel;this.fireEvent("afteredit",g)}}this.view.focusCell(b.row,b.col)},startEditing:function(i,c){this.stopEditing();if(this.colModel.isCellEditable(c,i)){this.view.ensureVisible(i,c,true);var d=this.store.getAt(i),h=this.colModel.getDataIndex(c),g={grid:this,record:d,field:h,value:d.data[h],row:i,column:c,cancel:false};if(this.fireEvent("beforeedit",g)!==false&&!g.cancel){this.editing=true;var b=this.colModel.getCellEditor(c,i);if(!b){return}if(!b.rendered){b.parentEl=this.view.getEditorParent(b);b.on({scope:this,render:{fn:function(e){e.field.focus(false,true)},single:true,scope:this},specialkey:function(k,j){this.getSelectionModel().onEditorKey(k,j)},complete:this.onEditComplete,canceledit:this.stopEditing.createDelegate(this,[true])})}Ext.apply(b,{row:i,col:c,record:d});this.lastEdit={row:i,col:c};this.activeEditor=b;b.selectSameEditor=(this.activeEditor==this.lastActiveEditor);var a=this.preEditValue(d,h);b.startEdit(this.view.getCell(i,c).firstChild,Ext.isDefined(a)?a:"");(function(){delete b.selectSameEditor}).defer(50)}}},preEditValue:function(a,c){var b=a.data[c];return this.autoEncode&&Ext.isString(b)?Ext.util.Format.htmlDecode(b):b},postEditValue:function(c,a,b,d){return this.autoEncode&&Ext.isString(c)?Ext.util.Format.htmlEncode(c):c},stopEditing:function(b){if(this.editing){var a=this.lastActiveEditor=this.activeEditor;if(a){a[b===true?"cancelEdit":"completeEdit"]();this.view.focusCell(a.row,a.col)}this.activeEditor=null}this.editing=false}});Ext.reg("editorgrid",Ext.grid.EditorGridPanel);Ext.grid.GridEditor=function(b,a){Ext.grid.GridEditor.superclass.constructor.call(this,b,a);b.monitorTab=false};Ext.extend(Ext.grid.GridEditor,Ext.Editor,{alignment:"tl-tl",autoSize:"width",hideEl:false,cls:"x-small-editor x-grid-editor",shim:false,shadow:false});Ext.grid.PropertyRecord=Ext.data.Record.create([{name:"name",type:"string"},"value"]);Ext.grid.PropertyStore=Ext.extend(Ext.util.Observable,{constructor:function(a,b){this.grid=a;this.store=new Ext.data.Store({recordType:Ext.grid.PropertyRecord});this.store.on("update",this.onUpdate,this);if(b){this.setSource(b)}Ext.grid.PropertyStore.superclass.constructor.call(this)},setSource:function(c){this.source=c;this.store.removeAll();var b=[];for(var a in c){if(this.isEditableValue(c[a])){b.push(new Ext.grid.PropertyRecord({name:a,value:c[a]},a))}}this.store.loadRecords({records:b},{},true)},onUpdate:function(e,a,d){if(d==Ext.data.Record.EDIT){var b=a.data.value;var c=a.modified.value;if(this.grid.fireEvent("beforepropertychange",this.source,a.id,b,c)!==false){this.source[a.id]=b;a.commit();this.grid.fireEvent("propertychange",this.source,a.id,b,c)}else{a.reject()}}},getProperty:function(a){return this.store.getAt(a)},isEditableValue:function(a){return Ext.isPrimitive(a)||Ext.isDate(a)},setValue:function(d,c,a){var b=this.getRec(d);if(b){b.set("value",c);this.source[d]=c}else{if(a){this.source[d]=c;b=new Ext.grid.PropertyRecord({name:d,value:c},d);this.store.add(b)}}},remove:function(b){var a=this.getRec(b);if(a){this.store.remove(a);delete this.source[b]}},getRec:function(a){return this.store.getById(a)},getSource:function(){return this.source}});Ext.grid.PropertyColumnModel=Ext.extend(Ext.grid.ColumnModel,{nameText:"Name",valueText:"Value",dateFormat:"m/j/Y",trueText:"true",falseText:"false",constructor:function(c,b){var d=Ext.grid,e=Ext.form;this.grid=c;d.PropertyColumnModel.superclass.constructor.call(this,[{header:this.nameText,width:50,sortable:true,dataIndex:"name",id:"name",menuDisabled:true},{header:this.valueText,width:50,resizable:false,dataIndex:"value",id:"value",menuDisabled:true}]);this.store=b;var a=new e.Field({autoCreate:{tag:"select",children:[{tag:"option",value:"true",html:this.trueText},{tag:"option",value:"false",html:this.falseText}]},getValue:function(){return this.el.dom.value=="true"}});this.editors={date:new d.GridEditor(new e.DateField({selectOnFocus:true})),string:new d.GridEditor(new e.TextField({selectOnFocus:true})),number:new d.GridEditor(new e.NumberField({selectOnFocus:true,style:"text-align:left;"})),"boolean":new d.GridEditor(a,{autoSize:"both"})};this.renderCellDelegate=this.renderCell.createDelegate(this);this.renderPropDelegate=this.renderProp.createDelegate(this)},renderDate:function(a){return a.dateFormat(this.dateFormat)},renderBool:function(a){return this[a?"trueText":"falseText"]},isCellEditable:function(a,b){return a==1},getRenderer:function(a){return a==1?this.renderCellDelegate:this.renderPropDelegate},renderProp:function(a){return this.getPropertyName(a)},renderCell:function(d,b,c){var a=this.grid.customRenderers[c.get("name")];if(a){return a.apply(this,arguments)}var e=d;if(Ext.isDate(d)){e=this.renderDate(d)}else{if(typeof d=="boolean"){e=this.renderBool(d)}}return Ext.util.Format.htmlEncode(e)},getPropertyName:function(b){var a=this.grid.propertyNames;return a&&a[b]?a[b]:b},getCellEditor:function(a,e){var b=this.store.getProperty(e),d=b.data.name,c=b.data.value;if(this.grid.customEditors[d]){return this.grid.customEditors[d]}if(Ext.isDate(c)){return this.editors.date}else{if(typeof c=="number"){return this.editors.number}else{if(typeof c=="boolean"){return this.editors["boolean"]}else{return this.editors.string}}}},destroy:function(){Ext.grid.PropertyColumnModel.superclass.destroy.call(this);this.destroyEditors(this.editors);this.destroyEditors(this.grid.customEditors)},destroyEditors:function(b){for(var a in b){Ext.destroy(b[a])}}});Ext.grid.PropertyGrid=Ext.extend(Ext.grid.EditorGridPanel,{enableColumnMove:false,stripeRows:false,trackMouseOver:false,clicksToEdit:1,enableHdMenu:false,viewConfig:{forceFit:true},initComponent:function(){this.customRenderers=this.customRenderers||{};this.customEditors=this.customEditors||{};this.lastEditRow=null;var b=new Ext.grid.PropertyStore(this);this.propStore=b;var a=new Ext.grid.PropertyColumnModel(this,b);b.store.sort("name","ASC");this.addEvents("beforepropertychange","propertychange");this.cm=a;this.ds=b.store;Ext.grid.PropertyGrid.superclass.initComponent.call(this);this.mon(this.selModel,"beforecellselect",function(e,d,c){if(c===0){this.startEditing.defer(200,this,[d,1]);return false}},this)},onRender:function(){Ext.grid.PropertyGrid.superclass.onRender.apply(this,arguments);this.getGridEl().addClass("x-props-grid")},afterRender:function(){Ext.grid.PropertyGrid.superclass.afterRender.apply(this,arguments);if(this.source){this.setSource(this.source)}},setSource:function(a){this.propStore.setSource(a)},getSource:function(){return this.propStore.getSource()},setProperty:function(c,b,a){this.propStore.setValue(c,b,a)},removeProperty:function(a){this.propStore.remove(a)}});Ext.reg("propertygrid",Ext.grid.PropertyGrid);Ext.grid.GroupingView=Ext.extend(Ext.grid.GridView,{groupByText:"Group By This Field",showGroupsText:"Show in Groups",hideGroupedColumn:false,showGroupName:true,startCollapsed:false,enableGrouping:true,enableGroupingMenu:true,enableNoGroups:true,emptyGroupText:"(None)",ignoreAdd:false,groupTextTpl:"{text}",groupMode:"value",cancelEditOnToggle:true,initTemplates:function(){Ext.grid.GroupingView.superclass.initTemplates.call(this);this.state={};var a=this.grid.getSelectionModel();a.on(a.selectRow?"beforerowselect":"beforecellselect",this.onBeforeRowSelect,this);if(!this.startGroup){this.startGroup=new Ext.XTemplate('
','
',this.groupTextTpl,"
",'
')}this.startGroup.compile();if(!this.endGroup){this.endGroup="
"}},findGroup:function(a){return Ext.fly(a).up(".x-grid-group",this.mainBody.dom)},getGroups:function(){return this.hasRows()?this.mainBody.dom.childNodes:[]},onAdd:function(d,a,b){if(this.canGroup()&&!this.ignoreAdd){var c=this.getScrollState();this.fireEvent("beforerowsinserted",d,b,b+(a.length-1));this.refresh();this.restoreScroll(c);this.fireEvent("rowsinserted",d,b,b+(a.length-1))}else{if(!this.canGroup()){Ext.grid.GroupingView.superclass.onAdd.apply(this,arguments)}}},onRemove:function(e,a,b,d){Ext.grid.GroupingView.superclass.onRemove.apply(this,arguments);var c=document.getElementById(a._groupId);if(c&&c.childNodes[1].childNodes.length<1){Ext.removeNode(c)}this.applyEmptyText()},refreshRow:function(a){if(this.ds.getCount()==1){this.refresh()}else{this.isUpdating=true;Ext.grid.GroupingView.superclass.refreshRow.apply(this,arguments);this.isUpdating=false}},beforeMenuShow:function(){var c,a=this.hmenu.items,b=this.cm.config[this.hdCtxIndex].groupable===false;if((c=a.get("groupBy"))){c.setDisabled(b)}if((c=a.get("showGroups"))){c.setDisabled(b);c.setChecked(this.canGroup(),true)}},renderUI:function(){var a=Ext.grid.GroupingView.superclass.renderUI.call(this);if(this.enableGroupingMenu&&this.hmenu){this.hmenu.add("-",{itemId:"groupBy",text:this.groupByText,handler:this.onGroupByClick,scope:this,iconCls:"x-group-by-icon"});if(this.enableNoGroups){this.hmenu.add({itemId:"showGroups",text:this.showGroupsText,checked:true,checkHandler:this.onShowGroupsClick,scope:this})}this.hmenu.on("beforeshow",this.beforeMenuShow,this)}return a},processEvent:function(b,i){Ext.grid.GroupingView.superclass.processEvent.call(this,b,i);var h=i.getTarget(".x-grid-group-hd",this.mainBody);if(h){var g=this.getGroupField(),d=this.getPrefix(g),a=h.id.substring(d.length),c=new RegExp("gp-"+Ext.escapeRe(g)+"--hd");a=a.substr(0,a.length-3);if(a||c.test(h.id)){this.grid.fireEvent("group"+b,this.grid,g,a,i)}if(b=="mousedown"&&i.button==0){this.toggleGroup(h.parentNode)}}},onGroupByClick:function(){var a=this.grid;this.enableGrouping=true;a.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));a.fireEvent("groupchange",a,a.store.getGroupState());this.beforeMenuShow();this.refresh()},onShowGroupsClick:function(a,b){this.enableGrouping=b;if(b){this.onGroupByClick()}else{this.grid.store.clearGrouping();this.grid.fireEvent("groupchange",this,null)}},toggleRowIndex:function(c,a){if(!this.canGroup()){return}var b=this.getRow(c);if(b){this.toggleGroup(this.findGroup(b),a)}},toggleGroup:function(c,b){var a=Ext.get(c),d=Ext.util.Format.htmlEncode(a.id);b=Ext.isDefined(b)?b:a.hasClass("x-grid-group-collapsed");if(this.state[d]!==b){if(this.cancelEditOnToggle!==false){this.grid.stopEditing(true)}this.state[d]=b;a[b?"removeClass":"addClass"]("x-grid-group-collapsed")}},toggleAllGroups:function(c){var b=this.getGroups();for(var d=0,a=b.length;d0){return setTimeout(d,c)}d();return 0}});Ext.applyIf(String,{format:function(b){var a=Ext.toArray(arguments,1);return b.replace(/\{(\d+)\}/g,function(c,d){return a[d]})}});Ext.applyIf(Array.prototype,{indexOf:function(b,c){var a=this.length;c=c||0;c+=(c<0)?a:0;for(;c0){for(var p=0;p0);if(!A){A=true;for(I=0;I=0){B=s.substr(0,A).toLowerCase();if(s.charAt(A+1)==" "){++A}C[B]=s.substr(A+1)}})}catch(z){}return{tId:u.tId,status:v?204:w.status,statusText:v?"No Content":w.statusText,getResponseHeader:function(s){return C[s.toLowerCase()]},getAllResponseHeaders:function(){return x},responseText:w.responseText,responseXML:w.responseXML,argument:y}}function o(s){if(s.tId){k.conn[s.tId]=null}s.conn=null;s=null}function f(x,y,t,s){if(!y){o(x);return}var v,u;try{if(x.conn.status!==undefined&&x.conn.status!=0){v=x.conn.status}else{v=13030}}catch(w){v=13030}if((v>=200&&v<300)||(Ext.isIE&&v==1223)){u=p(x,y.argument);if(y.success){if(!y.scope){y.success(u)}else{y.success.apply(y.scope,[u])}}}else{switch(v){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:u=e(x.tId,y.argument,(t?t:false),s);if(y.failure){if(!y.scope){y.failure(u)}else{y.failure.apply(y.scope,[u])}}break;default:u=p(x,y.argument);if(y.failure){if(!y.scope){y.failure(u)}else{y.failure.apply(y.scope,[u])}}}}o(x);u=null}function m(u,x,s,w,t,v){if(s&&s.readyState==4){clearInterval(t[w]);t[w]=null;if(v){clearTimeout(k.timeout[w]);k.timeout[w]=null}f(u,x)}}function r(s,t){k.abort(s,t,true)}function n(u,x){x=x||{};var s=u.conn,w=u.tId,t=k.poll,v=x.timeout||null;if(v){k.conn[w]=s;k.timeout[w]=setTimeout(r.createCallback(u,x),v)}t[w]=setInterval(m.createCallback(u,x,s,w,t,v),k.pollInterval)}function i(w,t,v,s){var u=l()||null;if(u){u.conn.open(w,t,true);if(k.useDefaultXhrHeader){j("X-Requested-With",k.defaultXhrHeader)}if(s&&k.useDefaultHeader&&(!k.headers||!k.headers[d])){j(d,k.defaultPostHeader)}if(k.defaultHeaders||k.headers){h(u)}n(u,v);u.conn.send(s||null)}return u}function l(){var t;try{if(t=q(k.transactionId)){k.transactionId++}}catch(s){}finally{return t}}function q(v){var s;try{s=new XMLHttpRequest()}catch(u){for(var t=Ext.isIE6?1:0;t0&&isFinite(w)){if(r.curFrame+w>=v){w=v-(u+1)}r.curFrame+=w}}};g.Bezier=new function(){this.getPosition=function(p,o){var r=p.length,m=[],q=1-o,l,k;for(l=0;l0&&!Ext.isArray(s[0])){s=[s]}else{}Ext.fly(p,"_anim").position();A.setXY(p,j(x)?x:A.getXY(p));o=w.getAttr("points");if(j(y)){q=k.call(w,y,o);for(r=0,t=s.length;r0){n=n.concat(s)}n[n.length]=q}else{m.setRunAttr.call(this,u)}}});var k=function(n,p){var o=g.Dom.getXY(this.el);return[n[0]-o[0]+p[0],n[1]-o[1]+p[1]]}})()})();(function(){var d=Math.abs,i=Math.PI,h=Math.asin,g=Math.pow,e=Math.sin,f=Ext.lib;Ext.apply(f.Easing,{easeBoth:function(k,j,m,l){return((k/=l/2)<1)?m/2*k*k+j:-m/2*((--k)*(k-2)-1)+j},easeInStrong:function(k,j,m,l){return m*(k/=l)*k*k*k+j},easeOutStrong:function(k,j,m,l){return -m*((k=k/l-1)*k*k*k-1)+j},easeBothStrong:function(k,j,m,l){return((k/=l/2)<1)?m/2*k*k*k*k+j:-m/2*((k-=2)*k*k*k-2)+j},elasticIn:function(l,j,q,o,k,n){if(l==0||(l/=o)==1){return l==0?j:j+q}n=n||(o*0.3);var m;if(k>=d(q)){m=n/(2*i)*h(q/k)}else{k=q;m=n/4}return -(k*g(2,10*(l-=1))*e((l*o-m)*(2*i)/n))+j},elasticOut:function(l,j,q,o,k,n){if(l==0||(l/=o)==1){return l==0?j:j+q}n=n||(o*0.3);var m;if(k>=d(q)){m=n/(2*i)*h(q/k)}else{k=q;m=n/4}return k*g(2,-10*l)*e((l*o-m)*(2*i)/n)+q+j},elasticBoth:function(l,j,q,o,k,n){if(l==0||(l/=o/2)==2){return l==0?j:j+q}n=n||(o*(0.3*1.5));var m;if(k>=d(q)){m=n/(2*i)*h(q/k)}else{k=q;m=n/4}return l<1?-0.5*(k*g(2,10*(l-=1))*e((l*o-m)*(2*i)/n))+j:k*g(2,-10*(l-=1))*e((l*o-m)*(2*i)/n)*0.5+q+j},backIn:function(k,j,n,m,l){l=l||1.70158;return n*(k/=m)*k*((l+1)*k-l)+j},backOut:function(k,j,n,m,l){if(!l){l=1.70158}return n*((k=k/m-1)*k*((l+1)*k+l)+1)+j},backBoth:function(k,j,n,m,l){l=l||1.70158;return((k/=m/2)<1)?n/2*(k*k*(((l*=(1.525))+1)*k-l))+j:n/2*((k-=2)*k*(((l*=(1.525))+1)*k+l)+2)+j},bounceIn:function(k,j,m,l){return m-f.Easing.bounceOut(l-k,0,m,l)+j},bounceOut:function(k,j,m,l){if((k/=l)<(1/2.75)){return m*(7.5625*k*k)+j}else{if(k<(2/2.75)){return m*(7.5625*(k-=(1.5/2.75))*k+0.75)+j}else{if(k<(2.5/2.75)){return m*(7.5625*(k-=(2.25/2.75))*k+0.9375)+j}}}return m*(7.5625*(k-=(2.625/2.75))*k+0.984375)+j},bounceBoth:function(k,j,m,l){return(k'about:blank', except for IE in secure mode, which is 'javascript:""'). * @type String */ SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank', /** * True if the browser is in strict (standards-compliant) mode, as opposed to quirks mode * @type Boolean */ isStrict : isStrict, /** * True if the page is running over SSL * @type Boolean */ isSecure : isSecure, /** * True when the document is fully initialized and ready for action * @type Boolean */ isReady : false, /** * True if the {@link Ext.Fx} Class is available * @type Boolean * @property enableFx */ /** * HIGHLY EXPERIMENTAL * True to force css based border-box model override and turning off javascript based adjustments. This is a * runtime configuration and must be set before onReady. * @type Boolean */ enableForcedBoxModel : false, /** * True to automatically uncache orphaned Ext.Elements periodically (defaults to true) * @type Boolean */ enableGarbageCollector : true, /** * True to automatically purge event listeners during garbageCollection (defaults to false). * @type Boolean */ enableListenerCollection : false, /** * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed. * Currently not optimized for performance. * @type Boolean */ enableNestedListenerRemoval : false, /** * Indicates whether to use native browser parsing for JSON methods. * This option is ignored if the browser does not support native JSON methods. * Note: Native JSON methods will not work with objects that have functions. * Also, property names must be quoted, otherwise the data will not parse. (Defaults to false) * @type Boolean */ USE_NATIVE_JSON : false, /** * Copies all the properties of config to obj if they don't already exist. * @param {Object} obj The receiver of the properties * @param {Object} config The source of the properties * @return {Object} returns obj */ applyIf : function(o, c){ if(o){ for(var p in c){ if(!Ext.isDefined(o[p])){ o[p] = c[p]; } } } return o; }, /** * Generates unique ids. If the element already has an id, it is unchanged * @param {Mixed} el (optional) The element to generate an id for * @param {String} prefix (optional) Id prefix (defaults "ext-gen") * @return {String} The generated Id. */ id : function(el, prefix){ el = Ext.getDom(el, true) || {}; if (!el.id) { el.id = (prefix || "ext-gen") + (++idSeed); } return el.id; }, /** *

Extends one class to create a subclass and optionally overrides members with the passed literal. This method * also adds the function "override()" to the subclass that can be used to override members of the class.

* For example, to create a subclass of Ext GridPanel: *

MyGridPanel = Ext.extend(Ext.grid.GridPanel, {
    constructor: function(config) {

//      Create configuration for this Grid.
        var store = new Ext.data.Store({...});
        var colModel = new Ext.grid.ColumnModel({...});

//      Create a new config object containing our computed properties
//      *plus* whatever was in the config parameter.
        config = Ext.apply({
            store: store,
            colModel: colModel
        }, config);

        MyGridPanel.superclass.constructor.call(this, config);

//      Your postprocessing here
    },

    yourMethod: function() {
        // etc.
    }
});
* *

This function also supports a 3-argument call in which the subclass's constructor is * passed as an argument. In this form, the parameters are as follows:

*
    *
  • subclass : Function
    The subclass constructor.
  • *
  • superclass : Function
    The constructor of class being extended
  • *
  • overrides : Object
    A literal with members which are copied into the subclass's * prototype, and are therefore shared among all instances of the new class.
  • *
* * @param {Function} superclass The constructor of class being extended. * @param {Object} overrides

A literal with members which are copied into the subclass's * prototype, and are therefore shared between all instances of the new class.

*

This may contain a special member named constructor. This is used * to define the constructor of the new class, and is returned. If this property is * not specified, a constructor is generated and returned which just calls the * superclass's constructor passing on its parameters.

*

It is essential that you call the superclass constructor in any provided constructor. See example code.

* @return {Function} The subclass constructor from the overrides parameter, or a generated one if not provided. */ extend : function(){ // inline overrides var io = function(o){ for(var m in o){ this[m] = o[m]; } }; var oc = Object.prototype.constructor; return function(sb, sp, overrides){ if(typeof sp == 'object'){ overrides = sp; sp = sb; sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);}; } var F = function(){}, sbp, spp = sp.prototype; F.prototype = spp; sbp = sb.prototype = new F(); sbp.constructor=sb; sb.superclass=spp; if(spp.constructor == oc){ spp.constructor=sp; } sb.override = function(o){ Ext.override(sb, o); }; sbp.superclass = sbp.supr = (function(){ return spp; }); sbp.override = io; Ext.override(sb, overrides); sb.extend = function(o){return Ext.extend(sb, o);}; return sb; }; }(), /** * Adds a list of functions to the prototype of an existing class, overwriting any existing methods with the same name. * Usage:

Ext.override(MyClass, {
    newMethod1: function(){
        // etc.
    },
    newMethod2: function(foo){
        // etc.
    }
});
* @param {Object} origclass The class to override * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal * containing one or more methods. * @method override */ override : function(origclass, overrides){ if(overrides){ var p = origclass.prototype; Ext.apply(p, overrides); if(Ext.isIE && overrides.hasOwnProperty('toString')){ p.toString = overrides.toString; } } }, /** * Creates namespaces to be used for scoping variables and classes so that they are not global. * Specifying the last node of a namespace implicitly creates all other nodes. Usage: *

Ext.namespace('Company', 'Company.data');
Ext.namespace('Company.data'); // equivalent and preferable to above syntax
Company.Widget = function() { ... }
Company.data.CustomStore = function(config) { ... }
* @param {String} namespace1 * @param {String} namespace2 * @param {String} etc * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created) * @method namespace */ namespace : function(){ var len1 = arguments.length, i = 0, len2, j, main, ns, sub, current; for(; i < len1; ++i) { main = arguments[i]; ns = arguments[i].split('.'); current = window[ns[0]]; if (current === undefined) { current = window[ns[0]] = {}; } sub = ns.slice(1); len2 = sub.length; for(j = 0; j < len2; ++j) { current = current[sub[j]] = current[sub[j]] || {}; } } return current; }, /** * Takes an object and converts it to an encoded URL. e.g. Ext.urlEncode({foo: 1, bar: 2}); would return "foo=1&bar=2". Optionally, property values can be arrays, instead of keys and the resulting string that's returned will contain a name/value pair for each array value. * @param {Object} o * @param {String} pre (optional) A prefix to add to the url encoded string * @return {String} */ urlEncode : function(o, pre){ var empty, buf = [], e = encodeURIComponent; Ext.iterate(o, function(key, item){ empty = Ext.isEmpty(item); Ext.each(empty ? key : item, function(val){ buf.push('&', e(key), '=', (!Ext.isEmpty(val) && (val != key || !empty)) ? (Ext.isDate(val) ? Ext.encode(val).replace(/"/g, '') : e(val)) : ''); }); }); if(!pre){ buf.shift(); pre = ''; } return pre + buf.join(''); }, /** * Takes an encoded URL and and converts it to an object. Example:

Ext.urlDecode("foo=1&bar=2"); // returns {foo: "1", bar: "2"}
Ext.urlDecode("foo=1&bar=2&bar=3&bar=4", false); // returns {foo: "1", bar: ["2", "3", "4"]}
* @param {String} string * @param {Boolean} overwrite (optional) Items of the same name will overwrite previous values instead of creating an an array (Defaults to false). * @return {Object} A literal with members */ urlDecode : function(string, overwrite){ if(Ext.isEmpty(string)){ return {}; } var obj = {}, pairs = string.split('&'), d = decodeURIComponent, name, value; Ext.each(pairs, function(pair) { pair = pair.split('='); name = d(pair[0]); value = d(pair[1]); obj[name] = overwrite || !obj[name] ? value : [].concat(obj[name]).concat(value); }); return obj; }, /** * Appends content to the query string of a URL, handling logic for whether to place * a question mark or ampersand. * @param {String} url The URL to append to. * @param {String} s The content to append to the URL. * @return (String) The resulting URL */ urlAppend : function(url, s){ if(!Ext.isEmpty(s)){ return url + (url.indexOf('?') === -1 ? '?' : '&') + s; } return url; }, /** * Converts any iterable (numeric indices and a length property) into a true array * Don't use this on strings. IE doesn't support "abc"[0] which this implementation depends on. * For strings, use this instead: "abc".match(/./g) => [a,b,c]; * @param {Iterable} the iterable object to be turned into a true Array. * @return (Array) array */ toArray : function(){ return isIE ? function(a, i, j, res){ res = []; for(var x = 0, len = a.length; x < len; x++) { res.push(a[x]); } return res.slice(i || 0, j || res.length); } : function(a, i, j){ return Array.prototype.slice.call(a, i || 0, j || a.length); }; }(), isIterable : function(v){ //check for array or arguments if(Ext.isArray(v) || v.callee){ return true; } //check for node list type if(/NodeList|HTMLCollection/.test(toString.call(v))){ return true; } //NodeList has an item and length property //IXMLDOMNodeList has nextNode method, needs to be checked first. return ((typeof v.nextNode != 'undefined' || v.item) && Ext.isNumber(v.length)); }, /** * Iterates an array calling the supplied function. * @param {Array/NodeList/Mixed} array The array to be iterated. If this * argument is not really an array, the supplied function is called once. * @param {Function} fn The function to be called with each item. If the * supplied function returns false, iteration stops and this method returns * the current index. This function is called with * the following arguments: *
    *
  • item : Mixed *
    The item at the current index * in the passed array
  • *
  • index : Number *
    The current index within the array
  • *
  • allItems : Array *
    The array passed as the first * argument to Ext.each.
  • *
* @param {Object} scope The scope (this reference) in which the specified function is executed. * Defaults to the item at the current index * within the passed array. * @return See description for the fn parameter. */ each : function(array, fn, scope){ if(Ext.isEmpty(array, true)){ return; } if(!Ext.isIterable(array) || Ext.isPrimitive(array)){ array = [array]; } for(var i = 0, len = array.length; i < len; i++){ if(fn.call(scope || array[i], array[i], i, array) === false){ return i; }; } }, /** * Iterates either the elements in an array, or each of the properties in an object. * Note: If you are only iterating arrays, it is better to call {@link #each}. * @param {Object/Array} object The object or array to be iterated * @param {Function} fn The function to be called for each iteration. * The iteration will stop if the supplied function returns false, or * all array elements / object properties have been covered. The signature * varies depending on the type of object being interated: *
    *
  • Arrays : (Object item, Number index, Array allItems) *
    * When iterating an array, the supplied function is called with each item.
  • *
  • Objects : (String key, Object value, Object) *
    * When iterating an object, the supplied function is called with each key-value pair in * the object, and the iterated object
  • *
* @param {Object} scope The scope (this reference) in which the specified function is executed. Defaults to * the object being iterated. */ iterate : function(obj, fn, scope){ if(Ext.isEmpty(obj)){ return; } if(Ext.isIterable(obj)){ Ext.each(obj, fn, scope); return; }else if(typeof obj == 'object'){ for(var prop in obj){ if(obj.hasOwnProperty(prop)){ if(fn.call(scope || obj, prop, obj[prop], obj) === false){ return; }; } } } }, /** * Return the dom node for the passed String (id), dom node, or Ext.Element. * Optional 'strict' flag is needed for IE since it can return 'name' and * 'id' elements by using getElementById. * Here are some examples: *

// gets dom node based on id
var elDom = Ext.getDom('elId');
// gets dom node based on the dom node
var elDom1 = Ext.getDom(elDom);

// If we don't know if we are working with an
// Ext.Element or a dom node use Ext.getDom
function(el){
    var dom = Ext.getDom(el);
    // do something with the dom node
}
         * 
* Note: the dom node to be found actually needs to exist (be rendered, etc) * when this method is called to be successful. * @param {Mixed} el * @return HTMLElement */ getDom : function(el, strict){ if(!el || !DOC){ return null; } if (el.dom){ return el.dom; } else { if (typeof el == 'string') { var e = DOC.getElementById(el); // IE returns elements with the 'name' and 'id' attribute. // we do a strict check to return the element with only the id attribute if (e && isIE && strict) { if (el == e.getAttribute('id')) { return e; } else { return null; } } return e; } else { return el; } } }, /** * Returns the current document body as an {@link Ext.Element}. * @return Ext.Element The document body */ getBody : function(){ return Ext.get(DOC.body || DOC.documentElement); }, /** * Returns the current document body as an {@link Ext.Element}. * @return Ext.Element The document body */ getHead : function() { var head; return function() { if (head == undefined) { head = Ext.get(DOC.getElementsByTagName("head")[0]); } return head; }; }(), /** * Removes a DOM node from the document. */ /** *

Removes this element from the document, removes all DOM event listeners, and deletes the cache reference. * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval} is * true, then DOM event listeners are also removed from all child nodes. The body node * will be ignored if passed in.

* @param {HTMLElement} node The node to remove */ removeNode : isIE && !isIE8 ? function(){ var d; return function(n){ if(n && n.tagName != 'BODY'){ (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n, true) : Ext.EventManager.removeAll(n); d = d || DOC.createElement('div'); d.appendChild(n); d.innerHTML = ''; delete Ext.elCache[n.id]; } }; }() : function(n){ if(n && n.parentNode && n.tagName != 'BODY'){ (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n, true) : Ext.EventManager.removeAll(n); n.parentNode.removeChild(n); delete Ext.elCache[n.id]; } }, /** *

Returns true if the passed value is empty.

*

The value is deemed to be empty if it is

    *
  • null
  • *
  • undefined
  • *
  • an empty array
  • *
  • a zero length string (Unless the allowBlank parameter is true)
  • *
* @param {Mixed} value The value to test * @param {Boolean} allowBlank (optional) true to allow empty strings (defaults to false) * @return {Boolean} */ isEmpty : function(v, allowBlank){ return v === null || v === undefined || ((Ext.isArray(v) && !v.length)) || (!allowBlank ? v === '' : false); }, /** * Returns true if the passed value is a JavaScript array, otherwise false. * @param {Mixed} value The value to test * @return {Boolean} */ isArray : function(v){ return toString.apply(v) === '[object Array]'; }, /** * Returns true if the passed object is a JavaScript date object, otherwise false. * @param {Object} object The object to test * @return {Boolean} */ isDate : function(v){ return toString.apply(v) === '[object Date]'; }, /** * Returns true if the passed value is a JavaScript Object, otherwise false. * @param {Mixed} value The value to test * @return {Boolean} */ isObject : function(v){ return !!v && Object.prototype.toString.call(v) === '[object Object]'; }, /** * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean. * @param {Mixed} value The value to test * @return {Boolean} */ isPrimitive : function(v){ return Ext.isString(v) || Ext.isNumber(v) || Ext.isBoolean(v); }, /** * Returns true if the passed value is a JavaScript Function, otherwise false. * @param {Mixed} value The value to test * @return {Boolean} */ isFunction : function(v){ return toString.apply(v) === '[object Function]'; }, /** * Returns true if the passed value is a number. Returns false for non-finite numbers. * @param {Mixed} value The value to test * @return {Boolean} */ isNumber : function(v){ return typeof v === 'number' && isFinite(v); }, /** * Returns true if the passed value is a string. * @param {Mixed} value The value to test * @return {Boolean} */ isString : function(v){ return typeof v === 'string'; }, /** * Returns true if the passed value is a boolean. * @param {Mixed} value The value to test * @return {Boolean} */ isBoolean : function(v){ return typeof v === 'boolean'; }, /** * Returns true if the passed value is an HTMLElement * @param {Mixed} value The value to test * @return {Boolean} */ isElement : function(v) { return v ? !!v.tagName : false; }, /** * Returns true if the passed value is not undefined. * @param {Mixed} value The value to test * @return {Boolean} */ isDefined : function(v){ return typeof v !== 'undefined'; }, /** * True if the detected browser is Opera. * @type Boolean */ isOpera : isOpera, /** * True if the detected browser uses WebKit. * @type Boolean */ isWebKit : isWebKit, /** * True if the detected browser is Chrome. * @type Boolean */ isChrome : isChrome, /** * True if the detected browser is Safari. * @type Boolean */ isSafari : isSafari, /** * True if the detected browser is Safari 3.x. * @type Boolean */ isSafari3 : isSafari3, /** * True if the detected browser is Safari 4.x. * @type Boolean */ isSafari4 : isSafari4, /** * True if the detected browser is Safari 2.x. * @type Boolean */ isSafari2 : isSafari2, /** * True if the detected browser is Internet Explorer. * @type Boolean */ isIE : isIE, /** * True if the detected browser is Internet Explorer 6.x. * @type Boolean */ isIE6 : isIE6, /** * True if the detected browser is Internet Explorer 7.x. * @type Boolean */ isIE7 : isIE7, /** * True if the detected browser is Internet Explorer 8.x. * @type Boolean */ isIE8 : isIE8, /** * True if the detected browser is Internet Explorer 9.x. * @type Boolean */ isIE9 : isIE9, /** * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox). * @type Boolean */ isGecko : isGecko, /** * True if the detected browser uses a pre-Gecko 1.9 layout engine (e.g. Firefox 2.x). * @type Boolean */ isGecko2 : isGecko2, /** * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x). * @type Boolean */ isGecko3 : isGecko3, /** * True if the detected browser is Internet Explorer running in non-strict mode. * @type Boolean */ isBorderBox : isBorderBox, /** * True if the detected platform is Linux. * @type Boolean */ isLinux : isLinux, /** * True if the detected platform is Windows. * @type Boolean */ isWindows : isWindows, /** * True if the detected platform is Mac OS. * @type Boolean */ isMac : isMac, /** * True if the detected platform is Adobe Air. * @type Boolean */ isAir : isAir }); /** * Creates namespaces to be used for scoping variables and classes so that they are not global. * Specifying the last node of a namespace implicitly creates all other nodes. Usage: *

Ext.namespace('Company', 'Company.data');
Ext.namespace('Company.data'); // equivalent and preferable to above syntax
Company.Widget = function() { ... }
Company.data.CustomStore = function(config) { ... }
* @param {String} namespace1 * @param {String} namespace2 * @param {String} etc * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created) * @method ns */ Ext.ns = Ext.namespace; })(); Ext.ns('Ext.util', 'Ext.lib', 'Ext.data', 'Ext.supports'); Ext.elCache = {}; /** * @class Function * These functions are available on every Function object (any JavaScript function). */ Ext.apply(Function.prototype, { /** * Creates an interceptor function. The passed function is called before the original one. If it returns false, * the original one is not called. The resulting function returns the results of the original function. * The passed function is called with the parameters of the original function. Example usage: *

var sayHi = function(name){
    alert('Hi, ' + name);
}

sayHi('Fred'); // alerts "Hi, Fred"

// create a new function that validates input without
// directly modifying the original function:
var sayHiToFriend = sayHi.createInterceptor(function(name){
    return name == 'Brian';
});

sayHiToFriend('Fred');  // no alert
sayHiToFriend('Brian'); // alerts "Hi, Brian"
* @param {Function} fcn The function to call before the original * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed. * If omitted, defaults to the scope in which the original function is called or the browser window. * @return {Function} The new function */ createInterceptor : function(fcn, scope){ var method = this; return !Ext.isFunction(fcn) ? this : function() { var me = this, args = arguments; fcn.target = me; fcn.method = method; return (fcn.apply(scope || me || window, args) !== false) ? method.apply(me || window, args) : null; }; }, /** * Creates a callback that passes arguments[0], arguments[1], arguments[2], ... * Call directly on any function. Example: myFunction.createCallback(arg1, arg2) * Will create a function that is bound to those 2 args. If a specific scope is required in the * callback, use {@link #createDelegate} instead. The function returned by createCallback always * executes in the window scope. *

This method is required when you want to pass arguments to a callback function. If no arguments * are needed, you can simply pass a reference to the function as a callback (e.g., callback: myFn). * However, if you tried to pass a function with arguments (e.g., callback: myFn(arg1, arg2)) the function * would simply execute immediately when the code is parsed. Example usage: *


var sayHi = function(name){
    alert('Hi, ' + name);
}

// clicking the button alerts "Hi, Fred"
new Ext.Button({
    text: 'Say Hi',
    renderTo: Ext.getBody(),
    handler: sayHi.createCallback('Fred')
});
* @return {Function} The new function */ createCallback : function(/*args...*/){ // make args available, in function below var args = arguments, method = this; return function() { return method.apply(window, args); }; }, /** * Creates a delegate (callback) that sets the scope to obj. * Call directly on any function. Example: this.myFunction.createDelegate(this, [arg1, arg2]) * Will create a function that is automatically scoped to obj so that the this variable inside the * callback points to obj. Example usage: *

var sayHi = function(name){
    // Note this use of "this.text" here.  This function expects to
    // execute within a scope that contains a text property.  In this
    // example, the "this" variable is pointing to the btn object that
    // was passed in createDelegate below.
    alert('Hi, ' + name + '. You clicked the "' + this.text + '" button.');
}

var btn = new Ext.Button({
    text: 'Say Hi',
    renderTo: Ext.getBody()
});

// This callback will execute in the scope of the
// button instance. Clicking the button alerts
// "Hi, Fred. You clicked the "Say Hi" button."
btn.on('click', sayHi.createDelegate(btn, ['Fred']));
* @param {Object} scope (optional) The scope (this reference) in which the function is executed. * If omitted, defaults to the browser window. * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller) * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding, * if a number the args are inserted at the specified position * @return {Function} The new function */ createDelegate : function(obj, args, appendArgs){ var method = this; return function() { var callArgs = args || arguments; if (appendArgs === true){ callArgs = Array.prototype.slice.call(arguments, 0); callArgs = callArgs.concat(args); }else if (Ext.isNumber(appendArgs)){ callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first var applyArgs = [appendArgs, 0].concat(args); // create method call params Array.prototype.splice.apply(callArgs, applyArgs); // splice them in } return method.apply(obj || window, callArgs); }; }, /** * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage: *

var sayHi = function(name){
    alert('Hi, ' + name);
}

// executes immediately:
sayHi('Fred');

// executes after 2 seconds:
sayHi.defer(2000, this, ['Fred']);

// this syntax is sometimes useful for deferring
// execution of an anonymous function:
(function(){
    alert('Anonymous');
}).defer(100);
* @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately) * @param {Object} scope (optional) The scope (this reference) in which the function is executed. * If omitted, defaults to the browser window. * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller) * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding, * if a number the args are inserted at the specified position * @return {Number} The timeout id that can be used with clearTimeout */ defer : function(millis, obj, args, appendArgs){ var fn = this.createDelegate(obj, args, appendArgs); if(millis > 0){ return setTimeout(fn, millis); } fn(); return 0; } }); /** * @class String * These functions are available on every String object. */ Ext.applyIf(String, { /** * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each * token must be unique, and must increment in the format {0}, {1}, etc. Example usage: *

var cls = 'my-class', text = 'Some text';
var s = String.format('<div class="{0}">{1}</div>', cls, text);
// s now contains the string: '<div class="my-class">Some text</div>'
     * 
* @param {String} string The tokenized string to be formatted * @param {String} value1 The value to replace token {0} * @param {String} value2 Etc... * @return {String} The formatted string * @static */ format : function(format){ var args = Ext.toArray(arguments, 1); return format.replace(/\{(\d+)\}/g, function(m, i){ return args[i]; }); } }); /** * @class Array */ Ext.applyIf(Array.prototype, { /** * Checks whether or not the specified object exists in the array. * @param {Object} o The object to check for * @param {Number} from (Optional) The index at which to begin the search * @return {Number} The index of o in the array (or -1 if it is not found) */ indexOf : function(o, from){ var len = this.length; from = from || 0; from += (from < 0) ? len : 0; for (; from < len; ++from){ if(this[from] === o){ return from; } } return -1; }, /** * Removes the specified object from the array. If the object is not found nothing happens. * @param {Object} o The object to remove * @return {Array} this array */ remove : function(o){ var index = this.indexOf(o); if(index != -1){ this.splice(index, 1); } return this; } }); /** * @class Ext.util.TaskRunner * Provides the ability to execute one or more arbitrary tasks in a multithreaded * manner. Generally, you can use the singleton {@link Ext.TaskMgr} instead, but * if needed, you can create separate instances of TaskRunner. Any number of * separate tasks can be started at any time and will run independently of each * other. Example usage: *

// Start a simple clock task that updates a div once per second
var updateClock = function(){
    Ext.fly('clock').update(new Date().format('g:i:s A'));
} 
var task = {
    run: updateClock,
    interval: 1000 //1 second
}
var runner = new Ext.util.TaskRunner();
runner.start(task);

// equivalent using TaskMgr
Ext.TaskMgr.start({
    run: updateClock,
    interval: 1000
});

 * 
*

See the {@link #start} method for details about how to configure a task object.

* Also see {@link Ext.util.DelayedTask}. * * @constructor * @param {Number} interval (optional) The minimum precision in milliseconds supported by this TaskRunner instance * (defaults to 10) */ Ext.util.TaskRunner = function(interval){ interval = interval || 10; var tasks = [], removeQueue = [], id = 0, running = false, // private stopThread = function(){ running = false; clearInterval(id); id = 0; }, // private startThread = function(){ if(!running){ running = true; id = setInterval(runTasks, interval); } }, // private removeTask = function(t){ removeQueue.push(t); if(t.onStop){ t.onStop.apply(t.scope || t); } }, // private runTasks = function(){ var rqLen = removeQueue.length, now = new Date().getTime(); if(rqLen > 0){ for(var i = 0; i < rqLen; i++){ tasks.remove(removeQueue[i]); } removeQueue = []; if(tasks.length < 1){ stopThread(); return; } } for(var i = 0, t, itime, rt, len = tasks.length; i < len; ++i){ t = tasks[i]; itime = now - t.taskRunTime; if(t.interval <= itime){ rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]); t.taskRunTime = now; if(rt === false || t.taskRunCount === t.repeat){ removeTask(t); return; } } if(t.duration && t.duration <= (now - t.taskStartTime)){ removeTask(t); } } }; /** * Starts a new task. * @method start * @param {Object} task

A config object that supports the following properties:

    *
  • run : Function

    The function to execute each time the task is invoked. The * function will be called at each interval and passed the args argument if specified, and the * current invocation count if not.

    *

    If a particular scope (this reference) is required, be sure to specify it using the scope argument.

    *

    Return false from this function to terminate the task.

  • *
  • interval : Number
    The frequency in milliseconds with which the task * should be invoked.
  • *
  • args : Array
    (optional) An array of arguments to be passed to the function * specified by run. If not specified, the current invocation count is passed.
  • *
  • scope : Object
    (optional) The scope (this reference) in which to execute the * run function. Defaults to the task config object.
  • *
  • duration : Number
    (optional) The length of time in milliseconds to invoke * the task before stopping automatically (defaults to indefinite).
  • *
  • repeat : Number
    (optional) The number of times to invoke the task before * stopping automatically (defaults to indefinite).
  • *

*

Before each invocation, Ext injects the property taskRunCount into the task object so * that calculations based on the repeat count can be performed.

* @return {Object} The task */ this.start = function(task){ tasks.push(task); task.taskStartTime = new Date().getTime(); task.taskRunTime = 0; task.taskRunCount = 0; startThread(); return task; }; /** * Stops an existing running task. * @method stop * @param {Object} task The task to stop * @return {Object} The task */ this.stop = function(task){ removeTask(task); return task; }; /** * Stops all tasks that are currently running. * @method stopAll */ this.stopAll = function(){ stopThread(); for(var i = 0, len = tasks.length; i < len; i++){ if(tasks[i].onStop){ tasks[i].onStop(); } } tasks = []; removeQueue = []; }; }; /** * @class Ext.TaskMgr * @extends Ext.util.TaskRunner * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks. See * {@link Ext.util.TaskRunner} for supported methods and task config properties. *

// Start a simple clock task that updates a div once per second
var task = {
    run: function(){
        Ext.fly('clock').update(new Date().format('g:i:s A'));
    },
    interval: 1000 //1 second
}
Ext.TaskMgr.start(task);
*

See the {@link #start} method for details about how to configure a task object.

* @singleton */ Ext.TaskMgr = new Ext.util.TaskRunner();(function(){ var libFlyweight; function fly(el) { if (!libFlyweight) { libFlyweight = new Ext.Element.Flyweight(); } libFlyweight.dom = el; return libFlyweight; } (function(){ var doc = document, isCSS1 = doc.compatMode == "CSS1Compat", MAX = Math.max, ROUND = Math.round, PARSEINT = parseInt; Ext.lib.Dom = { isAncestor : function(p, c) { var ret = false; p = Ext.getDom(p); c = Ext.getDom(c); if (p && c) { if (p.contains) { return p.contains(c); } else if (p.compareDocumentPosition) { return !!(p.compareDocumentPosition(c) & 16); } else { while (c = c.parentNode) { ret = c == p || ret; } } } return ret; }, getViewWidth : function(full) { return full ? this.getDocumentWidth() : this.getViewportWidth(); }, getViewHeight : function(full) { return full ? this.getDocumentHeight() : this.getViewportHeight(); }, getDocumentHeight: function() { return MAX(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, this.getViewportHeight()); }, getDocumentWidth: function() { return MAX(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, this.getViewportWidth()); }, getViewportHeight: function(){ return Ext.isIE ? (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) : self.innerHeight; }, getViewportWidth : function() { return !Ext.isStrict && !Ext.isOpera ? doc.body.clientWidth : Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth; }, getY : function(el) { return this.getXY(el)[1]; }, getX : function(el) { return this.getXY(el)[0]; }, getXY : function(el) { var p, pe, b, bt, bl, dbd, x = 0, y = 0, scroll, hasAbsolute, bd = (doc.body || doc.documentElement), ret = [0,0]; el = Ext.getDom(el); if(el != bd){ if (el.getBoundingClientRect) { b = el.getBoundingClientRect(); scroll = fly(document).getScroll(); ret = [ROUND(b.left + scroll.left), ROUND(b.top + scroll.top)]; } else { p = el; hasAbsolute = fly(el).isStyle("position", "absolute"); while (p) { pe = fly(p); x += p.offsetLeft; y += p.offsetTop; hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute"); if (Ext.isGecko) { y += bt = PARSEINT(pe.getStyle("borderTopWidth"), 10) || 0; x += bl = PARSEINT(pe.getStyle("borderLeftWidth"), 10) || 0; if (p != el && !pe.isStyle('overflow','visible')) { x += bl; y += bt; } } p = p.offsetParent; } if (Ext.isSafari && hasAbsolute) { x -= bd.offsetLeft; y -= bd.offsetTop; } if (Ext.isGecko && !hasAbsolute) { dbd = fly(bd); x += PARSEINT(dbd.getStyle("borderLeftWidth"), 10) || 0; y += PARSEINT(dbd.getStyle("borderTopWidth"), 10) || 0; } p = el.parentNode; while (p && p != bd) { if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) { x -= p.scrollLeft; y -= p.scrollTop; } p = p.parentNode; } ret = [x,y]; } } return ret; }, setXY : function(el, xy) { (el = Ext.fly(el, '_setXY')).position(); var pts = el.translatePoints(xy), style = el.dom.style, pos; for (pos in pts) { if (!isNaN(pts[pos])) { style[pos] = pts[pos] + "px"; } } }, setX : function(el, x) { this.setXY(el, [x, false]); }, setY : function(el, y) { this.setXY(el, [false, y]); } }; })();Ext.lib.Event = function() { var loadComplete = false, unloadListeners = {}, retryCount = 0, onAvailStack = [], _interval, locked = false, win = window, doc = document, // constants POLL_RETRYS = 200, POLL_INTERVAL = 20, TYPE = 0, FN = 1, OBJ = 2, ADJ_SCOPE = 3, SCROLLLEFT = 'scrollLeft', SCROLLTOP = 'scrollTop', UNLOAD = 'unload', MOUSEOVER = 'mouseover', MOUSEOUT = 'mouseout', // private doAdd = function() { var ret; if (win.addEventListener) { ret = function(el, eventName, fn, capture) { if (eventName == 'mouseenter') { fn = fn.createInterceptor(checkRelatedTarget); el.addEventListener(MOUSEOVER, fn, (capture)); } else if (eventName == 'mouseleave') { fn = fn.createInterceptor(checkRelatedTarget); el.addEventListener(MOUSEOUT, fn, (capture)); } else { el.addEventListener(eventName, fn, (capture)); } return fn; }; } else if (win.attachEvent) { ret = function(el, eventName, fn, capture) { el.attachEvent("on" + eventName, fn); return fn; }; } else { ret = function(){}; } return ret; }(), // private doRemove = function(){ var ret; if (win.removeEventListener) { ret = function (el, eventName, fn, capture) { if (eventName == 'mouseenter') { eventName = MOUSEOVER; } else if (eventName == 'mouseleave') { eventName = MOUSEOUT; } el.removeEventListener(eventName, fn, (capture)); }; } else if (win.detachEvent) { ret = function (el, eventName, fn) { el.detachEvent("on" + eventName, fn); }; } else { ret = function(){}; } return ret; }(); function checkRelatedTarget(e) { return !elContains(e.currentTarget, pub.getRelatedTarget(e)); } function elContains(parent, child) { if(parent && parent.firstChild){ while(child) { if(child === parent) { return true; } child = child.parentNode; if(child && (child.nodeType != 1)) { child = null; } } } return false; } // private function _tryPreloadAttach() { var ret = false, notAvail = [], element, i, v, override, tryAgain = !loadComplete || (retryCount > 0); if(!locked){ locked = true; for(i = 0; i < onAvailStack.length; ++i){ v = onAvailStack[i]; if(v && (element = doc.getElementById(v.id))){ if(!v.checkReady || loadComplete || element.nextSibling || (doc && doc.body)) { override = v.override; element = override ? (override === true ? v.obj : override) : element; v.fn.call(element, v.obj); onAvailStack.remove(v); --i; }else{ notAvail.push(v); } } } retryCount = (notAvail.length === 0) ? 0 : retryCount - 1; if (tryAgain) { startInterval(); } else { clearInterval(_interval); _interval = null; } ret = !(locked = false); } return ret; } // private function startInterval() { if(!_interval){ var callback = function() { _tryPreloadAttach(); }; _interval = setInterval(callback, POLL_INTERVAL); } } // private function getScroll() { var dd = doc.documentElement, db = doc.body; if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){ return [dd[SCROLLLEFT], dd[SCROLLTOP]]; }else if(db){ return [db[SCROLLLEFT], db[SCROLLTOP]]; }else{ return [0, 0]; } } // private function getPageCoord (ev, xy) { ev = ev.browserEvent || ev; var coord = ev['page' + xy]; if (!coord && coord !== 0) { coord = ev['client' + xy] || 0; if (Ext.isIE) { coord += getScroll()[xy == "X" ? 0 : 1]; } } return coord; } var pub = { extAdapter: true, onAvailable : function(p_id, p_fn, p_obj, p_override) { onAvailStack.push({ id: p_id, fn: p_fn, obj: p_obj, override: p_override, checkReady: false }); retryCount = POLL_RETRYS; startInterval(); }, // This function should ALWAYS be called from Ext.EventManager addListener: function(el, eventName, fn) { el = Ext.getDom(el); if (el && fn) { if (eventName == UNLOAD) { if (unloadListeners[el.id] === undefined) { unloadListeners[el.id] = []; } unloadListeners[el.id].push([eventName, fn]); return fn; } return doAdd(el, eventName, fn, false); } return false; }, // This function should ALWAYS be called from Ext.EventManager removeListener: function(el, eventName, fn) { el = Ext.getDom(el); var i, len, li, lis; if (el && fn) { if(eventName == UNLOAD){ if((lis = unloadListeners[el.id]) !== undefined){ for(i = 0, len = lis.length; i < len; i++){ if((li = lis[i]) && li[TYPE] == eventName && li[FN] == fn){ unloadListeners[el.id].splice(i, 1); } } } return; } doRemove(el, eventName, fn, false); } }, getTarget : function(ev) { ev = ev.browserEvent || ev; return this.resolveTextNode(ev.target || ev.srcElement); }, resolveTextNode : Ext.isGecko ? function(node){ if(!node){ return; } // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197 var s = HTMLElement.prototype.toString.call(node); if(s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]'){ return; } return node.nodeType == 3 ? node.parentNode : node; } : function(node){ return node && node.nodeType == 3 ? node.parentNode : node; }, getRelatedTarget : function(ev) { ev = ev.browserEvent || ev; return this.resolveTextNode(ev.relatedTarget || (/(mouseout|mouseleave)/.test(ev.type) ? ev.toElement : /(mouseover|mouseenter)/.test(ev.type) ? ev.fromElement : null)); }, getPageX : function(ev) { return getPageCoord(ev, "X"); }, getPageY : function(ev) { return getPageCoord(ev, "Y"); }, getXY : function(ev) { return [this.getPageX(ev), this.getPageY(ev)]; }, stopEvent : function(ev) { this.stopPropagation(ev); this.preventDefault(ev); }, stopPropagation : function(ev) { ev = ev.browserEvent || ev; if (ev.stopPropagation) { ev.stopPropagation(); } else { ev.cancelBubble = true; } }, preventDefault : function(ev) { ev = ev.browserEvent || ev; if (ev.preventDefault) { ev.preventDefault(); } else { if (ev.keyCode) { ev.keyCode = 0; } ev.returnValue = false; } }, getEvent : function(e) { e = e || win.event; if (!e) { var c = this.getEvent.caller; while (c) { e = c.arguments[0]; if (e && Event == e.constructor) { break; } c = c.caller; } } return e; }, getCharCode : function(ev) { ev = ev.browserEvent || ev; return ev.charCode || ev.keyCode || 0; }, //clearCache: function() {}, // deprecated, call from EventManager getListeners : function(el, eventName) { Ext.EventManager.getListeners(el, eventName); }, // deprecated, call from EventManager purgeElement : function(el, recurse, eventName) { Ext.EventManager.purgeElement(el, recurse, eventName); }, _load : function(e) { loadComplete = true; if (Ext.isIE && e !== true) { // IE8 complains that _load is null or not an object // so lets remove self via arguments.callee doRemove(win, "load", arguments.callee); } }, _unload : function(e) { var EU = Ext.lib.Event, i, v, ul, id, len, scope; for (id in unloadListeners) { ul = unloadListeners[id]; for (i = 0, len = ul.length; i < len; i++) { v = ul[i]; if (v) { try{ scope = v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) : win; v[FN].call(scope, EU.getEvent(e), v[OBJ]); }catch(ex){} } } }; Ext.EventManager._unload(); doRemove(win, UNLOAD, EU._unload); } }; // Initialize stuff. pub.on = pub.addListener; pub.un = pub.removeListener; if (doc && doc.body) { pub._load(true); } else { doAdd(win, "load", pub._load); } doAdd(win, UNLOAD, pub._unload); _tryPreloadAttach(); return pub; }(); /* * Portions of this file are based on pieces of Yahoo User Interface Library * Copyright (c) 2007, Yahoo! Inc. All rights reserved. * YUI licensed under the BSD License: * http://developer.yahoo.net/yui/license.txt */ Ext.lib.Ajax = function() { var activeX = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.3.0', 'Msxml2.XMLHTTP'], CONTENTTYPE = 'Content-Type'; // private function setHeader(o) { var conn = o.conn, prop, headers = {}; function setTheHeaders(conn, headers){ for (prop in headers) { if (headers.hasOwnProperty(prop)) { conn.setRequestHeader(prop, headers[prop]); } } } Ext.apply(headers, pub.headers, pub.defaultHeaders); setTheHeaders(conn, headers); delete pub.headers; } // private function createExceptionObject(tId, callbackArg, isAbort, isTimeout) { return { tId : tId, status : isAbort ? -1 : 0, statusText : isAbort ? 'transaction aborted' : 'communication failure', isAbort: isAbort, isTimeout: isTimeout, argument : callbackArg }; } // private function initHeader(label, value) { (pub.headers = pub.headers || {})[label] = value; } // private function createResponseObject(o, callbackArg) { var headerObj = {}, headerStr, conn = o.conn, t, s, // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223 isBrokenStatus = conn.status == 1223; try { headerStr = o.conn.getAllResponseHeaders(); Ext.each(headerStr.replace(/\r\n/g, '\n').split('\n'), function(v){ t = v.indexOf(':'); if(t >= 0){ s = v.substr(0, t).toLowerCase(); if(v.charAt(t + 1) == ' '){ ++t; } headerObj[s] = v.substr(t + 1); } }); } catch(e) {} return { tId : o.tId, // Normalize the status and statusText when IE returns 1223, see the above link. status : isBrokenStatus ? 204 : conn.status, statusText : isBrokenStatus ? 'No Content' : conn.statusText, getResponseHeader : function(header){return headerObj[header.toLowerCase()];}, getAllResponseHeaders : function(){return headerStr;}, responseText : conn.responseText, responseXML : conn.responseXML, argument : callbackArg }; } // private function releaseObject(o) { if (o.tId) { pub.conn[o.tId] = null; } o.conn = null; o = null; } // private function handleTransactionResponse(o, callback, isAbort, isTimeout) { if (!callback) { releaseObject(o); return; } var httpStatus, responseObject; try { if (o.conn.status !== undefined && o.conn.status != 0) { httpStatus = o.conn.status; } else { httpStatus = 13030; } } catch(e) { httpStatus = 13030; } if ((httpStatus >= 200 && httpStatus < 300) || (Ext.isIE && httpStatus == 1223)) { responseObject = createResponseObject(o, callback.argument); if (callback.success) { if (!callback.scope) { callback.success(responseObject); } else { callback.success.apply(callback.scope, [responseObject]); } } } else { switch (httpStatus) { case 12002: case 12029: case 12030: case 12031: case 12152: case 13030: responseObject = createExceptionObject(o.tId, callback.argument, (isAbort ? isAbort : false), isTimeout); if (callback.failure) { if (!callback.scope) { callback.failure(responseObject); } else { callback.failure.apply(callback.scope, [responseObject]); } } break; default: responseObject = createResponseObject(o, callback.argument); if (callback.failure) { if (!callback.scope) { callback.failure(responseObject); } else { callback.failure.apply(callback.scope, [responseObject]); } } } } releaseObject(o); responseObject = null; } function checkResponse(o, callback, conn, tId, poll, cbTimeout){ if (conn && conn.readyState == 4) { clearInterval(poll[tId]); poll[tId] = null; if (cbTimeout) { clearTimeout(pub.timeout[tId]); pub.timeout[tId] = null; } handleTransactionResponse(o, callback); } } function checkTimeout(o, callback){ pub.abort(o, callback, true); } // private function handleReadyState(o, callback){ callback = callback || {}; var conn = o.conn, tId = o.tId, poll = pub.poll, cbTimeout = callback.timeout || null; if (cbTimeout) { pub.conn[tId] = conn; pub.timeout[tId] = setTimeout(checkTimeout.createCallback(o, callback), cbTimeout); } poll[tId] = setInterval(checkResponse.createCallback(o, callback, conn, tId, poll, cbTimeout), pub.pollInterval); } // private function asyncRequest(method, uri, callback, postData) { var o = getConnectionObject() || null; if (o) { o.conn.open(method, uri, true); if (pub.useDefaultXhrHeader) { initHeader('X-Requested-With', pub.defaultXhrHeader); } if(postData && pub.useDefaultHeader && (!pub.headers || !pub.headers[CONTENTTYPE])){ initHeader(CONTENTTYPE, pub.defaultPostHeader); } if (pub.defaultHeaders || pub.headers) { setHeader(o); } handleReadyState(o, callback); o.conn.send(postData || null); } return o; } // private function getConnectionObject() { var o; try { if (o = createXhrObject(pub.transactionId)) { pub.transactionId++; } } catch(e) { } finally { return o; } } // private function createXhrObject(transactionId) { var http; try { http = new XMLHttpRequest(); } catch(e) { for (var i = Ext.isIE6 ? 1 : 0; i < activeX.length; ++i) { try { http = new ActiveXObject(activeX[i]); break; } catch(e) {} } } finally { return {conn : http, tId : transactionId}; } } var pub = { request : function(method, uri, cb, data, options) { if(options){ var me = this, xmlData = options.xmlData, jsonData = options.jsonData, hs; Ext.applyIf(me, options); if(xmlData || jsonData){ hs = me.headers; if(!hs || !hs[CONTENTTYPE]){ initHeader(CONTENTTYPE, xmlData ? 'text/xml' : 'application/json'); } data = xmlData || (!Ext.isPrimitive(jsonData) ? Ext.encode(jsonData) : jsonData); } } return asyncRequest(method || options.method || "POST", uri, cb, data); }, serializeForm : function(form) { var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements, hasSubmit = false, encoder = encodeURIComponent, name, data = '', type, hasValue; Ext.each(fElements, function(element){ name = element.name; type = element.type; if (!element.disabled && name) { if (/select-(one|multiple)/i.test(type)) { Ext.each(element.options, function(opt){ if (opt.selected) { hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified; data += String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text)); } }); } else if (!(/file|undefined|reset|button/i.test(type))) { if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) { data += encoder(name) + '=' + encoder(element.value) + '&'; hasSubmit = /submit/i.test(type); } } } }); return data.substr(0, data.length - 1); }, useDefaultHeader : true, defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8', useDefaultXhrHeader : true, defaultXhrHeader : 'XMLHttpRequest', poll : {}, timeout : {}, conn: {}, pollInterval : 50, transactionId : 0, // This is never called - Is it worth exposing this? // setProgId : function(id) { // activeX.unshift(id); // }, // This is never called - Is it worth exposing this? // setDefaultPostHeader : function(b) { // this.useDefaultHeader = b; // }, // This is never called - Is it worth exposing this? // setDefaultXhrHeader : function(b) { // this.useDefaultXhrHeader = b; // }, // This is never called - Is it worth exposing this? // setPollingInterval : function(i) { // if (typeof i == 'number' && isFinite(i)) { // this.pollInterval = i; // } // }, // This is never called - Is it worth exposing this? // resetDefaultHeaders : function() { // this.defaultHeaders = null; // }, abort : function(o, callback, isTimeout) { var me = this, tId = o.tId, isAbort = false; if (me.isCallInProgress(o)) { o.conn.abort(); clearInterval(me.poll[tId]); me.poll[tId] = null; clearTimeout(pub.timeout[tId]); me.timeout[tId] = null; handleTransactionResponse(o, callback, (isAbort = true), isTimeout); } return isAbort; }, isCallInProgress : function(o) { // if there is a connection and readyState is not 0 or 4 return o.conn && !{0:true,4:true}[o.conn.readyState]; } }; return pub; }();(function(){ var EXTLIB = Ext.lib, noNegatives = /width|height|opacity|padding/i, offsetAttribute = /^((width|height)|(top|left))$/, defaultUnit = /width|height|top$|bottom$|left$|right$/i, offsetUnit = /\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i, isset = function(v){ return typeof v !== 'undefined'; }, now = function(){ return new Date(); }; EXTLIB.Anim = { motion : function(el, args, duration, easing, cb, scope) { return this.run(el, args, duration, easing, cb, scope, Ext.lib.Motion); }, run : function(el, args, duration, easing, cb, scope, type) { type = type || Ext.lib.AnimBase; if (typeof easing == "string") { easing = Ext.lib.Easing[easing]; } var anim = new type(el, args, duration, easing); anim.animateX(function() { if(Ext.isFunction(cb)){ cb.call(scope); } }); return anim; } }; EXTLIB.AnimBase = function(el, attributes, duration, method) { if (el) { this.init(el, attributes, duration, method); } }; EXTLIB.AnimBase.prototype = { doMethod: function(attr, start, end) { var me = this; return me.method(me.curFrame, start, end - start, me.totalFrames); }, setAttr: function(attr, val, unit) { if (noNegatives.test(attr) && val < 0) { val = 0; } Ext.fly(this.el, '_anim').setStyle(attr, val + unit); }, getAttr: function(attr) { var el = Ext.fly(this.el), val = el.getStyle(attr), a = offsetAttribute.exec(attr) || []; if (val !== 'auto' && !offsetUnit.test(val)) { return parseFloat(val); } return (!!(a[2]) || (el.getStyle('position') == 'absolute' && !!(a[3]))) ? el.dom['offset' + a[0].charAt(0).toUpperCase() + a[0].substr(1)] : 0; }, getDefaultUnit: function(attr) { return defaultUnit.test(attr) ? 'px' : ''; }, animateX : function(callback, scope) { var me = this, f = function() { me.onComplete.removeListener(f); if (Ext.isFunction(callback)) { callback.call(scope || me, me); } }; me.onComplete.addListener(f, me); me.animate(); }, setRunAttr: function(attr) { var me = this, a = this.attributes[attr], to = a.to, by = a.by, from = a.from, unit = a.unit, ra = (this.runAttrs[attr] = {}), end; if (!isset(to) && !isset(by)){ return false; } var start = isset(from) ? from : me.getAttr(attr); if (isset(to)) { end = to; }else if(isset(by)) { if (Ext.isArray(start)){ end = []; for(var i=0,len=start.length; i 0 && isFinite(tweak)){ if(tween.curFrame + tweak >= frames){ tweak = frames - (frame + 1); } tween.curFrame += tweak; } }; }; EXTLIB.Bezier = new function() { this.getPosition = function(points, t) { var n = points.length, tmp = [], c = 1 - t, i, j; for (i = 0; i < n; ++i) { tmp[i] = [points[i][0], points[i][1]]; } for (j = 1; j < n; ++j) { for (i = 0; i < n - j; ++i) { tmp[i][0] = c * tmp[i][0] + t * tmp[parseInt(i + 1, 10)][0]; tmp[i][1] = c * tmp[i][1] + t * tmp[parseInt(i + 1, 10)][1]; } } return [ tmp[0][0], tmp[0][1] ]; }; }; EXTLIB.Easing = { easeNone: function (t, b, c, d) { return c * t / d + b; }, easeIn: function (t, b, c, d) { return c * (t /= d) * t + b; }, easeOut: function (t, b, c, d) { return -c * (t /= d) * (t - 2) + b; } }; (function() { EXTLIB.Motion = function(el, attributes, duration, method) { if (el) { EXTLIB.Motion.superclass.constructor.call(this, el, attributes, duration, method); } }; Ext.extend(EXTLIB.Motion, Ext.lib.AnimBase); var superclass = EXTLIB.Motion.superclass, pointsRe = /^points$/i; Ext.apply(EXTLIB.Motion.prototype, { setAttr: function(attr, val, unit){ var me = this, setAttr = superclass.setAttr; if (pointsRe.test(attr)) { unit = unit || 'px'; setAttr.call(me, 'left', val[0], unit); setAttr.call(me, 'top', val[1], unit); } else { setAttr.call(me, attr, val, unit); } }, getAttr: function(attr){ var me = this, getAttr = superclass.getAttr; return pointsRe.test(attr) ? [getAttr.call(me, 'left'), getAttr.call(me, 'top')] : getAttr.call(me, attr); }, doMethod: function(attr, start, end){ var me = this; return pointsRe.test(attr) ? EXTLIB.Bezier.getPosition(me.runAttrs[attr], me.method(me.curFrame, 0, 100, me.totalFrames) / 100) : superclass.doMethod.call(me, attr, start, end); }, setRunAttr: function(attr){ if(pointsRe.test(attr)){ var me = this, el = this.el, points = this.attributes.points, control = points.control || [], from = points.from, to = points.to, by = points.by, DOM = EXTLIB.Dom, start, i, end, len, ra; if(control.length > 0 && !Ext.isArray(control[0])){ control = [control]; }else{ /* var tmp = []; for (i = 0,len = control.length; i < len; ++i) { tmp[i] = control[i]; } control = tmp; */ } Ext.fly(el, '_anim').position(); DOM.setXY(el, isset(from) ? from : DOM.getXY(el)); start = me.getAttr('points'); if(isset(to)){ end = translateValues.call(me, to, start); for (i = 0,len = control.length; i < len; ++i) { control[i] = translateValues.call(me, control[i], start); } } else if (isset(by)) { end = [start[0] + by[0], start[1] + by[1]]; for (i = 0,len = control.length; i < len; ++i) { control[i] = [ start[0] + control[i][0], start[1] + control[i][1] ]; } } ra = this.runAttrs[attr] = [start]; if (control.length > 0) { ra = ra.concat(control); } ra[ra.length] = end; }else{ superclass.setRunAttr.call(this, attr); } } }); var translateValues = function(val, start) { var pageXY = EXTLIB.Dom.getXY(this.el); return [val[0] - pageXY[0] + start[0], val[1] - pageXY[1] + start[1]]; }; })(); })();// Easing functions (function(){ // shortcuts to aid compression var abs = Math.abs, pi = Math.PI, asin = Math.asin, pow = Math.pow, sin = Math.sin, EXTLIB = Ext.lib; Ext.apply(EXTLIB.Easing, { easeBoth: function (t, b, c, d) { return ((t /= d / 2) < 1) ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b; }, easeInStrong: function (t, b, c, d) { return c * (t /= d) * t * t * t + b; }, easeOutStrong: function (t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeBothStrong: function (t, b, c, d) { return ((t /= d / 2) < 1) ? c / 2 * t * t * t * t + b : -c / 2 * ((t -= 2) * t * t * t - 2) + b; }, elasticIn: function (t, b, c, d, a, p) { if (t == 0 || (t /= d) == 1) { return t == 0 ? b : b + c; } p = p || (d * .3); var s; if (a >= abs(c)) { s = p / (2 * pi) * asin(c / a); } else { a = c; s = p / 4; } return -(a * pow(2, 10 * (t -= 1)) * sin((t * d - s) * (2 * pi) / p)) + b; }, elasticOut: function (t, b, c, d, a, p) { if (t == 0 || (t /= d) == 1) { return t == 0 ? b : b + c; } p = p || (d * .3); var s; if (a >= abs(c)) { s = p / (2 * pi) * asin(c / a); } else { a = c; s = p / 4; } return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b; }, elasticBoth: function (t, b, c, d, a, p) { if (t == 0 || (t /= d / 2) == 2) { return t == 0 ? b : b + c; } p = p || (d * (.3 * 1.5)); var s; if (a >= abs(c)) { s = p / (2 * pi) * asin(c / a); } else { a = c; s = p / 4; } return t < 1 ? -.5 * (a * pow(2, 10 * (t -= 1)) * sin((t * d - s) * (2 * pi) / p)) + b : a * pow(2, -10 * (t -= 1)) * sin((t * d - s) * (2 * pi) / p) * .5 + c + b; }, backIn: function (t, b, c, d, s) { s = s || 1.70158; return c * (t /= d) * t * ((s + 1) * t - s) + b; }, backOut: function (t, b, c, d, s) { if (!s) { s = 1.70158; } return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; }, backBoth: function (t, b, c, d, s) { s = s || 1.70158; return ((t /= d / 2 ) < 1) ? c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b : c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; }, bounceIn: function (t, b, c, d) { return c - EXTLIB.Easing.bounceOut(d - t, 0, c, d) + b; }, bounceOut: function (t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2 / 2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b; } else if (t < (2.5 / 2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b; } return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b; }, bounceBoth: function (t, b, c, d) { return (t < d / 2) ? EXTLIB.Easing.bounceIn(t * 2, 0, c, d) * .5 + b : EXTLIB.Easing.bounceOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b; } }); })(); (function() { var EXTLIB = Ext.lib; // Color Animation EXTLIB.Anim.color = function(el, args, duration, easing, cb, scope) { return EXTLIB.Anim.run(el, args, duration, easing, cb, scope, EXTLIB.ColorAnim); }; EXTLIB.ColorAnim = function(el, attributes, duration, method) { EXTLIB.ColorAnim.superclass.constructor.call(this, el, attributes, duration, method); }; Ext.extend(EXTLIB.ColorAnim, EXTLIB.AnimBase); var superclass = EXTLIB.ColorAnim.superclass, colorRE = /color$/i, transparentRE = /^transparent|rgba\(0, 0, 0, 0\)$/, rgbRE = /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i, hexRE= /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i, hex3RE = /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i, isset = function(v){ return typeof v !== 'undefined'; }; // private function parseColor(s) { var pi = parseInt, base, out = null, c; if (s.length == 3) { return s; } Ext.each([hexRE, rgbRE, hex3RE], function(re, idx){ base = (idx % 2 == 0) ? 16 : 10; c = re.exec(s); if(c && c.length == 4){ out = [pi(c[1], base), pi(c[2], base), pi(c[3], base)]; return false; } }); return out; } Ext.apply(EXTLIB.ColorAnim.prototype, { getAttr : function(attr) { var me = this, el = me.el, val; if(colorRE.test(attr)){ while(el && transparentRE.test(val = Ext.fly(el).getStyle(attr))){ el = el.parentNode; val = "fff"; } }else{ val = superclass.getAttr.call(me, attr); } return val; }, doMethod : function(attr, start, end) { var me = this, val, floor = Math.floor, i, len, v; if(colorRE.test(attr)){ val = []; end = end || []; for(i = 0, len = start.length; i < len; i++) { v = start[i]; val[i] = superclass.doMethod.call(me, attr, v, end[i]); } val = 'rgb(' + floor(val[0]) + ',' + floor(val[1]) + ',' + floor(val[2]) + ')'; }else{ val = superclass.doMethod.call(me, attr, start, end); } return val; }, setRunAttr : function(attr) { var me = this, a = me.attributes[attr], to = a.to, by = a.by, ra; superclass.setRunAttr.call(me, attr); ra = me.runAttrs[attr]; if(colorRE.test(attr)){ var start = parseColor(ra.start), end = parseColor(ra.end); if(!isset(to) && isset(by)){ end = parseColor(by); for(var i=0,len=start.length; iǮ*B\DqYP@?'ﻗDZ?R 9wyri+G$'7UA"x̊y]D9>xe'[yGO tbxIDzf )kII~9 LW 1 N08R"D`ߩLLNpy]~<'WC 59>fvmݻD 坯_b/Rb Xl"'g2%2,LCIv{l ZPS,jFb^,˙^=l=t8}v8QT F" QjHE4\W<|b5]x>+n{x}IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/folder_wrench.png0000644000000000000000000000134413343334667027432 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<vIDAToTe{sNkSKW]F阰lX)Ewv,@!n&ĸsEjM,ԯF׽K2Ʈ̌j٧:ӗZޘzmrr2 }1 "- ,,,~OcAD)"R5%"URT!PVOJ%Z!{ݻ3g7}ov/7lllP޳XUgbgs`2p<9޸_}sK+f?He8K7lff[^zÅ!5 iΉA 7wK\4W=z#>2~A q`2_y!ޢ/YQCSkKh6CxJ ƀ%C:˿bzqkV2Xytgic[ ;mL;ǯWL$C;`@U|!-3R)5b¤cձРBɑ6XaFC&8^1!Xx )& ,40i`&mL34]}yg{#h0&9(<.$̼Ŗ7>,2wף}dԸWIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/grid.png0000644000000000000000000000100113343334667025524 0ustar rootroot00000000000000PNG  IHDRatIME 3j pHYsnu>gAMA aIDATxSKA-k@@PTR1~]Q ]u :A@hH[1sW#P*ǁ9$lкD]#{ۅR^*eĝN"kPVK]QgLdLkT`QbTfUkVhX`TndiekhkjniqexsytVLXLYNYM[OVPZR]Y\P_Te\fRc[f\j^dVs]kancoppf{uxqyyxy}|{pzuxwcyc}}~uhlp{wzpv~~~ܦܧ퓎ᩞ᫠갬볯þǺ!,HA984d@@Tةf2 ::$Qϕ%H8Q&Fq~DD|Y F`5aJnx󤈞PEЂ@ 3I@"0y!Fh<8*̊Qˁ(9H℈HtP`f0K'(QJ  !R4&;graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/information.png0000644000000000000000000000141213343334667027132 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥kW?wLdL&1 6Ѩ(hR,M!R;TA.\tm\ >VtZ΢043$BH|=/_>8U}T!su4-WNV8 (wOo^uŕr#ɞF֮`!r pzyeHnVZԜგ[C*³2??\S +K;EСzrc%5*cb]3_槻i4|vQ @hԎdÅ"@IzSlՒ,Ѿ1AֆFޟXq AǏd'bβE.r`o+)ȶ6P)G!wGCqnfG SJy8ux8q8+g~jnBs14({^&xqXxXƘ0 `~Mqrd;;?ln]-G "8:Z &V#_M_G_8T-y/LZOr_wnYf .m[/-q_1rdߪr^LJ&KӼ~-<]0(Œ1n+iU ' 4`)7 r珁s?w ?{Y!IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user.gif0000644000000000000000000000173313343334667025552 0ustar rootroot00000000000000GIF89ae>i?r?n@uArDkE"sjFH&H&J)H+L$I1M8U5`ILBPDTJ^Xfccq|_@@uCNPSSTUWYYZ[]^_za`cabdhxzcmir> JKHJKLIKKOSPXXXi/m4p/t4{M{Nw-x+{(~+~*W[|ς4Ȝqѩx~ԛמڀ߯Ӏߵ䷊廎#ŗ̜ʦ̫ͧϴӺϥЧШԯ!,7H0F@1D)C$0'ˀ`TBN-o1h>}ܩÒ:~ia8q1L&+@f` lܘ!C 5PH#p/.^HQ"]@HE΅3(N1 s !appA,P;graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/cog.png0000644000000000000000000000100013343334667025346 0ustar rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT(UQMKa^ tT2 .EQС:EeQPR麖QRq%!O^wm!c333 To^%ۚ\Y|gz),{ b,پ7g| fʨ'TǂLGUM W0FDNC \`BASJk4$0SrI ZHd (cO:/B&69I#M:X61đ|hK5AJTȐ9Zf) 9+<OQ  **4KrHk?WDj L>c!i9BL%^0n9ֈs>i ֓i붻k>Gj ]IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_female.gif0000644000000000000000000000172213343334667027061 0ustar rootroot00000000000000GIF89ae>r?uAkE"{D,> ? ?B JABEHAKLIAEEHKOSPXXe6e@rQgimgcnbjqjooogqlrp~{{}}WiŔ`˜lɛmȖɗЬӬѮۻʟ̜ȥ˧ʦ̫ͧͪϬϥѪѫԯ!s,HA,46h s8000p$Lŋ.h`TeL3aPp.gΐQ-:BX8Qcs qJ'44!% V:$1dI1^rF3HNX $XD3+BBL9D;graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_female.png0000644000000000000000000000122713343334667027100 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<)IDAT8˥]hRaA1=SgwHl ((A ]0*7Å*'IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_suit.gif0000644000000000000000000000173413343334667026617 0ustar rootroot00000000000000GIF89aQ1U2[5_9Y9a6`=i9s> s>v< v> }> p?t8z?}M(yR7|bw;h9uz)H2b9n@Q p{])K\d"dOb8 hPkIEW?"H(1CG9͍sԛu-jDc!г[,\B(:&&9QNzFxe8E 38pb5/LfcGަrNLg5iIEHZa+y fگ6~.+v H ƏUXgq%l# g| fA,ˮ1zF0kpeMc7 g^1;iN [+46:XI￀|A: =qrM.So2=g^umpV[6Z"MHN Ǹ;)U{GW1 13EdÿP\W߄~Um4QC54YdIag,. rIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_green.gif0000644000000000000000000000173113343334667026730 0ustar rootroot00000000000000GIF89aM&N'N.O:P-Q.S/S-Q0R3X1]7\;VC[\gBg@gAfOfihkskEpItMuOvOvQwPzSzT|U~W~X@@t4z5t)Kw-x+{(~+~*(IZ[^_bZWbcggdiiknosxς4D[̾䷊#ŝ̜ɣʦ̫ͧ׽ϥЧШԯܨ!",EHA+J4AR,RLd "Lq $B0 #F`,1M.^8:vġ7w!1alZ0 /,XS2~Af dؑ.bmHL#bak8g泸QQ6d``΁9S,QYܬ=Uw䎹!.Gf0w~̇ۇ_'imv'sMFHpO ePLᴙ#wLS=%5e013bo*&HQϲh|:yBY_U1 1ˏ [??DbarQ&>!B !|BCj n?BQpw;ln:n%ܐ{NyPufW:* -IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/application_go.png0000644000000000000000000000117213343334667027600 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe< IDAT͋Nq;4Èfbx)2XXHY b(Ijac$aa#/%R3ϋs~5gbor]&aݲu{G"p"ȝ;S7+[k9D׀S+uO2SZ)\?:|=R Uv("Bxg *ˇ4J$ P2dH0K9V"eN#y8"0,A`VQpnN܌FxH 1͌F)Ea)\xp7RNp23I )SEʙE Vjx#6{FX4\L3I9*v9+pG-e{/A3 W䨩 6,dʈ/)fr왡p\X8w :NZh;#**zbj6Ge\>$/֞K}^T;>_+$Yk{}?~4{<,LpIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/cross.gif0000644000000000000000000000166013343334667025724 0ustar rootroot00000000000000GIF89a@@%'-.-//14746=?>@?A@AACBDCEDGFIGJEGFHHKLOHJJLMPNPUWVXUXVYXZX[Y[Z\[][^\^TWVYX[[^]`_a^a_bacadbdbecfdgbcehghhjiljmlnmolnmpmpnppqprpsqtsvqtrurttwtvvyvywywzy{y|y|y}z}z}{~|~~~~!,H 1آF%V!"ǔ@Ć@8P+B\3vTDɈxr9HR 9A( ~1Ch`0P ,AF5ذ`A;graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/SILK.txt0000644000000000000000000000023113343334667025400 0ustar rootroot00000000000000The icons in this folder are direct gif conversions of the fam fam fam silk icons. Please see http://www.famfamfam.com/lab/icons/silk/ for more details.graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_red.png0000644000000000000000000000131513343334667026417 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<_IDAT8˥kHSaǥ~(˧>DQE !qmZr]l6NZj%EW*f!gQ60b r￳ ˜E~/^f*eE0uBd%PR T,"`(CPU)г\:.}#]Lh2#tV |."vvDvׂ=]뾎kab1cD_ݡРjj۸ Ξ[|afT]ykǓL{j077\#UQ(';DT1chSu+C31b}"r"7~0hTT1}2H+|a *KB-ˀGw|pATZ#p:(Tw ?d䮈>q>N,E>, ;P+VJ,+'z/xg&O Jnr.H<]uA4AtG&t!gj3 Gv3`4dfLsӬWlU5M"+'C4!,2r;A1ʦYXˑIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_orange.png0000644000000000000000000000132313343334667027117 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<eIDAT8˥kHQej!) ҅('0tv1HÙBaI63]{ LrƦN6ȉ(6 ٜo0d}s.y XurIQNUɂ ߊ襱%0+_dlL ZN _C`j[S"9r.wPIT!sN08*bJS=)D^k!eD9=.}+3*] 5HsA AFn $c+4x.(> s# ށЖAm;6*h_Xġ{aڇ.QcQc^姽HrX6ϷSZ` hzT;TIP0Z2_4da棙l@_BSѕ AXh++ɰA^N廗K: j=aylCulx@_`/0[+7/;1߲>x1' l3s&o`@-Le{aMK:bfcXdZ *aD(-j$TGaT͐8\0i]6aD/WnPIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_gray.png0000644000000000000000000000130213343334667026603 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<TIDAT8˥[haGPD*"A( a?M2i[sQ͋0]$эqժ@ی§{/pExyֽ0ڍHG=N k֬WE;{7U;0>`֑ !& !n@9 _^ZT6?ވj!Ưh0žX#am?m'Fl T[]|~kXCT秇$JsX.dqhKSjVhJx=~yJ!ɁN'維 P* *SHRChNq`0x7d2 Պl6L&Ʉax<twwWd2 lH˲H0 0rX,jlFp\W, 5^D8qp8@&3  ߏb.-*`1R)N DH%j+" S؏J?4jt:멑W,3鏮.,DEeּҴtl"]" x!%,6㫯E~!IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/connect.png0000644000000000000000000000135413343334667026243 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<~IDATKHa|δk(")С<ŃT`5:EP%FPdtJ""-Bh~츻_{؃B~15ч>JD+Rq%Aޤvj'5b cipk%O 029\ ^)T#DK2?30wqIeWs*'+ԕBvBH*2 (a[&13IEUqjn< b^c\Bm25XE.>4qp ϾMƓDe.agO|Eq >Oۚ:fǦ[D c^UZ ΍Q)Lh(oM?c+RF*!ЇQIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_delete.png0000644000000000000000000000137713343334667027117 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥kHQm J¾*,D22/SP,IbJ C,6VݲY}Q䦨_g Bq?,[L ZP&M˶@-@̵E $8˺%5Q<ܦ܈4DvyUATPWBp3~UUK8$@dqr5Ÿ3?`E^ǨM)wOӤ[QP:tF(zjNW&kb{NTX&d7Py_Ԭ_V ѹ)>}!/ <ox?O\w/l`bGR^O@S^g1>h`عᒓjP= ^( b\+H̴wPcn*`$d[6nַ!vb N5NèY\ ?A薰>9>S Dj "j ԂWvZw#N{*ǔ=PCAGOXB !+峒neY|>Rn1Wx[0H3!bDs"q.34ne^|d\XŰrm"wfӎftpvvQ{IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_add.png0000644000000000000000000000135213343334667026376 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<|IDAT8˥HSQ폒"[!BbQbԄ4ۖMh(3e͖:l-|[3CWsd!P!4ԅ+DAЅ=s ?yUQ'6HwB/fBȞˈUoH)&<+cC'a(~)=ȭZW`B"<\9Af kf].~;^1իǐ <'#_ć{M *0gE|7F|q o?9"wXfKPAcAvL;1?JPؙ?BJ0 Q5V -2r,Ox"5< E=},:= I/>pO:6%9ɃGP^{F ! KU. ȚOZ %;@"DV @Sg&~sЯ ZF h&yaW{\Ȅ/|9~R?ѹ g.jQf:ӑ/F.Pk?FFPIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/control_rewind.png0000644000000000000000000000114613343334667027641 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥Ɋ"A #Zj" /BV*m)nX,@UUө|>/^/ANXQC&!:7"bV@d2(xl6JhZ*6 , `fCF`ہMl6 m6L&ͧX,u2%\n^ f LQ0dDg |M#2buB CnP,!?* %BfGh4B|b|bCzH\Nf x8eh @ {鴜L&/Iƞ`8w~y DH(`P*QK1m4($IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/accept.png0000644000000000000000000000141513343334667026047 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥KSa;vvl dD!P{$; ż,Kݽ6cL2r^H)-jsNm֔2qQB̽BatoL#z {q' r=)La8,u%2Rg>ݾW ϛJ߸Pd makD|=G Vn6[Įd桚(Pm.0Q`'Fb#&ܧ6aP׏Q12[+zi; ]C17оpI9̾jD}›?7ayze,hXAK^3*bk @+wQ=!}uXzq:g쯺n= :d+_GTA;Ր Jƣ.!P)5!H:epր"݂"Kyw|{H2!i~3z_X;okBZK* ^R:O(jF*^ȰS诿_ gЬycIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/feed_error.png0000644000000000000000000000140213343334667026720 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATkugL7;bBJ[E^AJWQw]uS 𦐺-*Ah]D /:ʦNe;dw:>+|v9!B,#dd2dȐj:?69 gj?9p{9@JYi7@>Dh((:=2RB- "ݛ\a}Ml{/\>#\8VܟJUS@HLa~&fqڪ5+!EE;_: }uYS eE5W)Ş7vEGyu*Bc=kμfmCUDēAGwt-S#lax,.0}eC;n{Ah ?iF>e)'Li0:b(($$($X~!*hIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/book.png0000644000000000000000000000112113343334667025534 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8ujUAޛE,(HM (> v>( QEݙ,DcwaXv~}3${"`qY Avxo? 0^#87^^ln!%A n)Upu\[]`UUV?~xWnґ>~accE{*$bs{.e`4%Μ8;HAp OM6vHu cf i )%n^V8 PLSAITq{hSPrXl{PBR5BHds,%7Q)9b/P/e"0 ̄YV nBږ VQ.EQ}7lHv&|Ֆ: IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/image_add.png0000644000000000000000000000121513343334667026500 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥KQcY"ZH1;q-rQ@U}lJaD(ʌ-$aH#mr뽧ŌSA.9\ξ[ANN-'?.ð;ÌBd8A X(hf .>(aPA<;rF l ک  D%*4P )F,S`" &;graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/add.png0000644000000000000000000000133513343334667025341 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<oIDAT8˥Ka[/Y()%X(olNۖskn.-h;8fEP"jïMGˈ}yພ羹$I.tulu AX:𼂒ZHh1DnZJOJB{Z?`2`S=N$ő=;a &jw qJG#<"N2h8޵`6xցn_+ ~Zto}`x%XЛ͈ hXѿƻ/}BJ_G&|Qr-6Aރ EL⬡\U3:WUh[C6+ 6.f *K͸ܝFq ou4܄?d|XҥMvD` *_[ #A20liR|xq`4w=\uQ m+G|%$5Թ5RO*YGMUO Gqj4ְ(X& s1c˭(LVf RdjQ '-1ATA>U j4,pV"4L$e@.ArBY a~myY])Q8tNLܞt2"I o=CSd)__AF(IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_suit.png0000644000000000000000000000135413343334667026634 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<~IDAT8˥]HQDž.t~UW]D]w2KTcМj(?^mkL:KMmLSdPDE`B{{/̢<Naͫ%PPB eFBWXTN!1 z /2vA%4n+ .Ģ *&C=Yl8kRۀq" b0ʮ37-ڂEύn (/bzV<6,y;>FNW`= xY0vhmElx]7[+`]{0/2<~hZ(@oV)>L;4q%5єule?=՟=,EfJc6n+|n2$Ƙ7̋\.ir;Xr;y,Ǵ&VO|27H>V9IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_add.gif0000644000000000000000000000175113343334667026362 0ustar rootroot00000000000000GIF89a7k:n"=p 0Z_e>i?r?As%L{1n@uArDkE"FH&H&J+L8U5`LDTXfccq|S(W-\H]JfOr`@@auxSWYZ[]^_za`cabdhcmir> JKHJKLIKKOSPXXXi/m4p/t4{M{N{(~+~*a``bdegjW[|ȜqѩxԞڄ߱Ӑҕ֒ԕԜߵ䷊廎ŗ̜ʦ̫ͧӺϥЧШԯ!,?H1C!2A@~ّG0BgK `EBD@E@FAGBIDMJJENHOISNQKRLUOYRZS]V^WYO[Q]R_SaZb\c]e^`UaVaVcXdXdYfZf]h[i]i]j^l_lemfmejbkckbm`neofoeqium@@obqdqfresesgriwiwnwmzrypzp|v{q}s~t~tz{|uvvxxxy|~~yʀʀʁʂʃ˃̃̄͆ͅψψϊϋϊϋϋЌэЏҍ׏яҐԓՓՕזחךטؘؙؙٜؗٛ!9,sH7O1h=RMY,AR!8n(U C6W(Q3sIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/error.png0000644000000000000000000000123213343334667025736 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<,IDAT8˥SKHkTC[RL5 o= ck)c.$BDP,,NY(FFfkaf>o1Hb=fU~Ϣ=USl.ZkP(9X(>H3kR x_Oqg覆8t]tiXas_ j'{Љ_袺I~}^OTj5՟羇}vM悥]b7(Vl9o XoCn%M+ciѐ+Ci@IzW^5 @Ee6dVK>@dW2U/zW ѳ'BOlYxoT3# Yd(,aPG+_Rr\:m;gS o*0>N @aΣ5;50?kkz65ß} /qoI0-  R`ZUކ1̌ U rj1B CeqƊ@޳H \):xu4E3'ǃxǃ8>!@}X;Lc٧S/IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_comment.png0000644000000000000000000000134713343334667027314 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<yIDAT8}mHSQǧFH/}2J(A f-pjA&bΔԙ0s өs DMs6gRL:,p;w"{9QO AkS vF{4+ {Q !"_&,|^$d%\``^ K{fm_hp;i F, {4w GzvKl&3CT (CbJDy:Gǫk5<;5 QD@xw@||'Ѓŕ*%c3F쩞1<Ԅ/_%P#d*F*ucl'EgɕADcYoLZ_NDZ+ &p/Nސ^@uq6TD] u srs.7yKkB6@Rma 1dw?QT)805- ύ>W r$0S0[xR$h5UaObJLeS`V Tڇ. ({8ׁ1Q)05$_D6dfZ^vk.U.=ljވtL%*IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/plugin_add.gif0000644000000000000000000000176213343334667026704 0ustar rootroot00000000000000GIF89a7k;o=p <}*As%L{1E6S(W-D@FAGBJENHOI[C]ESNQKRLUOYRZS]V^W[Q]R_SfOoZaPaZc]m^e^`UaVaVcXdXdYfZh[i]i]j^l_lemfmejbkcm`neofoeqium@@obqdqfresesgriwiwnwmzrypzp{q}s~t~ta``bdegjuvvxxxy|yʀʁʂʃ˃̃̄͆ͅψψϊϋϊϋϋЌэЏҍ׏яҐԓՓՕזחךטؘؙؙٜؗٛӐҕ֒ԕԜ!:,uH9T1h0 ?WGN\,AbDϝ!9p(X% >yء I" =X(x;>v` QF (J 3Hab8<0 "kNؐC C5AΔ BHCDf^LpD A x$Q:.T !@!]:@;graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user.png0000644000000000000000000000134513343334667025570 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<wIDAT8˥mHQ *ֶVj)_OХ9]m*Y떩e-h-QJiLF2LJE8\9r~s)k­PiP.I$"EwDtR][)t$ҐB 4BtTPMjq1s M*nFj 4#/8A>PSPΞ S}|뺆y=9{sܔ:`;3evq/# ;us fEf q<^ք(ss7(`%|< ǗiXD%/X`a6.05 V);nǁ dN>*^?aW+jza)Zq\M9=Y[ ZR (mDV2’Jb 26TD]HCpލ C<^28;+Cu f5 7n:eF`Q( >%C%s2٥hid3@ A+YaU,Z1<@UZ%ձc_9]f'ɳ;fMj§88|_x +Q#Mɛ" WatgzR'9)&lz\v|JerРLZ\5kB_h;'_LhKD@xLG99;(zү/)y_Zf7IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/user_delete.gif0000644000000000000000000000175113343334667027074 0ustar rootroot00000000000000GIF89ae>i?r?n@uArDkE"vObFH&H&J+L8U5`LDTXfccq|@@uSWYZ[]^_za`cabdhcmir> JKHJKLIKKOSPXXXRBBCOQP$i/m4p/t4{M{NYY\#\$])dc'l/{(~+t=~*hvyvww!W[|!Ȝqѩ@McxrԞڄߘܦݦߵ⯔䷊廎㰕ŗ̜ʦ̫ͧӺϥͮЧШԯָ!,)H56f@64pܨBdp 0ˀ` 0w %РC ڂ@"2)bd6g|MC[YD5zL&I03p Vf"VЖ͘z^l}P/on7v YaڠKAFjxGHW.܋E]|0'O߮ݼ MIoQ1肴4N1,uS-.܎| ABNkcwzmhx'D1 Ox3dz2w!D؜X%H+,Nj:a)xsPg(_@~wCQ1I? r.+]St{ϡPwuE"tUG 7?\WW}V-C* ƐU W\FI$O8_y6}.hSJ1'.ݤҪK. S~"xj'D', t)C.,%) p_k<Ҫ2IENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/add.gif0000644000000000000000000000174213343334667025324 0ustar rootroot00000000000000GIF89a=5B6C8D;Elo2rjkC#σpS.Ԑw8G¯W^ҴWGzSZ- Y&y_+F\OHCG1m/mZNڵ6,kLuTPC yGm1hmfF.Wy} .[GIENDB`graphite-web-1.1.4/webapp/content/js/ext/resources/icons/fam/cog_edit.png0000644000000000000000000000154113343334667026365 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8uOaƙ[vZ9N:0b6XLMW, 5!i~ff4c 4`"( ~7]Jf/WƦݳ=~Qkjjk6' ǝK8ԙd"N"UB!HuJT*e2Y"&bfQ&)ESXXXR "aZ,w'0 ZmvC.Ljx8EQPDCZGz1F0??X,1WЂhleOOOjb||===gǧ rW: ή!IN|h4hjj2v#PZ>6]UlЋ.f1T&Abpp---{[Q҉; EXdt0dԧ/Ewc UⳘ כ2*JiX\!Lɡ+9;RDEӕA˳HJ$^ѯbEpzD"y<0X ]=K\ ^&qPUU+//?Ɓ!