From: Joe Presbrey Date: Fri, 13 Mar 2009 11:50:51 +0000 (+0000) Subject: implemented base interface X-Git-Url: http://andersk.mit.edu/gitweb/sql.git/commitdiff_plain/e4fd5e1b03c4d9aa73a7be54558fcc61de619531 implemented base interface git-svn-id: svn://presbrey.mit.edu/sql@171 a142d4bd-2cfb-0310-9673-cb33a7e74f58 --- diff --git a/etc/mitsql.ini b/etc/mitsql.ini new file mode 100644 index 0000000..7220cfb --- /dev/null +++ b/etc/mitsql.ini @@ -0,0 +1,5 @@ +[_DEFAULT] +db_host=sql.mit.edu +db_user=root +db_pass= +db_name=mitsql diff --git a/lib/python/mitsql/__init__.py b/lib/python/mitsql/__init__.py index e69de29..4282e02 100644 --- a/lib/python/mitsql/__init__.py +++ b/lib/python/mitsql/__init__.py @@ -0,0 +1,10 @@ +import config +import os as _os +if False and config._ENV_BASE in _os.environ.keys(): + _base = _os.environ[config._ENV_BASE] +else: + _base = _os.path.abspath(_os.path.dirname(_os.path.realpath(__file__)) + '/../../../') +config = config.Config('mitsql.ini') + +import logging +logging.disable(logging.DEBUG) \ No newline at end of file diff --git a/lib/python/mitsql/config.py b/lib/python/mitsql/config.py new file mode 100644 index 0000000..4c8e3f0 --- /dev/null +++ b/lib/python/mitsql/config.py @@ -0,0 +1,83 @@ +""" +Global configuration objects. + +Joe Presbrey +""" + +from ConfigParser import ConfigParser, NoSectionError, NoOptionError +import os + +_ENV_BASE='_SQL_MIT_EDU' + +class Section(object): + """Configuration section namespace.""" + def __init__(self, d): + self.items = d + + def __getattr__(self, k): + v = self.items.get(k, None) + try: + if str(int(v)) == str(v): v = int(v) + except TypeError: pass + except ValueError: pass + try: + if str(v).lower() == 'true': v = True + if str(v).lower() == 'false': v = False + except TypeError: pass + except ValueError: pass + return v + + def __iter__(self): + return iter(self.items) + + def __str__(self): + return str(self.items) + +class Config(object): + """Base configuration namespace.""" + def __init__(self, filename, *args, **kwargs): + self._cp = ConfigParser(*args, **kwargs) + self.read(filename) + self._none = Section({}) + + def _samefile(self, f1, f2): + try: + return os.path.samefile(f1, f2) + except OSError, e: + return False + + def read(self, filename): + config_path = [os.path.join(x, filename) for x in [ + os.path.dirname(__file__), + '/etc']] + if _ENV_BASE in os.environ: + config_path.insert(1, os.path.join(os.environ[_ENV_BASE], 'etc', filename)) + def append(path): + r = os.path.join(path, filename) + if not r in config_path: + config_path.append(r) + if 'HOME' in os.environ: + append(os.environ['HOME']) + if 'PWD' in os.environ: + append(os.environ['PWD']) + self._cp.read(config_path) + self._sections = dict(map(lambda x: (x, Section(dict(self._cp.items(x)))), + self._cp.sections())) + + def get(self, *av, **kw): + try: return self._cp.get(*av, **kw) + except NoSectionError: return None + except NoOptionError: return None + + def __getattr__(self, k): + return self._sections.get(k, self._none) + + def __str__(self): + d = {} + for x in self._cp.sections(): + d[x] = dict(self._cp.items(x)) + return str(d) + +if __name__ == '__main__': + import mitsql + print mitsql.config diff --git a/lib/python/mitsql/interface/__init__.py b/lib/python/mitsql/interface/__init__.py index 41c4f7a..4636607 100644 --- a/lib/python/mitsql/interface/__init__.py +++ b/lib/python/mitsql/interface/__init__.py @@ -1,7 +1,24 @@ -import cherrypy +import mitsql +config = mitsql.config.interface + +from MySQLdb import Connection as _connect +from MySQLdb.cursors import DictCursor as _cursor + +def _connect(*argv, **kw): + return _connect(config.db_host, config.db_user, config.db_pass, config.db_name, cursorclass=_Cursor) -from main import Root as root -root = root() +from handler import Root as _root +root = _root() + +from cheetah import load +load(mitsql._base + '/www/templates') + +import cherrypy +cherrypy.tree.mount(root, '/') +cherrypy.config.update({'error_page.404': root.error._404, + 'error_page.500': root.error._500, + 'tools.staticdir.on': True, + 'tools.staticdir.dir': mitsql._base + '/www/root'}) def map_uri(): print root.default() @@ -11,5 +28,5 @@ def map_uri(): # if authorization: # cherrypy.request.login = parseAuthorization(authorization) -cherrypy.tools.map_uri = cherrypy.Tool('on_start_resource', map_uri) +#cherrypy.tools.map_uri = cherrypy.Tool('on_start_resource', map_uri) #cherrypy.tools.map_uri = cherrypy.Tool('before_request_body', map_uri) diff --git a/lib/python/mitsql/templates.py b/lib/python/mitsql/interface/cheetah.py similarity index 61% rename from lib/python/mitsql/templates.py rename to lib/python/mitsql/interface/cheetah.py index f728005..22a2b17 100644 --- a/lib/python/mitsql/templates.py +++ b/lib/python/mitsql/interface/cheetah.py @@ -1,16 +1,15 @@ -# Joe Presbrey - +import qwobl import os, sys, types, time import threading from pyinotify import WatchManager, Notifier, EventsCodes from Cheetah.Template import Template as _Template - -__all__ = ['Templates'] +import logging class Template(object): - def __init__(self, name, ns={}, path=['templates']): - self._name, self._ns, self._path = name, ns, path+[name] - assert self._template() + def __init__(self, name='templates', ns={}, path=[]): + self._name, self._ns, self._path = name, ns, len(path) and path+[name] or [name] + if len(self._path) > 1: + assert self._template() def _template(self): return sys.modules['.'.join(self._path)] @@ -24,8 +23,7 @@ class Template(object): return getattr(self._template(), self._name)(namespaces=namespaces).respond() -class Templates(object): - _base = '' +class Loader(object): _mask = EventsCodes.ALL_FLAGS['IN_CREATE'] \ | EventsCodes.ALL_FLAGS['IN_MODIFY'] \ | EventsCodes.ALL_FLAGS['IN_DELETE'] \ @@ -38,12 +36,10 @@ class Templates(object): _files = {} _modules = {} - def __init__(self, template_path, global_namespace={}): - self._base = os.path.abspath(template_path) + def __init__(self, global_namespace={}): self._event_init() - self._assert_module('templates', [template_path]) + self._assert_module('templates') self._globals = global_namespace - self.load() self._thread = threading.Thread(target=self.loop) self._thread.setDaemon(True) self._thread.start() @@ -71,9 +67,9 @@ class Templates(object): self._modules[name].__path__ = path sys.modules[name] = self._modules[name] - def _get_template_path(self, template_file): - r = template_file.startswith(self._base) \ - and template_file[len(self._base)+1:].split(os.path.sep) or [] + def _get_template_path(self, path_name, template_file): + r = template_file.startswith(path_name) \ + and template_file[len(path_name)+1:].split(os.path.sep) or [] if r[-1].endswith('.tmpl'): r[-1] = r[-1][:-5] return tuple(r) @@ -87,8 +83,8 @@ class Templates(object): compilerSettings={'useStackFrames':False}) return template - def add(self, template_file): - template_path = self._get_template_path(template_file) + def add(self, path_name, template_file): + template_path = self._get_template_path(path_name, template_file) template_name = '.'.join(['templates']+list(template_path)) template_pkg = '.'.join(['templates']+list(template_path[:-1])) template_py = os.path.join(os.path.dirname(template_file), '%s.py' % template_path[-1]) @@ -98,47 +94,51 @@ class Templates(object): f_code = file(template_py, 'w') f_code.write(code) f_code.close() - if self._loaded and \ - template_name in sys.modules: + if template_name in sys.modules: reload(sys.modules[template_name]) def remove(self, template_file): #print 'removing', template_file pass - def _walk_load(self, args, dname, fnames): - for fname in fnames: - if fname.endswith('.tmpl'): - template_file = os.path.join(dname, fname) - self.add(template_file) - - def load(self): - if not self._loaded: - os.path.walk(self._base, self._walk_load, None) - for path, filename in self._files.items(): - __import__('.'.join(['templates']+list(path))) - self._loaded = True - - def _event_handler(self, event): - if event.name.endswith('.tmpl') and not event.name.startswith('.'): - if event.maskname == 'IN_DELETE': - self.remove(event.pathname) - else: - time.sleep(0.5) - self.add(event.pathname) - + def _walk_path(self, path_name): + def _walk_load(args, dname, fnames): + for fname in fnames: + if fname.endswith('.tmpl'): + template_file = os.path.join(dname, fname) + self.add(path_name, template_file) + return _walk_load + + def load(self, path_name): + os.path.walk(path_name, self._walk_path(path_name), None) + for path, filename in self._files.items(): + logging.debug('TEMPLATE LOAD [%s] %s', path, filename) + __import__('.'.join(['templates']+list(path))) + self._event_add_watch(path_name) + + def _path_event_handler(self, path_name): + def _event_handler(event): + logging.debug('TEMPLATE EVENT: %s', repr(event.__dict__.items())) + if event.name.endswith('.tmpl') and not event.name.startswith('.'): + if event.maskname == 'IN_DELETE': + self.remove(event.pathname) + else: + time.sleep(0.5) + self.add(path_name, event.pathname) + return _event_handler def _event_init(self): self._watch_manager = WatchManager() - self._watch_manager.add_watch(self._base, - self._mask, - self._event_handler, - rec=True, - auto_add=True) self._notifier = Notifier(self._watch_manager) + def _event_add_watch(self, path_name): + r = self._watch_manager.add_watch(path_name, + self._mask, + self._path_event_handler(path_name), + rec=True, + auto_add=True) + def loop(self): - self.load() while True: try: self._notifier.process_events() @@ -147,9 +147,12 @@ class Templates(object): except KeyboardInterrupt: self._notifier.stop() break - - def __getattr__(self, key): - return Template(key, self._globals) + +_loader = Loader() +load = _loader.load +templates = Template() + +__all__ = ['load', 'templates'] if __name__ == '__main__': - t = Templates('/home/joe/templates') + t = Loader('/home/joe/templates') diff --git a/lib/python/mitsql/interface/display.py b/lib/python/mitsql/interface/display.py new file mode 100644 index 0000000..e39d1ae --- /dev/null +++ b/lib/python/mitsql/interface/display.py @@ -0,0 +1,2 @@ +def db_list_entry(name, size_tot, size_max): + return '%s: %s/%s' % (name, size_tot, size_max) \ No newline at end of file diff --git a/lib/python/mitsql/interface/handler.py b/lib/python/mitsql/interface/handler.py new file mode 100644 index 0000000..14eac9e --- /dev/null +++ b/lib/python/mitsql/interface/handler.py @@ -0,0 +1,48 @@ +import mitsql +from cheetah import templates +import cherrypy + +from pprint import pformat + +def _500(*argv, **kw): + s_exc = cherrypy._cperror.format_exc() + mitsql.logging.error(s_exc) + return templates.common.shell(title='%s (CherryPy %s)' % (kw.get('status'), kw.get('version')), + content=('

%s

' + (2*'
%s
')) + % (kw.get('message'), kw.get('traceback'), + _phtml(dict(filter(lambda x: not x[0][:2] == '__', + cherrypy.request.__dict__.items()))))) + +class Root(object): + class Error(object): + @cherrypy.expose + def _404(*argv, **kw): + return templates.common._404() + def _500(*argv, **kw): + return _500(*argv, **kw) + error = Error() + + class DB(object): + def list(*argv, **kw): + r = {'db_list': [], + 'size_tot': '1M', + 'size_max': '100M', + 'user': { + 'db_prefix': 'presbrey+' + }} + return templates.main.db_list(**r) + list.exposed = True + db = DB() + + def test(self, *argv, **kw): + cherrypy.response.headers['Content-type'] = 'text/plain' + return pformat(dict(filter(lambda x: not x[0][:2] == '__', cherrypy.request.__dict__.items()))) + test.exposed = True + + def index(self, *argv, **kw): + return templates.main.index() + index.exposed = True + + def passwd(self, *argv, **kw): + return templates.main.passwd() + passwd.exposed = True \ No newline at end of file diff --git a/lib/python/mitsql/interface/main.py b/lib/python/mitsql/interface/main.py deleted file mode 100644 index 39f8591..0000000 --- a/lib/python/mitsql/interface/main.py +++ /dev/null @@ -1,13 +0,0 @@ -import cherrypy -from pprint import pformat - -class Root(object): - def default(self, *args, **kw): - cherrypy.response.headers['Content-type'] = 'text/plain' - return pformat(dict(filter(lambda x: not x[0][:2] == '__', cherrypy.request.__dict__.items()))) - default.exposed = True - - def test(self): - return 'test' - test.exposed = True - diff --git a/libexec/daily_afs_backups b/libexec/daily_afs_backups index 4346483..08b9535 100755 --- a/libexec/daily_afs_backups +++ b/libexec/daily_afs_backups @@ -1,7 +1,7 @@ #!/usr/bin/python import os, sys, time -import sql.db +from sql import db from sql.util import new_cursor, get_dbs, db_backup_pre, db_backup_mkdir from Queue import Queue, Empty import threading @@ -21,11 +21,11 @@ def consumer(): try: next = queue.get(timeout=3) print next[0] + ':', - log = sql.db.Backup.get_by(db=next[0]) + log = db.Backup.get_by(db=next[0]) if not log: - log = sql.db.Backup(db=next[0]) + log = db.Backup(db=next[0]) log.dump_path = next[1] - log.dump_date = sql.db.func.now() + log.dump_date = db.func.now() db_backup_mkdir(next[1]) args = ['mysqldump', next[0]] args.extend(MYSQLDUMP_ARGS) @@ -40,7 +40,7 @@ def consumer(): log.dump_errnum = None log.dump_errstr = None log.save_or_update() - sql.db.session.flush() + db.session.flush() print 'Done' except (KeyboardInterrupt, SystemExit): break @@ -54,9 +54,9 @@ t_consumer.start() def producer(): c = new_cursor('mysqldump') for db in get_dbs(c): - log = sql.db.Backup.get_by(db=db) + log = db.Backup.get_by(db=db) if not log: - log = sql.db.Backup(db=db) + log = db.Backup(db=db) elif log.skip_date: if time.mktime(log.skip_date.timetuple()) + 3600 > time.time(): # never recheck a db skipped in the past hour @@ -68,9 +68,9 @@ def producer(): log.skip_date = None else: log.skip_reason = d[1] - log.skip_date = sql.db.func.now() + log.skip_date = db.func.now() log.save_or_update() - #sql.db.session.flush() + #db.session.flush() try: producer() diff --git a/sbin/start b/sbin/start new file mode 100755 index 0000000..846b03a --- /dev/null +++ b/sbin/start @@ -0,0 +1,3 @@ +#!/bin/bash + +exec cherryd -c $_SQL_MIT_EDU/www/etc/devel.conf -i mitsql.interface diff --git a/www/etc/devel.conf b/www/etc/devel.conf new file mode 100644 index 0000000..bdb7cfc --- /dev/null +++ b/www/etc/devel.conf @@ -0,0 +1,8 @@ +[global] +server.socket_host: "0.0.0.0" +server.socket_port: 8336 +tools.encode.on: True +tools.encode.encoding: 'utf-8' +#tools.map_uri.on: True +#tools.header_auth.on: True +#request.show_tracebacks: True diff --git a/www/etc/live.conf b/www/etc/live.conf new file mode 100644 index 0000000..ef39428 --- /dev/null +++ b/www/etc/live.conf @@ -0,0 +1,11 @@ +[global] +tree.root: mitsql.interface.root +server.socket_file: '/tmp/mitsql.sock' + +tools.encode.on: True +tools.encode.encoding: 'utf-8' +#tools.map_uri.on: True +#autoreload.on: False +#checker.on: False +#engine.SIGHUP: None +#engine.SIGTERM: None diff --git a/www/root/assets/css/style.css b/www/root/assets/css/style.css new file mode 100644 index 0000000..687aeed --- /dev/null +++ b/www/root/assets/css/style.css @@ -0,0 +1,112 @@ +html,body,div,span,form,h1,h2,h3,h4,p,blockquote{margin:0;padding:0;border:0;outline:0;} + +/* ------- Layout CSS (Centered Fixed Width) ------ */ + +body { + font-family: Verdana, sans-serif; + font-size: 12px; + margin: 0; + padding: 0; + text-align: center; /* IE 5.5 hack */ +} +#farouter { + background: #ffffff; + width: 80%; + border: 1px solid #3A291F; + margin: 10px auto 10px auto; + text-align: left; /* IE 5.5 hack part II */ +} +#outer div#masthead { margin: 1em; } +#outer div#content_wide { margin: 1em; } +#content { float: left; width: 490px; } +#content_wide { padding: 0 35px 0 35px; } +#content_home { float: left; width: 490px; padding-top: 0px; } +#menu { float: right; width: 175px; margin-right: 10px; border-left: 1px dashed #2050A0; } +#nav { padding-left: 10px; } +#logo { padding-top: 25px; padding-left: 12px; } +#hmenu { height: 30px; } +#hnav { margin: 0; padding: 0; } +#clearer { clear: both; margin: 0; padding: 0; } +#footer { margin: 8px 8px 8px 8px; } + +/* ----------------- Color CSS ------------------------ */ + +body { background: #2050A0; } +#footer { background-color: #eee; border-top: 1px solid #ddd; } +#hnav { background: #2050A0; } +a { color: #2050A0; text-decoration: none; } +a:visited { color: #2050A0; text-decoration: none; } +a:hover { color: #2050A0; text-decoration: underline; } +h2 { /*color: #17397A; */ font-size: 0.9em; font-weight: normal; } +h3 { /* color: #17397A; */ } +h3 a { color: #17397A; } +h3 a:hover { color: #17397A; } +h3 a:visited { color: #17397A; } +h4 { font-size: 16px; font-weight: normal; } + +/* -------- Core CSS --------- */ + + +a { text-decoration: none; font-weight:bold; } +a img { border: none; } + +h1 { margin: 0; padding: 0; } +h1#title { float: left; } +h1#sitename { text-align: right; } +h1#sitename a:hover { text-decoration: underline; } +h2#tagline { text-align: right; letter-spacing: 0.2em; } +#hidden {display:none;} + +img { margin: 10px; padding: 10px; border: 0px solid #2F700F; } + +p { line-height: 175%; } + +.textarea { width:200px; margin:0; } + +/*------------- hnav------------*/ + +#hnav ul { + text-align: center; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 0; + margin-top: 0; + /* cancels gap caused by top padding in Opera 7.54 */ + margin-left: 0; + background-color: #2050A0; + color: #F2EEEC; + width: 100%; + line-height: 18px; + /* fixes Firefox 0.9.3 */ +} + +#hnav ul li { + display: inline; + padding-left: 0; + padding-right: 0; + padding-bottom: 5px; + /* matches link padding except for left and right */ + padding-top: 5px; +} + +#hnav ul li a { + padding-left: 10px; + padding-right: 10px; + padding-bottom: 5px; + padding-top: 5px; + color: #F2EEEC; + text-decoration: none; + border-right: 1px solid #F2EEEC; +} + +#hnav ul li.menu_last a { + border-right: 0; +} + +#hnav ul li a:hover { + background: #F2EEEC; + color: #3A291F; +} + +#hnav #active { border-left: 1px solid #F2EEEC; } + diff --git a/www/templates/common/_404.tmpl b/www/templates/common/_404.tmpl new file mode 100644 index 0000000..3a63713 --- /dev/null +++ b/www/templates/common/_404.tmpl @@ -0,0 +1,7 @@ +#extends templates.common.shell + +#def title: Page Not Found + +#block content +

We're sorry! The page you requested does not exist. Please return to our homepage or contact us.

+#end block content diff --git a/www/templates/common/_500.tmpl b/www/templates/common/_500.tmpl new file mode 100644 index 0000000..265af7a --- /dev/null +++ b/www/templates/common/_500.tmpl @@ -0,0 +1,8 @@ +#extends templates.common.shell + +#def title: Page Error + +#block content +

We're sorry! The requested you made caused an internal error we weren't expecting. The support team has been notified.

+

Please return to our homepage, go back and try again, or contact us.

+#end block content diff --git a/www/templates/common/shell.tmpl b/www/templates/common/shell.tmpl new file mode 100644 index 0000000..d8d043d --- /dev/null +++ b/www/templates/common/shell.tmpl @@ -0,0 +1 @@ +#extends skeleton diff --git a/www/templates/common/skeleton.tmpl b/www/templates/common/skeleton.tmpl new file mode 100644 index 0000000..761c0d2 --- /dev/null +++ b/www/templates/common/skeleton.tmpl @@ -0,0 +1,61 @@ +#from mitsql import config +#import cherrypy, time +#set global x_agent = cherrypy.request.headers.get('X-Agent', 'Mozilla') + + + + #set page_title = $title.split('|')[0] + $page_title + + + + #block head_append + #end block head_append + + +
+
+
+

$title

+

sql.mit.edu

+

MIT SIPB MySQL Service for Athena
+ email: sql@mit.edu

+
+
+ +
+
+
+ #block body + #end block body +
+
+ + + +
 
+ +
+
+ + diff --git a/www/templates/main/contact.tmpl b/www/templates/main/contact.tmpl new file mode 100644 index 0000000..4b212cb --- /dev/null +++ b/www/templates/main/contact.tmpl @@ -0,0 +1,11 @@ +#extends templates.common.shell + +#def title: Contact + +#block body +

Contact/Help

+ +

We welcome all questions, comments, and suggestions.

+

Please direct inquiries to sql@mit.edu

+ +#end block body \ No newline at end of file diff --git a/www/templates/main/db_list.tmpl b/www/templates/main/db_list.tmpl new file mode 100644 index 0000000..0a3b3ce --- /dev/null +++ b/www/templates/main/db_list.tmpl @@ -0,0 +1,39 @@ +#extends templates.common.shell +#from mitsql.interface import display + +#def title: Manage Databases + +#block body + + +#if len($db_list) == 0: + +#else: +#for name, data in $db_list: + +#end for +#end if + +
You have no databases. Add one below.
+#echo display.db_list_entry($name, $data.size_tot, $data.size_max) + +

drop

+
+
+
+#echo display.db_list_entry('TOTAL', $size_tot, $size_max) +
+ +
+

+ + + $user.db_prefix + +

+
+ +

Manage Data

+

One interface we recommend for managing SQL data is phpMyAdmin. Feel free to use it after you've created your databases.

+ +#end block body \ No newline at end of file diff --git a/www/templates/main/index.tmpl b/www/templates/main/index.tmpl new file mode 100644 index 0000000..cba7a22 --- /dev/null +++ b/www/templates/main/index.tmpl @@ -0,0 +1,28 @@ +#extends templates.common.shell + +#def title: MySQL Services + +#block body +

+This service provides MySQL databases to MIT certificate holders. +You must choose a MySQL password (which should be different from your Athena account password) when you sign up, and +then use this interface to create and drop databases. All subsequent SQL commands can be issued from any host, client, and/or script of your choice; +simply connect to the MySQL server at sql.mit.edu using your username and your new MySQL password. +You may find it convenient to run scripts using the web script service or +shortjobs service. +

+ +

+All uses of this service must comply with the MITnet rules of use. +

+ +

+This service has been designed with reliability in mind; we utilize RAID, live server mirroring and periodic offline backups to ensure data reliability. +However, the SIPB MySQL service should not be used to host critical applications that cannot tolerate downtime. +One nightly backup is available for your locker at /mit/sql/backup/LOCKER_NAME. You should perform additional backups of your data using the shortjobs service or phpMyAdmin. +

+ +
+ +
+#end block body \ No newline at end of file diff --git a/www/templates/main/passwd.tmpl b/www/templates/main/passwd.tmpl new file mode 100644 index 0000000..0a0773b --- /dev/null +++ b/www/templates/main/passwd.tmpl @@ -0,0 +1,28 @@ +#extends templates.common.shell + +#def title: Change Password + +#block body +

Your MySQL password should be different from your Athena account password.

+ +
+ + + + +
new password:
repeat new password:

+
+
+

Note: you need to also update your .my.cnf file on Athena if you want to use SIPB scripts auto-installers or access the MySQL service from the command-line.

+ +
+
+
+This website provides signups for individual user locker +MySQL accounts. Groups locker administrators can +signup for group locker MySQL accounts +from any Athena prompt. +
+ +#end block body \ No newline at end of file diff --git a/www/templates/main/signup.tmpl b/www/templates/main/signup.tmpl new file mode 100644 index 0000000..2df2c79 --- /dev/null +++ b/www/templates/main/signup.tmpl @@ -0,0 +1,18 @@ +#extends templates.common.shell + +#def title: Change Password + +#block body +

Your MySQL password should be different from your Athena account password.

+ +
+ + + + +
new password:
repeat new password:

+
+
+

Note: you need to also update your .my.cnf file on Athena if you want to use SIPB scripts auto-installers or access the MySQL service from the command-line.

+#end block body \ No newline at end of file