...
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   clientui/http.py
#	modified:   clientui/main.py
#	new file:   content/static/clientui/auth.html
#	modified:   content/static/clientui/index.html
#	new file:   content/static/clientui/partial/directory-list.html
#	new file:   content/static/clientui/partial/directory.html
#	modified:   content/static/clientui/ui.coffee
#	modified:   content/static/clientui/ui.coffee.c.js
#	modified:   content/static/clientui/ui.coffee.js
#	modified:   layout/admin-main.xml
#	modified:   mediadb.py
#
diff --git a/clientui/http.py b/clientui/http.py
index 89dfbaf..7a53894 100644
--- a/clientui/http.py
+++ b/clientui/http.py
@@ -1,16 +1,75 @@
+from passlib.hash import sha512_crypt
+from mako.lookup import TemplateLookup
+
 from ajenti.api import plugin, BasePlugin
 from ajenti.api.http import HttpPlugin, url
-from ..mediadb import DB
+from ajenti.plugins import manager
+from ..mediadb import DB, RootDirectory, ClientDirectoryPermission
+
 
 @plugin
 class ClientUIServer (BasePlugin, HttpPlugin):
     def init(self):
         self.db = DB.instance()
+        self.templates = TemplateLookup(directories=manager.resolve_path('custom_media') + '/content/static/clientui')
+
+    def require_login(fx):
+        def wrapper(self, context, **kwargs):
+            username = context.session.data.get('username', None)
+            if not username:
+                return context.redirect('/media/auth')
+            else:
+                kwargs['client'] = self.db.get_client(username)
+            return fx(self, context, **kwargs)
+        return wrapper
+
+    def render(self, template, **args):
+        return str(self.templates.get_template(template).render(**args))
+
+    @url('/media/auth')
+    def handle_auth(self, context):
+        if 'username' in context.query:
+            username = context.query['username'].value
+            client = self.db.get_client(username)
+            if client:
+                password = context.query['password'].value
+                if sha512_crypt.verify(password, client.password):
+                    context.session.data['username'] = username
+                    return context.redirect('/media')
+        context.add_header('Content-Type', 'text/html')
+        context.respond_ok()
+        return self.open_content('static/clientui/auth.html').read()
+
+    @url('/media/logout')
+    def handle_logout(self, context):
+        if 'username' in context.session.data:
+            del context.session.data['username']
+        return context.redirect('/media')
 
     @url('/media(/)?')
-    def handle_root(self, context):
-        if context.session.identity is None:
-            context.respond_forbidden()
+    @require_login
+    def handle_root(self, context, client=None):
         context.add_header('Content-Type', 'text/html')
         context.respond_ok()
         return self.open_content('static/clientui/index.html').read()
+
+    @url('/media/ajax/directory-list')
+    @require_login
+    def handle_directory_list(self, context, client=None):
+        context.add_header('Content-Type', 'text/html')
+        context.respond_ok()
+        return self.render(
+            'partial/directory-list.html',
+            directories=[_.directory for _ in client.directory_permissions],
+        )
+ 
+    @url('/media/ajax/directory/(?P<id>\d+)')
+    @require_login
+    def handle_directory(self, context, id=None, client=None):
+        directory = self.db.get_by_id(RootDirectory, id)
+        context.add_header('Content-Type', 'text/html')
+        context.respond_ok()
+        return self.render(
+            'partial/directory.html',
+            directory=directory,
+        )
diff --git a/clientui/main.py b/clientui/main.py
index a918194..2e44c60 100644
--- a/clientui/main.py
+++ b/clientui/main.py
@@ -5,22 +5,23 @@ from ajenti.plugins.main.api import SectionPlugin
 from ajenti.ui import on
 from ajenti.ui.binder import Binder
 
-from ..mediadb import DB, Client
+from ..mediadb import DB, Client, RootDirectory, ClientDirectoryPermission
 
 
 @plugin
-class ElementsDownloads (SectionPlugin):
+class MediaLibrarySection (SectionPlugin):
     def init(self):
         self.title = 'Media Library'
         self.icon = 'play-circle'
         self.category = 'Elements'
         self.append(self.ui.inflate('custom_media:admin-main'))
 
-        def post_client_bind(o, c, i, u):
-            u.find('edit').on('click', self.edit_client, i)
-            u.find('delete').on('click', self.delete_client, i)
-     
-        self.find('clients').post_item_bind = post_client_bind
+        def post_directory_bind(o, c, i, u):
+            u.find('create-permission').on('click', self.on_create_permission, i)
+
+        self.find('clients').delete_item = lambda i,c: self.delete_object(i)
+        self.find('directories').delete_item = lambda i,c: self.delete_object(i)
+        self.find('directories').post_item_bind = post_directory_bind
      
         self.clients = []
         self.directories = []
@@ -34,31 +35,45 @@ class ElementsDownloads (SectionPlugin):
 
     def refresh(self):
         self.clients = self.db.list(Client)
-        print self.clients
-        self.binder.unpopulate().populate()
+        self.directories = self.db.list(RootDirectory)
+        self.binder.unpopulate()
 
-    def edit_client(self, client):
-        self.editing_client = client
-        client.edit_binder = Binder(client, self.find('client-dialog'))
-        client.edit_binder.autodiscover().populate()
-        self.find('client-dialog').visible = True
+        self.find('user-dropdown').values = [None] + self.clients
+        self.find('user-dropdown').labels = [''] + [_.username for _ in self.clients]
 
-    @on('client-dialog', 'button')
-    def on_edit_client_done(self, button=None):
-        client = self.editing_client
-        self.find('client-dialog').visible = False
-        if button == 'ok':
-            client.edit_binder.update()
-            if not client.password.startswith('$'):
-                client.password = sha512_crypt.encrypt(client.password)
-            self.db.commit()
+        self.binder.populate()
 
-    def delete_client(self, client):
-        self.db.delete(client)
+    def delete_object(self, object):
+        self.save()
+        self.db.delete(object)
         self.refresh()
 
     @on('create-client', 'click')
     def on_create_client(self):
+        self.save()
         c = Client(username='unnamed', password='')
         self.db.create(c)
-        self.refresh()
\ No newline at end of file
+        self.refresh()
+
+    @on('create-directory', 'click')
+    def on_create_directory(self):
+        self.save()
+        d = RootDirectory(name='unnamed', path='/')
+        self.db.create(d)
+        self.refresh()
+
+    def on_create_permission(self, directory):
+        self.save()
+        p = ClientDirectoryPermission(directory=directory, user=None)
+        self.db.create(p)
+        self.refresh()
+
+    @on('save', 'click')
+    def save(self):
+        self.binder.update()
+        
+        for client in self.clients:
+            if not client.password.startswith('$'):
+                client.password = sha512_crypt.encrypt(client.password)
+        self.db.commit()
+        self.refresh()
diff --git a/content/static/clientui/auth.html b/content/static/clientui/auth.html
new file mode 100644
index 0000000..1204655
--- /dev/null
+++ b/content/static/clientui/auth.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset='utf-8' />
+        <link rel="icon" type="image/png" href="/ajenti:static/main/favicon.png" />
+        <title></title>
+        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
+        <link rel="stylesheet" href="/ajenti:static/custom_media/clientui/styles.less.c.css" />
+        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+        <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
+        <script src="/ajenti:static/custom_media/clientui/ui.coffee.c.js"></script>
+        <meta name="viewport" content="initial-scale=1.0, width=device-width, target-densitydpi=device-dpi" />
+    </head>
+    
+    <body>
+        <div class="container">
+            <div class="col-lg-4 col-offset-4">
+                <div class="panel panel-default">
+                    <div class="panel-body">
+                        <form class="form-horizontal" action="" method="POST">
+                            <div class="form-group">
+                                <label class="col-lg-4 control-label">Username</label>
+                                <div class="col-lg-8">
+                                    <input type="text" class="form-control" name="username" />
+                                </div>
+                            </div>
+                            <div class="form-group">
+                                <label class="col-lg-4 control-label">Password</label>
+                                <div class="col-lg-8">
+                                    <input type="password" class="form-control" name="password" />
+                                </div>
+                            </div>
+                            <div class="form-group">
+                                <label class="col-lg-4 control-label"></label>
+                                <div class="col-lg-8">
+                                    <input type="submit" class="btn btn-primary" value="Log in" />
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </body>
+</html>
diff --git a/content/static/clientui/index.html b/content/static/clientui/index.html
index ba34c25..5bf1bcd 100644
--- a/content/static/clientui/index.html
+++ b/content/static/clientui/index.html
@@ -24,8 +24,8 @@
             </div>
         </nav>
 
-        <div class="container">
-            asd
+        <div class="container" id="root">
+            
         </div>
     </body>
 </html>
diff --git a/content/static/clientui/partial/directory-list.html b/content/static/clientui/partial/directory-list.html
new file mode 100644
index 0000000..c0c840e
--- /dev/null
+++ b/content/static/clientui/partial/directory-list.html
@@ -0,0 +1,7 @@
+<div class="list-group">
+    % for directory in directories:
+    <a href="#" data-id="${directory.id}" class="list-group-item">
+        ${directory.name}
+    </a>
+    % endfor
+</div>
\ No newline at end of file
diff --git a/content/static/clientui/partial/directory.html b/content/static/clientui/partial/directory.html
new file mode 100644
index 0000000..5270d01
--- /dev/null
+++ b/content/static/clientui/partial/directory.html
@@ -0,0 +1,2 @@
+<h3>${directory.name}</h3>
+<a href="#" id="go-back" class="btn btn-default">Back</a>
diff --git a/content/static/clientui/ui.coffee b/content/static/clientui/ui.coffee
index e69de29..eaea5b5 100644
--- a/content/static/clientui/ui.coffee
+++ b/content/static/clientui/ui.coffee
@@ -0,0 +1,31 @@
+class ClientUI
+    constructor: () ->
+        @root = $('#root')
+        @goDirectoryList()
+
+    replace_view: (html) ->
+        @root.html(html)
+
+    go: (url, callback) ->
+        $.get(
+            '/media/ajax/' + url,
+            (html) =>
+                @replace_view(html)
+                callback()
+        )
+
+    goDirectory: (id) ->
+        @go "directory/#{id}", () =>
+            @root.find('#go-back').click () =>
+                @goDirectoryList()
+
+
+    goDirectoryList: () ->
+        @go 'directory-list', () =>
+            @root.find('a').each (i, e) =>
+                $(e).click () =>
+                    @goDirectory($(e).attr('data-id'))
+
+
+$ () ->
+    new ClientUI()
diff --git a/content/static/clientui/ui.coffee.c.js b/content/static/clientui/ui.coffee.c.js
index c8b0aa7..3909f9c 100644
--- a/content/static/clientui/ui.coffee.c.js
+++ b/content/static/clientui/ui.coffee.c.js
@@ -1,6 +1,52 @@
 // Generated by CoffeeScript 1.4.0
 (function() {
+  var ClientUI;
 
+  ClientUI = (function() {
 
+    function ClientUI() {
+      this.root = $('#root');
+      this.goDirectoryList();
+    }
+
+    ClientUI.prototype.replace_view = function(html) {
+      return this.root.html(html);
+    };
+
+    ClientUI.prototype.go = function(url, callback) {
+      var _this = this;
+      return $.get('/media/ajax/' + url, function(html) {
+        _this.replace_view(html);
+        return callback();
+      });
+    };
+
+    ClientUI.prototype.goDirectory = function(id) {
+      var _this = this;
+      return this.go("directory/" + id, function() {
+        return _this.root.find('#go-back').click(function() {
+          return _this.goDirectoryList();
+        });
+      });
+    };
+
+    ClientUI.prototype.goDirectoryList = function() {
+      var _this = this;
+      return this.go('directory-list', function() {
+        return _this.root.find('a').each(function(i, e) {
+          return $(e).click(function() {
+            return _this.goDirectory($(e).attr('data-id'));
+          });
+        });
+      });
+    };
+
+    return ClientUI;
+
+  })();
+
+  $(function() {
+    return new ClientUI();
+  });
 
 }).call(this);
diff --git a/content/static/clientui/ui.coffee.js b/content/static/clientui/ui.coffee.js
index c8b0aa7..3909f9c 100644
--- a/content/static/clientui/ui.coffee.js
+++ b/content/static/clientui/ui.coffee.js
@@ -1,6 +1,52 @@
 // Generated by CoffeeScript 1.4.0
 (function() {
+  var ClientUI;
 
+  ClientUI = (function() {
 
+    function ClientUI() {
+      this.root = $('#root');
+      this.goDirectoryList();
+    }
+
+    ClientUI.prototype.replace_view = function(html) {
+      return this.root.html(html);
+    };
+
+    ClientUI.prototype.go = function(url, callback) {
+      var _this = this;
+      return $.get('/media/ajax/' + url, function(html) {
+        _this.replace_view(html);
+        return callback();
+      });
+    };
+
+    ClientUI.prototype.goDirectory = function(id) {
+      var _this = this;
+      return this.go("directory/" + id, function() {
+        return _this.root.find('#go-back').click(function() {
+          return _this.goDirectoryList();
+        });
+      });
+    };
+
+    ClientUI.prototype.goDirectoryList = function() {
+      var _this = this;
+      return this.go('directory-list', function() {
+        return _this.root.find('a').each(function(i, e) {
+          return $(e).click(function() {
+            return _this.goDirectory($(e).attr('data-id'));
+          });
+        });
+      });
+    };
+
+    return ClientUI;
+
+  })();
+
+  $(function() {
+    return new ClientUI();
+  });
 
 }).call(this);
diff --git a/layout/admin-main.xml b/layout/admin-main.xml
index 6692e3e..df750d0 100644
--- a/layout/admin-main.xml
+++ b/layout/admin-main.xml
@@ -1,43 +1,89 @@
-<body>
-    <pad>
-        <tabs>
-            <tab title="Clients">
-                <bind:collection bind="clients" id="clients">
-                    <vc>
-                        <dt bind="__items" />
-                        <button icon="plus" id="create-client" text="Create" />
-                    </vc>
+<vc>
+    <body>
+        <pad>
+            <tabs>
+                <tab title="Clients">
+                    <bind:collection bind="clients" id="clients">
+                        <vc>
+                            <dt bind="__items" />
+                            <button icon="plus" id="create-client" text="Create" />
+                        </vc>
 
-                    <bind:template>
-                        <dtr>
-                            <dtd width="1"><icon icon="user" /></dtd>
-                            <dtd><label bind:text="username" /></dtd>
-                            <dtd width="1">
-                                <hc>
-                                    <button style="mini" id="edit" icon="pencil" />
-                                    <button style="mini" id="delete" icon="remove" />
-                                </hc>
-                            </dtd>
-                        </dtr>
-                    </bind:template>
-                </bind:collection>
+                        <bind:template>
+                            <collapserow>
+                                <box>
+                                    <right>
+                                        <button id="delete" icon="remove" style="icon" warning="Confirm deletion" />
+                                    </right>
+                                    <hc>
+                                        <icon icon="user" />
+                                        <label bind="username" />
+                                    </hc>   
+                                </box>
+                                
+                                <indent>
+                                    <vc>
+                                        <formline text="Username">
+                                            <textbox bind="username" />
+                                        </formline>
+                                        <formline text="Password">
+                                            <textbox bind="password" />
+                                        </formline>
+                                    </vc>
+                                </indent>
+                            </collapserow>
+                        </bind:template>
+                    </bind:collection>
+                </tab>
 
-                <dialog id="client-dialog" buttons="[
-                    {'id': 'ok', 'text': _('OK')},
-                    {'id': 'cancel', 'text': _('Cancel')},
-                ]" visible="False">
-                    <pad>
+                <tab title="Directories">
+                    <bind:collection bind="directories" id="directories">
                         <vc>
-                            <formline text="Username">
-                                <textbox bind="username" />
-                            </formline>
-                            <formline text="Password">
-                                <textbox bind="password" />
-                            </formline>
+                            <dt bind="__items" />
+                            <button icon="plus" id="create-directory" text="Create" />
                         </vc>
-                    </pad>
-                </dialog>
-            </tab>
-        </tabs>
-    </pad>
-</body>
\ No newline at end of file
+
+                        <bind:template>
+                            <collapserow>
+                                <box>
+                                    <right>
+                                        <button bind="__delete" icon="remove" style="icon" warning="Confirm deletion" />
+                                    </right>
+                                    <hc>
+                                        <icon icon="folder-close" />
+                                        <label bind="name" />
+                                    </hc>   
+                                </box>
+                                
+                                <indent>
+                                    <vc>
+                                        <formline text="Name">
+                                            <textbox bind="name" />
+                                        </formline>
+                                        <formline text="Path">
+                                            <pathbox directory="True" bind="path" />
+                                        </formline>
+                                        <formline text="Permissions">
+                                            <bind:collection bind="permissions" id="permissions">
+                                                <vc>
+                                                    <vc bind="__items" />
+                                                    <button icon="plus" id="create-permission" text="Create" />
+                                                </vc>
+
+                                                <bind:template>
+                                                    <dropdown bind:value="client" id="user-dropdown" />
+                                                </bind:template>
+                                            </bind:collection>
+                                        </formline>
+                                    </vc>
+                                </indent>
+                            </collapserow>
+                        </bind:template>
+                    </bind:collection>
+                </tab>
+            </tabs>
+        </pad>
+    </body>
+
+    <button id="save" icon="ok" text="Save" />
+</vc>
\ No newline at end of file
diff --git a/mediadb.py b/mediadb.py
index 2da7489..2653b38 100644
--- a/mediadb.py
+++ b/mediadb.py
@@ -4,7 +4,7 @@ from ajenti.plugins import manager
 from sqlalchemy.orm import *
 from sqlalchemy import *
 from sqlalchemy.ext.declarative import declarative_base
-
+from sqlalchemy.orm.exc import NoResultFound
 
 ModelBase = declarative_base()
 
@@ -15,6 +15,7 @@ class Client (ModelBase):
     id = Column(Integer, primary_key=True)
     username = Column(String(255))
     password = Column(String(255))
+    directory_permissions = relationship('ClientDirectoryPermission', backref='client')
 
 
 class RootDirectory (ModelBase):
@@ -23,6 +24,7 @@ class RootDirectory (ModelBase):
     id = Column(Integer, primary_key=True)
     name = Column(String(255))
     path = Column(String(1023))
+    permissions = relationship('ClientDirectoryPermission', backref='directory')
 
 
 class ClientDirectoryPermission (ModelBase):
@@ -30,9 +32,7 @@ class ClientDirectoryPermission (ModelBase):
     
     id = Column(Integer, primary_key=True)
     client_id = Column(Integer, ForeignKey('clients.id'))
-    client = relationship(Client, primaryjoin=client_id == Client.id)
     directory_id = Column(Integer, ForeignKey('root_directories.id'))
-    directory = relationship(RootDirectory, primaryjoin=directory_id == RootDirectory.id)
 
 
 @plugin
@@ -54,10 +54,10 @@ class DB (BasePlugin):
             encoding='utf-8'
         )
         try:
-            engine.execute("CREATE DATABASE IF NOT EXISTS %s" % self.DB_NAME)
+            engine.execute('CREATE DATABASE IF NOT EXISTS %s' % self.DB_NAME)
         except:
             pass
-        engine.execute("USE %s" % self.DB_NAME)
+        engine.execute('USE %s' % self.DB_NAME)
         ModelBase.metadata.create_all(engine)
         self.session = sessionmaker(engine)()
 
@@ -73,4 +73,14 @@ class DB (BasePlugin):
 
     def delete(self, obj):
         self.session.delete(obj)
-        self.session.commit()
\ No newline at end of file
+        self.session.commit()
+
+    def get_client(self, username):
+        try:
+            return self.session.query(Client).filter(Client.username == username).one()
+        except NoResultFound:
+            return None
+
+    def get_by_id(self, cls, id):
+        return self.session.query(cls).get(id)
+
