Merge cherrypy-rebased branch of invirt-web into trunk. 0.1.0
authorEvan Broder <broder@mit.edu>
Mon, 21 Dec 2009 04:27:10 +0000 (23:27 -0500)
committerEvan Broder <broder@mit.edu>
Mon, 21 Dec 2009 04:27:10 +0000 (23:27 -0500)
svn path=/trunk/packages/invirt-web/; revision=2737

43 files changed:
code/Makefile
code/auth.fcgi [new symlink]
code/controls.py
code/dev.conf [new file with mode: 0644]
code/getafsgroups.py
code/invirt.fcgi [new file with mode: 0755]
code/main.conf [new file with mode: 0644]
code/main.fcgi [deleted file]
code/main.py
code/static/power_installing.png [new file with mode: 0644]
code/static/style.css
code/templates/Makefile [deleted file]
code/templates/__init__.py [deleted file]
code/templates/command.mako [new file with mode: 0644]
code/templates/command.tmpl [deleted file]
code/templates/create.tmpl [deleted file]
code/templates/error.mako [moved from code/templates/error.tmpl with 57% similarity]
code/templates/error_raw.mako [new file with mode: 0644]
code/templates/error_raw.tmpl [deleted file]
code/templates/functions.mako [new file with mode: 0644]
code/templates/functions.tmpl [deleted file]
code/templates/helloworld.mako [new file with mode: 0644]
code/templates/help.mako [new file with mode: 0644]
code/templates/help.tmpl [deleted file]
code/templates/info.mako [new file with mode: 0644]
code/templates/info.tmpl [deleted file]
code/templates/invalid.mako [new file with mode: 0644]
code/templates/invalid.tmpl [deleted file]
code/templates/list.mako [new file with mode: 0644]
code/templates/list.tmpl [deleted file]
code/templates/skeleton.mako [new file with mode: 0644]
code/templates/skeleton.tmpl [deleted file]
code/templates/unauth.mako [moved from code/templates/unauth.tmpl with 90% similarity]
code/templates/vnc.mako [new file with mode: 0644]
code/templates/vnc.tmpl [deleted file]
code/unauth.fcgi [new symlink]
code/validation.py [changed mode: 0644->0755]
code/view.py [new file with mode: 0644]
debian/changelog
debian/control
debian/copyright
files/etc/apache2/sites-available/default.mako
files/etc/apache2/sites-available/ssl.mako

index b114e86..c406438 100644 (file)
@@ -1,6 +1,4 @@
-DIRS = templates
-
-all: kill chmod compile
+all: kill chmod
 
 chmod:
        chgrp -R invirt . 2>/dev/null || true
@@ -8,13 +6,3 @@ chmod:
 
 kill:
        -pkill main.fcgi
-
-compile:
-       for dir in $(DIRS); do \
-               (cd $$dir; $(MAKE) all); \
-       done
-
-clean:
-       for dir in $(DIRS); do \
-               (cd $$dir; $(MAKE) clean); \
-       done
diff --git a/code/auth.fcgi b/code/auth.fcgi
new file mode 120000 (symlink)
index 0000000..b94e858
--- /dev/null
@@ -0,0 +1 @@
+invirt.fcgi
\ No newline at end of file
index e32b1c2..bbc9f8c 100644 (file)
@@ -204,17 +204,16 @@ def deleteVM(machine):
         session.rollback()
         raise
 
-def commandResult(username, state, fields):
+def commandResult(username, state, command_name, machine_id, fields):
     start_time = 0
-    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
-    action = fields.getfirst('action')
-    cdrom = fields.getfirst('cdrom')
+    machine = validation.Validate(username, state, machine_id=machine_id).machine
+    action = command_name
+    cdrom = fields.get('cdrom') or None
     if cdrom is not None and not CDROM.query().filter_by(cdrom_id=cdrom).one():
         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
-    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 
-                      'Delete VM'):
+    if action not in "reboot create destroy shutdown delete".split(" "):
         raise CodeError("Invalid action '%s'" % action)
-    if action == 'Reboot':
+    if action == 'reboot':
         if cdrom is not None:
             out, err = remctl('control', machine.name, 'reboot', cdrom,
                               err=True)
@@ -230,13 +229,13 @@ def commandResult(username, state, fields):
                 print >> sys.stderr, err
                 raise CodeError('ERROR on remctl')
                 
-    elif action == 'Power on':
+    elif action == 'create':
         if validation.maxMemory(username, state, machine) < machine.memory:
             raise InvalidInput('action', 'Power on',
                                "You don't have enough free RAM quota "
                                "to turn on this machine.")
         bootMachine(machine, cdrom)
-    elif action == 'Power off':
+    elif action == 'destroy':
         out, err = remctl('control', machine.name, 'destroy', err=True)
         if err:
             if re.match("machine '.*' is not on", err):
@@ -246,7 +245,7 @@ def commandResult(username, state, fields):
                 print >> sys.stderr, 'Error on power off:'
                 print >> sys.stderr, err
                 raise CodeError('ERROR on remctl')
-    elif action == 'Shutdown':
+    elif action == 'shutdown':
         out, err = remctl('control', machine.name, 'shutdown', err=True)
         if err:
             if re.match("machine '.*' is not on", err):
@@ -256,7 +255,7 @@ def commandResult(username, state, fields):
                 print >> sys.stderr, 'Error on Shutdown:'
                 print >> sys.stderr, err
                 raise CodeError('ERROR on remctl')
-    elif action == 'Delete VM':
+    elif action == 'delete':
         deleteVM(machine)
 
     d = dict(user=username,
diff --git a/code/dev.conf b/code/dev.conf
new file mode 100644 (file)
index 0000000..e1c9ddb
--- /dev/null
@@ -0,0 +1,11 @@
+# For the testing site.
+
+[global]
+server.socket_port = 8080
+log.access_file = "/tmp/invirt-web-access_log.dev"
+log.error_file = "/tmp/invirt-web-error_log.dev"
+tools.mako.module_directory = "/tmp/invirt-web-templatecache.dev"
+tools.basic_auth.on = True
+tools.basic_auth.realm = 'Invirt Web DEVELOPMENT'
+tools.basic_auth.users = {'quentin': 'quentin','broder': 'broder'}
+tools.basic_auth.encrypt = __builtin__.str
index fed3a6b..1c6b82e 100755 (executable)
@@ -33,7 +33,8 @@ def getAfsGroupMembers(group, cell):
     for c in config.authz.afs.cells:
         if c.cell == cell and hasattr(c, 'auth'):
             encrypt = c.auth
-    subprocess.check_call(['aklog', cell], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    if encrypt:
+        subprocess.check_call(['aklog', cell], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     p = subprocess.Popen(["pts", "membership", "-encrypt" if encrypt else '-noauth', group, '-c', cell],
                          stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     err = p.stderr.read()
diff --git a/code/invirt.fcgi b/code/invirt.fcgi
new file mode 100755 (executable)
index 0000000..c01eb7f
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+"""Main FastCGI entry point for web interface"""
+
+import cherrypy
+import os
+import sys
+
+import main
+
+dev = False
+base_dir = os.path.dirname(__file__)
+
+def usage():
+    print >>sys.stderr, """%s [config]
+
+Run server as FastCGI, with CherryPy config from "main.conf".
+
+With `config`, run standalone with CherryPy config from `config`.
+
+Run this script as either 'auth.fcgi' or 'unauth.fcgi', to get
+the authenticated or unauthenticated site respectively.
+""" % sys.argv[0]
+    sys.exit(2)
+
+if __name__ == "__main__":
+    if len(sys.argv) > 2:
+        usage()
+    if len(sys.argv) > 1:
+        if sys.argv[1] in ('-h', '--help'):
+            usage()
+        conf_file = sys.argv[1]
+        dev = True
+    else:
+        conf_file = os.path.join(base_dir, 'main.conf')
+
+    app_config = {
+        '/': {
+            'tools.invirtwebstate.on': True,
+            },
+        }
+
+    if os.path.basename(sys.argv[0]).startswith('auth'):
+        root = main.InvirtWeb()
+    elif os.path.basename(sys.argv[0]).startswith('unauth'):
+        root = main.InvirtUnauthWeb()
+    else:
+        usage()
+
+    app = cherrypy.tree.mount(root, '/', app_config)
+    app.merge(conf_file)
+    cherrypy.config.update(conf_file)
+
+    if dev:
+        cherrypy.server.quickstart()
+        cherrypy.engine.start()
+        cherrypy.engine.block()
+    else:
+        cherrypy.engine.start(blocking=False)
+        from flup.server.fcgi import WSGIServer
+        server = WSGIServer(cherrypy.tree)
+        server.run()
diff --git a/code/main.conf b/code/main.conf
new file mode 100644 (file)
index 0000000..6381f19
--- /dev/null
@@ -0,0 +1,11 @@
+# This file is used when the web interface is loaded as a FastCGI
+[global]
+#auto_reload doesn't work with FastCGI
+engine.auto_reload = False
+tools.mako.module_directory = "/tmp/invirt-web-templatecache"
+tools.remote_user_login.on = True
+
+engine.SIGHUP = None
+engine.SIGTERM = None
+
+log.screen = False
diff --git a/code/main.fcgi b/code/main.fcgi
deleted file mode 100755 (executable)
index b2c7031..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import main
-main.main()
index 6fdd31a..dd2b591 100755 (executable)
@@ -6,24 +6,17 @@ import cPickle
 import cgi
 import datetime
 import hmac
+import os
 import random
 import sha
-import simplejson
 import sys
 import time
 import urllib
 import socket
+import cherrypy
+from cherrypy import _cperror
 from StringIO import StringIO
 
-def revertStandardError():
-    """Move stderr to stdout, and return the contents of the old stderr."""
-    errio = sys.stderr
-    if not isinstance(errio, StringIO):
-        return ''
-    sys.stderr = sys.stdout
-    errio.seek(0)
-    return errio.read()
-
 def printError():
     """Revert stderr to stdout, and print the contents of stderr"""
     if isinstance(sys.stderr, StringIO):
@@ -33,8 +26,6 @@ if __name__ == '__main__':
     import atexit
     atexit.register(printError)
 
-import templates
-from Cheetah.Template import Template
 import validation
 import cache_acls
 from webcommon import State
@@ -45,68 +36,360 @@ from invirt.database import Machine, CDROM, session, connect, MachineAccess, Typ
 from invirt.config import structs as config
 from invirt.common import InvalidInput, CodeError
 
-def pathSplit(path):
-    if path.startswith('/'):
-        path = path[1:]
-    i = path.find('/')
-    if i == -1:
-        i = len(path)
-    return path[:i], path[i:]
+from view import View, revertStandardError
+
+
+static_dir = os.path.join(os.path.dirname(__file__), 'static')
+InvirtStatic = cherrypy.tools.staticdir.handler(
+    root=static_dir,
+    dir=static_dir,
+    section='/static')
 
-class Checkpoint:
+class InvirtUnauthWeb(View):
+    static = InvirtStatic
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/unauth.mako")
+    def index(self):
+        return {'simple': True}
+
+class InvirtWeb(View):
     def __init__(self):
-        self.start_time = time.time()
-        self.checkpoints = []
-
-    def checkpoint(self, s):
-        self.checkpoints.append((s, time.time()))
-
-    def __str__(self):
-        return ('Timing info:\n%s\n' %
-                '\n'.join(['%s: %s' % (d, t - self.start_time) for
-                           (d, t) in self.checkpoints]))
-
-checkpoint = Checkpoint()
-
-def jquote(string):
-    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
-
-def helppopup(subj):
-    """Return HTML code for a (?) link to a specified help topic"""
-    return ('<span class="helplink"><a href="help?' +
-            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
-            +'" target="_blank" ' +
-            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
-
-def makeErrorPre(old, addition):
-    if addition is None:
-        return
-    if old:
-        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
-    else:
-        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
-
-Template.database = database
-Template.config = config
-Template.helppopup = staticmethod(helppopup)
-Template.err = None
-
-class JsonDict:
-    """Class to store a dictionary that will be converted to JSON"""
-    def __init__(self, **kws):
-        self.data = kws
-        if 'err' in kws:
-            err = kws['err']
-            del kws['err']
-            self.addError(err)
-
-    def __str__(self):
-        return simplejson.dumps(self.data)
-
-    def addError(self, text):
-        """Add stderr text to be displayed on the website."""
-        self.data['err'] = \
-            makeErrorPre(self.data.get('err'), text)
+        super(self.__class__,self).__init__()
+        connect()
+        self._cp_config['tools.require_login.on'] = True
+        self._cp_config['tools.catch_stderr.on'] = True
+        self._cp_config['tools.mako.imports'] = ['from invirt.config import structs as config',
+                                                 'from invirt import database']
+        self._cp_config['request.error_response'] = self.handle_error
+
+    static = InvirtStatic
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/invalid.mako")
+    def invalidInput(self):
+        """Print an error page when an InvalidInput exception occurs"""
+        err = cherrypy.request.prev.params["err"]
+        emsg = cherrypy.request.prev.params["emsg"]
+        d = dict(err_field=err.err_field,
+                 err_value=str(err.err_value), stderr=emsg,
+                 errorMessage=str(err))
+        return d
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/error.mako")
+    def error(self):
+        """Print an error page when an exception occurs"""
+        op = cherrypy.request.prev.path_info
+        username = cherrypy.request.login
+        err = cherrypy.request.prev.params["err"]
+        emsg = cherrypy.request.prev.params["emsg"]
+        traceback = cherrypy.request.prev.params["traceback"]
+        d = dict(op=op, user=username, fields=cherrypy.request.prev.params,
+                 errorMessage=str(err), stderr=emsg, traceback=traceback)
+        error_raw = cherrypy.request.lookup.get_template("/error_raw.mako")
+        details = error_raw.render(**d)
+        exclude = config.web.errormail_exclude
+        if username not in exclude and '*' not in exclude:
+            send_error_mail('xvm error on %s for %s: %s' % (op, cherrypy.request.login, err),
+                            details)
+        d['details'] = details
+        return d
+
+    def __getattr__(self, name):
+        if name in ("admin", "overlord"):
+            if not cherrypy.request.login in getAfsGroupMembers(config.adminacl, config.authz.afs.cells[0].cell):
+                raise InvalidInput('username', cherrypy.request.login,
+                                   'Not in admin group %s.' % config.adminacl)
+            cherrypy.request.state = State(cherrypy.request.login, isadmin=True)
+            return self
+        else:
+            return super(InvirtWeb, self).__getattr__(name)
+
+    def handle_error(self):
+        err = sys.exc_info()[1]
+        if isinstance(err, InvalidInput):
+            cherrypy.request.params['err'] = err
+            cherrypy.request.params['emsg'] = revertStandardError()
+            raise cherrypy.InternalRedirect('/invalidInput')
+        if not cherrypy.request.prev or 'err' not in cherrypy.request.prev.params:
+            cherrypy.request.params['err'] = err
+            cherrypy.request.params['emsg'] = revertStandardError()
+            cherrypy.request.params['traceback'] = _cperror.format_exc()
+            raise cherrypy.InternalRedirect('/error')
+        # fall back to cherrypy default error page
+        cherrypy.HTTPError(500).set_response()
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/list.mako")
+    def list(self, result=None):
+        """Handler for list requests."""
+        d = getListDict(cherrypy.request.login, cherrypy.request.state)
+        if result is not None:
+            d['result'] = result
+        return d
+    index=list
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/help.mako")
+    def help(self, subject=None, simple=False):
+        """Handler for help messages."""
+
+        help_mapping = {
+            'Autoinstalls': """
+The autoinstaller builds a minimal Debian or Ubuntu system to run as a
+ParaVM.  You can access the resulting system by logging into the <a
+href="help?simple=true&subject=ParaVM+Console">serial console server</a>
+with your Kerberos tickets; there is no root password so sshd will
+refuse login.</p>
+
+<p>Under the covers, the autoinstaller uses our own patched version of
+xen-create-image, which is a tool based on debootstrap.  If you log
+into the serial console while the install is running, you can watch
+it.
+""",
+            'ParaVM Console': """
+ParaVM machines do not support local console access over VNC.  To
+access the serial console of these machines, you can SSH with Kerberos
+to %s, using the name of the machine as your
+username.""" % config.console.hostname,
+            'HVM/ParaVM': """
+HVM machines use the virtualization features of the processor, while
+ParaVM machines rely on a modified kernel to communicate directly with
+the hypervisor.  HVMs support boot CDs of any operating system, and
+the VNC console applet.  The three-minute autoinstaller produces
+ParaVMs.  ParaVMs typically are more efficient, and always support the
+<a href="help?subject=ParaVM+Console">console server</a>.</p>
+
+<p>More details are <a
+href="https://xvm.scripts.mit.edu/wiki/Paravirtualization">on the
+wiki</a>, including steps to prepare an HVM guest to boot as a ParaVM
+(which you can skip by using the autoinstaller to begin with.)</p>
+
+<p>We recommend using a ParaVM when possible and an HVM when necessary.
+""",
+            'CPU Weight': """
+Don't ask us!  We're as mystified as you are.""",
+            'Owner': """
+The owner field is used to determine <a
+href="help?subject=Quotas">quotas</a>.  It must be the name of a
+locker that you are an AFS administrator of.  In particular, you or an
+AFS group you are a member of must have AFS rlidwka bits on the
+locker.  You can check who administers the LOCKER locker using the
+commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
+href="help?subject=Administrator">administrator</a>.""",
+            'Administrator': """
+The administrator field determines who can access the console and
+power on and off the machine.  This can be either a user or a moira
+group.""",
+            'Quotas': """
+Quotas are determined on a per-locker basis.  Each locker may have a
+maximum of 512 mebibytes of active ram, 50 gibibytes of disk, and 4
+active machines.""",
+            'Console': """
+<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
+setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
+your machine will run just fine, but the applet's display of the
+console will suffer artifacts.
+""",
+            'Windows': """
+<strong>Windows Vista:</strong> The Vista image is licensed for all MIT students and will automatically activate off the network; see <a href="/static/msca-email.txt">the licensing confirmation e-mail</a> for details. The installer requires 512 MiB RAM and at least 7.5 GiB disk space (15 GiB or more recommended).<br>
+<strong>Windows XP:</strong> This is the volume license CD image. You will need your own volume license key to complete the install. We do not have these available for the general MIT community; ask your department if they have one, or visit <a href="http://msca.mit.edu/">http://msca.mit.edu/</a> if you are staff/faculty to request one.
+"""
+            }
+
+        if not subject:
+            subject = sorted(help_mapping.keys())
+        if not isinstance(subject, list):
+            subject = [subject]
+
+        return dict(simple=simple,
+                    subjects=subject,
+                    mapping=help_mapping)
+    help._cp_config['tools.require_login.on'] = False
+
+    def parseCreate(self, fields):
+        kws = dict([(kw, fields[kw]) for kw in
+         'name description owner memory disksize vmtype cdrom autoinstall'.split()
+                    if fields[kw]])
+        validate = validation.Validate(cherrypy.request.login,
+                                       cherrypy.request.state,
+                                       strict=True, **kws)
+        return dict(contact=cherrypy.request.login, name=validate.name,
+                    description=validate.description, memory=validate.memory,
+                    disksize=validate.disksize, owner=validate.owner,
+                    machine_type=getattr(validate, 'vmtype', Defaults.type),
+                    cdrom=getattr(validate, 'cdrom', None),
+                    autoinstall=getattr(validate, 'autoinstall', None))
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/list.mako")
+    @cherrypy.tools.require_POST()
+    def create(self, **fields):
+        """Handler for create requests."""
+        try:
+            parsed_fields = self.parseCreate(fields)
+            machine = controls.createVm(cherrypy.request.login,
+                                        cherrypy.request.state, **parsed_fields)
+        except InvalidInput, err:
+            pass
+        else:
+            err = None
+        cherrypy.request.state.clear() #Changed global state
+        d = getListDict(cherrypy.request.login, cherrypy.request.state)
+        d['err'] = err
+        if err:
+            for field, value in fields.items():
+                setattr(d['defaults'], field, value)
+        else:
+            d['new_machine'] = parsed_fields['name']
+        return d
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/helloworld.mako")
+    def helloworld(self, **kwargs):
+        return {'request': cherrypy.request, 'kwargs': kwargs}
+    helloworld._cp_config['tools.require_login.on'] = False
+
+    @cherrypy.expose
+    def errortest(self):
+        """Throw an error, to test the error-tracing mechanisms."""
+        print >>sys.stderr, "look ma, it's a stderr"
+        raise RuntimeError("test of the emergency broadcast system")
+
+    class MachineView(View):
+        def __getattr__(self, name):
+            """Synthesize attributes to allow RESTful URLs like
+            /machine/13/info. This is hairy. CherryPy 3.2 adds a
+            method called _cp_dispatch that allows you to explicitly
+            handle URLs that can't be mapped, and it allows you to
+            rewrite the path components and continue processing.
+
+            This function gets the next path component being resolved
+            as a string. _cp_dispatch will get an array of strings
+            representing any subsequent path components as well."""
+
+            try:
+                cherrypy.request.params['machine_id'] = int(name)
+                return self
+            except ValueError:
+                return None
+
+        @cherrypy.expose
+        @cherrypy.tools.mako(filename="/info.mako")
+        def info(self, machine_id):
+            """Handler for info on a single VM."""
+            machine = validation.Validate(cherrypy.request.login,
+                                          cherrypy.request.state,
+                                          machine_id=machine_id).machine
+            d = infoDict(cherrypy.request.login, cherrypy.request.state, machine)
+            return d
+        index = info
+
+        @cherrypy.expose
+        @cherrypy.tools.mako(filename="/info.mako")
+        @cherrypy.tools.require_POST()
+        def modify(self, machine_id, **fields):
+            """Handler for modifying attributes of a machine."""
+            try:
+                modify_dict = modifyDict(cherrypy.request.login,
+                                         cherrypy.request.state,
+                                         machine_id, fields)
+            except InvalidInput, err:
+                result = None
+                machine = validation.Validate(cherrypy.request.login,
+                                              cherrypy.request.state,
+                                              machine_id=machine_id).machine
+            else:
+                machine = modify_dict['machine']
+                result = 'Success!'
+                err = None
+            info_dict = infoDict(cherrypy.request.login,
+                                 cherrypy.request.state, machine)
+            info_dict['err'] = err
+            if err:
+                for field, value in fields.items():
+                    setattr(info_dict['defaults'], field, value)
+            info_dict['result'] = result
+            return info_dict
+
+        @cherrypy.expose
+        @cherrypy.tools.mako(filename="/vnc.mako")
+        def vnc(self, machine_id):
+            """VNC applet page.
+
+            Note that due to same-domain restrictions, the applet connects to
+            the webserver, which needs to forward those requests to the xen
+            server.  The Xen server runs another proxy that (1) authenticates
+            and (2) finds the correct port for the VM.
+
+            You might want iptables like:
+
+            -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
+            --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
+            -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
+            --dport 10003 -j SNAT --to-source 18.187.7.142
+            -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
+            --dport 10003 -j ACCEPT
+
+            Remember to enable iptables!
+            echo 1 > /proc/sys/net/ipv4/ip_forward
+            """
+            machine = validation.Validate(cherrypy.request.login,
+                                          cherrypy.request.state,
+                                          machine_id=machine_id).machine
+            token = controls.vnctoken(machine)
+            host = controls.listHost(machine)
+            if host:
+                port = 10003 + [h.hostname for h in config.hosts].index(host)
+            else:
+                port = 5900 # dummy
+
+            status = controls.statusInfo(machine)
+            has_vnc = hasVnc(status)
+
+            d = dict(on=status,
+                     has_vnc=has_vnc,
+                     machine=machine,
+                     hostname=cherrypy.request.local.name,
+                     port=port,
+                     authtoken=token)
+            return d
+
+        @cherrypy.expose
+        @cherrypy.tools.mako(filename="/command.mako")
+        @cherrypy.tools.require_POST()
+        def command(self, command_name, machine_id, **kwargs):
+            """Handler for running commands like boot and delete on a VM."""
+            back = kwargs.get('back')
+            if command_name == 'delete':
+                back = 'list'
+            try:
+                d = controls.commandResult(cherrypy.request.login,
+                                           cherrypy.request.state,
+                                           command_name, machine_id, kwargs)
+            except InvalidInput, err:
+                if not back:
+                    raise
+                print >> sys.stderr, err
+                result = str(err)
+            else:
+                result = 'Success!'
+                if not back:
+                    return d
+            if back == 'list':
+                cherrypy.request.state.clear() #Changed global state
+                raise cherrypy.InternalRedirect('/list?result=%s'
+                                                % urllib.quote(result))
+            elif back == 'info':
+                raise cherrypy.HTTPRedirect(cherrypy.request.base
+                                            + '/machine/%d/' % machine_id,
+                                            status=303)
+            else:
+                raise InvalidInput('back', back, 'Not a known back page.')
+
+    machine = MachineView()
+
 
 class Defaults:
     """Class to store default values for fields."""
@@ -116,6 +399,7 @@ class Defaults:
     autoinstall = ''
     name = ''
     description = ''
+    administrator = ''
     type = 'linux-hvm'
 
     def __init__(self, max_memory=None, max_disk=None, **kws):
@@ -126,17 +410,6 @@ class Defaults:
         for key in kws:
             setattr(self, key, kws[key])
 
-
-
-DEFAULT_HEADERS = {'Content-Type': 'text/html'}
-
-def invalidInput(op, username, fields, err, emsg):
-    """Print an error page when an InvalidInput exception occurs"""
-    d = dict(op=op, user=username, err_field=err.err_field,
-             err_value=str(err.err_value), stderr=emsg,
-             errorMessage=str(err))
-    return templates.invalid(searchList=[d])
-
 def hasVnc(status):
     """Does the machine with a given status list support VNC?"""
     if status is None:
@@ -147,63 +420,32 @@ def hasVnc(status):
             return 'location' in d
     return False
 
-def parseCreate(username, state, fields):
-    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
-    validate = validation.Validate(username, state, strict=True, **kws)
-    return dict(contact=username, name=validate.name, description=validate.description, memory=validate.memory,
-                disksize=validate.disksize, owner=validate.owner, machine_type=getattr(validate, 'vmtype', Defaults.type),
-                cdrom=getattr(validate, 'cdrom', None),
-                autoinstall=getattr(validate, 'autoinstall', None))
-
-def create(username, state, path, fields):
-    """Handler for create requests."""
-    try:
-        parsed_fields = parseCreate(username, state, fields)
-        machine = controls.createVm(username, state, **parsed_fields)
-    except InvalidInput, err:
-        pass
-    else:
-        err = None
-    state.clear() #Changed global state
-    d = getListDict(username, state)
-    d['err'] = err
-    if err:
-        for field in fields.keys():
-            setattr(d['defaults'], field, fields.getfirst(field))
-    else:
-        d['new_machine'] = parsed_fields['name']
-    return templates.list(searchList=[d])
-
 
 def getListDict(username, state):
     """Gets the list of local variables used by list.tmpl."""
-    checkpoint.checkpoint('Starting')
     machines = state.machines
-    checkpoint.checkpoint('Got my machines')
     on = {}
     has_vnc = {}
+    installing = {}
     xmlist = state.xmlist
-    checkpoint.checkpoint('Got uptimes')
-    can_clone = 'ice3' not in state.xmlist_raw
     for m in machines:
         if m not in xmlist:
             has_vnc[m] = 'Off'
             m.uptime = None
         else:
             m.uptime = xmlist[m]['uptime']
+            installing[m] = bool(xmlist[m].get('autoinstall'))
             if xmlist[m]['console']:
                 has_vnc[m] = True
             elif m.type.hvm:
                 has_vnc[m] = "WTF?"
             else:
-                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
+                has_vnc[m] = "ParaVM"
     max_memory = validation.maxMemory(username, state)
     max_disk = validation.maxDisk(username)
-    checkpoint.checkpoint('Got max mem/disk')
     defaults = Defaults(max_memory=max_memory,
                         max_disk=max_disk,
                         owner=username)
-    checkpoint.checkpoint('Got defaults')
     def sortkey(machine):
         return (machine.owner != username, machine.owner, machine.name)
     machines = sorted(machines, key=sortkey)
@@ -214,57 +456,9 @@ def getListDict(username, state):
              defaults=defaults,
              machines=machines,
              has_vnc=has_vnc,
-             can_clone=can_clone)
+             installing=installing)
     return d
 
-def listVms(username, state, path, fields):
-    """Handler for list requests."""
-    checkpoint.checkpoint('Getting list dict')
-    d = getListDict(username, state)
-    checkpoint.checkpoint('Got list dict')
-    return templates.list(searchList=[d])
-
-def vnc(username, state, path, fields):
-    """VNC applet page.
-
-    Note that due to same-domain restrictions, the applet connects to
-    the webserver, which needs to forward those requests to the xen
-    server.  The Xen server runs another proxy that (1) authenticates
-    and (2) finds the correct port for the VM.
-
-    You might want iptables like:
-
-    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
-      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
-    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
-      --dport 10003 -j SNAT --to-source 18.187.7.142
-    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
-      --dport 10003 -j ACCEPT
-
-    Remember to enable iptables!
-    echo 1 > /proc/sys/net/ipv4/ip_forward
-    """
-    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
-
-    token = controls.vnctoken(machine)
-    host = controls.listHost(machine)
-    if host:
-        port = 10003 + [h.hostname for h in config.hosts].index(host)
-    else:
-        port = 5900 # dummy
-
-    status = controls.statusInfo(machine)
-    has_vnc = hasVnc(status)
-
-    d = dict(user=username,
-             on=status,
-             has_vnc=has_vnc,
-             machine=machine,
-             hostname=state.environ.get('SERVER_NAME', 'localhost'),
-             port=port,
-             authtoken=token)
-    return templates.vnc(searchList=[d])
-
 def getHostname(nic):
     """Find the hostname associated with a NIC.
 
@@ -318,44 +512,18 @@ def getDiskInfo(data_dict, machine):
         data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
     return disk_fields
 
-def command(username, state, path, fields):
-    """Handler for running commands like boot and delete on a VM."""
-    back = fields.getfirst('back')
-    try:
-        d = controls.commandResult(username, state, fields)
-        if d['command'] == 'Delete VM':
-            back = 'list'
-    except InvalidInput, err:
-        if not back:
-            raise
-        print >> sys.stderr, err
-        result = err
-    else:
-        result = 'Success!'
-        if not back:
-            return templates.command(searchList=[d])
-    if back == 'list':
-        state.clear() #Changed global state
-        d = getListDict(username, state)
-        d['result'] = result
-        return templates.list(searchList=[d])
-    elif back == 'info':
-        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
-        return ({'Status': '303 See Other',
-                 'Location': 'info?machine_id=%d' % machine.machine_id},
-                "You shouldn't see this message.")
-    else:
-        raise InvalidInput('back', back, 'Not a known back page.')
-
-def modifyDict(username, state, fields):
+def modifyDict(username, state, machine_id, fields):
     """Modify a machine as specified by CGI arguments.
 
-    Return a list of local variables for modify.tmpl.
+    Return a dict containing the machine that was modified.
     """
     olddisk = {}
     session.begin()
     try:
-        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
+        kws = dict([(kw, fields[kw]) for kw in
+         'owner admin contact name description memory vmtype disksize'.split()
+                    if fields[kw]])
+        kws['machine_id'] = machine_id
         validate = validation.Validate(username, state, **kws)
         machine = validate.machine
         oldname = machine.name
@@ -402,117 +570,11 @@ def modifyDict(username, state, fields):
         controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
     if hasattr(validate, 'name'):
         controls.renameMachine(machine, oldname, validate.name)
-    return dict(user=username,
-                command="modify",
-                machine=machine)
-
-def modify(username, state, path, fields):
-    """Handler for modifying attributes of a machine."""
-    try:
-        modify_dict = modifyDict(username, state, fields)
-    except InvalidInput, err:
-        result = None
-        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
-    else:
-        machine = modify_dict['machine']
-        result = 'Success!'
-        err = None
-    info_dict = infoDict(username, state, machine)
-    info_dict['err'] = err
-    if err:
-        for field in fields.keys():
-            setattr(info_dict['defaults'], field, fields.getfirst(field))
-    info_dict['result'] = result
-    return templates.info(searchList=[info_dict])
-
-
-def helpHandler(username, state, path, fields):
-    """Handler for help messages."""
-    simple = fields.getfirst('simple')
-    subjects = fields.getlist('subject')
-
-    help_mapping = {
-                    'Autoinstalls': """
-The autoinstaller builds a minimal Debian or Ubuntu system to run as a
-ParaVM.  You can access the resulting system by logging into the <a
-href="help?simple=true&subject=ParaVM+Console">serial console server</a>
-with your Kerberos tickets; there is no root password so sshd will
-refuse login.</p>
-
-<p>Under the covers, the autoinstaller uses our own patched version of
-xen-create-image, which is a tool based on debootstrap.  If you log
-into the serial console while the install is running, you can watch
-it.
-""",
-                    'ParaVM Console': """
-ParaVM machines do not support local console access over VNC.  To
-access the serial console of these machines, you can SSH with Kerberos
-to %s, using the name of the machine as your
-username.""" % config.console.hostname,
-                    'HVM/ParaVM': """
-HVM machines use the virtualization features of the processor, while
-ParaVM machines rely on a modified kernel to communicate directly with
-the hypervisor.  HVMs support boot CDs of any operating system, and
-the VNC console applet.  The three-minute autoinstaller produces
-ParaVMs.  ParaVMs typically are more efficient, and always support the
-<a href="help?subject=ParaVM+Console">console server</a>.</p>
-
-<p>More details are <a
-href="https://xvm.scripts.mit.edu/wiki/Paravirtualization">on the
-wiki</a>, including steps to prepare an HVM guest to boot as a ParaVM
-(which you can skip by using the autoinstaller to begin with.)</p>
-
-<p>We recommend using a ParaVM when possible and an HVM when necessary.
-""",
-                    'CPU Weight': """
-Don't ask us!  We're as mystified as you are.""",
-                    'Owner': """
-The owner field is used to determine <a
-href="help?subject=Quotas">quotas</a>.  It must be the name of a
-locker that you are an AFS administrator of.  In particular, you or an
-AFS group you are a member of must have AFS rlidwka bits on the
-locker.  You can check who administers the LOCKER locker using the
-commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
-href="help?subject=Administrator">administrator</a>.""",
-                    'Administrator': """
-The administrator field determines who can access the console and
-power on and off the machine.  This can be either a user or a moira
-group.""",
-                    'Quotas': """
-Quotas are determined on a per-locker basis.  Each locker may have a
-maximum of 512 mebibytes of active ram, 50 gibibytes of disk, and 4
-active machines.""",
-                    'Console': """
-<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
-setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
-your machine will run just fine, but the applet's display of the
-console will suffer artifacts.
-""",
-                    'Windows': """
-<strong>Windows Vista:</strong> The Vista image is licensed for all MIT students and will automatically activate off the network; see <a href="/static/msca-email.txt">the licensing confirmation e-mail</a> for details. The installer requires 512 MiB RAM and at least 7.5 GiB disk space (15 GiB or more recommended).<br>
-<strong>Windows XP:</strong> This is the volume license CD image. You will need your own volume license key to complete the install. We do not have these available for the general MIT community; ask your department if they have one.
-"""
-                    }
-
-    if not subjects:
-        subjects = sorted(help_mapping.keys())
-
-    d = dict(user=username,
-             simple=simple,
-             subjects=subjects,
-             mapping=help_mapping)
-
-    return templates.help(searchList=[d])
-
-
-def badOperation(u, s, p, e):
-    """Function called when accessing an unknown URI."""
-    return ({'Status': '404 Not Found'}, 'Invalid operation.')
+    return dict(machine=machine)
 
 def infoDict(username, state, machine):
     """Get the variables used by info.tmpl."""
     status = controls.statusInfo(machine)
-    checkpoint.checkpoint('Getting status info')
     has_vnc = hasVnc(status)
     if status is None:
         main_status = dict(name=machine.name,
@@ -526,7 +588,6 @@ def infoDict(username, state, machine):
         uptime = datetime.timedelta(seconds=int(time.time()-start_time))
         cpu_time_float = float(main_status.get('cpu_time', 0))
         cputime = datetime.timedelta(seconds=int(cpu_time_float))
-    checkpoint.checkpoint('Status')
     display_fields = [('name', 'Name'),
                       ('description', 'Description'),
                       ('owner', 'Owner'),
@@ -540,7 +601,6 @@ def infoDict(username, state, machine):
                       ('memory', 'RAM'),
                       'DISK_INFO',
                       ('state', 'state (xen format)'),
-                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
                       ]
     fields = []
     machine_info = {}
@@ -573,18 +633,14 @@ def infoDict(username, state, machine):
             pass
             #fields.append((disp, None))
 
-    checkpoint.checkpoint('Got fields')
-
-
     max_mem = validation.maxMemory(machine.owner, state, machine, False)
-    checkpoint.checkpoint('Got mem')
     max_disk = validation.maxDisk(machine.owner, machine)
     defaults = Defaults()
     for name in 'machine_id name description administrator owner memory contact'.split():
-        setattr(defaults, name, getattr(machine, name))
+        if getattr(machine, name):
+            setattr(defaults, name, getattr(machine, name))
     defaults.type = machine.type.type_id
     defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
-    checkpoint.checkpoint('Got defaults')
     d = dict(user=username,
              on=status is not None,
              machine=machine,
@@ -594,56 +650,9 @@ def infoDict(username, state, machine):
              ram=machine.memory,
              max_mem=max_mem,
              max_disk=max_disk,
-             owner_help=helppopup("Owner"),
              fields = fields)
     return d
 
-def info(username, state, path, fields):
-    """Handler for info on a single VM."""
-    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
-    d = infoDict(username, state, machine)
-    checkpoint.checkpoint('Got infodict')
-    return templates.info(searchList=[d])
-
-def unauthFront(_, _2, _3, fields):
-    """Information for unauth'd users."""
-    return templates.unauth(searchList=[{'simple' : True, 
-            'hostname' : socket.getfqdn()}])
-
-def admin(username, state, path, fields):
-    if path == '':
-        return ({'Status': '303 See Other',
-                 'Location': 'admin/'},
-                "You shouldn't see this message.")
-    if not username in getAfsGroupMembers(config.adminacl, 'athena.mit.edu'):
-        raise InvalidInput('username', username,
-                           'Not in admin group %s.' % config.adminacl)
-    newstate = State(username, isadmin=True)
-    newstate.environ = state.environ
-    return handler(username, newstate, path, fields)
-
-def throwError(_, __, ___, ____):
-    """Throw an error, to test the error-tracing mechanisms."""
-    raise RuntimeError("test of the emergency broadcast system")
-
-mapping = dict(list=listVms,
-               vnc=vnc,
-               command=command,
-               modify=modify,
-               info=info,
-               create=create,
-               help=helpHandler,
-               unauth=unauthFront,
-               admin=admin,
-               overlord=admin,
-               errortest=throwError)
-
-def printHeaders(headers):
-    """Print a dictionary as HTTP headers."""
-    for key, value in headers.iteritems():
-        print '%s: %s' % (key, value)
-    print
-
 def send_error_mail(subject, body):
     import subprocess
 
@@ -660,113 +669,4 @@ Subject: %s
     p.stdin.close()
     p.wait()
 
-def show_error(op, username, fields, err, emsg, traceback):
-    """Print an error page when an exception occurs"""
-    d = dict(op=op, user=username, fields=fields,
-             errorMessage=str(err), stderr=emsg, traceback=traceback)
-    details = templates.error_raw(searchList=[d])
-    exclude = config.web.errormail_exclude
-    if username not in exclude and '*' not in exclude:
-        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
-                        details)
-    d['details'] = details
-    return templates.error(searchList=[d])
-
-def getUser(environ):
-    """Return the current user based on the SSL environment variables"""
-    user = environ.get('REMOTE_USER')
-    if user is None:
-        return
-    
-    if environ.get('AUTH_TYPE') == 'Negotiate':
-        # Convert the krb5 principal into a krb4 username
-        if not user.endswith('@%s' % config.kerberos.realm):
-            return
-        else:
-            return user.split('@')[0].replace('/', '.')
-    else:
-        return user
-
-def handler(username, state, path, fields):
-    operation, path = pathSplit(path)
-    if not operation:
-        operation = 'list'
-    print 'Starting', operation
-    fun = mapping.get(operation, badOperation)
-    return fun(username, state, path, fields)
-
-class App:
-    def __init__(self, environ, start_response):
-        self.environ = environ
-        self.start = start_response
-
-        self.username = getUser(environ)
-        self.state = State(self.username)
-        self.state.environ = environ
-
-        random.seed() #sigh
-
-    def __iter__(self):
-        start_time = time.time()
-        database.clear_cache()
-        sys.stderr = StringIO()
-        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
-        operation = self.environ.get('PATH_INFO', '')
-        if not operation:
-            self.start("301 Moved Permanently", [('Location', './')])
-            return
-        if self.username is None:
-            operation = 'unauth'
-
-        try:
-            checkpoint.checkpoint('Before')
-            output = handler(self.username, self.state, operation, fields)
-            checkpoint.checkpoint('After')
-
-            headers = dict(DEFAULT_HEADERS)
-            if isinstance(output, tuple):
-                new_headers, output = output
-                headers.update(new_headers)
-            e = revertStandardError()
-            if e:
-                if hasattr(output, 'addError'):
-                    output.addError(e)
-                else:
-                    # This only happens on redirects, so it'd be a pain to get
-                    # the message to the user.  Maybe in the response is useful.
-                    output = output + '\n\nstderr:\n' + e
-            output_string =  str(output)
-            checkpoint.checkpoint('output as a string')
-        except Exception, err:
-            if not fields.has_key('js'):
-                if isinstance(err, InvalidInput):
-                    self.start('200 OK', [('Content-Type', 'text/html')])
-                    e = revertStandardError()
-                    yield str(invalidInput(operation, self.username, fields,
-                                           err, e))
-                    return
-            import traceback
-            self.start('500 Internal Server Error',
-                       [('Content-Type', 'text/html')])
-            e = revertStandardError()
-            s = show_error(operation, self.username, fields,
-                           err, e, traceback.format_exc())
-            yield str(s)
-            return
-        status = headers.setdefault('Status', '200 OK')
-        del headers['Status']
-        self.start(status, headers.items())
-        yield output_string
-        if fields.has_key('timedebug'):
-            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
-
-def constructor():
-    connect()
-    return App
-
-def main():
-    from flup.server.fcgi_fork import WSGIServer
-    WSGIServer(constructor()).run()
-
-if __name__ == '__main__':
-    main()
+random.seed() #sigh
diff --git a/code/static/power_installing.png b/code/static/power_installing.png
new file mode 100644 (file)
index 0000000..434e048
Binary files /dev/null and b/code/static/power_installing.png differ
index 49fe8e2..a764e17 100644 (file)
@@ -104,3 +104,7 @@ tr.stripedrow {
 #machinelist td, #machinelist th {
   padding: 0.1em 0.5em;
 }
+
+form {
+    display: inline;
+}
diff --git a/code/templates/Makefile b/code/templates/Makefile
deleted file mode 100644 (file)
index aa083e2..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-TEMPLATES=$(wildcard *.tmpl)
-OUTPUTS=$(TEMPLATES:.tmpl=.py)
-
-all: ${OUTPUTS}
-
-%.py: %.tmpl
-       cheetah compile $<
-
-#${OUTPUTS}:${TEMPLATES}
-#      cheetah compile $^
-
-clean:
-       @rm -f ${OUTPUTS} *.pyo *.pyc *.py.bak
diff --git a/code/templates/__init__.py b/code/templates/__init__.py
deleted file mode 100644 (file)
index b2b23da..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-__all__ = 'info command error error_raw help invalid list unauth vnc'.split()
-for _name in __all__:
-    try:
-        _module = __import__(_name, globals(), {}, [_name])
-        globals()[_name] = getattr(_module, _name)
-    except ImportError, e:
-        import sys
-        print >> sys.stderr, 'Importing template "%s" raised error: %s' % (_name, e)
-        
diff --git a/code/templates/command.mako b/code/templates/command.mako
new file mode 100644 (file)
index 0000000..17a16ed
--- /dev/null
@@ -0,0 +1,9 @@
+<%page expression_filter="h" />
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+$command ${machine.name}
+</%def>
+
+<p>${command} ${machine.name} was successful.</p>
+<p><a href="list">Return</a></p>
diff --git a/code/templates/command.tmpl b/code/templates/command.tmpl
deleted file mode 100644 (file)
index 904cba7..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-$command $machine.name
-#end def
-
-
-#def body
-<p>$command ${machine.name} was successful.</p>
-#if $command == "Delete VM" or True
-<p><a href="list">Return</a></p>
-#else
-<p><a href="info?machine_id=${machine.machine_id}">Return</a></p>
-#end if
-#end def
diff --git a/code/templates/create.tmpl b/code/templates/create.tmpl
deleted file mode 100644 (file)
index 8123d23..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Created!
-#end def
-
-#def body
-#if $machine
-<p>Congratulations!  You have a new machine named ${machine.name}.</p>
-#else
-<p>Odd... no error, but no machine.</p>
-#end if
-<p><a href="list">Return</a></p>
-#end def
similarity index 57%
rename from code/templates/error.tmpl
rename to code/templates/error.mako
index ff9b4e0..1edb1b4 100644 (file)
@@ -1,20 +1,18 @@
-#from skeleton import skeleton
-#extends skeleton
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
 
-#def title
+<%def name="title()">
 ERROR!
-#end def
+</%def>
 
-#def body
 <p>Uh-oh!  We experienced an error.  Sorry about that.  We've gotten
 mail about it.</p>
 
-<p>Feel free to poke us at <tt>xvm@mit.edu</tt> if this bug is
+<p>Feel free to poke us at <tt>${config.contact}</tt> if this bug is
 consistently biting you and we don't seem to be fixing it.</p>
 
 <p>In case you're curious, the gory details are below.</p>
 
 <pre>
-$details
+${details}
 </pre>
-#end def
diff --git a/code/templates/error_raw.mako b/code/templates/error_raw.mako
new file mode 100644 (file)
index 0000000..a8cf902
--- /dev/null
@@ -0,0 +1,12 @@
+Error on operation ${op} for user ${user}: ${errorMessage}
+
+Fields:
+%for f in fields:
+${f}=${fields[f]}
+%endfor
+
+Error output:
+${stderr}\
+---- end error output
+
+${traceback}
diff --git a/code/templates/error_raw.tmpl b/code/templates/error_raw.tmpl
deleted file mode 100644 (file)
index 5ae1490..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-Error on operation $op for user $user: $errorMessage
-
-Fields:
-#for $f in $fields:
-$f=$fields[$f].value
-#end for
-
-Error output:
-$stderr#slurp
----- end error output
-
-$traceback
diff --git a/code/templates/functions.mako b/code/templates/functions.mako
new file mode 100644 (file)
index 0000000..4f23635
--- /dev/null
@@ -0,0 +1,54 @@
+<%page expression_filter="h"/>
+<%def name="databaseList(lst, default, onchange, name, id, valueattr, descattr)">
+<select name="${name}" id="${id}" \
+% if onchange:
+onchange="${onchange}"\
+% endif
+>
+  <option ${'' if default else 'selected'} value="">None</option>
+  %for item in lst:
+  <option ${'selected' if default == getattr(item, valueattr) else ''} value="${getattr(item, valueattr)}">
+    ${getattr(item, descattr)}
+  </option>
+  % endfor
+</select>
+</%def>
+
+<%def name="cdromList(default='', onchange=None)">
+${databaseList(sorted(database.CDROM.query(), key=lambda x: x.description),
+               default, onchange, 'cdrom', 'cdromlist', 'cdrom_id', 'description')|n}
+</%def>
+
+<%def name="autoList(default='', onchange=None)">
+${databaseList(sorted(database.Autoinstall.query(), key=lambda x: x.description),
+               default, onchange, 'autoinstall', 'autoinstalllist', 'autoinstall_id', 'description')|n}
+</%def>
+
+<%def name="vmTypeList(default=None)">
+% for vmtype in (('linux-hvm', 'HVM'), ('linux', 'ParaVM'), ):
+<label>
+   <input ${'checked="checked"' if default == vmtype[0] else ''} type="radio" name="vmtype" id="vmtype-${vmtype[0]}" value="${vmtype[0]}">${vmtype[1]}</input>
+</label>
+% endfor
+</%def>
+
+<%def name="errorRow(value, err)">
+% if err and err.err_field == value:
+<tr>
+<td class="error" colspan="2">${str(err)}</td>
+</tr>
+% endif
+</%def>
+
+<%!
+def jquote(string):
+    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
+
+def nl2br(string):
+    return string.replace('\n', '<br/>')
+%>
+
+<%def name="helppopup(subj)">
+## Return HTML code for a (?) link to a specified help topic
+<span class="helplink"><a href="help?simple=true;subject=${subj | u}" target="_blank" onclick="return helppopup(${subj | u,jquote})">(?)</a></span>
+</%def>
diff --git a/code/templates/functions.tmpl b/code/templates/functions.tmpl
deleted file mode 100644 (file)
index 305b4e8..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#filter WebSafe
-#def databaseList($lst, $default, $onchange, $name, $id, $valueattr, $descattr)
-<select name="$name" id="$id"#slurp
-#if $onchange is not None
-onchange="$onchange"#slurp
-#end if
->
-  <option #slurp
-#if $default then '' else 'selected'
- value="">None</option>
-  #for $item in $lst
-  <option #slurp
-#if $default == getattr(item, valueattr) then 'selected' else ''
- value="${getattr(item, valueattr)}">
-    ${getattr(item, descattr)}
-  </option>
-  #end for
-</select>
-#end def
-
-#def cdromList($default="", $onchange=None)
-#filter None
-$databaseList(sorted($database.CDROM.query(), key=lambda x: x.description),
-              default, onchange, 'cdrom', 'cdromlist', 'cdrom_id', 'description')
-#end filter
-#end def
-
-#def autoList($default="", $onchange=None)
-#filter None
-$databaseList(sorted($database.Autoinstall.query(), key=lambda x: x.description),
-              default, onchange, 'autoinstall', 'autoinstalllist', 'autoinstall_id', 'description')
-#end filter
-#end def
-
-#def vmTypeList($default=None)
-#for $vmtype in (('linux-hvm', 'HVM'), ('linux', 'ParaVM'), )
-<label>
-   <input #slurp
-#if $default == $vmtype[0] then 'checked="checked"' else ''
- type="radio" name="vmtype" id="vmtype-${vmtype[0]}" value="${vmtype[0]}">${vmtype[1]}</input>
-</label>
-#end for
-#end def
-
-#def addError(txt)
-#if $varExists('txt')
-#set global $error_text = $error_text + '----\n' + $txt
-#else
-#set global $error_text = $txt
-#end if
-#end def
-
-#def errorRow($value, $err)
-#if $err and $err.err_field == $value
-<tr>
-<td class="error" colspan="2">${str($err)}</td>
-</tr>
-#end if
-#end def
-#filter None
-$full_body
-#end filter
-#end filter
diff --git a/code/templates/helloworld.mako b/code/templates/helloworld.mako
new file mode 100644 (file)
index 0000000..2cbb063
--- /dev/null
@@ -0,0 +1,19 @@
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<p>Hello world!</p>
+
+<p>kwargs:</p>
+<pre style="white-space: pre-wrap">
+${repr(kwargs)}
+</pre>
+
+<p>Your request:</p>
+
+<pre style="white-space: pre-wrap">
+${repr(dir(request))}
+</pre>
+
+<%def name="title()">
+helloworld
+</%def>
diff --git a/code/templates/help.mako b/code/templates/help.mako
new file mode 100644 (file)
index 0000000..ea27773
--- /dev/null
@@ -0,0 +1,37 @@
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<%!
+       pageclass = 'help'
+%>
+
+<%def name="title()">
+% if len(subjects) == 1:
+Help on ${subjects[0]}
+% else:
+Help
+% endif
+</%def>
+
+% if not simple:
+<p>Topics: 
+% for key in sorted(mapping):
+<a href="help?subject=${key}">${key}</a>
+% endfor
+</p>
+<p>
+See also <a href="trac/wiki/tips">tips and HOWTOs on the wiki</a>.
+</p>
+% endif
+
+% for subject in subjects:
+% if subject in mapping:
+<h2>${subject}</h2>
+<p>${mapping[subject]|n}</p>
+% else:
+<p>Unknown subject '${subject}'.</p>
+% endif
+% endfor
+% if simple:
+<a href="javascript:window.close();">Close</a>
+% endif
diff --git a/code/templates/help.tmpl b/code/templates/help.tmpl
deleted file mode 100644 (file)
index 61061ee..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#from skeleton import skeleton
-#extends skeleton
-
-#attr pageclass = 'help'
-
-#def title
-#if len($subjects) == 1
-Help on $subjects[0]
-#else
-Help
-#end if
-#end def
-
-
-#def body
-#if not $simple
-<p>Topics: 
-#for $key in sorted($mapping)
-<a href="help?subject=$key">$key</a>
-#end for
-</p>
-<p>
-See also <a href="trac/wiki/tips">tips and HOWTOs on the wiki</a>.
-</p>
-#end if
-#for $subject in $subjects
-#if $subject in $mapping 
-<h2>$subject</h2>
-#filter None
-<p>$mapping[$subject]</p>
-#end filter
-#else
-<p>Unknown subject '$subject'.</p>
-#end if
-#end for
-#if $simple
-<a href="javascript:window.close();">Close</a>
-#end if
-#end def
diff --git a/code/templates/info.mako b/code/templates/info.mako
new file mode 100644 (file)
index 0000000..0d1b66b
--- /dev/null
@@ -0,0 +1,103 @@
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+Info on ${machine.name}
+</%def>
+
+<%def name="infoTable()">
+<h2>Info</h2>
+<table>
+  % for key, value in fields:
+  <tr><td>${key}:</td><td>${value}</td></tr>
+  % endfor
+</table>
+</%def>
+
+<%def name="commands()">
+% if on:
+ % if not machine.type.hvm:
+  Console access: type
+  <tt>ssh ${machine.name}@${config.console.hostname}</tt>
+  on Athena. <a href="https://xvm.scripts.mit.edu/wiki/SerialConsole">(more info)</a>
+ % elif has_vnc:
+  <strong><a href="machine/${machine.machine_id}/vnc">Get Console</a></strong>
+ % else:
+  VNC console not enabled; still booting?
+ % endif
+% endif
+<%def name="command_button(title, value, cdrom=False, extra='')">
+<form action="machine/${machine.machine_id}/command/${value}" method="POST">
+  <input type="hidden" name="back" value="info" />
+  <input type="submit" class="button" name="action" value="${title}" ${extra | n}/>
+% if cdrom:
+  Boot CD: ${self.fn.cdromList()}
+% endif
+</form>
+</%def>
+  <div>
+       % if on:
+       ${command_button("Power off", "destroy")}
+       ${command_button("Shutdown", "shutdown")}
+       ${command_button("Reboot", "reboot", cdrom=True)}
+       % else:
+       ${command_button("Power on", "create", cdrom=True)}
+       % endif
+  </div>
+  <div>
+       ${command_button("Delete VM", "delete", extra='''onclick="return confirm('Are you sure that you want to delete this VM?');"''')}
+  </div>
+</form>
+</%def>
+
+<%def name="modifyForm()">
+% if err:
+<p class="error">We had a problem with your request:</p>
+% elif new_machine:
+<p>Successfully modified.</p>
+% endif
+% if on:
+(To edit ram, disk size, or machine name, turn off the machine first.)
+% endif
+<form action="machine/${machine.machine_id}/modify" method="POST">
+  <table>
+    <tr><td>Description:</td><td colspan="2"><textarea name="description" rows="4" cols="60">${defaults.description}</textarea></td></tr>
+    <tr><td>Owner${self.fn.helppopup("Owner")}:</td><td><input type="text" name="owner", value="${defaults.owner}"/></td></tr>
+${self.fn.errorRow('owner', err)}
+    <tr><td>Administrator${self.fn.helppopup("Administrator")}:</td><td><input type="text" name="admin", value="${defaults.administrator}"/></td></tr>
+${self.fn.errorRow('administrator', err)}
+    <tr><td>Contact email:</td><td><input type="text" name="contact" value="${defaults.contact}"/></td></tr>
+${self.fn.errorRow('contact', err)}
+% if not on:
+    <tr><td>Machine Name:</td><td><input type="text" name="name" value="${defaults.name}"/>.${config.dns.domains[0]}</td></tr>
+${self.fn.errorRow('name', err)}
+    <tr>
+      <td>HVM/ParaVM${self.fn.helppopup('HVM/ParaVM')}</td>
+      <td>${self.fn.vmTypeList(defaults.type)}</td>
+    </tr>
+    <tr><td>Ram:</td><td><input type="text" size=3 name="memory" value="${defaults.memory}"/>MiB (max ${max_mem})</td></tr>
+${self.fn.errorRow('memory', err)}
+    <tr><td>Disk:</td><td><input type="text" size=3 name="disksize" value="${defaults.disk}"/>GiB (max ${max_disk})</td><td>WARNING: Modifying disk size may corrupt your data.</td></tr>
+${self.fn.errorRow('disk', err)}
+% else:
+${self.fn.errorRow('name', err)}
+${self.fn.errorRow('memory', err)}
+${self.fn.errorRow('disk', err)}
+% endif
+    <tr><td><input type="submit" class="button" name="action" value="Change"/></td></tr>
+  </table>
+</form>
+</%def>
+
+<div id="info">
+  ${infoTable()}
+</div>
+
+<h2>Commands</h2>
+<div id="commands">
+  ${commands()}
+</div>
+<h2>Settings</h2>
+<div id="modify">
+  ${modifyForm()}
+</div>
diff --git a/code/templates/info.tmpl b/code/templates/info.tmpl
deleted file mode 100644 (file)
index d872fce..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Info on $machine.name
-#end def
-
-#def infoTable()
-<h2>Info</h2>
-<table>
-  #for $key, $value in $fields
-  <tr><td>$key:</td><td>$value</td></tr>
-  #end for
-</table>
-#end def
-
-#def commands()
-#if $on
- #if not $machine.type.hvm
-  Console access: type
-  <tt>ssh $machine.name@$config.console.hostname</tt>
-  on Athena. <a href="https://xvm.scripts.mit.edu/wiki/SerialConsole">(more info)</a>
- #elif $has_vnc
-  <strong><a href="vnc?machine_id=$machine.machine_id">Get Console</a></strong>
- #else
-  VNC console not enabled; still booting?
- #end if
-#end if
-<form action="command" method="POST">
-  <input type="hidden" name="back" value="info"/>
-  <input type="hidden" name="machine_id" value="$machine.machine_id"/>
-  <div>
-       #if $on
-       <button type="submit" class="button" name="action" value="Power off">Power off (hard)</button>
-       <button type="submit" class="button" name="action" value="Shutdown">Shut down</button>
-       <input type="submit" class="button" name="action" value="Reboot"/>
-       #else
-       <input type="submit" class="button" name="action" value="Power on"/>
-       #end if
-  </div>
-  <div>
-    Boot CD:
-#filter None
-$cdromList()#slurp
-#end filter
-  </div>
-  <div>
-      <input type="submit" class="button" name="action" value="Delete VM" onclick="return confirm('Are you sure that you want to delete this VM?');"/>
-  </div>
-</form>
-#end def
-
-#def modifyForm()
-#if $err
-<p class="error">We had a problem with your request:</p>
-#else if $varExists('new_machine')
-<p>Successfully modified.</p>
-#end if
-#if $on
-(To edit ram, disk size, or machine name, turn off the machine first.)
-#end if
-<form action="modify" method="POST">
-  <input type="hidden" name="machine_id" value="$defaults.machine_id"/>
-  <table>
-    <tr><td>Description:</td><td colspan="2"><textarea name="description" rows="4" cols="60">$defaults.description</textarea></td></tr>
-    <tr><td>Owner#slurp
-#filter None
-$helppopup("Owner")#slurp
-#end filter
-:</td><td><input type="text" name="owner", value="$defaults.owner"/></td></tr>
-#filter None
-$errorRow('owner', $err)
-#end filter
-    <tr><td>Administrator#slurp
-#filter None
-$helppopup("Administrator")#slurp
-#end filter
-:</td><td><input type="text" name="admin", value="$defaults.administrator"/></td></tr>
-#filter None
-$errorRow('administrator', $err)
-#end filter
-    <tr><td>Contact email:</td><td><input type="text" name="contact" value="$defaults.contact"/></td></tr>
-#filter None
-$errorRow('contact', $err)
-#end filter
-#if not $on
-    <tr><td>Machine Name:</td><td><input type="text" name="name" value="$defaults.name"/>.${config.dns.domains[0]}</td></tr>
-#filter None
-$errorRow('name', $err)
-#end filter
-    <tr>
-      <td>HVM/ParaVM#slurp
-#filter None
-$helppopup('HVM/ParaVM')#slurp
-#end filter
-</td>
-      <td>#slurp
-#filter None
-$vmTypeList($defaults.type)#slurp
-#end filter
-</td>
-    </tr>
-    <tr><td>Ram:</td><td><input type="text" size=3 name="memory" value="$defaults.memory"/>MiB (max $max_mem)</td></tr>
-#filter None
-$errorRow('memory', $err)
-#end filter
-    <tr><td>Disk:</td><td><input type="text" size=3 name="disksize" value="$defaults.disk"/>GiB (max $max_disk)</td><td>WARNING: Modifying disk size may corrupt your data.</td></tr>
-#filter None
-$errorRow('disk', $err)
-#end filter
-#else
-#filter None
-$errorRow('name', $err)
-$errorRow('memory', $err)
-$errorRow('disk', $err)
-#end filter
-#end if
-    <tr><td><input type="submit" class="button" name="action" value="Change"/></td></tr>
-  </table>
-</form>
-#end def
-
-#def body
-<div id="info">
-#filter None
-  $infoTable()
-#end filter
-</div>
-
-<h2>Commands</h2>
-<div id="commands">
-#filter None
-  $commands()
-#end filter
-</div>
-<h2>Settings</h2>
-<div id="modify">
-#filter None
-  $modifyForm()
-#end filter
-</div>
-#end def
diff --git a/code/templates/invalid.mako b/code/templates/invalid.mako
new file mode 100644 (file)
index 0000000..c597d18
--- /dev/null
@@ -0,0 +1,16 @@
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+Invalid Input
+</%def>
+
+<p>Your input was bad:</p>
+<table>
+<tr><td>Field</td><td>value</td><td>reason</td></tr>
+<tr><td>${err_field}</td><td>${err_value}</td><td>${errorMessage}</td></tr>
+%if stderr:
+<p>Printed to standard error:</p>
+<pre>${stderr}</pre>
+%endif
+</table>
diff --git a/code/templates/invalid.tmpl b/code/templates/invalid.tmpl
deleted file mode 100644 (file)
index 952e1c3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Invalid Input
-#end def
-
-#def body
-<p>Your input was bad:</p>
-<table>
-<tr><td>operation</td><td>Field</td><td>value</td><td>reason</td></tr>
-<tr><td>$op</td><td>$err_field</td><td>$err_value</td><td>$errorMessage</td></tr>
-#if $stderr
-<p>Printed to standard error:</p>
-<pre>$stderr</pre>
-#end if
-</table>
-#end def
diff --git a/code/templates/list.mako b/code/templates/list.mako
new file mode 100644 (file)
index 0000000..d513763
--- /dev/null
@@ -0,0 +1,164 @@
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+<%!
+       import datetime
+%>
+
+
+<%def name="title()">
+VM List
+</%def>
+
+<%def name="createForm()">
+% if cant_add_vm:
+<p>${cant_add_vm}</p>
+% else:
+<h2>Create a new VM</h2>
+% if err:
+<p class="error">We had a problem with your request:</p>
+% elif new_machine:
+<p>Congratulations! You successfully created a new VM called ${new_machine}.</p>
+% endif
+    <form action="create" method="POST">
+    <input type="hidden" name="back" value="list"/>
+      <table>
+       ${self.fn.errorRow('create', err)}
+       <tr>
+         <td>Name</td>
+         <td><input type="text" name="name" value="${defaults.name}"/>.${config.dns.domains[0]}</td>
+       </tr>
+       ${self.fn.errorRow('name', err)}
+       <tr>
+         <td>Description</td>
+         <td><textarea name="description" rows="4" cols="60">${defaults.description}</textarea></td>
+       </tr>
+       ${self.fn.errorRow('description', err)}
+       <tr>
+         <td>Memory</td>
+         <td><input type="text" name="memory" value="${defaults.memory}" size=3/> MiB (${max_memory} max)</td>
+       </tr>
+       ${self.fn.errorRow('memory', err)}
+       <tr>
+         <td>Disk</td>
+         <td><input type="text" name="disksize" value="${defaults.disk}" size=3/> GiB (${"%0.1f" % (max_disk-0.05)} max)</td>
+       </tr>
+       ${self.fn.errorRow('disk', err)}
+        <tr>
+          <td>HVM/ParaVM${self.fn.helppopup('HVM/ParaVM')}</td>
+          <td>
+         ${self.fn.vmTypeList(defaults.type)}
+         </td>
+        </tr>
+       ${self.fn.errorRow('vmtype', err)}
+       ${self.fn.errorRow('autoinstall', err)}
+       <tr>
+         <td>Autoinstall${self.fn.helppopup('Autoinstalls')}</td>
+         <td><input type="radio" name="cd_or_auto" id="cd_or_auto_auto"
+                 onchange="\$('cdromlist').value = ''; \$('vmtype-linux').checked = true">
+${self.fn.autoList(defaults.cdrom, "$('cd_or_auto_auto').checked = true;$('cdromlist').value = '';$('vmtype-linux').checked = true")}
+             (experimental; 2-3 minutes, and you have a machine with empty root password.)
+         </input>
+       </tr>
+       <tr>
+         <td>Boot CD</td>
+         <td><input type="radio" name="cd_or_auto" id="cd_or_auto_cd" checked="checked"
+                onchange="\$('autoinstalllist').value = ''; \$('vmtype-linux-hvm').checked = true">
+${self.fn.cdromList(defaults.cdrom, "$('cd_or_auto_cd').checked = true;$('autoinstalllist').value = '';$('vmtype-linux-hvm').checked = true")}
+</td>
+         </input>
+       </tr>
+       ${self.fn.errorRow('cdrom', err)}
+       ${self.fn.errorRow('cdrom', err)}
+       <tr>
+         <td>Owner</td>
+         <td><input type="text" name="owner" value="${defaults.owner}"/></td>
+       </tr>
+       ${self.fn.errorRow('owner', err)}
+      </table>
+      <input type="submit" class="button" value="Create it!"/><br />
+      Windows notes: ${self.fn.helppopup('Windows')}
+    </form>
+% endif
+</%def>
+
+<%def name="machineRow(machine)">
+      <tr> 
+       <td rowspan="2">
+       % if machine.uptime and installing[machine]:
+       <img src="static/power_installing.png" alt="Installing..." />
+       % else:
+         <form action="machine/${machine.machine_id}/command/${'shutdown' if machine.uptime else 'create'}" method="post">
+           <input type="hidden" name="back" value="list"/>
+           <input type="hidden" name="machine_id"
+                  value="${machine.machine_id}"/>
+<input type="submit" class="power ${'on' if machine.uptime else 'off'}" name="action" value="${'Shutdown' if machine.uptime else 'Power on'}"\
+% if machine.uptime:
+ onclick="return confirm('Are you sure you want to power off this VM?');"
+% endif
+/>
+         </form>
+         % endif
+       </td>
+       <td><a href="machine/${machine.machine_id}">${machine.name}</a></td>
+       <td>${machine.memory}M</td>
+       <td>${machine.owner}</td>
+       <td>${machine.administrator}</td>
+% if machine.nics:
+       <td>${', '.join(nic.ip for nic in machine.nics)}</td>
+% else:
+       <td></td>
+% endif
+<td>\
+% if machine.uptime:
+${datetime.timedelta(seconds=int(machine.uptime))}\
+% endif
+</td>
+       <td>\
+% if has_vnc[machine] == True:
+<a href="machine/${machine.machine_id}/vnc">Console</a>\
+% elif has_vnc[machine] == 'ParaVM':
+ParaVM${self.fn.helppopup("ParaVM Console")}
+% elif has_vnc[machine] != 'Off':
+${has_vnc[machine]}
+% endif
+</td>
+      </tr>
+      <tr>
+        <td colspan="7" style="padding-left: 1em; color: #666">${machine.description|self.fn.module.nl2br}</td>
+      </tr>
+</%def>
+
+<%def name="machineList(machines)">
+    <table cellspacing="0" cellpadding="2">
+      <tr>
+       <th></th>
+       <th>Name</th>
+       <th>Memory</th>
+       <th>Owner${self.fn.helppopup('Owner')}</th>
+        <th>Administrator${self.fn.helppopup('Administrator')}</th>
+       <th>IP</th>
+       <th>Uptime</th>
+       <th>VNC</th>
+      </tr>
+% for machine in machines:
+       ${machineRow(machine)}
+% endfor
+    </table>
+    <script type="text/javascript" src="/static/stripe.js"></script>
+    <script type="text/javascript">
+        document.observe("dom:loaded", function() {
+            stripe(\$('machinelist').getElementsByTagName('table')[0],
+                   'stripedrow');
+        });
+    </script>
+</%def>
+
+<p style="font-size: 125%;"><a href="http://${config.web.hostname}">What is XVM?</a></p>
+% if not machines:
+<p>You don't currently control any VMs.</p>   
+% endif
+    <p><a href="list">refresh</a></p>
+    <div id="machinelist">
+    ${machineList(machines)}
+    </div>
+${createForm()}
diff --git a/code/templates/list.tmpl b/code/templates/list.tmpl
deleted file mode 100644 (file)
index 8a0231e..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-#from skeleton import skeleton
-#from invirt.config import structs as config
-#extends skeleton
-#import datetime
-
-
-#def title
-VM List
-#end def
-
-#def createForm()
-#if $cant_add_vm
-<p>$cant_add_vm</p>
-#else
-<h2>Create a new VM</h2>
-#if $err
-<p class="error">We had a problem with your request:</p>
-#else if $varExists('new_machine')
-<p>Congratulations! You successfully created a new VM called $new_machine.</p>
-#end if
-    <form action="create" method="POST">
-    <input type="hidden" name="back" value="list"/>
-      <table>
-#filter None
-      $errorRow('create', $err)
-#end filter
-       <tr>
-         <td>Name</td>
-         <td><input type="text" name="name" value="$defaults.name"/>.${config.dns.domains[0]}</td>
-       </tr>
-#filter None
-$errorRow('name', $err)
-#end filter
-       <tr>
-         <td>Description</td>
-         <td><textarea name="description" rows="4" cols="60">$defaults.description</textarea></td>
-       </tr>
-#filter None
-$errorRow('description', $err)
-#end filter
-       <tr>
-         <td>Memory</td>
-         <td><input type="text" name="memory" value="$defaults.memory" size=3/> MiB ($max_memory max)</td>
-       </tr>
-#filter None
-$errorRow('memory', $err)
-#end filter
-       <tr>
-         <td>Disk</td>
-         <td><input type="text" name="disksize" value="$defaults.disk" size=3/> GiB (${"%0.1f" % ($max_disk-0.05)} max)</td>
-       </tr>
-#filter None
-$errorRow('disk', $err)
-#end filter
-        <tr>
-          <td>HVM/ParaVM#slurp
-#filter None
-$helppopup('HVM/ParaVM')#slurp
-#end filter
-</td>
-          <td>
-#filter None
-$vmTypeList($defaults.type)
-#end filter
-</td>
-        </tr>
-#filter None
-$errorRow('vmtype', $err)
-#end filter
-#filter None
-$errorRow('autoinstall', $err)
-#end filter
-       <tr>
-         <td>Autoinstall#slurp
-#filter None
-$helppopup('Autoinstalls')#slurp
-#end filter
-</td>
-         <td><input type="radio" name="cd_or_auto" id="cd_or_auto_auto"
-                 onchange="\$('cdromlist').value = ''; \$('vmtype-linux').checked = true">
-#filter None
-$autoList($defaults.cdrom, "$('cd_or_auto_auto').checked = true;$('cdromlist').value = '';$('vmtype-linux').checked = true")
-             (experimental; 2-3 minutes, and you have a machine with empty root password.)
-#end filter
-         </input>
-       </tr>
-       <tr>
-         <td>Boot CD</td>
-         <td><input type="radio" name="cd_or_auto" id="cd_or_auto_cd" checked="checked"
-                onchange="\$('autoinstalllist').value = ''; \$('vmtype-linux-hvm').checked = true">
-#filter None
-$cdromList($defaults.cdrom, "$('cd_or_auto_cd').checked = true;$('autoinstalllist').value = '';$('vmtype-linux-hvm').checked = true")
-#end filter
-</td>
-         </input>
-       </tr>
-$errorRow('cdrom', $err)
-$errorRow('cdrom', $err)
-       <tr>
-         <td>Owner</td>
-         <td><input type="text" name="owner" value="$defaults.owner"/></td>
-       </tr>
-#filter None
-       $errorRow('owner', $err)
-#end filter
-      </table>
-      <input type="submit" class="button" value="Create it!"/><br />
-      Windows notes: #slurp
-#filter None
-$helppopup('Windows')#slurp
-#end filter
-    </form>
-#end if
-#end def
-
-#def machineRow($machine)
-      <tr> 
-       <td rowspan="2">
-         <form action="command" method="post">
-           <input type="hidden" name="back" value="list"/>
-           <input type="hidden" name="machine_id"
-                  value="$machine.machine_id"/>
-<input type="submit" class="power #slurp
-#if $machine.uptime then 'on' else 'off'
-" name="action" value="#slurp
-#if $machine.uptime then 'Power off' else 'Power on'
-"#slurp
-#if $machine.uptime
- onclick="return confirm('Are you sure you want to power off this VM?');"
-#end if
-/>
-         </form>
-       </td>
-       <td><a href="info?machine_id=$machine.machine_id">$machine.name</a></td>
-       <td>${machine.memory}M</td>
-       <td>$machine.owner</td>
-       <td>$machine.administrator</td>
-#if $machine.nics
-#set $nic = $machine.nics[0]
-       <td>$nic.ip</td>
-#else
-       <td></td>
-#end if
-<td>#slurp
-#if $machine.uptime
-${datetime.timedelta(seconds=int(machine.uptime))}#slurp
-#end if
-</td>
-       <td>#slurp
-#if $has_vnc[$machine] == True
-<a href="vnc?machine_id=$machine.machine_id">Console</a>#slurp
-#else if $has_vnc[$machine] != 'Off'
-#filter None
-$has_vnc[$machine]
-#end filter
-#end if
-</td>
-      </tr>
-      <tr>
-        <td colspan="7" style="padding-left: 1em; color: #666">$machine.description</td>
-      </tr>
-#end def
-
-#def machineList($machines)
-    <table cellspacing="0" cellpadding="2">
-      <tr>
-       <th></th>
-       <th>Name</th>
-       <th>Memory</th>
-       <th>Owner#slurp
-#filter None
-$helppopup('Owner')#slurp
-#end filter
-</th>
-        <th>Administrator#slurp
-#filter None
-$helppopup('Administrator')#slurp
-#end filter
-</th>
-       <th>IP</th>
-       <th>Uptime</th>
-       <th>VNC</th>
-      </tr>
-      #for $machine in $machines:
-    #filter None
-       $machineRow($machine)
-    #end filter
-      #end for
-    </table>
-    <script type="text/javascript" src="/static/stripe.js"></script>
-    <script type="text/javascript">
-        document.observe("dom:loaded", function() {
-            stripe(\$('machinelist').getElementsByTagName('table')[0],
-                   'stripedrow');
-        });
-    </script>
-#end def
-
-
-#def body
-<p style="font-size: 125%;"><a href="http://${config.web.hostname}">What is XVM?</a></p>
-#if not $machines
-<p>You don't currently control any VMs.</p>   
-#end if
-    <p><a href="list">refresh</a></p>
-    <div id="machinelist">
-    #filter None
-    $machineList($machines)
-    #end filter
-    </div>
-#filter None
-$createForm()
-#end filter
-#end def
diff --git a/code/templates/skeleton.mako b/code/templates/skeleton.mako
new file mode 100644 (file)
index 0000000..a53d2bf
--- /dev/null
@@ -0,0 +1,88 @@
+<%page expression_filter="h"/>
+<%namespace name="fn" file="functions.mako" inheritable="True"/>
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head><title>${self.title()} &mdash; XVM</title>
+  <base href="${cherrypy.request.base}${"/admin/" if cherrypy.request.state.isadmin else ""}" />
+  <link href="/static/favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <link rel="stylesheet" href="/static/style.css" type="text/css" />
+  <link rel="stylesheet" href="/static/layout.css" type="text/css" media="screen" />
+  <script type="text/javascript" src="/static/prototype.js"></script>
+  <script type="text/javascript">
+var helpWin = null;
+function closeWin(){
+       if (helpWin != null){
+               if(!helpWin.closed)
+                       helpWin.close();
+       }
+}
+
+function helppopup(name){
+   closeWin()
+   helpWin = window.open("help?simple=true&subject="+encodeURIComponent(name), "Help",
+"status, height = 300, width = 400");
+   if (window.focus){helpWin.focus();}
+   return false;
+}
+</script>
+</head>
+<body id="body"
+% if hasattr(self.attr, 'pageclass'):
+  class="${self.attr.pageclass}"
+% endif
+  >
+
+% if False:
+<div>
+<p>We are in the process of modifying the service.  Things likely will not work.</p>
+</div>
+% endif
+
+% if error_text is not UNDEFINED:
+<div id="err">
+<p>STDERR:</p><pre>${error_text}</pre>
+</div>
+% endif
+
+% if not simple:
+% if cherrypy.request.login:
+<p class="loggedin">Welcome, <span class="name">${cherrypy.request.login}</span>.
+% if cherrypy.request.state.isadmin:
+You are currently authenticated as an administrator.
+% endif
+</p>
+% endif
+
+<ul class="navigation">
+<li><a href="list">List</a></li>
+% if machine:
+<li><a href="machine/${machine.machine_id}">Info</a></li>
+<li><a href="machine/${machine.machine_id}/vnc">Console</a></li>
+% endif
+<li><a href="help">Help</a></li>
+</ul>
+
+% endif
+
+<div id="result" class="result">
+% if result:
+${result}
+% endif
+</div>
+
+% if not simple:
+<h1>${self.title()} &mdash; XVM</h1>
+% endif
+${next.body()}
+% if not simple:
+<hr />
+Questions? Contact <a href="mailto:xvm@mit.edu">xvm@mit.edu</a>.
+% endif
+</body>
+</html>
+
+<%def name="title()">
+XVM
+</%def>
diff --git a/code/templates/skeleton.tmpl b/code/templates/skeleton.tmpl
deleted file mode 100644 (file)
index e71348c..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#from functions import functions
-#extends functions
-
-#def full_body
-<!DOCTYPE html
-PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html>
-<head><title>$title &mdash; XVM</title>
-  <link href="static/favicon.ico" type="image/x-icon" rel="shortcut icon">
-  <link rel="stylesheet" href="static/style.css" type="text/css" />
-  <link rel="stylesheet" href="static/layout.css" type="text/css" media="screen" />
-  <script type="text/javascript" src="static/prototype.js"></script>
-  <script type="text/javascript">
-var helpWin = null;
-function closeWin(){
-       if (helpWin != null){
-               if(!helpWin.closed)
-                       helpWin.close();
-       }
-}
-
-function helppopup(name){
-   closeWin()
-   helpWin = window.open("help?simple=true&subject="+encodeURIComponent(name), "Help",
-"status, height = 300, width = 400");
-   if (window.focus){helpWin.focus();}
-   return false;
-}
-</script>
-</head>
-<body id="body"
-#if hasattr($self, 'pageclass'):
-  class="$pageclass"
-#end if
-  >
-
-#if False
-<div>
-<p>We are in the process of modifying the service.  Things likely will not work.</p>
-</div>
-#end if
-
-<div id="err">
-#if $varExists('error_text')
-<p>STDERR:</p><pre>$error_text</pre>
-#end if
-</div>
-
-#if not $varExists('simple') or not $simple
-<p class="loggedin">Welcome, <span class="name">$user</span>.</p>
-
-<ul class="navigation">
-<li><a href="list">List</a></li>
-#if $varExists('machine')
-<li><a href="info?machine_id=$machine.machine_id">Info</a></li>
-<li><a href="vnc?machine_id=$machine.machine_id">Console</a></li>
-#end if
-<li><a href="help">Help</a></li>
-</ul>
-#end if
-<div id="result" class="result">
-#if $varExists('result')
-$result
-#end if
-</div>
-
-#if not $varExists('simple') or not $simple
-<h1>$title &mdash; XVM</h1>
-#end if
-#filter None
-$body
-#end filter
-#if not $varExists('simple') or not $simple
-<hr />
-Questions? Contact <a href="mailto:xvm@mit.edu">xvm@mit.edu</a>.
-#end if
-</body>
-</html>
-#end def
similarity index 90%
rename from code/templates/unauth.tmpl
rename to code/templates/unauth.mako
index 0715f2c..562481f 100644 (file)
@@ -1,12 +1,10 @@
-#from skeleton import skeleton
-#extends skeleton
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
 
-
-#def title
+<%def name="title()">
 Intro
-#end def
+</%def>
 
-#def body
 <h1>XVM &mdash; Virtual Servers for MIT </h1>
 
 <p><strong>xvm.mit.edu</strong> is a virtualization service for the
@@ -18,7 +16,7 @@ to any Athena account holder.</p>
 
 <p>MIT users:</p>
 <blockquote><big><a
-href="https://$hostname/"><strong><font color="green">&rarr;</font> Log in to XVM now</strong>
+href="https://${config.web.hostname}/"><strong><font color="green">&rarr;</font> Log in to XVM now</strong>
 </a></big></blockquote>
 <p>You'll need an <a href="http://ca.mit.edu/">MIT personal
 certificate</a>.</p>
@@ -56,5 +54,3 @@ contributors.</p>
 
 <p>Questions and feedback welcome at <a
 href="mailto:xvm@mit.edu">xvm@mit.edu</a>.</p>
-
-#end def
diff --git a/code/templates/vnc.mako b/code/templates/vnc.mako
new file mode 100644 (file)
index 0000000..867a145
--- /dev/null
@@ -0,0 +1,23 @@
+<%page expression_filter="h" />
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+Console to ${machine.name}
+</%def>
+
+<style type='text/css'>body { max-width: none }</style>
+% if not on:
+<p> Your machine appears to be off.</p>
+% elif not has_vnc:
+<p> Your machine appears to not be accepting VNC connections. Perhaps you have a ParaVM machine?</p>
+% endif
+
+<p>See <a href="help?subject=Console" target="_blank">tips</a> about framebuffer and other issues.</p>
+<applet code="VncViewer.class" archive="https://${hostname}:446/static/VncViewer.jar"
+        width="100%" height="1000">
+<param name="PORT" value="${port}">
+<param name="HOST" value="${hostname}">
+<param name="VMNAME" value="${machine.name}">
+<param name="AUTHTOKEN" value="${authtoken}">
+<param name="SocketFactory" value="VNCProxyConnectSocketFactory">
+</applet>
diff --git a/code/templates/vnc.tmpl b/code/templates/vnc.tmpl
deleted file mode 100644 (file)
index c61b4f6..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Console to $machine.name
-#end def
-
-#def body
-<style type='text/css'>body { max-width: none }</style>
-#if not $on
-<p> Your machine appears to be off.</p>
-#else if not $has_vnc
-<p> Your machine appears to not be accepting VNC connections. Perhaps you have a ParaVM machine?</p>
-#end if
-<p>See <a href="help?subject=Console" target="_blank">tips</a> about framebuffer and other issues.</p>
-<APPLET CODE="VncViewer.class" ARCHIVE="https://$hostname:446/static/VncViewer.jar"
-        WIDTH="100%" HEIGHT="1000">
-<PARAM NAME="PASSWORD" VALUE="moocow">
-<PARAM NAME="PORT" VALUE="$port">
-<PARAM NAME="HOST" VALUE="$hostname">
-<PARAM NAME="VMNAME" VALUE="$machine.name">
-<PARAM NAME="AUTHTOKEN" VALUE="$authtoken">
-<PARAM NAME="SocketFactory" VALUE="VNCProxyConnectSocketFactory">
-</APPLET>
-#end def
diff --git a/code/unauth.fcgi b/code/unauth.fcgi
new file mode 120000 (symlink)
index 0000000..b94e858
--- /dev/null
@@ -0,0 +1 @@
+invirt.fcgi
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index 875115c..9b7a0b0
@@ -258,9 +258,6 @@ def testContact(user, contact, machine=None):
         raise InvalidInput('contact', contact, "Not a valid email.")
     return contact
 
-def testDisk(user, disksize, machine=None):
-    return disksize
-
 def testName(user, name, machine=None):
     if name is None:
         return None
diff --git a/code/view.py b/code/view.py
new file mode 100644 (file)
index 0000000..9452cf0
--- /dev/null
@@ -0,0 +1,168 @@
+import os, sys
+
+import cherrypy
+from mako.template import Template
+from mako.lookup import TemplateLookup
+import simplejson
+import datetime, decimal
+from StringIO import StringIO
+from invirt.config import structs as config
+from webcommon import State
+
+
+class MakoHandler(cherrypy.dispatch.LateParamPageHandler):
+    """Callable which processes a dictionary, returning the rendered body."""
+    
+    def __init__(self, template, next_handler,
+                 content_type='text/html; charset=utf-8'):
+        self.template = template
+        self.next_handler = next_handler
+        self.content_type = content_type
+    
+    def __call__(self):
+        env = globals().copy()
+        env.update(self.next_handler())
+        cherrypy.response.headers['Content-Type'] = self.content_type
+        return self.template.render(**env)
+        
+
+class MakoLoader(object):
+    
+    def __init__(self):
+        self.lookups = {}
+
+    def get_lookup(self, directories, module_directory=None,
+                     collection_size=-1, imports=[], **kwargs):
+        # Find the appropriate template lookup.
+        key = (tuple(directories), module_directory)
+        try:
+            lookup = self.lookups[key]
+        except KeyError:
+            lookup = TemplateLookup(directories=directories,
+                                    module_directory=module_directory,
+                                    collection_size=collection_size,
+                                    default_filters=['decode.utf8'],
+                                    input_encoding='utf-8',
+                                    output_encoding='utf-8',
+                                    imports=imports,
+                                    )
+            self.lookups[key] = lookup
+        return lookup
+
+    def __call__(self, filename, directories, module_directory=None,
+                 collection_size=-1, content_type='text/html; charset=utf-8',
+                 imports=[]):
+        cherrypy.request.lookup = lookup = self.get_lookup(
+            directories, module_directory, collection_size, imports)
+        cherrypy.request.template = t = lookup.get_template(filename)
+        cherrypy.request.handler = MakoHandler(
+            t, cherrypy.request.handler, content_type)
+
+cherrypy.tools.mako = cherrypy.Tool('on_start_resource', MakoLoader())
+
+
+def revertStandardError():
+    """Move stderr to stdout, and return the contents of the old stderr."""
+    errio = sys.stderr
+    if not isinstance(errio, StringIO):
+        return ''
+    sys.stderr = sys.stdout
+    errio.seek(0)
+    return errio.read()
+
+
+def catchStderr():
+    old_handler = cherrypy.request.handler
+    def wrapper(*args, **kwargs):
+        sys.stderr = StringIO()
+        ret = old_handler(*args, **kwargs)
+        e = revertStandardError()
+        if e:
+            if isinstance(ret, dict):
+                ret["error_text"] = e
+        return ret
+    if old_handler:
+        cherrypy.request.handler = wrapper
+
+cherrypy.tools.catch_stderr = cherrypy.Tool('before_handler', catchStderr)
+
+
+class JSONEncoder(simplejson.JSONEncoder):
+       def default(self, obj):
+               if isinstance(obj, datetime.datetime):
+                       return str(obj)
+               elif isinstance(obj, decimal.Decimal):
+                       return float(obj)
+               else:
+                       return simplejson.JSONEncoder.default(self, obj)
+
+
+def jsonify_tool_callback(*args, **kwargs):
+    if not cherrypy.request.cached:
+        response = cherrypy.response
+        response.headers['Content-Type'] = 'text/javascript'
+        response.body = JSONEncoder().iterencode(response.body)
+
+cherrypy.tools.jsonify = cherrypy.Tool('before_finalize',
+                                       jsonify_tool_callback, priority=30)
+
+
+def require_login():
+    """If the user isn't logged in, raise 403 with an error."""
+    if cherrypy.request.login is False:
+        raise cherrypy.HTTPError(403,
+            "You are not authorized to access that resource")
+
+cherrypy.tools.require_login = cherrypy.Tool('on_start_resource',
+                                             require_login, priority=150)
+
+
+def require_POST():
+    """If the request isn't a POST request, raise 405 Method Not Allowed"""
+    if cherrypy.request.method != "POST":
+        raise cherrypy.HTTPError(405,
+                                 "You must submit this request with POST")
+
+cherrypy.tools.require_POST = cherrypy.Tool('on_start_resource',
+                                            require_POST, priority=150)
+
+
+def remote_user_login():
+    """Get remote user from SSL or GSSAPI, and store in request object.
+
+Get the current user based on environment variables set by SSL or
+GSSAPI, and store it in the attribute cherrpy.request.login.
+
+Per the CherryPy API (http://www.cherrypy.org/wiki/RequestObject#login),
+the attribute is set to the username on successful login, to False on
+failed login, and is left at None if the user attempted no authentication.
+"""
+    environ = cherrypy.request.wsgi_environ
+    user = environ.get('REMOTE_USER')
+    if user is None:
+        return
+    if environ.get('AUTH_TYPE') == 'Negotiate':
+        # Convert the krb5 principal into a krb4 username
+        if not user.endswith('@%s' % config.kerberos.realm):
+            cherrypy.request.login = False # failed to log in
+        else:
+            cherrypy.request.login = user.split('@')[0].replace('/', '.')
+    else:
+        cherrypy.request.login = user
+
+cherrypy.tools.remote_user_login = cherrypy.Tool('on_start_resource',
+                                                 remote_user_login, priority=50)
+
+
+def invirtwebstate_init():
+    """Initialize the cherrypy.request.state object from Invirt"""
+    if not hasattr(cherrypy.request, "state"):
+        cherrypy.request.state = State(cherrypy.request.login)
+
+cherrypy.tools.invirtwebstate = cherrypy.Tool('on_start_resource',
+                                              invirtwebstate_init, priority=100)
+
+
+class View(object):
+    _cp_config = {'tools.mako.directories':
+                      [os.path.join(os.path.dirname(__file__),'templates')]}
index 5479ee5..b37745c 100644 (file)
@@ -1,3 +1,21 @@
+invirt-web (0.1.0) unstable; urgency=low
+
+  [Quentin Smith]
+  * Switch to CherryPy in place of our home-grown web framework.
+  * Switch from the Cheetah templating engine to the Mako templating engine.
+  * New URI scheme: /machine/<numeric-id>/<operation>
+    rather than /<operation>?machine_id=<numeric-id> .
+  * Fix power-on/power-off/reboot buttons for IE <=8.
+  * Move some bits of presentation code from Python into templates.
+  * Clarify that Windows licenses are available from MIT for staff.
+
+  [Evan Broder]
+  * Show newlines from descriptions in list page.
+  * Only aklog to a cell if encryption is actually needed.
+  * Re-arrange the authz configuration.
+
+ -- Greg Price <price@mit.edu>  Sat, 19 Dec 2009 21:53:40 -0500
+
 invirt-web (0.0.24) unstable; urgency=low
 
   * Update authorization code for new config structure.
index 386b107..e22a9ff 100644 (file)
@@ -16,8 +16,9 @@ Depends: ${misc:Depends},
  libapache2-mod-auth-sslcert, libapache2-mod-auth-kerb,
  debathena-ssl-certificates,
 # python libraries
- python-flup, python-cheetah, python-simplejson,
- python-dns, python-dnspython,
+ python-flup, python-simplejson,
+ python-dns, python-dnspython, python-cherrypy3,
+ python-mako,
 # misc
  kstart,
  debathena-afs-config, openafs-modules-xen,
index 361ca8f..e37a78f 100644 (file)
@@ -14,3 +14,8 @@ Copyright :
 
 On Debian systems, the complete text of the GNU General Public License
 can be found in the file /usr/share/common-licenses/GPL.
+
+The file "code/static/power_installing.png" is from the Human-O2 icon
+set by Oliver Scholtz and is released under the "GNU/GPL" (source:
+http://www.iconfinder.net/icondetails/24350/128/ -
+http://schollidesign.deviantart.com/art/Human-O2-Iconset-105344123)
index dcbc191..ac0cf4f 100644 (file)
@@ -22,10 +22,8 @@ NameVirtualHost *:80
        RewriteRule ^/admin/static(.*) /static/$1 [L]
        RewriteRule ^/trac(.*) ${tracuri}$1 [R,L]
        RewriteRule ^/invirt - [L]
-       RewriteRule ^/sipb-xen(.*) /invirt$1 [PT]
        RewriteRule ^/kill.cgi - [L]
-       RewriteRule ^/~ - [L]
-       RewriteRule ^/(.*) /var/www/invirt-web/main.fcgi/$1 [L]
+       RewriteRule ^/(.*) /var/www/invirt-web/unauth.fcgi/$1 [L]
 
        ErrorLog /var/log/apache2/error.log
 
index 3ce7e5f..372563f 100644 (file)
@@ -26,8 +26,7 @@ ${caller.body()}
        RewriteRule ^/admin/static(.*) /static/$1 [L]
        RewriteRule ^/trac(.*) ${tracuri}$1 [R,L]
        RewriteRule ^/kill.cgi - [L]
-       RewriteRule ^/~ - [L]
-       RewriteRule ^/(.*) /var/www/invirt-web/main.fcgi/$1 [L]
+       RewriteRule ^/(.*) /var/www/invirt-web/auth.fcgi/$1 [L]
 
        RewriteLog /var/log/apache2/rewrite.log
        RewriteLogLevel 0