You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ee...@apache.org on 2009/10/07 18:22:08 UTC
svn commit: r822786 - in
/incubator/cassandra/trunk/contrib/cassandra_browser: ./ README cutejson.py
index.py
Author: eevans
Date: Wed Oct 7 16:22:08 2009
New Revision: 822786
URL: http://svn.apache.org/viewvc?rev=822786&view=rev
Log:
CASSANDRA-457 cassandra client with a web-based interface
Patch by Jun Rao, Hernan Badenes, and Michael Greene;
reviewed by eevans for CASSANDRA-457
Added:
incubator/cassandra/trunk/contrib/cassandra_browser/
incubator/cassandra/trunk/contrib/cassandra_browser/README
incubator/cassandra/trunk/contrib/cassandra_browser/cutejson.py (with props)
incubator/cassandra/trunk/contrib/cassandra_browser/index.py (with props)
Added: incubator/cassandra/trunk/contrib/cassandra_browser/README
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/cassandra_browser/README?rev=822786&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/cassandra_browser/README (added)
+++ incubator/cassandra/trunk/contrib/cassandra_browser/README Wed Oct 7 16:22:08 2009
@@ -0,0 +1,139 @@
+
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+## Cassandra Web Browser ########################################
+
+This is a simple but useful web-based browser for Cassandra.
+(http://incubator.apache.org/cassandra/).
+
+It offers the following features:
+ * form-based interface
+ * browse columns in a column family, including pagination of results
+ * all queries are based on URL parameters; so they can be bookmarked
+ in a browser for re-querying later.
+ * view detailed data on any column
+ * certain types of data are annotated:
+ - Timestamps are shown both as decimal and as date/time.
+ - JSON strings are parsed and pretty-printed, with syntax highlighting,
+ including parsing of nested elements (See the included cutejson library)
+
+== Install ================================================================
+
+There are two modes to serve the application:
+ 1) Via apache mod_python
+ 2) Creating a WSGI python server
+
+== 1. Setup via WSGI ==
+
+The only requisite is to generate the cassandra thrift interfaces, and
+have thrift libraries in PYTHONPATH.
+
+Just run the following command:
+$ python index.py
+This will do no output, and the server will be running. Then load this
+URL in your browser: http://localhost:8111/list
+
+Alternatively, you can change the default server's listen address and ports
+with --address and --port command line parameters -- See _index_wsgi function
+in index.py.
+
+== 2. The mod_python way ==
+
+This setup is probably harder but may be useful in a host already running
+an apache server.
+
+You need to setup mod_python to serve the directory where the files of
+this project live:
+ * index.py
+ * cutejson.py
+ * README (this file)
+You also need the following python libraries:
+ * simplejson: http://pypi.python.org/pypi/simplejson/
+ (I used 'json' library originally, but was not installed in
+ bluemino's python 2.4)
+ * thrift: a version compatible with your cassandra code
+ * cassandra: the cassandra thrift interface. This can be generated with
+ "thrift -gen py cassandra.interface", from your cassandra's interface
+ directory.
+
+You need to set a path to find this libraries -- in the same way
+PYTHONPATH is set for the python shell interpreter.
+Using mod_python, this parameter is given in the apache server configuration.
+Two examples follow:
+
+* Setup on Kubuntu 9.04:
+ (Note I point to several directories, where I downloaded, installed
+ or use each library)
+ <Directory /var/www/py>
+ SetHandler mod_python
+ PythonHandler mod_python.publisher
+ PythonDebug On
+ PythonPath "['/usr/lib/python2.6/site-packages', '/home/myuser/dev/Cassandra/interface/gen-py', '/home/myuser/dev/pybrowser' ] + sys.path"
+ PythonOption CassandraHost "example.cassandra.host"
+ PythonOption CassandraPort 9160
+ </Directory>
+
+* Setup on a RedHat 5.x:
+ (Note: I put all three libraries in the same directory there)
+ $ cat /etc/httpd/conf.d/pybrowser.conf
+ <Location /pybrowser>
+ SetHandler python-program
+ PythonHandler mod_python.publisher
+ PythonDebug On
+ PythonPath "['/home/myuser/python-modules'] + sys.path"
+ PythonOption CassandraHost "my.cassandra.host"
+ PythonOption CassandraPort 9160
+ </Location>
+
+Finally, on the directory (e.g. /var/www/py) you are configuring with the
+apache directives shown, you just need to put index.py and cutejson.py.
+Opening the directory's url in a browser should show the index() function from the
+script. (If you see a directory listing, then something is wrong).
+
+= Additional configuration =================================================
+
+ Opening the default URL will load a blank or almost-blank form, with default parameters.
+ If you always use the same cassandra server, for instance, it is useful to configure some
+ default parameters. They can be passed as python options, configured in the same apache
+ configuration section described above (See "Installing"). This is the list of all implemented
+ options:
+
+ Cassandra server: (it defaults to "localhost")
+ PythonOption CassandraHost <yourPreferredHost>
+ Thrift port: (it defaults to 9160)
+ PythonOption CassandraPort <thriftPort>
+ Keyspace, row, columnFamily (they all default to "")
+ PythonOption CassandraKeyspace <defaultKeyspace>
+ PythonOption CassandraRow <defaultRow>
+ PythonOption CassandraColumnFamily <defaultColumnFamily>
+
+== Changelog =============================================================
+
+* 2009.09.06: Added WSGI interface (as alternative)
+* 2009.09.28:
+ - using 'simplejson' only if 'json' lib is not available
+ - fixed remove() usage of old api
+ - using FieldStorage to process forms; cleaner code
+ - configuration of default parameters via PythonOption(s)
+* 2009.09.18: ported to cassandr 0.4rc1. Reworked styling.
+* 2009.08.31: Implemented column deletion.
+* 2009.08.27: Initial version, supports browsing and opening
+ a column for viewing formatted-json.
+
+===========================================================================
+
Added: incubator/cassandra/trunk/contrib/cassandra_browser/cutejson.py
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/cassandra_browser/cutejson.py?rev=822786&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/cassandra_browser/cutejson.py (added)
+++ incubator/cassandra/trunk/contrib/cassandra_browser/cutejson.py Wed Oct 7 16:22:08 2009
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+## Cute, HTML, smart json-interpreted pretty-printer! ;)
+
+## usage: call cutejson(object) on any object, string or whatever; print the resulting HTML code.
+
+try:
+ import json
+except:
+ import simplejson as json
+from datetime import datetime
+
+def _rep(v,nest):
+ s = ""
+ for i in range(0,nest):
+ s += v
+ return s
+
+def cutejson(v, nest=0, metanest=0):
+ t = type(v).__name__;
+ comment = ""
+ prefix = ""
+ postfix = ""
+ s = ""
+ if (t=='unicode' or t=='str'):
+ try:
+ v = json.loads(v)
+ # Cute! Parsed a json string
+ #s = "{<br/>\n%s}" % _mypp(j, nest+2)
+ comment = " ## Parsed contents"
+ metanest += 1
+ prefix = "<span class=\"interpreted metanest%d\">" % metanest
+ postfix = "</span>"
+ except ValueError:
+ # Do nothing
+ comment = ""
+
+ t = type(v).__name__;
+ if (t=='dict'):
+ s = "%s{%s<br/>\n" % (prefix, comment)
+ for key,val in v.iteritems():
+ s += "%s<span class=\"key\">%s</span>: %s<br/>\n" % (_rep(" ",(nest+1)*3), key, cutejson(val,nest+1,metanest))
+ s += "%s}%s" % (_rep(" ",nest*3), postfix)
+ elif (t=='list'):
+ s = "[<br/>\n"
+ sep = " "
+ for val in v:
+ spc = _rep(" ",nest*3) + sep + _rep(" ",2)
+ s += "%s%s<br/>\n" % (spc, cutejson(val,nest+1,metanest))
+ sep = ","
+ s += "%s]" % _rep(" ",nest*3)
+ #"%s<span class=\"key\">%s</span>: %s<br/>\n" % (_rep(" ",(nest+1)*3), key, cutejson(val,nest+1,metanest))
+ else:
+ if (t=='str' or t=='unicode'):
+ prefix = postfix = "\""
+ elif (t=='int' and v>900000000 and v<1300000000):
+ postfix = " <span class=\"interpreted metanest%d\">## Seconds: %s</span>" % (metanest+1, str(datetime.fromtimestamp(v)))
+ elif (t=='long' and v>900000000000 and v<1300000000000):
+ postfix = " <span class=\"interpreted metanest%d\">## Millis: %s</span>" % (metanest+1, str(datetime.fromtimestamp(v/1000)))
+ s = "%s<span class=\"%s\">%s%s%s</span>" % (comment, t, prefix, str(v), postfix)
+ return s
+
+# s = _rep(" ",nest)
+# for k,v in object.iteritems():
+# s += "<span class=\"key\">%s</span>: %s<br/>\n" % (k, _mypp_val(v,nest))
+# return s
+
+def css():
+ return """
+.hint { background-color: #FFFFBB; }
+.key { font-weight: bold; }
+.int, .long { color: green; }
+.float { color: magenta; }
+.bool { color: brown; }
+.unicode, .string { color: navy; }
+.interpreted { background-color: #BBBBBB; }
+.interpreted.metanest1 { background-color: #FFFFBB; }
+.interpreted.metanest2 { background-color: #BBFFBB; }
+.interpreted.metanest3 { background-color: #BBBBFF; }
+"""
+
+
+# Test as main()
+if __name__ == "__main__":
+ __jsonstr = """ {"test":"{\\"json\\":true,\\"str\\":\\"{\\\\\\"a\\\\\\":0}\\"}",
+"id":"00071BEB5F143D30832575D800773A8A"
+,"timestamp":1245275000
+,"last_revision":1245275000
+,"other_timestamp":1245274937123
+,"has_attachments":false,"from":{"email":"some@email.com","lname":"Someone Else"}
+,"to":[{"email":"john@doe.com","name":"John Doe"}]
+,"collections":[{"type":"SystemView","name":"all"},{"type":"SystemView","name":"sent"}]
+,"text":"This is a test string"
+,"type":0
+,"length":123.2
+}
+"""
+ print "<html><head><style>%s</style></head><body><tt>\n%s\n</tt></body>" % (css(), cutejson(json.loads(__jsonstr)))
Propchange: incubator/cassandra/trunk/contrib/cassandra_browser/cutejson.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/cassandra/trunk/contrib/cassandra_browser/index.py
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/cassandra_browser/index.py?rev=822786&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/cassandra_browser/index.py (added)
+++ incubator/cassandra/trunk/contrib/cassandra_browser/index.py Wed Oct 7 16:22:08 2009
@@ -0,0 +1,485 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+## Cassandra Web Browser
+
+import sys
+try:
+ from mod_python import util # FieldStorage form processing
+except:
+ pass
+try: # Choice of json parser
+ import json
+except:
+ import simplejson as json
+import pprint # JSON printer
+import cutejson # My own JSON printer - cute!
+try:
+ sys.path.append('../../interface/gen-py')
+except:
+ pass
+from cassandra import Cassandra # Cassandra thrift API
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+from cgi import escape, FieldStorage
+from datetime import datetime # Most timestamp handling
+import time # clock(), time()
+from copy import copy # Cloning objects
+import urllib # URL-encoding form parameters
+
+
+### Action handlers - Cassandra ###########################################################################################
+
+def _cassandraConnect(params):
+ # Make socket
+ transport = TSocket.TSocket(params['host'], params['port'])
+
+ # Buffering is critical. Raw sockets are very slow
+ transport = TTransport.TBufferedTransport(transport)
+
+ # Wrap in a protocol
+ protocol = TBinaryProtocol.TBinaryProtocol(transport)
+
+ # Create a client to use the protocol encoder
+ #lient = Calculator.Client(protocol)
+ client = Cassandra.Client(protocol)
+
+ # Connect!
+ transport.open()
+
+ return {'client':client, 'transport':transport }
+
+def _cassandraGetSliceFrom(params):
+ try:
+ cassandra = _cassandraConnect(params)
+ s = ""
+
+ range = Cassandra.SliceRange(params['search'],params['searchend'],params['descending'],params['count']+params['offset'])
+ predicate = Cassandra.SlicePredicate(None, range)
+ parent = Cassandra.ColumnParent(params['cf'], None) # No supercolumn, we are not using them ATM
+
+ result = cassandra['client'].get_slice(params['table'], params['row'], parent, predicate, 1)
+
+ ## Close!
+ cassandra['transport'].close()
+
+ n = len(result)-params['offset']
+ if (n<0):
+ n = 0
+ s += "Obtained: "+str(n)+" columns (click on column data to open)"
+ s += ' <span style="color:gray">- query predicate: '+str(predicate) + "</span><br>\n"
+ s += "%s<table class=\"main\">" % _navigationButtonsHtml(params)
+ strcol = """\
+<tr class=\"%s\">
+ <td class="rownum">%d</td>
+ <td><tt>%s</tt></td>
+ <td class=\"link\"><a href=\"%s\"><tt>%s</tt></a></td>
+ <td>%s<br/><span class="parsedts">%s</span></td>
+ <td><a title="Delete column" href=\"#\" onclick=\"javascript:if(confirm('Are you sure you want to delete this column?'))window.open('%s')\">×</a></td>
+</tr>
+"""
+ i=0
+ deleteParams = {\
+ "host":params["host"],\
+ "port":params["port"],\
+ "table":params["table"],\
+ "row":params["row"],\
+ "cf":params["cf"]\
+ }
+ clazz = "even"
+ for csp in result:
+ col = csp.column # Assuming we are only querying columns - need to change this for supercolumns?
+ s+="<!-- a column -- >\n"
+ if i>=params['offset']:
+ targetmap = copy(params)
+ targetmap['open'] = col.name
+ target = "view?%s" % urllib.urlencode(targetmap)
+ deleteParams['columnName'] = col.name
+ svalue = col.value
+ if len(svalue)>120:
+ svalue = escape(svalue[0:120])+_htmlEllipsis()
+ else:
+ svalue = escape(svalue)
+ s += strcol % (clazz, i, col.name, target, svalue, str(col.timestamp), _cassandraTimestampToDatetime(col.timestamp), "delete?"+urllib.urlencode(deleteParams))
+ if clazz=="odd":
+ clazz = "even"
+ else:
+ clazz = "odd"
+ i=i+1
+
+ s += "</table>\n"
+
+ s += _navigationButtonsHtml(params)
+
+ return s
+
+ except Thrift.TException, tx:
+ print '%s' % (tx.message)
+ return 'TException: %s' % (tx.message)
+
+ except Cassandra.InvalidRequestException, ire:
+ print '%s' % (ire.why)
+ return 'InvalidRequestException: %s' % (ire.why)
+
+def _cassandraGetColumn(params):
+ try:
+ cassandra = _cassandraConnect(params)
+ pp = pprint.PrettyPrinter(indent = 2)
+
+ result = cassandra['client'].get(params['table'], params['row'], Cassandra.ColumnPath(params["cf"], None, params['open']), 1)
+ col = result.column
+
+ # Close!
+ cassandra['transport'].close()
+ s = ""
+
+ # Headers
+ s += "Column: %s<br/>\nTimestamp: %s - <span class=\"parsedts\">%s</span>" % \
+ (col.name, col.timestamp, _cassandraTimestampToDatetime(col.timestamp))
+
+ jsonObj = None
+ error = ""
+ try:
+ jsonObj = json.loads(col.value)
+ except ValueError, exc:
+ error = str(exc)
+
+ # Colorful pretty printed
+ if jsonObj!=None:
+ s += "<h3>Data is a json object!</h3>\n<div class=\"columnData\"><tt>\n"+cutejson.cutejson(jsonObj)+"\n</tt></div>"
+ else:
+ s += "<br/><br/>(Not a valid json string) - "+str(error)
+
+ # Plain data
+ s += "<h3>Data</h3><div class=\"columnData\">%s</div><br/>" % escape(col.value)
+
+ if jsonObj!=None:
+ s += "<h3>By Python prettyprinter:</h3><div class=\"columnData\"><pre>%s</pre></div>" % json.dumps(json.loads(col.value), indent=4, sort_keys=True)
+# s += "Formatted json:<div class=\"columnData\"><pre>%s</pre></div>" % pp.pformat(json.loads(result.value))
+
+ return s
+
+ except Thrift.TException, tx:
+ print '%s' % (tx.message)
+ return 'Thrift Error: %s' % (tx.message)
+
+ except Cassandra.InvalidRequestException, ire:
+ print '%s' % (ire.message)
+ return 'Invalid Request Error: %s' % (ire.message)
+
+def _cassandraDeleteColumn(params):
+ try:
+ cassandra = _cassandraConnect(params)
+ cfcol = Cassandra.ColumnPath(params["cf"],None,params['columnName'])
+ cassandra['client'].remove(params['table'], params['row'], cfcol, _nowAsCassandraTimestamp(), 1)
+ cassandra['transport'].close()
+
+ s = "Column deleted. (You can delete this window, and refresh the launcher window contents)<ul><li>table: %s</li><li>Row: %s</li><li>CF:Col: %s</li>" %\
+ (params['table'], params['row'], cfcol)
+
+ return s
+ except Thrift.TException, tx:
+ print '%s' % (tx.message)
+ return 'Thrift Error: %s' % (tx.message)
+
+def _cassandraInsert(params, colName, columnValue):
+ try:
+
+ cassandra = _cassandraConnect(params)
+ cassandra['client'].insert(params['table'], params['row'], Cassandra.ColumnPath(params["cf"],None,colName), columnValue, _nowAsCassandraTimestamp(), 1)
+ cassandra['transport'].close()
+
+ return "Column '%s' inserted" % escape(colName)
+
+ except Thrift.TException, tx:
+ print '%s' % (tx.message)
+ return 'Error inserting column - Thrift Error: %s' % (tx.message)
+
+ except Cassandra.InvalidRequestException, ire:
+ print '%s' % (ire.message)
+ return 'Error inserting column - Invalid Request Error: %s' % (ire.message)
+
+
+### Request handlers ###########################################################################################
+
+def index(req):
+ # Defaults to "list":
+ return list(req)
+
+def list(req):
+ params = _processForm(req)
+ return _list(params)
+
+def _list(params):
+ t0 = time.clock()
+ if params['action']=="insert":
+ if (params['newname']!=None and params['value']!=None):
+ message="<div class=\"message\">%s</div>" % _cassandraInsert(params,params['newname'],params['value'])
+ ## Change params so that we browse on the inserted column
+ params['search'] = params['newname']
+ params['searchend'] = ''
+ params['descending'] = 0
+ params['offset'] = 0
+ else:
+ message="<div class=\"message\">%s</div>" % "Please specify a column name and a column value! (Press your browser's Back button)"
+ else:
+ message=""
+ paramsTable = _formatParams(params)
+ cassandraData = _cassandraGetSliceFrom(params);
+ t1 = time.clock()
+ return _mainHtml() % (params['host'], css(), paramsTable+message+cassandraData+_htmlFooter(t1-t0))
+
+def view(req):
+ params = _processForm(req)
+ return _view(params)
+
+def _view(params):
+ t0 = time.clock()
+ paramsTable = _formatParams(params)
+ if params['open']==None:
+ colstr = "<b>No column specified!</b>"
+ else:
+ colstr = _cassandraGetColumn(params);
+ t1 = time.clock()
+ return _mainHtml() % (params['host'], css(), paramsTable+colstr+_htmlFooter(t1-t0))
+
+def delete(req):
+ params = _processForm(req)
+ return _delete(params)
+
+def _delete(params):
+ t0 = time.clock()
+ paramsTable = _formatParams(params)
+ if params['columnName']==None:
+ colstr = "<b>No column specified!</b>"
+ else:
+ colstr = _cassandraDeleteColumn(params)
+ t1 = time.clock()
+ return _mainHtml() % (params['host'], css(), paramsTable+colstr+_htmlFooter(t1-t0))
+
+
+### HTML formatting functions ###########################################################################################
+
+def _formatParams(params):
+ s = "<form action=\"list\">"
+ s += "<div>"
+ s += "Host: <input size=\"30\" name=\"host\" value=\"%s\" title=\"%s\"></input>:\n" % (params['host'] , "Server address")
+ s += "<input size=\"4\" name=\"port\" value=\"%s\" title=\"%s\"></input>, \n" % (params['port'] , "Thrift port")
+ s += "Keyspace: <input size=\"10\" name=\"table\" value=\"%s\" title=\"%s\"></input>, \n" % (params['table'] , "")
+ s += "Row: <input size=\"60\" name=\"row\" value=\"%s\" title=\"%s\"></input>, \n" % (params['row'] , "")
+ s += "CF: <input size=\"30\" name=\"cf\" value=\"%s\" title=\"%s\"></input>\n" % (params['cf'] , "")
+ s += "</div><div class=\"onNotInserting\">"
+ s += "start value: <input size=\"60\" name=\"search\" value=\"%s\" title=\"%s\"></input>, \n" % (params['search'] , "Start value for the search (optional)")
+ s += "end value: <input size=\"60\" name=\"searchend\" value=\"%s\" title=\"%s\"></input>, \n" % (params['searchend'], "End value for the search (optional)")
+ s += "offset: <input size=\"4\" name=\"offset\" value=\"%s\" title=\"%s\"></input>, \n" % \
+ (params['offset'] , "Offset - This is used to allow paging in the web UI. Note that offset+count elements will be queried to cassandra; a big number impacts performance!")
+ s += "count: <input size=\"4\" name=\"count\" value=\"%s\" title=\"%s\"></input>\n" % (params['count'] , "")
+ if params['descending']==1:
+ checked = "checked=\"true\""
+ else:
+ checked = ""
+ s += " - <input type=\"checkbox\" name=\"descending\" value=\"1\" %s>Reversed</input><br/>\n" % checked
+ s += "</div>"
+ s += "<input id=\"querybutton\" class=\"submit onNotInserting\" type=\"submit\"/>\n"
+ s += """\
+ <a class="onNotInserting inserthelp" onclick="javascript:document.getElementsByTagName('body')[0].className = 'inserting';" href="#">Insert column</a>
+<div class="onInserting">
+<div>
+ Name: <input size=\"60\" name=\"newname\" value=\"\" title=\"Name for the new column\"></input>
+</div>
+<div>
+ Value:
+ <textarea rows="20" cols="60" name="value" title="Value of the new column"></textarea>
+</div>
+<input id="insertbutton" class="submit" type="submit" value="insert" name="action" title="Insert the column on the keyspace, row and columnfamily specified above."/>
+ <a class="" onclick="javascript:document.getElementsByTagName('body')[0].className = '';" href="#">Cancel</a>
+</div>
+"""
+ s += "</form>"
+ return s
+
+
+def _htmlEllipsis():
+ return " <span style=\"color:red\">(…)</span>"
+
+def _htmlFooter(ts=0):
+ return "<center style=\"color:gray\">Rendered in %fms - (c)Copyright Apache Software Foundation, 2009</center>" \
+ % ts
+
+def css():
+ return cutejson.css()+"""\
+body { font-family: Sans-serif; font-size: 8pt; }
+td { font-size: 8pt; }
+pre { margin: 0; }
+table { border: 1px solid gray; }
+table.main td.link { cursor: pointer; }
+table.main tr.odd { background: #f8f8f8; }
+table.main td.link:hover { background: #ffffcc; }
+table.main td { padding: 1px 5px; }
+table.main td a, table.maintd a:visited { text-decoration: none; color: black; }
+a, a:visited { color: #0000df; }
+div.columnData { overflow: auto; margin: 10px 0px; width: 90%; padding: 5px; border: 1px solid gray; font-family: monospace; }
+.rownum, .parsedts { color: gray; }
+.navbar { margin: 6px 0; font-size: 110%; }
+.navbar a { text-decoration: none; padding: 1px; }
+.navbar a:hover { color: white; background: #0000df;}
+input, textarea { background: #f8f8f8; border: 1px solid gray; padding: 2px; }
+input.submit { font-weight: bold; background: #FFFFBB; cursor: pointer; }
+/*#querybutton { vertical-align: top; margin-top: 4px; }
+#insertbutton, .inserthelp { vertical-align: bottom; margin-bottom: 4px; }*/
+textarea { vertical-align: baseline; }
+div.message { padding: 4px; border: 1px solid blue; background: #f8f8f8; width: 90%; color: blue; margin: 8px 0px; }
+
+.onInserting { display:none }
+body.inserting div.onInserting { display: block; }
+body.inserting span.onInserting { display: inline; }
+body.inserting .onNotInserting { display: none; }
+"""
+
+def _navigationButtonsHtml(params):
+ s = "<div class=\"navbar\">"
+ paramsFst = copy(params)
+ paramsFst['offset'] = 0
+
+ s += "<a href=\"list?%s\">⇐ ⇐ First</a> " % urllib.urlencode(paramsFst)
+ s += " • "
+
+ if params['offset']>0:
+ paramsPrev = copy(params)
+ paramsPrev['offset'] = paramsPrev['offset'] - paramsPrev['count']
+ if paramsPrev['offset']<0:
+ paramsPrev['offset'] = 0
+ s += "<a href=list?%s>⇐ Previous</a> " % urllib.urlencode(paramsPrev)
+ else:
+ s += "⇐ Previous"
+ s += " • "
+
+ s += "<a href=list?%s>Refresh</a> " % urllib.urlencode(params)
+ s += " • "
+
+ paramsNext = copy(params)
+ paramsNext['offset'] = paramsNext['offset'] + paramsNext['count']
+ s += "<a href=list?%s>⇒ Next</a> " % urllib.urlencode(paramsNext)
+ s += "</div>"
+ return s
+
+def _mainHtml():
+ return """\
+<html>
+<title>Cassandra on %s</title>
+<head><style>%s</style></head>
+<body>
+<h2>Cassandra browser</h2>
+%s
+</body></html>
+"""
+
+### Misc functions ###########################################################################################
+
+def _processForm(req):
+ form = util.FieldStorage(req)
+ pyopts = req.get_options()
+
+ defhost = "localhost"
+ defport = 9160
+ deftable = "Keyspace"
+ defrow = ""
+ defcf = ""
+ # ugly conditionals, 2.5 compatible
+ if "CassandraKeyspace" in pyopts:
+ deftable = pyopts["CassandraKeyspace"]
+ if "CassandraHost" in pyopts:
+ defhost = pyopts["CassandraHost"]
+ if "CassandraPort" in pyopts:
+ defport = pyopts["CassandraPort"]
+ if "CassandraRow" in pyopts:
+ defrow = pyopts["CassandraRow"]
+ if "CassandraColumnFamily" in pyopts:
+ defcf = pyopts["CassandraColumnFamily"]
+
+ return getRequiredParameters(form, defhost, defport, deftable, defrow, defcf)
+
+def getRequiredParameters(form, defhost, defport, deftable, defrow, defcf):
+ params = {}
+
+ # Generic params, used in most actions
+ params['table'] = str(form.getfirst('table', deftable))
+ params['host'] = str(form.getfirst('host', defhost))
+ params['port'] = int(form.getfirst('port', defport))
+
+ params['row'] = str(form.getfirst('row', defrow))
+ params['cf'] = str(form.getfirst('cf', defcf))
+
+ params['search'] = str(form.getfirst('search', ""))
+ params['searchend'] = str(form.getfirst('searchend', ""))
+
+ params['offset'] = int(form.getfirst('offset', 0))
+ params['count'] = int(form.getfirst('count', 20))
+ params['descending'] = int(form.getfirst('descending', 0))
+
+ # For column inserter
+ params['value'] = form.getfirst('value', None)
+ params['action'] = form.getfirst('action', None)
+ params['newname'] = form.getfirst('newname', None)
+ # For column viewer
+ params['open'] = form.getfirst('open', None)
+ params['columnName'] = form.getfirst('columnName', None) # For deletion -- TODO: unify this with other names!
+ return params
+
+def _cassandraTimestampToDatetime(colTimestamp):
+ return datetime.fromtimestamp(colTimestamp/1000)
+
+def _nowAsCassandraTimestamp():
+ return long(time.time()*1000)
+
+def _index_wsgi(environ, start_response):
+ path = environ.get('PATH_INFO', '')
+ found_path = None
+ for path_option in ['list', 'view', 'delete']:
+ if path_option in path:
+ found_path = path_option
+ break
+ if not found_path:
+ start_response('404 NOT FOUND', [('Content-Type', 'text/html')])
+ return 'Path must be list, view, or delete'
+ start_response('200 OK', [('Content-Type', 'text/html')])
+ form = FieldStorage(fp=environ['wsgi.input'], environ=environ)
+ params = getRequiredParameters(form, 'localhost', 9160, 'Keyspace1', '', 'Standard1')
+ try:
+ retval = globals()['_' + found_path](params)
+ except KeyError, e:
+ return ['Request missing parameter(s):' + str(e)]
+ return [retval]
+
+if __name__ == "__main__":
+ from wsgiref.simple_server import make_server
+ from optparse import OptionParser
+ parser = OptionParser()
+ parser.add_option("-a", "--address", dest="host",
+ help="Host address on which to listen", default="localhost")
+ parser.add_option("-p", "--port", dest="port",
+ help="Port on which to listen", default="8111")
+ (options, args) = parser.parse_args()
+
+ srv = make_server(options.host, int(options.port), _index_wsgi)
+ srv.serve_forever()
Propchange: incubator/cassandra/trunk/contrib/cassandra_browser/index.py
------------------------------------------------------------------------------
svn:eol-style = native