├── .gitignore ├── .gitmodules ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── clients ├── LICENSE ├── google-app-engine │ ├── LICENSE.txt │ ├── README.txt │ ├── __init__.py │ └── wrapper.py ├── growl │ ├── apple-touch-icon.png │ └── arecibo_growl.py ├── javascript │ ├── LICENSE.txt │ ├── error-compress.js │ ├── error-test.js │ ├── error.js │ └── test.html ├── php │ ├── LICENSE.txt │ ├── README.txt │ ├── arecibo.php │ └── example.php ├── plone │ └── clearwind.arecibo │ │ ├── HISTORY.txt │ │ ├── README.txt │ │ ├── clearwind.arecibo.egg-info │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ ├── entry_points.txt │ │ ├── namespace_packages.txt │ │ ├── not-zip-safe │ │ ├── requires.txt │ │ └── top_level.txt │ │ ├── clearwind │ │ ├── __init__.py │ │ └── arecibo │ │ │ ├── Extensions │ │ │ └── Install.py │ │ │ ├── __init__.py │ │ │ ├── browser │ │ │ ├── __init__.py │ │ │ ├── arecibo-icon.png │ │ │ ├── config.py │ │ │ └── configure.zcml │ │ │ ├── config.py │ │ │ ├── configure.zcml │ │ │ ├── interfaces.py │ │ │ ├── lib │ │ │ ├── LICENSE.txt │ │ │ ├── README.txt │ │ │ ├── __init__.py │ │ │ ├── arecibo.py │ │ │ └── simplejson │ │ │ │ ├── __init__.py │ │ │ │ ├── _speedups.c │ │ │ │ ├── decoder.py │ │ │ │ ├── encoder.py │ │ │ │ ├── scanner.py │ │ │ │ └── tool.py │ │ │ ├── patch.py │ │ │ ├── profiles │ │ │ └── default │ │ │ │ ├── actionicons.xml │ │ │ │ ├── clearwind.arecibo.txt │ │ │ │ └── controlpanel.xml │ │ │ ├── setuphandlers.py │ │ │ ├── site_configuration.py.in │ │ │ ├── tests.py │ │ │ ├── version.txt │ │ │ └── wrapper.py │ │ ├── docs │ │ ├── HISTORY.txt │ │ ├── INSTALL.txt │ │ ├── LICENSE.GPL │ │ └── LICENSE.txt │ │ ├── setup.cfg │ │ └── setup.py ├── python │ ├── LICENSE.txt │ ├── README.txt │ ├── arecibo │ │ ├── __init__.py │ │ ├── arecibo.py │ │ └── simplejson │ │ │ ├── __init__.py │ │ │ ├── _speedups.c │ │ │ ├── decoder.py │ │ │ ├── encoder.py │ │ │ ├── scanner.py │ │ │ └── tool.py │ ├── setup.cfg │ └── setup.py ├── rails │ ├── README │ ├── Rakefile │ ├── assets │ │ └── arecibo.rhtml │ ├── init.rb │ ├── install.rb │ ├── lib │ │ ├── arecibolib │ │ │ ├── LICENSE.txt │ │ │ ├── README.txt │ │ │ ├── arecibo.rb │ │ │ └── test.rb │ │ └── wrapper.rb │ ├── tasks │ │ └── arecibo_tasks.rake │ ├── test │ │ └── arecibo_test.rb │ └── uninstall.rb └── ruby │ ├── LICENSE.txt │ ├── README.txt │ ├── arecibo.rb │ └── test.rb ├── configure.py └── listener ├── LICENSE ├── app_engine ├── .gitignore ├── __init__.py ├── app.yaml.example ├── app │ ├── __init__.py │ ├── base.py │ ├── context.py │ ├── errors.py │ ├── fields.py │ ├── forms.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── remote.py │ ├── paginator.py │ ├── tags.py │ ├── templates │ │ ├── 403.html │ │ ├── 404.html │ │ ├── 500.html │ │ ├── base.html │ │ ├── error.js │ │ ├── index.html │ │ └── setup.html │ ├── test_runner.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── appengine_django │ ├── __init__.py │ ├── auth │ │ ├── __init__.py │ │ ├── decorators.py │ │ ├── middleware.py │ │ ├── models.py │ │ ├── signals.py │ │ └── templatetags.py │ ├── conf │ │ └── app_template │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ └── views.py │ ├── db │ │ ├── __init__.py │ │ ├── base.py │ │ └── creation.py │ ├── mail.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── console.py │ │ │ ├── flush.py │ │ │ ├── reset.py │ │ │ ├── rollback.py │ │ │ ├── runserver.py │ │ │ ├── startapp.py │ │ │ ├── testserver.py │ │ │ ├── update.py │ │ │ └── vacuum_indexes.py │ ├── models.py │ ├── replacement_imp.py │ ├── serializer │ │ ├── __init__.py │ │ ├── json.py │ │ ├── python.py │ │ ├── pyyaml.py │ │ └── xml.py │ ├── sessions │ │ ├── __init__.py │ │ ├── backends │ │ │ ├── __init__.py │ │ │ └── db.py │ │ └── models.py │ └── tests │ │ ├── __init__.py │ │ ├── commands_test.py │ │ ├── core_test.py │ │ ├── db_test.py │ │ ├── integration_test.py │ │ ├── memcache_test.py │ │ ├── model_test.py │ │ └── serialization_test.py ├── cron.yaml ├── custom │ ├── __init__.py │ ├── examples │ │ ├── __init__.py │ │ └── default_public.py │ ├── models.py │ ├── readme.txt │ └── tests.py ├── error │ ├── __init__.py │ ├── agent.py │ ├── feeds.py │ ├── forms.py │ ├── listeners.py │ ├── models.py │ ├── signals.py │ ├── templates │ │ ├── filter.html │ │ ├── group.html │ │ ├── list-snippet.html │ │ ├── list.html │ │ ├── pagination.html │ │ ├── subnav.html │ │ └── view.html │ ├── tests.py │ ├── urls.py │ ├── validations.py │ └── views.py ├── index.yaml ├── issues │ ├── __init__.py │ ├── forms.py │ ├── listeners.py │ ├── models.py │ ├── signals.py │ ├── templates │ │ ├── comment_add.html │ │ ├── issue_add.html │ │ ├── issue_edit.html │ │ ├── issue_filter.html │ │ ├── issue_list.html │ │ ├── issue_log_list.html │ │ ├── issue_log_snippet.html │ │ ├── issue_project_url.html │ │ ├── issue_subnav.html │ │ └── issue_view.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── local_settings.py.example ├── main.py ├── manage.py ├── markdown ├── media │ ├── css │ │ ├── date.css │ │ ├── ie.css │ │ ├── print.css │ │ ├── screen.css │ │ └── site.css │ ├── img │ │ ├── alert-overlay.png │ │ ├── apple-touch-icon.png │ │ ├── arecibo.png │ │ ├── arrow.png │ │ ├── favicon.ico │ │ ├── logo-opacity.png │ │ ├── priority-1.jpg │ │ ├── priority-2.jpg │ │ ├── priority-3.jpg │ │ ├── read.png │ │ ├── status-fixed.jpg │ │ └── status-not-fixed.jpg │ └── js │ │ └── site.js ├── notifications │ ├── __init__.py │ ├── email.py │ ├── listeners.py │ ├── models.py │ ├── registry.py │ ├── signals.py │ ├── templates │ │ └── notification_list.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── profiles │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── utils.py ├── projects │ ├── __init__.py │ ├── forms.py │ ├── listeners.py │ ├── models.py │ ├── signals.py │ ├── templates │ │ ├── project_add.html │ │ ├── project_edit.html │ │ ├── project_list.html │ │ ├── project_url_add.html │ │ ├── project_url_edit.html │ │ └── projects_subnav.html │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── readme.txt ├── receiving │ ├── __init__.py │ ├── http.py │ ├── mail.py │ ├── mail_django.py │ ├── models.py │ ├── post.py │ ├── tests.py │ └── urls.py ├── settings.py ├── stats │ ├── __init__.py │ ├── models.py │ ├── signals.py │ ├── templates │ │ └── stats.html │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── urls.py ├── users │ ├── __init__.py │ ├── forms.py │ ├── templates │ │ ├── user_edit.html │ │ └── user_list.html │ ├── urls.py │ ├── utils.py │ └── views.py └── userstorage │ ├── __init__.py │ ├── middleware.py │ └── utils.py ├── docs ├── Makefile └── source │ ├── _static │ └── default.css │ ├── client │ ├── django.rst │ ├── index.rst │ ├── javascript.rst │ ├── php.rst │ ├── plone.rst │ ├── post.rst │ ├── python.rst │ ├── rails.rst │ ├── ruby.rst │ └── variables.rst │ ├── conf.py │ ├── index.rst │ ├── introduction.rst │ ├── release.rst │ └── server │ ├── concepts.rst │ ├── customisation.rst │ ├── installation-appengine.rst │ ├── installation.rst │ ├── remoteaccess.rst │ ├── scripts.rst │ └── useraccess.rst ├── lib ├── __init__.py ├── markdown │ ├── __init__.py │ ├── blockparser.py │ ├── blockprocessors.py │ ├── commandline.py │ ├── etree_loader.py │ ├── extensions │ │ ├── __init__.py │ │ ├── abbr.py │ │ ├── codehilite.py │ │ ├── def_list.py │ │ ├── extra.py │ │ ├── fenced_code.py │ │ ├── footnotes.py │ │ ├── headerid.py │ │ ├── html_tidy.py │ │ ├── imagelinks.py │ │ ├── meta.py │ │ ├── rss.py │ │ ├── tables.py │ │ ├── toc.py │ │ └── wikilinks.py │ ├── html4.py │ ├── inlinepatterns.py │ ├── odict.py │ ├── postprocessors.py │ ├── preprocessors.py │ └── treeprocessors.py └── userstorage │ ├── __init__.py │ ├── middleware.py │ └── utils.py ├── media ├── css │ ├── date.css │ ├── ie.css │ ├── print.css │ ├── screen.css │ └── site.css ├── img │ ├── alert-overlay.png │ ├── apple-touch-icon.png │ ├── arecibo.png │ ├── arrow.png │ ├── favicon.ico │ ├── logo-opacity.png │ ├── priority-1.jpg │ ├── priority-2.jpg │ ├── priority-3.jpg │ ├── read.png │ ├── status-fixed.jpg │ └── status-not-fixed.jpg └── js │ └── site.js └── normal ├── __init__.py ├── app ├── __init__.py ├── base.py ├── context.py ├── decorators.py ├── errors.py ├── fixtures │ └── users.json ├── forms.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── groups.py ├── middleware.py ├── models.py ├── paginator.py ├── tags.py ├── templates │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── base.html │ ├── error.js │ ├── index.html │ └── setup.html ├── templatetags │ ├── __init__.py │ └── arecibo.py ├── test_runner.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── celeryconfig.py.dist ├── config └── arecibo.wsgi.sample ├── custom ├── __init__.py ├── examples │ ├── __init__.py │ ├── default_public.py │ └── no_notifications.py └── models.py ├── error ├── __init__.py ├── admin.py ├── agent.py ├── feeds.py ├── forms.py ├── listeners.py ├── models.py ├── signals.py ├── templates │ ├── filter.html │ ├── group-edit.html │ ├── group.html │ ├── list-snippet.html │ ├── list.html │ ├── pagination.html │ ├── subnav.html │ └── view.html ├── tests.py ├── urls.py ├── validations.py └── views.py ├── local_settings.py.dist ├── manage.py ├── migrations └── 01-add-count.sql ├── notifications ├── __init__.py ├── admin.py ├── email.py ├── listeners.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── cleanup_notifications.py │ │ └── send_notifications.py ├── models.py ├── signals.py ├── templates │ └── notification_list.html ├── tests.py ├── urls.py └── views.py ├── projects ├── __init__.py ├── admin.py ├── forms.py ├── listeners.py ├── models.py ├── signals.py ├── templates │ ├── project_add.html │ ├── project_edit.html │ ├── project_list.html │ ├── project_url_add.html │ ├── project_url_edit.html │ └── projects_subnav.html ├── tests.py ├── urls.py ├── utils.py └── views.py ├── receiving ├── __init__.py ├── http.py ├── models.py ├── post.py ├── tests.py └── urls.py ├── requirements.txt ├── settings.py ├── stats ├── __init__.py ├── models.py ├── signals.py ├── templates │ ├── stats_subnav.html │ └── stats_view.html ├── tests.py ├── urls.py └── views.py ├── urls.py └── users ├── __init__.py ├── forms.py ├── models.py ├── templates ├── user_create.html ├── user_edit.html ├── user_list.html ├── user_login.html └── user_password.html ├── tests.py ├── urls.py ├── utils.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.kpf 3 | listener/normal/local_settings.py 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clients/django"] 2 | path = clients/django 3 | url = git@github.com:andymckay/django-arecibo.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Most of the code and hence blame is by: 2 | 3 | Andy McKay (Clearwind Consulting) 4 | 5 | Contributions from: 6 | 7 | Chris Adams (http://github.com/acdha) 8 | David Glick (http://github.com/davisagli) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is under multiple licenses. 2 | 3 | - all work under the clients directory is under the BSD license (see clients/LICENSE) 4 | 5 | - all work under the listener directory is under the BSD license (see listener/LICENSE) 6 | 7 | - the following is under the listener directory is not by ClearWind and has the following licenses: 8 | 9 | - listener/lib/markdown 10 | 11 | This work is from: 12 | 13 | http://pypi.python.org/pypi/Markdown 14 | 15 | And is under the BSD License 16 | 17 | - listener/app_engine/appengine_django 18 | 19 | This is work from: 20 | 21 | http://code.google.com/p/google-app-engine-django/ 22 | 23 | And is under the Apache 2.0 license: 24 | 25 | http://www.apache.org/licenses/LICENSE-2.0 26 | 27 | - listener/app_engine/django 28 | 29 | This is the work of the Django Software Foundation and is under the BSD license: 30 | 31 | http://code.djangoproject.com/browser/django/trunk/LICENSE 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is dead. 2 | 3 | [No Maintenance Intended](http://unmaintained.tech/badge.svg) 4 | 5 | I'm also sad to see that [Arceibo itself will be demolished](https://www.sciencenews.org/article/arecibo-telescope-observatory-icon-puerto-rico-science-demolished). 6 | 7 | This is the open source Arecibo client that allows you to record errors on your website. 8 | 9 | This is an App Engine project that you can upload to App Engine and run this application, then point all your sites to use that system. 10 | 11 | Documentation is available in the listener/docs folder. 12 | -------------------------------------------------------------------------------- /clients/LICENSE: -------------------------------------------------------------------------------- 1 | BSD, Copyright Andy McKay and Contributors -------------------------------------------------------------------------------- /clients/google-app-engine/LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD, Copyright Andy McKay and contributors -------------------------------------------------------------------------------- /clients/google-app-engine/README.txt: -------------------------------------------------------------------------------- 1 | This is the next version of the Arecibo API which connects to the Google App Engine implementation of Arecibo. 2 | 3 | Arecibo is an open source application to provide error logging and notifications for your web sites. 4 | 5 | See http://areciboapp.com/docs/client/django.html for more information 6 | 7 | * Because when you upload an app to App Engine you can't specify other libraries to use, you pretty much have to include this module in your App Engine source. 8 | 9 | * The main API is HTTP. 10 | 11 | * Email is a bit problematic because Gmail inserts new lines into plain-text emails when you send them. These new lines break the JSON. 12 | 13 | * This requires Django, but only because the standard settings are pulled in from django.conf, if you aren't using Django on App Engine, replace that with something else. -------------------------------------------------------------------------------- /clients/google-app-engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/google-app-engine/__init__.py -------------------------------------------------------------------------------- /clients/growl/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/growl/apple-touch-icon.png -------------------------------------------------------------------------------- /clients/javascript/LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD, Copyright Andy McKay and contributors -------------------------------------------------------------------------------- /clients/javascript/test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | Arecibo JavaScript Client Test Page 8 | 9 | 10 | 11 |

12 | NOTE: This page assumes you have a local 13 | AppEngine server running locally. Configure your arecibo install 14 | accordingly. 15 |

16 | 17 | 18 | 21 | 22 | 30 | 31 | 42 |

Check your Arecibo server - you should have an error listed

43 | 44 | 45 | -------------------------------------------------------------------------------- /clients/php/LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD, Copyright Andy McKay and contributors -------------------------------------------------------------------------------- /clients/php/README.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/php/README.txt -------------------------------------------------------------------------------- /clients/php/arecibo.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /clients/php/example.php: -------------------------------------------------------------------------------- 1 | "w3;5qwy45qshtqu46tdtgheq47s.ert6ew45e4i2w65", 5 | ); 6 | post("http://test-areciboapp.appspot.com/v/1/", $fields) 7 | ?> -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/HISTORY.txt: -------------------------------------------------------------------------------- 1 | 0.2 > 0.3: 2 | 3 | - Removed the skins and layers 4 | 5 | - Made it catch all errors (even Zope ones) by patching SiteErrorLog 6 | 7 | - Allowed the override of configuration values by the use of site_configuration.py 8 | 9 | - Added in the ignores property 10 | 11 | - Made uninstalling remove configlet 12 | 13 | - Made install optional (just site_configuration.py will be enough) 14 | 15 | - Any errors sending to Arecibo are logged -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/README.txt: -------------------------------------------------------------------------------- 1 | This package is an interface to the Arecibo application from ClearWind. It provides an interface for Plone to report its errors. For more information see: http://areciboapp.com and specifically http://www.areciboapp.com/docs/plone/ 2 | 3 | 4 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: clearwind.arecibo 3 | Version: 0.3dev-r43 4 | Summary: Connector from Plone to Arecibo 5 | Home-page: http://clearwind.ca 6 | Author: ClearWind Consulting 7 | Author-email: andy@clearwind.ca 8 | License: BSD 9 | Description: This package is an interface to the Arecibo application from ClearWind. It provides an interface for Plone to report its errors. For more information see: http://areciboapp.com and specifically http://www.areciboapp.com/docs/plone/ 10 | 11 | 12 | 13 | 14 | Platform: UNKNOWN 15 | Classifier: Framework :: Plone 16 | Classifier: Programming Language :: Python 17 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 18 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | 2 | # -*- Entry points: -*- 3 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/namespace_packages.txt: -------------------------------------------------------------------------------- 1 | clearwind 2 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | setuptools -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind.arecibo.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | clearwind 2 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/__init__.py: -------------------------------------------------------------------------------- 1 | # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages 2 | try: 3 | __import__('pkg_resources').declare_namespace(__name__) 4 | except ImportError: 5 | from pkgutil import extend_path 6 | __path__ = extend_path(__path__, __name__) 7 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/Extensions/Install.py: -------------------------------------------------------------------------------- 1 | import transaction 2 | from Products.CMFCore.utils import getToolByName 3 | from Products.CMFCore.DirectoryView import createDirectoryView 4 | from clearwind.arecibo.interfaces import IAreciboConfiguration 5 | from clearwind.arecibo.config import AreciboConfiguration 6 | 7 | EXTENSION_PROFILES = ('clearwind.arecibo:default',) 8 | 9 | def uninstall(self): 10 | """ Uninstall """ 11 | cp = getToolByName(self, 'portal_controlpanel') 12 | if "arecibo" in [ c.id for c in cp._actions ]: 13 | cp.unregisterConfiglet("arecibo") 14 | 15 | 16 | def install(self, reinstall=False): 17 | """ We still have to do this? """ 18 | 19 | portal_quickinstaller = getToolByName(self, 'portal_quickinstaller') 20 | portal_setup = getToolByName(self, 'portal_setup') 21 | 22 | sm = self.getSiteManager() 23 | 24 | if not sm.queryUtility(IAreciboConfiguration, 25 | name='Arecibo_config'): 26 | sm.registerUtility(AreciboConfiguration(), 27 | IAreciboConfiguration, 28 | 'Arecibo_config') 29 | 30 | for extension_id in EXTENSION_PROFILES: 31 | portal_setup.runAllImportStepsFromProfile('profile-%s' % extension_id, purge_old=False) 32 | product_name = extension_id.split(':')[0] 33 | portal_quickinstaller.notifyInstalled(product_name) 34 | transaction.savepoint() -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/__init__.py: -------------------------------------------------------------------------------- 1 | from AccessControl import allow_module, allow_class, allow_type 2 | from AccessControl import ModuleSecurityInfo, ClassSecurityInfo 3 | 4 | ModuleSecurityInfo('clearwind.arecibo.wrapper').declarePublic('arecibo') 5 | 6 | import config 7 | import patch 8 | 9 | def initialize(context): 10 | """Initializer called when used as a Zope 2 product.""" -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/browser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/plone/clearwind.arecibo/clearwind/arecibo/browser/__init__.py -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/browser/arecibo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/plone/clearwind.arecibo/clearwind/arecibo/browser/arecibo-icon.png -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/browser/config.py: -------------------------------------------------------------------------------- 1 | from zope.formlib import form 2 | from Products.Five.formlib import formbase 3 | from plone.app.controlpanel.form import ControlPanelForm 4 | from clearwind.arecibo.interfaces import IAreciboConfiguration 5 | 6 | from zope.i18nmessageid import MessageFactory 7 | _ = MessageFactory('clearwind.arecibo') 8 | 9 | class AreciboConfigurationForm(ControlPanelForm): 10 | form_fields = form.Fields(IAreciboConfiguration) 11 | 12 | description = _(u"Configure Plone to work with your Arecibo account here.") 13 | form_name = _(u"Arecibo settings") 14 | label = _(u"Arecibo settings") -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/browser/configure.zcml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 15 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/config.py: -------------------------------------------------------------------------------- 1 | from zope.interface import implements 2 | from zope.schema.fieldproperty import FieldProperty 3 | from zope.component import getUtility 4 | 5 | from interfaces import IAreciboConfiguration 6 | 7 | from OFS.SimpleItem import SimpleItem 8 | 9 | class AreciboConfiguration(SimpleItem): 10 | implements(IAreciboConfiguration) 11 | account_number = FieldProperty(IAreciboConfiguration['account_number']) 12 | transport = FieldProperty(IAreciboConfiguration['transport']) 13 | 14 | def form_adapter(context): 15 | return getUtility(IAreciboConfiguration, name='Arecibo_config', context=context) -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/configure.zcml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/interfaces.py: -------------------------------------------------------------------------------- 1 | from zope.interface import Interface 2 | from zope.schema import Choice 3 | from zope.schema import TextLine 4 | from zope.schema.vocabulary import SimpleTerm 5 | from zope.schema.vocabulary import SimpleVocabulary 6 | 7 | from zope.i18nmessageid import MessageFactory 8 | _ = MessageFactory('clearwind.arecibo') 9 | 10 | arecibo_choices = { 11 | _(u"Send via http"): "http", 12 | _(u"Send via email"): "smtp" 13 | } 14 | arecibo_choices_vocab = SimpleVocabulary( 15 | [SimpleTerm(v, v, k) for k, v in arecibo_choices.items()] 16 | ) 17 | 18 | class IAreciboConfiguration(Interface): 19 | """ This interface defines the configlet.""" 20 | account_number = TextLine(title=_(u"Arecibo public account number"), 21 | required=True) 22 | transport = Choice(title=_(u'Transport'), 23 | description=_(u"""How errors will be sent to Arecibo, for mail 24 | to work, your mail host must be correctly configured."""), 25 | default='http', 26 | vocabulary=arecibo_choices_vocab, 27 | required=False) -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/lib/README.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/plone/clearwind.arecibo/clearwind/arecibo/lib/README.txt -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/plone/clearwind.arecibo/clearwind/arecibo/lib/__init__.py -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/lib/simplejson/tool.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Using simplejson from the shell to validate and 3 | pretty-print:: 4 | 5 | $ echo '{"json":"obj"}' | python -msimplejson 6 | { 7 | "json": "obj" 8 | } 9 | $ echo '{ 1.2:3.4}' | python -msimplejson 10 | Expecting property name: line 1 column 2 (char 2) 11 | 12 | Note that the JSON produced by this module's default settings 13 | is a subset of YAML, so it may be used as a serializer for that as well. 14 | """ 15 | import simplejson 16 | 17 | # 18 | # Pretty printer: 19 | # curl http://mochikit.com/examples/ajax_tables/domains.json | python -msimplejson.tool 20 | # 21 | 22 | def main(): 23 | import sys 24 | if len(sys.argv) == 1: 25 | infile = sys.stdin 26 | outfile = sys.stdout 27 | elif len(sys.argv) == 2: 28 | infile = open(sys.argv[1], 'rb') 29 | outfile = sys.stdout 30 | elif len(sys.argv) == 3: 31 | infile = open(sys.argv[1], 'rb') 32 | outfile = open(sys.argv[2], 'wb') 33 | else: 34 | raise SystemExit("%s [infile [outfile]]" % (sys.argv[0],)) 35 | try: 36 | obj = simplejson.load(infile) 37 | except ValueError, e: 38 | raise SystemExit(e) 39 | simplejson.dump(obj, outfile, sort_keys=True, indent=4) 40 | outfile.write('\n') 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/patch.py: -------------------------------------------------------------------------------- 1 | from Products.SiteErrorLog import SiteErrorLog 2 | from wrapper import arecibo 3 | import traceback 4 | 5 | old_raising = SiteErrorLog.SiteErrorLog.raising 6 | def raising(self, *args, **kw): 7 | if self.aq_parent.meta_type == "Plone Site": 8 | err = str(getattr(args[0][0], '__name__', args[0][0])) 9 | tb = "\n".join(traceback.format_tb(args[0][2])) 10 | msg = args[0][1] 11 | arecibo(self, error_type=err, error_tb=tb, error_msg=msg) 12 | return old_raising(self, *args, **kw) 13 | 14 | SiteErrorLog.SiteErrorLog.raising = raising 15 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/profiles/default/actionicons.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/profiles/default/clearwind.arecibo.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/plone/clearwind.arecibo/clearwind/arecibo/profiles/default/clearwind.arecibo.txt -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/profiles/default/controlpanel.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | Manage portal 7 | 8 | 9 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/setuphandlers.py: -------------------------------------------------------------------------------- 1 | from Products.CMFCore.DirectoryView import createDirectoryView 2 | from Products.CMFCore.utils import getToolByName 3 | from Products.CMFPlone.interfaces import ISelectableConstrainTypes 4 | from Products.CMFEditions.setuphandlers import DEFAULT_POLICIES 5 | import string 6 | 7 | def setupSkins(portal): 8 | def add(portal, name, location): 9 | portal_skins = getToolByName(portal, "portal_skins") 10 | if name not in portal_skins.objectIds(): 11 | createDirectoryView(portal_skins, location, name) 12 | 13 | skins = portal_skins.getSkinSelections() 14 | for skin in skins: 15 | path = portal_skins.getSkinPath(skin) 16 | path = [ p.strip() for p in path.split(',') ] 17 | if name not in path: 18 | if 'custom' in path: 19 | pos = path.index('custom') + 1 20 | else: 21 | pos = 0 22 | path.insert(pos, name) 23 | path = ", ".join(path) 24 | portal_skins.addSkinSelection(skin, path) 25 | 26 | add(portal, "arecibo", "clearwind.arecibo:skins") 27 | 28 | def importVarious(context): 29 | if context.readDataFile("clearwind.arecibo.txt") is None: 30 | return 31 | portal = context.getSite() 32 | setupSkins(portal) 33 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/site_configuration.py.in: -------------------------------------------------------------------------------- 1 | # some of these values can be set in the plone 2 | # control panel admin interface, any values set 3 | # there will be override these values. 4 | # 5 | # the idea here is that you can set these as 6 | # defaults en-mass through say buildout or 7 | # whatever mechanism you'd like 8 | config = { 9 | # your arecibo account number 10 | "account": "your account number", 11 | # the transport you'd like, we'd default to http 12 | "transport": "http", 13 | # a dictionary of all the priorities by status so 14 | # you can alter the priorities as you would like 15 | "priorities": { 16 | 404: 5, 17 | 500: 1, 18 | }, 19 | # the default priority for everything else 20 | "default-priority": 3, 21 | # types you'd like to ignore 22 | "ignores": ["Redirect","NotFound"] 23 | } -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from zope.testing import doctestunit 4 | from zope.component import testing 5 | from Testing import ZopeTestCase as ztc 6 | 7 | from Products.Five import zcml 8 | from Products.Five import fiveconfigure 9 | from Products.PloneTestCase import PloneTestCase as ptc 10 | from Products.PloneTestCase.layer import PloneSite 11 | ptc.setupPloneSite() 12 | 13 | import clearwind.arecibo 14 | 15 | class TestCase(ptc.PloneTestCase): 16 | class layer(PloneSite): 17 | @classmethod 18 | def setUp(cls): 19 | fiveconfigure.debug_mode = True 20 | zcml.load_config('configure.zcml', 21 | clearwind.arecibo) 22 | fiveconfigure.debug_mode = False 23 | 24 | @classmethod 25 | def tearDown(cls): 26 | pass 27 | 28 | 29 | def test_suite(): 30 | return unittest.TestSuite([ 31 | 32 | # Unit tests 33 | #doctestunit.DocFileSuite( 34 | # 'README.txt', package='clearwind.arecibo', 35 | # setUp=testing.setUp, tearDown=testing.tearDown), 36 | 37 | #doctestunit.DocTestSuite( 38 | # module='clearwind.arecibo.mymodule', 39 | # setUp=testing.setUp, tearDown=testing.tearDown), 40 | 41 | 42 | # Integration tests that use PloneTestCase 43 | #ztc.ZopeDocFileSuite( 44 | # 'README.txt', package='clearwind.arecibo', 45 | # test_class=TestCase), 46 | 47 | #ztc.FunctionalDocFileSuite( 48 | # 'browser.txt', package='clearwind.arecibo', 49 | # test_class=TestCase), 50 | 51 | ]) 52 | 53 | if __name__ == '__main__': 54 | unittest.main(defaultTest='test_suite') 55 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/clearwind/arecibo/version.txt: -------------------------------------------------------------------------------- 1 | 0.3 -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/docs/HISTORY.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.0 - Unreleased 5 | ---------------- 6 | 7 | * Initial release 8 | 9 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/docs/INSTALL.txt: -------------------------------------------------------------------------------- 1 | clearwind.arecibo Installation 2 | ========================== 3 | 4 | To install clearwind.arecibo into the global Python environment (or a workingenv), 5 | using a traditional Zope 2 instance, you can do this: 6 | 7 | * When you're reading this you have probably already run 8 | ``easy_install clearwind.arecibo``. Find out how to install setuptools 9 | (and EasyInstall) here: 10 | http://peak.telecommunity.com/DevCenter/EasyInstall 11 | 12 | * If you are using Zope 2.9 (not 2.10), get `pythonproducts`_ and install it 13 | via:: 14 | 15 | python setup.py install --home /path/to/instance 16 | 17 | into your Zope instance. 18 | 19 | * Create a file called ``clearwind.arecibo-configure.zcml`` in the 20 | ``/path/to/instance/etc/package-includes`` directory. The file 21 | should only contain this:: 22 | 23 | 24 | 25 | .. _pythonproducts: http://plone.org/products/pythonproducts 26 | 27 | 28 | Alternatively, if you are using zc.buildout and the plone.recipe.zope2instance 29 | recipe to manage your project, you can do this: 30 | 31 | * Add ``clearwind.arecibo`` to the list of eggs to install, e.g.: 32 | 33 | [buildout] 34 | ... 35 | eggs = 36 | ... 37 | clearwind.arecibo 38 | 39 | * Tell the plone.recipe.zope2instance recipe to install a ZCML slug: 40 | 41 | [instance] 42 | recipe = plone.recipe.zope2instance 43 | ... 44 | zcml = 45 | clearwind.arecibo 46 | 47 | * Re-run buildout, e.g. with: 48 | 49 | $ ./bin/buildout 50 | 51 | You can skip the ZCML slug if you are going to explicitly include the package 52 | from another package's configure.zcml file. 53 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/docs/LICENSE.txt: -------------------------------------------------------------------------------- 1 | clearwind.arecibo is copyright ClearWind Consulting 2 | 3 | This program is free software; you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation; either version 2 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program; if not, write to the Free Software 15 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, 16 | MA 02111-1307 USA. 17 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = dev 3 | tag_svn_revision = true 4 | -------------------------------------------------------------------------------- /clients/plone/clearwind.arecibo/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | version = '0.3' 5 | 6 | setup(name='clearwind.arecibo', 7 | version=version, 8 | description="Connector from Plone to Arecibo", 9 | long_description=open("README.txt").read() + "\n", 10 | # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers 11 | classifiers=[ 12 | "Framework :: Plone", 13 | "Programming Language :: Python", 14 | "Topic :: Software Development :: Libraries :: Python Modules", 15 | ], 16 | keywords='', 17 | author='ClearWind Consulting', 18 | author_email='andy@clearwind.ca', 19 | url='http://clearwind.ca', 20 | license='BSD', 21 | packages=find_packages(exclude=['ez_setup']), 22 | namespace_packages=['clearwind'], 23 | include_package_data=True, 24 | zip_safe=False, 25 | install_requires=[ 26 | 'setuptools', 27 | # -*- Extra requirements: -*- 28 | ], 29 | entry_points=""" 30 | # -*- Entry points: -*- 31 | """, 32 | ) 33 | -------------------------------------------------------------------------------- /clients/python/README.txt: -------------------------------------------------------------------------------- 1 | This is the next version of the Arecibo API which connects to the Google App Engine implementation of Arecibo. 2 | 3 | Arecibo is an open source application to provide error logging and notifications for your web sites. 4 | 5 | See http://areciboapp.com/docs/client/python.html for more information 6 | -------------------------------------------------------------------------------- /clients/python/arecibo/__init__.py: -------------------------------------------------------------------------------- 1 | from arecibo import post -------------------------------------------------------------------------------- /clients/python/arecibo/simplejson/tool.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Using simplejson from the shell to validate and 3 | pretty-print:: 4 | 5 | $ echo '{"json":"obj"}' | python -msimplejson 6 | { 7 | "json": "obj" 8 | } 9 | $ echo '{ 1.2:3.4}' | python -msimplejson 10 | Expecting property name: line 1 column 2 (char 2) 11 | 12 | Note that the JSON produced by this module's default settings 13 | is a subset of YAML, so it may be used as a serializer for that as well. 14 | """ 15 | import simplejson 16 | 17 | # 18 | # Pretty printer: 19 | # curl http://mochikit.com/examples/ajax_tables/domains.json | python -msimplejson.tool 20 | # 21 | 22 | def main(): 23 | import sys 24 | if len(sys.argv) == 1: 25 | infile = sys.stdin 26 | outfile = sys.stdout 27 | elif len(sys.argv) == 2: 28 | infile = open(sys.argv[1], 'rb') 29 | outfile = sys.stdout 30 | elif len(sys.argv) == 3: 31 | infile = open(sys.argv[1], 'rb') 32 | outfile = open(sys.argv[2], 'wb') 33 | else: 34 | raise SystemExit("%s [infile [outfile]]" % (sys.argv[0],)) 35 | try: 36 | obj = simplejson.load(infile) 37 | except ValueError, e: 38 | raise SystemExit(e) 39 | simplejson.dump(obj, outfile, sort_keys=True, indent=4) 40 | outfile.write('\n') 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /clients/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_svn_revision = true 3 | -------------------------------------------------------------------------------- /clients/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | version = '0.1.3' 5 | 6 | setup(name='arecibo', 7 | version=version, 8 | description="Connector from Python to Arecibo", 9 | long_description=open("README.txt").read() + "\n", 10 | # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers 11 | classifiers=[ 12 | "Programming Language :: Python", 13 | "Topic :: Software Development :: Libraries :: Python Modules", 14 | ], 15 | keywords='', 16 | author='Clearwind Consulting', 17 | author_email='andy@clearwind.ca', 18 | url='http://clearwind.ca', 19 | license='BSD', 20 | packages=find_packages(exclude=['ez_setup']), 21 | include_package_data=True, 22 | zip_safe=False, 23 | install_requires=[ 24 | 'setuptools', 25 | # -*- Extra requirements: -*- 26 | ], 27 | entry_points=""" 28 | # -*- Entry points: -*- 29 | """, 30 | ) 31 | -------------------------------------------------------------------------------- /clients/rails/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'rake/rdoctask' 4 | 5 | desc 'Default: run unit tests.' 6 | task :default => :test 7 | 8 | desc 'Test the arecibo plugin.' 9 | Rake::TestTask.new(:test) do |t| 10 | t.libs << 'lib' 11 | t.pattern = 'test/**/*_test.rb' 12 | t.verbose = true 13 | end 14 | 15 | desc 'Generate documentation for the arecibo plugin.' 16 | Rake::RDocTask.new(:rdoc) do |rdoc| 17 | rdoc.rdoc_dir = 'rdoc' 18 | rdoc.title = 'Arecibo' 19 | rdoc.options << '--line-numbers' << '--inline-source' 20 | rdoc.rdoc_files.include('README') 21 | rdoc.rdoc_files.include('lib/**/*.rb') 22 | end 23 | -------------------------------------------------------------------------------- /clients/rails/assets/arecibo.rhtml: -------------------------------------------------------------------------------- 1 |

Error

2 | 3 |

An error has occurred in completing that request. This could be by 4 | design, or it might be an error. This error has been reported 5 | and your reference number is: <%= @arecibodata[:uid] %>.

6 |

Please contact your system administrator for more information.

7 | -------------------------------------------------------------------------------- /clients/rails/init.rb: -------------------------------------------------------------------------------- 1 | # Include hook code here 2 | require 'wrapper' 3 | puts "requiring wrapper" -------------------------------------------------------------------------------- /clients/rails/install.rb: -------------------------------------------------------------------------------- 1 | # Install hook code here 2 | -------------------------------------------------------------------------------- /clients/rails/lib/arecibolib/LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/clients/rails/lib/arecibolib/LICENSE.txt -------------------------------------------------------------------------------- /clients/rails/lib/arecibolib/README.txt: -------------------------------------------------------------------------------- 1 | This is the standlone Ruby library for posting errors to Arecibo.... more to come if this works -------------------------------------------------------------------------------- /clients/rails/lib/arecibolib/arecibo.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | 4 | class Arecibo 5 | def initialize data 6 | @data = data 7 | # where errors are going 8 | @arecibo_url = URI.parse('http://www.areciboapp.com/v/1/') 9 | # the length of time to wait for a server to respond 10 | @timeout = 10 11 | end 12 | 13 | def send 14 | # make a post 15 | post = Net::HTTP::Post.new(@arecibo_url.path) 16 | post.set_form_data(@data) 17 | # make request 18 | req = Net::HTTP.new(@arecibo_url.host, @arecibo_url.port) 19 | req.read_timeout = @timeout 20 | # push it through 21 | res = req.start {|http| http.request(post) } 22 | case res 23 | when Net::HTTPSuccess 24 | # ok, we don't need to do anything else 25 | else 26 | # let this bubble up to the rest of the script 27 | raise res.error! 28 | end 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /clients/rails/lib/arecibolib/test.rb: -------------------------------------------------------------------------------- 1 | require 'arecibo' 2 | 3 | dict = { 4 | :account => 'yournumber', 5 | :priority => 1, 6 | :url => "http://badapp.org", 7 | :uid => "123124123123", 8 | :ip => "127.0.0.1", 9 | :type => "ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει", 10 | :server => "Test Script" 11 | } 12 | p = Arecibo.new(dict) 13 | p.send -------------------------------------------------------------------------------- /clients/rails/tasks/arecibo_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :arecibo do 3 | # # Task goes here 4 | # end -------------------------------------------------------------------------------- /clients/rails/test/arecibo_test.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | class AreciboTest < Test::Unit::TestCase 4 | # Replace this with your real tests. 5 | def test_this_plugin 6 | flunk 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /clients/rails/uninstall.rb: -------------------------------------------------------------------------------- 1 | # Uninstall hook code here 2 | -------------------------------------------------------------------------------- /clients/ruby/LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD, Copyright Andy McKay and contributors -------------------------------------------------------------------------------- /clients/ruby/README.txt: -------------------------------------------------------------------------------- 1 | This is the standlone Ruby library for posting errors to Arecibo.... more to come if this works -------------------------------------------------------------------------------- /clients/ruby/arecibo.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | 4 | class Arecibo 5 | def initialize(url, data) 6 | @data = data 7 | # where errors are going 8 | @arecibo_url = URI.parse(url) 9 | # the length of time to wait for a server to respond 10 | @timeout = 10 11 | end 12 | 13 | def send 14 | # make a post 15 | post = Net::HTTP::Post.new(@arecibo_url.path) 16 | post.set_form_data(@data) 17 | # make request 18 | req = Net::HTTP.new(@arecibo_url.host, @arecibo_url.port) 19 | req.read_timeout = @timeout 20 | # push it through 21 | res = req.start {|http| http.request(post) } 22 | case res 23 | when Net::HTTPSuccess 24 | # ok, we don't need to do anything else 25 | else 26 | # let this bubble up to the rest of the script 27 | raise res.error! 28 | end 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /clients/ruby/test.rb: -------------------------------------------------------------------------------- 1 | require 'arecibo' 2 | 3 | dict = { 4 | :account => 'youraccount number', 5 | :priority => 1, 6 | :url => "http://badapp.org", 7 | :uid => "sefsef", 8 | :ip => "127.0.0.1", 9 | :type => "sdfs", 10 | :server => "Test Script" 11 | } 12 | p = Arecibo.new("http://yourservername/v/1/", dict) 13 | p.send -------------------------------------------------------------------------------- /listener/LICENSE: -------------------------------------------------------------------------------- 1 | BSD, Copyright Andy McKay and Contributors 2 | (except for existing libraries as noted) -------------------------------------------------------------------------------- /listener/app_engine/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | django 3 | app.yaml 4 | local_settings.py 5 | custom/listeners.py 6 | custom/templates 7 | -------------------------------------------------------------------------------- /listener/app_engine/__init__.py: -------------------------------------------------------------------------------- 1 | import django 2 | version = django.VERSION 3 | 4 | if version[0] < 1 or version[1] < 2: 5 | raise ValueError("You need at least Django 1.2 to run Arecibo, "\ 6 | "please see http://www.areciboapp.com/docs/server/installation.html.") -------------------------------------------------------------------------------- /listener/app_engine/app.yaml.example: -------------------------------------------------------------------------------- 1 | application: your_application_error 2 | version: 1 3 | runtime: python 4 | api_version: 1 5 | 6 | handlers: 7 | - url: /media 8 | static_dir: media 9 | 10 | # lock down these urls 11 | # this is the task queue for errors 12 | - url: /send/created/ 13 | script: main.py 14 | login: admin 15 | 16 | # the cron job for sending notifications 17 | # see cron.yaml 18 | - url: /notification/send/ 19 | script: main.py 20 | login: admin 21 | 22 | # the cron job for cleaning out data from notifications 23 | # see cron.yaml 24 | - url: /notification/cleanup/ 25 | script: main.py 26 | login: admin 27 | 28 | - url: /stats/generate/.* 29 | script: main.py 30 | login: admin 31 | 32 | - url: /remote_api 33 | script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py 34 | login: admin 35 | 36 | - url: /.* 37 | script: main.py 38 | 39 | inbound_services: 40 | - mail -------------------------------------------------------------------------------- /listener/app_engine/app/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /listener/app_engine/app/base.py: -------------------------------------------------------------------------------- 1 | from appengine_django.models import BaseModel 2 | from google.appengine.ext import db 3 | 4 | class Base(BaseModel): 5 | @property 6 | def id(self): 7 | try: 8 | return str(self.key()) 9 | except db.NotSavedError: 10 | pass 11 | 12 | pk = id 13 | -------------------------------------------------------------------------------- /listener/app_engine/app/context.py: -------------------------------------------------------------------------------- 1 | from urllib import urlencode 2 | from django.conf import settings 3 | 4 | def context(request): 5 | data = {} 6 | data["user"] = request.user 7 | data["public_key"] = settings.ARECIBO_PUBLIC_ACCOUNT_NUMBER 8 | data["private_key"] = settings.ARECIBO_PRIVATE_ACCOUNT_NUMBER 9 | data["site_url"] = settings.SITE_URL 10 | 11 | qs = request.GET.copy() 12 | if "page" in qs: 13 | del qs["page"] 14 | 15 | data["qs"] = "" 16 | if qs: 17 | data["qs"] = "%s" % urlencode(qs) 18 | 19 | return data 20 | -------------------------------------------------------------------------------- /listener/app_engine/app/errors.py: -------------------------------------------------------------------------------- 1 | from exceptions import Exception 2 | 3 | class StatusDoesNotExist(Exception): pass 4 | 5 | from django.template import RequestContext, loader 6 | from django.http import HttpResponse 7 | 8 | def not_found_error(request): 9 | t = loader.get_template('404.html') 10 | c = RequestContext(request) 11 | return HttpResponse(t.render(c), status=404) 12 | 13 | def application_error(request): 14 | t = loader.get_template('500.html') 15 | c = RequestContext(request) 16 | return HttpResponse(t.render(c), status=500) -------------------------------------------------------------------------------- /listener/app_engine/app/fields.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.core.validators import EMPTY_VALUES 3 | 4 | class OurModelChoiceIterator(forms.models.ModelChoiceIterator): 5 | def __iter__(self): 6 | if self.field.empty_label is not None: 7 | yield (u"", self.field.empty_label) 8 | if self.field.cache_choices: 9 | if self.field.choice_cache is None: 10 | self.field.choice_cache = [ 11 | self.choice(obj) for obj in self.queryset 12 | ] 13 | for choice in self.field.choice_cache: 14 | yield choice 15 | else: 16 | for obj in self.queryset: 17 | yield self.choice(obj) 18 | 19 | class OurModelChoiceField(forms.ModelChoiceField): 20 | """ This required a few modifications to get working on app engine it seems """ 21 | 22 | def __init__(self, *args, **kwargs): 23 | self.model = kwargs.pop("model") 24 | super(OurModelChoiceField, self).__init__(*args, **kwargs) 25 | 26 | def to_python(self, value): 27 | if value in EMPTY_VALUES: 28 | return None 29 | value = self.model.get(value) 30 | return value 31 | 32 | def _get_choices(self): 33 | if hasattr(self, '_choices'): 34 | return self._choices 35 | return OurModelChoiceIterator(self) 36 | 37 | choices = property(_get_choices, forms.ModelChoiceField._set_choices) -------------------------------------------------------------------------------- /listener/app_engine/app/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.html import conditional_escape 3 | from django.utils.encoding import force_unicode 4 | from django.utils.safestring import mark_safe 5 | 6 | def as_blue_print(self): 7 | return self._html_output(u""" 8 |
9 |
10 | %(label)s
11 | %(errors)s 12 | %(help_text)s 13 | %(field)s 14 |
15 |
16 | """, u'%s', '', u'%s', False) 17 | 18 | class Form(forms.Form): 19 | required_css_class = 'required' 20 | 21 | def as_custom(self): 22 | return as_blue_print(self) 23 | 24 | class ModelForm(forms.ModelForm): 25 | required_css_class = 'required' 26 | 27 | def as_custom(self): 28 | return as_blue_print(self) 29 | 30 | def as_div(self): 31 | if not self: 32 | return u'' 33 | template = "%s" 34 | errors = ''.join([u'

%s

' % conditional_escape(force_unicode(e)) for e in self]) 35 | template = template % errors 36 | return mark_safe(template) 37 | 38 | forms.util.ErrorList.__unicode__ = as_div 39 | -------------------------------------------------------------------------------- /listener/app_engine/app/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/app/management/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/app/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/app/management/commands/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/app/management/commands/remote.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | 3 | import code 4 | import getpass 5 | import sys 6 | import os 7 | 8 | from google.appengine.ext.remote_api import remote_api_stub 9 | from google.appengine.ext import db 10 | 11 | def auth_func(): 12 | return raw_input('Username:'), getpass.getpass('Password:') 13 | 14 | class Command(BaseCommand): 15 | help = 'Command shell for the remote App Engine instance' 16 | 17 | def handle(self, *args, **options): 18 | app_id = os.environ.get("APPLICATION_ID") 19 | host = "%s.appspot.com" % app_id 20 | remote_api_stub.ConfigureRemoteDatastore(app_id, '/remote_api', auth_func, host) 21 | code.interact('App Engine interactive console for %s' % (app_id,), None, locals()) -------------------------------------------------------------------------------- /listener/app_engine/app/paginator.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import Paginator as BasePaginator 2 | from django.core.paginator import Page, InvalidPage, EmptyPage 3 | 4 | class GAEPaginator(BasePaginator): 5 | def page(self, number): 6 | "Returns a Page object for the given 1-based page number." 7 | bottom = (number - 1) * self.per_page 8 | top = bottom + self.per_page 9 | queryset = self.object_list.fetch((number * self.per_page)+1) 10 | results = queryset[bottom:top] 11 | try: 12 | queryset[top] 13 | self._num_pages = number + 1 14 | except IndexError: 15 | self._num_pages = number 16 | 17 | return Page(results, number, self) 18 | 19 | Paginator = GAEPaginator 20 | 21 | def get_page(request, paginator): 22 | try: 23 | page = int(request.GET.get('page', '1')) 24 | except ValueError: 25 | page = 1 26 | 27 | try: 28 | page = paginator.page(page) 29 | except (EmptyPage, InvalidPage): 30 | page = paginator.page(paginator.num_pages) 31 | 32 | return page -------------------------------------------------------------------------------- /listener/app_engine/app/tags.py: -------------------------------------------------------------------------------- 1 | from google.appengine.ext import webapp 2 | register = webapp.template.create_template_register() 3 | 4 | from app.utils import trunc_string 5 | 6 | from django.contrib.markup.templatetags.markup import markdown as real_markdown 7 | 8 | @register.filter 9 | def trunc(value, arg): 10 | "Removes all values of arg from the given string" 11 | return trunc_string(value, arg) 12 | 13 | @register.filter 14 | def markdown(value, arg=""): 15 | return real_markdown(value, arg) 16 | -------------------------------------------------------------------------------- /listener/app_engine/app/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Not allowed

6 |

You can't do that. Sorry.

7 | {% if request.is_anonymous %}

You are not logged in, you could try logging in.

{% endif %} 8 |
9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /listener/app_engine/app/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Page not found

6 |

Oops.

7 |
8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /listener/app_engine/app/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Application error.

6 |

Oops.

7 |
8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /listener/app_engine/app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {{ get_user }} 4 | {% if user.is_anonymous %} 5 |
6 |

Existing user?

7 |

Login here with your Google account. If you are having problems, reset it here.

8 |
9 |
10 |

New user?

11 |

Login with your google account, the administrator of this site will have to approve your access to the site.

12 |
13 | {% else %} 14 | {% if not user.is_staff %} 15 |

You have logged in, but the admin has not granted you access yet. Please be patient.

16 | {% endif %} 17 | {% endif %} 18 | 19 |
Welcome to this Arecibo installation. Arecibo is an observatory, instead of listening to the stars, this site listens for errors from your sites. We pronounce it "Ah-reh-see-bow". If you have any questions about the site, please contact it's administrator.
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /listener/app_engine/app/templates/setup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
4 |

URLs and keysUsers

5 |
6 | {% endblock %} 7 | {% block content %} 8 |
9 |
Public key
10 |
{{ public_key }} 11 |

Use this in your error reporting

12 |
13 |
URL for posting
14 |
{{ site_url }}{% url error-post %} 15 |

Use this in your libraries for posting

16 |
17 |
E-mail for posting
18 |
django-{{ public_key }}@{{ app_id }}.appspotmail.com 19 |

Where to send email (more details)

20 |
Private key
21 |
{{ private_key }} 22 |

This is for reading API and RSS

23 |
24 |
RSS Feeds
25 |
Errors: {{ site_url }}/feed/{{ private_key }}/ 26 |

You can also filter the RSS feed by any of the filters on the list page.

27 |
Groups: {{ site_url }}/group/feed/{{ private_key }}/ 28 |

You can also filter the RSS feed by any of the filters on the group page.

29 | 30 |
31 | 32 |

For logs and admin, use Google App Engine console.

33 | 34 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/app/test_runner.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.test.simple import DjangoTestSuiteRunner 5 | 6 | from django.core import mail 7 | from django.core.mail.backends import locmem 8 | 9 | from google.appengine.api import mail 10 | from django.core.mail.message import EmailMessage 11 | 12 | os.environ['SERVER_SOFTWARE'] = "Development" 13 | settings.DATABASES['default']['SUPPORTS_TRANSACTIONS'] = True 14 | 15 | # amongst other things this will suppress those annoying logs 16 | settings.DEBUG = False 17 | 18 | def send_email_dummy(sender=None, to=None, subject=None, body=None): 19 | # app engine doesn't use the backend, so that if you try to write 20 | # unit tests that check the mail api, they fail, this patches it 21 | # back in, for the purpose of unit_tests 22 | return EmailMessage(subject, body, sender, [to,], connection=None).send() 23 | 24 | class AreciboRunner(DjangoTestSuiteRunner): 25 | def setup_test_environment(self, **kwargs): 26 | super(AreciboRunner, self).setup_test_environment(**kwargs) 27 | mail.send_mail = send_email_dummy -------------------------------------------------------------------------------- /listener/app_engine/app/tests.py: -------------------------------------------------------------------------------- 1 | # test data 2 | from django.conf import settings 3 | 4 | try: 5 | account = settings.ARECIBO_PUBLIC_ACCOUNT_NUMBER 6 | except ImportError: 7 | account = "1231241243" 8 | 9 | test_data = { 10 | "account": account, 11 | "priority": 4, 12 | "user_agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X...", 13 | "url": "http://badapp.org/-\ufffdwe-cant-lose", 14 | "uid": "123124123123", 15 | "ip": "127.0.0.1", 16 | "type": "Test from python", 17 | "status": "403", 18 | "server": "Test Script", 19 | "request": """This is the bit that goes in the request""", 20 | "username": "Jimbob", 21 | "msg": """ 22 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut 23 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 24 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit 25 | esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 26 | culpa qui officia deserunt mollit anim id est laborum 27 | """, 28 | "traceback": """Traceback (most recent call last",: 29 | File "", line 1, in 30 | ZeroDivisionError: integer division or modulo by zero df 31 | """,} -------------------------------------------------------------------------------- /listener/app_engine/app/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^$', 'app.views.index', name="index"), 5 | url(r'^lib/error.js', 'app.views.javascript_client', name="error-javascript"), 6 | url(r'^lib/error-compress.js', 'app.views.javascript_client', name="error-javascript-compressed"), 7 | url(r'^accounts/login/$', 'app.views.login', name="login"), 8 | url(r'^accounts/logout/$', 'app.views.logout', name="logout"), 9 | url(r'^accounts/not-allowed/$', 'app.views.not_allowed', name="not-allowed"), 10 | url(r'^setup$', 'app.views.setup', name="setup") 11 | ) 12 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/auth/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Authentication module that mimics the behavior of Django's authentication 17 | implementation. 18 | 19 | Limitations: 20 | - all user permissions methods are not available (requires contenttypes) 21 | """ 22 | 23 | from django.template import add_to_builtins 24 | 25 | add_to_builtins('appengine_django.auth.templatetags') 26 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/auth/decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Decorators for the authentication framework.""" 16 | 17 | from django.http import HttpResponseRedirect 18 | 19 | from google.appengine.api import users 20 | 21 | 22 | def login_required(function): 23 | """Implementation of Django's login_required decorator. 24 | 25 | The login redirect URL is always set to request.path 26 | """ 27 | def login_required_wrapper(request, *args, **kw): 28 | if request.user.is_authenticated(): 29 | return function(request, *args, **kw) 30 | return HttpResponseRedirect(users.create_login_url(request.path)) 31 | return login_required_wrapper 32 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/auth/middleware.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django.contrib.auth.models import AnonymousUser 16 | 17 | from google.appengine.api import users 18 | 19 | from appengine_django.auth.models import User 20 | 21 | 22 | class LazyUser(object): 23 | def __get__(self, request, obj_type=None): 24 | if not hasattr(request, '_cached_user'): 25 | user = users.get_current_user() 26 | if user: 27 | request._cached_user = User.get_djangouser_for_user(user) 28 | else: 29 | request._cached_user = AnonymousUser() 30 | return request._cached_user 31 | 32 | 33 | class AuthenticationMiddleware(object): 34 | def process_request(self, request): 35 | request.__class__.user = LazyUser() 36 | return None 37 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/auth/signals.py: -------------------------------------------------------------------------------- 1 | # add in a user created signal 2 | import django.dispatch 3 | 4 | user_created = django.dispatch.Signal(providing_args=["instance",]) -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/conf/app_template/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/appengine_django/conf/app_template/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/conf/app_template/models.py: -------------------------------------------------------------------------------- 1 | from appengine_django.models import BaseModel 2 | from google.appengine.ext import db 3 | 4 | # Create your models here. 5 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/conf/app_template/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/db/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Explicitly set the name of this package to "appengine". 16 | # 17 | # The rationale for this is so that Django can refer to the database as 18 | # "appengine" even though at a filesystem level it appears as the "db" package 19 | # within the appengine_django package. 20 | __name__ = "appengine" 21 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/db/creation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright 2008 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import logging 19 | 20 | from django.conf import settings 21 | from django.db.backends.creation import BaseDatabaseCreation 22 | 23 | 24 | class DatabaseCreation(BaseDatabaseCreation): 25 | 26 | def create_test_db(self, *args, **kw): 27 | """Destroys the test datastore. A new store will be recreated on demand""" 28 | settings.DATABASE_SUPPORTS_TRANSACTIONS = False 29 | self.destroy_test_db() 30 | self.connection.use_test_datastore = True 31 | self.connection.flush() 32 | 33 | 34 | def destroy_test_db(self, *args, **kw): 35 | """Destroys the test datastore files.""" 36 | from appengine_django.db.base import destroy_datastore 37 | from appengine_django.db.base import get_test_datastore_paths 38 | destroy_datastore(*get_test_datastore_paths()) 39 | logging.debug("Destroyed test datastore") 40 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/appengine_django/management/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/appengine_django/management/commands/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/management/commands/flush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright 2008 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import logging 18 | import os 19 | import sys 20 | 21 | from django.core.management.base import BaseCommand 22 | 23 | 24 | class Command(BaseCommand): 25 | """Overrides the default Django flush command. 26 | """ 27 | help = 'Clears the current datastore and loads the initial fixture data.' 28 | 29 | def run_from_argv(self, argv): 30 | from django.db import connection 31 | connection.flush() 32 | from django.core.management import call_command 33 | call_command('loaddata', 'initial_data') 34 | 35 | def handle(self, *args, **kwargs): 36 | self.run_from_argv(None) 37 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/management/commands/reset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright 2008 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import logging 19 | import os 20 | import sys 21 | 22 | from django.core.management.base import BaseCommand 23 | 24 | 25 | class Command(BaseCommand): 26 | """Overrides the default Django reset command. 27 | """ 28 | help = 'Clears the current datastore.' 29 | 30 | def run_from_argv(self, argv): 31 | from django.db import connection 32 | connection.flush() 33 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/management/commands/startapp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import os 17 | 18 | import django 19 | from django.core.management.commands import startapp 20 | 21 | import appengine_django 22 | 23 | 24 | class Command(startapp.Command): 25 | def handle_label(self, *args, **kwds): 26 | """Temporary adjust django.__path__ to load app templates from the 27 | helpers directory. 28 | """ 29 | old_path = django.__path__ 30 | django.__path__ = appengine_django.__path__ 31 | startapp.Command.handle_label(self, *args, **kwds) 32 | django.__path__ = old_path 33 | 34 | 35 | class ProjectCommand(Command): 36 | def __init__(self, project_directory): 37 | super(ProjectCommand, self).__init__() 38 | self.project_directory = project_directory 39 | 40 | def handle_label(self, app_name, **options): 41 | super(ProjectCommand, self).handle_label(app_name, self.project_directory, 42 | **options) 43 | 44 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/replacement_imp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright 2008 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """This file acts as a very minimal replacement for the 'imp' module. 18 | 19 | It contains only what Django expects to use and does not actually implement the 20 | same functionality as the real 'imp' module. 21 | """ 22 | 23 | 24 | def find_module(name, path=None): 25 | """Django needs imp.find_module, but it works fine if nothing is found.""" 26 | raise ImportError 27 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/serializer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/appengine_django/serializer/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/sessions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/appengine_django/sessions/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/sessions/backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/appengine_django/sessions/backends/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/sessions/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright 2008 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from google.appengine.ext import db 18 | 19 | class Session(db.Model): 20 | """Django compatible App Engine Datastore session model.""" 21 | session_data = db.BlobProperty() 22 | expire_date = db.DateTimeProperty() 23 | -------------------------------------------------------------------------------- /listener/app_engine/appengine_django/tests/memcache_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright 2008 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Ensures the App Engine memcache API works as Django's memcache backend.""" 18 | 19 | import unittest 20 | 21 | from django.core.cache import get_cache 22 | from appengine_django import appid 23 | from appengine_django import have_appserver 24 | 25 | 26 | class AppengineMemcacheTest(unittest.TestCase): 27 | """Tests that the memcache backend works.""" 28 | 29 | def setUp(self): 30 | """Get the memcache cache module so it is available to tests.""" 31 | self._cache = get_cache("memcached://") 32 | 33 | def testSimpleSetGet(self): 34 | """Tests that a simple set/get operation through the cache works.""" 35 | self._cache.set("test_key", "test_value") 36 | self.assertEqual(self._cache.get("test_key"), "test_value") 37 | 38 | def testDelete(self): 39 | """Tests that delete removes values from the cache.""" 40 | self._cache.set("test_key", "test_value") 41 | self.assertEqual(self._cache.has_key("test_key"), True) 42 | self._cache.delete("test_key") 43 | self.assertEqual(self._cache.has_key("test_key"), False) 44 | -------------------------------------------------------------------------------- /listener/app_engine/cron.yaml: -------------------------------------------------------------------------------- 1 | cron: 2 | - description: notification process 3 | url: /notification/send/ 4 | schedule: every 5 minutes 5 | 6 | - description: notification process 7 | url: /notification/cleanup/ 8 | schedule: every 24 hours 9 | 10 | - description: daily summary 11 | url: /stats/generate/ 12 | schedule: every 24 hours -------------------------------------------------------------------------------- /listener/app_engine/custom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/custom/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/custom/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/custom/examples/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/custom/examples/default_public.py: -------------------------------------------------------------------------------- 1 | from error.models import Error 2 | 3 | from error import signals 4 | 5 | def default_public(instance, **kw): 6 | instance.public = True 7 | instance.save() 8 | 9 | signals.error_created.connect(default_public, dispatch_uid="default_public") 10 | -------------------------------------------------------------------------------- /listener/app_engine/custom/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/custom/models.py -------------------------------------------------------------------------------- /listener/app_engine/custom/readme.txt: -------------------------------------------------------------------------------- 1 | # this is the custom folder, add or remove any signals to alter how Arecibo works here -------------------------------------------------------------------------------- /listener/app_engine/custom/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.test.client import Client 3 | from django.core.urlresolvers import reverse 4 | 5 | from app.tests import test_data as data 6 | from error.models import Error, Group 7 | from error import signals 8 | 9 | from custom.examples import default_public 10 | 11 | class ErrorTests(TestCase): 12 | # test the view for writing errors 13 | def setUp(self): 14 | for error in Error.all(): error.delete() 15 | 16 | def testNotDefaultAsPublic(self): 17 | signals.error_created.disconnect(default_public.default_public, dispatch_uid="default_public") 18 | 19 | c = Client() 20 | assert not Error.objects.all().count() 21 | c.post(reverse("error-post"), data) 22 | assert Error.objects.all().count() == 1 23 | assert Error.objects.all()[0].public == False 24 | 25 | def testDefaultAsPublic(self): 26 | signals.error_created.connect(default_public.default_public, dispatch_uid="default_public") 27 | 28 | c = Client() 29 | assert not Error.objects.all().count() 30 | c.post(reverse("error-post"), data) 31 | assert Error.objects.all().count() == 1 32 | assert Error.objects.all()[0].public == True 33 | 34 | signals.error_created.disconnect(default_public.default_public, dispatch_uid="default_public") -------------------------------------------------------------------------------- /listener/app_engine/error/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/error/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/error/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | error_created = django.dispatch.Signal(providing_args=["instance",]) 4 | group_created = django.dispatch.Signal(providing_args=["instance",]) 5 | group_assigned = django.dispatch.Signal(providing_args=["instance",]) -------------------------------------------------------------------------------- /listener/app_engine/error/templates/filter.html: -------------------------------------------------------------------------------- 1 |
2 | {{ form.as_custom }} 3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /listener/app_engine/error/templates/group.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
5 | {% if page.object_list %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for group in page.object_list %} 19 | 20 | 21 | {% with group.sample as sample %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endwith %} 29 | 30 | {% endfor %} 31 | 32 |
Most recentStatusTypeCountPathProjectStage
{{ group.timestamp|date:"d M, Y P" }}{{ sample.status }}{{ sample.type|trunc:20 }}{{ group.count }}{{ sample.query|trunc:20 }}{{ group.project_url.project.name }}{{ group.project_url.get_stage_display }}
33 | {% else %} 34 |

No errors found. Could be you've not sent any errors yet. Perhaps you've got some filters 35 | set in the page, in which case reset them.

36 | {% endif %} 37 |
38 |
39 | {% include "filter.html" %} 40 |
41 | {% include "pagination.html" %} 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /listener/app_engine/error/templates/list-snippet.html: -------------------------------------------------------------------------------- 1 | {% for error in object_list %} 2 | 3 | {% if error.priority|stringformat:"s" in "123" %}{% endif %} 4 | {% if error.read %}{% else %}{% endif %} 5 | {{ error.status }} 6 | {{ error.title|trunc:20 }} 7 | {{ error.timestamp|timesince }} 8 | {{ error.domain }} 9 | {{ error.query|trunc:20 }} 10 | 11 | {% endfor %} 12 | -------------------------------------------------------------------------------- /listener/app_engine/error/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "subnav.html" %}{% endblock %} 3 | {% block content %} 4 | {% with page.object_list as object_list %} 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% include "list-snippet.html" %} 19 | 20 | 21 |
StatusDetailsAgoDomainPath
22 | 23 |

No errors found. Could be you've not sent any errors yet. Perhaps you've got some filters 24 | set in the page, in which case reset them.

25 |
26 | {% endwith %} 27 |
28 | {% include "filter.html" %} 29 |
30 | {% include "pagination.html" %} 31 | 32 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/error/templates/pagination.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /listener/app_engine/error/templates/subnav.html: -------------------------------------------------------------------------------- 1 |
2 | {% if user.is_staff %}List 3 | • Group 4 | • RSS feed{% endif %} 5 |
6 |
7 | {% if refresh %} 8 | Automatic update pending. 9 | {% endif %} 10 |
11 | -------------------------------------------------------------------------------- /listener/app_engine/error/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # if you put the key in here it will get exposed in errors 4 | # so probably 5 | urlpatterns = patterns('', 6 | url(r'^feed/.*?/json/$', 'error.feeds.json', name="json"), 7 | url(r'^feed/.*?/$', 'error.feeds.atom', name="rss"), 8 | url(r'^group/feed/.*?/json/$', 'error.feeds.group_json', name="json"), 9 | url(r'^group/feed/.*?/$', 'error.feeds.group_atom', name="rss"), 10 | url(r'^list/$', 'error.views.errors_list', name="error-list"), 11 | url(r'^list/snippet/$', 'error.views.errors_snippet', name="error-snippet"), 12 | url(r'^groups/$', 'error.views.groups_list', name="group-list"), 13 | url(r'^view/(?P[\w-]+)/$', 'error.views.error_view', name="error-view"), 14 | url(r'^view/toggle/(?P[\w-]+)/$','error.views.error_public_toggle', name="error-toggle"), 15 | url(r'^send/created/(?P[\w-]+)/$', 'error.views.send_signal', name="error-created"), 16 | ) 17 | -------------------------------------------------------------------------------- /listener/app_engine/error/validations.py: -------------------------------------------------------------------------------- 1 | from app.errors import StatusDoesNotExist 2 | 3 | codes = ['100', '101', '102', '200', '201', '202', '203', '204', '205', '206', 4 | '207', '226', '300', '301', '302', '303', '304', '305', '307', '400', '401', 5 | '402', '403', '404', '405', '406', '407', '408', '409', '410', '411', '412', 6 | '413', '414', '415', '416', '417', '422', '423', '424', '426', '500', '501', 7 | '502', '503', '504', '505', '507', '510'] 8 | 9 | def valid_status(code): 10 | if isinstance(code, str): 11 | code = str(code) 12 | if code not in codes: 13 | raise StatusDoesNotExist, 'The status "%s" does not exist.' % code 14 | -------------------------------------------------------------------------------- /listener/app_engine/issues/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/issues/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/issues/listeners.py: -------------------------------------------------------------------------------- 1 | import md5 2 | 3 | from django.utils.translation import ugettext as _ 4 | 5 | from app.utils import safe_string, log 6 | from issues import signals 7 | from issues.models import IssueProjectURL 8 | 9 | def default_add_issue(instance, **kw): 10 | log("Firing signal: default_add_issue") 11 | instance.add_log(_("Issue created.")) 12 | 13 | signals.issue_created.connect(default_add_issue, dispatch_uid="default_add_issue") 14 | 15 | def default_add_comment(instance, **kw): 16 | log("Firing signal: default_add_comment") 17 | instance.issue.add_log(_("Comment created.")) 18 | 19 | signals.comment_created.connect(default_add_comment, dispatch_uid="default_add_comment") 20 | 21 | def default_add_project_urls(instance, **kw): 22 | log("Firing signal: default_add_project_urls") 23 | if instance.project: 24 | for project_url in instance.project.projecturl_set: 25 | issue_project_url = IssueProjectURL( 26 | issue=instance, 27 | project_url=project_url, 28 | status="not_fixed") 29 | issue_project_url.save() 30 | 31 | signals.issue_created.connect(default_add_project_urls, dispatch_uid="default_add_project_urls") 32 | -------------------------------------------------------------------------------- /listener/app_engine/issues/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | issue_created = django.dispatch.Signal(providing_args=["instance",]) 4 | comment_created = django.dispatch.Signal(providing_args=["instance",]) 5 | 6 | issue_status_changed = django.dispatch.Signal(providing_args=["instance", "old", "new"]) 7 | issue_assigned_changed = django.dispatch.Signal(providing_args=["instance", "old", "new"]) 8 | issue_priority_changed = django.dispatch.Signal(providing_args=["instance", "old", "new"]) 9 | 10 | issue_changed = django.dispatch.Signal(providing_args=["instance", "old"]) 11 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/comment_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "issue_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
5 |

{{ issue.number }} • {{ issue.title }}

6 |
7 |
8 |

Add a comment, change status or assignee.

9 |
10 | {{ form.as_custom }} 11 |
12 |
13 |
14 |
15 |

16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "issue_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
5 |

Add in issue

7 | {{ issue_form.as_custom }} 8 | {{ group_form.as_custom }} 9 |
10 | 11 |
12 | 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "issue_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
5 |

{{ issue.number }} • {{ issue.title }}

6 |

Return to issue 7 |

8 |
9 | {{ form.as_custom }} 10 |
11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_filter.html: -------------------------------------------------------------------------------- 1 |
2 | {{ form.as_custom }} 3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "issue_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for issue in page.object_list %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
NumberTitleStatusAssignedAgo
{% if issue.priority|stringformat:"s" in "123" %}{% endif %} {{ issue.number }}{{ issue.title }}{{ issue.status|capfirst }}{{ issue.assigned }}{{ issue.timestamp|timesince }}
27 | 28 | {% if not page.object_list %} 29 |

There are no issues yet.

30 | {% endif %} 31 |
32 |
33 | {% include "issue_filter.html" %} 34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_log_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "issue_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
5 |

{{ issue.number }} • {{ issue.title }}

6 |
7 |
8 |

Full log of changes to this issue.

9 |
    10 | {% for log in page.object_list %} 11 | {% include "issue_log_snippet.html" %} 12 | {% empty %} 13 |
  • No log.
  • 14 | {% endfor %} 15 |
16 |
17 |
18 |

19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_log_snippet.html: -------------------------------------------------------------------------------- 1 |
  • 2 | Added by {{ log.creator }}, {{ log.timestamp|timesince }}.
    3 |

    {{ log.text }}

    4 |
  • 5 | 6 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_project_url.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "issue_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |

    {{ issue.number }} • {{ issue.title }}

    6 |
    7 | {% for url in urls %} 8 |
    9 |
    10 | 16 |
    17 | {% endfor %} 18 |
    19 |
    20 |
    21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /listener/app_engine/issues/templates/issue_subnav.html: -------------------------------------------------------------------------------- 1 |
    2 | Add an issue 3 | • 4 | List 5 |
    6 |
    7 | Issues are in alpha, expect changes. 8 |
    9 | -------------------------------------------------------------------------------- /listener/app_engine/issues/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^$', 'issues.views.issue_list', name="issues-list"), 5 | url(r'^add/$', 'issues.views.issue_add', name="issues-add"), 6 | 7 | url(r'^edit/project-url/(?P[\w-]+)/$', 'issues.views.edit_project_url', name="issues-project-url"), 8 | url(r'^edit/(?P[\w-]+)/$', 'issues.views.issue_edit', name="issues-edit"), 9 | 10 | url(r'^view/(?P[\w-]+)/$', 'issues.views.issue_view', name="issues-view"), 11 | url(r'^view/logs/(?P[\w-]+)/$', 'issues.views.issue_log_view', name="issues-log-view"), 12 | 13 | url(r'^add/comment/(?P[\w-]+)/$', 'issues.views.comment_add', name="issues-add-comment"), 14 | ) 15 | -------------------------------------------------------------------------------- /listener/app_engine/local_settings.py.example: -------------------------------------------------------------------------------- 1 | ARECIBO_PUBLIC_ACCOUNT_NUMBER = "your_public_account_number_here" 2 | ARECIBO_PRIVATE_ACCOUNT_NUMBER = "your_private_account_number_here" 3 | 4 | DEFAULT_FROM_EMAIL = "you.account@gmail.com.that.is.authorized.for.app_engine" 5 | SITE_URL = "http://theurl.to.your.arecibo.instance.com" -------------------------------------------------------------------------------- /listener/app_engine/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2008 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | from appengine_django import InstallAppengineHelperForDjango 18 | InstallAppengineHelperForDjango() 19 | 20 | from django.core.management import execute_manager 21 | try: 22 | import settings # Assumed to be in the same directory. 23 | except ImportError: 24 | import sys 25 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 26 | sys.exit(1) 27 | 28 | 29 | if __name__ == "__main__": 30 | execute_manager(settings) 31 | -------------------------------------------------------------------------------- /listener/app_engine/markdown: -------------------------------------------------------------------------------- 1 | ../lib/markdown -------------------------------------------------------------------------------- /listener/app_engine/media/css/ie.css: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------- 2 | 3 | 4 | Blueprint CSS Framework 0.8 5 | http://blueprintcss.org 6 | 7 | * Copyright (c) 2007-Present. See LICENSE for more info. 8 | * See README for instructions on how to use Blueprint. 9 | * For credits and origins, see AUTHORS. 10 | * This is a compressed file. See the sources in the 'src' directory. 11 | 12 | ----------------------------------------------------------------------- */ 13 | 14 | /* ie.css */ 15 | body {text-align:center;} 16 | .container {text-align:left;} 17 | * html .column, * html div.span-1, * html div.span-2, * html div.span-3, * html div.span-4, * html div.span-5, * html div.span-6, * html div.span-7, * html div.span-8, * html div.span-9, * html div.span-10, * html div.span-11, * html div.span-12, * html div.span-13, * html div.span-14, * html div.span-15, * html div.span-16, * html div.span-17, * html div.span-18, * html div.span-19, * html div.span-20, * html div.span-21, * html div.span-22, * html div.span-23, * html div.span-24 {overflow-x:hidden;} 18 | * html legend {margin:0px -8px 16px 0;padding:0;} 19 | ol {margin-left:2em;} 20 | sup {vertical-align:text-top;} 21 | sub {vertical-align:text-bottom;} 22 | html>body p code {*white-space:normal;} 23 | hr {margin:-8px auto 11px;} 24 | .clearfix, .container {display:inline-block;} 25 | * html .clearfix, * html .container {height:1%;} 26 | fieldset {padding-top:0;} -------------------------------------------------------------------------------- /listener/app_engine/media/css/print.css: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------- 2 | 3 | 4 | Blueprint CSS Framework 0.8 5 | http://blueprintcss.org 6 | 7 | * Copyright (c) 2007-Present. See LICENSE for more info. 8 | * See README for instructions on how to use Blueprint. 9 | * For credits and origins, see AUTHORS. 10 | * This is a compressed file. See the sources in the 'src' directory. 11 | 12 | ----------------------------------------------------------------------- */ 13 | 14 | /* print.css */ 15 | body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;} 16 | .container {background:none;} 17 | hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;} 18 | hr.space {background:#fff;color:#fff;} 19 | h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;} 20 | code {font:.9em "Courier New", Monaco, Courier, monospace;} 21 | img {float:left;margin:1.5em 1.5em 1.5em 0;} 22 | a img {border:none;} 23 | p img.top {margin-top:0;} 24 | blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;} 25 | .small {font-size:.9em;} 26 | .large {font-size:1.1em;} 27 | .quiet {color:#999;} 28 | .hide {display:none;} 29 | a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;} 30 | a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;} -------------------------------------------------------------------------------- /listener/app_engine/media/img/alert-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/alert-overlay.png -------------------------------------------------------------------------------- /listener/app_engine/media/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/apple-touch-icon.png -------------------------------------------------------------------------------- /listener/app_engine/media/img/arecibo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/arecibo.png -------------------------------------------------------------------------------- /listener/app_engine/media/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/arrow.png -------------------------------------------------------------------------------- /listener/app_engine/media/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/favicon.ico -------------------------------------------------------------------------------- /listener/app_engine/media/img/logo-opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/logo-opacity.png -------------------------------------------------------------------------------- /listener/app_engine/media/img/priority-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/priority-1.jpg -------------------------------------------------------------------------------- /listener/app_engine/media/img/priority-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/priority-2.jpg -------------------------------------------------------------------------------- /listener/app_engine/media/img/priority-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/priority-3.jpg -------------------------------------------------------------------------------- /listener/app_engine/media/img/read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/read.png -------------------------------------------------------------------------------- /listener/app_engine/media/img/status-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/status-fixed.jpg -------------------------------------------------------------------------------- /listener/app_engine/media/img/status-not-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/media/img/status-not-fixed.jpg -------------------------------------------------------------------------------- /listener/app_engine/notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/notifications/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/notifications/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from google.appengine.api import memcache 4 | from appengine_django.models import BaseModel 5 | from appengine_django.auth.models import User 6 | 7 | from google.appengine.ext import db 8 | 9 | from notifications.signals import notification_created 10 | from registry import get 11 | 12 | class Notification(BaseModel): 13 | user = db.ListProperty(str) 14 | 15 | tried = db.BooleanProperty(default=False) 16 | completed = db.BooleanProperty(default=False) 17 | error_msg = db.TextProperty() 18 | timestamp = db.DateTimeProperty() 19 | 20 | type = db.StringProperty() 21 | type_key = db.StringProperty() 22 | 23 | def notifier(self): 24 | """ Returns the object that you'd like to be notified about """ 25 | if self.type and self.type_key: 26 | return get()[self.type].get(self.type_key) 27 | 28 | def save(self): 29 | created = not hasattr(self, "id") 30 | if created: 31 | self.timestamp = datetime.now() 32 | self.put() 33 | if created: 34 | notification_created.send(sender=self.__class__, instance=self) 35 | 36 | def user_list(self): 37 | users = [] 38 | for key in self.user: 39 | data = memcache.get(key) 40 | if data: 41 | users.append(data) 42 | else: 43 | user = User.get(key) 44 | users.append(user) 45 | memcache.set(key, user, 60) 46 | return users 47 | 48 | -------------------------------------------------------------------------------- /listener/app_engine/notifications/registry.py: -------------------------------------------------------------------------------- 1 | _registry = {} 2 | 3 | def register(klass, name): 4 | global _registry 5 | _registry[name] = klass 6 | 7 | def get(): 8 | return _registry -------------------------------------------------------------------------------- /listener/app_engine/notifications/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | notification_created = django.dispatch.Signal(providing_args=["instance",]) -------------------------------------------------------------------------------- /listener/app_engine/notifications/templates/notification_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
    4 | {% if page.object_list %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for notification in page.object_list %} 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 |
    AgoAboutUsersTriedCompletedComments
    {{ notification.timestamp|timesince }}{% if notification.notifier %} 20 | {{ notification.notifier.title }} 21 | {% else %} 22 | Unknown 23 | {% endif %} 24 | {% with notification.user_list as list %}{{ list.0.username }}{% if list.count > 1 %}...{% endif %}{% endwith %}{{ notification.tried|yesno }}{{ notification.completed|yesno }}{% if notification.error_msg %}{{ notification.error_msg }}{% endif %}
    33 | {% else %} 34 |

    No notifications found. Could be you've not sent any errors yet and generated 35 | notifications. Or we've cleaned out this list of notifications. Or your 36 | notifications are not enabled. More than likely one of those.

    37 | {% endif %} 38 |
    39 | {% include "pagination.html" %} 40 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/notifications/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^list/$', 'notifications.views.notifications_list', name="notification-list"), 5 | url(r'^send/$', 'notifications.views.notifications_send', name="notification-send"), 6 | url(r'^cleanup/$', 'notifications.views.notifications_cleanup', name="notification-clean"), 7 | ) 8 | -------------------------------------------------------------------------------- /listener/app_engine/profiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/profiles/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/profiles/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from app.base import Base 4 | from google.appengine.ext import db 5 | from appengine_django.auth.models import User 6 | 7 | class Profile(Base): 8 | user = db.ReferenceProperty(User) 9 | notification = db.IntegerProperty() 10 | 11 | def __str__(self): 12 | return str(self.user) 13 | -------------------------------------------------------------------------------- /listener/app_engine/profiles/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from appengine_django.auth.models import User as AppUser 4 | from google.appengine.api.users import User 5 | 6 | from profiles.models import Profile 7 | from profiles.utils import get_profile 8 | 9 | class TestProfile(TestCase): 10 | 11 | def setUp(self): 12 | for user in AppUser.all(): user.delete() 13 | for profile in Profile.all(): profile.delete() 14 | 15 | def test_add_user(self): 16 | user = AppUser(user=User(email="test@foo.com"), 17 | username="test", 18 | email="test@foo.com", 19 | is_staff=True).save() 20 | assert not Profile.all().count() 21 | profile = get_profile(user) 22 | assert profile.notification == 5 23 | assert Profile.all().count() 24 | -------------------------------------------------------------------------------- /listener/app_engine/profiles/utils.py: -------------------------------------------------------------------------------- 1 | from profiles.models import Profile 2 | 3 | def get_profile(user): 4 | try: 5 | return Profile.all().filter("user = ", user)[0] 6 | except IndexError: 7 | profile = Profile(user=user, notification=5) 8 | profile.save() 9 | return profile -------------------------------------------------------------------------------- /listener/app_engine/projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/projects/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/projects/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from app.forms import ModelForm 3 | 4 | from projects.models import Project, ProjectURL, stage_choices 5 | 6 | class ProjectForm(ModelForm): 7 | name = forms.CharField(required=True, label="Name") 8 | description = forms.CharField(required=False, label="Description", widget=forms.Textarea) 9 | 10 | class Meta: 11 | model = Project 12 | 13 | class ProjectURLForm(ModelForm): 14 | url = forms.CharField(required=True, label="Domain") 15 | stage = forms.CharField( 16 | required=True, label="Project stage", 17 | widget=forms.Select(choices=stage_choices) 18 | ) 19 | 20 | class Meta: 21 | model = ProjectURL 22 | fields = ("url", "stage") -------------------------------------------------------------------------------- /listener/app_engine/projects/listeners.py: -------------------------------------------------------------------------------- 1 | from app.utils import log 2 | 3 | from projects.models import Project 4 | from error import signals 5 | 6 | def lookup_domain(domain): 7 | # given a domain, find the project 8 | projects = Project.all() 9 | for project in projects: 10 | for url in project.projecturl_set: 11 | if domain == url.url: 12 | return url 13 | 14 | def default_project(instance, **kw): 15 | log("Firing signal: default_project") 16 | if instance.project_url: 17 | return 18 | 19 | error = instance.sample() 20 | if error: 21 | domain = lookup_domain(error.domain) 22 | if domain: 23 | instance.project_url = domain 24 | instance.save() 25 | 26 | signals.group_assigned.connect(default_project, dispatch_uid="default_browser_parsing") -------------------------------------------------------------------------------- /listener/app_engine/projects/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from app.base import Base 4 | from appengine_django.models import BaseModel 5 | from google.appengine.ext import db 6 | 7 | from django.utils.translation import ugettext as _ 8 | 9 | stage_choices = ( 10 | ["dev", _("Development")], 11 | ["testing", _("Testing")], 12 | ["staging", _("Staging")], 13 | ["backup", _("Backups")], 14 | ["production", _("Production")], 15 | ["other", _("Other")] 16 | ) 17 | 18 | class Project(Base): 19 | name = db.StringProperty(required=True) 20 | description = db.TextProperty(required=False) 21 | 22 | def __str__(self): 23 | return self.name 24 | 25 | class ProjectURL(Base): 26 | project = db.ReferenceProperty(Project) 27 | url = db.StringProperty(required=False) 28 | stage = db.StringProperty(required=False) 29 | 30 | def get_stage_display(self): 31 | return dict(stage_choices).get(self.stage) 32 | 33 | def __str__(self): 34 | return self.url 35 | -------------------------------------------------------------------------------- /listener/app_engine/projects/signals.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/projects/signals.py -------------------------------------------------------------------------------- /listener/app_engine/projects/templates/project_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |
    6 | {{ form.as_custom }} 7 |
    8 |
    9 |
    10 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/projects/templates/project_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |
    6 | {{ form.as_custom }} 7 |
    8 |
    9 |
    10 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/projects/templates/project_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 | {% for project in page %} 6 |

    {{ project.name }}

    7 | {% if project.description %}

    {{ project.description }}{% endif %} 8 |
    9 | Edit • 10 | Add 11 |

    12 | 13 | {% for domain in project.projecturl_set %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% empty %} 21 | 22 | {% endfor %} 23 |
    {{ domain.url }}{{ domain.get_stage_display }}EditList Errors
    No domains defined yet for this project, you should probably add some.
    24 | {% endfor %} 25 |

    Add in a project

    26 |
    27 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/projects/templates/project_url_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |

    {{ project.name }}

    6 |

    Add a domain to this project

    7 |
    8 | {{ form.as_custom }} 9 |
    10 |
    11 |
    12 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/projects/templates/project_url_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |

    {{ project.name }}

    6 |

    Edit a domain for this project

    7 |
    8 | {{ form.as_custom }} 9 |
    10 |
    11 |
    12 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/projects/templates/projects_subnav.html: -------------------------------------------------------------------------------- 1 |
    2 | List 3 |
    -------------------------------------------------------------------------------- /listener/app_engine/projects/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.test.client import Client 3 | 4 | from django.core.urlresolvers import reverse 5 | 6 | from error.models import Error, Group 7 | from projects.models import Project, ProjectURL 8 | 9 | from app.tests import test_data 10 | from app.utils import _pdb 11 | 12 | class ProjectTests(TestCase): 13 | # test the signals for project 14 | def setUp(self): 15 | for error in Error.all(): error.delete() 16 | for group in Group.all(): group.delete() 17 | for project in Project.all(): project.delete() 18 | 19 | def _addError(self): 20 | c = Client() 21 | assert not Error.all().count() 22 | c.post(reverse("error-post"), test_data) 23 | assert test_data["priority"] < 5, test_data["priority"] 24 | assert Error.all().count() == 1 25 | 26 | def testAddProject(self): 27 | project = Project(name="test") 28 | project.save() 29 | 30 | project_url = ProjectURL() 31 | project_url.url = "badapp.org" 32 | project_url.stage = "dev" 33 | project_url.project = project 34 | project_url.save() 35 | 36 | self._addError() 37 | 38 | assert Group.all().count() == 1 39 | assert Group.all()[0].project_url == project_url -------------------------------------------------------------------------------- /listener/app_engine/projects/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^$', 'projects.views.project_list', name="projects-list"), 5 | 6 | url(r'^add/url/(?P[\w-]+)/$', 'projects.views.project_url_add', name="projects-url-add"), 7 | url(r'^edit/url/(?P[\w-]+)/(?P[\w-]+)/$', 'projects.views.project_url_edit', name="projects-url-edit"), 8 | 9 | url(r'^add/$', 'projects.views.project_add', name="projects-add"), 10 | url(r'^edit/(?P[\w-]+)/$', 'projects.views.project_edit', name="projects-edit"), 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /listener/app_engine/projects/utils.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/projects/utils.py -------------------------------------------------------------------------------- /listener/app_engine/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is the app engine port of arecibo. And its essentially frozen until someone else wants to take it up again. 2 | -------------------------------------------------------------------------------- /listener/app_engine/receiving/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/receiving/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/receiving/http.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from error.models import Error 3 | 4 | from app.utils import render_plain 5 | from receiving.post import populate 6 | 7 | def post(request): 8 | """ Add in a post """ 9 | err = Error() 10 | err.ip = request.META.get("REMOTE_ADDR", "") 11 | err.user_agent = request.META.get("HTTP_USER_AGENT", "") 12 | 13 | populate(err, request.POST) 14 | return render_plain("Error recorded") 15 | -------------------------------------------------------------------------------- /listener/app_engine/receiving/mail.py: -------------------------------------------------------------------------------- 1 | from django.utils.simplejson import loads 2 | from django.http import HttpResponse 3 | 4 | from error.models import Error 5 | 6 | from app.utils import log, render_plain 7 | from google.appengine.api import mail 8 | from receiving.post import populate 9 | 10 | def parse(content_type, body): 11 | data = body.decode() 12 | if data.rfind("}") > 0: 13 | # strip out any crap on the end 14 | end = data.rfind("}") 15 | # not sure why i was doing this, i'm sure there 16 | # was a good reason at one point 17 | text = data[:end] + "}" 18 | err = Error() 19 | json = loads(text, strict=False) 20 | populate(err, json) 21 | return True 22 | 23 | def post(request): 24 | """ Add in a post """ 25 | log("Processing email message") 26 | mailobj = mail.InboundEmailMessage(request.raw_post_data) 27 | found = False 28 | 29 | for content_type, body in mailobj.bodies("text/plain"): 30 | found = parse(content_type, body) 31 | for content_type, body in mailobj.bodies("text/html"): 32 | found = parse(content_type, body) 33 | 34 | if not found: 35 | log("No contents found in the message.") 36 | 37 | return render_plain("message parsed") -------------------------------------------------------------------------------- /listener/app_engine/receiving/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/receiving/models.py -------------------------------------------------------------------------------- /listener/app_engine/receiving/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^v/1/$', 'receiving.http.post', name="error-post"), 5 | url(r'^_ah/mail/django-.*', 'receiving.mail_django.post', name="mail-django-post"), 6 | url(r'^_ah/mail/.*', 'receiving.mail.post', name="mail-post"), 7 | ) 8 | -------------------------------------------------------------------------------- /listener/app_engine/stats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/stats/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/stats/models.py: -------------------------------------------------------------------------------- 1 | from django.utils import simplejson 2 | 3 | from datetime import datetime 4 | 5 | from google.appengine.ext import db 6 | 7 | from appengine_django.models import BaseModel 8 | from stats.signals import stats_completed 9 | 10 | class Stats(BaseModel): 11 | """ A series of stats for a date """ 12 | date = db.DateProperty() 13 | timestamp = db.DateTimeProperty() 14 | stats = db.TextProperty() 15 | completed = db.BooleanProperty(default=False) 16 | 17 | @property 18 | def id(self): 19 | return str(self.key()) 20 | 21 | def save(self, *args, **kw): 22 | created = not hasattr(self, "id") 23 | if created: 24 | self.timestamp = datetime.now() 25 | 26 | if not self.completed and self.complete(): 27 | self.completed = True 28 | self.put() 29 | stats_completed.send(sender=self.__class__, instance=self) 30 | else: 31 | self.put() 32 | 33 | def get_stats(self): 34 | if self.stats: 35 | return simplejson.loads(self.stats) 36 | return simplejson.loads("{}") 37 | 38 | def set_stats(self, data): 39 | self.stats = simplejson.dumps(data) 40 | 41 | def complete(self): 42 | values = self.get_stats().values() 43 | if not values: 44 | return False 45 | return not None in values 46 | -------------------------------------------------------------------------------- /listener/app_engine/stats/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | stats_completed = django.dispatch.Signal(providing_args=["instance",]) 4 | -------------------------------------------------------------------------------- /listener/app_engine/stats/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime, timedelta 3 | 4 | from django.test import TestCase 5 | from django.test.client import Client 6 | 7 | from django.contrib.auth.models import User 8 | from django.core.exceptions import ValidationError 9 | from django.core.urlresolvers import reverse 10 | from django.db import connection 11 | 12 | from app import tests 13 | from error.models import Error, Group 14 | from stats.utils import count 15 | 16 | class StatsTests(TestCase): 17 | # test the view for writing errors 18 | def setUp(self): 19 | for error in Error.all(): error.delete() 20 | 21 | def testCount(self): 22 | for x in range(0, 1110): 23 | Error().save(dont_send_signals=True) 24 | assert count() == 1110 25 | for x in range(0, 5): 26 | err = Error(dont_send_signals=True) 27 | err.priority = 4 28 | err.save() 29 | assert count(["priority = ", 4]) == 5 30 | assert count(["priority = ", None]) == 1110 31 | assert count() == 1115 -------------------------------------------------------------------------------- /listener/app_engine/stats/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # if you put the key in here it will get exposed in errors 4 | # so probably 5 | urlpatterns = patterns('', 6 | url(r'^$', 'stats.views.view', name="stats-view"), 7 | url(r'^generate/$', 'stats.views.start', name="stats-start"), 8 | url(r'^generate/action/(?P[\w-]+)/(?P[\w-]+)/$', 'stats.views.get_action', name="stats-action"), 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /listener/app_engine/stats/utils.py: -------------------------------------------------------------------------------- 1 | from error.models import Error 2 | 3 | # get around the 1000 count limit, based on 4 | # http://notes.mhawthorne.net/post/172608709/appengine-counting-more-than-1000-entities 5 | max_fetch = 1000 6 | 7 | def count(*filters): 8 | count = 0 9 | 10 | query = Error.all(keys_only=True) 11 | for k, v in filters: 12 | query = query.filter(k, v) 13 | 14 | query = query.order('__key__') 15 | 16 | while count % max_fetch == 0: 17 | current_count = query.count() 18 | if current_count == 0: 19 | break 20 | count += current_count 21 | 22 | if current_count == max_fetch: 23 | last_key = query.fetch(1, max_fetch - 1)[0] 24 | query = query.filter('__key__ > ', last_key) 25 | 26 | return count -------------------------------------------------------------------------------- /listener/app_engine/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django.conf.urls.defaults import * 16 | 17 | urlpatterns = patterns('', 18 | (r'', include('error.urls')), 19 | (r'', include('receiving.urls')), 20 | (r'', include('app.urls')), 21 | (r'', include('users.urls')), 22 | (r'^issues/', include('issues.urls')), 23 | (r'^stats/', include('stats.urls')), 24 | (r'^projects/', include('projects.urls')), 25 | (r'^notification/', include('notifications.urls')), 26 | ) 27 | 28 | handler404 = 'app.errors.not_found_error' 29 | handler500 = 'app.errors.application_error' -------------------------------------------------------------------------------- /listener/app_engine/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/users/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.translation import ugettext as _ 3 | 4 | from app.forms import ModelForm 5 | 6 | from appengine_django.auth.models import User 7 | from profiles.models import Profile 8 | 9 | class UserForm(ModelForm): 10 | is_staff = forms.BooleanField(required=False, label=_("Access to Arecibo")) 11 | 12 | class Meta: 13 | model = User 14 | fields = ("is_staff", "first_name", "last_name", "email") 15 | 16 | choices = [ [x,x] for x in range(0, 10) ] 17 | choices[0][1] = "No notifications" 18 | 19 | class ProfileForm(ModelForm): 20 | notification = forms.IntegerField( 21 | required=False, 22 | label=_("Send notification at this priority level and above"), 23 | widget=forms.Select(choices=choices)) 24 | 25 | class Meta: 26 | model = Profile 27 | fields = ("notification",) -------------------------------------------------------------------------------- /listener/app_engine/users/templates/user_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
    4 |

    URLs and keysUsers

    5 |
    6 | {% endblock %} 7 | {% block content %} 8 |
    9 |

    Edit user

    10 |
    11 | {{ form.as_custom }} 12 | {{ profile.as_custom }} 13 |
    14 |
    15 |
    16 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/users/templates/user_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
    4 |

    URLs and keysUsers

    5 |
    6 | {% endblock %} 7 | {% block content %} 8 |
    9 | {% if page.object_list %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for object in page.object_list %} 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
    NameEmailAllowed
    {{ object.first_name|default:"" }} {{ object.last_name|default:"" }}{{ object.email }} {% ifequal object.key user.key %}(this is you){% endifequal %}{{ object.is_staff|yesno|capfirst }}
    27 |

    It's possible to lock yourself out of this site. If so use the Google App Engine console.

    28 | {% else %} 29 |

    No users found, which is a worry, how did you get here?

    30 | {% endif %} 31 |
    32 | {% include "pagination.html" %} 33 | {% endblock %} -------------------------------------------------------------------------------- /listener/app_engine/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^users/$', 'users.views.user_list', name="user-list"), 5 | url(r'^users/edit/(?P[\w-]+)/$', 'users.views.user_edit', name="user-edit"), 6 | ) 7 | -------------------------------------------------------------------------------- /listener/app_engine/users/utils.py: -------------------------------------------------------------------------------- 1 | from appengine_django.auth.models import User 2 | 3 | def approved_users(): 4 | return User.all().filter("is_staff = ", True) 5 | -------------------------------------------------------------------------------- /listener/app_engine/users/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import user_passes_test 2 | from django.views.generic.simple import direct_to_template 3 | from django.http import HttpResponseRedirect 4 | from django.core.urlresolvers import reverse 5 | 6 | from appengine_django.auth.models import User 7 | from app.paginator import Paginator, get_page 8 | 9 | from users.forms import UserForm, ProfileForm 10 | from profiles.utils import get_profile 11 | 12 | @user_passes_test(lambda u: u.is_staff) 13 | def user_list(request): 14 | queryset = User.all() 15 | # this number doesn't need to be high and its quite an expensive 16 | # page to generate 17 | paginated = Paginator(queryset, 10) 18 | page = get_page(request, paginated) 19 | return direct_to_template(request, "user_list.html", extra_context={ 20 | "page": page, 21 | "nav": {"selected": "setup"} 22 | }) 23 | 24 | @user_passes_test(lambda u: u.is_staff) 25 | def user_edit(request, pk): 26 | user = User.get(pk) 27 | form = UserForm(request.POST or None, instance=user) 28 | profile = ProfileForm(request.POST or None, instance=get_profile(user)) 29 | if form.is_valid() and profile.is_valid(): 30 | form.save() 31 | profile.save() 32 | return HttpResponseRedirect(reverse("user-list")) 33 | return direct_to_template(request, "user_edit.html", extra_context={ 34 | "form": form, 35 | "profile": profile, 36 | "nav": {"selected": "users",}, 37 | }) 38 | 39 | -------------------------------------------------------------------------------- /listener/app_engine/userstorage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/app_engine/userstorage/__init__.py -------------------------------------------------------------------------------- /listener/app_engine/userstorage/middleware.py: -------------------------------------------------------------------------------- 1 | from userstorage.utils import activate, deactivate 2 | 3 | class UserStorage: 4 | def process_request(self, request): 5 | activate(request) 6 | 7 | def process_exception(self, request, exception): 8 | deactivate(request) 9 | 10 | def process_response(self, request, response): 11 | deactivate(request) 12 | return response 13 | -------------------------------------------------------------------------------- /listener/app_engine/userstorage/utils.py: -------------------------------------------------------------------------------- 1 | from threading import currentThread 2 | 3 | _active = {} 4 | 5 | def activate(request): 6 | if request and request.user: 7 | _active[currentThread()] = request.user 8 | 9 | def deactivate(request): 10 | global _active 11 | if currentThread() in _active: 12 | del _active[currentThread()] 13 | 14 | def get_user(): 15 | if currentThread() not in _active: 16 | return None 17 | return _active[currentThread()] 18 | -------------------------------------------------------------------------------- /listener/docs/source/client/index.rst: -------------------------------------------------------------------------------- 1 | Arecibo clients 2 | ============================================ 3 | 4 | Clients are the parts that send information to Arecibo using one of its API's. 5 | 6 | All clients can be found in the clients directory of the Arecibo source. -------------------------------------------------------------------------------- /listener/docs/source/client/php.rst: -------------------------------------------------------------------------------- 1 | Sample PHP Client 2 | ==================================== 3 | 4 | The PHP client allows you to easily send errors via HTTP. The PHP client can be used independently, or as part of a greater implementation. Quick example. 5 | 6 | .. code-block:: php 7 | 8 | "yourpublicaccountnumber", 12 | "status" => "403", 13 | "url" => "http://badphpapp.org" 14 | ); 15 | post("http://yoursevers/v/1/", $fields); 16 | ?> 17 | 18 | Requirements 19 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | * Tested on PHP 5.2.6. 22 | 23 | * If using the http client, permission to make a HTTP post to the Arecibo server is needed. 24 | 25 | Notes 26 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 27 | 28 | * No values are automatically set. -------------------------------------------------------------------------------- /listener/docs/source/client/ruby.rst: -------------------------------------------------------------------------------- 1 | Sample Ruby Client 2 | ==================================== 3 | 4 | The Ruby client allows you to easily send errors via HTTP. The Ruby client can be used independently, or as part of a greater implementation. Quick example: 5 | 6 | .. code-block:: ruby 7 | 8 | require 'arecibo' 9 | dict = { 10 | :account => 'yourpublicaccountnumber', 11 | :priority => 1, 12 | :url => "http://badapp.org", 13 | :uid => "123124123123", 14 | :ip => "127.0.0.1", 15 | :type => "An error", 16 | :server => "Test Script" 17 | } 18 | p = Arecibo.new("http://yoursite/v/1/", dict) 19 | p.send 20 | 21 | Requirements 22 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | 24 | * If using the http client, permission to make a HTTP post to the Arecibo server is needed. 25 | 26 | Notes 27 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 28 | 29 | * No values are automatically set. 30 | 31 | * There is 10 second timeout set on the HTTP post to prevent hangs. -------------------------------------------------------------------------------- /listener/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Arecibo documentation 2 | =================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | introduction.rst 10 | server/installation.rst 11 | server/installation-appengine.rst 12 | server/useraccess.rst 13 | server/remoteaccess.rst 14 | server/customisation.rst 15 | server/concepts.rst 16 | client/index.rst 17 | client/post.rst 18 | client/variables.rst 19 | client/javascript.rst 20 | client/python.rst 21 | client/ruby.rst 22 | client/php.rst 23 | client/plone.rst 24 | client/django.rst 25 | client/rails.rst 26 | release.rst 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`modindex` 33 | * :ref:`search` 34 | 35 | -------------------------------------------------------------------------------- /listener/docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction to Arecibo 2 | ============================== 3 | 4 | Arecibo is an error tracker. It allows you to log errors from web sites and collect them all in one location. It then allows you to prioritise and group them. Notifications can then be sent to developers of the errors. 5 | 6 | There are two main components to Arecibo: 7 | 8 | * client, this is normally a web application on residing on a server that sends an error into Arecibo. If there isn't an existing API for your application, then there's an API you can use to build your own. 9 | 10 | * server, this is your instance of Arecibo that you can use to receive errors. 11 | 12 | The server was written to run on App Engine in 2010. In 2011 a none App Engine port was made that should run in a "standard" environment. The App Engine port is essentially frozen now with no new updates. It works, but isn't realy developed. This situation may change, but if we can recommend a version to use, don't use the App Engine one. 13 | 14 | There is also a bit of feature fork between the two. Primarily the App Engine one has an Issue tracker. The none App Engine one does not. Because of it's use at Mozilla the none App Engine one has no need of an Issue tracker. 15 | 16 | The best way to get a feel for what Arecibo does, is to give the demo server a try. The details of the demo server are at http://www.areciboapp.com/demo 17 | -------------------------------------------------------------------------------- /listener/docs/source/server/concepts.rst: -------------------------------------------------------------------------------- 1 | Server side models 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | The following describes the models that exist on the server side and their relationships. 5 | 6 | Error 7 | ------------------- 8 | 9 | The error that has been recorded by the incoming receiver. 10 | 11 | Group 12 | ------------------- 13 | 14 | A grouping of errors. Rather than dealing with individual errors, you might like to deal with groups. This is probably more advisable for tickets or tracking. When an error is written a signal is triggered. A default group method listens to this signal to place the error in a group. An error can only be in one group. 15 | 16 | Project 17 | ------------------- 18 | 19 | A website or group of websites collected into a project. A project will have one or more domains. When an error is written a signal is triggered. A default project method listens to this signal to place the error within the appropriate project. An error can only be in one project -------------------------------------------------------------------------------- /listener/docs/source/server/remoteaccess.rst: -------------------------------------------------------------------------------- 1 | Remote access (App Engine) 2 | ========================================== 3 | 4 | The instance on Google App Engine can be accessed by using the *remote_api*[1]_. A management command has been added to allow you to do this:: 5 | 6 | python manage.py remote 7 | App Engine interactive console for test-areciboapp 8 | >>> 9 | 10 | You can then execute Python commands. To view the timestamp of the first error:: 11 | 12 | >>> Error.all()[0].timestamp 13 | Username:***********@googlemail.com 14 | Password: 15 | datetime.datetime(2010, 6, 14, 16, 25, 49, 835365) 16 | 17 | Note that the console is not appropriate for tasks that do a large number of datastore requests, since each is a HTTP request. For example: batch altering of records - for that you'd be better off uploading a specific script. 18 | 19 | .. [1] http://code.google.com/appengine/articles/remote_api.html -------------------------------------------------------------------------------- /listener/docs/source/server/scripts.rst: -------------------------------------------------------------------------------- 1 | Example scripts 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | These are some example scripts to work with Arecibo differently. 5 | 6 | Giving all users access automatically 7 | --------------------------------------------------------- 8 | 9 | In *custom/listeners.py*, add the following: 10 | 11 | .. code-block:: python 12 | 13 | from appengine_django.auth.signals import user_created 14 | 15 | def make_staff(sender, instance, **kw): 16 | if not instance.is_staff: 17 | instance.is_staff = True 18 | instance.save() 19 | 20 | user_created.connect(make_staff, dispatch_uid="make_staff") 21 | -------------------------------------------------------------------------------- /listener/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/lib/__init__.py -------------------------------------------------------------------------------- /listener/lib/markdown/etree_loader.py: -------------------------------------------------------------------------------- 1 | 2 | from markdown import message, CRITICAL 3 | import sys 4 | 5 | ## Import 6 | def importETree(): 7 | """Import the best implementation of ElementTree, return a module object.""" 8 | etree_in_c = None 9 | try: # Is it Python 2.5+ with C implemenation of ElementTree installed? 10 | import xml.etree.cElementTree as etree_in_c 11 | except ImportError: 12 | try: # Is it Python 2.5+ with Python implementation of ElementTree? 13 | import xml.etree.ElementTree as etree 14 | except ImportError: 15 | try: # An earlier version of Python with cElementTree installed? 16 | import cElementTree as etree_in_c 17 | except ImportError: 18 | try: # An earlier version of Python with Python ElementTree? 19 | import elementtree.ElementTree as etree 20 | except ImportError: 21 | message(CRITICAL, "Failed to import ElementTree") 22 | sys.exit(1) 23 | if etree_in_c and etree_in_c.VERSION < "1.0": 24 | message(CRITICAL, "For cElementTree version 1.0 or higher is required.") 25 | sys.exit(1) 26 | elif etree_in_c : 27 | return etree_in_c 28 | elif etree.VERSION < "1.1": 29 | message(CRITICAL, "For ElementTree version 1.1 or higher is required") 30 | sys.exit(1) 31 | else : 32 | return etree 33 | 34 | -------------------------------------------------------------------------------- /listener/lib/markdown/extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/lib/markdown/extensions/__init__.py -------------------------------------------------------------------------------- /listener/lib/userstorage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/lib/userstorage/__init__.py -------------------------------------------------------------------------------- /listener/lib/userstorage/middleware.py: -------------------------------------------------------------------------------- 1 | from userstorage.utils import activate, deactivate 2 | 3 | class UserStorage: 4 | def process_request(self, request): 5 | activate(request) 6 | 7 | def process_exception(self, request, exception): 8 | deactivate(request) 9 | 10 | def process_response(self, request, response): 11 | deactivate(request) 12 | return response 13 | -------------------------------------------------------------------------------- /listener/lib/userstorage/utils.py: -------------------------------------------------------------------------------- 1 | from threading import currentThread 2 | 3 | _active = {} 4 | 5 | def activate(request): 6 | if request and request.user: 7 | _active[currentThread()] = request.user 8 | 9 | def deactivate(request): 10 | global _active 11 | if currentThread() in _active: 12 | del _active[currentThread()] 13 | 14 | def get_user(): 15 | if currentThread() not in _active: 16 | return None 17 | return _active[currentThread()] 18 | -------------------------------------------------------------------------------- /listener/media/css/ie.css: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------- 2 | 3 | 4 | Blueprint CSS Framework 0.8 5 | http://blueprintcss.org 6 | 7 | * Copyright (c) 2007-Present. See LICENSE for more info. 8 | * See README for instructions on how to use Blueprint. 9 | * For credits and origins, see AUTHORS. 10 | * This is a compressed file. See the sources in the 'src' directory. 11 | 12 | ----------------------------------------------------------------------- */ 13 | 14 | /* ie.css */ 15 | body {text-align:center;} 16 | .container {text-align:left;} 17 | * html .column, * html div.span-1, * html div.span-2, * html div.span-3, * html div.span-4, * html div.span-5, * html div.span-6, * html div.span-7, * html div.span-8, * html div.span-9, * html div.span-10, * html div.span-11, * html div.span-12, * html div.span-13, * html div.span-14, * html div.span-15, * html div.span-16, * html div.span-17, * html div.span-18, * html div.span-19, * html div.span-20, * html div.span-21, * html div.span-22, * html div.span-23, * html div.span-24 {overflow-x:hidden;} 18 | * html legend {margin:0px -8px 16px 0;padding:0;} 19 | ol {margin-left:2em;} 20 | sup {vertical-align:text-top;} 21 | sub {vertical-align:text-bottom;} 22 | html>body p code {*white-space:normal;} 23 | hr {margin:-8px auto 11px;} 24 | .clearfix, .container {display:inline-block;} 25 | * html .clearfix, * html .container {height:1%;} 26 | fieldset {padding-top:0;} -------------------------------------------------------------------------------- /listener/media/css/print.css: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------- 2 | 3 | 4 | Blueprint CSS Framework 0.8 5 | http://blueprintcss.org 6 | 7 | * Copyright (c) 2007-Present. See LICENSE for more info. 8 | * See README for instructions on how to use Blueprint. 9 | * For credits and origins, see AUTHORS. 10 | * This is a compressed file. See the sources in the 'src' directory. 11 | 12 | ----------------------------------------------------------------------- */ 13 | 14 | /* print.css */ 15 | body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;} 16 | .container {background:none;} 17 | hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;} 18 | hr.space {background:#fff;color:#fff;} 19 | h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;} 20 | code {font:.9em "Courier New", Monaco, Courier, monospace;} 21 | img {float:left;margin:1.5em 1.5em 1.5em 0;} 22 | a img {border:none;} 23 | p img.top {margin-top:0;} 24 | blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;} 25 | .small {font-size:.9em;} 26 | .large {font-size:1.1em;} 27 | .quiet {color:#999;} 28 | .hide {display:none;} 29 | a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;} 30 | a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;} -------------------------------------------------------------------------------- /listener/media/img/alert-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/alert-overlay.png -------------------------------------------------------------------------------- /listener/media/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/apple-touch-icon.png -------------------------------------------------------------------------------- /listener/media/img/arecibo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/arecibo.png -------------------------------------------------------------------------------- /listener/media/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/arrow.png -------------------------------------------------------------------------------- /listener/media/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/favicon.ico -------------------------------------------------------------------------------- /listener/media/img/logo-opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/logo-opacity.png -------------------------------------------------------------------------------- /listener/media/img/priority-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/priority-1.jpg -------------------------------------------------------------------------------- /listener/media/img/priority-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/priority-2.jpg -------------------------------------------------------------------------------- /listener/media/img/priority-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/priority-3.jpg -------------------------------------------------------------------------------- /listener/media/img/read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/read.png -------------------------------------------------------------------------------- /listener/media/img/status-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/status-fixed.jpg -------------------------------------------------------------------------------- /listener/media/img/status-not-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/media/img/status-not-fixed.jpg -------------------------------------------------------------------------------- /listener/normal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/__init__.py -------------------------------------------------------------------------------- /listener/normal/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/__init__.py -------------------------------------------------------------------------------- /listener/normal/app/base.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/base.py -------------------------------------------------------------------------------- /listener/normal/app/context.py: -------------------------------------------------------------------------------- 1 | from urllib import urlencode 2 | from django.conf import settings 3 | 4 | 5 | def context(request): 6 | data = {} 7 | data["request"] = request 8 | data["user"] = request.user 9 | data["public_key"] = settings.ARECIBO_PUBLIC_ACCOUNT_NUMBER 10 | data["private_key"] = settings.ARECIBO_PRIVATE_ACCOUNT_NUMBER 11 | data["site_url"] = settings.SITE_URL.strip() 12 | data["anonymous_access"] = settings.ANONYMOUS_ACCESS 13 | 14 | qs = request.GET.copy() 15 | if "page" in qs: 16 | del qs["page"] 17 | 18 | data["qs"] = "" 19 | if qs: 20 | data["qs"] = "%s" % urlencode(qs) 21 | 22 | return data 23 | -------------------------------------------------------------------------------- /listener/normal/app/decorators.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import user_passes_test 2 | from django.conf import settings 3 | 4 | 5 | def arecibo_login_required(func): 6 | return user_passes_test(lambda u: settings.ANONYMOUS_ACCESS or u.is_staff)(func) -------------------------------------------------------------------------------- /listener/normal/app/errors.py: -------------------------------------------------------------------------------- 1 | from exceptions import Exception 2 | 3 | from django.template import RequestContext, loader 4 | from django.http import HttpResponse 5 | 6 | 7 | class StatusDoesNotExist(Exception): pass 8 | 9 | 10 | def not_found_error(request): 11 | t = loader.get_template('404.html') 12 | c = RequestContext(request) 13 | return HttpResponse(t.render(c), status=404) 14 | 15 | 16 | def application_error(request): 17 | t = loader.get_template('500.html') 18 | c = RequestContext(request) 19 | return HttpResponse(t.render(c), status=500) -------------------------------------------------------------------------------- /listener/normal/app/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "auth.user", 5 | "fields": { 6 | "username": "admin", 7 | "first_name": "", 8 | "last_name": "", 9 | "is_active": true, 10 | "is_superuser": true, 11 | "is_staff": true, 12 | "last_login": "2011-03-21 03:26:13", 13 | "groups": [], 14 | "user_permissions": [], 15 | "password": "sha1$5fb4a$f37fe533d025ec1f3d65504d4c26b3dedace29db", 16 | "email": "andy@clearwind.ca", 17 | "date_joined": "2011-03-15 17:02:45" 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /listener/normal/app/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.html import conditional_escape 3 | from django.utils.encoding import force_unicode 4 | from django.utils.safestring import mark_safe 5 | 6 | 7 | def as_blue_print(self): 8 | return self._html_output(u""" 9 |
    10 |
    11 | %(label)s
    12 | %(errors)s 13 | %(help_text)s 14 | %(field)s 15 |
    16 |
    17 | """, u'%s', '', u'%s', False) 18 | 19 | 20 | class Form(forms.Form): 21 | required_css_class = 'required' 22 | 23 | def as_custom(self): 24 | return as_blue_print(self) 25 | 26 | 27 | class ModelForm(forms.ModelForm): 28 | required_css_class = 'required' 29 | 30 | def as_custom(self): 31 | return as_blue_print(self) 32 | 33 | 34 | def as_div(self): 35 | if not self: 36 | return u'' 37 | template = "%s" 38 | errors = ''.join([u'

    %s

    ' % conditional_escape(force_unicode(e)) for e in self]) 39 | template = template % errors 40 | return mark_safe(template) 41 | 42 | 43 | forms.util.ErrorList.__unicode__ = as_div 44 | -------------------------------------------------------------------------------- /listener/normal/app/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/management/__init__.py -------------------------------------------------------------------------------- /listener/normal/app/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/management/commands/__init__.py -------------------------------------------------------------------------------- /listener/normal/app/management/commands/groups.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from error.models import Error 4 | from error.models import Group 5 | from error.signals import error_created 6 | 7 | class Command(BaseCommand): 8 | help = 'Drops groups and then refires signals on addons, recreating the groups' 9 | 10 | def handle(self, *args, **options): 11 | groups = Group.objects.all() 12 | print 'Deleting %s group(s)' % groups.count() 13 | groups.delete() 14 | 15 | for error in Error.objects.all(): 16 | error_created.send(sender=Error, instance=error) 17 | -------------------------------------------------------------------------------- /listener/normal/app/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | class Middleware: 4 | def process_request(self, request): 5 | request.view_access = (settings.ANONYMOUS_ACCESS or 6 | request.user.is_authenticated()) 7 | request.write_access = request.user.is_authenticated() -------------------------------------------------------------------------------- /listener/normal/app/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/models.py -------------------------------------------------------------------------------- /listener/normal/app/paginator.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import Paginator as BasePaginator 2 | from django.core.paginator import Page, InvalidPage, EmptyPage 3 | 4 | class Paginator(BasePaginator): 5 | def page(self, number): 6 | "Returns a Page object for the given 1-based page number." 7 | bottom = (number - 1) * self.per_page 8 | top = bottom + self.per_page 9 | queryset = self.object_list[:(number * self.per_page)+1] 10 | results = queryset[bottom:top] 11 | try: 12 | queryset[top] 13 | self._num_pages = number + 1 14 | except IndexError: 15 | self._num_pages = number 16 | 17 | return Page(results, number, self) 18 | 19 | 20 | def get_page(request, paginator): 21 | try: 22 | page = int(request.GET.get('page', '1')) 23 | except ValueError: 24 | page = 1 25 | 26 | try: 27 | page = paginator.page(page) 28 | except (EmptyPage, InvalidPage): 29 | page = paginator.page(paginator.num_pages) 30 | 31 | return page -------------------------------------------------------------------------------- /listener/normal/app/tags.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/tags.py -------------------------------------------------------------------------------- /listener/normal/app/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
    5 |

    Not allowed

    6 |

    You can't do that. Sorry.

    7 | {% if request.is_anonymous %}

    You are not logged in, you could try logging in.

    {% endif %} 8 |
    9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /listener/normal/app/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
    5 |

    Page not found

    6 |

    Oops.

    7 |
    8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /listener/normal/app/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
    5 |

    Application error.

    6 |

    Oops.

    7 |
    8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /listener/normal/app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {{ get_user }} 4 | {% if user.is_anonymous %} 5 |
    6 |

    Existing user?

    7 |

    Login here with your account. If you are having problems, reset it here.

    8 |
    9 |
    10 |

    New user?

    11 |

    Create a new account, the administrator of this site will have to approve your access to the site.

    12 | {% if not anonymous_access %}

    An account is required to access any errors not open to the public.

    {% endif %} 13 |
    14 | {% else %} 15 | {% if not user.is_staff %} 16 |

    You have logged in, but the admin has not granted you access yet. Please be patient.

    17 | {% endif %} 18 | {% endif %} 19 | 20 |
    Welcome to this Arecibo installation. Arecibo is an observatory, instead of listening to the stars, this site listens for errors from your sites. We pronounce it "Ah-reh-see-bow". If you have any questions about the site, please contact it's administrator.
    21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /listener/normal/app/templates/setup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
    4 | 5 |
    6 | {% endblock %} 7 | {% block content %} 8 |
    9 |
    Public key
    10 |
    {{ public_key }} 11 |

    Use this in your error reporting

    12 |
    13 |
    URL for posting
    14 |
    {{ site_url }}{% url error-post %} 15 |

    Use this in your libraries for posting

    16 |
    17 |
    E-mail for posting
    18 |
    django-{{ public_key }}@{{ app_id }}.appspotmail.com 19 |

    Where to send email (more details)

    20 |
    Private key
    21 |
    {{ private_key }} 22 |

    This is for reading API and RSS

    23 |
    24 |
    RSS Feeds
    25 |
    Errors: {{ site_url }}/feed/{{ private_key }}/ 26 |

    You can also filter the RSS feed by any of the filters on the list page.

    27 |
    Groups: {{ site_url }}/group/feed/{{ private_key }}/ 28 |

    You can also filter the RSS feed by any of the filters on the group page.

    29 | 30 |
    31 | 32 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/app/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/app/templatetags/__init__.py -------------------------------------------------------------------------------- /listener/normal/app/templatetags/arecibo.py: -------------------------------------------------------------------------------- 1 | from app.utils import trunc_string 2 | from django.template.defaultfilters import stringfilter 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | @stringfilter 10 | def trunc(value, arg): 11 | return trunc_string(value, arg) 12 | -------------------------------------------------------------------------------- /listener/normal/app/test_runner.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.test.simple import DjangoTestSuiteRunner 5 | 6 | from django.core import mail 7 | from django.core.mail.backends import locmem 8 | 9 | from django.core.mail.message import EmailMessage 10 | 11 | 12 | # amongst other things this will suppress those annoying logs 13 | settings.DEBUG = False 14 | 15 | 16 | class AreciboRunner(DjangoTestSuiteRunner): 17 | def setup_test_environment(self, **kwargs): 18 | msgs = 'django.contrib.messages.context_processors.messages' 19 | if msgs not in settings.TEMPLATE_CONTEXT_PROCESSORS: 20 | tcp = list(settings.TEMPLATE_CONTEXT_PROCESSORS) 21 | tcp.append(msgs) 22 | settings.TEMPLATE_CONTEXT_PROCESSORS = tuple(tcp) 23 | settings.DEBUG_PROPAGATE_EXCEPTIONS = True 24 | settings.CELERY_ALWAYS_EAGER = True 25 | super(AreciboRunner, self).setup_test_environment(**kwargs) 26 | -------------------------------------------------------------------------------- /listener/normal/app/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^$', 'app.views.index', name="index"), 5 | url(r'^lib/error.js', 'app.views.javascript_client', name="error-javascript"), 6 | url(r'^lib/error-compress.js', 'app.views.javascript_client', name="error-javascript-compressed"), 7 | url(r'^setup$', 'app.views.setup', name="setup") 8 | ) 9 | -------------------------------------------------------------------------------- /listener/normal/celeryconfig.py.dist: -------------------------------------------------------------------------------- 1 | CELERY_RESULT_BACKEND = "amqp" 2 | 3 | CELERY_IMPORTS = ("custom", "users", "notifications", "receiving.post", ) 4 | 5 | BROKER_HOST = "" 6 | BROKER_PORT = 5672 7 | BROKER_USER = "" 8 | BROKER_PASSWORD = "" 9 | BROKER_VHOST = "" 10 | -------------------------------------------------------------------------------- /listener/normal/config/arecibo.wsgi.sample: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.path.dirname(os.path.dirname(__file__))) 5 | os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' 6 | 7 | import django.core.handlers.wsgi 8 | application = django.core.handlers.wsgi.WSGIHandler() -------------------------------------------------------------------------------- /listener/normal/custom/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | import listeners 3 | except ImportError: 4 | pass 5 | -------------------------------------------------------------------------------- /listener/normal/custom/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/custom/examples/__init__.py -------------------------------------------------------------------------------- /listener/normal/custom/examples/default_public.py: -------------------------------------------------------------------------------- 1 | from error.models import Error 2 | 3 | from error import signals 4 | 5 | def default_public(instance, **kw): 6 | instance.public = True 7 | instance.save() 8 | 9 | signals.error_created.connect(default_public, dispatch_uid="default_public") 10 | -------------------------------------------------------------------------------- /listener/normal/custom/examples/no_notifications.py: -------------------------------------------------------------------------------- 1 | from notifications.listeners import default_notification 2 | from error.signals import error_created 3 | 4 | error_created.disconnect(default_notification, dispatch_uid="default_notification") 5 | 6 | -------------------------------------------------------------------------------- /listener/normal/custom/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/custom/models.py -------------------------------------------------------------------------------- /listener/normal/error/__init__.py: -------------------------------------------------------------------------------- 1 | import listeners -------------------------------------------------------------------------------- /listener/normal/error/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from error.models import Error, Group 4 | 5 | 6 | class ErrorAdmin(admin.ModelAdmin): 7 | pass 8 | 9 | 10 | class GroupAdmin(admin.ModelAdmin): 11 | pass 12 | 13 | 14 | admin.site.register(Error, ErrorAdmin) 15 | admin.site.register(Group, GroupAdmin) 16 | -------------------------------------------------------------------------------- /listener/normal/error/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | error_created = django.dispatch.Signal(providing_args=["instance",]) 4 | group_created = django.dispatch.Signal(providing_args=["instance",]) 5 | group_assigned = django.dispatch.Signal(providing_args=["instance",]) 6 | error_assigned = django.dispatch.Signal(providing_args=["instance",]) 7 | -------------------------------------------------------------------------------- /listener/normal/error/templates/filter.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ form.as_custom }} 3 |
    4 | 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /listener/normal/error/templates/group-edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |
    6 | {% csrf_token %} 7 | {{ form.as_custom }} 8 |
    9 |
    10 |
    11 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/error/templates/group.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load arecibo %} 3 | {% block subnav %}{% include "subnav.html" %}{% endblock %} 4 | {% block content %} 5 |
    6 | {% if page.object_list %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for group in page.object_list %} 20 | 21 | 22 | 23 | {% with group.sample as sample %} 24 | 25 | 26 | 27 | 28 | 29 | {% endwith %} 30 | 31 | {% endfor %} 32 | 33 |
    CountMost recentStatusTypeNameProjectStage
    {{ group.count }}{{ group.timestamp|timesince }}{{ sample.status }}{{ sample.type|trunc:20 }}{{ group.name }} [edit]{{ group.project_url.project.name }}{{ group.project_url.get_stage_display }}
    34 | {% else %} 35 |

    No errors found. Could be you've not sent any errors yet. Perhaps you've got some filters 36 | set in the page, in which case reset them.

    37 | {% endif %} 38 |
    39 |
    40 | {% include "filter.html" %} 41 |
    42 | {% include "pagination.html" %} 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /listener/normal/error/templates/list-snippet.html: -------------------------------------------------------------------------------- 1 | {% load arecibo %} 2 | {% for error in object_list %} 3 | 4 | {% if error.priority|stringformat:"s" in "123" %}{% endif %} 5 | {% if error.read %}{% else %}{% endif %} 6 | {{ error.status }} 7 | 8 | {{ error.type }} 9 | {{ error.domain }} 10 | {{ error.query|trunc:25 }} 11 | 12 | {% endfor %} 13 | -------------------------------------------------------------------------------- /listener/normal/error/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "subnav.html" %}{% endblock %} 3 | {% block content %} 4 | {% with page.object_list as object_list %} 5 |
    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% include "list-snippet.html" %} 19 | 20 | 21 |
    StatusAgoTypeDomainPath
    22 | 23 |

    No errors found. Could be you've not sent any errors yet. Perhaps you've got some filters 24 | set in the page, in which case reset them.

    25 |
    26 | {% endwith %} 27 |
    28 | {% include "filter.html" %} 29 |
    30 | {% include "pagination.html" %} 31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /listener/normal/error/templates/pagination.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /listener/normal/error/templates/subnav.html: -------------------------------------------------------------------------------- 1 |
    2 | {% if request.view_access %}List 3 | • Group 4 | • RSS feed{% endif %} 5 |
    6 |
    7 | {% if refresh %} 8 | Automatic update pending. 9 | {% endif %} 10 |
    11 | -------------------------------------------------------------------------------- /listener/normal/error/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # if you put the key in here it will get exposed in errors 4 | # so probably 5 | urlpatterns = patterns('', 6 | url(r'^feed/.*?/json/$', 'error.feeds.json', name="json"), 7 | url(r'^feed/.*?/$', 'error.feeds.atom', name="rss"), 8 | url(r'^group/feed/.*?/json/$', 'error.feeds.group_json', name="json"), 9 | url(r'^group/feed/.*?/$', 'error.feeds.group_atom', name="rss"), 10 | url(r'^list/$', 'error.views.errors_list', name="error-list"), 11 | url(r'^list/snippet/$', 'error.views.errors_snippet', name="error-snippet"), 12 | url(r'^groups/$', 'error.views.groups_list', name="group-list"), 13 | url(r'^group/(?P[\w-]+)$', 'error.views.group_edit', name="group-edit"), 14 | url(r'^view/(?P[\w-]+)/$', 'error.views.error_view', name="error-view"), 15 | url(r'^view/toggle/(?P[\w-]+)/$','error.views.error_public_toggle', name="error-toggle") 16 | ) 17 | -------------------------------------------------------------------------------- /listener/normal/error/validations.py: -------------------------------------------------------------------------------- 1 | from app.errors import StatusDoesNotExist 2 | 3 | codes = ['100', '101', '102', '200', '201', '202', '203', '204', '205', '206', 4 | '207', '226', '300', '301', '302', '303', '304', '305', '307', '400', '401', 5 | '402', '403', '404', '405', '406', '407', '408', '409', '410', '411', '412', 6 | '413', '414', '415', '416', '417', '422', '423', '424', '426', '500', '501', 7 | '502', '503', '504', '505', '507', '510'] 8 | 9 | def valid_status(code): 10 | if isinstance(code, str): 11 | code = str(code) 12 | if code not in codes: 13 | raise StatusDoesNotExist, 'The status "%s" does not exist.' % code 14 | -------------------------------------------------------------------------------- /listener/normal/local_settings.py.dist: -------------------------------------------------------------------------------- 1 | # This is an ASCII string so that you can have hard to guess URLs for 2 | # reading data from Arecibo without authentication, useful for RSS readers 3 | # for example. It can be any long or short ASCII string. 4 | ARECIBO_PUBLIC_ACCOUNT_NUMBER = "your_public_account_number_here" 5 | 6 | # If you do not allow anonymous posting (below) you will need to set 7 | # this to a value to ensure that not everyone can post errors to Arecibo. 8 | # It can be any long ASCII string. 9 | ARECIBO_PRIVATE_ACCOUNT_NUMBER = "your_private_account_number_here" 10 | 11 | # The root domain that the server will be served from, used in emails 12 | # and other places. 13 | SITE_URL = "http://theurl.to.your.arecibo.instance.com" 14 | 15 | # If true allows anyone to the view the site and errors. 16 | ANONYMOUS_ACCESS = False 17 | 18 | # If true allows anyone to post errors without having the private account 19 | # number defined above. 20 | ANONYMOUS_POSTING = False 21 | 22 | # Standard Django database config. 23 | DATABASES = { 24 | 'default': { 25 | 'ENGINE': 'django.db.backends', 26 | 'NAME': '', 27 | 'USER': '', 28 | 'PASSWORD': '', 29 | 'HOST': '', 30 | 'PORT': '', 31 | } 32 | } 33 | 34 | # The standard Django secret key, please define. 35 | SECRET_KEY = '' 36 | DEBUG = False -------------------------------------------------------------------------------- /listener/normal/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /listener/normal/migrations/01-add-count.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `error_error` ADD COLUMN `count` integer NOT NULL DEFAULT 1; 2 | CREATE INDEX `error_error_count` ON `error_error` (`count`); 3 | -------------------------------------------------------------------------------- /listener/normal/notifications/__init__.py: -------------------------------------------------------------------------------- 1 | import listeners -------------------------------------------------------------------------------- /listener/normal/notifications/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from notifications.models import Notification 4 | 5 | 6 | class NotificationAdmin(admin.ModelAdmin): 7 | pass 8 | 9 | 10 | admin.site.register(Notification, NotificationAdmin) 11 | -------------------------------------------------------------------------------- /listener/normal/notifications/email.py: -------------------------------------------------------------------------------- 1 | error_msg = """Arecibo is notifying you of the following errors: 2 | -------------------- 3 | 4 | %s 5 | -------------------- 6 | You are receiving this because it's the email address set for your account at %s. 7 | 8 | Please remember I'm just a bot and don't really exist, so replying to this email will 9 | not do you any good I'm afraid. 10 | """ 11 | 12 | from django.core.mail import send_mail 13 | from django.conf import settings 14 | from app.utils import log 15 | 16 | def as_text(issue): 17 | pass 18 | 19 | def as_text(error): 20 | details = [" Error: %s%s" % (settings.SITE_URL, error.get_absolute_url()), ] 21 | if error.raw: 22 | details.append(" URL: %s" % error.raw) 23 | details.append(" Error: %s, %s" % (error.type, error.msg)) 24 | for key in ("timestamp", "status", "server"): 25 | value = getattr(error, key) 26 | if value: 27 | details.append(" %s: %s" % (key.capitalize(), value)) 28 | details.append("") 29 | details = "\n".join(details) 30 | return details 31 | 32 | def send_error_email(holder): 33 | alot = 10 34 | data = "\n".join([ as_text(obj) for obj in holder.objs[:alot]]) 35 | count = len(holder.objs) 36 | if count > 1: 37 | subject = "Reporting %s errors" % count 38 | else: 39 | subject = "Reporting an error" 40 | if count > alot: 41 | data += "\n...truncated. For more see the website.\n" 42 | log("Sending email to: %s of %s error(s)" % (holder.user.email, count)) 43 | send_mail(subject, 44 | error_msg % (data, settings.SITE_URL), 45 | settings.DEFAULT_FROM_EMAIL, 46 | [holder.user.email], 47 | fail_silently=False) 48 | -------------------------------------------------------------------------------- /listener/normal/notifications/listeners.py: -------------------------------------------------------------------------------- 1 | from app.utils import log 2 | from notifications.models import Notification 3 | from users.utils import approved_users 4 | 5 | from error.signals import error_created 6 | 7 | def default_notification(instance, **kw): 8 | """ Given an error see if we need to send a notification """ 9 | log("Firing signal: default_notification") 10 | 11 | if instance.priority >= 5: 12 | return 13 | 14 | users = approved_users() 15 | if not users.count(): 16 | return 17 | 18 | notification = Notification() 19 | notification.notifier = instance 20 | notification.save() 21 | for user in users: 22 | notification.user.add(user) 23 | 24 | 25 | error_created.connect(default_notification, dispatch_uid="default_notification") -------------------------------------------------------------------------------- /listener/normal/notifications/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/notifications/management/__init__.py -------------------------------------------------------------------------------- /listener/normal/notifications/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/notifications/management/commands/__init__.py -------------------------------------------------------------------------------- /listener/normal/notifications/management/commands/cleanup_notifications.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from notifications.models import Notification 6 | from app.utils import log, render_plain 7 | 8 | class Command(BaseCommand): 9 | def handle(self, *args, **options): 10 | notifications_cleanup() 11 | 12 | def notifications_cleanup(days=0): 13 | log("Firing cron: notifications_cleanup") 14 | expired = datetime.today() - timedelta(days=days) 15 | queryset = Notification.objects.filter(tried=True, timestamp__lt=expired) 16 | for notification in queryset: 17 | notification.delete() 18 | 19 | return render_plain("Cron job completed") 20 | 21 | -------------------------------------------------------------------------------- /listener/normal/notifications/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | from django.utils.timesince import timesince 4 | 5 | from error.models import Error 6 | from notifications.signals import notification_created 7 | 8 | 9 | class Notification(models.Model): 10 | user = models.ManyToManyField(User) 11 | 12 | tried = models.BooleanField(default=False) 13 | completed = models.BooleanField(default=False) 14 | error_msg = models.TextField() 15 | timestamp = models.DateTimeField(auto_now_add=True) 16 | 17 | notifier = models.ForeignKey(Error, blank=True, null=True) 18 | 19 | def save(self, *args, **kw): 20 | created = not getattr(self, "id", None) 21 | super(Notification, self).save(*args, **kw) 22 | if created: 23 | notification_created.send(sender=self.__class__, instance=self) 24 | 25 | def __unicode__(self): 26 | rest = '' 27 | if self.tried: 28 | rest += '[tried]' 29 | if self.completed: 30 | rest += '[completed]' 31 | since = timesince(self.timestamp) 32 | return u'[%s] %s %s' % (since, self.error_msg[50:], rest) 33 | -------------------------------------------------------------------------------- /listener/normal/notifications/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | notification_created = django.dispatch.Signal(providing_args=["instance",]) -------------------------------------------------------------------------------- /listener/normal/notifications/templates/notification_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
    4 | {% if page.object_list %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for notification in page.object_list %} 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 |
    AgoAboutUsersTriedCompletedComments
    {{ notification.timestamp|timesince }}{% if notification.notifier %} 20 | {{ notification.notifier.title }} 21 | {% else %} 22 | Unknown 23 | {% endif %} 24 | {% with notification.user.all as list %}{{ list.0.username }}{% if list.count > 1 %}...{% endif %}{% endwith %}{{ notification.tried|yesno }}{{ notification.completed|yesno }}{% if notification.error_msg %}{{ notification.error_msg }}{% endif %}
    33 | {% else %} 34 |

    No notifications found. Could be you've not sent any errors yet and generated 35 | notifications. Or we've cleaned out this list of notifications. Or your 36 | notifications are not enabled. More than likely one of those.

    37 | {% endif %} 38 |
    39 | {% include "pagination.html" %} 40 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/notifications/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^list/$', 'notifications.views.notifications_list', name="notification-list"), 5 | ) 6 | -------------------------------------------------------------------------------- /listener/normal/notifications/views.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import datetime, timedelta 3 | 4 | from django.contrib.auth.decorators import user_passes_test 5 | from django.views.generic.simple import direct_to_template 6 | 7 | from notifications.models import Notification 8 | from notifications.email import send_error_email 9 | 10 | from app.decorators import arecibo_login_required 11 | from app.paginator import Paginator, get_page 12 | from app.utils import log, render_plain 13 | 14 | 15 | @arecibo_login_required 16 | def notifications_list(request): 17 | queryset = Notification.objects.all().order_by("-timestamp") 18 | paginated = Paginator(queryset, 10) 19 | page = get_page(request, paginated) 20 | return direct_to_template(request, "notification_list.html", extra_context={ 21 | "page": page, 22 | "nav": {"selected": "notifications"} 23 | }) 24 | -------------------------------------------------------------------------------- /listener/normal/projects/__init__.py: -------------------------------------------------------------------------------- 1 | import listeners -------------------------------------------------------------------------------- /listener/normal/projects/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from projects.models import Project, ProjectURL 4 | 5 | 6 | class ProjectAdmin(admin.ModelAdmin): 7 | pass 8 | 9 | 10 | class ProjectURLAdmin(admin.ModelAdmin): 11 | pass 12 | 13 | 14 | admin.site.register(Project, ProjectAdmin) 15 | admin.site.register(ProjectURL, ProjectURLAdmin) 16 | -------------------------------------------------------------------------------- /listener/normal/projects/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from app.forms import ModelForm 3 | 4 | from projects.models import Project, ProjectURL, stage_choices 5 | 6 | 7 | class ProjectForm(ModelForm): 8 | name = forms.CharField(required=True, label="Name") 9 | description = forms.CharField(required=False, label="Description", 10 | widget=forms.Textarea) 11 | 12 | class Meta: 13 | model = Project 14 | 15 | 16 | class ProjectURLForm(ModelForm): 17 | url = forms.CharField(required=True, label="Domain") 18 | stage = forms.CharField(required=True, label="Project stage", 19 | widget=forms.Select(choices=stage_choices)) 20 | 21 | class Meta: 22 | model = ProjectURL 23 | fields = ("url", "stage") -------------------------------------------------------------------------------- /listener/normal/projects/listeners.py: -------------------------------------------------------------------------------- 1 | from app.utils import log 2 | 3 | from projects.models import Project 4 | from error import signals 5 | 6 | 7 | def lookup_domain(domain): 8 | # given a domain, find the project 9 | projects = Project.objects.all() 10 | for project in projects: 11 | for url in project.projecturl_set.all(): 12 | if domain == url.url: 13 | return url 14 | 15 | 16 | def default_project(instance, **kw): 17 | log("Firing signal: default_project") 18 | if instance.project_url: 19 | return 20 | 21 | error = instance.sample() 22 | if error: 23 | domain = lookup_domain(error.domain) 24 | if domain: 25 | instance.project_url = domain 26 | instance.save() 27 | 28 | 29 | signals.group_assigned.connect(default_project, 30 | dispatch_uid="default_browser_parsing") -------------------------------------------------------------------------------- /listener/normal/projects/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.db import models 4 | 5 | from django.utils.translation import ugettext as _ 6 | 7 | stage_choices = ( 8 | ["dev", _("Development")], 9 | ["testing", _("Testing")], 10 | ["staging", _("Staging")], 11 | ["backup", _("Backups")], 12 | ["production", _("Production")], 13 | ["other", _("Other")] 14 | ) 15 | 16 | class Project(models.Model): 17 | name = models.CharField(blank=False, max_length=255) 18 | description = models.CharField(blank=False, max_length=255) 19 | 20 | def __unicode__(self): 21 | return self.name 22 | 23 | class ProjectURL(models.Model): 24 | project = models.ForeignKey(Project) 25 | url = models.CharField(blank=False, max_length=255) 26 | stage = models.CharField(choices=stage_choices, blank=False, max_length=255) 27 | 28 | def get_stage_display(self): 29 | return dict(stage_choices).get(self.stage) 30 | 31 | def __unicode__(self): 32 | return self.url 33 | -------------------------------------------------------------------------------- /listener/normal/projects/signals.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/projects/signals.py -------------------------------------------------------------------------------- /listener/normal/projects/templates/project_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |
    6 | {% csrf_token %} 7 | {{ form.as_custom }} 8 |
    9 |
    10 |
    11 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/projects/templates/project_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |
    6 | {% csrf_token %} 7 | {{ form.as_custom }} 8 |
    9 |
    10 |
    11 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/projects/templates/project_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 | {% for project in page %} 6 |

    {{ project.name }}

    7 | {% if project.description %} 8 |

    {{ project.description }}

    9 | {% endif %} 10 |
    11 | Edit • 12 | Add 13 | 14 | 15 | {% for domain in project.projecturl_set.all %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% empty %} 23 | 24 | {% endfor %} 25 |
    {{ domain.url }}{{ domain.get_stage_display }}EditList Errors
    No domains defined yet for this project, you should probably add some.
    26 | {% endfor %} 27 | 28 |

    Add in a project

    29 |
    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /listener/normal/projects/templates/project_url_add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |

    {{ project.name }}

    6 |

    Add a domain to this project

    7 |
    8 | {% csrf_token %} 9 | {{ form.as_custom }} 10 |
    11 |
    12 |
    13 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/projects/templates/project_url_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "projects_subnav.html" %}{% endblock %} 3 | {% block content %} 4 |
    5 |

    {{ project.name }}

    6 |

    Edit a domain for this project

    7 |
    8 | {% csrf_token %} 9 | {{ form.as_custom }} 10 |
    11 |
    12 |
    13 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/projects/templates/projects_subnav.html: -------------------------------------------------------------------------------- 1 |
    2 | List 3 |
    -------------------------------------------------------------------------------- /listener/normal/projects/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.test.client import Client 3 | 4 | from django.core.urlresolvers import reverse 5 | 6 | from error.models import Error, Group 7 | from projects.models import Project, ProjectURL 8 | 9 | from app.tests import test_data 10 | 11 | 12 | class ProjectTests(TestCase): 13 | fixtures = ['users.json'] 14 | 15 | def _addError(self): 16 | c = Client() 17 | assert not Error.objects.all().count() 18 | c.post(reverse("error-post"), test_data) 19 | assert test_data["priority"] < 5, test_data["priority"] 20 | assert Error.objects.all().count() == 1 21 | 22 | def testEditProject(self): 23 | project = Project(name="test") 24 | project.save() 25 | 26 | self.client.login(username='admin', password='password') 27 | r = self.client.get(reverse("projects-edit", args=[project.pk])) 28 | self.assertEquals(200, r.status_code) 29 | 30 | def testAddProject(self): 31 | project = Project(name="test") 32 | project.save() 33 | 34 | project_url = ProjectURL() 35 | project_url.url = "badapp.org" 36 | project_url.stage = "dev" 37 | project_url.project = project 38 | project_url.save() 39 | 40 | self._addError() 41 | 42 | assert Group.objects.count() == 1 43 | assert Group.objects.all()[0].project_url == project_url 44 | -------------------------------------------------------------------------------- /listener/normal/projects/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^$', 'projects.views.project_list', name="projects-list"), 5 | 6 | url(r'^add/url/(?P[\w-]+)/$', 'projects.views.project_url_add', name="projects-url-add"), 7 | url(r'^edit/url/(?P[\w-]+)/(?P[\w-]+)/$', 'projects.views.project_url_edit', name="projects-url-edit"), 8 | 9 | url(r'^add/$', 'projects.views.project_add', name="projects-add"), 10 | url(r'^edit/(?P[\w-]+)/$', 'projects.views.project_edit', name="projects-edit"), 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /listener/normal/projects/utils.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/projects/utils.py -------------------------------------------------------------------------------- /listener/normal/receiving/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/receiving/__init__.py -------------------------------------------------------------------------------- /listener/normal/receiving/http.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.views.decorators.csrf import csrf_exempt 3 | 4 | from app.utils import render_plain 5 | from error.models import Error 6 | from receiving.post import populate 7 | 8 | 9 | @csrf_exempt 10 | def post(request): 11 | """ Add in a post """ 12 | data = request.POST.copy() 13 | if "ip" not in data: 14 | data["ip"] = request.META.get("REMOTE_ADDR", "") 15 | if "user_agent" not in data: 16 | data["user_agent"] = request.META.get("HTTP_USER_AGENT", "") 17 | populate.delay(data) 18 | return render_plain("Error recorded") 19 | -------------------------------------------------------------------------------- /listener/normal/receiving/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/receiving/models.py -------------------------------------------------------------------------------- /listener/normal/receiving/tests.py: -------------------------------------------------------------------------------- 1 | from receiving import post 2 | from django.conf import settings 3 | from django.test import TestCase 4 | from error.models import Error 5 | 6 | class PostTest(TestCase): 7 | 8 | def test_post_key(self): 9 | settings.ANONYMOUS_POSTING = False 10 | acc = settings.ARECIBO_PUBLIC_ACCOUNT_NUMBER 11 | post.populate({'account': acc}) 12 | assert(Error.objects.count() == 1) 13 | self.assertRaises(ValueError, post.populate, {'account': acc + "a"}) 14 | 15 | def test_post_no_key(self): 16 | settings.ANONYMOUS_POSTING = True 17 | acc = settings.ARECIBO_PUBLIC_ACCOUNT_NUMBER 18 | post.populate({'account': acc}) 19 | assert(Error.objects.count() == 1) 20 | post.populate({}) 21 | assert(Error.objects.count() == 2) 22 | settings.ANONYMOUS_POSTING = False 23 | -------------------------------------------------------------------------------- /listener/normal/receiving/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^v/1/$', 'receiving.http.post', name="error-post"), 5 | ) 6 | -------------------------------------------------------------------------------- /listener/normal/requirements.txt: -------------------------------------------------------------------------------- 1 | celery 2 | django-celery 3 | 4 | -------------------------------------------------------------------------------- /listener/normal/stats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/stats/__init__.py -------------------------------------------------------------------------------- /listener/normal/stats/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/stats/models.py -------------------------------------------------------------------------------- /listener/normal/stats/signals.py: -------------------------------------------------------------------------------- 1 | import django.dispatch 2 | 3 | stats_completed = django.dispatch.Signal(providing_args=["instance",]) 4 | -------------------------------------------------------------------------------- /listener/normal/stats/templates/stats_subnav.html: -------------------------------------------------------------------------------- 1 |
    2 | {% for key, stat in stats %} 3 | {{ stat.title }} 4 | {% if not forloop.last %}•{% endif %} 5 | {% endfor %} 6 |
    7 | -------------------------------------------------------------------------------- /listener/normal/stats/templates/stats_view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %}{% include "stats_subnav.html" %}{% endblock %} 3 | {% block content %} 4 | {% if stat %} 5 |
    6 |

    {{ stat.title }}

    7 |
    8 |

    From start date {{ start|date:"d M, Y" }} to end date {{ end|date:"d M, Y" }}.

    9 | 10 | 32 |
    33 | {% else %} 34 |

    Click a link, any link. I dare you.

    35 | {% endif %} 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /listener/normal/stats/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime, timedelta 3 | 4 | from django.test import TestCase 5 | from django.test.client import Client 6 | 7 | from django.contrib.auth.models import User 8 | from django.conf import settings 9 | from django.core.exceptions import ValidationError 10 | from django.core.urlresolvers import reverse 11 | from django.db import connection 12 | 13 | from app import tests 14 | from error.models import Error 15 | 16 | def create_error(): 17 | return Error(timestamp=datetime.now(), 18 | timestamp_date=datetime.today()) 19 | 20 | 21 | class StatsTests(TestCase): 22 | # test the view for writing errors 23 | def setUp(self): 24 | settings.ANONYMOUS_ACCESS = True 25 | Error.objects.all().delete() 26 | 27 | def testCount(self): 28 | for x in range(0, 10): 29 | create_error().save() 30 | 31 | for x in range(0, 5): 32 | err = create_error() 33 | err.priority = 4 34 | err.save() 35 | 36 | url = reverse('stats-view', args=['priority']) 37 | res = self.client.get(url) 38 | assert 'data.setValue(0, 1, 10);' in res.content 39 | assert 'data.setValue(0, 2, 5);' in res.content -------------------------------------------------------------------------------- /listener/normal/stats/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^$', 'stats.views.stats_view', name="stats-view"), 5 | url(r'^view/(?P[\w-]+)/$', 'stats.views.stats_view', name="stats-view"), 6 | ) 7 | -------------------------------------------------------------------------------- /listener/normal/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | from django.contrib import admin 3 | admin.autodiscover() 4 | 5 | import os 6 | from django.conf import settings 7 | 8 | urlpatterns = patterns('', 9 | (r'', include('error.urls')), 10 | (r'', include('receiving.urls')), 11 | (r'', include('app.urls')), 12 | (r'', include('users.urls')), 13 | (r'^stats/', include('stats.urls')), 14 | (r'^projects/', include('projects.urls')), 15 | (r'^notification/', include('notifications.urls')), 16 | (r'^admin/', include(admin.site.urls)), 17 | (r'^media/(?P.*)', 'django.views.static.serve', 18 | {'document_root' : os.path.join(settings.ROOT_PATH, '..', 'media')}) 19 | ) 20 | 21 | #handler404 = 'app.errors.not_found_error' 22 | #handler500 = 'app.errors.application_error' 23 | -------------------------------------------------------------------------------- /listener/normal/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/users/__init__.py -------------------------------------------------------------------------------- /listener/normal/users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.models import User 3 | from django.contrib.auth.forms import (AuthenticationForm, UserCreationForm, 4 | SetPasswordForm) 5 | from django.utils.translation import ugettext as _ 6 | 7 | from app.forms import Form, ModelForm 8 | 9 | 10 | class LoginForm(Form, AuthenticationForm): 11 | pass 12 | 13 | 14 | class CreateForm(ModelForm, UserCreationForm): 15 | username = forms.RegexField(max_length=30, regex=r'^[\w.@+-]+$', 16 | error_messages={'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")}) 17 | 18 | class Meta: 19 | model = User 20 | fields = ("username", "first_name", "last_name", "password1", 21 | "password2", "email") 22 | 23 | 24 | class EditForm(ModelForm): 25 | username = forms.CharField(max_length=30) 26 | is_staff = forms.BooleanField(required=False, label=_("Access to Arecibo")) 27 | 28 | class Meta: 29 | model = User 30 | fields = ("username", "first_name", "last_name", "email", "is_staff") 31 | 32 | 33 | class PasswordForm(Form, SetPasswordForm): 34 | pass -------------------------------------------------------------------------------- /listener/normal/users/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/users/models.py -------------------------------------------------------------------------------- /listener/normal/users/templates/user_create.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
    4 |
    5 | {% csrf_token %} 6 | {{ form.as_custom }} 7 |
    8 |
    9 |
    10 |
    11 |

    After your account is created, an admin will need to approve this account.

    12 |
    13 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/users/templates/user_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
    4 |

    URLs and keysUsers

    5 |
    6 | {% endblock %} 7 | {% block content %} 8 |
    9 |
    10 | {% csrf_token %} 11 | {{ form.as_custom }} 12 |
    13 |
    14 |
    15 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/users/templates/user_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
    4 |

    URLs and keysUsers

    5 |
    6 | {% endblock %} 7 | {% block content %} 8 |
    9 | {% if page.object_list %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for object in page.object_list %} 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
    NameEmailAllowed
    {{ object.first_name|default:"" }} {{ object.last_name|default:"" }}{{ object.email }} {% ifequal object.pk user.pk %}(this is you){% endifequal %}{{ object.is_staff|yesno|capfirst }}
    27 |

    It's possible to lock yourself out of this site. 28 | Full user administration, such as deleting users or fixing account passwords 29 | is available via the Django admin interface. 30 |

    31 | {% endif %} 32 |
    33 | {% include "pagination.html" %} 34 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/users/templates/user_login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {% if anonymous_access %} 4 |
    5 |

    Although the site is set to allow anonymous access, without an authenticated 6 | user we don't allow access to manage user accounts.

    7 |
    8 | {% endif %} 9 |
    10 |
    11 | {% csrf_token %} 12 | {{ form.as_custom }} 13 |
    14 |
    15 |
    16 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/users/templates/user_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block subnav %} 3 |
    4 |

    URLs and keysUsers

    5 |
    6 | {% endblock %} 7 | {% block content %} 8 |
    9 |
    10 | {% csrf_token %} 11 | {{ form.as_custom }} 12 |
    13 |
    14 |
    15 | {% endblock %} -------------------------------------------------------------------------------- /listener/normal/users/tests.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymckay/arecibo/eb6787ea0a276047ef5add2df67a4dd051e5c961/listener/normal/users/tests.py -------------------------------------------------------------------------------- /listener/normal/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | url(r'^users/$', 'users.views.user_list', name="user-list"), 5 | url(r'^users/edit/(?P[\w-]+)/$', 'users.views.user_edit', name="user-edit"), 6 | url(r'^users/create/$', 'users.views.user_create', name="user-create"), 7 | url(r'^users/password/$', 'users.views.user_password', name="user-password"), 8 | 9 | url(r'^login/$', 'users.views.login', name="login"), 10 | url(r'^logout/$', 'users.views.logout', name="logout"), 11 | ) 12 | -------------------------------------------------------------------------------- /listener/normal/users/utils.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | 3 | def approved_users(): 4 | return User.objects.filter(is_staff=True) 5 | --------------------------------------------------------------------------------