Trac
Trac是一套问题追踪系统(英语:Issue tracking system)。
简介
优点
- 简单,专注在事情的记录上,而不是限制行为上。
缺点
目前的版本(1.4)还是不支援Python 3,但1.5已经在开发了。目前支援Python 3的1.6版已经出了。
安装
我是将Python装在service-trac
这个使用者的pyenv里面(权限会是service-trac:service-trac
):
sudo useradd service-trac -m -r -s /bin/bash
在装完Python后,用pip
安装以下套件:
sudo apt install -y libmysqlclient-dev
pip install mysqlclient pytz PyMySQL Pygments Trac
使用MariaDB时需要把改变dependency:
sudo apt install -y libmariadb-dev
pip install mysqlclient pytz PyMySQL Pygments Trac
使用MySQL或是MariaDB作为后端资料库时,建议用utf8mb4作为基础,这样资料库可以存所有范围的Unicode字元[1],另外建立独立的使用者供Trac使用:
CREATE DATABASE trac DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
CREATE USER `trac`@`127.0.0.1` IDENTIFIED BY 'password_here';
GRANT ALL ON trac.* TO `trac`@`127.0.0.1`;
接下来就可以产生环境:
cd ~; trac-admin trac initenv
这边在安装Trac时的资料库连线设定会是mysql://trac:password_here@127.0.0.1/trac
(需要自己修改对应栏位)。
在安装套件时,软体本体常常会放在Subversion的伺服器内,在透过pip
或是easy_install
时会呼叫Subversion,所以会需要安装对应的软体:
sudo apt install -y subversion
另外要建立目录(放附件之类的东西):
cd ~; mkdir trac/files
FastCGI
然后是Trac的FastCGI档案,放在Trac的project目录下,并且用chmod 755 trac.fcgi
改成可执行,这边的num
是指跑daemon的数量,通常可以设定成与CPU或是vCPU数量相同的数字:
#!/usr/bin/env python3
import os
from trac.web.main import dispatch_request
import trac.web._fcgi
num = 4
sockaddr = os.path.expanduser('~') + '/trac/trac.sock'
os.environ['TRAC_ENV'] = os.path.dirname(__file__)
fcgiserv = trac.web._fcgi.WSGIServer(dispatch_request, bindAddress = sockaddr, umask = 7)
for _ in range(num - 1):
if 0 == os.fork():
break
fcgiserv.run()
另外使用userland systemd(需要先启用这个设定),让Trac在开机时将FastCGI daemon跑起来,这个档案放在~/.config/systemd/user/trac-fcgi.service
:
[Unit]
Description=Trac FastCGI daemon
After=remote-fs.target
[Service]
ExecStart=/bin/bash -l -c "exec ~/trac/trac.fcgi"
Type=simple
[Install]
WantedBy=default.target
然后让systemd重新读取,并且设定为开机启动,最后手动先跑起来(或是重开机):
systemctl --user daemon-reload
systemctl --user enable --now trac-fcgi.service
另外要让nginx可以存取/var/run/trac/trac.sock
,然后完全重跑nginx:
sudo useradd -G service-trac www-data; sudo service nginx restart
套件
目前有安装的套件
除了基本安装外,还会安装这些套件:
- AccountManagerPlugin – Trac Hacks - Plugins Macros etc.
- 可以吃HTML Form Login,而不局限在HTTP authentication。
- BlueFlatTheme – Trac Hacks - Plugins Macros etc.
- 目前Trac上少数支援Responsive Design的Theme,需要使用ThemeEnginePlugin安装并启用。
- DuplicateTicketSearchPlugin – Trac Hacks - Plugins Macros etc.
- 在开新票时可以拉出有哪些是可能重复的票(尤其是已经关掉的票)。
- GraphvizPlugin – Trac Hacks - Plugins Macros etc.
- Graphviz可以拿来画各类图,我自己最常用的是Dot图。
- HttpAuthPlugin
- 将HTTP header中的Authentication转成AccountManagerPlugin也可以读到的栏位,主要是使用AccountManagerPlugin与XmlRpcPlugin时的户接,PyPI上面的版本太旧已经不相容现在的Trac,请安装trunk版本。
- RobotsTxtPlugin
- 支援
/robots.txt
,透过Trac的Wiki实作。
- 支援
- SlackIntegration – Trac Hacks - Plugins Macros etc.
- 可以把变更丢到Slack上,这边要注意的是套件本身没有把相依性做好,需要自己安装
requests
套件。在Trac 1.6上面因为Python 3的原因会有问题,可以装我修的版本。
- 可以把变更丢到Slack上,这边要注意的是套件本身没有把相依性做好,需要自己安装
- SubticketsPlugin – Trac Hacks - Plugins Macros etc.
- 子母票的延伸套件,基本上是必备项目。原版的程式安装在MySQL 8.0的环境下会出问题(也不支援Trac 1.6),可以用我修的版本。
- ThemeEnginePlugin – Trac Hacks - Plugins Macros etc.
- Theme的套件管理工具,配合BlueFlatTheme用的。
- TracCronPlugin – Trac Hacks - Plugins Macros etc.
- 定时跑任务,但其实没很好用... 目前主要是拿来每天寄信通知有哪些票过期。
- TracDragDropPlugin – Trac Hacks - Plugins Macros etc.
- 拖拉就可以上传附件的套件。
- TracWysiwygPlugin – Trac Hacks - Plugins Macros etc.
- 可以针对Textarea区块使用WYSIWYG界面,对于非技术类的使用者来说会方便不少(因为不需要学Trac的Wiki语法)。另外有不少文件可以直接剪下贴上(虽然还是会掉不少效果,但比起完全不能剪下贴上来说已经方便不少)。
- XmlRpcPlugin – Trac Hacks - Plugins Macros etc.
- 让使用者可以透过API操作Trac。
目前Trac 1.4版的安装方式是:
#!/bin/bash
pip install -U TracCronPlugin
pip install -U TracRobotsTxt
pip install -U genshi
pip install -U git+https://github.com/gslin/trac-subtickets-plugin.git
pip install -U git+https://github.com/gslin/trac-slack-plugin.git
pip install -U requests
pip install -U svn+https://trac-hacks.org/svn/accountmanagerplugin/trunk/
pip install -U svn+https://trac-hacks.org/svn/duplicateticketsearchplugin/trunk/
pip install -U svn+https://trac-hacks.org/svn/graphvizplugin/branches/1.2/
pip install -U svn+https://trac-hacks.org/svn/httpauthplugin/trunk/
pip install -U svn+https://trac-hacks.org/svn/tracdragdropplugin/0.12/
pip install -U svn+https://trac-hacks.org/svn/tracwysiwygplugin/0.12/
pip install -U svn+https://trac-hacks.org/svn/xmlrpcplugin/trunk/
对应的requirements.txt
是:
-e git+https://github.com/gslin/trac-subtickets-plugin.git
-e git+https://github.com/gslin/trac-slack-plugin.git
-e svn+https://trac-hacks.org/svn/accountmanagerplugin/trunk/
-e svn+https://trac-hacks.org/svn/duplicateticketsearchplugin/trunk/
-e svn+https://trac-hacks.org/svn/graphvizplugin/branches/1.2/
-e svn+https://trac-hacks.org/svn/httpauthplugin/trunk/
-e svn+https://trac-hacks.org/svn/tracdragdropplugin/0.12/
-e svn+https://trac-hacks.org/svn/tracwysiwygplugin/0.12/
-e svn+https://trac-hacks.org/svn/xmlrpcplugin/trunk/
TracCronPlugin
TracHTTPAuth
TracRobotsTxt
genshi
requests
可以用pip install -r requirements.txt
安装。
曾经装过的套件
以前会安装,但现在因为自己用而没有装上(没有需求或是不想装):
- AutocompleteUsersPlugin – Trac Hacks - Plugins Macros etc.
- 让使用者帐号名称可以autocomplete,在Cc栏位与Owner栏位会很好用。
- DefaultCcPlugin – Trac Hacks - Plugins Macros etc.
- 针对不同的Component自动加上Cc列表。
- 对于企业内还蛮有用的。像是专案可以开
Proj.
开头的Component,而组织可以开Org.
开头的Component,这些类型可以设定对应的Cc列表。 - 因为新版在更改Component时不会增加Cc列表,但这对于企业其实还蛮好用的,所以我自己包了一个版本在gslin/defaultccplugin这边。
- LDAPAcctMngrPlugin – Trac Hacks - Plugins Macros etc.
- 可以使用LDAP管理帐号,是AccountManagerPlugin的延伸套件。
- 要注意在文件上没有提到需要安装Pythoh module,
ldap
与python-ldap
,需要自己手动装。(这两个模组需要一些系统的套件,可以透过sudo apt install -y libsasl2-dev python-dev libldap2-dev libssl-dev
安装)
- gslin/trac-addtocc-plugin: Add participator to cc list automatically.
- 自动把参与者加到Cc列表内,这样才会收到后续的更新。
- Add header's Message-ID field to References field for Trac.
- 当使用Amazon SES发信时,会因为
Message-ID
被换掉,而导致Mail Client无法透过References
将同一张票的通知信整在一起。这个套件透过把信件本身的Message-ID
复制一份到References
的workaround让Mail Client可以正确判断出这些信件是同一张票。
- 当使用Amazon SES发信时,会因为
- gslin/trac-secret-checkbox-ticket: Add ticket security policy for Trac.
- 将票设定为秘密,只有Reporter(开票人)、Owner(目前有票的人)、Cc列表内的人可以读。
- 这对于企业内有些需求很好用(像是还在进行中的计画,或是一些不会对全公司公开的数据)。
- TracTicketReferencePlugin – Trac Hacks - Plugins Macros etc.
- 可以设定相关连的票,不过我很少用到这个功能(更多是子母票)。
设定
权限
透过CLI的方式可以更新权限:
trac-admin /path/to/myproject permission add admin TRAC_ADMIN
Trac预设的权限设定过于严格,只给内部使用的情境下,建议对authenticated
群组加上:
TICKET_EDIT_CC
TICKET_EDIT_DESCRIPTION
conf/trac.ini
记得修改Logo网址:
[header_logo]
alt = Logo
link = https://trac.example.com/
src = https://static.example.com/logo.png
Trac可以设定预设发通知信件:
[notification]
smtp_default_domain = example.com
smtp_enabled = enabled
smtp_from = trac@trac.example.com
smtp_replyto = noreply@trac.example.com
use_public_cc = enabled
我自己的Report不想分页(预设是一页100笔),所以设为:
[report]
items_per_page = 0
另外因为我们不太用Trac内建的Wiki,但又关不掉,所以只能针对没找到的页面就不要产生连结了:
[wiki]
ignore_missing_pages = enabled
htdoc/style.css
- 修正button因为CSS效果而有时会按不到的问题。
- 针对code block内一行过长时自动换行。
- 针对今天到期与过期的票用不同的标示标出(配合下方的JavaScript)。
- 将已经关掉的票变淡(配合下方的JavaScript)。
- 让编辑区域使用等宽字型。
- 贴超长字串的时候不要产生横向卷轴。
- 让可用范围变宽。
- 让图片不要超过萤幕宽度。
#changelog, #ticket {
overflow-wrap: break-word;
}
#content.ticket {
width: 78em;
}
#main img {
max-width: 100%;
}
input[type=button]:active, input[type=submit]:active,
input[type=reset]:active {
position: relative;
top: 0;
left: 0;
}
pre {
white-space: pre-wrap;
}
table.listing.tickets td {
vertical-align: middle;
}
table.listing.tickets td.due_date, table.listing.tickets td.reporter {
white-space: nowrap;
}
table.tickets tr.duedate_overdue {
font-weight: bold;
}
table.tickets tr.duedate_today {
border: 2px solid;
}
table.subtickets tr td:last-of-type {
width: 15em;
}
table.subtickets tr.ticket_closed, .closed.ticket {
opacity: 0.5;
}
textarea {
font-family: monospace;
}
template/site_head.html
这是Trac 1.4的Jinja2设计,如果是Trac 1.2或更早版本的使用者,可以在template/site.html
里面增加。
<meta name="referrer" content="no-referrer" />
<link rel="stylesheet" href="${href.chrome('site/style.css')}" />
<script src="https://cdn.jsdelivr.net/npm/strftime@0.10.0/strftime.min.js"></script>
这是Trac 1.4的Jinja2设计,如果是Trac 1.2或更早版本的使用者,可以在template/site.html
里面增加。
- 用JavaScript针对今天到期以及过期的票增加CSS。
- 将新票里的Due Date改为零点零分零秒。
- 增加把票直接延到下个月月底的button。
- 增加button,让关票可以直接点击。
- 将票里的attachments与modify内容展开。
- 将开票人的资讯放到Action栏位里,比较好确认这张票的情况。
- 将日历选择器中,关闭动画效果[2],另外将每周的第一天设为星期天[2],并且允许选择其他月份的日期[2]。
- 对所有
button
都设定accesskey。
<script>
<!--
'use strict';
// Run immediately.
(function($) {
// Due date css handling
let d = new Date();
let today = (new Date(d.getTime() - d.getTimezoneOffset() * 60000)).toISOString().slice(0, 10);
document.querySelectorAll('table.tickets td.due_date').forEach(function(el) {
let due = el.innerText.trim();
if (due < today) {
el.parentElement.classList.add('duedate_overdue');
} else if (due === today) {
el.parentElement.classList.add('duedate_today');
}
});
if (document.getElementById('ticketbox')?.style.display === '') {
// Due date postpone button
const pptoday = $('<input type="button" value="Postpone (today)" />');
pptoday.click(function(){
const duedate = $('#field-due_date');
let t = duedate.val();
if ('Z' === t.slice(-1)) {
t = new Date(Date.now());
t = strftime('%Y-%m-%dT00:00:00Z', t);
duedate.val(t);
}
const a = t.match(/\+(\d\d:\d\d)$/);
if (null !== a) {
let tz = a[1];
let new_str = 'T' + tz + ':00+' + tz;
t = new Date(Date.now());
t = strftime('%Y-%m-%d' + new_str, t);
duedate.val(t);
}
$('#propertyform input[name="submit"]').click();
});
$('#action').append(pptoday);
const ppday = $('<input type="button" value="Postpone (day)" />');
ppday.click(function(){
const duedate = $('#field-due_date');
let t = duedate.val();
if ('Z' === t.slice(-1)) {
t = new Date(Date.now());
t.setDate(t.getDate() + 1);
t = strftime('%Y-%m-%dT00:00:00Z', t);
duedate.val(t);
}
const a = t.match(/\+(\d\d:\d\d)$/);
if (null !== a) {
let tz = a[1];
let new_str = 'T' + tz + ':00+' + tz;
t = new Date(Date.now());
t.setDate(t.getDate() + 1);
t = strftime('%Y-%m-%d' + new_str, t);
duedate.val(t);
}
$('#propertyform input[name="submit"]').click();
});
$('#action').append(ppday);
const ppmonth = $('<input type="button" value="Postpone (month)" />');
ppmonth.click(function(){
const duedate = $('#field-due_date');
let t = duedate.val();
if ('Z' === t.slice(-1)) {
t = new Date(Date.now());
t.setDate(1);
t.setMonth(t.getMonth() + 2);
t.setDate(0);
t = strftime('%Y-%m-%dT00:00:00Z', t);
duedate.val(t);
}
const a = t.match(/\+(\d\d:\d\d)$/);
if (null !== a) {
let tz = a[1];
let new_str = 'T' + tz + ':00+' + tz;
t = new Date(Date.now());
t.setDate(1);
t.setMonth(t.getMonth() + 2);
t.setDate(0);
t = strftime('%Y-%m-%d' + new_str, t);
duedate.val(t);
}
$('#propertyform input[name="submit"]').click();
});
$('#action').append(ppmonth);
}
// Accept & start buttons
$('#action_accept, #action_start').each(function(){
var v = $(this).val();
var that = this;
var b = $('<button/>').click(function(e){
var v = $(this).val();
$(that).click();
$('#propertyform input[name="submit"]').click();
e.preventDefault();
});
b.prop('value', v).text(v);
$(this).closest('div').append(b);
});
// Resolve with Buttons
$('#action_resolve_resolve_resolution option').each(function(){
var v = $(this).val();
var b = $('<button/>').click(function(e){
var v = $(this).val();
$('#action_resolve').click();
$('#action_resolve_resolve_resolution').prop('disabled', false);
$('#action_resolve_resolve_resolution').find('option[value="' + v + '"]').prop('selected', 'selected');
$('#propertyform input[name="submit"]').click();
e.preventDefault();
});
b.prop('value', v).text(v);
$(this).closest('div').append(b);
});
// Run after content loaded.
$(function() {
// Reset Drag & Drop hovering issue.
let el = document.querySelector('.tracdragdrop-paste.beautytips');
el?.parentElement.addEventListener('mouseover', ev => {ev.stopPropagation();}, true);
// Layout
$('#attachments').removeClass('collapsed');
$('#modify').parent().removeClass('collapsed');
// Datepicker
if ($.datepicker) {
$.datepicker.setDefaults({
firstDay: 0,
selectOtherMonths: true,
showAnim: '',
showOtherMonths: true
});
}
// Add accesskey
$('button').each(function() {
var el = $(this);
var a = el.text()[0].toLowerCase();
el.attr('accesskey', a);
});
// Add submit accesskey
$('input[name="submit"]').each(function() {
$(this).attr('accesskey', 'c');
});
// Closed tickets handling
$('tr:has(a.closed)').addClass('ticket_closed');
});
})(jQuery);
//-->
</script>
其他
删除使用者时可能会需要手动到以下表格清除资料(清完后需要重跑Trac,因为Trac会读出来cache一份),避免autocomplete之类的套件仍然显示:
- auth_cookie
- session
- session_attribute
著名使用单位
- Django
- FFmpeg
- jQuery
- MacPorts
- nginx
- OpenVPN
- Tor(2020年六月终止[3])
- VirtualBox
- WebKit
- WordPress
相关连结
参考资料
- ↑ MySqlDb – The Trac Project. [2018-03-01].
- ↑ 2.0 2.1 2.2 Datepicker Widget | jQuery UI API Documentation. [2018-02-28].
- ↑ From Trac into Gitlab for Tor. 2020-11-20 [2020-11-22] (English).