diff options
27 files changed, 1194 insertions, 7 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 38dabf47..8f9588c7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,6 +8,7 @@ include config/cobblerd_rotate include config/cobbler_hosts include config/modules.conf include config/auth.conf +include config/webui-cherrypy.cfg recursive-include templates *.template recursive-include kickstarts *.ks include docs/cobbler.1.gz @@ -16,6 +17,9 @@ include scripts/watcher.py include scripts/cobblerd include scripts/findks.cgi include scripts/nopxe.cgi +include scripts/cobbler_webui.cgi include snippets/* recursive-include po *.pot recursive-include po *.po +recursive-include webui_content * +recursive-include webui_templates * @@ -20,7 +20,7 @@ test: install build: clean messages python setup.py build -f -install: clean +install: clean manpage python setup.py install -f sdist: clean messages diff --git a/cobbler.spec b/cobbler.spec index 9db884a7..00aa7a5f 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -72,6 +72,9 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %defattr(2744,apache,apache) /var/www/cgi-bin/findks.cgi /var/www/cgi-bin/nopxe.cgi +/var/www/cgi-bin/cobbler_webui.cgi +%dir /usr/share/cobbler/webui_templates +/usr/share/cobbler/webui_templates/*.tmpl %dir /var/log/cobbler %dir /var/log/cobbler/kicklog %dir /var/www/cobbler/ @@ -86,6 +89,9 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /var/www/cobbler/profiles %dir /var/www/cobbler/systems %dir /var/www/cobbler/links +%dir /var/www/cobbler/webui +/var/www/cobbler/webui/*.css +/var/www/cobbler/webui/*.png %defattr(-,root,root) %dir /tftpboot/pxelinux.cfg %dir /tftpboot/images @@ -105,12 +111,15 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /etc/cobbler/rsync.exclude %config(noreplace) /etc/logrotate.d/cobblerd_rotate %config(noreplace) /etc/cobbler/modules.conf +%config(noreplace) /etc/cobbler/webui-cherrypy.cfg %dir %{python_sitelib}/cobbler %dir %{python_sitelib}/cobbler/yaml %dir %{python_sitelib}/cobbler/modules +%dir %{python_sitelib}/cobbler/webui %{python_sitelib}/cobbler/*.py* %{python_sitelib}/cobbler/yaml/*.py* %{python_sitelib}/cobbler/modules/*.py* +%{python_sitelib}/cobbler/webui/*.py* %{_mandir}/man1/cobbler.1.gz /etc/init.d/cobblerd %config(noreplace) /etc/httpd/conf.d/cobbler.conf diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 19c6d636..86aaa2f6 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -249,7 +249,7 @@ class BootSync: if not x.endswith(".py"): self.rmfile(path) if os.path.isdir(path): - if not x in ["localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links"] : + if not x in ["webui", "localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links"] : # delete directories that shouldn't exist self.rmtree(path) if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles"]: diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py new file mode 100644 index 00000000..4ed8d390 --- /dev/null +++ b/cobbler/webui/CobblerWeb.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python + +import xmlrpclib +from Cheetah.Template import Template +import os + +class CobblerWeb(object): + def __init__(self, server=None, base_url='/'): + self.server = server + self.base_url = base_url + + def xmlrpc(self): + return xmlrpclib.ServerProxy(self.server, allow_none=True) + + def __render(self, template, data): + data['base_url'] = self.base_url + #filepath = "%s/%s" % (os.path.dirname(__file__), template) + filepath = os.path.join("/usr/share/cobbler/webui_templates/",template) + tmpl = Template( file=filepath, searchList=data ) + return str(tmpl) + + def modes(self): + retval = list() + for m in dir(self): + func = getattr( self, m ) + if hasattr(func, 'exposed') and getattr(func,'exposed'): + retval.append(m) + return retval + + # ------------------------------------------------------------------------ # + # Index + # ------------------------------------------------------------------------ # + def index(self): + return self.__render( 'index.tmpl', dict() ) + + # ------------------------------------------------------------------------ # + # Settings + # ------------------------------------------------------------------------ # + def settings_view(self): + return self.__render( 'item.tmpl', { + 'item_data': self.xmlrpc().get_settings(), + 'caption': "Cobbler Settings" + } ) + + # ------------------------------------------------------------------------ # + # Distributions + # ------------------------------------------------------------------------ # + def distro_view(self, distribution): + # get_distro_for_koan() flattens out the inherited options + #distro = self.xmlrpc().get_distro_for_koan(distribution) + return self.__render( 'item.tmpl', { + 'item_data': self.xmlrpc().get_distro(distribution), + 'caption': "Distribution \"%s\" Details" % distribution + } ) + + def distro_list(self): + return self.__render( 'distro_list.tmpl', { + 'distros': self.xmlrpc().get_distros() + } ) + + # ------------------------------------------------------------------------ # + # Systems + # ------------------------------------------------------------------------ # + # if the system list is huge, this will probably need to use an + # iterator so the list doesn't get copied around + def system_list(self): + return self.__render( 'system_list.tmpl', { + 'systems': self.xmlrpc().get_systems() + } ) + + def system_add(self): + return self.__render( 'system_edit.tmpl', { + 'profiles': self.xmlrpc().get_profiles() + } ) + + def system_view(self, name): + return self.__render( 'item.tmpl', { + 'item_data': self.xmlrpc().get_profile(name), + 'caption': "Profile %s Settings" % name + } ) + + def system_save(self, name, profile, submit, new_or_edit, mac=None, ip=None, hostname=None, kopts=None, ksmeta=None, netboot='n'): + # parameter checking + if name is None: + return self.error_page("System name parameter is REQUIRED.") + + if mac is None and ip is None and hostname is None: + return self.error_page("System must have at least one of MAC/IP/hostname.") + + # resolve_ip, is_mac, and is_ip are from cobbler.utils + if hostname and not ip: + ip = resolve_ip( hostname ) + + if mac and not is_mac( mac ): + return self.error_page("The provided MAC address appears to be invalid.") + + if ip and not is_ip( ip ): + return self.error_page("The provided IP address appears to be invalid.") + + if new_or_edit == "edit": + system = self.xmlrpc().get_system(name) + else: + # FIXME: convert to r/w xmlrpc + system = None + #system = self.api.new_system() + system.set_name( name ) + self.api.systems().add( system ) + + system.set_profile( profile ) + + return self.__render( 'item.tmpl', { + 'item_data': system, + 'caption': "Profile %s Settings" % name + } ) + + def system_edit(self, name): + return self.__render( 'system_edit.tmpl', { + 'system': self.xmlrpc().get_system(name), + 'profiles': self.xmlrpc().get_profiles() + } ) + + # ------------------------------------------------------------------------ # + # Profiles + # ------------------------------------------------------------------------ # + def profile_list(self): + return self.__render( 'profile_list.tmpl', { + 'profiles': self.xmlrpc().get_profiles() + } ) + + def profile_add(self): + return self.__render( 'profile_add.tmpl', { + 'distros': self.xmlrpc().get_distros(), + 'ksfiles': self.__ksfiles() + } ) + + def profile_save(self): + pass + + # ------------------------------------------------------------------------ # + # Kickstart files + # ------------------------------------------------------------------------ # + def ksfile_list(self): + return self.__render( 'ksfile_list.tmpl', { + 'ksfiles': self.__ksfiles() + } ) + + def ksfile_view(self, ksfile): + return self.__render( 'ksfile_view.tmpl', { + 'ksdata': self.__ksfile_data( ksfile ), + 'ksfile': ksfile + } ) + + def __ksfiles(self): + ksfiles = list() + for profile in self.xmlrpc().get_profiles(): + ksfile = profile['kickstart'] + if not ksfile in ksfiles: + ksfiles.append( ksfile ) + return ksfiles + + def __ksfile_data(self, ksfile): + pass + + # ------------------------------------------------------------------------ # + # Miscellaneous + # ------------------------------------------------------------------------ # + def error_page(self, message): + return self.__render( 'error_page.tmpl', { + 'message': message + } ) + + # make CherryPy and related frameworks able to use this module easily + # by borrowing the 'exposed' function attritbute standard and using + # it for the modes() method + modes.exposed = False + error_page.exposed = False + distro_list.exposed = True + distro_view.exposed = True + index.exposed = True + profile_add.exposed = True + profile_list.exposed = True + profile_save.exposed = True + settings_view.exposed = True + system_add.exposed = True + system_edit.exposed = True + system_list.exposed = True + system_save.exposed = True + system_view.exposed = True + ksfile_view.exposed = True + ksfile_list.exposed = True + diff --git a/cobbler/webui/__init__.py b/cobbler/webui/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/cobbler/webui/__init__.py diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py new file mode 100644 index 00000000..58b50472 --- /dev/null +++ b/cobbler/webui/master.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python + + + + +################################################## +## DEPENDENCIES +import sys +import os +import os.path +from os.path import getmtime, exists +import time +import types +import __builtin__ +from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion +from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple +from Cheetah.Template import Template +from Cheetah.DummyTransaction import DummyTransaction +from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList +from Cheetah.CacheRegion import CacheRegion +import Cheetah.Filters as Filters +import Cheetah.ErrorCatchers as ErrorCatchers + +################################################## +## MODULE CONSTANTS +try: + True, False +except NameError: + True, False = (1==1), (1==0) +VFFSL=valueFromFrameOrSearchList +VFSL=valueFromSearchList +VFN=valueForName +currentTime=time.time +__CHEETAH_version__ = '2.0rc8' +__CHEETAH_versionTuple__ = (2, 0, 0, 'candidate', 8) +__CHEETAH_genTime__ = 1189393696.009383 +__CHEETAH_genTimestamp__ = 'Sun Sep 9 20:08:16 2007' +__CHEETAH_src__ = 'master.tmpl' +__CHEETAH_srcLastModified__ = 'Sun Sep 9 20:02:36 2007' +__CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' + +if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: + raise AssertionError( + 'This template was compiled with Cheetah version' + ' %s. Templates compiled before version %s must be recompiled.'%( + __CHEETAH_version__, RequiredCheetahVersion)) + +################################################## +## CLASSES + +class master(Template): + + ################################################## + ## CHEETAH GENERATED METHODS + + + def __init__(self, *args, **KWs): + + Template.__init__(self, *args, **KWs) + if not self._CHEETAH__instanceInitialized: + cheetahKWArgs = {} + allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split() + for k,v in KWs.items(): + if k in allowedKWs: cheetahKWArgs[k] = v + self._initCheetahInstance(**cheetahKWArgs) + + + def body(self, **KWS): + + + + ## CHEETAH: generated from #block body at line 46, col 1. + trans = KWS.get("trans") + if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)): + trans = self.transaction # is None unless self.awake() was called + if not trans: + trans = DummyTransaction() + _dummyTrans = True + else: _dummyTrans = False + write = trans.response().write + SL = self._CHEETAH__searchList + _filter = self._CHEETAH__currentFilter + + ######################################## + ## START - generated method body + + write(''' + <h1 style="color: red;">Template Failure</h1> + +''') + + ######################################## + ## END - generated method body + + return _dummyTrans and trans.response().getvalue() or "" + + + def respond(self, trans=None): + + + + ## CHEETAH: main method generated for this template + if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)): + trans = self.transaction # is None unless self.awake() was called + if not trans: + trans = DummyTransaction() + _dummyTrans = True + else: _dummyTrans = False + write = trans.response().write + SL = self._CHEETAH__searchList + _filter = self._CHEETAH__currentFilter + + ######################################## + ## START - generated method body + + write('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>''') + _v = VFFSL(SL,"title",True) # '$title' on line 5, col 12 + if _v is not None: write(_filter(_v, rawExpr='$title')) # from line 5, col 12. + write('''</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> + + <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/style.css" /> + <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/cobblerweb.css" /> +</head> + +<body> + +<div id="wrap"> + <h1 id="masthead"> + <a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 16, col 18 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 16, col 18. + write('''/index"> + <img alt="Cobbler Logo" + src="/cobbler/webui/logo-cobbler.png"/> + </a> + </h1> +</div> + +<div id="main"> + +<div id="sidebar"> + <ul id="nav"> + <li> + <select name="cobbler_server" style="text-transform: none;"> + <option value="http://localhost:25151">http://localhost:25151</option> + </select> + </li> + <li><hr/></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 33, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 33, col 22. + write('''/settings_view" class="menu">Cobbler Settings</a></li> + <li><hr/></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 35, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 35, col 22. + write('''/profile_list" class="menu">List Profiles</a></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 36, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 36, col 22. + write('''/distro_list" class="menu">List Distros</a></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 37, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 37, col 22. + write('''/system_list" class="menu">List Systems</a></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 38, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 38, col 22. + write('''/ksfile_list" class="menu">List KS Files</a></li> + <li><hr/></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 40, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 40, col 22. + write('''/system_add" class="menu">Add System</a></li> + <li><a href="''') + _v = VFFSL(SL,"base_url",True) # '$base_url' on line 41, col 22 + if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 41, col 22. + write('''/profile_add" class="menu">Add Profile</a></li> + </ul> +</div> + +<div id="content"> +''') + self.body(trans=trans) + write('''</div><!-- content --> +</div><!-- main --> + +</body> +</html> +''') + + ######################################## + ## END - generated method body + + return _dummyTrans and trans.response().getvalue() or "" + + ################################################## + ## CHEETAH GENERATED ATTRIBUTES + + + _CHEETAH__instanceInitialized = False + + _CHEETAH_version = __CHEETAH_version__ + + _CHEETAH_versionTuple = __CHEETAH_versionTuple__ + + _CHEETAH_genTime = __CHEETAH_genTime__ + + _CHEETAH_genTimestamp = __CHEETAH_genTimestamp__ + + _CHEETAH_src = __CHEETAH_src__ + + _CHEETAH_srcLastModified = __CHEETAH_srcLastModified__ + + title = "Cobbler Web Interface" + + _mainCheetahMethod_for_master= 'respond' + +## END CLASS DEFINITION + +if not hasattr(master, '_initCheetahAttributes'): + templateAPIClass = getattr(master, '_CHEETAH_templateClass', Template) + templateAPIClass._addCheetahPlumbingCodeToClass(master) + + +# CHEETAH was developed by Tavis Rudd and Mike Orr +# with code, advice and input from many other volunteers. +# For more information visit http://www.CheetahTemplate.org/ + +################################################## +## if run from command line: +if __name__ == '__main__': + from Cheetah.TemplateCmdLineIface import CmdLineIface + CmdLineIface(templateObj=master()).run() + + diff --git a/cobbler/webui/webui-cgi.py b/cobbler/webui/webui-cgi.py new file mode 100755 index 00000000..42387bd7 --- /dev/null +++ b/cobbler/webui/webui-cgi.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +import cgi +import cgitb +import wsgiref +import os +import sys +from cobbler.webui.CobblerWeb import CobblerWeb + +def map_modes(): + path = os.environ.get( 'PATH_INFO', 'index' ) + + if path.startswith('/'): + path = path[1:] + if path.endswith('/'): + path = path[:-1] + + if path is '': + path = 'index' + + return path + +def base_url(): + return os.environ.get('SCRIPT_NAME', '') + +def main(): + print "Content-type: text/html" + print + + path = map_modes() + form = cgi.parse() + + # ditch single-element arrays in the 'form' dictionary + # - may be bad for form elements that are sometimes lists and sometimes + # single items + for key,val in form.items(): + if isinstance(val, list): + if len(val) == 1: + form[key] = val[0] + + cw = CobblerWeb( server="http://localhost:25151", base_url=base_url() ) + + if path in cw.modes(): + func = getattr( cw, path ) + print func( **form ) + +main() diff --git a/cobbler/webui/webui-cherrypy3.py b/cobbler/webui/webui-cherrypy3.py new file mode 100755 index 00000000..fff5e77d --- /dev/null +++ b/cobbler/webui/webui-cherrypy3.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from CobblerWeb import CobblerWeb +import cherrypy + +cherrypy.tree.mount( CobblerWeb(server="http://localhost:25151", base_url=''), script_name='/', config='webui-cherrypy.cfg' ) +cherrypy.server.quickstart() +cherrypy.engine.start() +cherrypy.engine.block() + diff --git a/config/webui-cherrypy.cfg b/config/webui-cherrypy.cfg new file mode 100644 index 00000000..5d7dca93 --- /dev/null +++ b/config/webui-cherrypy.cfg @@ -0,0 +1,9 @@ +[global] +base_url_filter.on = True +server.thread_pool = 10 + +[/static] +tools.staticdir.on = True +tools.staticdir.dir = "webui" +tools.staticdir.root = "/var/www/cobbler" + diff --git a/scripts/cobbler_webui.cgi b/scripts/cobbler_webui.cgi new file mode 100755 index 00000000..42387bd7 --- /dev/null +++ b/scripts/cobbler_webui.cgi @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +import cgi +import cgitb +import wsgiref +import os +import sys +from cobbler.webui.CobblerWeb import CobblerWeb + +def map_modes(): + path = os.environ.get( 'PATH_INFO', 'index' ) + + if path.startswith('/'): + path = path[1:] + if path.endswith('/'): + path = path[:-1] + + if path is '': + path = 'index' + + return path + +def base_url(): + return os.environ.get('SCRIPT_NAME', '') + +def main(): + print "Content-type: text/html" + print + + path = map_modes() + form = cgi.parse() + + # ditch single-element arrays in the 'form' dictionary + # - may be bad for form elements that are sometimes lists and sometimes + # single items + for key,val in form.items(): + if isinstance(val, list): + if len(val) == 1: + form[key] = val[0] + + cw = CobblerWeb( server="http://localhost:25151", base_url=base_url() ) + + if path in cw.modes(): + func = getattr( cw, path ) + print func( **form ) + +main() @@ -17,11 +17,13 @@ if __name__ == "__main__": etcpath = "/etc/cobbler/" wwwconf = "/etc/httpd/conf.d/" wwwpath = "/var/www/cobbler/" + wwwgfx = "/var/www/cobbler/webui/" initpath = "/etc/init.d/" logpath = "/var/log/cobbler/" logpath2 = "/var/log/cobbler/kicklog" logpath3 = "/var/log/cobbler/syslog" snippets = "/var/lib/cobbler/snippets" + wwwtmpl = "/usr/share/cobbler/webui_templates/" vw_localmirror = "/var/www/cobbler/localmirror" vw_kickstarts = "/var/www/cobbler/kickstarts" vw_kickstarts_sys = "/var/www/cobbler/kickstarts_sys" @@ -48,34 +50,55 @@ if __name__ == "__main__": "cobbler", "cobbler/yaml", "cobbler/modules", + "cobbler/webui", ], scripts = ["scripts/cobbler", "scripts/cobblerd"], data_files = [ + + # cgi files (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']), + (cgipath, ['scripts/cobbler_webui.cgi']), + + # miscellaneous config files (rotpath, ['config/cobblerd_rotate']), (wwwconf, ['config/cobbler.conf']), + (cobpath, ['config/cobbler_hosts']), + (etcpath, ['config/modules.conf']), + (etcpath, ['config/auth.conf']), + (etcpath, ['config/webui-cherrypy.cfg']), + (etcpath, ['config/rsync.exclude']), + (initpath, ['config/cobblerd']), + + # bootloaders and syslinux support files (cobpath, ['loaders/elilo-3.6-ia64.efi']), (cobpath, ['loaders/menu.c32']), - (cobpath, ['config/cobbler_hosts']), + + # sample kickstart files (etcpath, ['kickstarts/kickstart_fc5.ks']), (etcpath, ['kickstarts/kickstart_fc6.ks']), (etcpath, ['kickstarts/kickstart_fc6_domU.ks']), (etcpath, ['kickstarts/default.ks']), + + # templates for DHCP and syslinux configs (etcpath, ['templates/dhcp.template']), (etcpath, ['templates/dnsmasq.template']), (etcpath, ['templates/pxedefault.template']), (etcpath, ['templates/pxesystem.template']), (etcpath, ['templates/pxesystem_ia64.template']), (etcpath, ['templates/pxeprofile.template']), - (etcpath, ['config/modules.conf']), - (etcpath, ['config/auth.conf']), + + # useful kickstart snippets that we ship (snippets, ['snippets/partition_select']), + + # documentation (manpath, ['docs/cobbler.1.gz']), - (etcpath, ['config/rsync.exclude']), - (initpath, ['config/cobblerd']), + + # logfiles (logpath, []), (logpath2, []), (logpath3, []), + + # web page directories that we own (vw_localmirror, []), (vw_kickstarts, []), (vw_kickstarts_sys, []), @@ -87,8 +110,39 @@ if __name__ == "__main__": (vw_systems, []), (vw_profiles, []), (vw_links, []), + + # tftp directories that we own (tftp_cfg, []), (tftp_images, []), + + # Web UI templates for object viewing & modification + # FIXME: other templates to add as they are created. + # slurp in whole directory? + (wwwtmpl, ['webui_templates/distro_list.tmpl']), + (wwwtmpl, ['webui_templates/profile_list.tmpl']), + (wwwtmpl, ['webui_templates/profile_add.tmpl']), + (wwwtmpl, ['webui_templates/system_list.tmpl']), + (wwwtmpl, ['webui_templates/system_edit.tmpl']), + #(wwwtmpl, ['webui_templates/repo_list.tmpl']), + + # Web UI common templates + (wwwtmpl, ['webui_templates/error_page.tmpl']), + (wwwtmpl, ['webui_templates/master.tmpl']), + (wwwtmpl, ['webui_templates/item.tmpl']), + (wwwtmpl, ['webui_templates/index.tmpl']), + + # Web UI kickstart file editing + (wwwtmpl, ['webui_templates/ksfile_edit.tmpl']), + (wwwtmpl, ['webui_templates/ksfile_list.tmpl']), + (wwwtmpl, ['webui_templates/ksfile_view.tmpl']), + + # Web UI support files + (wwwgfx, []), + (wwwgfx, ['webui_content/style.css']), + (wwwgfx, ['webui_content/logo-cobbler.png']), + (wwwgfx, ['webui_content/cobblerweb.css']), + + # Directories to hold cobbler triggers ("/var/lib/cobbler/triggers/add/distro/pre", []), ("/var/lib/cobbler/triggers/add/distro/post", []), ("/var/lib/cobbler/triggers/add/profile/pre", []), diff --git a/webui_content/cobblerweb.css b/webui_content/cobblerweb.css new file mode 100644 index 00000000..806725b1 --- /dev/null +++ b/webui_content/cobblerweb.css @@ -0,0 +1,34 @@ + +fieldset#cform label { + display: block; + width: 150px; + margin-bottom: 10px; + float: left; + padding-right: 4em; +} + +pre.config_data { + font-family: monospace; + background: white; + color: black; + width: 640px; + min-height: 480px; +} + +br { + clear: both; +} + +tr.rowodd { background-color: #212121; } +tr.roweven { background-color: #3f3d3d; } + +table.sortable th { + background-color: #363a4e; + margin: 0; +} + +table.sortable caption { + background-color: #202331; + margin: 0; +} + diff --git a/webui_content/logo-cobbler.png b/webui_content/logo-cobbler.png Binary files differnew file mode 100644 index 00000000..f8fe53ca --- /dev/null +++ b/webui_content/logo-cobbler.png diff --git a/webui_content/style.css b/webui_content/style.css new file mode 100644 index 00000000..d1aabe02 --- /dev/null +++ b/webui_content/style.css @@ -0,0 +1,183 @@ +body, html { + background-color: black; + margin: 0; + padding: 0; +} + +body { + min-width: 750px; +} + +img { + border: none; +} + +/* site-wide font specifications */ +body, ul, li, p, h1, h2, h3 { + font-family: "Liberation Sans", "Helvetica", "Luxi Sans", "Bitstream Vera Sans", sans-serif; + color: white; +} + +ul { + list-style-type: square; +} + +#sidebar { + height: auto; + float: left; +} + +#sidebar p, #sidebar h2 { + margin-left: 24px; +} + +ul#nav { + text-transform: uppercase; + letter-spacing: -.05em; + list-style-type: none; + font-family: "URW Gothic", "Liberation Sans", "Helvetica", "Luxi Sans", "Bitstream Vera Sans", sans-serif; +} + +ul#nav li a { + color: #59cbe1; + text-decoration: none; +} + +ul#nav li#active { + list-style-image: url('http://cobbler.et.redhat.com/img/current-page.png'); +} + +ul#nav li#active a, ul#nav li#active a:link, ul#nav li#active a:visited { + color: white; +} + +div#feed { + width: 160px; + font-size: small; + margin-top: 28px; + margin-bottom: 50px; + border-top: 1px solid #444; + padding-top: 5px; +} + +div#feed ul { + font-size: x-small; +} + + +#wrap { + min-width: 750px; + margin: 0px 3%; + padding-top: 12px; +} + +div#main { + background-color: #212121; + border-top: 1px solid #59cbe1; + border-bottom: 1px solid #59cbe1; + overflow: auto; + padding: 20px 0px; +} + +div#content { + width: 70%; + float: right; + padding: 0px 24px; +} + +div#content h1 { + font-size: x-large; + font-weight: normal; +} + +div#content h2 { + font-size: medium; + font-weight: 900; + text-transform: uppercase; +} + +div#content h3 { + color: #59cbe1; +} + + +div#content p { + font-size: small; + color: #ccc; +} + +a:link { + color: #59cbe1; +} + +a:hover { + color: white; +} + +a:visited { + color: #99d9e8; +} + + +#content p.metadata { + font-size: x-small; + color: white; +} + +dt { + font-weight: 900; + margin-bottom: 1em; +} + +dd { + color: #ccc; + margin-bottom: 2em; +} + +.back-to-top { + background: url('http://cobbler.et.redhat.com/img/up-arrow.gif') left no-repeat; + padding-left: 20px; + font-size: small; +} + +blockquote { + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + background: #444; + padding: 2px 4px; +} + +p.note, p.tip { + margin: 16px 8px; + padding: 8px 12px; + color: white !important; + background-color: #666; + -moz-border-radius: 10px; +} + +p.note strong, p.tip strong { + font-size: 120%; +} + +tt { + font-size: 130%; + font-weight: 700; +} + +#faq, #toc { + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + background: #444; + padding-top: 12px; + padding-bottom: 16px; + padding-left: 30px; +} + +#footer { + width: 100%; + font-size: x-small; + color: #aaa; + text-align: center; + padding-top: 16px; + padding-bottom: 16px; +} diff --git a/webui_templates/distro_list.tmpl b/webui_templates/distro_list.tmpl new file mode 100644 index 00000000..46549726 --- /dev/null +++ b/webui_templates/distro_list.tmpl @@ -0,0 +1,35 @@ +#extends cobbler.webui.master +#attr $title = "Cobbler: List of Distributions" + +#block body +<table class="sortable"> + <thead> + <caption>Cobbler Distributions</caption> + <tr> + <th class="text">Name</th> + <th class="text">Breed</th> + <th class="text">Arch</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $distro in $distros + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td> + <a href="$base_url/distro_view?distribution=$distro.name">$distro.name</a> + </td> + <td>$distro.breed</td> + <td>$distro.arch</td> + </tr> + #end for + </tbody> +</table> +#end block body + diff --git a/webui_templates/error_page.tmpl b/webui_templates/error_page.tmpl new file mode 100644 index 00000000..9bf74736 --- /dev/null +++ b/webui_templates/error_page.tmpl @@ -0,0 +1,6 @@ +#extends cobbler.webui.master +#attr $title = "Cobbler: Error" + +#block body +<h1 style="color: red;">$message</h1> +#end block body diff --git a/webui_templates/index.tmpl b/webui_templates/index.tmpl new file mode 100644 index 00000000..b249f32b --- /dev/null +++ b/webui_templates/index.tmpl @@ -0,0 +1,8 @@ +#extends cobbler.webui.master + +#block body + +This is my empty index page. + +#end block body + diff --git a/webui_templates/item.tmpl b/webui_templates/item.tmpl new file mode 100644 index 00000000..e06083f9 --- /dev/null +++ b/webui_templates/item.tmpl @@ -0,0 +1,38 @@ +#extends cobbler.webui.master + +#block body +<table class="sortable"> + <thead> + <caption>$caption</caption> + <th>Key</th> + <th>Value</th> + </thead> + <tbody> + #set $evenodd = 1 + #for $key,$value in $item_data.items(): + + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td>$key</td> + <td> + #if isinstance($value,dict): + <ul style="list-style-type: none; margin: 0; padding: 0;"> + #for $skey,$sval in $value.items(): + <li>$skey = $sval</li> + #end for + </ul> + #else + $value + #end if + </td> + </tr> + #end for + </tbody> +</table> +#end block body diff --git a/webui_templates/ksfile_edit.tmpl b/webui_templates/ksfile_edit.tmpl new file mode 100644 index 00000000..a28fe747 --- /dev/null +++ b/webui_templates/ksfile_edit.tmpl @@ -0,0 +1,17 @@ +#extends cobbler.webui.master +#attr $title = "Cobbler: Edit Kickstart File $ksfile" + +#block body +<form method="post" action="$base_url/ksfile_save"> + <input type="hidden" name="ksfile" value="$ksfile"/> + <fieldset id="cform"> + <legend>Edit Kickstart File</legend> + + <pre><textarea rows="40" cols="120" name="ksdata">$ksdata</textarea></pre> + <br/> + + <input type="submit" name="submit" value="Save"/> + <input type="reset" name="reset" value="Reset"/> + </fieldset> +</form> +#end block body diff --git a/webui_templates/ksfile_list.tmpl b/webui_templates/ksfile_list.tmpl new file mode 100644 index 00000000..af196ec0 --- /dev/null +++ b/webui_templates/ksfile_list.tmpl @@ -0,0 +1,33 @@ +#extends cobbler.webui.master + +#block body +<table class="sortable"> + <thead> + <caption>Cobbler Kickstart Files</caption> + <tr> + <th class="text">Name</th> + <th> </th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $ksfile in $ksfiles + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td>$ksfile</td> + <td><!-- + <a href="/ksfile_edit?ksfile=$ksfile">edit</a> + <a href="/ksfile_view?ksfile=$ksfile">view</a> + --> + </td> + </tr> + #end for + </tbody> +</table> +#end block body diff --git a/webui_templates/ksfile_view.tmpl b/webui_templates/ksfile_view.tmpl new file mode 100644 index 00000000..b6abf674 --- /dev/null +++ b/webui_templates/ksfile_view.tmpl @@ -0,0 +1,6 @@ +#extends cobbler.webui.master + +#block body +<pre class="config_data">$ksdata</pre> +#end block body + diff --git a/webui_templates/master.tmpl b/webui_templates/master.tmpl new file mode 100644 index 00000000..5517d9c7 --- /dev/null +++ b/webui_templates/master.tmpl @@ -0,0 +1,55 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml"> +<head> +#attr $title = "Cobbler Web Interface" + <title>$title</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> + + <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/style.css" /> + <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/cobblerweb.css" /> +</head> + +<body> + +<div id="wrap"> + <h1 id="masthead"> + <a href="$base_url/index"> + <img alt="Cobbler Logo" + src="/cobbler/webui/logo-cobbler.png"/> + </a> + </h1> +</div> + +<div id="main"> + +<div id="sidebar"> + <ul id="nav"> + <li> + <select name="cobbler_server" style="text-transform: none;"> + <option value="http://localhost:25151">http://localhost:25151</option> + </select> + </li> + <li><hr/></li> + <li><a href="$base_url/settings_view" class="menu">Cobbler Settings</a></li> + <li><hr/></li> + <li><a href="$base_url/profile_list" class="menu">List Profiles</a></li> + <li><a href="$base_url/distro_list" class="menu">List Distros</a></li> + <li><a href="$base_url/system_list" class="menu">List Systems</a></li> + <li><a href="$base_url/ksfile_list" class="menu">List KS Files</a></li> + <li><hr/></li> + <li><a href="$base_url/system_add" class="menu">Add System</a></li> + <li><a href="$base_url/profile_add" class="menu">Add Profile</a></li> + </ul> +</div> + +<div id="content"> +#block body + + <h1 style="color: red;">Template Failure</h1> + +#end block body +</div><!-- content --> +</div><!-- main --> + +</body> +</html> diff --git a/webui_templates/profile_add.tmpl b/webui_templates/profile_add.tmpl new file mode 100644 index 00000000..424e7d9b --- /dev/null +++ b/webui_templates/profile_add.tmpl @@ -0,0 +1,45 @@ +#extends cobbler.webui.master + +#block body +<form method="post" action="/profile_save"> +<fieldset id="cform"> + <legend>Add a Profile</legend> + +<!-- + cobbler profile add -name=string -distro=string [-kickstart=url] + [-kopts=string] [-ksmeta=string] [-virt-file-size=gigabytes] + [-virt-ram=megabytes] [-virt-type=string] [-virt-path=string] +--> + + <label for="name">Profile Name</label> + <input type="text" size="32" style="width: 150px;" name="name" id="name"/> + <br/> + + <label for="distro">Distribution</label> + <select name="distro" id="distro"> + #for $distro in $distros: + <option name="$distro.name">$distro.name</option> + #end for + </select> + <br/> + + <label for="ksfile">Kickstart File</label> + <select name="ksfile" id="ksfile"> + #for $ksfile in $ksfiles: + <option name="$ksfile">$ksfile</option> + #end for + </select> + <br/> + + <label for="kopts">Kernel Options</label> + <input type="text" size="128" style="width: 150px;" name="kopts" id="kopts"/> + <br/> + + <label for="ksmeta">Kickstart Metadata</label> + <input type="text" size="128" style="width: 150px;" name="ksmeta" id="ksmeta"/> + <br/> + + <input type="submit" name="submit" value="Save"/> + <input type="reset" name="reset" value="Reset"/> +</fieldset> +#end block body diff --git a/webui_templates/profile_list.tmpl b/webui_templates/profile_list.tmpl new file mode 100644 index 00000000..8699b405 --- /dev/null +++ b/webui_templates/profile_list.tmpl @@ -0,0 +1,33 @@ +#extends cobbler.webui.master + +#block body +<table class="sortable"> + <thead> + <caption>Cobbler Profiles</caption> + <tr> + <th class="text">Name</th> + <th class="text">Distribution</th> + <th class="text">KS File</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $profile in $profiles + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td>$profile.name</td> + <td> + <a href="$base_url/distro_view?distribution=$profile.distro">$profile.distro</a> + </td> + <td>$profile.kickstart</td> + </tr> + #end for + </tbody> +</table> +#end block body diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl new file mode 100644 index 00000000..8034bd28 --- /dev/null +++ b/webui_templates/system_edit.tmpl @@ -0,0 +1,48 @@ +#extends cobbler.webui.master + +#block body +<form method="post" action="$base_url/system_save"> +<fieldset id="cform"> + <legend>Add a System</legend> + + <label for="name">System Name</label> + <input type="text" size="32" style="width: 150px;" name="name" id="name"/> + <br/> + + <label for="profile">Profile</label> + <select name="profile" id="profile"> + #for $profile in $profiles: + <option name="$profile.name">$profile.name</option> + #end for + </select> + <br/> + + <label for="mac">MAC</label> + <input type="text" size="18" style="width: 150px;" name="mac" id="mac"/> + <br/> + + <label for="ip">IP</label> + <input type="text" size="15" style="width: 150px;" name="ip" id="ip"/> + <br/> + + <label for="hostname">Hostname</label> + <input type="text" size="128" style="width: 150px;" name="hostname" id="hostname"/> + <br/> + + <label for="kopts">Kernel Options</label> + <input type="text" size="128" style="width: 150px;" name="kopts" id="kopts"/> + <br/> + + <label for="ksmeta">Kickstart Metadata</label> + <input type="text" size="128" style="width: 150px;" name="ksmeta" id="ksmeta"/> + <br/> + + <label for="netboot">Netboot Enabled</label> + <input type="radio" name="netboot" value="y"/>Yes <input type="radio" name="netboot" value="n" checked="1"/> No + <br/> + + <input type="submit" name="submit" value="Save"/> + <input type="reset" name="reset" value="Reset"/> +</fieldset> +</form> +#end block body diff --git a/webui_templates/system_list.tmpl b/webui_templates/system_list.tmpl new file mode 100644 index 00000000..8e6d1d81 --- /dev/null +++ b/webui_templates/system_list.tmpl @@ -0,0 +1,35 @@ +#extends cobbler.webui.master + +#block body +<table class="sortable"> + <thead> + <caption>Cobbler Systems</caption> + <tr> + <th class="text">Name</th> + <th class="text">Distribution</th> + <th class="nosort"> </th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $system in $systems + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td>${system.name}</td> + <td> + <a href="$base_url/distro_view?distribution=${system.distro}">${system.distro}</a> + </td> + <td> + <a href="$base_url/system_edit?name=${system.name}">edit</a> + </td> + </tr> + #end for + </tbody> +</table> +#end block body |