summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in4
-rw-r--r--Makefile2
-rw-r--r--cobbler.spec9
-rw-r--r--cobbler/action_sync.py2
-rw-r--r--cobbler/webui/CobblerWeb.py191
-rw-r--r--cobbler/webui/__init__.py0
-rw-r--r--cobbler/webui/master.py240
-rwxr-xr-xcobbler/webui/webui-cgi.py47
-rwxr-xr-xcobbler/webui/webui-cherrypy3.py10
-rw-r--r--config/webui-cherrypy.cfg9
-rwxr-xr-xscripts/cobbler_webui.cgi47
-rw-r--r--setup.py64
-rw-r--r--webui_content/cobblerweb.css34
-rw-r--r--webui_content/logo-cobbler.pngbin0 -> 15085 bytes
-rw-r--r--webui_content/style.css183
-rw-r--r--webui_templates/distro_list.tmpl35
-rw-r--r--webui_templates/error_page.tmpl6
-rw-r--r--webui_templates/index.tmpl8
-rw-r--r--webui_templates/item.tmpl38
-rw-r--r--webui_templates/ksfile_edit.tmpl17
-rw-r--r--webui_templates/ksfile_list.tmpl33
-rw-r--r--webui_templates/ksfile_view.tmpl6
-rw-r--r--webui_templates/master.tmpl55
-rw-r--r--webui_templates/profile_add.tmpl45
-rw-r--r--webui_templates/profile_list.tmpl33
-rw-r--r--webui_templates/system_edit.tmpl48
-rw-r--r--webui_templates/system_list.tmpl35
27 files changed, 1194 insertions, 7 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 38dabf4..8f9588c 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 *
diff --git a/Makefile b/Makefile
index aa14401..4d162f3 100644
--- a/Makefile
+++ b/Makefile
@@ -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 9db884a..00aa7a5 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 19c6d63..86aaa2f 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 0000000..4ed8d39
--- /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 0000000..e69de29
--- /dev/null
+++ b/cobbler/webui/__init__.py
diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py
new file mode 100644
index 0000000..58b5047
--- /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 0000000..42387bd
--- /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 0000000..fff5e77
--- /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 0000000..5d7dca9
--- /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 0000000..42387bd
--- /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()
diff --git a/setup.py b/setup.py
index c99ccfc..0e76cd8 100644
--- a/setup.py
+++ b/setup.py
@@ -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 0000000..806725b
--- /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
new file mode 100644
index 0000000..f8fe53c
--- /dev/null
+++ b/webui_content/logo-cobbler.png
Binary files differ
diff --git a/webui_content/style.css b/webui_content/style.css
new file mode 100644
index 0000000..d1aabe0
--- /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 0000000..4654972
--- /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 0000000..9bf7473
--- /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 0000000..b249f32
--- /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 0000000..e06083f
--- /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 0000000..a28fe74
--- /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 0000000..af196ec
--- /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 0000000..b6abf67
--- /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 0000000..5517d9c
--- /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 0000000..424e7d9
--- /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 0000000..8699b40
--- /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 0000000..8034bd2
--- /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 0000000..8e6d1d8
--- /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