graphite-web-1.0.2/0000755000000000000000000000000013131245073014025 5ustar rootroot00000000000000graphite-web-1.0.2/bin/0000755000000000000000000000000013131245073014575 5ustar rootroot00000000000000graphite-web-1.0.2/bin/build-index.sh0000755000000000000000000000022413131244446017341 0ustar rootroot00000000000000#!/bin/bash export PYTHONPATH="/opt/graphite/webapp/:$PYTHONPATH" BINDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ${BINDIR}/build-index graphite-web-1.0.2/bin/build-index0000755000000000000000000000212113131244446016726 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.util 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("-c", "--ceres_dir", default=settings.CERES_DIR, help="default: %default") parser.add_option("-w", "--whisper_dir", default=settings.WHISPER_DIR, help="default: %default") parser.add_option("-i", "--index", default=settings.INDEX_FILE, help="default: %default") (options, args) = parser.parse_args() write_index(options.whisper_dir, options.ceres_dir, options.index) graphite-web-1.0.2/bin/run-graphite-devel-server.py0000755000000000000000000000250513131244446022165 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.0.2/conf/0000755000000000000000000000000013131245073014752 5ustar rootroot00000000000000graphite-web-1.0.2/conf/dashboard.conf.example0000644000000000000000000000354313131244446021212 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.0.2/conf/graphite.wsgi.example0000755000000000000000000000013213131244446021104 0ustar rootroot00000000000000import sys sys.path.append('/opt/graphite/webapp') from graphite.wsgi import application graphite-web-1.0.2/conf/graphTemplates.conf.example0000644000000000000000000000461513131244446022244 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.0.2/PKG-INFO0000644000000000000000000000114213131245073015120 0ustar rootroot00000000000000Metadata-Version: 1.1 Name: graphite-web Version: 1.0.2 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 :: 2 :: Only graphite-web-1.0.2/setup.py0000644000000000000000000000675213131244465015555 0ustar rootroot00000000000000#!/usr/bin/env python from __future__ import with_statement import os import ConfigParser from glob import glob from collections import defaultdict try: from io import BytesIO except ImportError: from StringIO import StringIO as BytesIO # 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.ConfigParser() cf.readfp(BytesIO(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 ConfigParser.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', 'wb') 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', 'ceres', 'rrd', 'log', 'log/webapp'): 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.0.2', 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.browser', 'graphite.composer', 'graphite.dashboard', 'graphite.events', 'graphite.finders', 'graphite.metrics', 'graphite.render', 'graphite.url_shortener', 'graphite.version', 'graphite.whitelist', 'graphite.worker_pool', ], package_data={'graphite' : ['templates/*', 'local_settings.py.example']}, scripts=glob('bin/*'), data_files=webapp_content.items() + storage_dirs + conf_files + examples, install_requires=['Django>=1.8,<1.9.99', 'django-tagging==0.4.3', 'pytz', 'pyparsing', 'cairocffi', 'urllib3', 'scandir'], 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 :: 2 :: Only', ], **setup_kwargs ) finally: with open('setup.cfg', 'w') as f: f.write(orig_setup_cfg) graphite-web-1.0.2/LICENSE0000644000000000000000000002616313131244446015045 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.0.2/webapp/0000755000000000000000000000000013131245073015303 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/0000755000000000000000000000000013131245073017106 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/user_util.py0000644000000000000000000000316513131244446021503 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.models import User from graphite.account.models import Profile from graphite.logger import log def getProfile(request, allowDefault=True): if request.user.is_authenticated(): 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.0.2/webapp/graphite/node.py0000644000000000000000000000164713131244465020421 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', 'intervals') 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: 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.0.2/webapp/graphite/render/0000755000000000000000000000000013131245073020365 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/render/grammar.py0000644000000000000000000000601113131244446022366 0ustar rootroot00000000000000from pyparsing import ( Forward, Combine, Optional, Word, Literal, CaselessKeyword, CaselessLiteral, Group, FollowedBy, LineEnd, OneOrMore, ZeroOrMore, alphas, alphanums, printables, delimitedList, quotedString, Regex, __version__, ) 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') 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 | aString | expression )('args*') kwarg = Group(argname + equal + arg)('kwargs*') args = delimitedList(~kwarg + arg) # lookahead to prevent failing on equals kwargs = delimitedList(kwarg) call = Group( funcname + leftParen + Optional( args + Optional( comma + kwargs ) ) + rightParen )('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') if __version__.startswith('1.'): expression << Group(template | call | pathExpression)('expression') grammar << expression else: expression <<= Group(template | call | pathExpression)('expression') grammar <<= expression def enableDebug(): for name,obj in globals().items(): try: obj.setName(name) obj.setDebug(True) except: pass graphite-web-1.0.2/webapp/graphite/render/__init__.py0000644000000000000000000000000013131244446022467 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/render/hashing.py0000644000000000000000000000766313131244446022377 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 try: import pyhash hasher = pyhash.fnv1a_32() def fnv32a(string, seed=0x811c9dc5): return hasher(string, seed=seed) except ImportError: def fnv32a(string, 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 for s in string: hval = hval ^ ord(s) hval = (hval * fnv_32_prime) % uint32_max return hval def hashRequest(request): # Normalize the request parameters so 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): targetsString = ','.join(sorted(targets)) startTimeString = startTime.strftime("%Y%m%d_%H%M") endTimeString = endTime.strftime("%Y%m%d_%H%M") myHash = targetsString + '@' + startTimeString + ':' + endTimeString return compactHash(myHash) def compactHash(string): hash = md5() hash.update(string.encode('utf-8')) return hash.hexdigest() 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): if self.hash_type == 'fnv1a_ch': big_hash = '{:x}'.format(int(fnv32a( str(key) ))) small_hash = int(big_hash[:4], 16) ^ int(big_hash[4:], 16) else: big_hash = md5(str(key)).hexdigest() small_hash = int(big_hash[:4], 16) return small_hash 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) 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, None) index = bisect.bisect_left(self.ring, search_entry) % self.ring_len entry = self.ring[index] return entry[1] def get_nodes(self, key): nodes = [] position = self.compute_ring_position(key) search_entry = (position, None) 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.append(next_node) nodes_len += 1 index = (index + 1) % self.ring_len return nodes graphite-web-1.0.2/webapp/graphite/render/functions.py0000644000000000000000000041075313131244465022765 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 import random import re import time from datetime import datetime, timedelta from itertools import izip, imap from os import environ from graphite.logger import log from graphite.render.attime import parseTimeOffset, parseATTime from graphite.events import models from graphite.util import epoch, timestamp, deltaseconds from graphite.render.grammar import grammar # 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 = 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 float(a) / float(b) def safePow(a, b): if a is None: return None if b is None: return None try: result = math.pow(a, b) except: 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(a): return safeDiv(safeSum(a),safeLen(a)) 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): safeValues = [v for v in values if v is not None] if safeValues: return min(safeValues) def safeMax(values): safeValues = [v for v in values if v is not None] if safeValues: return max(safeValues) 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) def safeIsNotEmpty(values): safeValues = [v for v in values if v is not None] return len(safeValues) > 0 # 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 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() class NormalizeEmptyResultError(Exception): # throw error for normalize() when empty pass def matchSeries(seriesList1, seriesList2): assert len(seriesList2) == len(seriesList1), "The number of series in each argument must be the same" return izip(sorted(seriesList1, lambda a,b: cmp(a.name, b.name)), sorted(seriesList2, lambda a,b: cmp(a.name, b.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 #NOTE: Some of the functions below use izip, which may be problematic. #izip stops when it hits the end of the shortest series #in practice this *shouldn't* matter because all series will cover #the same interval, despite having possibly different steps... 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. """ try: (seriesList,start,end,step) = normalize(seriesLists) except: return [] name = "sumSeries(%s)" % formatPathExpressions(seriesList) values = ( safeSum(row) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series] 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 ``target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value)`` """ if isinstance(position, int): positions = [position] else: positions = position newSeries = {} newNames = list() for series in seriesList: newname = '.'.join(map(lambda x: x[1], filter(lambda i: i[0] not in positions, enumerate(series.name.split('.'))))) if newname in newSeries: newSeries[newname] = sumSeries(requestContext, (series, newSeries[newname]))[0] else: newSeries[newname] = series newNames.append(newname) newSeries[newname].name = newname return [newSeries[name] for name in newNames] 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 ``target=averageSeries(host.*.cpu-user.value)&target=averageSeries(host.*.cpu-system.value)`` """ if isinstance(position, int): positions = [position] else: positions = position result = [] matchedList = {} for series in seriesList: newname = '.'.join(map(lambda x: x[1], filter(lambda i: i[0] not in positions, enumerate(series.name.split('.'))))) if newname not in matchedList: matchedList[newname] = [] matchedList[newname].append(series) for name in matchedList.keys(): result.append( averageSeries(requestContext, (matchedList[name]))[0] ) result[-1].name = name return result 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)... """ if type(position) is int: positions = [position] else: positions = position newSeries = {} newNames = list() for series in seriesList: newname = '.'.join(map(lambda x: x[1], filter(lambda i: i[0] not in positions, enumerate(series.name.split('.'))))) if newname in newSeries: newSeries[newname] = multiplySeries(requestContext, (newSeries[newname], series))[0] else: newSeries[newname] = series newNames.append(newname) newSeries[newname].name = newname return [newSeries[name] for name in newNames] 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) """ (seriesList,start,end,step) = normalize(seriesLists) name = "diffSeries(%s)" % formatPathExpressions(seriesList) values = ( safeDiff(row) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series] 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) """ (seriesList,start,end,step) = normalize(seriesLists) name = "averageSeries(%s)" % formatPathExpressions(seriesList) values = ( safeDiv(safeSum(row),safeLen(row)) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series] 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) """ (seriesList,start,end,step) = normalize(seriesLists) name = "stddevSeries(%s)" % formatPathExpressions(seriesList) values = ( safeStdDev(row) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series] 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) """ (seriesList, start, end, step) = normalize(seriesLists) name = "minSeries(%s)" % formatPathExpressions(seriesList) values = ( safeMin(row) for row in izip(*seriesList) ) series = TimeSeries(name, start, end, step, values) series.pathExpression = name return [series] 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) """ (seriesList, start, end, step) = normalize(seriesLists) name = "maxSeries(%s)" % formatPathExpressions(seriesList) values = ( safeMax(row) for row in izip(*seriesList) ) series = TimeSeries(name, start, end, step, values) series.pathExpression = name return [series] 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) """ (seriesList,start,end,step) = normalize(seriesLists) name = "rangeOfSeries(%s)" % formatPathExpressions(seriesList) values = ( safeSubtract(max(row), min(row)) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series] 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') name = 'percentileOfSeries(%s,%g)' % (seriesList[0].pathExpression, n) (start, end, step) = normalize([seriesList])[1:] values = [ _getPercentile(row, n, interpolate) for row in izip(*seriesList) ] resultSeries = TimeSeries(name, start, end, step, values) resultSeries.pathExpression = name return [resultSeries] 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 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 else: if 0 < consecutiveNones <= limit: # 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 xrange(i - consecutiveNones, i): series[index] = series[i - 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 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 xrange(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 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 def asPercent(requestContext, seriesList, total=None): """ 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. The `total` parameter may be a single series, reference the same number of series as `seriesList` or a numeric value. Example: .. code-block:: none &target=asPercent(Server01.connections.{failed,succeeded}, Server01.connections.attempted) &target=asPercent(Server*.connections.{failed,succeeded}, Server*.connections.attempted) &target=asPercent(apache01.threads.busy,1500) &target=asPercent(Server01.cpu.*.jiffies) """ normalize([seriesList]) if total is None: totalValues = [ safeSum(row) for row in izip(*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(series1,series2) ] resultSeries = TimeSeries(name,start,end,step,resultValues) resultSeries.pathExpression = name resultList.append(resultSeries) else: for series in seriesList: resultValues = [ safeMul(safeDiv(val, totalVal), 100.0) for val,totalVal in izip(series,totalValues) ] name = "asPercent(%s,%s)" % (series.name, totalText or series.pathExpression) resultSeries = TimeSeries(name,series.start,series.end,series.step,resultValues) resultSeries.pathExpression = name resultList.append(resultSeries) return resultList 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(*bothSeries) ) quotientSeries = TimeSeries(name, start, end, step, values) quotientSeries.pathExpression = name results.append(quotientSeries) return results 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(*bothSeries) ) quotientSeries = TimeSeries(name, start, end, step, values) quotientSeries.pathExpression = name results.append(quotientSeries) return results 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) """ (seriesList,start,end,step) = normalize(seriesLists) if len(seriesList) == 1: return seriesList name = "multiplySeries(%s)" % ','.join([s.name for s in seriesList]) product = imap(lambda x: safeMul(*x), izip(*seriesList)) resultSeries = TimeSeries(name, start, end, step, product) resultSeries.pathExpression = name return [ resultSeries ] 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. Example: .. code-block:: none &target=weightedAverage(*.transactions.mean,*.transactions.count,0) &target=weightedAverage(*.transactions.mean,*.transactions.count,1,3,4) """ if isinstance(nodes, int): nodes=[nodes] sortedSeries={} for seriesAvg, seriesWeight in izip(seriesListAvg , seriesListWeight): key = '' for node in nodes: key += seriesAvg.name.split(".")[node] if key not in sortedSeries: sortedSeries[key]={} sortedSeries[key]['avg']=seriesAvg key = '' for node in nodes: key += seriesWeight.name.split(".")[node] 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(seriesAvg,seriesWeight) ] name='product(%s,%s)' % (seriesWeight.name, seriesAvg.name) productSeries = TimeSeries(name,seriesAvg.start,seriesAvg.end,seriesAvg.step,productValues) productSeries.pathExpression=name 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(sumProducts,sumWeights) ] name = "weightedAverage(%s, %s, %s)" % (','.join(sorted(set(s.pathExpression for s in seriesListAvg))) ,','.join(sorted(set(s.pathExpression for s in sorted(seriesListWeight)))), ','.join(map(str,nodes))) resultSeries = TimeSeries(name,sumProducts.start,sumProducts.end,sumProducts.step,resultValues) resultSeries.pathExpression = name return [resultSeries] def exponentialMovingAverage(requestContext, seriesList, windowSize): """ Takes a series of values and a window size and produces an exponential moving average utilizing the following formula: ema(current) = constant * (Current Value) + (1 - constant) * ema(previous) The Constant is calculated as: 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 [] windowInterval = None if isinstance(windowSize, basestring): delta = parseTimeOffset(windowSize) windowInterval = abs(delta.seconds + (delta.days * 86400)) # set previewSeconds and constant based on windowSize string or integer if windowInterval: previewSeconds = windowInterval constant = (float(2) / (int(windowInterval) + 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 = evaluateTokens(newContext, requestContext['args'][0]) result = [] for series in previewList: if windowInterval: windowPoints = windowInterval / series.step else: windowPoints = int(windowSize) if isinstance(windowSize, basestring): newName = 'exponentialMovingAverage(%s,"%s")' % (series.name, windowSize) else: newName = "exponentialMovingAverage(%s,%s)" % (series.name, windowSize) newSeries = TimeSeries(newName, series.start + previewSeconds, series.end, series.step, []) newSeries.pathExpression = newName window_sum = safeSum(series[:windowPoints]) or 0 count = safeLen(series[:windowPoints]) ema = safeDiv(window_sum, count) if ema is None: ema = 0 else: ema = float(ema) newSeries.append(ema) for i in range(windowPoints, len(series)): if series[i] is not None: ema = float(constant) * float(series[i]) + (1 - float(constant)) * float(ema) newSeries.append(round(ema, 3)) else: newSeries.append(None) result.append(newSeries) return result def movingMedian(requestContext, seriesList, windowSize): """ 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). 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') """ if not seriesList: return [] windowInterval = None if isinstance(windowSize, basestring): delta = parseTimeOffset(windowSize) windowInterval = abs(delta.seconds + (delta.days * 86400)) if windowInterval: previewSeconds = windowInterval else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) # 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 = evaluateTokens(newContext, requestContext['args'][0]) result = [] for series in previewList: if windowInterval: windowPoints = windowInterval / series.step else: windowPoints = int(windowSize) if isinstance(windowSize, basestring): newName = 'movingMedian(%s,"%s")' % (series.name, windowSize) else: newName = "movingMedian(%s,%s)" % (series.name, windowSize) newSeries = TimeSeries(newName, series.start + previewSeconds, series.end, series.step, []) newSeries.pathExpression = newName for i in range(windowPoints,len(series)): window = series[i - windowPoints:i] nonNull = [v for v in window if v is not None] if nonNull: m_index = len(nonNull) / 2 newSeries.append(sorted(nonNull)[m_index]) else: newSeries.append(None) result.append(newSeries) return result 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.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 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.name = "scaleToSeconds(%s,%d)" % (series.name,seconds) series.pathExpression = series.name for i,value in enumerate(series): factor = seconds * 1.0 / series.step series[i] = safeMul(value,factor) return seriesList 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.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 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: return [] name = "powSeries(%s)" % ','.join([s.name for s in seriesList]) values = [] for row in izip(*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) series.pathExpression = name return [series] 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.name = "squareRoot(%s)" % (series.name) for i,value in enumerate(series): series[i] = safePow(value, 0.5) return seriesList 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.name = "invert(%s)" % (series.name) for i,value in enumerate(series): series[i] = safePow(value, -1) return seriesList 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.name = "absolute(%s)" % (series.name) series.pathExpression = series.name for i,value in enumerate(series): series[i] = safeAbs(value) return seriesList 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.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 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: series.name = "offsetToZero(%s)" % (series.name) minimum = safeMin(series) for i,value in enumerate(series): if value is not None: series[i] = value - minimum return seriesList def movingAverage(requestContext, seriesList, windowSize): """ 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). 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') """ if not seriesList: return [] windowInterval = None if isinstance(windowSize, basestring): delta = parseTimeOffset(windowSize) windowInterval = abs(delta.seconds + (delta.days * 86400)) if windowInterval: previewSeconds = windowInterval else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) # 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 = evaluateTokens(newContext, requestContext['args'][0]) result = [] for series in previewList: if windowInterval: windowPoints = windowInterval / series.step else: windowPoints = int(windowSize) if isinstance(windowSize, basestring): newName = 'movingAverage(%s,"%s")' % (series.name, windowSize) else: newName = "movingAverage(%s,%s)" % (series.name, windowSize) newSeries = TimeSeries(newName, series.start + previewSeconds, series.end, series.step, []) newSeries.pathExpression = newName window_sum = safeSum(series[:windowPoints]) or 0 count = safeLen(series[:windowPoints]) newSeries.append(safeDiv(window_sum, count)) for n, last in enumerate(series[windowPoints:-1]): if series[n] is not None: window_sum -= series[n] count -= 1 if last is not None: window_sum += last count += 1 newSeries.append(safeDiv(window_sum, count)) result.append(newSeries) return result def movingSum(requestContext, seriesList, windowSize): """ 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). 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') """ if not seriesList: return [] windowInterval = None if isinstance(windowSize, basestring): delta = parseTimeOffset(windowSize) windowInterval = abs(delta.seconds + (delta.days * 86400)) if windowInterval: previewSeconds = windowInterval else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) # 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 = evaluateTokens(newContext, requestContext['args'][0]) result = [] for series in previewList: if windowInterval: windowPoints = windowInterval / series.step else: windowPoints = int(windowSize) if isinstance(windowSize, basestring): newName = 'movingSum(%s,"%s")' % (series.name, windowSize) else: newName = "movingSum(%s,%s)" % (series.name, windowSize) newSeries = TimeSeries(newName, series.start + previewSeconds, series.end, series.step, []) newSeries.pathExpression = newName window_sum = safeSum(series[:windowPoints]) newSeries.append(window_sum) for n, last in enumerate(series[windowPoints:-1]): if series[n] is not None: window_sum -= series[n] if last is not None: window_sum = (window_sum or 0) + last newSeries.append(window_sum) result.append(newSeries) return result def movingMin(requestContext, seriesList, windowSize): """ 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). 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') """ if not seriesList: return [] windowInterval = None if isinstance(windowSize, basestring): delta = parseTimeOffset(windowSize) windowInterval = abs(delta.seconds + (delta.days * 86400)) if windowInterval: previewSeconds = windowInterval else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) # 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 = evaluateTokens(newContext, requestContext['args'][0]) result = [] for series in previewList: if windowInterval: windowPoints = windowInterval / series.step else: windowPoints = int(windowSize) if isinstance(windowSize, basestring): newName = 'movingMin(%s,"%s")' % (series.name, windowSize) else: newName = "movingMin(%s,%s)" % (series.name, windowSize) newSeries = TimeSeries(newName, series.start + previewSeconds, series.end, series.step, []) newSeries.pathExpression = newName for i in range(windowPoints,len(series)): window = series[i - windowPoints:i] newSeries.append(safeMin(window)) result.append(newSeries) return result def movingMax(requestContext, seriesList, windowSize): """ 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). 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') """ if not seriesList: return [] windowInterval = None if isinstance(windowSize, basestring): delta = parseTimeOffset(windowSize) windowInterval = abs(delta.seconds + (delta.days * 86400)) if windowInterval: previewSeconds = windowInterval else: previewSeconds = max([s.step for s in seriesList]) * int(windowSize) # 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 = evaluateTokens(newContext, requestContext['args'][0]) result = [] for series in previewList: if windowInterval: windowPoints = windowInterval / series.step else: windowPoints = int(windowSize) if isinstance(windowSize, basestring): newName = 'movingMax(%s,"%s")' % (series.name, windowSize) else: newName = "movingMax(%s,%s)" % (series.name, windowSize) newSeries = TimeSeries(newName, series.start + previewSeconds, series.end, series.step, []) newSeries.pathExpression = newName for i in range(windowPoints,len(series)): window = series[i - windowPoints:i] newSeries.append(safeMax(window)) result.append(newSeries) return result 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') def consolidateBy(requestContext, seriesList, consolidationFunc): """ Takes one metric or a wildcard seriesList and a consolidation function name. Valid function names are 'sum', 'average', 'min', and 'max'. 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', or 'min'. 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.name = 'consolidateBy(%s,"%s")' % (series.name, series.consolidationFunc) series.pathExpression = series.name return seriesList 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 newName = "derivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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 nonNegativeDerivative 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 step = series.step for val in series: if prev is None: newValues.append(None) prev = val continue if val is None: newValues.append(None) step = step * 2 continue diff = val - prev if diff >= 0: newValues.append(diff / step) elif maxValue is not None and maxValue >= val: newValues.append( ((maxValue - prev) + val + 1) / step ) else: newValues.append(None) step = series.step prev = val newName = "perSecond(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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: newValues = [] prev = [] for val in series: if len(prev) < steps: newValues.append(None) prev.append(val) continue newValues.append(prev.pop(0)) prev.append(val) newName = "delay(%s,%d)" % (series.name, steps) newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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) newName = "integral(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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 newName = "integralByInterval(%s,'%s')" % (series.name, intervalUnit) newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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: if None in (prev, val): newValues.append(None) prev = val continue diff = val - prev if diff >= 0: newValues.append(diff) elif maxValue is not None and maxValue >= val: newValues.append( (maxValue - prev) + val + 1 ) else: newValues.append(None) prev = val newName = "nonNegativeDerivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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__': newName = "stacked(%s)" % series.name else: newName = series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.options['stacked'] = True newSeries.pathExpression = newName results.append(newSeries) requestContext['totalStack'][stackName] = totalStack return results 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 lower.name = upper.name = "areaBetween(%s)" % upper.pathExpression return seriesList 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 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 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: name = series.name 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 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. Node indices are 0 indexed. .. code-block:: none &target=aliasByNode(ganglia.*.cpu.load5,1) """ if isinstance(nodes, int): nodes=[nodes] for series in seriesList: pathExpression = _getFirstPathExpression(series.name) metric_pieces = pathExpression.split('.') series.name = '.'.join(metric_pieces[n] for n in nodes) return seriesList 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) 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') """ def last(s): "Work-around for the missing last point" v = s[-1] if v is None: return s[-2] return v valueFuncs = { 'avg': lambda s: safeDiv(safeSum(s), safeLen(s)), 'total': safeSum, 'min': safeMin, 'max': safeMax, 'last': last } system = None if valueTypes[-1] in ('si', 'binary'): system = valueTypes[-1] valueTypes = valueTypes[:-1] for valueType in valueTypes: valueFunc = valueFuncs.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 value is not None: formatted = "%.2f%s" % format_units(value, system=system) series.name = "%-20s%-5s%-10s" % (series.name, valueType, formatted) return seriesList 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 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 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 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)) newName = "log(%s, %s)" % (series.name, base) newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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. """ results = [] for series in seriesList: val = safeMax(series) if val is not None and val > n: results.append(series) return results 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. """ results = [] for series in seriesList: val = safeMin(series) if val is not None and val > n: results.append(series) return results 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. """ result = [] for series in seriesList: val = safeMax(series) if val is None or val <= n: result.append(series) return result 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. """ result = [] for series in seriesList: val = safeMin(series) if val is None or val <= n: result.append(series) return result 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. """ return sorted( seriesList, key=safeLast )[-n:] 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. """ result_list = sorted( seriesList, key=lambda s: safeMax(s) )[-n:] return sorted(result_list, key=lambda s: max(s), reverse=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. """ return sorted( seriesList, key=safeLast )[:n] 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. """ results = [] for series in seriesList: val = safeLast(series) if val is not None and val >= n: results.append(series) return results 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. """ results = [] for series in seriesList: val = safeLast(series) if val is None or val <= n: results.append(series) return results 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. """ return sorted( seriesList, key=lambda s: safeDiv(safeSum(s),safeLen(s)) )[-n:] 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. """ return sorted( seriesList, key=lambda s: safeDiv(safeSum(s),safeLen(s)) )[:n] 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. """ results = [] for series in seriesList: val = safeAvg(series) if val is not None and val >= n: results.append(series) return results 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. """ results = [] for series in seriesList: val = safeAvg(series) if val is None or val <= n: results.append(series) return results 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 = TimeSeries( s.name, s.start, s.end, s.step, 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: name = 'nPercentile(%s, %g)' % (s_copy.name, n) point_count = int((s.end - s.start)/s.step) perc_series = TimeSeries(name, s_copy.start, s_copy.end, s_copy.step, [perc_val] * point_count ) perc_series.pathExpression = name results.append(perc_series) return results def averageOutsidePercentile(requestContext, seriesList, n): """ Removes functions lying inside an average percentile interval """ averages = [] for s in seriesList: averages.append(safeDiv(safeSum(s), safeLen(s))) if n < 50: n = 100 - n; lowPercentile = _getPercentile(averages, 100 - n) highPercentile = _getPercentile(averages, n) return [s for s in seriesList if not lowPercentile < safeDiv(safeSum(s), safeLen(s)) < highPercentile] def removeBetweenPercentile(requestContext, seriesList, n): """ Removes lines who do not have an value lying in the x-percentile of all the values at a moment """ if n < 50: n = 100 - n transposed = 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] 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 > percentile: s[index] = None return seriesList 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 > n: s[index] = None return seriesList 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 < percentile: s[index] = None return seriesList 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 < n: s[index] = None return seriesList 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] def sortByName(requestContext, seriesList, natural=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 paddedName(name): return re.sub("(\d+)", lambda x: "{0:010}".format(int(x.group(0))), name) def compare(x,y): return cmp(x.name, y.name) def natSortCompare(x,y): return cmp(paddedName(x.name), paddedName(y.name)) if natural: seriesList.sort(natSortCompare) else: seriesList.sort(compare) return seriesList def sortByTotal(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. Sorts the list of metrics by the sum of values across the time period specified. """ def compare(x,y): return cmp(safeSum(y), safeSum(x)) seriesList.sort(compare) return seriesList def sortByMaxima(requestContext, seriesList): """ Takes one metric or a wildcard seriesList. Sorts the list of metrics 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) """ def compare(x,y): return cmp(max(y), max(x)) seriesList.sort(compare) return seriesList 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. Example: .. code-block:: none &target=sortByMinima(server*.instance*.memory.free) """ def compare(x,y): return cmp(min(x), min(y)) newSeries = [series for series in seriesList if max(series) > 0] newSeries.sort(compare) return newSeries 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") """ newSeries = [] for series in seriesList: newname = re.sub(search, replace, series.name) if max(series) > value: n = evaluateTarget(requestContext, newname) if n is not None and len(n) > 0: newSeries.append(n[0]) return newSeries 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 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 = safeDiv( safeSum(series), safeLen(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=lambda i: i[0], reverse=True) #sort by sigma return [ series for (_, series) in deviants ][:n] #return the n most deviant series 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): stdevSeries = TimeSeries("stdev(%s,%d)" % (series.name, int(points)), series.start, series.end, series.step, []) stdevSeries.pathExpression = "stdev(%s,%d)" % (series.name, int(points)) 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 def secondYAxis(requestContext, seriesList): """ Graph the series on the secondary Y axis. """ for series in seriesList: series.options['secondYAxis'] = True series.name= 'secondYAxis(%s)' % series.name return seriesList 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): alpha = gamma = 0.1 beta = 0.0035 # season is currently one day season_length = (24*60*60) / series.step intercept = 0 slope = 0 pred = 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 forecastName = "holtWintersForecast(%s)" % series.name forecastSeries = TimeSeries(forecastName, series.start, series.end , series.step, predictions) forecastSeries.pathExpression = forecastName # make the new deviation series deviationName = "holtWintersDeviation(%s)" % series.name deviationSeries = TimeSeries(deviationName, series.start, series.end , series.step, deviations) deviationSeries.pathExpression = deviationName results = { 'predictions': forecastSeries , 'deviations': deviationSeries , 'intercepts': intercepts , 'slopes': slopes , 'seasonals': seasonals } return results def holtWintersForecast(requestContext, seriesList, bootstrapInterval='7d'): """ 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 = evaluateTokens(newContext, requestContext['args'][0]) results = [] for series in previewList: analysis = holtWintersAnalysis(series) predictions = analysis['predictions'] windowPoints = previewSeconds / predictions.step result = TimeSeries("holtWintersForecast(%s)" % series.name, predictions.start + previewSeconds, predictions.end, predictions.step, predictions[windowPoints:]) result.pathExpression = result.name results.append(result) return results def holtWintersConfidenceBands(requestContext, seriesList, delta=3, bootstrapInterval='7d'): """ 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 = evaluateTokens(newContext, requestContext['args'][0]) results = [] for series in previewList: analysis = holtWintersAnalysis(series) data = analysis['predictions'] windowPoints = previewSeconds / data.step forecast = TimeSeries(data.name, data.start + previewSeconds, data.end, data.step, data[windowPoints:]) forecast.pathExpression = data.pathExpression data = analysis['deviations'] windowPoints = previewSeconds / data.step deviation = TimeSeries(data.name, data.start + previewSeconds, data.end, data.step, data[windowPoints:]) 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) upperName = "holtWintersConfidenceUpper(%s)" % series.name lowerName = "holtWintersConfidenceLower(%s)" % series.name upperSeries = TimeSeries(upperName, forecast.start, forecast.end , forecast.step, upperBand) lowerSeries = TimeSeries(lowerName, forecast.start, forecast.end , forecast.step, lowerBand) upperSeries.pathExpression = series.pathExpression lowerSeries.pathExpression = series.pathExpression results.append(lowerSeries) results.append(upperSeries) return results def holtWintersAberration(requestContext, seriesList, delta=3, bootstrapInterval='7d'): """ 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 = [] for series in seriesList: confidenceBands = holtWintersConfidenceBands(requestContext, [series], delta, bootstrapInterval) lowerBand = confidenceBands[0] upperBand = confidenceBands[1] 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) newName = "holtWintersAberration(%s)" % series.name results.append(TimeSeries(newName, series.start, series.end , series.step, aberration)) return results def holtWintersConfidenceArea(requestContext, seriesList, delta=3, bootstrapInterval='7d'): """ 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) results = areaBetween(requestContext, bands) for series in results: series.name = series.name.replace('areaBetween', 'holtWintersConfidenceArea') return results 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 liner 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 = [] for series in seriesList: source = evaluateTarget(sourceContext, series.pathExpression) sourceList.extend(source) for source,series in zip(sourceList, seriesList): 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) newSeries.pathExpression = newSeries.name results.append(newSeries) return results 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.name = 'drawAsInfinite(%s)' % series.name return seriesList 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 def dashed(requestContext, *seriesList): """ 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) """ if len(seriesList) == 2: dashLength = seriesList[1] else: dashLength = 5 for series in seriesList[0]: series.name = 'dashed(%s, %g)' % (series.name, dashLength) series.options['dashed'] = dashLength return seriesList[0] def timeStack(requestContext, seriesList, timeShiftUnit, timeShiftStart, timeShiftEnd): """ 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, they will all have the same pathExpression, which is all we care about. 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, series.pathExpression): 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 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) > 0: # if len(seriesList) > 1, they will all have the same pathExpression, which is all we care about. series = seriesList[0] for shiftedSeries in evaluateTarget(myContext, series.pathExpression): 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 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.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 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]) series.pathExpression = name return [series] def aggregateLine(requestContext, seriesList, func='avg'): """ Takes a metric or wildcard seriesList and draws a horizontal line based on the function applied to each 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') """ t_funcs = { 'avg': safeAvg, 'min': safeMin, 'max': safeMax } if func not in t_funcs: raise ValueError("Invalid function %s" % func) results = [] for series in seriesList: value = t_funcs[func](series) if value is not None: name = 'aggregateLine(%s, %g)' % (series.name, value) else: name = 'aggregateLine(%s, None)' % (series.name) [series] = constantLine(requestContext, value) series.name = name series.pathExpression = series.name results.append(series) return results 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]) series.options['drawAsInfinite'] = True if color: series.color = color return [series] 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] 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(*referenceSeries)] else: defaults = None for series in seriesList: 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(series, defaults)] else: values = [transform(v, default) for v in series] series.extend(values) del series[:len(values)] return 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.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 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 delta = timedelta(seconds=step) start = int(epoch(requestContext["startTime"])) end = int(epoch(requestContext["endTime"])) values = range(start, end, step) series = TimeSeries(name, start, end, step, values) series.pathExpression = 'identity("%s")' % name return [series] 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(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name else: series = constantLine(requestContext, 0).pop() series.pathExpression = "countSeries()" return [series] 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 def mapSeries(requestContext, seriesList, mapNode): """ Short form: ``map()`` Takes a seriesList and maps it to a list of seriesList. Each seriesList has the given mapNode 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.* ] """ metaSeries = {} keys = [] for series in seriesList: key = series.name.split(".")[mapNode] if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) return [ metaSeries[k] for k in keys ] 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] = SeriesFunctions[reduceFunction](requestContext,*[[l] for l in metaSeries[key]])[0] metaSeries[key].name = key return [ metaSeries[key] for key in keys ] 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(%.2XX, 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 def groupByNode(requestContext, seriesList, nodeNum, callback): """ 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),... """ return groupByNodes(requestContext, seriesList, callback, nodeNum) 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*,"sumSeries",1,4) Would return multiple series which are each the result of applying the "sumSeries" function 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),... """ metaSeries = {} keys = [] if isinstance(nodes, int): nodes=[nodes] for series in seriesList: key = '.'.join(series.name.split(".")[n] for n in nodes) if key not in metaSeries: metaSeries[key] = [series] keys.append(key) else: metaSeries[key].append(series) for key in metaSeries.keys(): metaSeries[key] = SeriesFunctions[callback](requestContext, metaSeries[key])[0] metaSeries[key].name = key return [ metaSeries[key] for key in keys ] 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)] 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)] def smartSummarize(requestContext, seriesList, intervalString, func='sum', alignToFrom=False): """ Smarter experimental version of summarize. The alignToFrom parameter has been deprecated, it no longer has any effect. Alignment happens automatically for days, hours, and minutes. """ if alignToFrom: log.info("Deprecated parameter 'alignToFrom' is being ignored.") results = [] delta = parseTimeOffset(intervalString) interval = delta.seconds + (delta.days * 86400) # Adjust the start time to fit an entire day for intervals >= 1 day 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 = evaluateTokens(requestContext, requestContext['args'][0]) for series in seriesList: buckets = {} # { timestamp: [values] } timestamps = range( int(series.start), int(series.end), int(series.step) ) datapoints = zip(timestamps, series) # Populate buckets for timestamp_, value in datapoints: bucketInterval = int((timestamp_ - series.start) / interval) if bucketInterval not in buckets: buckets[bucketInterval] = [] if value is not None: buckets[bucketInterval].append(value) newValues = [] for timestamp_ in range(series.start, series.end, interval): bucketInterval = int((timestamp_ - series.start) / interval) bucket = buckets.get(bucketInterval, []) if bucket: if func == 'avg': newValues.append( float(sum(bucket)) / float(len(bucket)) ) elif func == 'last': newValues.append( bucket[len(bucket)-1] ) elif func == 'max': newValues.append( max(bucket) ) elif func == 'min': newValues.append( min(bucket) ) else: newValues.append( sum(bucket) ) else: newValues.append( None ) newName = "smartSummarize(%s, \"%s\", \"%s\")" % (series.name, intervalString, func) alignedEnd = series.start + (bucketInterval * interval) + interval newSeries = TimeSeries(newName, series.start, alignedEnd, interval, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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 'avg' 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. 'max', 'min' or 'last' can also be specified. 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: buckets = {} timestamps = range( int(series.start), int(series.end), int(series.step) ) datapoints = zip(timestamps, series) for timestamp_, value in datapoints: if alignToFrom: bucketInterval = int((timestamp_ - series.start) / interval) else: bucketInterval = timestamp_ - (timestamp_ % interval) if bucketInterval not in buckets: buckets[bucketInterval] = [] if value is not None: buckets[bucketInterval].append(value) if alignToFrom: newStart = series.start newEnd = series.end else: newStart = series.start - (series.start % interval) newEnd = series.end - (series.end % interval) + interval newValues = [] for timestamp_ in range(newStart, newEnd, interval): if alignToFrom: newEnd = timestamp_ bucketInterval = int((timestamp_ - series.start) / interval) else: bucketInterval = timestamp_ - (timestamp_ % interval) bucket = buckets.get(bucketInterval, []) if bucket: if func == 'avg': newValues.append( float(sum(bucket)) / float(len(bucket)) ) elif func == 'last': newValues.append( bucket[len(bucket)-1] ) elif func == 'max': newValues.append( max(bucket) ) elif func == 'min': newValues.append( min(bucket) ) else: newValues.append( sum(bucket) ) else: newValues.append( None ) if alignToFrom: newEnd += interval newName = "summarize(%s, \"%s\", \"%s\"%s)" % (series.name, intervalString, func, alignToFrom and ", true" or "") newSeries = TimeSeries(newName, newStart, newEnd, interval, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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) elif interval >= HOUR: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour) elif interval >= MINUTE: requestContext['startTime'] = datetime(s.year, s.month, s.day, s.hour, s.minute) # Ignore the originally fetched data and pull new using # the modified requestContext. seriesList = evaluateTokens(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: length = len(series) 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) newName = 'hitcount(%s, "%s"%s)' % (series.name, intervalString, alignToInterval and ", true" or "") newSeries = TimeSeries(newName, newStart, series.end, interval, newValues) newSeries.pathExpression = newName results.append(newSeries) return results 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) """ 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) series.pathExpression = name return [series] 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)] def removeEmptySeries(requestContext, seriesList): """ 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. """ return [ series for series in seriesList if safeIsNotEmpty(series) ] 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)] 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(datetime.fromtimestamp(start_timestamp), datetime.fromtimestamp(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') result_series.pathExpression = name return [result_series] def pieAverage(requestContext, series): return safeDiv(safeSum(series),safeLen(series)) def pieMaximum(requestContext, series): return max(series) def pieMinimum(requestContext, series): return min(series) PieFunctions = { 'average': pieAverage, 'maximum': pieMaximum, 'minimum': pieMinimum, } SeriesFunctions = { # Combine functions 'sumSeries': sumSeries, 'sum': sumSeries, 'multiplySeries': multiplySeries, 'averageSeries': averageSeries, 'stddevSeries': stddevSeries, 'avg': averageSeries, 'sumSeriesWithWildcards': sumSeriesWithWildcards, 'averageSeriesWithWildcards': averageSeriesWithWildcards, 'multiplySeriesWithWildcards': multiplySeriesWithWildcards, 'minSeries': minSeries, 'maxSeries': maxSeries, 'rangeOfSeries': rangeOfSeries, 'percentileOfSeries': percentileOfSeries, 'countSeries': countSeries, 'weightedAverage': weightedAverage, # Transform functions 'scale': scale, 'scaleToSeconds': scaleToSeconds, 'offset': offset, 'offsetToZero': offsetToZero, 'derivative': derivative, 'delay': delay, 'squareRoot': squareRoot, 'pow': pow, 'powSeries': powSeries, 'perSecond': perSecond, 'integral': integral, 'integralByInterval' : integralByInterval, 'nonNegativeDerivative': nonNegativeDerivative, 'log': logarithm, 'invert': invert, 'timeStack': timeStack, 'timeShift': timeShift, 'timeSlice': timeSlice, 'summarize': summarize, 'smartSummarize': smartSummarize, 'hitcount': hitcount, 'absolute': absolute, 'interpolate': interpolate, # Calculate functions 'movingAverage': movingAverage, 'movingMedian': movingMedian, 'movingSum': movingSum, 'movingMin': movingMin, 'movingMax': movingMax, 'stdev': stdev, 'holtWintersForecast': holtWintersForecast, 'holtWintersConfidenceBands': holtWintersConfidenceBands, 'holtWintersConfidenceArea': holtWintersConfidenceArea, 'holtWintersAberration': holtWintersAberration, 'linearRegression': linearRegression, 'asPercent': asPercent, 'pct': asPercent, 'diffSeries': diffSeries, 'divideSeries': divideSeries, 'divideSeriesLists': divideSeriesLists, 'exponentialMovingAverage': exponentialMovingAverage, # Series Filter functions 'fallbackSeries': fallbackSeries, 'mostDeviant': mostDeviant, 'highestCurrent': highestCurrent, 'lowestCurrent': lowestCurrent, 'highestMax': highestMax, 'currentAbove': currentAbove, 'currentBelow': currentBelow, 'highestAverage': highestAverage, 'lowestAverage': lowestAverage, 'averageAbove': averageAbove, 'averageBelow': averageBelow, 'maximumAbove': maximumAbove, 'minimumAbove': minimumAbove, 'maximumBelow': maximumBelow, 'minimumBelow': minimumBelow, 'nPercentile': nPercentile, 'limit': limit, 'sortByTotal': sortByTotal, 'sortByName': sortByName, 'averageOutsidePercentile': averageOutsidePercentile, 'removeBetweenPercentile': removeBetweenPercentile, 'sortByMaxima': sortByMaxima, 'sortByMinima': sortByMinima, 'useSeriesAbove': useSeriesAbove, 'exclude': exclude, 'grep': grep, 'removeEmptySeries': removeEmptySeries, # Data Filter functions 'removeAbovePercentile': removeAbovePercentile, 'removeAboveValue': removeAboveValue, 'removeBelowPercentile': removeBelowPercentile, 'removeBelowValue': removeBelowValue, # Special functions 'legendValue': legendValue, 'alias': alias, 'aliasSub': aliasSub, 'aliasByNode': aliasByNode, 'aliasByMetric': aliasByMetric, 'cactiStyle': cactiStyle, 'color': color, 'alpha': alpha, 'cumulative': cumulative, 'consolidateBy': consolidateBy, 'keepLastValue': keepLastValue, 'changed': changed, 'drawAsInfinite': drawAsInfinite, 'secondYAxis': secondYAxis, 'lineWidth': lineWidth, 'dashed': dashed, 'substr': substr, 'group': group, 'map': mapSeries, 'mapSeries': mapSeries, 'reduce': reduceSeries, 'reduceSeries': reduceSeries, 'applyByNode': applyByNode, 'groupByNode': groupByNode, 'groupByNodes' : groupByNodes, 'constantLine': constantLine, 'stacked': stacked, 'areaBetween': areaBetween, 'threshold': threshold, 'transformNull': transformNull, 'isNonNull': isNonNull, 'threshold' : threshold, 'verticalLine' : verticalLine, 'identity': identity, 'aggregateLine': aggregateLine, # test functions 'time': timeFunction, "sin": sinFunction, "randomWalk": randomWalkFunction, 'timeFunction': timeFunction, "sinFunction": sinFunction, "randomWalkFunction": randomWalkFunction, # events 'events': events, } # Avoid import circularity if not environ.get('READTHEDOCS'): from graphite.render.evaluator import evaluateTarget, evaluateTokens graphite-web-1.0.2/webapp/graphite/render/glyph.py0000644000000000000000000021513213131244446022071 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 try: import cairocffi as cairo except ImportError: import cairo import StringIO from datetime import datetime, timedelta from urllib import unquote_plus from ConfigParser import SafeConfigParser from django.conf import settings from graphite.render.datalib import TimeSeries from graphite.util import json import pytz 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 > 10 or spanPrefix != prefix: if type(value) is float: return "%.1f %s" % (value, prefix) else: return "%d %s" % (int(value), prefix) elif span > 3: return "%.1f %s" % (float(value), prefix) elif 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 bestDivisor = 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 bestDivisor = divisor 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 = StringIO.StringIO() self.surface = cairo.SVGSurface(self.surfaceData, self.width, self.height) elif outputFormat == 'pdf': self.surfaceData = StringIO.StringIO() 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 type(value) in (str,unicode) 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 = 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) fileObj.write(""" """ % json.dumps(metaData)) 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'] = unicode(params['yUnitSystem']).lower() if params['yUnitSystem'] not in UnitSystems.keys(): 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 = self.colors.next() 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( unicode( unquote_plus(params['title']) ) ) if params.get('vtitle'): self.drawVTitle( unicode( unquote_plus(params['vtitle']) ) ) if self.secondYAxis and params.get('vtitleRight'): self.drawVTitle( unicode( 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 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' : self.colors.next(), '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 ] 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 = unicode(int(slice['value'])) extents = self.getExtents(label) 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 xrange(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 = math.floor(v2) if units: prefix = "%s%s" % (prefix, units) return v2, prefix if (v - math.floor(v)) < 0.00000000001 and v > 1 : v = 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.0.2/webapp/graphite/render/attime.py0000644000000000000000000001171313131244465022231 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 from time import daylight 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'] def parseATTime(s, tzinfo=None, now=None): if tzinfo is None: tzinfo = pytz.timezone(settings.TIME_ZONE) 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) elif ':' in s and len(s) == 13: return tzinfo.localize(datetime.strptime(s,'%H:%M%Y%m%d'), daylight) 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 or now).astimezone(tzinfo) + parseTimeOffset(offset)) def parseTimeReference(ref): if isinstance(ref, datetime): return ref if not ref or ref == 'now': return datetime.now(pytz.utc) #Time-of-day reference i = ref.find(':') hour,min = 0,0 if i != -1: hour = int( ref[:i] ) min = 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:] if ref.startswith('noon'): hour,min = 12,0 ref = ref[4:] elif ref.startswith('midnight'): hour,min = 0,0 ref = ref[8:] elif ref.startswith('teatime'): hour,min = 16,0 ref = ref[7:] refDate = datetime.now(pytz.utc).replace(hour=hour,minute=min,second=0) #Day reference if ref in ('yesterday','today','tomorrow'): #yesterday, today, tomorrow if ref == 'yesterday': refDate = refDate - timedelta(days=1) if ref == 'tomorrow': refDate = 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 = refDate.replace(year=y) try: # Fix for Bug #551771 refDate = refDate.replace(month=m) refDate = refDate.replace(day=d) except: refDate = refDate.replace(day=d) refDate = refDate.replace(month=m) elif len(ref) == 8 and ref.isdigit(): #YYYYMMDD refDate = refDate.replace(year= int(ref[:4])) try: # Fix for Bug #551771 refDate = refDate.replace(month= int(ref[4:6])) refDate = refDate.replace(day= int(ref[6:8])) except: refDate = refDate.replace(day= int(ref[6:8])) refDate = refDate.replace(month= int(ref[4:6])) elif ref[:3] in months: #MonthName DayOfMonth refDate = refDate.replace(month= months.index(ref[:3]) + 1) if ref[-2:].isdigit(): refDate = refDate.replace(day= int(ref[-2:])) elif ref[-1:].isdigit(): refDate = refDate.replace(day= int(ref[-1:])) else: raise Exception("Day of month required after month name") 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 Exception("Unknown day reference") return refDate def parseTimeOffset(offset): if not offset: return timedelta() t = timedelta() if offset[0].isdigit(): sign = 1 else: sign = { '+' : 1, '-' : -1 }[offset[0]] 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': unitString = 'days' num = num * 30 if unitString == 'years': unitString = 'days' num = num * 365 t += timedelta(**{ unitString : sign * num}) return t def getUnitString(s): if s.startswith('s'): return 'seconds' if s.startswith('min'): return 'minutes' if s.startswith('h'): return 'hours' if s.startswith('d'): return 'days' if s.startswith('w'): return 'weeks' if s.startswith('mon'): return 'months' if s.startswith('y'): return 'years' raise Exception("Invalid offset unit '%s'" % s) graphite-web-1.0.2/webapp/graphite/render/evaluator.py0000644000000000000000000000672313131244465022755 0ustar rootroot00000000000000import re from graphite.render.grammar import grammar from graphite.render.datalib import fetchData, TimeSeries def evaluateTarget(requestContext, target): tokens = grammar.parseString(target) result = evaluateTokens(requestContext, tokens) if type(result) is TimeSeries: return [result] #we have to return a list of TimeSeries objects else: return result def evaluateTokens(requestContext, tokens, replacements=None): if tokens.template: arglist = dict() if tokens.template.kwargs: arglist.update(dict([(kwarg.argname, evaluateTokens(requestContext, kwarg.args[0])) for kwarg in tokens.template.kwargs])) if tokens.template.args: arglist.update(dict([(str(i+1), evaluateTokens(requestContext, arg)) for i, arg in enumerate(tokens.template.args)])) if 'template' in requestContext: arglist.update(requestContext['template']) return evaluateTokens(requestContext, tokens.template, arglist) elif tokens.expression: return evaluateTokens(requestContext, tokens.expression, replacements) elif tokens.pathExpression: expression = tokens.pathExpression if replacements: for name in replacements: if expression == '$'+name: val = replacements[name] if not isinstance(val, str) and not isinstance(val, basestring): 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) elif 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") func = SeriesFunctions[tokens.call.funcname] args = [evaluateTokens(requestContext, arg, replacements) for arg in tokens.call.args] requestContext['args'] = tokens.call.args 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 [] elif tokens.number: if tokens.number.integer: return int(tokens.number.integer) elif tokens.number.float: return float(tokens.number.float) elif tokens.number.scientific: return float(tokens.number.scientific[0]) elif tokens.string: return tokens.string[1:-1] elif tokens.boolean: return tokens.boolean[0] == 'true' else: raise ValueError("unknown token in target evaluator") def extractPathExpressions(targets): # Returns a list of unique pathExpressions found in the targets list pathExpressions = set() def extractPathExpression(tokens): if tokens.expression: return extractPathExpression(tokens.expression) elif tokens.pathExpression: pathExpressions.add(tokens.pathExpression) elif tokens.call: for a in tokens.call.args: extractPathExpression(a) for target in targets: tokens = grammar.parseString(target) extractPathExpression(tokens) return list(pathExpressions) # Avoid import circularities from graphite.render.functions import (SeriesFunctions, NormalizeEmptyResultError) # noqa graphite-web-1.0.2/webapp/graphite/render/datalib.py0000755000000000000000000002313213131244465022347 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 graphite.logger import log from graphite.storage import STORE from graphite.readers import FetchInProgress from django.conf import settings from graphite.util import timebounds, logtime from traceback import format_exc class TimeSeries(list): def __init__(self, name, start, end, step, values, consolidate='average'): 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 = name def __eq__(self, other): if isinstance(other, TimeSeries): color_check = True if hasattr(self, 'color'): if hasattr(other, 'color'): color_check = (self.color == other.color) else: color_check = False elif hasattr(other, 'color'): color_check = False return ((self.name, self.start, self.end, self.step, self.consolidationFunc, self.valuesPerPoint, self.options) == (other.name, other.start, other.end, other.step, other.consolidationFunc, other.valuesPerPoint, other.options)) and list.__eq__(self, other) and color_check return False 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) def __consolidatingGenerator(self, gen): buf = [] for x in gen: buf.append(x) if len(buf) == self.valuesPerPoint: while None in buf: buf.remove(None) if buf: yield self.__consolidate(buf) buf = [] else: yield None while None in buf: buf.remove(None) if buf: yield self.__consolidate(buf) else: yield None raise StopIteration def __consolidate(self, values): usable = [v for v in values if v is not None] if not usable: return None if self.consolidationFunc == 'sum': return sum(usable) if self.consolidationFunc == 'average': return float(sum(usable)) / len(usable) if self.consolidationFunc == 'max': return max(usable) if self.consolidationFunc == 'min': return min(usable) raise Exception("Invalid consolidation function: '%s'" % self.consolidationFunc) def __repr__(self): return 'TimeSeries(name=%s, start=%s, end=%s, step=%s)' % (self.name, self.start, self.end, self.step) def getInfo(self): """Pickle-friendly representation of the series""" return { 'name' : self.name, 'start' : self.start, 'end' : self.end, 'step' : self.step, 'values' : list(self), 'pathExpression' : self.pathExpression, } @logtime() def _fetchData(pathExpr, startTime, endTime, now, requestContext, seriesList): if settings.REMOTE_PREFETCH_DATA: matching_nodes = [node for node in STORE.find(pathExpr, startTime, endTime, local=True)] # inflight_requests is only present if at least one remote store # has been queried if 'inflight_requests' in requestContext: fetches = requestContext['inflight_requests'] else: fetches = {} def result_queue_generator(): for node in matching_nodes: if node.is_leaf: yield (node.path, node.fetch(startTime, endTime, now, requestContext)) log.info( 'render.datalib.fetchData:: result_queue_generator got {count} fetches' .format(count=len(fetches)), ) for key, fetch in fetches.iteritems(): log.info( 'render.datalib.fetchData:: getting results of {host}' .format(host=key), ) if isinstance(fetch, FetchInProgress): fetch = fetch.waitForResults() if fetch is None: log.info('render.datalib.fetchData:: fetch is None') continue for result in fetch: if result['pathExpression'] == pathExpr: yield ( result['path'], ( (result['start'], result['end'], result['step']), result['values'], ), ) result_queue = result_queue_generator() else: matching_nodes = [node for node in STORE.find(pathExpr, startTime, endTime, local=requestContext['localOnly'])] result_queue = [ (node.path, node.fetch(startTime, endTime, now, requestContext)) for node in matching_nodes if node.is_leaf ] log.info("render.datalib.fetchData :: starting to merge") for path, results in result_queue: if isinstance(results, FetchInProgress): results = results.waitForResults() if not results: log.info("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) # hack to pass expressions through to render functions series.pathExpression = pathExpr # Used as a cache to avoid recounting series None values below. series_best_nones = {} 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]) if known_nones > candidate_nones: if settings.REMOTE_STORE_MERGE_RESULTS: # This series has potential data that might be missing from # earlier series. Attempt to merge in useful data and update # the cache count. log.info("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 settings.REMOTE_PREFETCH_DATA: # if we're using REMOTE_PREFETCH_DATA we can save some time by skipping # find, but that means we don't know how many nodes to expect so we # have to iterate over all returned results continue # In case if we are merging data - the existing series has no gaps and # there is nothing to merge together. Save ourselves some work here. # # OR - if we picking best serie: # # We already have this series in the seriesList, and the # candidate is 'worse' than what we already have, we don't need # to compare anything else. Save ourselves some work here. break 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)] # Data retrieval API @logtime() def fetchData(requestContext, pathExpr): seriesList = {} (startTime, endTime, now) = timebounds(requestContext) retries = 1 # start counting at one to make log output and settings more readable while True: try: seriesList = _fetchData(pathExpr, startTime, endTime, now, requestContext, seriesList) break except Exception: if retries >= settings.MAX_FETCH_RETRIES: log.exception("Failed after %s retry! 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 return seriesList def nonempty(series): for value in series: if value is not None: return True return False graphite-web-1.0.2/webapp/graphite/render/urls.py0000644000000000000000000000153313131244446021731 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('local/?$', views.renderLocalView, name='render_local'), url('~(?P[^/]+)/(?P[^/]+)/?', views.renderMyGraphView, name='render_my_graph'), url('', views.renderView, name='render'), ] graphite-web-1.0.2/webapp/graphite/render/views.py0000755000000000000000000005141613131244465022112 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 httplib from datetime import datetime from time import time from random import shuffle from urllib import urlencode from urlparse import urlsplit, urlunsplit from cgi import parse_qs from cStringIO import StringIO try: import cPickle as pickle except ImportError: import pickle from graphite.compat import HttpResponse from graphite.user_util import getProfileByUsername from graphite.util import json, unpickle from graphite.remote_storage import extractForwardHeaders, prefetchRemoteData from graphite.storage import STORE from graphite.logger import log from graphite.render.evaluator import evaluateTarget, extractPathExpressions from graphite.render.attime import parseATTime from graphite.render.functions import PieFunctions from graphite.render.hashing import hashRequest, hashData from graphite.render.glyph import GraphTypes 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 def renderView(request): start = time() (graphOptions, requestOptions) = parseOptions(request) useCache = 'noCache' not in requestOptions cacheTimeout = requestOptions['cacheTimeout'] requestContext = { 'startTime' : requestOptions['startTime'], 'endTime' : requestOptions['endTime'], 'now': requestOptions['now'], 'localOnly' : requestOptions['localOnly'], 'template' : requestOptions['template'], 'tzinfo' : requestOptions['tzinfo'], 'forwardHeaders': extractForwardHeaders(request), 'data' : [] } data = requestContext['data'] # First we check the request cache if useCache: requestKey = hashRequest(request) cachedResponse = cache.get(requestKey) if cachedResponse: log.cache('Request-Cache hit [%s]' % requestKey) log.rendering('Returned cached response in %.6f' % (time() - start)) return cachedResponse else: log.cache('Request-Cache miss [%s]' % requestKey) # Now we prepare the requested data if requestOptions['graphType'] == 'pie': if settings.REMOTE_PREFETCH_DATA and not requestOptions.get('localOnly'): targets = [target for target in requestOptions['targets'] if target.find(':') < 0] log.rendering("Prefetching remote data") pathExpressions = extractPathExpressions(targets) prefetchRemoteData(STORE.remote_stores, requestContext, pathExpressions) for target in requestOptions['targets']: if target.find(':') >= 0: try: name,value = target.split(':',1) value = float(value) except: raise ValueError("Invalid target '%s'" % target) data.append( (name,value) ) else: seriesList = evaluateTarget(requestContext, target) for series in seriesList: func = PieFunctions[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 if useCache: targets = requestOptions['targets'] startTime = requestOptions['startTime'] endTime = requestOptions['endTime'] dataKey = hashData(targets, startTime, endTime) cachedData = cache.get(dataKey) if cachedData: log.cache("Data-Cache hit [%s]" % dataKey) else: log.cache("Data-Cache miss [%s]" % dataKey) else: cachedData = None if cachedData is not None: requestContext['data'] = data = cachedData else: # Have to actually retrieve the data now targets = requestOptions['targets'] if settings.REMOTE_PREFETCH_DATA and not requestOptions.get('localOnly'): log.rendering("Prefetching remote data") pathExpressions = extractPathExpressions(targets) prefetchRemoteData(STORE.remote_stores, requestContext, pathExpressions) for target in targets: if not target.strip(): continue t = time() seriesList = evaluateTarget(requestContext, target) log.rendering("Retrieval of %s took %.6f" % (target, time() - t)) data.extend(seriesList) if useCache: cache.add(dataKey, data, cacheTimeout) # If data is all we needed, we're done format = requestOptions.get('format') if format == 'csv': 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 if format == 'json': jsonStart = time() series_data = [] if 'maxDataPoints' in requestOptions and any(data): startTime = min([series.start for series in data]) endTime = max([series.end for series in data]) timeRange = endTime - startTime maxDataPoints = requestOptions['maxDataPoints'] for series in data: 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) timestamps = range(int(series.start), int(series.end) + 1, int(secondsPerPoint)) else: timestamps = range(int(series.start), int(series.end) + 1, int(series.step)) datapoints = zip(series, timestamps) series_data.append(dict(target=series.name, datapoints=datapoints)) elif 'noNullPoints' in requestOptions and any(data): for series in data: values = [] for (index,v) in enumerate(series): if v is not None: timestamp = series.start + (index * series.step) values.append((v,timestamp)) if len(values) > 0: series_data.append(dict(target=series.name, datapoints=values)) else: for series in data: timestamps = range(int(series.start), int(series.end) + 1, int(series.step)) datapoints = zip(series, timestamps) series_data.append(dict(target=series.name, datapoints=datapoints)) output = json.dumps(series_data).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') if useCache: cache.add(requestKey, response, cacheTimeout) patch_response_headers(response, cache_timeout=cacheTimeout) else: add_never_cache_headers(response) log.rendering('JSON rendering time %6f' % (time() - jsonStart)) log.rendering('Total request processing time %6f' % (time() - start)) return response if format == 'dygraph': labels = ['Time'] result = '{}' 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] result = '{"labels" : %s, "data" : [%s]}' % (json.dumps(labels), ', '.join(lines)) response = HttpResponse(content=result, content_type='application/json') if useCache: cache.add(requestKey, response, cacheTimeout) patch_response_headers(response, cache_timeout=cacheTimeout) else: add_never_cache_headers(response) log.rendering('Total dygraph rendering time %.6f' % (time() - start)) return response if format == 'rickshaw': 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) ) if 'jsonp' in requestOptions: response = HttpResponse( content="%s(%s)" % (requestOptions['jsonp'], json.dumps(series_data)), mimetype='text/javascript') else: response = HttpResponse(content=json.dumps(series_data), content_type='application/json') if useCache: cache.add(requestKey, response, cacheTimeout) patch_response_headers(response, cache_timeout=cacheTimeout) else: add_never_cache_headers(response) log.rendering('Total rickshaw rendering time %.6f' % (time() - start)) return response if format == 'raw': 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') log.rendering('Total rawData rendering time %.6f' % (time() - start)) return response if format == 'svg': graphOptions['outputFormat'] = 'svg' elif format == 'pdf': graphOptions['outputFormat'] = 'pdf' if format == 'pickle': response = HttpResponse(content_type='application/pickle') seriesInfo = [series.getInfo() for series in data] pickle.dump(seriesInfo, response, protocol=-1) log.rendering('Total pickle rendering time %.6f' % (time() - start)) return response # 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, requestContext['forwardHeaders']) else: image = doImageRender(requestOptions['graphClass'], graphOptions) useSVG = graphOptions.get('outputFormat') == 'svg' if useSVG and 'jsonp' in requestOptions: response = HttpResponse( content="%s(%s)" % (requestOptions['jsonp'], json.dumps(image)), content_type='text/javascript') elif graphOptions.get('outputFormat') == 'pdf': response = buildResponse(image, 'application/x-pdf') else: response = buildResponse(image, 'image/svg+xml' if useSVG else 'image/png') if useCache: cache.add(requestKey, response, cacheTimeout) patch_response_headers(response, cache_timeout=cacheTimeout) else: add_never_cache_headers(response) log.rendering('Total rendering time %.6f seconds' % (time() - start)) 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') assert graphType in GraphTypes, "Invalid graphType '%s', must be one of %s" % (graphType,GraphTypes.keys()) 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'] = [] # 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'] 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 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']) 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 return (graphOptions, requestOptions) connectionPools = {} def connector_class_selector(https_support=False): return httplib.HTTPSConnection if https_support else httplib.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 httplib.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: 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 = StringIO(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: 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 = StringIO() 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.0.2/webapp/graphite/readers.py0000644000000000000000000002416013131244465021114 0ustar rootroot00000000000000import os import sys 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 from graphite.intervals import Interval, IntervalSet from graphite.carbonlink import CarbonLink from graphite.logger import log from django.conf import settings try: import whisper except ImportError: whisper = False # The parser was repalcing __readHeader with the __readHeader # which was not working. if bool(whisper): whisper__readHeader = whisper.__readHeader try: import ceres except ImportError: ceres = False try: import rrdtool except ImportError: rrdtool = False try: import gzip except ImportError: gzip = False class FetchInProgress(object): def __init__(self, wait_callback): self.wait_callback = wait_callback def waitForResults(self): return self.wait_callback() class MultiReader(object): __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: log.exception("Failed to initiate subfetch for %s" % str(n)) def merge_results(): results = {} # Wait for any asynchronous operations to complete for i, result in enumerate(fetches): if isinstance(result, FetchInProgress): try: results[i] = result.waitForResults() except: log.exception("Failed to complete subfetch") results[i] = None else: results[i] = result results = [r for r in results.values() if r is not None] if not results: raise Exception("All sub-fetches failed") return reduce(self.merge, results) return FetchInProgress(merge_results) def merge(self, 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) class CeresReader(object): __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) # Merge in data from carbon's cache try: cached_datapoints = CarbonLink.query(self.real_metric_path) except: log.exception("Failed CarbonLink query '%s'" % self.real_metric_path) cached_datapoints = [] values = merge_with_cache(cached_datapoints, data.startTime, data.timeStep, values) return time_info, values class WhisperReader(object): __slots__ = ('fs_path', 'real_metric_path') supported = bool(whisper) def __init__(self, fs_path, real_metric_path): self.fs_path = fs_path self.real_metric_path = real_metric_path def get_intervals(self): start = time.time() - whisper.info(self.fs_path)['maxRetention'] end = max( stat(self.fs_path).st_mtime, start ) return IntervalSet( [Interval(start, end)] ) def fetch(self, startTime, endTime): try: data = whisper.fetch(self.fs_path, startTime, endTime) 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 = whisper.info(self.fs_path) aggregation_method = meta_info['aggregationMethod'] lowest_step = min([i['secondsPerPoint'] for i in meta_info['archives']]) # Merge in data from carbon's cache cached_datapoints = [] try: cached_datapoints = CarbonLink.query(self.real_metric_path) except: log.exception("Failed CarbonLink query '%s'" % self.real_metric_path) cached_datapoints = [] if isinstance(cached_datapoints, dict): cached_datapoints = cached_datapoints.items() values = merge_with_cache(cached_datapoints, start, step, values, aggregation_method) return time_info, values class GzippedWhisperReader(WhisperReader): supported = bool(whisper and gzip) def get_intervals(self): fh = gzip.GzipFile(self.fs_path, 'rb') try: info = whisper__readHeader(fh) # evil, but necessary. finally: fh.close() start = time.time() - info['maxRetention'] end = max( stat(self.fs_path).st_mtime, start ) return IntervalSet( [Interval(start, end)] ) def fetch(self, startTime, endTime): fh = gzip.GzipFile(self.fs_path, 'rb') try: return whisper.file_fetch(fh, startTime, endTime) finally: fh.close() class RRDReader: supported = bool(rrdtool) @staticmethod def _convert_fs_path(fs_path): if isinstance(fs_path, unicode): 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'] def merge_with_cache(cached_datapoints, start, step, values, func=None): 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 func: 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] for interval in consolidated_dict: value = consolidate(func, consolidated_dict[interval]) consolidated.append((interval, value)) 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: pass return values graphite-web-1.0.2/webapp/graphite/__init__.py0000644000000000000000000000006513131244446021223 0ustar rootroot00000000000000# Two wrongs don't make a right, but three lefts do. graphite-web-1.0.2/webapp/graphite/whitelist/0000755000000000000000000000000013131245073021122 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/whitelist/__init__.py0000644000000000000000000000000013131244446023224 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/whitelist/urls.py0000644000000000000000000000142613131244446022467 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('add', views.add, name='whitelist_add'), url('remove', views.remove, name='whitelist_remove'), url('', views.show, name='whitelist_show'), ] graphite-web-1.0.2/webapp/graphite/whitelist/views.py0000644000000000000000000000371113131244446022636 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.0.2/webapp/graphite/dashboard/0000755000000000000000000000000013131245073021035 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/dashboard/__init__.py0000644000000000000000000000000013131244446023137 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/dashboard/admin.py0000644000000000000000000000016113131244446022500 0ustar rootroot00000000000000from django.contrib import admin from graphite.dashboard.models import Dashboard admin.site.register(Dashboard) graphite-web-1.0.2/webapp/graphite/dashboard/models.py0000644000000000000000000000254313131244446022701 0ustar rootroot00000000000000from django.db import models from graphite.account.models import Profile from graphite.util import json 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, unicode): 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.0.2/webapp/graphite/dashboard/send_graph.py0000644000000000000000000000127313131244446023527 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.0.2/webapp/graphite/dashboard/urls.py0000644000000000000000000000262713131244446022406 0ustar rootroot00000000000000from django.conf.urls import url from . import views urlpatterns = [ url('^save/(?P[^/]+)', views.save, name='dashboard_save'), url('^save_template/(?P[^/]+)/(?P[^/]+)', views.save_template, name='dashboard_save_template'), url('^load/(?P[^/]+)', views.load, name='dashboard_load'), url('^load/(?P[^/]+)/(?P[^/]+)', views.load_template, name='dashboard_load_template'), url('^load_template/(?P[^/]+)/(?P[^/]+)', views.load_template, name='dashboard_load_template'), url('^delete/(?P[^/]+)', views.delete, name='dashboard_delete'), url('^create-temporary/?', views.create_temporary, name='dashboard_create_temporary'), url('^email', views.email, name='dashboard_email'), url('^find/', views.find, name='dashboard_find'), url('^delete_template/(?P[^/]+)', views.delete_template, name='dashboard_delete_template'), url('^find_template/', views.find_template, name='dashboard_find_template'), url('^login/?', views.user_login, name='dashboard_login'), url('^logout/?', views.user_logout, name='dashboard_logout'), url('^help/', views.help, name='dashboard_help'), url('^(?P[^/]+)/(?P[^/]+)', views.template, name='dashboard_template'), url('^(?P[^/]+)', views.dashboard, name='dashboard'), url('', views.dashboard, name='dashboard'), ] graphite-web-1.0.2/webapp/graphite/dashboard/views.py0000644000000000000000000003103213131244446022546 0ustar rootroot00000000000000import json import re import errno from os.path import getmtime from urllib import urlencode from 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.render.views import renderView from send_graph import send_graph_email 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, 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 user.is_authenticated(): 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.0.2/webapp/graphite/wsgi.py0000644000000000000000000000233113131244446020433 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 < 2.0.1 does not support Python 2.6 if sys.version_info[:2] < (2, 7): whitenoise_version = tuple(map( int, getattr(whitenoise, '__version__', '0').split('.'))) if whitenoise_version < (2, 0, 1): 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.0.2/webapp/graphite/storage.py0000755000000000000000000001631413131244465021140 0ustar rootroot00000000000000import time import random import Queue from collections import defaultdict try: from importlib import import_module except ImportError: # python < 2.7 compatibility from django.utils.importlib import import_module from django.conf import settings from graphite.logger import log from graphite.util import is_local_interface, is_pattern from graphite.remote_storage import RemoteStore from graphite.node import LeafNode from graphite.intervals import Interval, IntervalSet from graphite.readers import MultiReader from graphite.worker_pool.pool import get_pool def get_finder(finder_path): module_name, class_name = finder_path.rsplit('.', 1) module = import_module(module_name) return getattr(module, class_name)() class Store: def __init__(self, finders=None, hosts=None): if finders is None: finders = [get_finder(finder_path) for finder_path in settings.STORAGE_FINDERS] self.finders = finders if hosts is None: hosts = settings.CLUSTER_SERVERS remote_hosts = [host for host in hosts if not settings.REMOTE_EXCLUDE_LOCAL or not is_local_interface(host)] self.remote_stores = [ RemoteStore(host) for host in remote_hosts ] def find(self, pattern, startTime=None, endTime=None, local=False, headers=None): query = FindQuery(pattern, startTime, endTime, local) for match in self.find_all(query, headers): yield match def find_all(self, query, headers=None): start = time.time() result_queue = Queue.Queue() jobs = [] # Start remote searches if not query.local: random.shuffle(self.remote_stores) jobs.extend([ (store.find, query, headers) for store in self.remote_stores if store.available ]) # Start local searches for finder in self.finders: jobs.append((finder.find_nodes, query)) if settings.USE_WORKER_POOL: return_result = lambda x: result_queue.put(x) for job in jobs: get_pool().apply_async(func=job[0], args=job[1:], callback=return_result) else: for job in jobs: result_queue.put(job[0](*job[1:])) # Group matching nodes by their path nodes_by_path = defaultdict(list) deadline = start + settings.REMOTE_FIND_TIMEOUT result_cnt = 0 while result_cnt < len(jobs): wait_time = deadline - time.time() try: nodes = result_queue.get(True, wait_time) # ValueError could happen if due to really unlucky timing wait_time is negative except (Queue.Empty, ValueError): if time.time() > deadline: log.info("Timed out in find_all after %fs" % (settings.REMOTE_FIND_TIMEOUT)) break else: continue log.info("Got a find result after %fs" % (time.time() - start)) result_cnt += 1 if nodes: for node in nodes: nodes_by_path[node.path].append(node) log.info("Got all find results in %fs" % (time.time() - start)) # Reduce matching nodes for each path to a minimal set found_branch_nodes = set() items = list(nodes_by_path.iteritems()) 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) elif node.path not in found_branch_nodes: #TODO need to filter branch nodes based on requested interval... how?!?!? yield node found_branch_nodes.add(node.path) if not leaf_nodes: continue # Fast-path when there is a single node. if len(leaf_nodes) == 1: yield leaf_nodes[0] continue # 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 len(minimal_node_set) == 1: yield minimal_node_set.pop() elif len(minimal_node_set) > 1: reader = MultiReader(minimal_node_set) yield LeafNode(path, reader) class FindQuery: def __init__(self, pattern, startTime, endTime, local=False): 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 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) STORE = Store() graphite-web-1.0.2/webapp/graphite/middleware.py0000644000000000000000000000040213131244465021575 0ustar rootroot00000000000000from graphite.logger import log class LogExceptionsMiddleware(object): 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.0.2/webapp/graphite/carbonlink.py0000644000000000000000000001576313131244465021622 0ustar rootroot00000000000000import time import socket import struct import errno import random from select import select from django.conf import settings from graphite.render.hashing import ConsistentHashRing from graphite.logger import log from graphite.util import load_module, unpickle try: import 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 CarbonLinkPool: 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: 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,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,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 = recv_exactly(conn, 4) body_size = struct.unpack("!L", len_prefix)[0] body = recv_exactly(conn, body_size) return unpickle.loads(body) class CarbonLinkRequestError(Exception): pass # Socket helper functions def still_connected(sock): is_readable = select([sock], [], [], 0)[0] if is_readable: try: recv_buf = sock.recv(1, socket.MSG_DONTWAIT|socket.MSG_PEEK) except socket.error as e: if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK): return True else: raise else: return bool(recv_buf) else: return True def recv_exactly(conn, num_bytes): buf = '' while len(buf) < num_bytes: data = conn.recv( num_bytes - len(buf) ) if not data: raise Exception("Connection lost") buf += data return buf #parse hosts from local_settings.py 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) ) #A shared importable singleton CarbonLink = CarbonLinkPool(hosts, settings.CARBONLINK_TIMEOUT) graphite-web-1.0.2/webapp/graphite/compat.py0000644000000000000000000000257413131244446020756 0ustar rootroot00000000000000import json from django import VERSION from django.core.serializers.json import DjangoJSONEncoder 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 class JsonResponse(HttpResponse): # Django < 1.7 does not have JsonResponse # https://github.com/django/django/commit/024213 # https://github.com/django/django/blob/master/LICENSE def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs): if safe and not isinstance(data, dict): raise TypeError( 'In order to allow non-dict objects to be serialized set the ' 'safe parameter to False.' ) if json_dumps_params is None: json_dumps_params = {} kwargs.setdefault('content_type', 'application/json') data = json.dumps(data, cls=encoder, **json_dumps_params) super(JsonResponse, self).__init__(content=data, **kwargs) graphite-web-1.0.2/webapp/graphite/templates/0000755000000000000000000000000013131245073021104 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/templates/dashboard.html0000644000000000000000000000660713131244446023735 0ustar rootroot00000000000000{% load static staticfiles %} Graphite Dashboard
Drop To Merge
{% if jsdebug %} {% else %} {% endif %} graphite-web-1.0.2/webapp/graphite/templates/version.html0000644000000000000000000000001413131244446023455 0ustar rootroot00000000000000{{version}} graphite-web-1.0.2/webapp/graphite/templates/login.html0000644000000000000000000000300113131244446023077 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.0.2/webapp/graphite/templates/editProfile.html0000644000000000000000000000174413131244446024251 0ustar rootroot00000000000000

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

{% if nextPage %} {% endif %} Enable advanced/experimental UI features
graphite-web-1.0.2/webapp/graphite/templates/event.html0000644000000000000000000000151413131244446023117 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.0.2/webapp/graphite/templates/browserHeader.html0000644000000000000000000000637713131244446024606 0ustar rootroot00000000000000{% load staticfiles %} Graphite Browser Header
graphite-web-1.0.2/webapp/graphite/templates/500.html0000644000000000000000000000113713131244446022303 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.0.2/webapp/graphite/templates/dashboardHelp.html0000644000000000000000000000223113131244446024533 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.0.2/webapp/graphite/templates/composer.html0000644000000000000000000001107313131244446023626 0ustar rootroot00000000000000 {% load static staticfiles %} Graphite Composer {% if jsdebug %} {% else %} {% endif %} graphite-web-1.0.2/webapp/graphite/templates/browser.html0000644000000000000000000000221113131244446023454 0ustar rootroot00000000000000 Graphite Browser {% if target %} {% else %} {% endif %} graphite-web-1.0.2/webapp/graphite/templates/events.html0000644000000000000000000000263613131244446023310 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}}
graphite-web-1.0.2/webapp/graphite/version/0000755000000000000000000000000013131245073020573 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/version/__init__.py0000755000000000000000000000000013131244446022700 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/version/urls.py0000755000000000000000000000127213131244446022142 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('', views.index, name='version_index'), ] graphite-web-1.0.2/webapp/graphite/version/views.py0000755000000000000000000000032413131244446022307 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.0.2/webapp/graphite/composer/0000755000000000000000000000000013131245073020735 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/composer/__init__.py0000644000000000000000000000000013131244446023037 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/composer/urls.py0000644000000000000000000000145213131244446022301 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('send_email', views.send_email, name='composer_send_email'), url('mygraph', views.mygraph, name='composer_mygraph'), url('', views.composer, name='composer'), ] graphite-web-1.0.2/webapp/graphite/composer/views.py0000644000000000000000000001002413131244446022444 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.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText from email.MIMEImage import MIMEImage from httplib import HTTPConnection from urlparse 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: 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: return HttpResponse( format_exc() ) graphite-web-1.0.2/webapp/graphite/worker_pool/0000755000000000000000000000000013131245073021450 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/worker_pool/__init__.py0000644000000000000000000000000013131244446023552 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/worker_pool/pool.py0000644000000000000000000000137613131244465023006 0ustar rootroot00000000000000from threading import Lock from django.conf import settings from multiprocessing.pool import ThreadPool init_lock = Lock() is_initialized = False # the number of workers should increase linear with the number of # backend servers, plus we need some baseline for local finds and # other stuff that always happens thread_count = settings.POOL_WORKERS_PER_BACKEND * len(settings.CLUSTER_SERVERS) + settings.POOL_WORKERS def get_pool(): with init_lock: instance = getattr(get_pool, 'instance', None) if instance is None: instance = ThreadPool(thread_count) setattr(get_pool, 'instance', instance) is_initialized = True return instance def stop_pool(): with init_lock: if not is_initialized: return get_pool().close() graphite-web-1.0.2/webapp/graphite/events/0000755000000000000000000000000013131245073020412 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/events/__init__.py0000644000000000000000000000006513131244446022527 0ustar rootroot00000000000000# Two wrongs don't make a right, but three lefts do. graphite-web-1.0.2/webapp/graphite/events/admin.py0000644000000000000000000000014613131244446022060 0ustar rootroot00000000000000from django.contrib import admin from graphite.events.models import Event admin.site.register(Event) graphite-web-1.0.2/webapp/graphite/events/models.py0000644000000000000000000000315513131244446022256 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.0.2/webapp/graphite/events/compat.py0000644000000000000000000000076713131244446022264 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.0.2/webapp/graphite/events/urls.py0000644000000000000000000000150013131244446021750 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('^get_data?$', views.get_data, name='events_get_data'), url(r'(?P\d+)/$', views.detail, name='events_detail'), url('^$', views.view_events, name='events'), ] graphite-web-1.0.2/webapp/graphite/events/views.py0000644000000000000000000000767413131244465022143 0ustar rootroot00000000000000import datetime import pytz 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.forms.models import model_to_dict from django.shortcuts import render_to_response, get_object_or_404 from django.utils.timezone import now, make_aware from graphite.compat import HttpResponse, JsonResponse from graphite.util import json, epoch 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) def detail(request, event_id): if request.META['HTTP_ACCEPT'] == 'application/json': try: e = Event.objects.get(id=event_id) e.tags = e.tags.split() response = JsonResponse(model_to_dict(e)) return response except ObjectDoesNotExist: error = {'error': 'Event matching query does not exist'} response = JsonResponse(error, status=404) return response else: 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: if not isinstance(tags, list): return HttpResponse( json.dumps({'error': '"tags" must be an array'}), status=400) tags = ' '.join(tags) if 'when' in event: when = make_aware( datetime.datetime.utcfromtimestamp( event.get('when')), pytz.utc) 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 = datetime.datetime.fromtimestamp(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.0.2/webapp/graphite/settings.py0000644000000000000000000002010413131244465021321 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 import os import sys from os.path import abspath, dirname, join from warnings import warn from django.core.urlresolvers import reverse_lazy GRAPHITE_WEB_APP_SETTINGS_LOADED = False WEBAPP_VERSION = '1.0.2' 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 = [] # Cluster settings CLUSTER_SERVERS = [] USE_WORKER_POOL = True POOL_WORKERS_PER_BACKEND = 1 POOL_WORKERS = 1 # This settings control whether https is used to communicate between cluster members INTRACLUSTER_HTTPS = False REMOTE_FIND_TIMEOUT = 3.0 REMOTE_FETCH_TIMEOUT = 3.0 REMOTE_RETRY_DELAY = 60.0 REMOTE_EXCLUDE_LOCAL = False REMOTE_STORE_MERGE_RESULTS = True REMOTE_STORE_FORWARD_HEADERS = [] REMOTE_STORE_USE_POST = False REMOTE_PREFETCH_DATA = False 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 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 = [] LOG_CACHE_PERFORMANCE = False LOG_ROTATION = True LOG_ROTATION_COUNT = 1 MAX_FETCH_RETRIES = 2 #Remote rendering settings REMOTE_RENDERING = False #if True, rendering is delegated to RENDERING_HOSTS RENDERING_HOSTS = [] REMOTE_RENDER_CONNECT_TIMEOUT = 1.0 LOG_RENDERING_PERFORMANCE = False #Miscellaneous settings SMTP_SERVER = "localhost" DOCUMENTATION_URL = "http://graphite.readthedocs.io/" ALLOW_ANONYMOUS_CLI = True LEGEND_MAX_ITEMS = 10 RRD_CF = 'AVERAGE' STORAGE_FINDERS = ( 'graphite.finders.standard.StandardFinder', ) MIDDLEWARE_CLASSES='' 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 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 >> sys.stderr, "Could not import graphite.local_settings, using defaults!" ## 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'), ) ## 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 >> sys.stderr, "WARNING: whisper module could not be loaded, whisper support disabled" 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: MIDDLEWARE_CLASSES += ('django.contrib.auth.middleware.RemoteUserMiddleware',) 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.0.2/webapp/graphite/finders/0000755000000000000000000000000013131245073020540 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/finders/__init__.py0000644000000000000000000000453613131244465022665 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_fs_path = os.path.realpath(absolute_path) if absolute_path != real_fs_path: relative_fs_path = metric_path.replace('.', os.sep) base_fs_path = os.path.dirname(absolute_path[:-len(relative_fs_path)]) real_base_fs_path = os.path.realpath(base_fs_path) relative_real_fs_path = real_fs_path[len(real_base_fs_path):].lstrip('/') return fs_to_metric(relative_real_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 _deduplicate(entries): yielded = set() for entry in entries: if entry not in yielded: yielded.add(entry) yield entry 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 ] else: variants = [ pattern ] return list( _deduplicate(variants) ) 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(_deduplicate(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.0.2/webapp/graphite/finders/ceres.py0000644000000000000000000000215413131244465022221 0ustar rootroot00000000000000from __future__ import absolute_import import os.path 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 . import get_real_metric_path, extract_variants class CeresFinder: def __init__(self, directory=None): directory = directory or settings.CERES_DIR self.directory = directory self.tree = CeresTree(directory) def find_nodes(self, query): 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) yield LeafNode(metric_path, reader) elif os.path.isdir(fs_path): yield BranchNode(metric_path) graphite-web-1.0.2/webapp/graphite/finders/standard.py0000644000000000000000000001162413131244465022722 0ustar rootroot00000000000000import operator from os.path import isdir, isfile, join, basename 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 . import fs_to_metric, get_real_metric_path, match_entries class StandardFinder: 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('\\', '') pattern_parts = clean_pattern.split('.') for root_dir in self.directories: for absolute_path in self._find_paths(root_dir, pattern_parts): if basename(absolute_path).startswith('.'): continue if self.DATASOURCE_DELIMITER in basename(absolute_path): (absolute_path, datasource_pattern) = absolute_path.rsplit(self.DATASOURCE_DELIMITER, 1) else: datasource_pattern = None relative_path = absolute_path[ len(root_dir): ].lstrip('/') metric_path = fs_to_metric(relative_path) real_metric_path = get_real_metric_path(absolute_path, metric_path) 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 isfile(absolute_path): if 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""" pattern = patterns[0] patterns = patterns[1:] has_wildcard = pattern.find('{') > -1 or pattern.find('[') > -1 or pattern.find('*') > -1 or pattern.find('?') > -1 using_globstar = pattern == "**" if has_wildcard: # this avoids os.listdir() for performance try: entries = [x.name for x in scandir(current_dir)] except OSError as e: log.exception(e) entries = [] else: entries = [ pattern ] if using_globstar: matching_subdirs = map(operator.itemgetter(0), walk(current_dir)) else: subdirs = [entry for entry in entries if isdir(join(current_dir, entry))] matching_subdirs = match_entries(subdirs, pattern) # if this is a terminal globstar, add a pattern for all files in subdirs if using_globstar and not patterns: patterns = ["*"] if len(patterns) == 1 and RRDReader.supported: #the last pattern may apply to RRD data sources if not has_wildcard: entries = [ pattern + ".rrd" ] files = [entry for entry in entries if isfile(join(current_dir, entry))] 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: absolute_path = join(current_dir, rrd_file) yield absolute_path + 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 match else: #we've got the last pattern if not has_wildcard: entries = [ pattern + '.wsp', pattern + '.wsp.gz', pattern + '.rrd' ] files = [entry for entry in entries if isfile(join(current_dir, entry))] matching_files = match_entries(files, pattern + '.*') for base_name in matching_files + matching_subdirs: yield join(current_dir, base_name) graphite-web-1.0.2/webapp/graphite/browser/0000755000000000000000000000000013131245073020571 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/browser/__init__.py0000644000000000000000000000000013131244446022673 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/browser/urls.py0000644000000000000000000000170113131244446022132 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('^header/?$', views.header, name='browser_header'), url('^search/?$', views.search, name='browser_search'), url('^mygraph/?$', views.myGraphLookup, name='browser_my_graph'), url('^usergraph/?$', views.userGraphLookup, name='browser_usergraph'), url('^$', views.browser, name='browser'), ] graphite-web-1.0.2/webapp/graphite/browser/views.py0000644000000000000000000002006113131244465022303 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 urlparse import urlparse, parse_qsl from urllib import urlencode 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: 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' : str(profile.user.username), 'id' : str(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.all().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(str(nodeName)), 'id' : str(username + '.' + prefix + nodeName + '.'), } node.update(branchNode) else: # leaf m = md5() m.update(nodeName) # 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(str(nodeName)), 'id' : str(username + '.' + prefix + m.hexdigest()), 'graphUrl' : urlEscaped, } node.update(leafNode) nodes.append(node) except: 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.0.2/webapp/graphite/url_shortener/0000755000000000000000000000000013131245073022001 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/url_shortener/__init__.py0000644000000000000000000000000013131244446024103 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/url_shortener/models.py0000644000000000000000000000022213131244446023635 0ustar rootroot00000000000000from django.db import models class Link(models.Model): url = models.TextField() date_submitted = models.DateTimeField(auto_now_add=True) graphite-web-1.0.2/webapp/graphite/url_shortener/views.py0000644000000000000000000000170313131244465023515 0ustar rootroot00000000000000from 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.0.2/webapp/graphite/url_shortener/baseconv.py0000644000000000000000000000301713131244446024157 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.0.2/webapp/graphite/remote_storage.py0000755000000000000000000002562713131244465022522 0ustar rootroot00000000000000import time import urllib3 from Queue import Queue from urllib import urlencode from threading import Lock, current_thread from django.conf import settings from django.core.cache import cache from graphite.intervals import Interval, IntervalSet from graphite.node import LeafNode, BranchNode from graphite.readers import FetchInProgress from graphite.logger import log from graphite.util import unpickle, logtime, timebounds from graphite.render.hashing import compactHash from graphite.worker_pool.pool import get_pool http = urllib3.PoolManager(num_pools=10, maxsize=5) def prefetchRemoteData(remote_stores, requestContext, pathExpressions): if requestContext['localOnly']: return if requestContext is None: requestContext = {} (startTime, endTime, now) = timebounds(requestContext) log.info('thread %s prefetchRemoteData:: Starting fetch_list on all backends' % current_thread().name) # Go through all of the remote nodes, and launch a fetch for each one. # Each fetch will take place in its own thread, since it's naturally parallel work. for store in remote_stores: reader = RemoteReader(store, {'intervals': []}, bulk_query=pathExpressions) reader.fetch_list(startTime, endTime, now, requestContext) class RemoteStore(object): def __init__(self, host): self.host = host self.last_failure = 0 @property def available(self): return time.time() - self.last_failure > settings.REMOTE_RETRY_DELAY def find(self, query, headers=None): return list(FindRequest(self, query).send(headers)) def fail(self): self.last_failure = time.time() class FindRequest(object): __slots__ = ('store', 'query', 'cacheKey') def __init__(self, store, query): self.store = store self.query = query if query.startTime: start = query.startTime - (query.startTime % settings.FIND_CACHE_DURATION) else: start = "" if query.endTime: end = query.endTime - (query.endTime % settings.FIND_CACHE_DURATION) else: end = "" self.cacheKey = "find:%s:%s:%s:%s" % (store.host, compactHash(query.pattern), start, end) @logtime(custom_msg=True) def send(self, headers=None, msg_setter=None): log.info("FindRequest.send(host=%s, query=%s) called" % (self.store.host, self.query)) if headers is None: headers = {} results = cache.get(self.cacheKey) if results is not None: log.info("FindRequest.send(host=%s, query=%s) using cached result" % (self.store.host, self.query)) else: url = "%s://%s/metrics/find/" % ('https' if settings.INTRACLUSTER_HTTPS else 'http', self.store.host) query_params = [ ('local', '1'), ('format', 'pickle'), ('query', self.query.pattern), ] if self.query.startTime: query_params.append( ('from', self.query.startTime) ) if self.query.endTime: query_params.append( ('until', self.query.endTime) ) try: result = http.request('POST' if settings.REMOTE_STORE_USE_POST else 'GET', url, fields=query_params, headers=headers, timeout=settings.REMOTE_FIND_TIMEOUT) except: log.exception("FindRequest.send(host=%s, query=%s) exception during request" % (self.store.host, self.query)) self.store.fail() return if result.status != 200: log.exception("FindRequest.send(host=%s, query=%s) error response %d from %s?%s" % (self.store.host, self.query, result.status, url, urlencode(query_params))) self.store.fail() return try: results = unpickle.loads(result.data) except: log.exception("FindRequest.send(host=%s, query=%s) exception processing response" % (self.store.host, self.query)) self.store.fail() return cache.set(self.cacheKey, results, settings.FIND_CACHE_DURATION) msg_setter('host: {host}, query: {query}'.format(host=self.store.host, query=self.query)) 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.store, node_info, bulk_query=[self.query.pattern]) node = LeafNode(path, reader) else: node = BranchNode(path) node.local = False yield node class RemoteReader(object): __slots__ = ('store', 'metric_path', 'intervals', 'bulk_query', 'connection') inflight_lock = Lock() def __init__(self, store, node_info, bulk_query=None): self.store = store self.metric_path = node_info.get('path') or node_info.get('metric_path') self.intervals = node_info['intervals'] self.bulk_query = bulk_query or [self.metric_path] self.connection = None def __repr__(self): return '' % (id(self), self.store.host) @staticmethod def _log(msg, logger): logger(('thread %s at %fs ' % (current_thread().name, time.time())) + msg) @classmethod def log_debug(cls, msg): if settings.DEBUG: cls._log(msg, log.info) @classmethod def log_error(cls, msg): cls._log(msg, log.exception) def get_intervals(self): return self.intervals def fetch(self, startTime, endTime, now=None, requestContext=None): seriesList = self.fetch_list(startTime, endTime, now, requestContext) def _fetch(seriesList): if seriesList is None: return None for series in seriesList: if series['name'] == self.metric_path: time_info = (series['start'], series['end'], series['step']) return (time_info, series['values']) return None if isinstance(seriesList, FetchInProgress): return FetchInProgress(lambda: _fetch(seriesList.waitForResults())) return _fetch(seriesList) def fetch_list(self, startTime, endTime, now=None, requestContext=None): t = time.time() query_params = [ ('format', 'pickle'), ('local', '1'), ('noCache', '1'), ('from', str( int(startTime) )), ('until', str( int(endTime) )) ] for target in self.bulk_query: query_params.append(('target', target)) if now is not None: query_params.append(('now', str( int(now) ))) query_string = urlencode(query_params) urlpath = '/render/' url = "%s://%s%s" % ('https' if settings.INTRACLUSTER_HTTPS else 'http', self.store.host, urlpath) headers = requestContext.get('forwardHeaders') if requestContext else None cacheKey = "%s?%s" % (url, query_string) if requestContext is not None and 'inflight_requests' in requestContext and cacheKey in requestContext['inflight_requests']: self.log_debug("RemoteReader:: Returning cached FetchInProgress %s?%s" % (url, query_string)) return requestContext['inflight_requests'][cacheKey] if requestContext is None or 'inflight_locks' not in requestContext or cacheKey not in requestContext['inflight_locks']: with self.inflight_lock: self.log_debug("RemoteReader:: Got global lock %s?%s" % (url, query_string)) if requestContext is None: requestContext = {} if 'inflight_locks' not in requestContext: requestContext['inflight_locks'] = {} if 'inflight_requests' not in requestContext: requestContext['inflight_requests'] = {} if cacheKey not in requestContext['inflight_locks']: self.log_debug("RemoteReader:: Creating lock %s?%s" % (url, query_string)) requestContext['inflight_locks'][cacheKey] = Lock() self.log_debug("RemoteReader:: Released global lock %s?%s" % (url, query_string)) cacheLock = requestContext['inflight_locks'][cacheKey] with cacheLock: self.log_debug("RemoteReader:: got url lock %s?%s" % (url, query_string)) if cacheKey in requestContext['inflight_requests']: self.log_debug("RemoteReader:: Returning cached FetchInProgress %s?%s" % (url, query_string)) return requestContext['inflight_requests'][cacheKey] q = Queue() if settings.USE_WORKER_POOL: get_pool().apply_async( func=self._fetch, args=[url, query_string, query_params, headers], callback=lambda x: q.put(x), ) else: q.put( self._fetch(url, query_string, query_params, headers), ) def retrieve(): with retrieve.lock: # if the result is known we return it directly if hasattr(retrieve, '_result'): results = getattr(retrieve, '_result') self.log_debug( 'RemoteReader:: retrieve completed (cached) %s' % (', '.join([result['path'] for result in results])), ) return results # otherwise we get it from the queue and keep it for later results = q.get(block=True) for i in range(len(results)): results[i]['path'] = results[i]['name'] if not results: self.log_debug('RemoteReader:: retrieve has received no results') setattr(retrieve, '_result', results) self.log_debug( 'RemoteReader:: retrieve completed %s' % (', '.join([result['path'] for result in results])), ) return results self.log_debug( 'RemoteReader:: Storing FetchInProgress with cacheKey {cacheKey}' .format(cacheKey=cacheKey), ) retrieve.lock = Lock() data = FetchInProgress(retrieve) requestContext['inflight_requests'][cacheKey] = data self.log_debug("RemoteReader:: Returning %s?%s in %fs" % (url, query_string, time.time() - t)) return data def _fetch(self, url, query_string, query_params, headers): self.log_debug("RemoteReader:: Starting to execute _fetch %s?%s" % (url, query_string)) try: self.log_debug("ReadResult:: Requesting %s?%s" % (url, query_string)) result = http.request( 'POST' if settings.REMOTE_STORE_USE_POST else 'GET', url, fields=query_params, headers=headers, timeout=settings.REMOTE_FETCH_TIMEOUT, ) if result.status != 200: self.store.fail() self.log_error("ReadResult:: Error response %d from %s?%s" % (result.status, url, query_string)) data = [] else: data = unpickle.loads(result.data) except Exception as err: self.store.fail() self.log_error("ReadResult:: Error requesting %s?%s: %s" % (url, query_string, err)) data = [] self.log_debug("RemoteReader:: Completed _fetch %s?%s" % (url, query_string)) return data def extractForwardHeaders(request): headers = {} for name in settings.REMOTE_STORE_FORWARD_HEADERS: headers[name] = request.META.get('HTTP_%s' % name.upper().replace('-', '_')) return headers graphite-web-1.0.2/webapp/graphite/account/0000755000000000000000000000000013131245073020542 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/account/ldapBackend.py0000644000000000000000000000437413131244446023317 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] except: userMail = "Unknown" conn.simple_bind_s(userDN,password) try: user = User.objects.get(username=username) except: #First time login, not in django's database randomPasswd = User.objects.make_random_password(length=16) #To prevent login from django db user 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.0.2/webapp/graphite/account/__init__.py0000644000000000000000000000000013131244446022644 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/account/admin.py0000644000000000000000000000040713131244446022210 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.0.2/webapp/graphite/account/models.py0000644000000000000000000000303713131244465022406 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) 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) name = models.CharField(max_length=64) value = models.CharField(max_length=64) class View(models.Model): profile = models.ForeignKey(Profile) name = models.CharField(max_length=64) class Window(models.Model): view = models.ForeignKey(View) 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) name = models.CharField(max_length=64) url = models.TextField() graphite-web-1.0.2/webapp/graphite/account/urls.py0000644000000000000000000000157013131244446022107 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('^login/?$', views.loginView, name='account_login'), url('^logout/?$', views.logoutView, name='account_logout'), url('^edit/?$', views.editProfile, name='account_edit'), url('^update/?$', views.updateProfile, name='account_update'), ] graphite-web-1.0.2/webapp/graphite/account/views.py0000644000000000000000000000426113131244465022260 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 from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from graphite.user_util import getProfile 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 request.user.is_authenticated(): 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.0.2/webapp/graphite/metrics/0000755000000000000000000000000013131245073020554 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/metrics/__init__.py0000644000000000000000000000000013131244446022656 0ustar rootroot00000000000000graphite-web-1.0.2/webapp/graphite/metrics/urls.py0000644000000000000000000000204713131244446022121 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('^index\.json$', views.index_json, name='metrics_index'), url('^find/?$', views.find_view, name='metrics_find'), url('^expand/?$', views.expand_view, name='metrics_expand'), url('^get-metadata/?$', views.get_metadata_view, name='metrics_get_metadata'), url('^set-metadata/?$', views.set_metadata_view, name='metrics_set_metadata'), url('', views.find_view, name='metrics'), ] graphite-web-1.0.2/webapp/graphite/metrics/views.py0000644000000000000000000002616113131244446022274 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.""" import fnmatch import os import urllib from django.conf import settings from graphite.compat import HttpResponse, HttpResponseBadRequest from graphite.user_util import getProfile from graphite.util import json from graphite.logger import log from graphite.readers import RRDReader from graphite.storage import STORE from graphite.carbonlink import CarbonLink from graphite.remote_storage import extractForwardHeaders try: import cPickle as pickle except ImportError: import pickle def index_json(request): queryParams = request.GET.copy() queryParams.update(request.POST) jsonp = queryParams.get('jsonp', False) cluster = queryParams.get('cluster', False) def find_matches(): matches = [] for root, dirs, files in os.walk(settings.WHISPER_DIR): root = root.replace(settings.WHISPER_DIR, '') for basename in files: if fnmatch.fnmatch(basename, '*.wsp'): matches.append(os.path.join(root, basename)) for root, dirs, files in os.walk(settings.CERES_DIR): root = root.replace(settings.CERES_DIR, '') for filename in files: if filename == '.ceres-node': matches.append(root) # 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, dirs, files in os.walk(settings.RRD_DIR, followlinks=True): root = root.replace(settings.RRD_DIR, '') for basename in files: if fnmatch.fnmatch(basename, '*.rrd'): absolute_path = os.path.join(settings.RRD_DIR, root, basename) (basename,extension) = os.path.splitext(basename) metric_path = os.path.join(root, basename) rrd = RRDReader(absolute_path, metric_path) for datasource_name in rrd.get_datasources(absolute_path): matches.append(os.path.join(metric_path, datasource_name)) matches = [ m .replace('.wsp', '') .replace('.rrd', '') .replace('/', '.') .lstrip('.') for m in sorted(matches) ] return matches matches = [] if cluster and len(settings.CLUSTER_SERVERS) >= 1: try: matches = reduce( lambda x, y: list(set(x + y)), \ [json.loads(urllib.urlopen('http://' + cluster_server + '/metrics/index.json').read()) \ for cluster_server in settings.CLUSTER_SERVERS]) except urllib.URLError: log.exception() return json_response_for(request, matches, jsonp=jsonp, status=500) else: matches = find_matches() 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') local_only = int( queryParams.get('local', 0) ) wildcards = int( queryParams.get('wildcards', 0) ) fromTime = int( queryParams.get('from', -1) ) untilTime = int( queryParams.get('until', -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: return HttpResponseBadRequest(content="Missing required parameter 'query'", 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) ) except: 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 == '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: 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: 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: 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' : urllib.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 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' content = json.dumps(data, ensure_ascii=ensure_ascii) 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.0.2/webapp/graphite/local_settings.py.example0000644000000000000000000003534113131244465024136 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 #LOG_ROTATION = True #LOG_ROTATION_COUNT = 1 #LOG_RENDERING_PERFORMANCE = True #LOG_CACHE_PERFORMANCE = True # 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 } # 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 ##################################### # 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 ##################################### # 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"] # Creates a pool of worker threads to which tasks can be dispatched. This makes # sense if there are multiple CLUSTER_SERVERS because then the communication # with them can be parallelized # The number of threads is equal to: # POOL_WORKERS_PER_BACKEND * len(CLUSTER_SERVERS) + POOL_WORKERS # Be careful when increasing the number of threads, in particular if your start # multiple graphite-web processes (with uwsgi or similar) as this will increase # memory consumption (and number of connections to memcached). #USE_WORKER_POOL = True # The number of worker threads that should be created per backend server. # It makes sense to have more than one thread per backend server if # the graphite-web web server itself is multi threaded and can handle multiple # incoming requests at once. #POOL_WORKERS_PER_BACKEND = 1 # A baseline number of workers that should always be created, no matter how many # cluster servers are configured. These are used for other tasks that can be # off-loaded from the request handling threads. #POOL_WORKERS = 1 # This setting controls whether https is used to communicate between cluster members #INTRACLUSTER_HTTPS = False # These are timeout values (in seconds) for requests to remote webapps #REMOTE_FIND_TIMEOUT = 3.0 # Timeout for metric find requests #REMOTE_FETCH_TIMEOUT = 3.0 # Timeout to fetch series data #REMOTE_RETRY_DELAY = 60.0 # Time before retrying a failed remote webapp # 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 # 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 ## Prefetch cache # set to True to fetch all metrics using a single http request per remote server # instead of one http request per target, per remote server. # Especially useful when generating graphs with more than 4-5 targets or if # there's significant latency between this server and the backends. #REMOTE_PREFETCH_DATA = False ## 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 ##################################### # Additional Django Settings # ##################################### # Uncomment the following line for direct access to Django settings such as # MIDDLEWARE_CLASSES or APPS #from graphite.app_settings import * graphite-web-1.0.2/webapp/graphite/util.py0000644000000000000000000001643413131244465020451 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 os import socket import time import sys import calendar import pytz from os.path import splitext, basename, relpath from shutil import move from tempfile import mkstemp try: import cPickle as pickle USING_CPICKLE = True except: import pickle USING_CPICKLE = False try: from cStringIO import StringIO except ImportError: from StringIO import StringIO from django.conf import settings from graphite.logger import log # There are a couple different json modules floating around out there with # different APIs. Hide the ugliness here. try: import json except ImportError: import simplejson as json if hasattr(json, 'read') and not hasattr(json, 'loads'): json.loads = json.read json.dumps = json.write json.load = lambda file: json.read( file.read() ) json.dump = lambda obj, file: file.write( json.write(obj) ) def epoch(dt): """ Returns the epoch timestamp of a timezone-aware datetime object. """ return calendar.timegm(dt.astimezone(pytz.utc).timetuple()) 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 ':' in host: try: 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('[]') socket.inet_pton(socket.AF_INET6, host) is_ipv6 = True except socket.error: host = host.split(':',1)[0] try: if is_ipv6: sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) else: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect( (host, 4242) ) local_ip = sock.getsockname()[0] sock.close() except: log.exception("Failed to open socket with %s" % host) raise if local_ip == host: return True return False 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(datetime): "Convert a datetime object into epoch time" return time.mktime( datetime.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 USING_CPICKLE: 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(StringIO(pickle_string)) pickle_obj.find_global = cls.find_class return pickle_obj.load() else: class SafeUnpickler(pickle.Unpickler): PICKLE_SAFE = { 'copy_reg': set(['_reconstructor']), '__builtin__': 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) @classmethod def loads(cls, pickle_string): return cls(StringIO(pickle_string)).load() unpickle = SafeUnpickler def write_index(whisper_dir=None, ceres_dir=None, index=None): if not whisper_dir: whisper_dir = settings.WHISPER_DIR if not ceres_dir: ceres_dir = settings.CERES_DIR if not index: index = settings.INDEX_FILE try: fd, tmp = mkstemp() try: tmp_index = os.fdopen(fd, 'wt') build_index(whisper_dir, ".wsp", tmp_index) build_index(ceres_dir, ".ceres-node", tmp_index) finally: tmp_index.close() move(tmp, index) finally: try: os.unlink(tmp) except: pass return None def logtime(custom_msg=False): def wrap(f): def wrapped_f(*args, **kwargs): msg = 'completed in' t = time.time() if custom_msg: def set_msg(msg): wrapped_f.msg = msg kwargs['msg_setter'] = set_msg res = f(*args, **kwargs) msg = getattr(wrapped_f, 'msg', msg) log.info( '{module}.{name} :: {msg} {sec:.6}s'.format( module=f.__module__, name=f.__name__, msg=msg, sec=time.time() - t, ) ) return res return wrapped_f return wrap @logtime(custom_msg=True) def build_index(base_path, extension, fd, msg_setter=None): t = time.time() total_entries = 0 contents = os.walk(base_path, followlinks=True) extension_len = len(extension) for (dirpath, dirnames, filenames) in contents: path = relpath(dirpath, base_path).replace('/', '.') for metric in filenames: if metric.endswith(extension): metric = metric[:-extension_len] else: continue line = "{0}.{1}\n".format(path, metric) total_entries += 1 fd.write(line) fd.flush() msg_setter("[IndexSearcher] index rebuild of \"%s\" took" % base_path) return None graphite-web-1.0.2/webapp/graphite/urls.py0000644000000000000000000000332713131244465020456 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/', include(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('^s/(?P.*)', shorten, name='shorten'), url('^S/(?P[a-zA-Z0-9]+)/?$', follow, name='follow'), url('^$', browser, name='browser'), ] url_prefix = '' if settings.URL_PREFIX.strip('/'): url_prefix = '{0}/'.format(settings.URL_PREFIX.strip('/')) urlpatterns = [ url(r'^{0}'.format(url_prefix), include(graphite_urls)), ] handler500 = 'graphite.views.server_error' graphite-web-1.0.2/webapp/graphite/intervals.py0000644000000000000000000000736713131244446021507 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 __hash__(self): return hash( self.tuple ) def __cmp__(self, other): return cmp(self.start, other.start) def __len__(self): raise TypeError("len() doesn't support infinite values, use the 'size' attribute instead") def __nonzero__(self): 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.0.2/webapp/graphite/views.py0000644000000000000000000000052613131244465020624 0ustar rootroot00000000000000import traceback from django.http import HttpResponseServerError from django.template import Context, loader def server_error(request, template_name='500.html'): template = loader.get_template(template_name) context = Context({ 'stacktrace' : traceback.format_exc() }) return HttpResponseServerError( template.render(context) ) graphite-web-1.0.2/webapp/graphite/logger.py0000644000000000000000000000650713131244465020753 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 except ImportError as ie: # py2.6 from logging.handlers import FileHandler from django.conf import settings logging.addLevelName(30,"rendering") logging.addLevelName(30,"cache") class GraphiteLogger: def __init__(self): self.infoLogger = self._config_logger('info.log', 'info', True, level = logging.INFO, ) self.exceptionLogger = self._config_logger('exception.log', 'exception', True, ) self.cacheLogger = self._config_logger('cache.log', 'cache', settings.LOG_CACHE_PERFORMANCE, ) self.renderingLogger = self._config_logger('rendering.log', 'rendering', settings.LOG_RENDERING_PERFORMANCE, ) @staticmethod def _config_logger(log_file_name, name, activate, level=None, when='midnight', backupCount=settings.LOG_ROTATION_COUNT): log_file = os.path.join(settings.LOG_DIR, log_file_name) logger = logging.getLogger(name) if level is not None: logger.setLevel(level) if activate: # if want to log this one formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d :: %(message)s',datefmt='%Y-%m-%d,%H:%M:%S') 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 exception(self,msg="Exception Caught",**kwargs): return self.exceptionLogger.exception(msg,**kwargs) def cache(self,msg,*args,**kwargs): return self.cacheLogger.log(30,msg,*args,**kwargs) def rendering(self,msg,*args,**kwargs): return self.renderingLogger.log(30,msg,*args,**kwargs) log = GraphiteLogger() # import-shared logger instance graphite-web-1.0.2/webapp/graphite/app_settings.py0000644000000000000000000000555513131244465022176 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 #Django settings below, do not touch! APPEND_SLASH = False TEMPLATE_DEBUG = False 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_CLASSES = ( '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', ) ROOT_URLCONF = 'graphite.urls' INSTALLED_APPS = ( 'graphite.metrics', 'graphite.render', 'graphite.browser', 'graphite.composer', 'graphite.account', 'graphite.dashboard', 'graphite.whitelist', 'graphite.events', 'graphite.url_shortener', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.staticfiles', 'tagging', ) AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend'] GRAPHITE_WEB_APP_SETTINGS_LOADED = True graphite-web-1.0.2/webapp/content/0000755000000000000000000000000013131245073016755 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/content/css/0000755000000000000000000000000013131245073017545 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/content/css/default/0000755000000000000000000000000013131245073021171 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/content/css/default/inspect.gif0000755000000000000000000000105413131244446023333 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.0.2/webapp/content/css/default/overlay.png0000755000000000000000000000540213131244446023367 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.0.2/webapp/content/css/default/sizer.gif0000755000000000000000000000031113131244446023015 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, F!"p2 ,wm/ٮuC58T+HUu0h~8 ;graphite-web-1.0.2/webapp/content/css/default/close.gif0000755000000000000000000000176413131244446023003 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.0.2/webapp/content/css/default/center_left.gif0000755000000000000000000000006413131244446024160 0ustar rootroot00000000000000GIF89a !,  g;graphite-web-1.0.2/webapp/content/css/default/top_left.gif0000755000000000000000000000054613131244446023507 0ustar rootroot00000000000000GIF89a !., @P8FÉ#9 % \PJ(\KP(&a6w.D=x(X0#  ###u$%&'()*+ ,-A;graphite-web-1.0.2/webapp/content/css/default/maximize.gif0000755000000000000000000000200013131244446023501 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.0.2/webapp/content/css/default/bottom_left.gif0000755000000000000000000000027313131244446024206 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, 8rB 窞A, 0˵z}Ǿ5\o_;0]ۯ ,v`48;graphite-web-1.0.2/webapp/content/css/default/bottom_mid.gif0000755000000000000000000000010413131244446024016 0ustar rootroot00000000000000GIF89a죣ttt!, 8"ϱH;graphite-web-1.0.2/webapp/content/css/default/center_right.gif0000755000000000000000000000006113131244446024340 0ustar rootroot00000000000000GIF89a!,D\;graphite-web-1.0.2/webapp/content/css/default/bottom_right.gif0000755000000000000000000000027313131244446024371 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, 8!"p2 ,wm/ٮu˩^EL -+}8 ;graphite-web-1.0.2/webapp/content/css/default/clear.gif0000755000000000000000000000176613131244446022766 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.0.2/webapp/content/css/default/resize.gif0000755000000000000000000000021213131244446023162 0ustar rootroot00000000000000GIF89aՇ! ,7Ik8k}ݗ @( H)bb^7J7M{ gC0H;Btҭ";graphite-web-1.0.2/webapp/content/css/default/top_right.gif0000755000000000000000000000054513131244446023671 0ustar rootroot00000000000000GIF89a !/, @Cy 3* "T"*. l3z}Cd>+A+^hhH$$$$$%&'()*+!,.-A;graphite-web-1.0.2/webapp/content/css/default/bottom_right_resize.gif0000755000000000000000000000031113131244446025743 0ustar rootroot00000000000000GIF89a 쬬|||ttt!, F!"p2 ,wm/ٮuC58T+HUu0h~8 ;graphite-web-1.0.2/webapp/content/css/default/top_mid.gif0000755000000000000000000000022513131244446023320 0ustar rootroot00000000000000GIF89a!,`q 0@D;graphite-web-1.0.2/webapp/content/css/default/minimize.gif0000755000000000000000000000177713131244446023523 0ustar rootroot00000000000000GIF89awTUX*-gjgj2j;?XKLb:>{úTBxa`Q燤dT_rnqqsz{R)srqr)`bbcqNMIjm "#,?MI",<ҍЍZ[]]]__ܕ-ٓ-{;|[aaA Rದ%d Q\ 'O\;graphite-web-1.0.2/webapp/content/css/default.css0000644000000000000000000000522313131244446021710 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.0.2/webapp/content/css/dashboard-default.css0000644000000000000000000000135713131244446023641 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.0.2/webapp/content/css/table.css0000644000000000000000000000202613131244446021351 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.0.2/webapp/content/css/darkX.css0000644000000000000000000000512013131244446021331 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.0.2/webapp/content/css/dashboard-white.css0000644000000000000000000000135513131244446023333 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.0.2/webapp/content/css/dashboard.css0000644000000000000000000000522313131244446022213 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.0.2/webapp/content/css/darkX/0000755000000000000000000000000013131245073020616 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/content/css/darkX/frame-right-focused.png0000755000000000000000000000021613131244446025164 0ustar rootroot00000000000000PNG  IHDRqVbKGD pHYsiTStIME'0zIDATxcĀVvmq۾IENDB`graphite-web-1.0.2/webapp/content/css/darkX/titlebar-right-focused.png0000755000000000000000000000026413131244446025703 0ustar rootroot00000000000000PNG  IHDR*bKGD pHYs/tIME rAIDATxڵ˹@CQ3$P8plE 蓻?QwIf. Ub^T7!yIENDB`graphite-web-1.0.2/webapp/content/css/darkX/frame-left-focused.png0000755000000000000000000000021613131244446025001 0ustar rootroot00000000000000PNG  IHDRqVbKGD pHYsiTStIME'y0IDATxcTRT301 "9T]or(IENDB`graphite-web-1.0.2/webapp/content/css/darkX/frame-bottom-right-focused.png0000755000000000000000000000021613131244446026466 0ustar rootroot00000000000000PNG  IHDR[6bKGD pHYsiTStIME$$SoIDATxcπX0YXX0Qm eIENDB`graphite-web-1.0.2/webapp/content/css/darkX/button-close-focused.png0000755000000000000000000000161013131244446025374 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.0.2/webapp/content/css/darkX/frame-bottom-mid-focused.png0000755000000000000000000000021613131244446026122 0ustar rootroot00000000000000PNG  IHDR[6bKGD pHYsiTStIME%$b.IDATxcπX0YXX0Qm eIENDB`graphite-web-1.0.2/webapp/content/css/darkX/button-maximize-focused.png0000755000000000000000000000156213131244446026120 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.0.2/webapp/content/css/darkX/titlebar-left-focused.png0000755000000000000000000000026413131244446025520 0ustar rootroot00000000000000PNG  IHDR*bKGD pHYs/tIMEkVAIDATxڵ˹@CQ3$P8plE 蓻?QwIf. Ub^T7!yIENDB`graphite-web-1.0.2/webapp/content/css/darkX/frame-bottom-left-focused.png0000755000000000000000000000021613131244446026303 0ustar rootroot00000000000000PNG  IHDR[6bKGD pHYsiTStIME$_0`IDATxcπX0YXX0Qm eIENDB`graphite-web-1.0.2/webapp/content/css/darkX/titlebar-mid-focused.png0000755000000000000000000000025713131244446025341 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 startTimeControl = new Ext.form.TimeField({ id: 'start-time', increment: 30, allowBlank: false, value: "12:00 AM", listeners: {select: calendarSelectionMade, specialkey: ifEnter(calendarSelectionMade)} }); 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() { 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) { store = this.targetList.getStore(); selectedRecords = this.targetList.getSelectedRecords(); // Don't move past boundaries exit = false; Ext.each(selectedRecords, function(record) { 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; newSelections = []; Ext.each(selectedRecords, function(recordA) { indexA = store.indexOf( recordA ); valueA = recordA.get('value'); 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() { selectedRecords = this.targetList.getSelectedRecords(); if (selectedRecords.length != 2) return; recordA = selectedRecords[0]; recordB = selectedRecords[1]; 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('movingMin', '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: '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.0.2/webapp/content/js/composer.js0000644000000000000000000001710113131244446021561 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. */ 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(); }, syncTargetList: function () { this.url.removeParam('target'); TargetStore.each(function (record) { this.url.addParam('target', record.data.value); }, this); }, updateImage: function () { /* 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(); } }, 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.0.2/webapp/content/js/completer.js0000644000000000000000000000275713131244446021737 0ustar rootroot00000000000000var 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.0.2/webapp/content/js/ext/0000755000000000000000000000000013131245073020171 5ustar rootroot00000000000000graphite-web-1.0.2/webapp/content/js/ext/ext-all.js0000644000000000000000000257127313131244446022121 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.0.2/webapp/content/js/ext/resources/icons/fam/folder_wrench.png0000644000000000000000000000134413131244446027415 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.0.2/webapp/content/js/ext/resources/icons/fam/grid.png0000644000000000000000000000100113131244446025507 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.0.2/webapp/content/js/ext/resources/icons/fam/information.png0000644000000000000000000000141213131244446027115 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.0.2/webapp/content/js/ext/resources/icons/fam/user.gif0000644000000000000000000000173313131244446025535 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.0.2/webapp/content/js/ext/resources/icons/fam/cog.png0000644000000000000000000000100013131244446025331 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.0.2/webapp/content/js/ext/resources/icons/fam/user_female.gif0000644000000000000000000000172213131244446027044 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.0.2/webapp/content/js/ext/resources/icons/fam/user_female.png0000644000000000000000000000122713131244446027063 0ustar rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<)IDAT8˥]hRaA1=SgwHl ((A ]0*7Å*'IENDB`graphite-web-1.0.2/webapp/content/js/ext/resources/icons/fam/user_suit.gif0000644000000000000000000000173413131244446026602 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.0.2/webapp/content/js/ext/resources/icons/fam/user_green.gif0000644000000000000000000000173113131244446026713 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.0.2/webapp/content/js/ext/resources/icons/fam/application_go.png0000644000000000000000000000117213131244446027563 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.0.2/webapp/content/js/ext/resources/icons/fam/cross.gif0000644000000000000000000000166013131244446025707 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.0.2/webapp/content/js/ext/resources/icons/fam/SILK.txt0000644000000000000000000000023113131244446025363 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.0.2/webapp/content/js/ext/resources/icons/fam/user_red.png0000644000000000000000000000131513131244446026402 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.0.2/webapp/content/js/ext/resources/icons/fam/user_orange.png0000644000000000000000000000132313131244446027102 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.0.2/webapp/content/js/ext/resources/icons/fam/user_gray.png0000644000000000000000000000130213131244446026566 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.0.2/webapp/content/js/ext/resources/icons/fam/connect.png0000644000000000000000000000135413131244446026226 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.0.2/webapp/content/js/ext/resources/icons/fam/user_delete.png0000644000000000000000000000137713131244446027102 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.0.2/webapp/content/js/ext/resources/icons/fam/user_add.png0000644000000000000000000000135213131244446026361 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.0.2/webapp/content/js/ext/resources/icons/fam/control_rewind.png0000644000000000000000000000114613131244446027624 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.0.2/webapp/content/js/ext/resources/icons/fam/accept.png0000644000000000000000000000141513131244446026032 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.0.2/webapp/content/js/ext/resources/icons/fam/feed_error.png0000644000000000000000000000140213131244446026703 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.0.2/webapp/content/js/ext/resources/icons/fam/book.png0000644000000000000000000000112113131244446025517 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.0.2/webapp/content/js/ext/resources/icons/fam/image_add.png0000644000000000000000000000121513131244446026463 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.0.2/webapp/content/js/ext/resources/icons/fam/add.png0000644000000000000000000000133513131244446025324 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.0.2/webapp/content/js/ext/resources/icons/fam/user_suit.png0000644000000000000000000000135413131244446026617 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.0.2/webapp/content/js/ext/resources/icons/fam/user_add.gif0000644000000000000000000000175113131244446026345 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.0.2/webapp/content/js/ext/resources/icons/fam/error.png0000644000000000000000000000123213131244446025721 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.0.2/webapp/content/js/ext/resources/icons/fam/user_comment.png0000644000000000000000000000134713131244446027277 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.0.2/webapp/content/js/ext/resources/icons/fam/plugin_add.gif0000644000000000000000000000176213131244446026667 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.0.2/webapp/content/js/ext/resources/icons/fam/user.png0000644000000000000000000000134513131244446025553 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.0.2/webapp/content/js/ext/resources/icons/fam/user_delete.gif0000644000000000000000000000175113131244446027057 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.0.2/webapp/content/js/ext/resources/icons/fam/add.gif0000644000000000000000000000174213131244446025307 0ustar rootroot00000000000000GIF89a=5B6C8D;Elo2rjkC#σpS.Ԑw8G¯W^ҴWGzSZ- Y&y_+F\OHCG1m/mZNڵ6,kLuTPC yGm1hmfF.Wy} .[GIENDB`graphite-web-1.0.2/webapp/content/js/ext/resources/icons/fam/cog_edit.png0000644000000000000000000000154113131244446026350 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+//?Ɓ!