For this round, I used straight html.
I've moved the form generation to the server-side.
I've added names to match the keys that are being edited.
The design allows any part of the key structure to be edited with urls like
http://localhost:8080/vserver/default
or
http://localhost:8080/vserver/default/directory/$
and the links in the document headers allow more specific edit requests.
I've added a Commit Changes button at the bottom, hitting it does a post
to the same location, and the resulting keys are displayed above the
usual request content.
Some look and feel of the Cherokee Project site has been co-opted.
Index: admin.html
===================================================================
--- admin.html (revision 932)
+++ admin.html (working copy)
@@ -1,15 +1,46 @@
<html>
<head>
+<title>%(title)s</title>
+<link href="http://cherokee-project.com/themes/cherokee/cherokee.css" rel="stylesheet" type="text/css" media="all" />
+<!--
<link rel="stylesheet" type="text/css" href="js/resources/css/ext-all.css" />
<script text="text/javascript" src="js/adapter/ext/ext-base.js"></script>
<script text="text/javascript" src="js/ext-all.js"></script>
<script type="text/javascript" src="admin.js"></script>
-<style>
- .section { padding: 0 0 0 1em; }
-</style>
+-->
</head>
-<body>
+<body id="cherokee_body">
+ <div id="container">
+ <table id="container-table" >
+ <tr>
+ <td id="container-logo" onclick="window.location='/';"> </td>
+ <td id="container-nav">
+ <ul id="nav">
+ <li><a href="/">Configuration Home</a></li>
+ <li><a href="http://cherokee-project.com/">Project</a></li>
+ <li><a href="http://cherokee-project.com/doc">Documentation</a></li>
+ <li><a href="http://cherokee-project.com/branding">Logos</a></li>
+ </ul>
+ </td>
+ </tr>
+ </table>
+ <br />
+
+ <div id="main">
+
+ %(post_info_display)s
+ %(content)s
+
+ <div class="clearfix"></div>
+ <div id="footer">
+ Obviously <a href="http://www.cherokee-project.com/" class="a-pie">powered by Cherokee</a>
+ <a href="http://www.cherokee-project.com/about/" class="a-pie"><b> !</b></a><br/>
+
+ © 2005 Alvaro Lopez Ortega
+ </div>
+ </div> <!-- #main -->
+</div> <!-- #container -->
<div id="vserver-list" style="width: 180px;"></div>
<div id="vserver-options"></div>
</body>
Index: server.py
===================================================================
--- server.py (revision 932)
+++ server.py (working copy)
@@ -2,37 +2,78 @@
from pyscgi.pyscgi import ServerFactory, SCGIHandler
from cherokeeconf import Config
+from urllib import unquote
DEFAULT_PORT = 4000
CONFIG_FILE = "admin_test.conf"
ADMIN_TEMPLATE = "admin.html"
+CONTENT_TYPES = {'js': 'text/javascript', 'css': 'text/css', 'png': 'image/png'}
def strip_slashes (uri):
if uri[0] == '/':
uri = uri[1:]
- if uri[-1] == '/':
+ if uri and uri[-1] == '/':
uri = uri[:-1]
return uri
class MyHandler(SCGIHandler):
def __init__ (self, request, client_address, server):
- SCGIHandler.__init__ (self, request, client_address, server)
+ SCGIHandler.__init__ (self, request, client_address, server)
+ def _content_type(self, request_uri):
+ ext = request_uri.split('.')[-1]
+ try:
+ return CONTENT_TYPES[ext]
+ except:
+ return "text/html"
+
+
def handle_request (self):
- self.wfile.write('Content-Type: text/html\r\n\r\n')
+ """Main Request Method"""
+
self.config = Config(CONFIG_FILE)
-
request_uri = self.env['REQUEST_URI']
+ self.wfile.write('Content-Type: %s\r\n\r\n' % (self._content_type(request_uri)))
-
- if request_uri.startswith("/config"):
- self._config_request(request_uri)
+ # just send through images and scripts
+ ext = request_uri.split('.')[-1]
+ if ext in ('js', 'css', 'png'):
+ content = open(request_uri[1:])
+ self.wfile.write(content.read())
+ content.close()
return
+
+ self._process_post()
+ self._config_request(request_uri)
- # By deafault, it's an admin interface request
- self._admin_request(request_uri)
+ def _process_post(self):
+ """If values are sent via a post, process them
+ Currently, they are simply displayed above the main content
+ """
+
+ self.handle_post()
+
+ if self.post:
+ post_info = {}
+ keys = []
+ for keyvalue in self.post.split("&"):
+ keyvalue = keyvalue.split("=")
+ key, value = unquote(keyvalue[0]), unquote(keyvalue[1])
+ keys.append(key)
+ post_info[key] = value
+
+ keys.sort()
+ view_results = []
+ last_key = keys and keys[0]
+ for key in keys:
+ view_results.append("%s = %s" % (key, post_info[key]))
+
+ self.post_info = post_info
+ self.post_info_display = "\n".join(view_results)
+
+
def _config_request(self, request_uri):
"""Configuration request handler."""
@@ -40,23 +81,27 @@
request_path, request_options = request_uri.split("?")
else:
request_path = request_uri
- request_options = ""
+ request_options = ""
-
request_path = strip_slashes(request_path)
request_options = self._parse_options(request_options)
request_uri_elements = request_path.split("/")
- config_path = request_uri_elements[1:] # Override the /config/ element
+ if request_uri_elements and request_uri_elements[0] == "config":
+ config_path = request_uri_elements[1:] # Override the /config/ element
+ else:
+ config_path = request_uri_elements
config_node = self.config.root
+ paths = []
for path_element in config_path:
path_element = path_element.replace("$", "/")
if not path_element:
continue
+ paths.append(path_element)
config_node = config_node[path_element]
#Special EXT JS request for the treeview
@@ -71,11 +116,18 @@
return
if config_node:
- self._node_as_json(config_node)
+ if len(paths) == 0 or (len(paths) == 1 and paths[0] == 'vserver'):
+ # for server listings... just display a list of server, vserver, etc... or vserver's servers
+ self._node_as_html(config_node, paths, self._node_as_html_server_list)
+ else:
+ # this is a server configuration context... display editable content
+ self._node_as_html(config_node, paths, self._node_as_html_content)
def _admin_request(self, request_uri):
"""Javascript or CSS content request handler."""
- if request_uri.endswith(".js") or request_uri.endswith(".css"):
+
+ ext = request_uri.split('.')[-1]
+ if ext in ('js', 'css', 'png'):
content = open(request_uri[1:])
self.wfile.write(content.read())
content.close()
@@ -121,6 +173,139 @@
self.wfile.write("]")
+ def _node_as_html(self, config_node, paths, content_function):
+ """HTML Presentation of a Config Node"""
+
+ page_info = {}
+ page_info["title"] = "Cherokee Config :: " + " :: ".join(paths)
+ page_info["content"] = content_function(config_node, paths)
+ page_info["post_info_display"] = ""
+ if self.post:
+ page_info["post_info_display"] = "<pre>\n%s\n</pre>" % self.post_info_display
+
+ ui = open(ADMIN_TEMPLATE)
+ admin_template = ui.read()
+ page = admin_template % page_info
+ self.wfile.write(page)
+ ui.close()
+
+ def _paths_request(self, paths):
+ return "/" + "/".join([path.replace("/", "$") for path in paths])
+
+ def _node_as_html_server_list(self, config_node, paths):
+ """HTML list of available servers to configure"""
+
+ content = []
+ parent_path = "/".join(paths) or "Cherokee Configation Site"
+ content.append("<h1>%s</h1>" % parent_path)
+ content.append("<ul>")
+ servers = []
+ for server in config_node:
+ servers.append(server)
+ servers.sort()
+ new_paths = paths[:]
+ new_paths.append("")
+ for server in servers:
+ new_paths[-1] = server
+ content.append("<li><a href=\"%s\">%s</a></li>" % (self._paths_request(new_paths), server))
+ content.append("</ul>")
+ return "\n".join(content)
+
+ def _node_as_html_content(self, config_node, paths):
+ """HTML page of editable entries"""
+
+ content = []
+ content.append("<form method=\"post\" action=\"\">")
+
+ # list out the path to the this server location
+ parent_path = "/".join(paths)
+ if parent_path:
+ content.append("<h1>")
+ for depth, path in enumerate(paths):
+ if depth < len(paths) - 1:
+ content.append("<a href=\"%s\">%s</a>" % (self._paths_request(paths[:depth+1]), path))
+ content.append(" > ")
+ else:
+ content.append(path)
+ content.append("</h1>")
+
+ edit_html = self._node_as_html_edit(config_node, paths)
+ content.append(edit_html)
+
+ content.append("<p>")
+ content.append("<input type=\"submit\" value=\"Commit Changes\" />")
+ content.append("</p>")
+
+ content.append("</form>")
+
+ return "\n".join(content)
+
+ def _node_as_html_edit(self, config_node, paths):
+ """top, recursable call, turning config_nodes into form fields"""
+
+ key_prefix = "!" + "!".join(paths)
+ content = []
+ content.append("<div class=\"section\">")
+
+ simple_keys = []
+ complex_keys = []
+ for key in config_node:
+ has_kids = False
+ for entry in config_node[key]:
+ has_kids = True
+ break
+ if has_kids:
+ complex_keys.append(key)
+ else:
+ simple_keys.append(key)
+
+ simple_keys.sort()
+ for key in simple_keys:
+ content.append(self._to_form_field(key_prefix, key, config_node[key].value))
+
+ complex_keys.sort()
+ for key in complex_keys:
+ content.append(self._to_section(key_prefix, key, config_node[key]))
+
+ content.append("</div>")
+
+ return "\n".join(content)
+
+
+ def _to_form_field(self, key_prefix, key, value):
+ """given key and value, return a div with a label and an imput box"""
+
+ content = []
+ content.append("<div class=\"entry\">")
+ input_box = "<input name=\"%s\" size=\"%s\" value=\"%s\" />" % (key_prefix + "!" + key, len(value), value)
+ content.append("<label>%s %s</label>" % (key, input_box))
+ content.append("</div>")
+ return "\n".join(content)
+
+
+ def _to_section(self, key_prefix, key, config_node):
+ """give a key, config_node, return a header followed (ala _node_as_html_edit) by key/value inputs, then more complex sections
+ """
+
+ content = []
+ paths = key_prefix[1:].split("!")
+ depth = max(1, len(paths) - 2)
+ h_number = min(depth, 6)
+ input_box = ""
+ if config_node.value:
+ input_box = " <input name=\"%s\" size=\"%s\" value=\"%s\" />" % (key_prefix + "!" + key, len(config_node.value), config_node.value)
+ my_path = paths[:]
+ my_path.append(key)
+ link_to_key = "<a href=\"%s\">%s</a>" % (self._paths_request(my_path), key)
+ content.append("<h%s>%s%s</h%s>" % (h_number, link_to_key, input_box, h_number))
+
+ paths.append(key)
+ section_content = self._node_as_html_edit(config_node, paths)
+ content.append(section_content)
+
+ return "\n".join(content)
+
+
def _node_as_json(self, config_node):
"""Recursive JSON formatted response
_______________________________________________
Cherokee mailing list
[email protected]
http://cherokee-project.com/cgi-bin/mailman/listinfo/cherokee