Ajenti documentation¶
Documentation¶
FAQ¶
How do I add domains/PHP/email accounts/websites?¶
Pure Ajenti is a server control panel, not a hosting control panel. You need the Ajenti V add-on for web-hosting stuff: http://ajenti.org/#product-ajenti-v.
I forgot my password¶
Open /etc/ajenti/config.json, look for your user entry, and replace whole password hash entry with a new plaintext password. Restart Ajenti. Click “save” under “Configuration” to rehash the password.
My OS isn’t supported, but I’m a brave adventurer¶
pip install ajenti
Installation¶
Debian Packages¶
Ajenti requires Debian 6 or later. Debian 5 might work with Python 2.6 installed.
Debian Squeeze requires squeeze-backports repository: http://backports.debian.org/Instructions/
Add repository key:
wget http://repo.ajenti.org/debian/key -O- | apt-key add -
Add repository to /etc/apt/sources.list:
echo "deb http://repo.ajenti.org/debian main main debian" >> /etc/apt/sources.list
Install the package:
apt-get update && apt-get install ajenti
Start the service:
service ajenti restart
Ubuntu Packages¶
Ajenti requires ubuntu 12.04 Precise Pangolin. Previous releases might work with Python upgraded.
Add repository key:
wget http://repo.ajenti.org/debian/key -O- | apt-key add -
Add repository to /etc/apt/sources.list:
echo "deb http://repo.ajenti.org/ng/debian main main ubuntu" >> /etc/apt/sources.list
Install the package:
apt-get update && apt-get install ajenti
Start the service:
service ajenti restart
RPM Packages¶
Ajenti requires EPEL repositories: http://fedoraproject.org/wiki/EPEL
Add repository key:
wget http://repo.ajenti.org/ajenti-repo-1.0-1.noarch.rpm
rpm -i ajenti-repo-1.0-1.noarch.rpm
Install the package:
yum install ajenti
Start the service:
service ajenti restart
Note
Package does not match intended download?
yum clean metadata
FreeBSD Installation¶
Prerequisites:
cd /usr/ports/devel/py-gevent; make install clean;
cd /usr/ports/devel/py-lxml; make install clean;
cd /usr/ports/devel/py-pip; make install clean;
cd /usr/ports/net/py-ldap2; make install clean;
cd /usr/ports/security/stunnel; make install clean;
Download and install latest Ajenti build from PYPI:
pip install ajenti
Install rc.d script:
wget https://raw.github.com/ajenti/ajenti/master/packaging/files/ajenti-bsd -O /etc/rc.d/ajenti
Running Ajenti¶
Starting service¶
Packages install binary ajenti-panel and initscript ajenti. You can ensure the service is running:
service ajenti restart
or:
/etc/init.d/ajenti restart
Ajenti can be run in a verbose debug mode:
ajenti-panel -v
The panel will be available on HTTPS port 8000 by default. The default username is root, and the password is admin
Commandline options¶
- -c, –config <file> - Use given config file instead of default
- -v - Debug/verbose logging
- -d, –daemon - Run in background (daemon mode)
- –set-platform <id> - Override OS detection
Debugging¶
Running ajenti with -v
enables additional logging and Exconsole emergency console (see https://github.com/Eugeny/exconsole).
Exconsole can be triggered by a crash, sending SIGQUIT or pressing Ctrl-\
on the controlling terminal.
Developers¶
Getting Started with Plugin Development¶
Prerequisites¶
The following are the absolutely minimal set of software required to build and run Ajenti:
- git
- coffee-script (use NPM)
- lessc (use NPM)
If you don’t have CoffeeScript or LESS compiler, you won’t be able to make changes to Ajenti CSS/JS files. In this case, download sources from PyPI, which includes compiled CSS/JS resources.
Debian/Ubuntu extras:
- apt-show-versions
- python-dbus (ubuntu)
Setting up¶
Download the source:
git clone git://github.com/ajenti/ajenti.git -b 1.x
(or download them from PyPI: https://pypi.python.org/pypi/ajenti)
Install the dependencies:
[sudo] pip install -Ur requirements.txt
Launch Ajenti in debug mode:
make run
Navigate to http://localhost:8000/.
Press Ctrl-at any time to launch an interactive Python shell and Ctrl-D to resume Ajenti.
CoffeeScript and LESS files will be recompiled automatically when you refresh the page; Python code will not. Additional debug information will be available in the console output and browser console.
Ajenti source code includes various example plugins under Demo category; their source is available in ajenti/plugins/test
directory.
Creating new plugin package¶
New plugins can be placed in both <source>/ajenti/plugins/
(if you expect inclusion in the source tree) and /var/lib/ajenti/plugins
.
Each plugin package consists of few Python modules, which contain ajenti.api.plugin
classes (plugins).
Packages also may contain static files, CoffeeScript and LESS code, and XML user interface layouts:
* ajenti
* plugins
* test
* content
* css
- 1.less
* js
- 2.coffee
* static
- 3.png
* layout
- 4.xml
- __init__.py
- main.py
Plugins¶
To get started, create an empty directory <source>/ajenti/plugins/test
.
Place a file called __init__.py
there:
from ajenti.api import *
from ajenti.plugins import *
info = PluginInfo(
title='Test',
icon=None,
dependencies=[
PluginDependency('main'),
],
)
def init():
import main
In the same directory, create module main.py
. The comments explain the concept behind plugins architecture:
from ajenti.api import *
@interface
class IShape (object):
"""
This is an interface, specifying the methods required.
"""
def number_of_corners(self):
pass
@plugin
class Square (BasePlugin, IShape):
"""
A sample implementation, note the inheritance from both BasePlugin (optional but gives extra options such as context management) and the interface.
"""
def init(self):
"""
init() methods are automatically called for plugins, maintaining inheritance hierarchy
"""
print 'Square #%s initialized' % id(self)
def number_of_corners(self):
return 4
@plugin
class Circle (BasePlugin, IShape):
def number_of_corners(self):
return 0
print 'IShape is implemented by', IShape.get_class()
foo = IShape.get() # get/create any instance of any IShape implementation
# or, more verbose, IShape.get_class().new()
print 'foo corners:', foo.number_of_corners()
# The instances are by default singleton:
print foo == IShape.get() # True
# But you can create separate ones:
foo2 = IShape.get_class().new()
print foo == foo2 # False, different instances
for another_foo in IShape.get_all(): # iterate over all possible IShape implementations
print '\n%s says:' % another_foo, another_foo.number_of_corners()
print IShape.get_instances() # lists all three active IShape instances
Output:
IShape is implemented by <class 'ajenti.plugins.test.main.Square'>
Square #24838864 initialized
foo corners: 4
True
Square #24838928 initialized
False
<ajenti.plugins.test.main.Square object at 0x17b02d0> says: 4
<ajenti.plugins.test.main.Circle object at 0x17b0390> says: 0
[<ajenti.plugins.test.main.Square object at 0x17b02d0>, <ajenti.plugins.test.main.Square object at 0x17b0310>, <ajenti.plugins.test.main.Circle object at 0x17b0390>]
Learn about more interface and plugin methods here: ajenti.api.plugin
Continue to User Interface
User Interface¶
Theory¶
The whole Ajenti UI is a DOM tree of ajenti.ui.UIElement
objects. After each update, the UI tree is serialized into JSON and sent to browser, where HTML DOM is assembled from it with the help of CoffeeScript code.
Unlike conventional web apps, Ajenti is a stateful machine, which means you adopt a simple workflow similar to developing desktop apps, not websites.
Example¶
from ajenti.api import *
from ajenti.plugins.main.api import SectionPlugin
from ajenti.ui import on
@plugin
class TestPlugin (SectionPlugin):
def init(self):
self.title = 'Test' # those are not class attributes and can be only set in or after init()
self.icon = 'question'
self.category = 'Demo'
"""
UI Inflater searches for the named XML layout and inflates it into
an UIElement object tree
"""
self.append(self.ui.inflate('test:main'))
self.counter = 0
self.refresh()
def refresh(self):
"""
Changing element properties automatically results
in an UI updated being issued to client
"""
self.find('counter-label').text = 'Counter: %i' % self.counter
@on('increment-button', 'click')
def on_button(self):
"""
This method is called every time a child element
with ID 'increment-button' fires a 'click' event
"""
self.counter += 1
self.refresh()
Add a subdirectory layout
and place a file named main.xml
there:
<body> <!-- an overall plugin container panel -->
<pad> <!-- adds whitespace padding -->
<hc> <!-- horizontal container -->
<label id="counter-label" />
<button id="increment-button" text="+1" style="mini" />
</hc>
</pad>
</body>
Now restart Ajenti. The new plugin Test will be visible under Demo category. Clicking the +1 button will increase the counter.

The visible part of plugin is an UIElement, inherited from ajenti.plugins.main.api.SectionPlugin
.
When you click the button, the ‘click’ even is fired down the UI tree. The first method to have correctly decorated @on
method will handle the event. Alternatively, you can set event handler on the element itself by adding this code to init
:
self.find('increment-button').on('click', self.on_button)
List of UI Elements¶
Containers¶
<box>: Box¶
Simplest container ever, can be scrollable
@p('width', default=None)
@p('height', default=None)
@p('scroll', default=False, type=bool)
<pad>: Whitespace¶
Adds a padding on four sides.
<indent>: Indentation¶
Adds a padding on two sides.
<right>: Pull-to-right¶
Pulls its content to right with float: right
<hc>: Horizontal Container¶
A horizontal stacking container
<vc>: Vertical Container¶
A vertical stacking container
<formline>: Form Line¶

Container for form controls, has a caption
@p('text', default='', bindtypes=[str, unicode])
<formgroup>: Form Group¶

Provides a nice form section separator
@p('text', default='', bindtypes=[str, unicode])
<dt>, <dtr>, <dth> <dtd>: Data Table¶

A lined table
<dt>
<dtr>
<dth text="Header" />
</dtr>
<dtr>
<dtd>
<label text="Child" />
</dtd>
</dtr>
</dt>
<collapserow>: Collapsible Table Row¶
A click-to expand table row
<dt>
<collapserow>
<label text="Header Child" />
<label text="Body Child" />
</collapserow>
</dt>
First child is a header and always visible. Second is the collapsible body.
@p('expanded', default=False, type=bool, bindtypes=[bool])
<lt>, <ltr>, <ltd>: Layout Table¶
An invisible layout grid (no padding).
<sortabledt>: Sortable Data Table¶

User will be able to reorder rows
<sortabledt>
<dtr>
<dtd>
<label text="Child 1" />
</dtd>
</dtr>
<dtr>
<dtd>
<label text="Child 2" />
</dtd>
</dtr>
<dtr>
<dtd>
<label text="Child 3" />
</dtd>
</dtr>
</sortabledt>
@p('sortable', default=True, type=bool)
@p('order', default='', type=str)
The order property holds the reordered element indexes ([2,1,3]
as seen on the image)
<tabs>, <tab>: Tabs¶

User will be able to reorder rows
<tabs>
<tab title="1">
<label text="Child 1" />
</tab>
<tab title="2">
<label text="Child 2" />
</tab>
<tab title="3">
<label text="Child 3" />
</tab>
</tabs>
<tabs>:
@p('active', default=0)
<title>:
@p('title', default='', bindtypes=[str, unicode])
Standard Controls¶
<label>: Label¶
@p(‘text’, default=’‘, bindtypes=[str, unicode, int, float])
<button>: Button¶

@p('text', default='', bindtypes=[str, unicode])
@p('icon', default=None)
@p('warning', default=None) # display a warning text before click
click() # fired on click
<icon>: Inline Icon¶
Icon IDs in Ajenti are coming from this page: http://fortawesome.github.io/Font-Awesome/icons/
@p(‘icon’, default=None, bindtypes=[str, unicode])
<progressbar>: Progress Bar¶

@p('width', default=None)
@p('value', default=0, type=float, bindtypes=[float]) # between 0.0 and 1.0
<list>, <listitem>: Lists¶

A list with clickable items:
<list>
<listitem>
<label text="child" />
</listitem>
<listitem>
<label text="child" />
</listitem>
<listitem>
<label text="child" />
</listitem>
</list>
<listitem>:
click() # fired on click
Inputs¶
<textbox>: Textbox¶
@p('value', default='', bindtypes=[str, unicode, int])
@p('type', default='text') # or 'integer'
<editable>: Editable Label¶

A label that becomes textbox when clicked:
@p('value', default='', bindtypes=[str, unicode])
@p('icon', default=None)
<checkbox>: Checkbox¶
@p('text', default='')
@p('value', default=False, bindtypes=[bool])
<dropdown>: Dropdown Select¶
@p('labels', default=[], type=list)
@p('values', default=[], type=list)
@p('value', default='', bindtypes=[str, int, unicode])
<combobox>: Combo Box¶
@p('labels', default=[], type=list)
@p('values', default=[], type=list)
@p('separator', default=None, type=str) # if set, combobox becomes autocomplete-multiple-input-box
@p('value', default='', bindtypes=[str, unicode])
Plugin resources¶
Plugin resource files are contained under content
directory nested in the plugin directory. The content
directory can optionally contain css
, js
and static
directories holding files of respective types.
Ajenti will accept following filename extensions. injected
resources will be added to <head>
of web UI. cleaned
resources will be deleted before build. compile
resources will be pre-processed using applicable compiler.
/content/js/*.js
- source JS (compile)/content/css/*.css
- source JS (compile)/content/js/*.coffee
- source CoffeeScript (compile)/content/css/*.less
- source LESS (compile)/content/css/*.i.less
- source LESS included somewhere else (ignored)/content/js/*.m.js
- pre-built JS (injected)/content/css/*.m.css
- pre-built CSS (injected)/content/js/*.c.js
- compiled JS (injected, cleaned)/content/css/*.c.css
- compiled CSS (injected, cleaned)
Resources under /static/
are served over HTTP at the following URL: /ajenti:static/<plugin-name>/<resource-path>
, e.g.: /ajenti:static/main/favicon.png
.
Notifications¶
Example¶
Code:
from ajenti.api import plugin
from ajenti.plugins.main.api import SectionPlugin
from ajenti.ui import on
@plugin
class Test (SectionPlugin):
def init(self):
self.title = 'Notifications'
self.icon = 'smile'
self.category = 'Demo'
self.append(self.ui.inflate('test_notifications:main'))
self.find('style').labels = self.find('style').values = ['info', 'warning', 'error']
@on('show', 'click')
def on_show(self):
self.context.notify(self.find('style').value, self.find('text').value)
Layout:
<body>
<pad>
<vc>
<formline text="Text">
<textbox id="text" />
</formline>
<formline text="Style">
<dropdown id="style" />
</formline>
<formline>
<button icon="ok" id="show" text="Show" />
</formline>
</vc>
</pad>
</body>
Bindings¶
Binding mechanism lets you bind your Python objects directly to UI elements and build CRUD interfaces in minutes.
Example: https://github.com/Eugeny/ajenti/blob/dev/ajenti/plugins/test/binder/main.py
Simple bindings¶
Code:
from ajenti.api import plugin
from ajenti.plugins.main.api import SectionPlugin
from ajenti.ui import on
from ajenti.ui.binder import Binder
class Settings (object): # use new-style object at all times!
def __init__(self):
self.label_text = ''
self.label_bold = False
self.label_style = ''
@plugin
class Test (SectionPlugin):
def init(self):
self.title = 'Bindings'
self.icon = 'smile'
self.category = 'Demo'
self.append(self.ui.inflate('test_bindings:main'))
self.settings = Settings()
# Bind the settings object to the section UI element (self)
self.binder = Binder(self.settings, self)
self.binder.populate()
@on('apply', 'click')
def on_apply(self):
self.binder.update() # update objects from UI
self.settings.label_style = 'bold' if self.settings.label_bold else ''
self.binder.populate() # update UI with objects
Here, the Settings
object acts as a data model. ajenti.ui.binder.Binder
object connects data with UI. autodiscover
method scans the UI for bindable elements, populate
method updates UI with the data from bound objects, and update
method applies UI changes to objects.
Layout:
<body>
<pad>
<vc>
<formline text="Text">
<textbox bind="label_text" />
</formline>
<formline text="Bold">
<checkbox bind="label_bold" />
</formline>
<formline>
<button icon="ok" id="apply" text="Apply" />
</formline>
<formline text="Result">
<label bind:text="label_text" bind:style="label_style" />
</formline>
</vc>
</pad>
</body>
We have added bind
attributes to the elements which are to be auto-populated with values. If you want to bind multiple properties, use XML attributes like bind:text
or bind:style
. Dictionary values and __getattr__
powered indexers can be bound by enclosing the key name in square brackets, e.g.: <label bind:value="[somekey]" />

If you would like to continue binding on a nested object, use binder:context
attribute:
<body>
<vc>
<label bind:value="simple_str_field" /> <!-- data.simple_str_field -->
<box binder:context="object_field">
<label bind:value="objects_str_field" /> <!-- data.object_field.objects_str_field -->
</box>
<box binder:context="dict_field">
<label bind:value="[dict_key]" /> <!-- data.dict_field['dict_key'] -->
</box>
</vc>
</body>
Collection Bindings¶
Ajenti supports following collection bindings:
- Binding iterable to list of elements (
ajenti.ui.binder.ListAutoBinding
)- Binding dict to key-annotated elements (
ajenti.ui.binder.DictAutoBinding
)- Binding iterable with a child template (
ajenti.ui.binder.CollectionAutoBinding
)
Code:
import json
from ajenti.api import plugin
from ajenti.plugins.main.api import SectionPlugin
from ajenti.ui import on
from ajenti.ui.binder import Binder
class Person (object):
def __init__(self, name, **kwargs):
self.name = name
self.params = kwargs
def __repr__(self):
return json.dumps({'name': self.name, 'params': self.params})
@plugin
class Test (SectionPlugin):
def init(self):
self.title = 'Collection Bindings'
self.icon = 'smile'
self.category = 'Demo'
self.append(self.ui.inflate('test_bindings_collections:main'))
andy = Person('andy', phone='123')
bob = Person('bob', phone='321')
self.obj_list = (andy, bob)
self.obj_collection = [andy, bob]
# This callback is used to autogenerate a new item with 'Add' button
self.find('collection').new_item = lambda c: Person('new person', phone='000')
self.binder = Binder(self, self)
self.refresh()
def refresh(self):
self.binder.update()
self.raw_data = repr(self.obj_collection)
self.binder.populate()
@on('apply', 'click')
def on_apply(self):
self.refresh()
Layout:
<body>
<pad>
<vc>
<formline text="bind:list">
<bind:list bind="obj_list">
<box>
<label bind="name" />
</box>
<box>
<label bind="name" />
</box>
</bind:list>
</formline>
<formline text="bind:collection">
<bind:collection bind="obj_collection" id="collection">
<vc>
<dt bind="__items">
<dtr>
<dth text="Name" />
<dth text="Phone" />
<dth />
</dtr>
</dt>
<button icon="plus" style="mini" bind="__add" />
</vc>
<bind:template>
<dtr>
<dtd> <textbox bind="name" /> </dtd>
<dtd>
<bind:dict bind="params">
<textbox bind="phone" />
</bind:dict>
</dtd>
<dtd> <button icon="remove" style="mini" bind="__delete" /> </dtd>
</dtr>
</bind:template>
</bind:collection>
</formline>
<formline text="Raw data">
<label bind="raw_data" />
</formline>
<formline>
<button icon="ok" id="apply" text="Apply" />
</formline>
</vc>
</pad>
</body>
Note the special bind
attribute values used in bind:collection
:
__items
denotes the container for items__add
denotes a button which will generate a new item (optional)__remove
denotes a button which will remove an item (optional)

Custom UI Controls¶
You can create any type of a reusable UI control. Remember to take a look at default controls in ajenti/plugins/main
for guidance.
Example¶
In this example, we’ll create a HTML5 slider control.
Code:
from ajenti.api import plugin
from ajenti.plugins.main.api import SectionPlugin
from ajenti.ui import on, p, UIElement
@plugin
class Test (SectionPlugin):
def init(self):
self.title = 'Controls'
self.icon = 'smile'
self.category = 'Demo'
self.append(self.ui.inflate('test_controls:main'))
@on('check', 'click')
def on_show(self):
self.context.notify('info', 'Value is %i' % self.find('slider').value)
@p('value', type=int, default=0)
@plugin
class Slider (UIElement):
typeid = 'slider'
Layout:
<body>
<pad>
<vc>
<formline text="Slider">
<slider id="slider" value="0" />
</formline>
<formline>
<button icon="ok" id="check" text="Get value" />
</formline>
</vc>
</pad>
</body>
Control class is decorated with ajenti.ui.p()
for each of its properties.
The main client-side logic is implemented through CoffeeScript code (though you can try to get away with pure-JS).
CoffeeScript:
class window.Controls.slider extends window.Control
createDom: () ->
# createDom() must return HTML
"""
<div>
<input type="range" min="0" max="10" />
</div>
"""
setupDom: (dom) ->
# setupDom may attach event handler and perform other DOM manipulations
# use this.properties hash to populate control with its current state
super(dom)
@input = $(@dom).find('input')
@input.val(@properties.value)
detectUpdates: () ->
# detectUpdates() should return a hash containing only changed properties
# be sure to not report unchanged properties since this will lead to infinite update loops
r = {}
value = parseInt(@input.val())
if value != @properties.value
r.value = value
return r

Handling HTTP Requests¶
Example¶
This example illustrates various HTTP responses. Try following URLs:
Code:
from ajenti.api import plugin, BasePlugin
from ajenti.api.http import HttpPlugin, url
@plugin
class HttpDemo (BasePlugin, HttpPlugin):
@url('/ajenti:demo/notify')
def get_page(self, context):
if context.session.identity is None:
context.respond_redirect('/')
self.context.notify('info', context.query.getvalue('text', ''))
context.respond_ok()
return ''
@url('/ajenti:demo/respond/(?P<what>.+)')
def get_response(self, context, what=None):
if what == 'ok':
context.respond_ok()
return 'Hello!'
if what == 'redirect':
return context.respond_redirect('/')
if what == 'server_error':
return context.respond_server_error()
if what == 'forbidden':
return context.respond_forbidden()
if what == 'not_found':
return context.respond_not_found()
if what == 'file':
return context.file('/etc/issue')
Dashboard Widgets¶
Dashboard plugin API exposes two widget types: ajenti.plugins.dashboard.api.DashboardWidget
and ajenti.plugins.dashboard.api.ConfigurableWidget
.
Simple widget¶
Example:
from ajenti.api import plugin
from ajenti.plugins.dashboard.api import DashboardWidget
@plugin
class HelloWidget (DashboardWidget):
name = 'Hello Widget'
icon = 'comment'
def init(self):
self.append(self.ui.inflate('hello_widget:widget'))
self.find('text').text = 'Hello'
Layout:
<hc>
<box width="20">
<icon id="icon" icon="comment" />
</box>
<box width="90">
<label id="name" style="bold" text="Widget" />
</box>
<label id="text" />
</hc>
Configurable widget¶
Configurable widgets have a dict
config, configuration dialog and a configuration button.
Example (real dashboard Text widget):
from ajenti.api import plugin
from ajenti.plugins.dashboard.api import ConfigurableWidget
@plugin
class TextWidget (ConfigurableWidget):
name = _('Text')
icon = 'font'
def on_prepare(self):
# probably not configured yet!
self.append(self.ui.inflate('dashboard:text'))
def on_start(self):
# configured by now
self.find('text').text = self.config['text']
def create_config(self):
return {'text': ''}
def on_config_start(self):
# configuration begins now, a chance to fill the configuration dialog
pass
def on_config_save(self):
self.config['text'] = self.dialog.find('text').value
Layout:
<hc>
<label id="text" text="---" />
<dialog id="config-dialog" visible="False">
<pad>
<formline text="{Text}">
<textbox id="text" />
</formline>
</pad>
</dialog>
</hc>
API Reference¶
ajenti¶
-
ajenti.
config
= None¶ Loaded config, is a reconfigure.items.ajenti.AjentiData
-
ajenti.
platform
= None¶ Current platform
-
ajenti.
platform_string
= None¶ Human-friendly platform name
-
ajenti.
platform_unmapped
= None¶ Current platform without “Ubuntu is Debian”-like mapping
-
ajenti.
installation_uid
= None¶ Unique installation ID
-
ajenti.
version
= None¶ Ajenti version
-
ajenti.
server
= None¶ Web server
-
ajenti.
debug
= False¶ Debug mode
ajenti.api¶
-
class
ajenti.api.
BasePlugin
[source]¶ A base plugin class that provides
AppContext
andclassconfig
functionality.-
classconfig_editor
= None¶ Override this in your class with an ajenti.plugins.configurator.api.ClassConfigEditor derivative
-
classconfig_name
= None¶ Override this in your class if you want this plugin to be configurable through Configure > Plugins
-
classconfig_root
= False¶ When True, classconfig will be stored in root’s config section disregarding current user
-
context
= None¶ Automatically receives a reference to the current
AppContext
-
default_classconfig
= None¶ Override this in your class with a default config object (must be JSON-serializable)
-
init
()[source]¶ Do your initialization here. Correct bottom-to-up inheritance call order guaranteed.
-
load_classconfig
()[source]¶ Loads the content of
classconfig
attribute from the user’s configuration section.
-
-
class
ajenti.api.
AppContext
(parent, httpcontext)[source]¶ A session-specific context provided to everyone who inherits
BasePlugin
.-
session
¶ current HTTP session:
ajenti.middleware.Session
-
user
¶ current logged in user:
reconfigure.items.ajenti.UserData
Methods injected by MainPlugin:
-
notify
(text)¶ Parameters: text – Notification text to show
-
launch
(id, *args, **kwargs)¶ Parameters: id – Intent ID to be launched
-
-
ajenti.api.
plugin
(cls)[source]¶ A decorator to create plugin classes:
@plugin class SomePlugin (ISomething): pass
If the class has a
verify
method returningbool
, it’s invoked. If the method returnedFalse
, plugin is rejected and removed from implementation lists.If the class has a
platforms
attribute, which is a list of supported platform names, it’s compared against the current runtime OS platform. If the current platform is not in the list, plugin is also rejected.Following class methods are injected.
-
.
get
(context=<current context>)¶ Returns: any existing instance or creates a new one
-
.
new
(*args, context=<current context>, **kwargs)¶ Returns: a new instance. Use this method instead of constructor, since it invokes the proper initialization chain and registers the instance
Return type: class, None -
-
ajenti.api.
rootcontext
(cls)[source]¶ Enforces use of root PluginContext by default for .get() and .new() classmethods.
-
ajenti.api.
notrack
(cls)[source]¶ Disables instance tracking of plugin (and derivative) instances within PluginContext via get/get_all and similar methods.
Return type: class
-
ajenti.api.
notrack_this
(cls)[source]¶ Disables instance tracking of plugin instances within PluginContext via get/get_all and similar methods.
Return type: class
-
ajenti.api.
track
(cls)[source]¶ Enables previously disabled instance tracking of plugin.
Return type: class
-
ajenti.api.
extract_context
()[source]¶ An utility function that extracts and returns the nearest
AppContext
from the current call stack.Return type: ajenti.plugins.PluginContext
, None
-
ajenti.api.
interface
(cls)[source]¶ A decorator to create plugin interfaces:
@interface class ISomething (object): def contract(self): pass
Following class methods are injected:
-
.
get
(context=<current context>) Returns: any existing instance or creates a new one
-
.
get_all
(context=<current context>)¶ Returns: list of instances for each implementation
-
.
get_class
()¶ Returns: any implementation class
-
.
get_classes
()¶ Returns: list of implementation classes
-
.
get_instances
(context=<current context>)¶ Returns: list of all existing instances
Return type: class -
ajenti.api.http¶
ajenti.api.sensors¶
-
class
ajenti.api.sensors.
Sensor
[source]¶ Base class for a Sensor. Sensors measure system status parameters and can be queried from other plugins.
-
static
find
(id)[source]¶ Returns a Sensor by name
Parameters: id (str) – sensor ID Return type: Sensor
, None
-
id
= None¶
-
measure
(variant=None)[source]¶ Override this and perform the measurement.
Parameters: variant (str, None) – variant to measure Return type: int, float, tuple, list, dict, str
-
timeout
= 0¶
-
static
ajenti.http¶
ajenti.ipc¶
ajenti.middleware¶
ajenti.plugins¶
-
class
ajenti.plugins.
ModuleDependency
(module_name)[source]¶ -
-
ModuleDependency.
description
= 'Python module'¶
-
-
class
ajenti.plugins.
PluginDependency
(plugin_name)[source]¶ -
-
PluginDependency.
description
= 'Plugin'¶
-
-
class
ajenti.plugins.
BinaryDependency
(binary_name)[source]¶ -
-
BinaryDependency.
description
= 'Application binary'¶
-
ajenti.profiler¶
ajenti.ui¶
-
class
ajenti.ui.
UI
[source]¶ The root UI object, one per session
-
create
(typeid, *args, **kwargs)[source]¶ Creates an element by its type ID.
Parameters: typeid (str) – type ID
-
dispatch_event
(uid, event, params=None)[source]¶ Dispatches an event to an element with given UID
Parameters: - uid (int) – element UID
- event (str) – event name
- params (dict, None) – event arguments
-
find
(id)[source]¶ Parameters: id (str) – element ID Returns: nearest element with given ID Return type: UIElement, None
-
find_uid
(uid)[source]¶ Parameters: uid (int) – element UID Returns: nearest element with given unique ID Return type: UIElement, None
-
-
class
ajenti.ui.
UIElement
(ui, typeid=None, children=[], **kwargs)[source]¶ Base UI element class
-
bind
¶ Bound property name
-
bindtransform
¶ Value transformation function for one-direction bindings
-
broadcast
(method, *args, **kwargs)[source]¶ Calls
method
on every member of the subtreeParameters: method (str) – method
-
client
¶ Whether this element’s events are only processed on client side
-
clone
(set_ui=None, set_context=None)[source]¶ Returns: a deep copy of the element and its children. Property values are shallow copies. Return type: UIElement
-
contains
(element)[source]¶ Checks if the
element
is in the subtree ofself
Parameters: element ( UIElement
) – element
-
dispatch_event
(uid, event, params=None)[source]¶ Dispatches an event to an element with given UID
Parameters: - uid (int) – element UID
- event (str) – event name
- params (dict, None) – event arguments
-
event
(event, params=None)[source]¶ Invokes handler for
event
on this element with given**params
Parameters: - event (str) – event name
- params (dict, None) – event arguments
-
find
(id)[source]¶ Parameters: id (str) – element ID Returns: the nearest child with given ID or None
Return type: UIElement
, None
-
find_type
(typeid)[source]¶ Returns: the nearest child with given type ID or None
Return type: UIElement
, None
-
find_uid
(uid)[source]¶ Parameters: uid (int) – element UID Returns: the nearest child with given UID or None
Return type: UIElement
, None
-
id
¶ Element ID
-
nearest
(predicate, exclude=None, descend=True)[source]¶ Returns the nearest child which matches an arbitrary predicate lambda
Parameters: - predicate (function) –
lambda element: bool
- exclude (function, None) –
lambda element: bool
- excludes matching branches from search - descend (bool) – whether to descend inside matching elements
- predicate (function) –
-
on
(event, handler, *args)[source]¶ Binds event with ID
event
tohandler
.*args
will be passed to thehandler
. :param event: event :type event: str :param handler: handler :type handler: function
-
path_to
(element)[source]¶ Returns: a list of elements forming a path from self
toelement
Return type: list
-
property_definitions
¶
-
reverse_event
(event, params=None)[source]¶ Raises the event on this element by feeding it to the UI root (so that
@on
methods in ancestors will work).Parameters: - event (str) – event name
- params (dict) – event arguments
-
style
¶ Additional CSS class
-
typeid
= None¶
-
visible
¶ Visibility of the element
-
-
ajenti.ui.
p
(prop, default=None, bindtypes=[], type=<type 'unicode'>, public=True, doc=None)[source]¶ Creates an UI property inside an
UIElement
:@p('title') @p('category', default='Other', doc='Section category name') @p('active', default=False) class SectionPlugin (BasePlugin, UIElement): typeid = 'main:section'
Parameters: - default (object) – Default value
- bindtypes (list) – List of Python types that can be bound to this property
- type (object) – expected Python type for this value
- public (bool) – whether this property is rendered and sent to client
- doc (str, None) – docstring
Return type: function
-
ajenti.ui.
on
(id, event)[source]¶ Sets the decorated method to handle indicated event:
@plugin class Hosts (SectionPlugin): def init(self): self.append(self.ui.inflate('hosts:main')) ... @on('save', 'click') def save(self): self.config.save()
Parameters: - id (str) – element ID
- event (str) – event name
Return type: function
ajenti.ui.binder¶
-
class
ajenti.ui.binder.
Binding
(object, attribute, ui)[source]¶ A base class for bindings. Binding is a link between a Python object attribute and Ajenti UI element’s property.
Parameters: - object – a Python object
- attribute – attribute name
- ui – Ajenti
ajenti.ui.UIElement
-
class
ajenti.ui.binder.
PropertyBinding
(obj, attribute, ui, property=None)[source]¶ A simple binding between UI element’s property and Python object’s attribute
Parameters: property – UI property name. If None
, property is deduced frombindtypes
-
class
ajenti.ui.binder.
ListAutoBinding
(object, attribute, ui)[source]¶ Binds values of a collection to UI element’s children consecutively, using
Binder
-
class
ajenti.ui.binder.
DictAutoBinding
(object, attribute, ui)[source]¶ Binds values from a dict to UI element’s children mapping ‘bind’ attribute to dict key, using
Binder
-
class
ajenti.ui.binder.
CollectionAutoBinding
(object, attribute, ui)[source]¶ Binds values of a collection to UI element’s children using a template. The expected UI layout:
<xml xmlns:bind="bind"> <bind:collection id="<binding to this>"> <container-element bind="__items"> <1-- instantiated templates will appear here --> </container-element> <bind:template> <!-- a template for one collection item it will be bound to item using ajenti.ui.binder.Binder --> <label bind="some_property" /> <button id="__delete" /> <!-- a delete button may appear in the template --> </bind:template> <button id="__add" /> <!-- an add button may appear inside collection tag --> </bind:collection> </xml>
-
class
ajenti.ui.binder.
Binder
(object=None, ui=None)[source]¶ An automatic object-to-ui-hierarchy binder. Uses
bind
UI property to find what and where to bind. Ifobject
is not None, the Binder is also initialized (seesetup(object)
) with this data object.Parameters: - object – Python object
- ui – UI hierarchy root
-
class
ajenti.ui.binder.
BasicCollectionElement
(ui, typeid=None, children=[], **kwargs)[source]¶ -
binding
¶ Collection binding class to use
-
filter
¶ Called to filter collections values,
lambda value: bool
-
post_bind
¶ Called after binding is complete,
lambda object, collection, ui: None
-
post_item_bind
¶ Called after an item is bound,
lambda object, collection, item, item-ui: None
-
post_item_update
¶ Called after an item is updated,
lambda object, collection, item, item-ui: None
-
values
¶ Called to extract values from the collection,
lambda collection: []
-
-
class
ajenti.ui.binder.
ListElement
(ui, typeid=None, children=[], **kwargs)[source]¶ -
typeid
= 'bind:list'¶
-
-
class
ajenti.ui.binder.
CollectionElement
(ui, typeid=None, children=[], **kwargs)[source]¶ -
add_item
¶ Called to append value to the collection,
lambda item, collection: None
-
delete_item
¶ Called to remove value from the collection,
lambda item, collection: None
-
new_item
¶ Called to create an empty new item,
lambda collection: object()
-
pagesize
¶
-
sorting
¶ If defined, used as key function to sort items
-
typeid
= 'bind:collection'¶
-
ajenti.users¶
-
ajenti.users.
restrict
(permission)[source]¶ Marks a decorated function as requiring
permission
. If the invoking user doesn’t have one,SecurityError
is raised.
-
class
ajenti.users.
PermissionProvider
[source]¶ Override to create your own set of permissions
-
exception
ajenti.users.
SecurityError
(permission)[source]¶ Indicates that user didn’t have a required permission.
-
permission
¶ permission ID
-
-
class
ajenti.users.
UserManager
[source]¶ -
check_password
(username, password, env=None)[source]¶ Verifies the given username/password combo
Return type: bool
-
classconfig_root
= True¶
-
default_classconfig
= {'sync-provider': ''}¶
-
has_permission
(context, permission)[source]¶ Checks whether the current user has a permission
Return type: bool
-
require_permission
(context, permission)[source]¶ Checks current user for given permission and raises
SecurityError
if he doesn’t have one :type permission: str :raises: SecurityError
-
ajenti.util¶
-
ajenti.util.
public
(f)[source]¶ ” Use a decorator to avoid retyping function/class names.
Based on an idea by Duncan Booth: http://groups.google.com/group/comp.lang.python/msg/11cbb03e09611b8a
Improved via a suggestion by Dave Angel: http://groups.google.com/group/comp.lang.python/msg/3d400fb22d8a42e1
-
ajenti.util.
cache_value
(duration=None)[source]¶ Makes a function lazy.
Parameters: duration (int) – cache duration in seconds (default: infinite)
Plugin API Reference¶
ajenti.plugins.main.api¶
ajenti.plugins.dashboard.api¶
-
class
ajenti.plugins.dashboard.api.
ConfigurableWidget
(ui, typeid=None, children=[], **kwargs)[source]¶ Base class for widgets with a configuration dialog
-
on_config_start
()[source]¶ Called when user begins to configure the widget. Should populate the config dialog.
-
-
class
ajenti.plugins.dashboard.api.
DashboardWidget
(ui, typeid=None, children=[], **kwargs)[source]¶ Base class for widgets (inherits
ajenti.ui.UIElement
).-
config
¶ current configuration dict of this widget instance
-
configurable
¶
-
container
¶
If True, user will not be able to add this widget through dashboard
-
icon
= None¶ Widget icon name
-
index
¶
-
name
= '---'¶ Widget type name
-
typeid
= 'dashboard:widget'¶
-
ajenti.plugins.configurator.api¶
ajenti.plugins.db_common.api¶
ajenti.plugins.webserver_common.api¶
ajenti.plugins.packages.api¶
ajenti.plugins.services.api¶
-
class
ajenti.plugins.services.api.
Service
[source]¶ -
-
icon
¶
-
source
= 'unknown'¶ Marks which ServiceManager owns this object
-
ajenti.plugins.tasks.api¶
-
class
ajenti.plugins.tasks.api.
Task
(**kwargs)[source]¶ Base class for custom tasks
Parameters: - name – display name
- ui – full layout name for parameter editor, will be bound to parameter dictionary (so begin it with <bind:dict bind=”params”>)
- hidden – if True, task won’t be available for manual creation
-
name
= '---'¶
-
run
(**kwargs)[source]¶ Override with your task actions here. Raise
TaskError
in case of emergency. Check aborted often and return if it’s True
-
ui
= None¶