diff options
-rw-r--r-- | cobbler/item_system.py | 69 | ||||
-rw-r--r-- | cobbler/modules/cli_system.py | 19 | ||||
-rw-r--r-- | cobbler/settings.py | 1 | ||||
-rw-r--r-- | cobbler/webui/CobblerWeb.py | 8 | ||||
-rw-r--r-- | snippets/network_config | 53 | ||||
-rw-r--r-- | webui_content/cobbler.js | 214 | ||||
-rw-r--r-- | webui_templates/system_edit.tmpl | 61 |
7 files changed, 112 insertions, 313 deletions
diff --git a/cobbler/item_system.py b/cobbler/item_system.py index c83e74b6..4d8796a7 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -45,6 +45,7 @@ class System(item.Item): self.kernel_options_post = {} self.ks_meta = {} self.interfaces = {} + self.default_interface = self.settings.default_interface self.netboot_enabled = True self.depth = 2 self.mgmt_classes = [] @@ -61,12 +62,10 @@ class System(item.Item): def delete_interface(self,name): """ - Used to remove an interface. Not valid for intf0. + Used to remove an interface. Not valid for the default interface. """ - if name == "intf0": - raise CX(_("the first interface cannot be deleted")) - if self.interfaces.has_key(name): - del self.interfaces[name] + if self.interfaces.has_key(name) and self.default_interface != name: + del self.interfaces[name] else: # NOTE: raising an exception here would break the WebUI as currently implemented return False @@ -74,9 +73,8 @@ class System(item.Item): def __get_interface(self,name): - - if name not in [ "intf0", "intf1", "intf2", "intf3", "intf4", "intf5", "intf6", "intf7" ]: - raise CX(_("internal error: invalid key for interface lookup or storage, must be 'intfX' where x is 0..7")) + if name is None: + return self.__get_default_interface() if not self.interfaces.has_key(name): self.interfaces[name] = { @@ -91,6 +89,12 @@ class System(item.Item): } return self.interfaces[name] + def __get_default_interface(self): + if self.default_interface != "": + return self.__get_interface(self.default_interface) + else: + raise CX(_("no default interface defined")) + def from_datastruct(self,seed_data): # load datastructures from previous and current versions of cobbler @@ -110,6 +114,7 @@ class System(item.Item): self.netboot_enabled = self.load_item(seed_data, 'netboot_enabled', True) self.server = self.load_item(seed_data, 'server', '<<inherit>>') self.mgmt_classes = self.load_item(seed_data, 'mgmt_classes', []) + self.default_interface = self.load_item(seed_data, 'default_interface', self.settings.default_interface) # virt specific self.virt_path = self.load_item(seed_data, 'virt_path', '<<inherit>>') @@ -182,7 +187,7 @@ class System(item.Item): Set the name. If the name is a MAC or IP, and the first MAC and/or IP is not defined, go ahead and fill that value in. """ - intf = self.__get_interface("intf0") + intf = self.__get_default_interface() if self.name not in ["",None] and self.parent not in ["",None] and self.name == self.parent: @@ -211,14 +216,14 @@ class System(item.Item): self.server = server return True - def get_mac_address(self,interface="intf0"): + def get_mac_address(self,interface): """ Get the mac address, which may be implicit in the object name or explicit with --mac-address. Use the explicit location first. """ - intf = self.__get_interface(interface) + if intf["mac_address"] != "": return intf["mac_address"] # obsolete, because we should have updated the mac field already with set_name (?) @@ -227,13 +232,14 @@ class System(item.Item): else: return None - def get_ip_address(self,interface="intf0"): + def get_ip_address(self,interface): """ Get the IP address, which may be implicit in the object name or explict with --ip-address. Use the explicit location first. """ intf = self.__get_interface(interface) + if intf["ip_address"] != "": return intf["ip_address"] else: @@ -258,22 +264,28 @@ class System(item.Item): return True return False - def set_dhcp_tag(self,dhcp_tag,interface="intf0"): + def set_default_interface(self,interface): + if self.interfaces.has_key(interface): + self.default_interface = interface + else: + raise CX(_("invalid interface (%s)") % interface) + + def set_dhcp_tag(self,dhcp_tag,interface): intf = self.__get_interface(interface) intf["dhcp_tag"] = dhcp_tag return True - def set_hostname(self,hostname,interface="intf0"): + def set_hostname(self,hostname,interface): intf = self.__get_interface(interface) intf["hostname"] = hostname return True - def set_static(self,truthiness,interface="intf0"): + def set_static(self,truthiness,interface): intf = self.__get_interface(interface) intf["static"] = utils.input_boolean(truthiness) return True - def set_ip_address(self,address,interface="intf0"): + def set_ip_address(self,address,interface): """ Assign a IP or hostname in DHCP when this MAC boots. Only works if manage_dhcp is set in /etc/cobbler/settings @@ -284,24 +296,24 @@ class System(item.Item): return True raise CX(_("invalid format for IP address (%s)") % address) - def set_mac_address(self,address,interface="intf0"): + def set_mac_address(self,address,interface): intf = self.__get_interface(interface) if address == "" or utils.is_mac(address): intf["mac_address"] = address return True raise CX(_("invalid format for MAC address (%s)" % address)) - def set_gateway(self,gateway,interface="intf0"): + def set_gateway(self,gateway,interface): intf = self.__get_interface(interface) intf["gateway"] = gateway return True - def set_subnet(self,subnet,interface="intf0"): + def set_subnet(self,subnet,interface): intf = self.__get_interface(interface) intf["subnet"] = subnet return True - def set_virt_bridge(self,bridge,interface="intf0"): + def set_virt_bridge(self,bridge,interface): intf = self.__get_interface(interface) intf["virt_bridge"] = bridge return True @@ -414,6 +426,7 @@ class System(item.Item): 'kernel_options_post' : self.kernel_options_post, 'depth' : self.depth, 'interfaces' : self.interfaces, + 'default_interface' : self.default_interface, 'ks_meta' : self.ks_meta, 'kickstart' : self.kickstart, 'netboot_enabled' : self.netboot_enabled, @@ -451,9 +464,21 @@ class System(item.Item): buf = buf + _("virt ram : %s\n") % self.virt_ram buf = buf + _("virt type : %s\n") % self.virt_type + # list the default interface first + name = self.default_interface + x = self.interfaces[name] + buf = buf + _("interface : %s (default)\n") % (name) + buf = buf + _(" mac address : %s\n") % x.get("mac_address","") + buf = buf + _(" ip address : %s\n") % x.get("ip_address","") + buf = buf + _(" hostname : %s\n") % x.get("hostname","") + buf = buf + _(" gateway : %s\n") % x.get("gateway","") + buf = buf + _(" subnet : %s\n") % x.get("subnet","") + buf = buf + _(" virt bridge : %s\n") % x.get("virt_bridge","") + buf = buf + _(" dhcp tag : %s\n") % x.get("dhcp_tag","") + buf = buf + _(" is static? : %s\n") % x.get("static",False) - counter = 0 for (name,x) in self.interfaces.iteritems(): + if name == self.default_interface: continue buf = buf + _("interface : %s\n") % (name) buf = buf + _(" mac address : %s\n") % x.get("mac_address","") buf = buf + _(" ip address : %s\n") % x.get("ip_address","") @@ -463,8 +488,6 @@ class System(item.Item): buf = buf + _(" virt bridge : %s\n") % x.get("virt_bridge","") buf = buf + _(" dhcp tag : %s\n") % x.get("dhcp_tag","") buf = buf + _(" is static? : %s\n") % x.get("static",False) - counter = counter + 1 - return buf diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 7d5a0324..3ffd7cd5 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -29,7 +29,7 @@ sys.path.insert(0, mod_path) from utils import _, get_random_mac import commands -import cexceptions +from cexceptions import * class SystemFunction(commands.CobblerFunction): @@ -54,9 +54,11 @@ class SystemFunction(commands.CobblerFunction): p.add_option("--hostname", dest="hostname", help="ex: server.example.org") if not self.matches_args(args,["find"]): - p.add_option("--interface", dest="interface", help="edit this interface # (0-7, default 0)") + p.add_option("--interface", dest="interface", help="edit this interface") + p.add_option("--delete-interface", dest="delete_interface", metavar="INTERFACE", help="delete the selected interface") + p.add_option("--default-interface", dest="default_interface", metavar="INTERFACE", help="set INTERFACE as the system default") p.add_option("--image", dest="image", help="inherit values from this image, not compatible with --profile") - p.add_option("--ip", dest="ip", help="ex: 192.168.1.55, for static IP or dynamic reservation, (RECOMMENDED)") + p.add_option("--ip", dest="ip", help="ex: 192.168.1.55, (RECOMMENDED)") p.add_option("--kickstart", dest="kickstart", help="override profile kickstart template") p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'") p.add_option("--kopts-post", dest="kopts_post", help="ex: 'clocksource=pit'") @@ -130,9 +132,9 @@ class SystemFunction(commands.CobblerFunction): if self.options.virt_path: obj.set_virt_path(self.options.virt_path) if self.options.interface: - my_interface = "intf%s" % self.options.interface + my_interface = self.options.interface else: - my_interface = "intf0" + my_interface = None if self.options.hostname: obj.set_hostname(self.options.hostname, my_interface) if self.options.mac: @@ -146,7 +148,12 @@ class SystemFunction(commands.CobblerFunction): if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag, my_interface) if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge, my_interface) if self.options.static: obj.set_static(self.options.static, my_interface) - + + if self.options.delete_interface: + success = obj.delete_interface(self.options.delete_interface) + if not success: + raise CX(_('interface does not exist or is the default interface (%s)') % self.options.delete_interface) + if self.options.default_interface: obj.set_default_interface(self.options.default_interface) if self.options.owners: obj.set_owners(self.options.owners) if self.options.mgmt_classes: obj.set_mgmt_classes(self.options.mgmt_classes) diff --git a/cobbler/settings.py b/cobbler/settings.py index fd2af0a4..d652e4a0 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -34,6 +34,7 @@ DEFAULTS = { "allow_duplicate_ips" : 0, "bind_bin" : "/usr/sbin/named", "cobbler_master" : '', + "default_interface" : 'intf0', "default_kickstart" : "/etc/cobbler/default.ks", "default_virt_bridge" : "xenbr0", "default_virt_type" : "auto", diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 800bce85..764afebf 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -383,8 +383,10 @@ class CobblerWeb(object): self.remote.modify_system(system, 'virt-path', virtpath, self.token) - for x in range(0,7): - interface = "intf%s" % x + interfaces = args.get("interfaces","") + interfaces = interfaces.split(",") + + for x in interfaces: macaddress = args.get("macaddress-%s" % interface, "") ipaddress = args.get("ipaddress-%s" % interface, "") hostname = args.get("hostname-%s" % interface, "") @@ -396,7 +398,7 @@ class CobblerWeb(object): if not (macaddress != "" or ipaddress != "" or hostname != "" or virtbridge != "" or dhcptag != "" or subnet != "" or gateway != ""): # if we have nothing to modify, request that we remove the interface unless it's the # the first interface, in which case it is NOT removeable - if not interface == "intf0": + if not interface == interfaces[0]: self.remote.modify_system(system,'delete-interface', interface, self.token) else: # it looks like we have at least one value to submit, just send the ones over that are diff --git a/snippets/network_config b/snippets/network_config index fc53b474..454d2bc1 100644 --- a/snippets/network_config +++ b/snippets/network_config @@ -1,38 +1,37 @@ ## start of cobbler network_config generated code #if $getVar("system_name","") != "" #set ikeys = $interfaces.keys() - #for $i in range(0,7) - #set $iname = "intf" + str($i) - #if $interfaces.has_key($iname) - #set $idata = $interfaces[$iname] - #set $mac = $idata["mac_address"] - #set $static = $idata["static"] - #set $ip = $idata["ip_address"] - #set $netmask = $idata["subnet"] - #set $gateway = $idata["gateway"] - #set $hostname = $idata["hostname"] - #if $mac != "" or $ip != "" - #if $static == "True": - #set $network_str = "--bootproto=static" - #if $ip != "": - #set $network_str = $network_str + " --ip=" + $ip - #end if - #if $netmask != "": - #set $network_str = $network_str + " --netmask=" + $netmask - #end if - #if $gateway != "": - #set $network_str = $network_str + " --gateway=" + $gateway - #end if - #else - #set $network_str = "--bootproto=dhcp" + #set $i = 0 + #for $iname in $ikeys + #set $i = $i + 1 + #set $idata = $interfaces[$iname] + #set $mac = $idata["mac_address"] + #set $static = $idata["static"] + #set $ip = $idata["ip_address"] + #set $netmask = $idata["subnet"] + #set $gateway = $idata["gateway"] + #set $hostname = $idata["hostname"] + #if $mac != "" or $ip != "" + #if $static == "True": + #set $network_str = "--bootproto=static" + #if $ip != "": + #set $network_str = $network_str + " --ip=" + $ip #end if - #if $hostname != "" - #set $network_str = $network_str + " --hostname=" + $hostname + #if $netmask != "": + #set $network_str = $network_str + " --netmask=" + $netmask #end if + #if $gateway != "": + #set $network_str = $network_str + " --gateway=" + $gateway + #end if + #else + #set $network_str = "--bootproto=dhcp" + #end if + #if $hostname != "" + #set $network_str = $network_str + " --hostname=" + $hostname #end if + #end if ## network details are populated from the cobbler system object network $network_str --device=eth$i --onboot=on - #end if #end for #else ## profile based install so just provide one interface for starters diff --git a/webui_content/cobbler.js b/webui_content/cobbler.js index 318a342c..fd381bdd 100644 --- a/webui_content/cobbler.js +++ b/webui_content/cobbler.js @@ -5,217 +5,3 @@ function global_onload() { } } -// mizmo's fancy list-code <duffy@redhat.com> -// some adaptations to work better with Cobbler WebUI - -IMAGE_COLLAPSED_PATH = '/cobbler/webui/list-expand.png'; -IMAGE_EXPANDED_PATH = '/cobbler/webui/list-collapse.png'; - -//not really used: -IMAGE_CHILDLESS_PATH = '/cobbler/webui/list-parent.png'; - -var rowHash = new Array(); -var browserType; -var columnsPerRow; - -// tip of the Red Hat to Mar Orlygsson for this little IE detection script -var is_ie/*@cc_on = { - quirksmode : (document.compatMode=="BackCompat"), - version : parseFloat(navigator.appVersion.match(/MSIE (.+?);/)[1]) -}@*/; -browserType = is_ie; - -function onLoadStuff(columns) { - columnsPerRow = columns; - var channelTable = document.getElementById('channel-list'); - createParentRows(channelTable, rowHash); - reuniteChildrenWithParents(channelTable, rowHash); - iconifyChildlessParents(rowHash); -} - -function iconifyChildlessParents(rowHash) { - for (var i in rowHash) { - if (!rowHash[i].hasChildren && rowHash[i].image) { - // not needed in this implementation - // rowHash[i].image.src = IMAGE_CHILDLESS_PATH; - } - } -} - -// called from clicking the show/hide button on individual rows in the page -function toggleRowVisibility(id) { - if (!rowHash[id]) { return; } - if (!rowHash[id].hasChildren) { return; } - rowHash[id].toggleVisibility(); - return; -} - -function showAllRows() { - var row; - for (var i in rowHash) { - row = rowHash[i]; - if (!row) { continue; } - if (!row.hasChildren) { continue; } - row.show(); - } - return; -} - -function hideAllRows() { - var row; - for (var i in rowHash) { - row = rowHash[i]; - if (!row) { continue; } - if (!row.hasChildren) { continue; } - row.hide(); - } - return; -} - -function Row(cells, image) { - this.cells = new Array(); - for (var i = 0; i < cells.length; i++) { this.cells[i] = cells[i]; } - this.image = image; - this.hasChildren = 0; - this.isHidden = 0; // 1 = hidden; 0 = visible. all rows are visible by default - - -// Row object methods below! - this.toggleVisibility = function() { - if (this.isHidden == 1) { this.show(); } - else if (this.isHidden == 0) { this.hide(); } - return; - } - - this.hide = function hide() { - - this.image.src = IMAGE_COLLAPSED_PATH; - // we start with columnsPerRow, because we want to skip the td cells of the parent tr. - for (var i = columnsPerRow; i < this.cells.length; i++) { - // this looks suspicious - // this.cells[i].parentNode.style.display = 'none'; - - // MPD: I added this: - if (! this.isParent) { - this.cells[i].style.display = 'none'; - } - } - this.isHidden = 1; - return; - } - - this.show = function() { - displayType = ''; - this.image.src = IMAGE_EXPANDED_PATH; - - for (var i = 0; i < this.cells.length; i++) { - this.cells[i].style.display = ''; - // also suspicious - // this.cells[i].parentNode.style.display = displayType; - } - this.isHidden = 0; - return; - } -} - -function createParentRows(channelTable, rowHash) { - for (var i = 0; i < channelTable.rows.length; i++) { - tableRowNode = channelTable.rows[i]; - if (isParentRowNode(tableRowNode)) { - if (!tableRowNode.id) { continue; } - id = tableRowNode.id; - var cells = tableRowNode.cells; - var image = findRowImageFromCells(cells, id) - if (!image) { continue; } - rowHash[id] = new Row(cells, image); - // MPD: I added this - rowHash[id].isParent = 1 - } - else { - // MPD: I added this - rowHash[id].isParent = 0 - - } - } - return; -} - -function reuniteChildrenWithParents(channelTable, rowHash) { - var parentNode; - var childId; - var tableChildRowNode; - for (var i = 0; i < channelTable.rows.length; i++) { - tableChildRowNode = channelTable.rows[i]; - // when we find a parent, set it as parent for the children after it - if (isParentRowNode(tableChildRowNode) && tableChildRowNode.id) { - parentNode = tableChildRowNode; - continue; - } - if (!parentNode) { continue; } - - // it its not a child node we bail here - if (!isChildRowNode(tableChildRowNode)) { continue; } - // check child id against parent id - if (!rowHash[parentNode.id]) { /*alert('bailing, cant find parent in hash');*/ continue; } - for (var j = 0; j < tableChildRowNode.cells.length; j++) { - rowHash[parentNode.id].cells.push(tableChildRowNode.cells[j]); - rowHash[parentNode.id].hasChildren = 1; - } - } - return; -} - - -function getNodeTagName(node) { - var tagName; - var nodeId; - tagName = new String(node.tagName); - return tagName.toLowerCase(); -} - -function isParentRowNode(node) { - var nodeInLowercase = getNodeTagName(node); - if (nodeInLowercase != 'tr') { return 0; } - nodeId = node.id; - if ((nodeId.indexOf('id')) && !(nodeId.indexOf('child'))) { - return 0; - } - return 1; -} - -function isChildRowNode(node) { - var nodeInLowercase = getNodeTagName(node); - var nodeId; - if (nodeInLowercase != 'tr') { return 0; } - nodeId = node.id; - if (nodeId.indexOf('child')) { return 0; } - return 1; -} - - -function findRowImageFromCells(cells, id) { - var imageId = id + '-image'; - var childNodes; // first level child - var grandchildNodes; // second level child - for (var i = 0; i < cells.length; i++) { - childNodes = null; - grandchildNodes = null; - - if (!cells[i].hasChildNodes()) { continue; } - - childNodes = cells[i].childNodes; - - for (var j = 0; j < childNodes.length; j++) { - if (!childNodes[j].hasChildNodes()) { continue; } - if (getNodeTagName(childNodes[j]) != 'a') { continue; } - grandchildNodes = childNodes[j].childNodes; - - for (var k = 0; k < grandchildNodes.length; k++) { - if (grandchildNodes[k].name != imageId) { continue; } - if (grandchildNodes[k].nodeName == 'IMG' || grandchildNodes[k].nodeName == 'img') { return grandchildNodes[k]; } - } - } - } - return null; -} - diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 24caf535..624c1393 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -12,16 +12,6 @@ <script language="javascript"> -function delete_interface(num) -{ - #if $editable == True - #for $field in $fields - document.getElementById("${field}-intf" + num).value = ""; - #end for - #end if - toggleRowVisibility("id" + num); -} - #if $system function disablename(value) { @@ -53,18 +43,10 @@ function get_random_mac(field) ## determine a bit about what interfaces should be shown and which should not. ## -#set $all_interfaces = [ "intf0", "intf1", "intf2", "intf3", "intf4", "intf5", "intf6", "intf7" ] #if $system #set $interfaces = $system.interfaces.keys() - #set $defined_interfaces = [] - #for $potential in $all_interfaces - #if $potential in $interfaces - #set $rc = $defined_interfaces.append($potential) - #end if - #end for #else - #set $interfaces = [ "intf0", "intf1", "intf2", "intf3", "intf4", "intf5", "intf6", "intf7" ] - #set $defined_interfaces = [ "intf0" ] + #set $interfaces = [ "eth0" ] #end if ### @@ -78,7 +60,7 @@ function page_onload() { onLoadStuff(2); hideAllRows(); #set counter = 0 - #for $interface in $all_interfaces + #for $interface in $interfaces #if $interface in $defined_interfaces toggleRowVisibility("id${counter}"); #end if @@ -391,7 +373,9 @@ function page_onload() { ## render the toggle link to hide the interfaces not yet defined ## ---------------------------------------- - <tr class="listrow" id="1000${counter}"> + <div class="interface_definition" id="interface-$iname"> + + <tr> <td> <hr width="100%"/> </td> @@ -400,7 +384,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="id${counter}"> + <tr> <td> Interface $interface.replace("intf","") </td> @@ -409,12 +393,7 @@ function page_onload() { </td> </tr> - ## ---------------------------------------- - ## now show all of the interface fields which may or may not - ## be hidden but are always there - ## ---------------------------------------- - - <tr class="listrow" id="child-id${counter}-0"> + <tr> <td> <label for="macaddress-$interface">MAC</label> </td> @@ -431,7 +410,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-1"> + <tr> <td> <label for="ipaddress-$interface">IP</label> </td> @@ -443,7 +422,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-3"> + <tr> <td> <label for="hostname-$interface">Hostname</label> </td> @@ -455,7 +434,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-4"> + <tr> <td> <label for="dhcptag-$interface">DHCP Tag</label> </td> @@ -467,7 +446,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-5"> + <tr> <td> <label for="virtbridge-$interface">Virt Bridge</label> </td> @@ -479,7 +458,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-6"> + <tr> <td> <label for="subnet-$interface">Subnet</label> </td> @@ -491,7 +470,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-7"> + <tr> <td> <label for="gateway-$interface">Gateway</label> </td> @@ -503,7 +482,7 @@ function page_onload() { </td> </tr> - <tr class="listrow" id="child-id${counter}-8"> + <tr> <td> <label for="static-$interface">Static?</label> </td> @@ -518,8 +497,10 @@ function page_onload() { </tr> - #if $interface != "intf0" - <tr class="listrow" id="child-id${counter}-9"> + # FIXME: if interface is not first interface + + #if $interface != "intf0" and $interface != "eth0" + <tr> <td> #if $editable == True <label for="enabled-$interface">Remove</label> @@ -547,7 +528,7 @@ function page_onload() { #end for ## ====================================== end of looping through interfaces - <tr class="listrow" id="id10000"> + <tr> <td> <hr width="95%"/> </td> @@ -557,7 +538,7 @@ function page_onload() { </tr> #if $system and $editable == True - <tr id="id10001"> + <tr> <td> <label for="delete">Delete</label> </td> @@ -570,7 +551,7 @@ function page_onload() { #end if #if $editable == True - <tr id="9008"> + <tr> <td> </td> <td> |