Index: main/plugin-odf/plugin.xml
===================================================================
--- main/plugin-odf/plugin.xml	(revision 27588)
+++ main/plugin-odf/plugin.xml	(working copy)
@@ -416,6 +416,11 @@
 					   
 			<component role="org.ametys.odf.workflow.ChangeProgramStateFunction"
 					   class="org.ametys.odf.workflow.ChangeProgramStateFunction"/>
+					   
+		    <component role="org.ametys.odf.workflow.OdfWorkflowTasksComponent"
+			           class="org.ametys.odf.workflow.OdfWorkflowTasksComponent"
+			           logger="org.ametys.odf.workflow.OdfWorkflowTasksComponent"
+			           config="/WEB-INF/param/odf-todolist.xml"/>
         </components>
     </feature>  
     
@@ -4556,4 +4561,51 @@
             </extension>
         </extensions>
     </feature>
+    
+    <feature name="org.ametys.odf.dashboard.tool">
+        <extensions>
+            <extension id="org.ametys.odf.dashboard.Tool"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.ribbon.control.button.UniqueInstanceToolButton">
+                    <param name="role">uitool-dashboard</param>
+                    
+                    <param name="label" i18n="true">PLUGINS_ODF_DASHBOARD_TOOL_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_ODF_DASHBOARD_TOOL_DESC</param>
+                    <param name="footer" i18n="true">PLUGINS_ODF_DASHBOARD_TOOL_FOOTER</param>
+                    
+                    <param name="icon-small" file="true">img/dashboard/icon_small.png</param>
+                    <param name="icon-medium" file="true">img/dashboard/icon_medium.png</param>
+                    <param name="icon-large" file="true">img/dashboard/icon_large.png</param>
+                </action>
+            </extension>
+            
+            <extension id="org.ametys.odf.dashboard.ToolFactory"
+                       point="org.ametys.cms.workspace.uitool.UIToolsFactoriesManager"
+                       class="org.ametys.runtime.ui.impl.StaticClientSideElement">
+                <action class="org.ametys.odf.dashboard.tool.DashboardToolFactory">
+                    <param name="role">uitool-dashboard</param>
+                    
+                    <param name="title" i18n="true">PLUGINS_ODF_DASHBOARD_TOOL_LABEL</param>
+                    <param name="description" i18n="true">PLUGINS_ODF_DASHBOARD_TOOL_DESC</param>
+                    <param name="footer" i18n="true">PLUGINS_ODF_DASHBOARD_TOOL_FOOTER</param>
+                    
+                    <param name="icon-small" file="true">img/dashboard/icon_small.png</param>
+                    <param name="icon-medium" file="true">img/dashboard/icon_medium.png</param>
+                    <param name="icon-large" file="true">img/dashboard/icon_large.png</param>
+                    
+                    <param name="default-location"></param>
+                </action>
+                <scripts>
+                    <file plugin="web">js/org/ametys/web/helper/LanguageHelper.i18n.js</file>
+                    <file>js/org/ametys/odf/dashboard/tool/DashboardTool.i18n.js</file>
+                    <file>js/org/ametys/odf/dashboard/tool/DashboardToolFactory.js</file>
+                </scripts>
+                <css>
+                    <file>css/dashboard.css</file>
+                </css>
+            </extension>
+            
+        </extensions>
+    </feature>
 </plugin>
Index: main/plugin-odf/resources/js/org/ametys/odf/dashboard/tool/DashboardTool.i18n.js
===================================================================
--- main/plugin-odf/resources/js/org/ametys/odf/dashboard/tool/DashboardTool.i18n.js	(revision 0)
+++ main/plugin-odf/resources/js/org/ametys/odf/dashboard/tool/DashboardTool.i18n.js	(revision 0)
@@ -0,0 +1,489 @@
+/*
+ *  Copyright 2014 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+Ext.namespace('org.ametys.odf.dashboard.tool');
+
+org.ametys.odf.dashboard.tool.DashboardTool = function(id, pluginName) 
+{
+    org.ametys.odf.dashboard.tool.DashboardTool.superclass.constructor.call(this, id, pluginName);
+};
+
+Ext.extend(org.ametys.odf.dashboard.tool.DashboardTool, org.ametys.uitool.tool.Tool, {});
+
+//org.ametys.odf.dashboard.tool.DashboardTool.prototype._proxy;
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._tasks;
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._grids;
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype.createPanel = function()
+{
+    var serverMessage = new org.ametys.servercomm.ServerMessage('odf', '/tasks.xml', {}, org.ametys.servercomm.ServerComm.PRIORITY_SYNCHRONOUS, null, this, null);
+    var response = org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+    
+    this._tasks = {};
+    this._grids = [];
+    
+    var taskNodes = response.selectNodes('tasks/task');
+    for (var i=0; i &lt; taskNodes.length; i++)
+    {
+        var taskId = taskNodes[i].getAttribute('id');
+        var taskLabel = taskNodes[i][org.ametys.servercomm.ServerComm.xmlTextContent];
+        this._tasks[taskId] = taskLabel;
+    }
+    
+    var taskPanels = [];
+    for (var taskId in this._tasks)
+    {
+        if (taskId != 'recent-drafts')
+        {
+            var taskLabel = this._tasks[taskId];
+            taskPanels.push(
+                new Ext.Panel({
+                    id: taskId + '-panel',
+                    title: taskLabel,
+                    cls: 'transparent-panel',
+                    layout: 'border',
+                    border: false,
+                    items: [this._drawGridPanel(taskId, taskLabel, 10)]
+                })
+            );
+        }
+    }
+    
+    this._mainPanel = new Ext.Panel ({
+        layout: 'border',
+        layoutConfig: {
+            columns: 1
+        },
+        autoScroll: true,
+        cls: 'dashboard-panel',
+        border: false,
+        
+        items: [
+            new Ext.Panel({
+                id: 'recent-drafts-panel',
+                region: 'north',
+                cls: 'draft-panel',
+                border: false,
+                title: this._tasks['recent-drafts'],
+                height: 180,
+                items: [this._lastDraftPanel('recent-drafts', this._tasks['recent-drafts'], 5)]
+            }),
+            
+            new Ext.TabPanel({
+                activeTab: 0,
+                region: 'center',
+                cls: 'transparent-panel',
+                deferredRender: false,
+                border: false,
+                items: taskPanels
+            })
+        ]
+    });
+    
+    this._mainPanel.addListener('focus', this._onFocus, this);
+    this._mainPanel.addListener('close', this._onClose, this);
+    this._mainPanel.addListener('blur', this._onBlur, this);
+    
+    return this._mainPanel;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype.refresh = function ()
+{
+    for (var i=0; i &lt; this._grids.length; i++)
+    {
+        this._grids[i].getStore().load();
+    }
+    
+    this.upToDate();
+        
+}
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._lastDraftPanel = function(taskId, taskLabel, max)
+{
+    // The mapping for XML doc
+    var mapping = Ext.data.Record.create([
+       {name: 'id', mapping: '@id'},    
+       {name: 'name', mapping: '@name'},
+       {name: 'icon-small', mapping: '@smallIcon'},    
+       {name: 'title', mapping: '@title', sortType: Utils.sortAsNonAccentedUCString},
+       {name: 'lastModified', mapping: '@lastModified'},
+       {name: 'contributor', mapping: '@author'},
+       {name: 'workflow-icon-small', mapping: 'workflow-icon-small'},
+       {name: 'workflow-step', mapping: 'workflow-step'}
+    ]);
+    
+    var url = getPluginDirectUrl('odf') + '/contents/todo-list.xml';
+    
+    var proxy = new Ext.data.HttpProxy({
+        url: url,
+        method: 'post',
+        timeout: 60000*10
+    });
+    
+    var store = new Ext.data.Store({
+        proxy: proxy,
+        reader: new Ext.data.XmlReader({
+                record: 'content',       // The repeated element which contains record information
+                id: '@id'                 // The element within the row that provides an ID for the record (optional)
+            }, 
+            // mapping XML
+            mapping
+        ),
+        baseParams: {taskId: taskId},
+        sortInfo: {field : 'lastModified', direction: "DESC"},
+        taskId: taskId,
+        taskLabel: taskLabel
+    });
+    
+    var grid = new Ext.grid.GridPanel({
+        id: taskId + '-grid',
+        store : store,
+        autoScroll: true,
+        
+        region: 'north',
+        deferRowRender : false,
+        
+        width: (270 + 160 + 50 + 22),
+        height: 147,
+        
+        columns: [
+            {header: "<i18n:text i18n:key='PLUGINS_ODF_DASHBOARD_COLUMN_CONTENT_TITLE'/>", width: 270, sortable: true, dataIndex: 'title', renderer: this.renderTitle},
+            {header: "<i18n:text i18n:key='PLUGINS_ODF_DASHBOARD_COLUMN_LASTMODIFIED'/>", width: 160, sortable: true, dataIndex: 'lastModified', renderer: this.renderDate},
+            {header: "<i18n:text i18n:key='PLUGINS_ODF_DASHBOARD_COLUMN_WORKFLOW_STEP'/>", width: 50, sortable: true, dataIndex: 'workflow-step', renderer: this.renderWorkflowStep}
+        ],
+        
+        cls: 'mask-below-menu'
+    });
+    
+    // Events
+    grid.getSelectionModel().on('beforerowselect', this._onBeforeSelectionChange, this);
+    grid.getSelectionModel().on('selectionchange', this._onSelectionChange, this);
+    grid.on('celldblclick', this._openContent, this);
+    grid.getStore().on('load', this._onLoad, this, {taskId: taskId, taskLabel: taskLabel});
+    grid.getStore().on('beforeload', this._onBeforeLoad, this);
+    
+    // Load the grid contents.
+    grid.getStore().load();
+    
+    this._grids.push(grid);
+    
+    return grid;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._drawGridPanel = function(taskId, taskLabel, max)
+{
+    // The mapping for XML doc
+    var mapping = Ext.data.Record.create([
+       {name: 'id', mapping: '@id'},    
+       {name: 'name', mapping: '@name'},
+       {name: 'icon-small', mapping: '@smallIcon'},    
+       {name: 'title', mapping: '@title', sortType: Utils.sortAsNonAccentedUCString},
+       {name: 'lastModified', mapping: '@lastModified'},
+       {name: 'contributor', mapping: '@author'}
+    ]);
+    
+    var url = getPluginDirectUrl('odf') + '/contents/todo-list.xml';
+    
+    var proxy = new Ext.data.HttpProxy({
+        url: url,
+        method: 'post',
+        timeout: 60000*10
+    });
+    
+    var store = new Ext.data.Store({
+        proxy: proxy,
+        reader: new Ext.data.XmlReader({
+               record: 'content',        // The repeated element which contains record information
+               id: '@id'                 // The element within the row that provides an ID for the record (optional)
+            }, 
+            // mapping XML
+            mapping
+        ),
+        baseParams: {taskId: taskId},
+        sortInfo: {field : 'lastModified', direction: "DESC"},
+        taskId: taskId,
+        taskLabel: taskLabel
+    });
+    
+    var grid = new Ext.grid.GridPanel({
+        id: taskId + '-grid',
+        store : store,
+        autoScroll: true,
+        
+        region: 'center',
+        deferRowRender : false,
+        
+        columns: [
+             {header: "<i18n:text i18n:key='PLUGINS_ODF_DASHBOARD_COLUMN_CONTENT_TITLE'/>", width: 270, sortable: true, dataIndex: 'title', renderer: this.renderTitle},
+             {header: "<i18n:text i18n:key='PLUGINS_ODF_DASHBOARD_COLUMN_CONTRIBUTOR'/>", width: 270, sortable: true, dataIndex: 'contributor'},
+             {header: "<i18n:text i18n:key='PLUGINS_ODF_DASHBOARD_COLUMN_LASTMODIFIED'/>", width: 160, sortable: true, dataIndex: 'lastModified', renderer: this.renderDate} 
+        ],
+        
+        cls: 'mask-below-menu'
+    });
+    
+    // Events.
+    grid.getSelectionModel().on('beforerowselect', this._onBeforeSelectionChange, this);
+    grid.getSelectionModel().on('selectionchange', this._onSelectionChange, this);
+    grid.on('celldblclick', this._openContent, this);
+    grid.getStore().on('load', this._onLoad, this, {taskId: taskId, taskLabel: taskLabel});
+    grid.getStore().on('beforeload', this._onBeforeLoad, this);
+    
+    // Load the grid contents.
+    grid.getStore().load();
+    
+    this._grids.push(grid);
+    
+    return grid;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype.renderTitle = function(value, p, record)
+{
+    var title = '';
+    if (record.data['icon-small'])
+    {
+        title += '&lt;img src="' + context.contextPath + record.data['icon-small'] + '" style="float:left; margin-right: 5px"/&gt;';
+    }
+    title += record.data['title'] ? record.data['title'] : record.data['name'];
+    
+    return title;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype.renderDate = function(value, p, record)
+{
+    var parsedDate = Date.parseDate(value, 'c');
+    
+    var formattedValue = parsedDate == null ? '' : Ext.util.Format.date(parsedDate, "<i18n:text i18n:catalogue='plugin.cms' i18n:key='UITOOL_SEARCH_DATETIME_FORMAT'/>");
+    
+    return formattedValue;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype.renderWorkflowStep = function(value, p, record)
+{
+    var step;
+    if (record.data['workflow-icon-small'])
+    {
+        step = '&lt;img src="' + context.contextPath + record.data['workflow-icon-small'] + '" style="float:left; margin-right: 5px" alt="' + record.data['workflow-step'] + '" title="' + record.data['workflow-step'] + '"/&gt;';
+    }
+    else
+    {
+        step = record.data['workflow-step'];
+    }
+    return step;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onBeforeSelectionChange = function (sm)
+{
+    // clear selections of the others grids
+    for (var i=0; i &lt; this._grids.length; i++)
+    {
+        if (this._grids[i].id != sm.grid.id)
+        {
+            this._grids[i].getSelectionModel().clearSelections();
+        }
+    }
+}
+
+/**
+ * Call when selection change in the grid
+ * @param Ext.grid.AbstractSelectionModel sm The selection model
+ * @private
+ */
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onSelectionChange = function(sm)
+{
+    if (!this._hasFocus)
+    {
+        // Force focus
+        org.ametys.uitool.ToolsManager.getInstance().gotElFocus(this._mainPanel);
+    }
+    
+    window.clearTimeout(this._sendTimeout);
+    this._sendTimeout = this._fireMessages.defer(10, this, [sm]);
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._fireMessages = function(sm)
+{
+    var targets = [];
+    
+    if (sm != null)
+    {
+        var records = sm.getSelections();
+        for (var i=0; i &lt; records.length; i++)
+        {
+            var id = records[i].get('id');
+            var contentTarget = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget('content', {'id': id});
+            targets.push(contentTarget);
+        }
+    }
+    
+    var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, {creation: "content"}, targets);
+    org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
+}
+
+/**
+ * Open the selected content
+ * @param grid The grid
+ * @param rowIndex The selected row index
+ * @param columnIndex The selected column index
+ * @param e The event
+ */
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._openContent = function(grid, rowIndex, columnIndex, e)
+{
+    var record = grid.getSelectionModel().getSelected();
+    var id = record.get('id');
+    
+    org.ametys.uitool.ToolsManager.getInstance().openTool('uitool-content', {'id': id});
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onBeforeLoad = function(store, options)
+{
+    // Get criteria search
+    this._fillSearchParameters(options.params);
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onLoad = function(store, records, options)
+{
+    this._modifyPanelTitle(store.taskId, store.taskLabel, records.length);
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._modifyPanelTitle = function(taskId, taskLabel, count)
+{
+    if (taskId &amp;&amp; taskLabel)
+    {
+        var panel = Ext.getCmp(taskId + '-panel');
+        if (panel != null)
+        {
+            panel.setTitle(taskLabel + ' (' + count + ')');
+        }
+    }
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._fillSearchParameters = function(params)
+{
+    params.siteName = context.parameters.siteName;
+    params.lang = org.ametys.web.helper.LanguageHelper.getCurrentLanguage(params.lang);
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype.onMessage = function (messages)
+{
+    for (var i=messages.length - 1; i &gt;= 0; i--)
+    {
+        if (messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_WORKFLOWCHANGED ||
+                messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_METADATACHANGED)
+        {
+            var allTargets = messages[i].getTargets();
+            var targets = org.ametys.messagebus.message.MessageTargetHelper.findAll(allTargets, function(target) { return target.getType() == "content";});
+            var contentIds = this._convertToId(targets);
+            
+            if (this._areInStore(contentIds))
+            {
+                this.outOfDate();
+                return;
+            }
+        }
+        else if (messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_DELETED)
+        {
+            var allTargets = messages[i].getTargets();
+            var targets = org.ametys.messagebus.message.MessageTargetHelper.findAll(allTargets, function(target) { return target.getType() == 'content';});
+            
+            var contentIds = this._convertToId(targets);
+            var upToDate = true;
+            
+            for (var j = 0; j &lt; contentIds.length; j++)
+            {
+                for (var k=0; k &lt; this._grids.length; k++)
+                {
+                    var store = this._grids[k].getStore();
+                    var record = store.getById(contentIds[j]);
+                    if (record != null)
+                    {
+                        store.remove(record);
+                        
+                        this._modifyPanelTitle(store.taskId, store.taskLabel, store.getCount());
+                        upToDate = false;
+                    }
+                }
+            }
+            
+            if (!upToDate)
+            {
+                this.outOfDate();
+            }
+        }
+    }
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._convertToId = function (targets)
+{
+    var targetsIds = [];
+    for (var i = 0; i &lt; targets.length; i++)
+    {
+        targetsIds.push(targets[i].getParameters()['id']);
+    }
+    return targetsIds;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._areInStore = function (contentIds)
+{
+    for (var i = 0; i &lt; contentIds.length; i++)
+    {
+        for (var j=0; j &lt; this._grids.length; j++)
+        {
+            var record = this._grids[j].getStore().getById(contentIds[i]);
+            if (record != null)
+            {
+                return true;
+            }
+        }
+    }
+    
+    return false;
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onFocus = function ()
+{
+    this._hasFocus = true;
+    
+    for (var i=0; i &lt; this._grids.length; i++)
+    {
+        var sm = this._grids[i].getSelectionModel();
+        if (sm.getSelections().length &gt; 0)
+        {
+            this._fireMessages(sm);
+            return;
+        }
+    }
+    
+    // No selection
+    var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, null, []);
+    var eventBus =  org.ametys.messagebus.MessageBus.getInstance();
+    eventBus.fireMessages([event]);
+    
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onClose = function ()
+{
+    if (this._hasFocus)
+    {
+        var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, null, []);
+        var eventBus =  org.ametys.messagebus.MessageBus.getInstance();
+        eventBus.fireMessages([event]);
+    }
+}
+
+org.ametys.odf.dashboard.tool.DashboardTool.prototype._onBlur = function ()
+{
+    this._hasFocus = false;
+}
Index: main/plugin-odf/resources/js/org/ametys/odf/dashboard/tool/DashboardToolFactory.js
===================================================================
--- main/plugin-odf/resources/js/org/ametys/odf/dashboard/tool/DashboardToolFactory.js	(revision 0)
+++ main/plugin-odf/resources/js/org/ametys/odf/dashboard/tool/DashboardToolFactory.js	(revision 0)
@@ -0,0 +1,30 @@
+/*
+ *  Copyright 2014 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+Ext.namespace('org.ametys.odf.dashboard.tool');
+
+org.ametys.odf.dashboard.tool.DashboardToolFactory = function(id, config, pluginName) 
+{
+	org.ametys.odf.dashboard.tool.DashboardToolFactory.superclass.constructor.call(this, id, config, pluginName);
+};
+
+Ext.extend(org.ametys.odf.dashboard.tool.DashboardToolFactory, org.ametys.uitool.tool.impl.UniqueInstanceToolFactory,
+{
+	createTool : function (id)
+	{
+		return new org.ametys.odf.dashboard.tool.DashboardTool (id, this.getPluginName());
+	}
+});
\ No newline at end of file
Index: main/plugin-odf/resources/css/dashboard.css
===================================================================
--- main/plugin-odf/resources/css/dashboard.css	(revision 0)
+++ main/plugin-odf/resources/css/dashboard.css	(revision 0)
@@ -0,0 +1,44 @@
+/*
+ *  Copyright 2010 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+.dashboard-panel {
+	background-color: #fafafa;
+	background-image: url('../../../../plugins/cms/resources/img/content/tab_bg.png');
+	background-repeat: no-repeat;
+	padding: 10px 40px 10px 40px;
+	
+}
+
+.dashboard-panel .x-panel-body {
+	background: none !important;
+}
+
+.transparent-panel {
+	background: none !important;
+	background-color: transparent !important;
+}
+.transparent-panel .x-panel-body, .transparent-panel .x-tab-panel-body {
+	background: none !important;
+	background-color: transparent !important;
+}
+
+.dashboard-panel .draft-panel .x-panel-header {
+	background: transparent url('../img/group-expand-sprite.png') no-repeat scroll 5px 5px !important;
+	border: none;
+	font-family:arial,sans-serif !important;
+	font-size: 12px !important;
+	padding-left: 20px;
+}
+
Index: main/plugin-odf/cms-ribbon.xml
===================================================================
--- main/plugin-odf/cms-ribbon.xml	(revision 27588)
+++ main/plugin-odf/cms-ribbon.xml	(working copy)
@@ -82,6 +82,14 @@
 						</layout>
 					</small>
 				</group>
+                <!-- Tools Group -->
+                <group label="plugin.odf:RIBBON_TABS_TAB_ODF_GROUPS_GROUP_TOOL_LABEL" icon="">
+                    <large/>
+                    <medium>
+                        <control id="org.ametys.odf.dashboard.Tool"/>
+                    </medium>
+                    <small/>
+                </group>                
 			</groups>
 		</tab>	
 		<tab label="plugin.odf:RIBBON_TABS_TAB_ODF_ORGUNIT_CONTENT_LABEL" id="org.ametys.odf.orgunit.content.Tab" contextualColor="4" contextualGroup="O"  contextualLabel="plugin.odf:RIBBON_TABS_TAB_ODF_CONTENT_LABEL">
Index: main/plugin-odf/src/org/ametys/odf/workflow/OdfWorkflowTasksComponent.java
===================================================================
--- main/plugin-odf/src/org/ametys/odf/workflow/OdfWorkflowTasksComponent.java	(revision 0)
+++ main/plugin-odf/src/org/ametys/odf/workflow/OdfWorkflowTasksComponent.java	(revision 0)
@@ -0,0 +1,74 @@
+/*
+ *  Copyright 2014 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.ametys.odf.workflow;
+
+import java.util.Set;
+
+import org.ametys.cms.repository.Content;
+import org.ametys.cms.repository.ContentTypeExpression;
+import org.ametys.cms.repository.DefaultContent;
+import org.ametys.cms.repository.WorkflowStepExpression;
+import org.ametys.cms.workflow.WorkflowTasksComponent;
+import org.ametys.odf.course.CourseFactory;
+import org.ametys.odf.program.ProgramFactory;
+import org.ametys.plugins.repository.AmetysObjectIterable;
+import org.ametys.plugins.repository.query.SortCriteria;
+import org.ametys.plugins.repository.query.expression.AndExpression;
+import org.ametys.plugins.repository.query.expression.Expression;
+import org.ametys.plugins.repository.query.expression.OrExpression;
+import org.ametys.plugins.repository.query.expression.StringExpression;
+import org.ametys.plugins.repository.query.expression.Expression.Operator;
+import org.ametys.runtime.user.User;
+
+/**
+ * Override WorkflowTasksComponent to target ODF contents
+ */
+public class OdfWorkflowTasksComponent extends WorkflowTasksComponent
+{
+    /** The avalon role. */
+    public static final String ROLE = OdfWorkflowTasksComponent.class.getName();
+    
+    @Override
+    protected AmetysObjectIterable<Content> _getContents(TaskStep step, User user)
+    {
+        Expression expr = null;
+        Set<Integer> stepIds = step.getStepIds();
+        for (Integer stepId : stepIds)
+        {
+            Expression stepExpr = new WorkflowStepExpression(Operator.EQ, stepId);
+            expr = expr == null ? stepExpr : new OrExpression(expr, stepExpr);
+        }
+        
+        Expression contentExpr = null;
+        if (step.getUserContentsOnly())
+        {
+            contentExpr = new StringExpression(DefaultContent.METADATA_CONTRIBUTOR, Operator.EQ, user.getName());
+            expr = new AndExpression(expr, contentExpr);
+        }
+
+        Expression contentTypeExpr = new OrExpression(new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE),
+                new ContentTypeExpression(Operator.EQ, CourseFactory.COURSE_CONTENT_TYPE));
+        expr = new AndExpression(expr, contentTypeExpr);
+        
+        SortCriteria sort = new SortCriteria();
+        sort.addCriterion(DefaultContent.METADATA_MODIFIED, false, false);
+        String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr, sort);
+
+        return _objectResolver.query(xpathQuery);
+    }
+
+}
Index: main/plugin-odf/src/org/ametys/odf/workflow/OdfWorkflowTasksGenerator.java
===================================================================
--- main/plugin-odf/src/org/ametys/odf/workflow/OdfWorkflowTasksGenerator.java	(revision 0)
+++ main/plugin-odf/src/org/ametys/odf/workflow/OdfWorkflowTasksGenerator.java	(revision 0)
@@ -0,0 +1,20 @@
+package org.ametys.odf.workflow;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+
+import org.ametys.cms.workflow.WorkflowTasksComponent;
+import org.ametys.cms.workflow.WorkflowTasksGenerator;
+
+/**
+ * Override the WorkflowTasksGenerator to use the Odf tasks manager component
+ */
+public class OdfWorkflowTasksGenerator extends WorkflowTasksGenerator
+{
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        super.service(serviceManager);
+        _tasksManager = (WorkflowTasksComponent) serviceManager.lookup(OdfWorkflowTasksComponent.ROLE);
+    }
+}
Index: main/plugin-odf/src/org/ametys/odf/workflow/OdfTasksGenerator.java
===================================================================
--- main/plugin-odf/src/org/ametys/odf/workflow/OdfTasksGenerator.java	(revision 0)
+++ main/plugin-odf/src/org/ametys/odf/workflow/OdfTasksGenerator.java	(revision 0)
@@ -0,0 +1,17 @@
+package org.ametys.odf.workflow;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+
+import org.ametys.cms.workflow.TasksGenerator;
+import org.ametys.cms.workflow.WorkflowTasksComponent;
+
+public class OdfTasksGenerator extends TasksGenerator
+{
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        super.service(serviceManager);
+        _tasksManager = (WorkflowTasksComponent) serviceManager.lookup(OdfWorkflowTasksComponent.ROLE);
+    }
+}
Index: main/plugin-odf/i18n/messages_en.xml
===================================================================
--- main/plugin-odf/i18n/messages_en.xml	(revision 27588)
+++ main/plugin-odf/i18n/messages_en.xml	(working copy)
@@ -261,6 +261,7 @@
     <message key="RIBBON_TABS_TAB_ODF_COURSELIST_LABEL">Courses list</message>
     <message key="RIBBON_TABS_TAB_ODF_PERSON_LABEL">People</message>    
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_PERSONS_LABEL">Peoples</message>
+    <message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_TOOL_LABEL">Tools</message>
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_ORGUNIT_LABEL">OrgUnits</message>
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_COURSES_LABEL">Courses</message>
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_COURSELIST_LABEL">Courses lists</message>
@@ -1132,5 +1133,19 @@
     <message key="PLUGINS_ODF_COURSE_LOM_SHEET_LINK_URL_DESC">LOM sheet's URL to be read to extract available resources</message>
     <message key="PLUGINS_ODF_COURSE_LOM_SHEET_LINK_LABEL">Label</message>
     <message key="PLUGINS_ODF_COURSE_LOM_SHEET_LINK_LABEL_DESC">LOM sheet's label</message>
+    
+    <!-- +
+         | DASHBOARD TOOL
+         + -->
+         
+    <message key="PLUGINS_ODF_DASHBOARD_TOOL_LABEL">ODF Dashboard</message>
+    <message key="PLUGINS_ODF_DASHBOARD_TOOL_DESC">The ODF dashboard is your TODO list : recently modified programs and courses, programs and courses awaiting validation, ...</message>
+    <message key="PLUGINS_ODF_DASHBOARD_TOOL_FOOTER">See help for more details</message>
+    
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_CONTENT_TITLE">Content</message>
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_LASTMODIFIED">Latest modification</message>
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_WORKFLOW_STEP">Status</message>
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_CONTRIBUTOR">Latest contributor</message>
+    
 </catalogue>
 	
\ No newline at end of file
Index: main/plugin-odf/i18n/messages_fr.xml
===================================================================
--- main/plugin-odf/i18n/messages_fr.xml	(revision 27588)
+++ main/plugin-odf/i18n/messages_fr.xml	(working copy)
@@ -261,6 +261,7 @@
     <message key="RIBBON_TABS_TAB_ODF_COURSELIST_LABEL">Liste d\'ELP</message>
     <message key="RIBBON_TABS_TAB_ODF_PERSON_LABEL">Contact</message>    
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_PERSONS_LABEL">Contacts</message>
+    <message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_TOOL_LABEL">Outils</message>
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_ORGUNIT_LABEL">Composantes</message>
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_COURSES_LABEL">ELP</message>
 	<message key="RIBBON_TABS_TAB_ODF_GROUPS_GROUP_COURSELIST_LABEL">Liste d\'éléments pédagogiques</message>
@@ -1135,5 +1136,18 @@
     <message key="PLUGINS_ODF_COURSE_LOM_SHEET_LINK_URL_DESC">URL de la fiche LOM qui sera lue afin d'en extraire les ressources disponibles</message>
     <message key="PLUGINS_ODF_COURSE_LOM_SHEET_LINK_LABEL">Libellé</message>
     <message key="PLUGINS_ODF_COURSE_LOM_SHEET_LINK_LABEL_DESC">Libellé de la fiche LOM</message>
+    
+    <!-- +
+         | DASHBOARD TOOL
+         + -->
+    <message key="PLUGINS_ODF_DASHBOARD_TOOL_LABEL">Tableau de bord de l'ODF</message>
+    <message key="PLUGINS_ODF_DASHBOARD_TOOL_DESC">Le tableau de bord de l'ODF correspond à la liste de vos actions en cours : formations et ELPs modifiés récemment, formations et ELPs à valider, ...</message>
+    <message key="PLUGINS_ODF_DASHBOARD_TOOL_FOOTER">Voir l'aide pour plus de détails</message>
+    
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_CONTENT_TITLE">Contenu</message>
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_LASTMODIFIED">Dernière modification</message>
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_WORKFLOW_STEP">Etat</message>
+    <message key="PLUGINS_ODF_DASHBOARD_COLUMN_CONTRIBUTOR">Dernier contributeur</message>
+    
 </catalogue>
 	
\ No newline at end of file
Index: main/plugin-odf/sitemap.xmap
===================================================================
--- main/plugin-odf/sitemap.xmap	(revision 27588)
+++ main/plugin-odf/sitemap.xmap	(working copy)
@@ -50,6 +50,9 @@
 			<map:generator name="search-archives" src="org.ametys.odf.program.ArchiveSearchGenerator"/>
             
 			<map:generator name="content-input-data" src="org.ametys.odf.inputdata.ContentInputDataGenerator" logger="org.ametys.odf.inputdata.ContentInputDataGenerator"/>
+            
+            <map:generator name="tasks" src="org.ametys.odf.workflow.OdfTasksGenerator"/>
+            <map:generator name="workflow-tasks" src="org.ametys.odf.workflow.OdfWorkflowTasksGenerator"/>
 		</map:generators>
 		
 		<map:actions>
@@ -850,6 +853,31 @@
                 </map:transform>
                 <map:serialize type="xml"/>
             </map:match>
+            
+            <!-- +
+                 | Dashboard
+                 + -->
+                 
+            <map:match pattern="contents/todo-list.xml">
+                <map:generate type="workflow-tasks">
+                    <map:parameter name="login" value="{request-param:login}"/>
+                    <map:parameter name="taskId" value="{request-param:taskId}"/>
+                </map:generate>
+                <map:transform type="i18n" label="xml">
+                    <map:parameter name="locale" value="{locale:locale}"/>
+                    <map:parameter name="default-catalogue-id" value="plugin.{request-attr:pluginName}"/>
+                </map:transform>
+                <map:serialize type="xml"/>
+            </map:match>
+            
+            <map:match pattern="tasks.xml">
+                <map:generate type="tasks"/>
+                <map:transform type="i18n" label="xml">
+                    <map:parameter name="locale" value="{locale:locale}"/>
+                    <map:parameter name="default-catalogue-id" value="plugin.{request-attr:pluginName}"/>
+                </map:transform>
+                <map:serialize type="xml"/>
+            </map:match>
 			
 		</map:pipeline>
 	</map:pipelines>
