diff --git a/pyweb/mumble/media/js/banviewer.js b/pyweb/mumble/media/js/banviewer.js
new file mode 100644
index 0000000..73c5d37
--- /dev/null
+++ b/pyweb/mumble/media/js/banviewer.js
@@ -0,0 +1,52 @@
+// kate: space-indent on; indent-width 4; replace-tabs on;
+
+Ext.namespace('Ext.ux');
+
+Ext.ux.BanViewerPanel = function( config ){
+ Ext.apply( this, config );
+
+ Ext.applyIf( this, {
+ xtype: 'grid',
+ title: gettext('Bans'),
+ colModel: new Ext.grid.ColumnModel([{
+ header: gettext('Timestamp'),
+ dataIndex: 'start',
+ width: 100,
+ renderer: function( value ){
+ return new Date(value*1000).format( "Y-m-d H:i:s" );
+ }
+ }, {
+ header: gettext('Duration'),
+ width: 100,
+ dataIndex: 'duration'
+ }, {
+ header: gettext('Reason'),
+ width: 500,
+ dataIndex: 'reason'
+ }]),
+ bbar: [{
+ iconCls: 'x-tbar-loading',
+ tooltip: gettext('Refresh'),
+ handler: function(){
+ this.ownerCt.ownerCt.store.reload();
+ }
+ }],
+ store: new Ext.data.DirectStore({
+ baseParams: {'server': this.server},
+ directFn: Mumble.bans,
+ paramOrder: ['server'],
+ root: 'data',
+ fields: ['start', 'address', 'bits', 'duration', 'reason'],
+ autoLoad: true,
+ remoteSort: false
+ }),
+ viewConfig: { forceFit: true }
+ });
+ Ext.ux.LogViewerPanel.superclass.constructor.call( this );
+}
+
+Ext.extend( Ext.ux.BanViewerPanel, Ext.grid.EditorGridPanel, {
+} );
+
+Ext.reg( 'banViewerPanel', Ext.ux.BanViewerPanel );
+
diff --git a/pyweb/mumble/models.py b/pyweb/mumble/models.py
index a7e15db..6aed3f8 100644
--- a/pyweb/mumble/models.py
+++ b/pyweb/mumble/models.py
@@ -307,6 +307,9 @@ class Mumble( models.Model ):
ctl = property( lambda self: self.server.ctl )
+ def getBans( self ):
+ return self.ctl.getBans( self.srvid )
+
def getConf( self, field ):
return self.ctl.getConf( self.srvid, field )
diff --git a/pyweb/mumble/templates/mumble/mumble.html b/pyweb/mumble/templates/mumble/mumble.html
index afcd7e3..1527e67 100644
--- a/pyweb/mumble/templates/mumble/mumble.html
+++ b/pyweb/mumble/templates/mumble/mumble.html
@@ -17,6 +17,7 @@
+