* Provide support for non-Javascript enabled browsers to edit arrays of values.
* Adding missing images to Bongo.rules (images are already in SVN)
* Large changes to agent handling code for configuration dialogs; this includes the ability to POST data to forms and get settings saved, as well as the ability to easily define the layout of the items displayed and the datatype of each object. :)
-- plus one fix of a missing { in Login.js
This commit is contained in:
+3
-1
@@ -201,7 +201,9 @@ dist_img_DATA = \
|
||||
src/www/img/agent-bongocalcmd.png \
|
||||
src/www/img/agent-bongocollector.png \
|
||||
src/www/img/agent-bongoqueue.png \
|
||||
src/www/img/agent-unknown.png
|
||||
src/www/img/agent-unknown.png \
|
||||
src/www/img/list-add.png \
|
||||
src/www/img/list-remove.png
|
||||
|
||||
jsdir = $(htdocsdir)/js
|
||||
|
||||
|
||||
+127
-139
@@ -2,11 +2,42 @@ import bongo.commonweb
|
||||
import bongo.commonweb.BongoUtil
|
||||
from HawkeyeHandler import HawkeyeHandler
|
||||
|
||||
# Status of operation output (if any).
|
||||
doneop = 0
|
||||
|
||||
class AgentHandler(HawkeyeHandler):
|
||||
# Agent definitions
|
||||
agentdefs = { 'bongoantispam' : {
|
||||
'filename' : '/config/antispam',
|
||||
'data' : {
|
||||
'timeout' : { 'label' : 'Timeout', 'type' : 'int', 'suffix' : 'milliseconds' },
|
||||
'hosts' : { 'label' : 'Hosts', 'type' : 'array,string' }
|
||||
}
|
||||
},
|
||||
'bongoqueue' : {
|
||||
'filename' : '/config/queue',
|
||||
'data' : {
|
||||
'postmaster': { 'label' : 'Postmaster', 'type' : 'string' },
|
||||
'officialname' : { 'label' : 'Official name', 'type' : 'string' },
|
||||
'hosteddomains' : { 'label' : 'Hosted domains', 'type' : 'array,string' },
|
||||
'quotamessage' : { 'label' : 'Quota message', 'type' : 'string' }
|
||||
}
|
||||
},
|
||||
'bongoavirus' : {
|
||||
'filename' : '/config/antivirus',
|
||||
'data' : {
|
||||
'host' : { 'label':'Host', 'type' : 'string' },
|
||||
'port' : { 'label' : 'Port', 'type' : 'int' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AgentHandler(HawkeyeHandler):
|
||||
|
||||
def index_GET(self, req, rp):
|
||||
'''
|
||||
Handler for main agent list view.
|
||||
'''
|
||||
global doneop
|
||||
|
||||
config = self.GetStoreData(req, "/config/manager")
|
||||
@@ -39,108 +70,118 @@ class AgentHandler(HawkeyeHandler):
|
||||
self.SetVariable("agntab", "selecteditem")
|
||||
return self.SendTemplate(req, rp, "index.tpl", title="Agents")
|
||||
|
||||
############### AGENTS ###############
|
||||
|
||||
# Antispam
|
||||
def bongoantispam_GET(self, req, rp):
|
||||
# Antispam
|
||||
return self.doAgent("bongoantispam", self.GetStoreData(req, "/config/antispam"), req, rp)
|
||||
return self.showConfig('bongoantispam', req, rp)
|
||||
|
||||
def bongomailprox_GET(self, req, rp):
|
||||
# Mail proxy
|
||||
return self.doAgent("bongomailprox", self.GetStoreData(req, "/config/mailproxy"), req, rp)
|
||||
def bongoantispam_POST(self, req, rp):
|
||||
return self.saveConfig('bongoantispam', req, rp)
|
||||
|
||||
# Antivirus
|
||||
def bongoavirus_GET(self, req, rp):
|
||||
# Antivirus
|
||||
return self.doAgent("bongoavirus", self.GetStoreData(req, "/config/antivirus"), req, rp)
|
||||
return self.showConfig('bongoavirus', req, rp)
|
||||
|
||||
def bongoavirus_POST(self, req, rp):
|
||||
return self.saveConfig('bongoavirus', req, rp)
|
||||
|
||||
# Queue
|
||||
def bongoqueue_GET(self, req, rp):
|
||||
# Queue
|
||||
return self.doAgent("bongoqueue", self.GetStoreData(req, "/config/queue"), req, rp)
|
||||
|
||||
def bongopop3_GET(self, req, rp):
|
||||
# POP3
|
||||
return self.doAgent("bongopop3", self.GetStoreData(req, "/config/pop3"), req, rp)
|
||||
|
||||
def bongoimap_GET(self, req, rp):
|
||||
# IMAP
|
||||
return self.doAgent("bongoimap", self.GetStoreData(req, "/config/imap"), req, rp)
|
||||
|
||||
def bongosmtp_GET(self, req, rp):
|
||||
# SMTP
|
||||
return self.doAgent("bongosmtp", self.GetStoreData(req, "/config/smtp"), req, rp)
|
||||
|
||||
return self.showConfig('bongoqueue', req, rp)
|
||||
|
||||
def bongoqueue_POST(self, req, rp):
|
||||
# Queue POST
|
||||
return self.saveConfig('bongoqueue', req, rp)
|
||||
|
||||
############### CONFIGURATION HELPERS ################
|
||||
|
||||
def saveConfig(self, agentname, req, rp):
|
||||
global doneop, agentdefs
|
||||
|
||||
config = self.GetStoreData(req, "/config/queue")
|
||||
self.SetStoreData(req, agentdefs[agentname]['filename'], self.setAgent(self.GetStoreData(req, agentdefs[agentname]['filename']), req.form, agentdefs[agentname]['data']))
|
||||
|
||||
for key in req.form:
|
||||
value = req.form[key].value
|
||||
|
||||
# Mm, formatting.
|
||||
if value == "on":
|
||||
value = True
|
||||
elif value.isdigit():
|
||||
value = int(value)
|
||||
|
||||
doneop = 1
|
||||
return self.showConfig(agentname, req, rp)
|
||||
|
||||
def setAgent(self, config, rf, agent):
|
||||
for key in rf:
|
||||
value = rf[key].value
|
||||
print "Has key: %s (value: %s)" % (key, value)
|
||||
|
||||
# Mm, value-type formatting.
|
||||
# Note: keys with type 'string' remain unchanged.
|
||||
if agent[key]['type'] == 'int':
|
||||
value = int(value)
|
||||
elif agent[key]['type'] == 'bool':
|
||||
value = bool(value)
|
||||
elif agent[key]['type'].startswith('array,'):
|
||||
# Split value type and determine what kind of array.
|
||||
arraytype = agent[key]['type'].split(",")[1]
|
||||
|
||||
# Now, split input data into an array, and change valuetypes as required.
|
||||
# Input data for this key should be a comma-seperated string of values.
|
||||
arraydata = value.split(",")
|
||||
for dobj in arraydata:
|
||||
if arraytype == "int":
|
||||
dobj = int(dobj)
|
||||
elif arraytype == "bool":
|
||||
dobj = bool(dobj)
|
||||
|
||||
# Otherwise, type was string (default).
|
||||
|
||||
# Set the data back.
|
||||
value = arraydata
|
||||
|
||||
# Set new value.
|
||||
config[key] = value
|
||||
|
||||
print "Final config: %s" % config
|
||||
|
||||
|
||||
def bongosmtp_POST(self, req, rp):
|
||||
# SMTP POST
|
||||
|
||||
config = self.GetStoreData(req, "/config/smtp")
|
||||
|
||||
for key in req.form:
|
||||
value = req.form[key].value
|
||||
print "Has key: %s (value: %s)" % (key, value)
|
||||
if value.isdigit():
|
||||
value = int(value)
|
||||
|
||||
|
||||
# Set new value.
|
||||
config[key] = req.form[key].value
|
||||
|
||||
|
||||
return bongo.commonweb.HTTP_UNAUTHORIZED
|
||||
return config
|
||||
|
||||
def showConfig(self, agentname, req, rp):
|
||||
global agentdefs
|
||||
return self.doAgent(agentname, self.GetStoreData(req, agentdefs[agentname]['filename']), agentdefs[agentname]['data'], req, rp)
|
||||
|
||||
def doAgent(self, agentname, config, req, rp):
|
||||
def doAgent(self, agentname, config, d, req, rp):
|
||||
'''
|
||||
Displays an agent configuration page, using information
|
||||
provided via method arguments.
|
||||
'''
|
||||
global doneop
|
||||
rkeys = []
|
||||
|
||||
if config != None and config.has_key("version"):
|
||||
for key in config:
|
||||
# Don't allow block_rts_spam - unused.
|
||||
if key != "block_rts_spam":
|
||||
# Disallow other keys that weren't passed along (eg version, etc)
|
||||
if d.has_key(key):
|
||||
nkey = {}
|
||||
nkey["id"] = key
|
||||
nkey["value"] = config[key]
|
||||
nkey["name"] = d[key]['label'] + ":"
|
||||
if d[key].has_key('suffix'):
|
||||
nkey["suffix"] = d[key]['suffix']
|
||||
|
||||
# Don't allow version as editable.
|
||||
if key != "version":
|
||||
nkey["name"] = self._humanReadableKey(key) + ":"
|
||||
suffix = self.getParamSuffix(key)
|
||||
if suffix:
|
||||
nkey["suffix"] = suffix
|
||||
nkey["numericentry"] = self.isNumericEntry(key)
|
||||
if not nkey["numericentry"]:
|
||||
nkey["boolentry"] = self.isBoolEntry(key)
|
||||
if not nkey["boolentry"]:
|
||||
nkey["selectentry"] = self.isSelectEntry(key)
|
||||
if not nkey["selectentry"]:
|
||||
# Has to be text entry if it ain't numeric or boolean.
|
||||
nkey["textentry"] = True
|
||||
else:
|
||||
# Setup per-list settings.
|
||||
nkey["jsadd"] = "javascript:addToList('" + key + "');"
|
||||
nkey["jsrm"] = "javascript:removeFromList('" + key + "');"
|
||||
nkey["jsid"] = key + "-removebtn"
|
||||
else:
|
||||
nkey["hidden"] = True
|
||||
|
||||
if d[key]['type'] == "int":
|
||||
nkey["numericentry"] = True
|
||||
elif d[key]['type'] == "bool":
|
||||
nkey["boolentry"] = True
|
||||
elif d[key]['type'] == "string":
|
||||
nkey["textentry"] = True
|
||||
elif d[key]['type'].startswith('array,'):
|
||||
nkey["selectentry"] = True
|
||||
# TODO: Fix this so we can have plain lists, instead of arrays
|
||||
nkey["jsadd"] = "javascript:addToList('" + key + "');"
|
||||
nkey["jsrm"] = "javascript:removeFromList('" + key + "');"
|
||||
nkey["jsid"] = key + "-removebtn"
|
||||
nkey["selectorid"] = key + "-box"
|
||||
nkey["revertbox"] = key + "-normal"
|
||||
nkey["revertjs"] = "$('" + nkey["revertbox"] + "').hide(); $('" + nkey["selectorid"] + "').show();"
|
||||
|
||||
strthingy = ""
|
||||
for value in config[key]:
|
||||
strthingy += value + ","
|
||||
|
||||
nkey["strvalue"] = strthingy
|
||||
|
||||
rkeys.append(nkey)
|
||||
|
||||
self.SetVariable("settinglist", rkeys)
|
||||
@@ -150,72 +191,19 @@ class AgentHandler(HawkeyeHandler):
|
||||
else:
|
||||
self.SetVariable("success", 0)
|
||||
|
||||
# Check if we've done stuff (ie saving config)
|
||||
if doneop:
|
||||
self.SetVariable("opsuccess", 1)
|
||||
doneop = 0
|
||||
else:
|
||||
self.SetVariable("opsuccess", 0)
|
||||
|
||||
self.SetVariable("agntab", "selecteditem")
|
||||
self.SetVariable("name", agentname)
|
||||
hname = self._humanReadableName(agentname)
|
||||
self.SetVariable("hname", hname)
|
||||
return self.SendTemplate(req, rp, "agentview.tpl", title=hname)
|
||||
|
||||
def index_POST(self, req, rp):
|
||||
global doneop
|
||||
|
||||
if not req.form:
|
||||
return bongo.commonweb.HTTP_UNAUTHORIZED
|
||||
|
||||
if not req.form.has_key("command"):
|
||||
return bongo.commonweb.HTTP_UNAUTHORIZED
|
||||
|
||||
# Operation completed OK.
|
||||
doneop = 1
|
||||
return self.index_GET(req, rp)
|
||||
|
||||
def isSelectEntry(self, k):
|
||||
keys = [ 'hosts', 'hosteddomains', 'trustedhosts' ]
|
||||
if keys.count(k) > 0:
|
||||
return True
|
||||
else:
|
||||
return None
|
||||
|
||||
def isBoolEntry(self, k):
|
||||
keys = [ 'use_relay_host', 'allow_expn', 'block_rts_spam', 'allow_vrfy', 'allow_client_ssl', 'verify_address', 'relay_local_mail', 'send_etrn', 'accept_etrn', 'forwardundeliverable_enabled', 'allow_auth', 'bounce_return', 'bounceccpostmaster', 'queuetuning_debug', 'limitremoteprocessing', 'bouncereturn', 'rtsantispamconfig_enabled' ]
|
||||
if keys.count(k) > 0:
|
||||
return True
|
||||
else:
|
||||
# Return None, not False (for Tal)
|
||||
return None
|
||||
|
||||
def isNumericEntry(self, k):
|
||||
keys = [ 'port', 'port_ssl', 'max_mx_servers', 'socket_timeout', 'maximum_recipients', 'max_flood_count', 'message_size_limit', 'max_thread_load', 'max_null_sender', 'threads_max' ]
|
||||
|
||||
if keys.count(k) > 0:
|
||||
# If it's in 'keys', return True.
|
||||
return True
|
||||
else:
|
||||
# Return None, not False (for Tal)
|
||||
return None
|
||||
|
||||
def getParamSuffix(self, k):
|
||||
keys = { 'interval': 'seconds', 'socket_timeout':'seconds', 'message_size_limit': 'bytes (set to 0 for none)', 'timeout':'milliseconds' }
|
||||
|
||||
if keys.has_key(k):
|
||||
return keys[k]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _humanReadableKey(self, k):
|
||||
"""
|
||||
Converts a setting key name into a human-readable keyname.
|
||||
"""
|
||||
|
||||
keys = { 'port': 'Port', 'port_ssl': 'SSL Port', 'hostname': 'Hostname', 'hostaddr': 'Host address', 'allow_client_ssl': 'Allow client SSL', 'allow_vrfy': 'Allow verification', 'relay_host': 'Relay to host', 'max_mx_servers': 'Maximum MX servers', 'socket_timeout': 'Socket timeout', 'block_rts_spam': 'Block RTS spam', 'verify_address': 'Verify address before accepting mail', 'relay_local_mail': 'Relay local mail', 'max_flood_count': 'Maximum flood count', 'message_size_limit': 'Maximum message size', 'maximum_recipients': 'Maximum recipients', 'use_relay_host': 'Use relay host', 'threads_max': 'Maximum threads', 'postmaster': 'Postmaster', 'max_threads': 'Maximum threads', 'interval': 'Interval', 'queue':'Queue host', 'max_accounts':'Maximum accounts', 'host':'Host', 'flags':'Operation mode(s) - see avirus.h', 'minimumfreespace':'Minimum free quota space', 'send_etrn':'Send ETRN', 'allow_expn':'Allow group expansion', 'accept_etrn':'Accept ETRN', 'max_thread_load':'Maximum thread load', 'max_null_sender':'Maximum recipients for null sender', 'hosts':'Hosts', 'timeout': 'Timeout', 'forwardundeliverable_enabled': 'Forward undeliverable mail', 'allow_auth':'Allow authentication', 'trustedhosts':'Trusted hosts', 'quotamessage':'Message if quota is reached', 'forwardundeliverable_to':'Forward undeliverable mail to', 'limitremoteprocessing': 'Limit remote processing', 'patterns':'Patterns', 'hosteddomains':'Hosted domains', 'bouncereturn':'Return bounced messages', 'bounceccpostmaster':'CC bounced messages to postmaster' }
|
||||
|
||||
if keys.has_key(k):
|
||||
return keys[k]
|
||||
else:
|
||||
# Return the unchanged key back if we dunno
|
||||
# what to do with it.
|
||||
return k
|
||||
|
||||
def _humanReadableName(self, agentname):
|
||||
# TODO: Translation, once we have a system in place.
|
||||
if agentname == "bongoqueue":
|
||||
|
||||
@@ -30,6 +30,28 @@ class HawkeyeHandler:
|
||||
return None
|
||||
|
||||
return config
|
||||
|
||||
def SetStoreData(self, req, filename, data, storeName="_system"):
|
||||
encoder = simplejson.JSONEncoder()
|
||||
store = None
|
||||
try:
|
||||
store = StoreClient(req.session["credUser"], req.session["credUser"], authPassword=req.session["credPass"])
|
||||
store.Store(storeName)
|
||||
configfile = encoder.encode(data)
|
||||
store.Replace(filename, configfile)
|
||||
if store:
|
||||
store.Quit()
|
||||
return True
|
||||
except ValueError:
|
||||
self.SetVariable("error", "Failed to parse JSON.")
|
||||
if store:
|
||||
store.Quit()
|
||||
return False
|
||||
except Exception, e:
|
||||
self.SetVariable("error", "Unable to read data from store: %s" % str(e))
|
||||
if store:
|
||||
store.Quit()
|
||||
return False
|
||||
|
||||
def NeedsAuth(self, rp):
|
||||
return True
|
||||
|
||||
@@ -27,16 +27,24 @@
|
||||
</td>
|
||||
|
||||
<td tal:condition="setting/selectentry">
|
||||
<div style="width: 325px;">
|
||||
<div style="width: 325px; visibility: none">
|
||||
<div style="width: 25px; float: right;">
|
||||
<a tal:attributes="href setting/jsadd" style="float: right;"><img src="/img/list-add.png" width="16" height="16" border="0" alt="Add" /></a><br />
|
||||
<a tal:attributes="href setting/jsrm;id setting/jsid" style="float: right;"><img src="/img/list-remove.png" width="16" height="16" border="0" alt="Remove" /></a>
|
||||
</div>
|
||||
|
||||
<select tal:attributes="name setting/id;id setting/id" style="width: 300px; border: 1px solid #888a85; background-color: #fff;" size="5">
|
||||
<select tal:attributes="id setting/selectorid" style="width: 300px; border: 1px solid #888a85; background-color: #fff;" size="5">
|
||||
<option tal:repeat="op setting/value" tal:content="op"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div tal:attributes="id setting/revertbox">
|
||||
<!-- For non-JS browsers -->
|
||||
<input type="text" tal:attributes="name setting/id;id setting/id;value setting/strvalue" />
|
||||
<p>Please enter values into the above textbox, seperated by a comma.</p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" tal:content="setting/revertjs">
|
||||
</script>
|
||||
</td>
|
||||
|
||||
<input type="hidden" tal:condition="setting/hidden" tal:attributes="name setting/id;id setting/id;value setting/value" />
|
||||
|
||||
+10
-8
@@ -69,16 +69,18 @@ function addToList(div)
|
||||
try
|
||||
{
|
||||
// W3C
|
||||
$(div).add(e, null);
|
||||
$(div+"-box").add(e, null);
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
//alert(ex);
|
||||
// IE
|
||||
$(div).add(e);
|
||||
$(div+"-box").add(e);
|
||||
}
|
||||
|
||||
$(div).selectedIndex = $(div).options.length - 1;
|
||||
$(div).value += d + ",";
|
||||
|
||||
$((div+"-box")).selectedIndex = $((div+"-box")).options.length - 1;
|
||||
|
||||
Effect.Appear(div + '-removebtn');
|
||||
new Effect.Highlight(div);
|
||||
@@ -87,15 +89,15 @@ function addToList(div)
|
||||
|
||||
function removeFromList(div)
|
||||
{
|
||||
var start = $(div).selectedIndex;
|
||||
$(div).remove(start);
|
||||
if ($(div).selectedIndex != start)
|
||||
var start = $(div+"-box").selectedIndex;
|
||||
$(div+"-box").remove(start);
|
||||
if ($(div+"-box").selectedIndex != start)
|
||||
{
|
||||
//new Effect.Highlight(div);
|
||||
}
|
||||
|
||||
// Check if there's anymore items left.
|
||||
if ($(div).options.length == 0)
|
||||
if ($(div+"-box").options.length == 0)
|
||||
{
|
||||
// Hide the remove button for zaaroo ooptoons.
|
||||
Effect.SwitchOff(div + '-removebtn');
|
||||
@@ -103,6 +105,6 @@ function removeFromList(div)
|
||||
else
|
||||
{
|
||||
// Update newly selected.
|
||||
$(div).selectedIndex = start - 1;
|
||||
$(div+"-box").selectedIndex = start - 1;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ Dragonfly.login = function (user)
|
||||
} else if (err.req && (err.req.status == 401 || err.req.status == 403)) {
|
||||
// Only tell the user its their fault if we get a permission denied HTTP response.
|
||||
d.setLoginMessage (_('Incorrect username or password.'));
|
||||
} else if (err.req && err.req.status == 200)
|
||||
} else if (err.req && err.req.status == 200) {
|
||||
d.setLoginMessage (_('The server backend seems to be fubared. It might be useful to check your Apache logs to see what the symptom is. It is usually one of two things:<ul><li>A Python exception, which can be determined by looking for a traceback, or;</li><li>You\'re missing mod_python. If this is the case, please nag so_solid_moo in #bongo.</li></ul>'));
|
||||
//logError ('backend was fubared and returned: ' + d.responseText);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user