diff options
30 files changed, 506 insertions, 104 deletions
diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..29d0e9c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:14.04 +MAINTAINER Frederic Guillot <fred@kanboard.net> + +RUN apt-get update && apt-get install -y apache2 php5 php5-sqlite git && apt-get clean +RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf +RUN cd /var/www && git clone https://github.com/fguillot/kanboard.git +RUN rm -rf /var/www/html && mv /var/www/kanboard /var/www/html +RUN chown -R www-data:www-data /var/www/html/data + +EXPOSE 80 + +ENV APACHE_RUN_USER www-data +ENV APACHE_RUN_GROUP www-data +ENV APACHE_LOG_DIR /var/log/apache2 +ENV APACHE_LOCK_DIR /var/lock/apache2 +ENV APACHE_PID_FILE /var/run/apache2.pid + +CMD /usr/sbin/apache2ctl -D FOREGROUND diff --git a/README.markdown b/README.markdown index 41cc3821..16c32a2a 100644 --- a/README.markdown +++ b/README.markdown @@ -50,6 +50,8 @@ Documentation #### Introduction +- [What is Kanban?](docs/what-is-kanban.markdown) +- [Kanban vs Todo Lists and Scrum](docs/kanban-vs-todo-and-scrum.markdown) - [Usage examples](docs/usage-examples.markdown) #### Working with projects @@ -103,10 +105,12 @@ Documentation - [Command line interface](docs/cli.markdown) - [Json-RPC API](docs/api-json-rpc.markdown) - [Webhooks](docs/webhooks.markdown) -- [How to use Kanboard with Vagrant](docs/vagrant.markdown) +- [Run Kanboard with Vagrant](docs/vagrant.markdown) +- [Run Kanboard with Docker](docs/docker.markdown) ### Contributors +- [Contributor guide](docs/contributing.markdown) - [Translations](docs/translations.markdown) - [Coding standards](docs/coding-standards.markdown) - [Running tests](docs/tests.markdown) diff --git a/app/Controller/App.php b/app/Controller/App.php index b7f79b1d..feec4221 100644 --- a/app/Controller/App.php +++ b/app/Controller/App.php @@ -13,17 +13,21 @@ use Model\Project as ProjectModel; class App extends Base { /** - * Redirect to the project creation page or the board controller + * Dashboard for the current user * * @access public */ public function index() { - if ($this->project->countByStatus(ProjectModel::ACTIVE)) { - $this->response->redirect('?controller=board'); - } - else { - $this->redirectNoProject(); - } + $user_id = $this->acl->getUserId(); + $projects = $this->projectPermission->getAllowedProjects($user_id); + + $this->response->html($this->template->layout('app_index', array( + 'board_selector' => $projects, + 'events' => $this->projectActivity->getProjects(array_keys($projects), 10), + 'tasks' => $this->taskFinder->getAllTasksByUser($user_id), + 'menu' => 'dashboard', + 'title' => t('Dashboard'), + ))); } } diff --git a/app/Controller/Project.php b/app/Controller/Project.php index 1fac3ffb..503eb3a5 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -379,7 +379,7 @@ class Project extends Base $project = $this->getProject(); $this->response->html($this->template->layout('project_activity', array( - 'events' => $this->projectActivity->getAll($project['id']), + 'events' => $this->projectActivity->getProject($project['id']), 'menu' => 'projects', 'project' => $project, 'title' => t('%s\'s activity', $project['name']) @@ -427,6 +427,7 @@ class Project extends Base 'project_id' => $project['id'], ), 'project' => $project, + 'menu' => 'projects', 'columns' => $this->board->getColumnsList($project['id']), 'categories' => $this->category->getList($project['id'], false), 'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') @@ -461,6 +462,7 @@ class Project extends Base 'limit' => $limit, ), 'project' => $project, + 'menu' => 'projects', 'columns' => $this->board->getColumnsList($project['id']), 'categories' => $this->category->getList($project['id'], false), 'tasks' => $tasks, diff --git a/app/Controller/User.php b/app/Controller/User.php index bbed9f6f..834b2379 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -59,7 +59,7 @@ class User extends Base $this->response->redirect('?'.$redirect_query); } else { - $this->response->redirect('?controller=board'); + $this->response->redirect('?controller=app'); } } diff --git a/app/Locales/de_DE/translations.php b/app/Locales/de_DE/translations.php index 4b901148..e8f4f53f 100644 --- a/app/Locales/de_DE/translations.php +++ b/app/Locales/de_DE/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/es_ES/translations.php b/app/Locales/es_ES/translations.php index 0cdd5f32..ffaeaf2d 100644 --- a/app/Locales/es_ES/translations.php +++ b/app/Locales/es_ES/translations.php @@ -449,6 +449,7 @@ return array( 'Email:' => 'Correo electrónico:', 'Default project:' => 'Proyecto por defecto:', 'Notifications:' => 'Notificaciones:', + // 'Notifications' => '', 'Group:' => 'Grupo:', 'Regular user' => 'Usuario regular:', 'Account type:' => 'Tipo de Cuenta:', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/fi_FI/translations.php b/app/Locales/fi_FI/translations.php index 5d05e92d..1c1b3439 100644 --- a/app/Locales/fi_FI/translations.php +++ b/app/Locales/fi_FI/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/fr_FR/translations.php b/app/Locales/fr_FR/translations.php index f4bffc3b..5809ef69 100644 --- a/app/Locales/fr_FR/translations.php +++ b/app/Locales/fr_FR/translations.php @@ -449,6 +449,7 @@ return array( 'Email:' => 'Email :', 'Default project:' => 'Projet par défaut :', 'Notifications:' => 'Notifications :', + 'Notifications' => 'Notifications', 'Group:' => 'Groupe :', 'Regular user' => 'Utilisateur normal', 'Account type:' => 'Type de compte :', @@ -543,4 +544,9 @@ return array( 'Started on %B %e, %Y' => 'Commençé le %d/%m/%Y', 'Start date' => 'Date de début', 'Time estimated' => 'Temps estimé', + 'There is nothing assigned to you.' => 'Aucune tâche assignée pour vous.', + 'My tasks' => 'Mes tâches', + 'Activity stream' => 'Flux d\'activité', + 'Dashboard' => 'Tableau de bord', + 'Confirmation' => 'Confirmation', ); diff --git a/app/Locales/it_IT/translations.php b/app/Locales/it_IT/translations.php index 5854d033..d933d5c7 100644 --- a/app/Locales/it_IT/translations.php +++ b/app/Locales/it_IT/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/pl_PL/translations.php b/app/Locales/pl_PL/translations.php index fe76b34c..6f6732d6 100644 --- a/app/Locales/pl_PL/translations.php +++ b/app/Locales/pl_PL/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/pt_BR/translations.php b/app/Locales/pt_BR/translations.php index 59fcc983..0a5b2e28 100644 --- a/app/Locales/pt_BR/translations.php +++ b/app/Locales/pt_BR/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/ru_RU/translations.php b/app/Locales/ru_RU/translations.php index 1f5bfea2..dc9dc9ba 100644 --- a/app/Locales/ru_RU/translations.php +++ b/app/Locales/ru_RU/translations.php @@ -164,7 +164,7 @@ return array( 'Ready' => 'Готовые', 'Backlog' => 'Ожидающие', 'Work in progress' => 'В процессе', - 'Done' => 'Завершенные', + 'Done' => 'Выполнена', 'Application version:' => 'Версия приложения :', 'Completed on %B %e, %Y at %k:%M %p' => 'Завершен %d/%m/%Y в %H:%M', '%B %e, %Y at %k:%M %p' => '%d/%m/%Y в %H:%M', @@ -192,11 +192,11 @@ return array( 'Edit users access' => 'Изменить доступ пользователей', 'Allow this user' => 'Разрешить этого пользователя', 'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту :', - 'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ всюду.', + 'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ ко всему.', 'revoke' => 'отозвать', 'List of authorized users' => 'Список авторизованных пользователей', 'User' => 'Пользователь', - // 'Nobody have access to this project.' => '', + 'Nobody have access to this project.' => 'Ни у кого нет доступа к этому проекту', 'You are not allowed to access to this project.' => 'Вам запрешен доступ к этому проекту.', 'Comments' => 'Комментарии', 'Post comment' => 'Оставить комментарий', @@ -208,7 +208,7 @@ return array( 'Unable to create your comment.' => 'Невозможно создать комментарий.', 'The description is required' => 'Требуется описание', 'Edit this task' => 'Изменить задачу', - 'Due Date' => 'Срок', + 'Due Date' => 'Сделать до', 'Invalid date' => 'Неверная дата', 'Must be done before %B %e, %Y' => 'Должно быть сделано до %d/%m/%Y', '%B %e, %Y' => '%d/%m/%Y', @@ -249,7 +249,7 @@ return array( 'Move Down' => 'Сдвинуть вниз', 'Duplicate to another project' => 'Клонировать в другой проект', 'Duplicate' => 'Клонировать', - 'link' => 'связь', + 'link' => 'ссылка', 'Update this comment' => 'Обновить комментарий', 'Comment updated successfully.' => 'Комментарий обновлен.', 'Unable to update your comment.' => 'Не удалось обновить ваш комментарий.', @@ -349,13 +349,13 @@ return array( 'estimated' => 'расчетное', 'Sub-Tasks' => 'Подзадачи', 'Add a sub-task' => 'Добавить подзадачу', - 'Original estimate' => 'Начальная оценка', + 'Original estimate' => 'Первичная оценка', 'Create another sub-task' => 'Создать другую подзадачу', 'Time spent' => 'Времени затрачено', 'Edit a sub-task' => 'Изменить подзадачу', 'Remove a sub-task' => 'Удалить подзадачу', 'The time must be a numeric value' => 'Время должно быть числом!', - 'Todo' => 'TODO', + 'Todo' => 'К исполнению', 'In progress' => 'В процессе', 'Sub-task removed successfully.' => 'Подзадача удалена.', 'Unable to remove this sub-task.' => 'Не удалось удалить подзадачу.', @@ -449,10 +449,11 @@ return array( 'Email:' => 'Email:', 'Default project:' => 'Проект по умолчанию:', 'Notifications:' => 'Уведомления:', + 'Notifications' => 'Уведомления', 'Group:' => 'Группа:', 'Regular user' => 'Обычный пользователь', 'Account type:' => 'Тип профиля:', - 'Edit profile' => 'Редактировать профиль:', + 'Edit profile' => 'Редактировать профиль', 'Change password' => 'Сменить пароль', 'Password modification' => 'Изменение пароля', 'External authentications' => 'Внешняя аутентификация', @@ -494,53 +495,58 @@ return array( 'Activity' => 'Активность', 'Default values are "%s"' => 'Колонки по умолчанию: "%s"', 'Default columns for new projects (Comma-separated)' => 'Колонки по умолчанию для новых проектов (разделять запятой)', - // 'Task assignee change' => '', - // '%s change the assignee of the task #%d to %s' => '', - // '%s change the assignee of the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to %s' => '', - // '[%s][Column Change] %s (#%d)' => '', - // '[%s][Position Change] %s (#%d)' => '', - // '[%s][Assignee Change] %s (#%d)' => '', - // 'New password for the user "%s"' => '', - // 'Choose an event' => '', - // 'Github commit received' => '', - // 'Github issue opened' => '', - // 'Github issue closed' => '', - // 'Github issue reopened' => '', - // 'Github issue assignee change' => '', - // 'Github issue label change' => '', - // 'Create a task from an external provider' => '', - // 'Change the assignee based on an external username' => '', - // 'Change the category based on an external label' => '', - // 'Reference' => '', - // 'Reference: %s' => '', - // 'Label' => '', - // 'Database' => '', - // 'About' => '', - // 'Database driver:' => '', - // 'Board settings' => '', - // 'URL and token' => '', - // 'Webhook settings' => '', - // 'URL for task creation:' => '', - // 'Reset token' => '', - // 'API endpoint:' => '', - // 'Refresh interval for private board' => '', - // 'Refresh interval for public board' => '', - // 'Task highlight period' => '', - // 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '', - // 'Frequency in second (60 seconds by default)' => '', - // 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '', - // 'Application URL' => '', - // 'Example: http://example.kanboard.net/ (used by email notifications)' => '', - // 'Token regenerated.' => '', - // 'Date format' => '', - // 'ISO format is always accepted, example: "%s" and "%s"' => '', - // 'New private project' => '', - // 'This project is private' => '', - // 'Type here to create a new sub-task' => '', - // 'Add' => '', - // 'Estimated time: %s hours' => '', - // 'Time spent: %s hours' => '', - // 'Started on %B %e, %Y' => '', - // 'Start date' => '', - // 'Time estimated' => '', + 'Task assignee change' => 'Изменен назначенный', + '%s change the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s', + '%s change the assignee of the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to %s' => '%s сменил назначенного для задачи <a href="?controller=task&action=show&task_id=%d">#%d</a> на %s', + '[%s][Column Change] %s (#%d)' => '[%s][Изменение колонки] %s (#%d)', + '[%s][Position Change] %s (#%d)' => '[%s][Изменение позиции] %s (#%d)', + '[%s][Assignee Change] %s (#%d)' => '[%s][Изменение назначеного] %s (#%d)', + 'New password for the user "%s"' => 'Новый пароль для пользователя %s"', + 'Choose an event' => 'Выберите событие', + 'Github commit received' => 'Github: коммит получен', + 'Github issue opened' => 'Github: новая проблема', + 'Github issue closed' => 'Github: проблема закрыта', + 'Github issue reopened' => 'Github: проблема переоткрыта', + 'Github issue assignee change' => 'Github: сменить ответственного за проблему', + 'Github issue label change' => 'Github: ярлык проблемы изменен', + 'Create a task from an external provider' => 'Создать задачу из внешнего источника', + 'Change the assignee based on an external username' => 'Изменить назначенного основываясь на внешнем имени пользователя', + 'Change the category based on an external label' => 'Изменить категорию основываясь на внешнем ярлыке', + 'Reference' => 'Ссылка', + 'Reference: %s' => 'Ссылка: %s', + 'Label' => 'Ярлык', + 'Database' => 'База данных', + 'About' => 'Информация', + 'Database driver:' => 'Драйвер базы данных', + 'Board settings' => 'Настройки доски', + 'URL and token' => 'URL и токен', + 'Webhook settings' => 'Параметры Webhook', + 'URL for task creation:' => 'URL для создания задачи:', + 'Reset token' => 'Перезагрузить токен', + 'API endpoint:' => 'API endpoint:', + 'Refresh interval for private board' => 'Период обновления для частных досок', + 'Refresh interval for public board' => 'Период обновления для публичных досок', + 'Task highlight period' => 'Время подсвечивания задачи', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Период (в секундах) в течении которого задача считается недавно измененной (0 для выключения, 2 дня по умолчанию)', + 'Frequency in second (60 seconds by default)' => 'Частота в секундах (60 секунд по умолчанию)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Частота в секундах (0 для выключения, 10 секунд по умолчанию)', + 'Application URL' => 'URL приложения', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Пример: http://example.kanboard.net (используется в email уведомлениях)', + 'Token regenerated.' => 'Токен пересоздан', + 'Date format' => 'Формат даты', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Время должно быть в ISO-формате, например: "%s" или "%s"', + 'New private project' => 'Новый проект с ограниченным доступом', + 'This project is private' => 'Это проект с ограниченным доступом', + 'Type here to create a new sub-task' => 'Печатайте сюда чтобы создать подзадачу', + 'Add' => 'Добавить', + 'Estimated time: %s hours' => 'Планируемое время: %s часов', + 'Time spent: %s hours' => 'Потрачено времени: %s часов', + 'Started on %B %e, %Y' => 'Начато %B %e, %Y', + 'Start date' => 'Дата начала', + 'Time estimated' => 'Планируемое время', + 'There is nothing assigned to you.' => 'Вам ничего не назначено', + 'My tasks' => 'Мои задачи', + 'Activity stream' => 'Текущая активность', + 'Dashboard' => 'Инфопанель', + 'Confirmation' => 'Подтверждение пароля', ); diff --git a/app/Locales/sv_SE/translations.php b/app/Locales/sv_SE/translations.php index 20b685ef..d0f179f8 100644 --- a/app/Locales/sv_SE/translations.php +++ b/app/Locales/sv_SE/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Locales/zh_CN/translations.php b/app/Locales/zh_CN/translations.php index 92f46ef9..4b2d57ed 100644 --- a/app/Locales/zh_CN/translations.php +++ b/app/Locales/zh_CN/translations.php @@ -449,6 +449,7 @@ return array( // 'Email:' => '', // 'Default project:' => '', // 'Notifications:' => '', + // 'Notifications' => '', // 'Group:' => '', // 'Regular user' => '', // 'Account type:' => '', @@ -543,4 +544,9 @@ return array( // 'Started on %B %e, %Y' => '', // 'Start date' => '', // 'Time estimated' => '', + // 'There is nothing assigned to you.' => '', + // 'My tasks' => '', + // 'Activity stream' => '', + // 'Dashboard' => '', + // 'Confirmation' => '', ); diff --git a/app/Model/ProjectActivity.php b/app/Model/ProjectActivity.php index d2457609..6d6ef454 100644 --- a/app/Model/ProjectActivity.php +++ b/app/Model/ProjectActivity.php @@ -61,15 +61,32 @@ class ProjectActivity extends Base * @param integer $limit Maximum events number * @return array */ - public function getAll($project_id, $limit = 50) + public function getProject($project_id, $limit = 50) { + return $this->getProjects(array($project_id), $limit); + } + + /** + * Get all events for the given projects list + * + * @access public + * @param integer $project_id Project id + * @param integer $limit Maximum events number + * @return array + */ + public function getProjects(array $projects, $limit = 50) + { + if (empty($projects)) { + return array(); + } + $events = $this->db->table(self::TABLE) ->columns( self::TABLE.'.*', User::TABLE.'.username AS author_username', User::TABLE.'.name AS author_name' ) - ->eq('project_id', $project_id) + ->in('project_id', $projects) ->join(User::TABLE, 'id', 'creator_id') ->desc('id') ->limit($limit) diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 5bf8c139..56795152 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -112,6 +112,33 @@ class TaskFinder extends Base } /** + * Get all open tasks for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAllTasksByUser($user_id) + { + return $this->db + ->table(Task::TABLE) + ->columns( + 'tasks.id', + 'tasks.title', + 'tasks.date_due', + 'tasks.date_creation', + 'tasks.project_id', + 'tasks.color_id', + 'projects.name AS project_name' + ) + ->join(Project::TABLE, 'id', 'project_id') + ->eq('tasks.owner_id', $user_id) + ->eq('tasks.is_active', Task::STATUS_OPEN) + ->asc('tasks.id') + ->findAll(); + } + + /** * Get all tasks for a given project and status * * @access public diff --git a/app/Templates/app_index.php b/app/Templates/app_index.php new file mode 100644 index 00000000..91eecce4 --- /dev/null +++ b/app/Templates/app_index.php @@ -0,0 +1,45 @@ +<section id="main"> + <div class="page-header"> + <h2><?= t('Dashboard') ?></h2> + </div> + <section id="dashboard"> + <div class="dashboard-left-column"> + <h2><?= t('My tasks') ?></h2> + <?php if (empty($tasks)): ?> + <p class="alert"><?= t('There is nothing assigned to you.') ?></p> + <?php else: ?> + <table> + <tr> + <th> </th> + <th width="15%"><?= t('Project') ?></th> + <th width="40%"><?= t('Title') ?></th> + <th><?= t('Due date') ?></th> + <th><?= t('Date created') ?></th> + </tr> + <?php foreach ($tasks as $task): ?> + <tr> + <td class="task-table task-<?= $task['color_id'] ?>"> + <?= Helper\a('#'.$task['id'], 'task', 'show', array('task_id' => $task['id'])) ?> + </td> + <td> + <?= Helper\a(Helper\escape($task['project_name']), 'board', 'show', array('project_id' => $task['project_id'])) ?> + </td> + <td> + <?= Helper\a(Helper\escape($task['title']), 'task', 'show', array('task_id' => $task['id'])) ?> + </td> + <td> + <?= dt('%B %e, %Y', $task['date_due']) ?> + </td> + <td> + <?= dt('%B %e, %Y', $task['date_creation']) ?> + </td> + </tr> + <?php endforeach ?> + </table> + <?php endif ?> + </div> + <div class="dashboard-right-column"> + <h2><?= t('Activity stream') ?></h2> + <?= Helper\template('project_events', array('events' => $events)) ?> + </section> +</section>
\ No newline at end of file diff --git a/app/Templates/layout.php b/app/Templates/layout.php index 434c5aca..a86d613b 100644 --- a/app/Templates/layout.php +++ b/app/Templates/layout.php @@ -50,6 +50,9 @@ </select> </li> <?php endif ?> + <li <?= isset($menu) && $menu === 'dashboard' ? 'class="active"' : '' ?>> + <a href="?controller=app"><?= t('Dashboard') ?></a> + </li> <li <?= isset($menu) && $menu === 'boards' ? 'class="active"' : '' ?>> <a href="?controller=board"><?= t('Boards') ?></a> </li> diff --git a/app/Templates/project_activity.php b/app/Templates/project_activity.php index 50743d68..d07ba86a 100644 --- a/app/Templates/project_activity.php +++ b/app/Templates/project_activity.php @@ -9,29 +9,10 @@ </ul> </div> <section> - <?php if (empty($events)): ?> - <p class="alert"><?= t('No activity.') ?></p> - <?php else: ?> - <?php if ($project['is_public']): ?> <p class="pull-right"><i class="fa fa-rss-square"></i> <?= Helper\a(t('RSS feed'), 'project', 'feed', array('token' => $project['token'])) ?></p> <?php endif ?> - <?php foreach ($events as $event): ?> - <div class="activity-event"> - <p class="activity-datetime"> - <?php if (Helper\contains($event['event_name'], 'task')): ?> - <i class="fa fa-newspaper-o"></i> - <?php elseif (Helper\contains($event['event_name'], 'subtask')): ?> - <i class="fa fa-tasks"></i> - <?php elseif (Helper\contains($event['event_name'], 'comment')): ?> - <i class="fa fa-comments-o"></i> - <?php endif ?> - <?= dt('%B %e, %Y at %k:%M %p', $event['date_creation']) ?> - </p> - <div class="activity-content"><?= $event['event_content'] ?></div> - </div> - <?php endforeach ?> - <?php endif ?> + <?= Helper\template('project_events', array('events' => $events)) ?> </section> </section>
\ No newline at end of file diff --git a/app/Templates/project_events.php b/app/Templates/project_events.php new file mode 100644 index 00000000..1b606414 --- /dev/null +++ b/app/Templates/project_events.php @@ -0,0 +1,21 @@ +<?php if (empty($events)): ?> + <p class="alert"><?= t('No activity.') ?></p> +<?php else: ?> + + <?php foreach ($events as $event): ?> + <div class="activity-event"> + <p class="activity-datetime"> + <?php if (Helper\contains($event['event_name'], 'subtask')): ?> + <i class="fa fa-tasks"></i> + <?php elseif (Helper\contains($event['event_name'], 'task')): ?> + <i class="fa fa-newspaper-o"></i> + <?php elseif (Helper\contains($event['event_name'], 'comment')): ?> + <i class="fa fa-comments-o"></i> + <?php endif ?> + <?= dt('%B %e, %Y at %k:%M %p', $event['date_creation']) ?> + </p> + <div class="activity-content"><?= $event['event_content'] ?></div> + </div> + <?php endforeach ?> + +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/task_table.php b/app/Templates/task_table.php index b6fdb2b9..fa04fa55 100644 --- a/app/Templates/task_table.php +++ b/app/Templates/task_table.php @@ -13,7 +13,7 @@ <?php foreach ($tasks as $task): ?> <tr> <td class="task-table task-<?= $task['color_id'] ?>"> - <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>" title="<?= t('View this task') ?>"><?= Helper\escape($task['id']) ?></a> + <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>" title="<?= t('View this task') ?>">#<?= Helper\escape($task['id']) ?></a> </td> <td> <?= Helper\in_list($task['column_id'], $columns) ?> diff --git a/app/helpers.php b/app/helpers.php index c9f2f8ab..9e0c2698 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -566,9 +566,9 @@ function a($label, $controller, $action, array $params = array(), $csrf = false, } /** - * URL + * URL query string * - * a('link', 'task', 'show', array('task_id' => $task_id)) + * u('task', 'show', array('task_id' => $task_id)) * * @param string $controller Controller name * @param string $action Action name diff --git a/assets/css/app.css b/assets/css/app.css index 0bb25740..a4d71cb4 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1119,6 +1119,22 @@ tr td.task-orange, color: #555; } +/* dashboard */ +#dashboard table { + font-size: 0.95em; +} + +.dashboard-left-column { + width: 55%; + float: left; +} + +.dashboard-right-column { + margin-left: 5%; + width: 40%; + float: left; +} + /* confirmation box */ .confirm { max-width: 700px; diff --git a/assets/js/app.js b/assets/js/app.js index 395dfb6e..7dcf04eb 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -67,6 +67,15 @@ var Kanboard = (function() { dateFormat: 'yy-mm-dd', constrainInput: false }); + + // Project select box + $("#board-selector").chosen({ + width: 180 + }); + + $("#board-selector").change(function() { + window.location = "?controller=board&action=show&project_id=" + $(this).val(); + }); } }; @@ -229,17 +238,11 @@ Kanboard.Board = (function() { return { Init: function() { - board_load_events(); - filter_load_events(); - // Project select box - $("#board-selector").chosen({ - width: 180 - }); + Kanboard.Before(); - $("#board-selector").change(function() { - window.location = "?controller=board&action=show&project_id=" + $(this).val(); - }); + board_load_events(); + filter_load_events(); } }; @@ -274,6 +277,18 @@ Kanboard.Project = (function() { })(); +// Dashboard related functions +Kanboard.Dashboard = (function() { + + return { + Init: function() { + Kanboard.Before(); + } + }; + +})(); + + // Initialization $(function() { if ($("#board").length) { @@ -282,6 +297,9 @@ $(function() { else if ($("#task-section").length) { Kanboard.Task.Init(); } + else if ($("#dashboard").length) { + Kanboard.Dashboard.Init(); + } else if ($("#project-section").length) { Kanboard.Project.Init(); } diff --git a/docs/contributing.markdown b/docs/contributing.markdown new file mode 100644 index 00000000..a6b9029c --- /dev/null +++ b/docs/contributing.markdown @@ -0,0 +1,69 @@ +Contributor Guidelines +====================== + +How can I help? +--------------- + +Kanboard is not perfect but there is many ways to help: + +- Give feedback +- Report bugs +- Add or update translations +- Improve the documentation +- Writing code +- Tell your friends that Kanboard is awesome :) + +Before doing any large undertaking, open a new issue and explain your proposal. + +I want to give feedback +----------------------- + +- You think something should be improved (user interface, feature request) +- Check if your idea is not already proposed +- Open a new issue +- Describe your idea +- You can also up vote with +1 on existing proposals + +I want to report a bug +---------------------- + +- Check if the issue is not already reported +- Open a new ticket +- Explain what is broken +- Describe how to reproduce the bug +- Describe your environment (Kanboard version, OS, web server, PHP version, database version, hosting type) + +I want to translate Kanboard +---------------------------- + +Kanboard is translated in many languages. +However, translations are not complete, take look at the [translation guide to contribute](http://kanboard.net/documentation/translations). + +I want to improve the documentation +----------------------------------- + +- You think something is not clear, there is grammatical errors, typo errors, anything. +- The documentation is written in Markdown and stored in the folder `docs`. +- Edit the file and send a pull-request. +- The documentation on the official website is synchronized with the repository. + +I want to contribute to the code +-------------------------------- + +Pull-requests are always welcome, however to be accepted you have to follow those directives: + +- **Before doing any large change or design proposal, open a new ticket to start a discussion.** +- If you want to add a new feature, respect the philosophy behind Kanboard. **We focus on simplicity**, we don't want to have a bloated software. +- The same apply for the user interface, **simplicity and efficiency**. +- Send only one pull-request per feature or bug fix, your patch will be merged into one single commit in the master branch. +- Make sure the [unit tests pass](http://kanboard.net/documentation/tests). +- Respect the [coding standards](http://kanboard.net/documentation/coding-standards). +- Write maintainable code, avoid code duplication, use PHP good practices. + +In any case, if you are not sure about something open a new ticket. + +Tell your friends that Kanboard is awesome :) +--------------------------------------------- + +If you use Kanboard, spread the word around you. +Tell them that free and open source software are cool :) diff --git a/docs/docker.markdown b/docs/docker.markdown new file mode 100644 index 00000000..92fcf2c3 --- /dev/null +++ b/docs/docker.markdown @@ -0,0 +1,48 @@ +How to test Kanboard with Docker? +================================= + +Kanboard can run with [Docker](https://www.docker.com). +You can use the public image or build your own image from the `Dockerfile`. + +Actually, the Docker image is based on the master branch (development version). + +Build your own Docker image +--------------------------- + +From your kanboard directory run the following command: + +```bash +docker build -t youruser\kanboard:master . +``` + +To run your image in background on the port 80: + +```bash +docker run -d --name kanboard -p 80:80 -t youruser/kanboard:master +``` + +Run the public Kanboard image +----------------------------- + +This image is stored on the [Docker Hub](https://hub.docker.com). + +Fetch the image on your machine: + +```bash +docker pull kanboard/kanboard:master +``` + +Run the image: + +```bash +docker run -d --name kanboard -p 80:80 -t kanboard/kanboard:master +``` + +Store your data on a volume +--------------------------- + +You can also save your data outside of the container, on the local machine: + +```bash +docker run -d --name kanboard -v /your/local/data/folder:/var/www/html/data -p 80:80 -t kanboard/kanboard:master +``` diff --git a/docs/kanban-vs-todo-and-scrum.markdown b/docs/kanban-vs-todo-and-scrum.markdown new file mode 100644 index 00000000..3d53023a --- /dev/null +++ b/docs/kanban-vs-todo-and-scrum.markdown @@ -0,0 +1,37 @@ +Kanban vs Todo lists and Scrum +============================== + +Kanban vs Todo lists +-------------------- + +### Todo lists: + +- Single phase (just a list of items) +- Multitasking possible (not efficient) + +### Kanban: + +- Multiple phases, each column represent a step +- Bring focus and avoid multitasking because you can set a work in progress limit per column + +Kanban vs Scrum +--------------- + +### Scrum: + +- Sprints are time-boxed, usually 2 or 4 weeks +- Do not allow changes during the iteration +- Estimation is required +- Uses velocity as default metric +- Scrum board is cleared between each sprint +- Scrum has predefined roles like scrum master, product owner and the team +- A lot of meetings: planning, backlog grooming, daily stand-up, retrospective + +### Kanban: + +- Continuous flow +- Changes can be made at anytime +- Estimation is optional +- Use lead and cycle time to measure performance +- Kanban board is persistent +- Kanban doesn't impose strict constraints or meetings, process is more flexible diff --git a/docs/what-is-kanban.markdown b/docs/what-is-kanban.markdown new file mode 100644 index 00000000..e3c3cf53 --- /dev/null +++ b/docs/what-is-kanban.markdown @@ -0,0 +1,32 @@ +What is Kanban? +=============== + +Kanban is a methodology originally developed by Toyota to be more efficient. + +There is only two constraints imposed by Kanban: + +- Visualize your workflow +- Limit your work in progress + +Visualize your workflow +----------------------- + +- Your work is visualized on a board, you have a clear overview of your project +- Each column represent a step in your workflow + +Bring focus and avoid multitasking +---------------------------------- + +- Each phase can have a work in progress limit +- Limits are great to identify bottlenecks +- Limits avoid working on too many tasks in the same time + +Measure performance and improvement +----------------------------------- + +Kanban uses lead and cycle times to measure performance: + +- **Lead time**: Time between the task is created and completed +- **Cycle time**: Time between the task is started and completed + +By example, you may have a lead time of 100 days and only have to work 1 hour to complete the task. diff --git a/tests/units/ProjectActivityTest.php b/tests/units/ProjectActivityTest.php index 2565b6e7..7e7841dd 100644 --- a/tests/units/ProjectActivityTest.php +++ b/tests/units/ProjectActivityTest.php @@ -24,7 +24,7 @@ class ProjectActivityTest extends Base $this->assertTrue($e->createEvent(1, 2, 1, Task::EVENT_UPDATE, array('task' => $tf->getById(2)))); $this->assertFalse($e->createEvent(1, 1, 0, Task::EVENT_OPEN, array('task' => $tf->getbyId(1)))); - $events = $e->getAll(1); + $events = $e->getProject(1); $this->assertNotEmpty($events); $this->assertTrue(is_array($events)); @@ -50,7 +50,7 @@ class ProjectActivityTest extends Base $this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_UPDATE, array('task' => $tf->getbyId(1)))); } - $events = $e->getAll(1); + $events = $e->getProject(1); $this->assertNotEmpty($events); $this->assertTrue(is_array($events)); @@ -80,7 +80,7 @@ class ProjectActivityTest extends Base $this->assertEquals($nb_events, $this->registry->shared('db')->table('project_activities')->count()); $e->cleanup($max); - $events = $e->getAll(1); + $events = $e->getProject(1); $this->assertNotEmpty($events); $this->assertTrue(is_array($events)); |
