Commit 25fc7b8d authored by Serge S. Koval's avatar Serge S. Koval

Static files were rearranged, redis-cli prototype

parent 2a694c35
from flask import Flask
from redis import Redis
from flask.ext import admin
from flask.ext.admin.contrib import rediscli
# Create flask app
app = Flask(__name__)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
if __name__ == '__main__':
# Create admin interface
admin = admin.Admin(app)
admin.add_view(rediscli.RedisCli(Redis()))
# Start app
app.run(debug=True)
......@@ -13,7 +13,7 @@
import sys
PY2 = sys.version_info[0] == 2
VER = sys.version_info
if not PY2:
text_type = str
......
import logging
import shlex
import warnings
from flask import request
from flask.json import jsonify
from jinja2 import Markup
from flask.ext.admin.base import BaseView, expose
from flask.ext.admin.babel import gettext
from flask.ext.admin._compat import VER
class RedisCli(BaseView):
shlex_check = True
"""
shlex from stdlib does not work with unicode on 2.7.2 and lower.
If you want to suppress warning, set this attribute to False.
"""
remapped_commands = {
'del': 'delete'
}
"""
List of redis remapped commands.
"""
def __init__(self, redis,
name=None, category=None, endpoint=None, url=None):
"""
Constructor.
:param redis:
Redis connection
:param name:
View name. If not provided, will use the model class name
:param category:
View category
:param endpoint:
Base endpoint. If not provided, will use the model name + 'view'.
For example if model name was 'User', endpoint will be
'userview'
:param url:
Base URL. If not provided, will use endpoint as a URL.
"""
super(RedisCli, self).__init__(name, category, endpoint, url)
self.redis = redis
self.commands = {}
self._inspect_commands()
if self.shlex_check and VER < (2, 7, 3):
warnings.warn('Warning: rediscli uses shlex library and it does not work with unicode until Python 2.7.3. ' +
'To remove this warning, upgrade to Python 2.7.3 or suppress it by setting shlex_check attribute ' +
'to False.')
def _inspect_commands(self):
for name in dir(self.redis):
if not name.startswith('_'):
attr = getattr(self.redis, name)
if callable(attr):
doc = (getattr(attr, '__doc__', '') or '').strip()
self.commands[name] = (attr, doc)
def _execute_command(self, name, args):
# Do some remapping
new_cmd = self.remapped_commands.get(name)
if new_cmd:
name = new_cmd
# Execute command
if name not in self.commands:
return self._error(gettext('Cli: Invalid command.'))
handler, _ = self.commands[name]
return self._result(handler(*args))
def _parse_cmd(self, cmd):
if VER < (2, 7, 3):
# shlex can't work with unicode until 2.7.3
return tuple(x.decode('utf-8') for x in shlex.split(cmd.encode('utf-8')))
return tuple(shlex.split(cmd))
def _error(self, msg):
return Markup('<div class="error">%s</div>' % msg)
def _result(self, result):
return self.render('admin/rediscli/response.html',
type_name=lambda d: type(d).__name__,
result=result)
@expose('/')
def console_view(self):
return self.render('admin/rediscli/console.html')
@expose('/run/', methods=('POST',))
def execute_view(self):
try:
cmd = request.form.get('cmd').lower()
if not cmd:
return self._error('Cli: Empty command.')
parts = self._parse_cmd(cmd)
if not parts:
return self._error('Cli: Failed to parse command.')
return self._execute_command(parts[0], parts[1:])
except Exception as ex:
logging.exception(ex)
return self._error('Cli: %s' % ex)
.console {
position: relative;
width: 100%;
min-height: 400px;
}
.console-container {
border-radius: 4px;
position: absolute;
border: 1px solid #d4d4d4;
padding: 2px;
overflow: scroll;
top: 2px;
left: 2px;
right: 2px;
bottom: 5em;
}
.console-line {
position: absolute;
left: 2px;
right: 2px;
bottom: 2px;
}
.console-line input {
width: 100%;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
height: 2em;
}
.console .cmd {
background-color: #f5f5f5;
}
.console .response {
background-color: #f0f0f0;
}
.console .error {
color: red;
}
\ No newline at end of file
var RedisCli = function(postUrl) {
var $con = $('.console');
var $container = $con.find('.console-container');
var $input = $con.find('input');
function resizeConsole() {
var height = $(window).height();
var offset = $con.offset();
$con.height(height - offset.top);
}
function scrollBottom() {
$container.animate({scrollTop: $container.height()}, 100);
}
function createEntry(cmd) {
var $entry = $('<div>').addClass('entry').appendTo($container);
$entry.append($('<div>').addClass('cmd').html(cmd));
scrollBottom();
return $entry;
}
function addResponse($entry, response) {
$entry.append($('<div>').addClass('response').html(response));
scrollBottom();
}
function submitCommand() {
var val = $input.val().trim();
if (!val.length)
return false;
var $entry = createEntry('> ' + val);
$.ajax({
type: 'POST',
url: postUrl,
data: {'cmd': val},
success: function(response) {
addResponse($entry, response);
},
error: function() {
addResponse($entry, 'Failed to communicate with server.');
}
});
$input.val('');
return false;
}
// Setup
$con.find('form').submit(submitCommand);
$(window).resize(resizeConsole);
resizeConsole();
$input.focus();
};
......@@ -11,7 +11,7 @@
{% block head_css %}
<link href="{{ url_for('admin.static', filename='bootstrap/css/bootstrap.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='bootstrap/css/bootstrap-responsive.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/admin.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='admin/css/admin.css') }}" rel="stylesheet">
{% endblock %}
{% block head %}
{% endblock %}
......@@ -40,7 +40,7 @@
{% block body %}{% endblock %}
</div>
{% endblock %}
<script src="{{ url_for('admin.static', filename='js/jquery-1.8.3.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='vendor/jquery-1.8.3.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='select2/select2.min.js') }}" type="text/javascript"></script>
......
......@@ -8,7 +8,7 @@
{% block head %}
{{ super() }}
<link href="{{ url_for('admin.static', filename='select2/select2.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='datepicker/bootstrap-datepicker.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
......@@ -29,6 +29,6 @@
{% block tail %}
{{ super() }}
<script src="{{ url_for('admin.static', filename='js/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='js/form.js') }}"></script>
<script src="{{ url_for('admin.static', filename='datepicker/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='admin/js/form.js') }}"></script>
{% endblock %}
......@@ -4,7 +4,7 @@
{% block head %}
{{ super() }}
<link href="{{ url_for('admin.static', filename='select2/select2.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='datepicker/bootstrap-datepicker.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
......@@ -16,6 +16,6 @@
{% block tail %}
{{ super() }}
<script src="{{ url_for('admin.static', filename='js/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='js/form.js') }}"></script>
<script src="{{ url_for('admin.static', filename='datepicker/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='admin/js/form.js') }}"></script>
{% endblock %}
......@@ -6,7 +6,7 @@
{% block head %}
{{ super() }}
<link href="{{ url_for('admin.static', filename='select2/select2.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='datepicker/bootstrap-datepicker.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
......@@ -129,9 +129,9 @@
{% block tail %}
{{ super() }}
<script src="{{ url_for('admin.static', filename='js/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='js/form.js') }}"></script>
<script src="{{ url_for('admin.static', filename='js/filters.js') }}"></script>
<script src="{{ url_for('admin.static', filename='datepicker/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='admin/js/form.js') }}"></script>
<script src="{{ url_for('admin.static', filename='admin/js/filters.js') }}"></script>
{{ actionlib.script(_gettext('Please select at least one model.'),
actions,
......
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib with context %}
{% block head %}
{{ super() }}
<link href="{{ url_for('admin.static', filename='admin/css/rediscli.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
<div class="console">
<div class="console-container">
</div>
<div class="console-line">
<form action="#">
<input type="text"></input>
</form>
</div>
</div>
{% endblock %}
{% block tail %}
{{ super() }}
<script src="{{ url_for('admin.static', filename='admin/js/rediscli.js') }}"></script>
<script language="javascript">
$(function() {
var redisCli = new RedisCli({{ url_for('.execute_view')|tojson }});
});
</script>
{% endblock %}
\ No newline at end of file
{% macro render(item, depth=0) %}
{% set type = type_name(item) %}
{% if type == 'tuple' or type == 'list' %}
{% if not item %}
Empty {{ type }}.
{% else %}
{% for n in item %}
{{ loop.index }}) {{ render(n, depth + 1) }}<br/>
{% endfor %}
{% endif %}
{% elif type == 'bool' %}
{% if depth == 0 and item %}
OK
{% else %}
<span class="type-bool">{{ item }}</span>
{% endif %}
{% elif type == 'str' or type == 'unicode' %}
"{{ item }}"
{% elif type == 'binary' %}
"{{ item.decode('utf-8') }}"
{% else %}
{{ item }}
{% endif %}
{% endmacro %}
{{ render(result) }}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment