##// END OF EJS Templates
rewrote graph plotting, added zooming and json dump insted of stupid string formating.
marcink -
r486:5c376ac2 celery
parent child Browse files
Show More
@@ -1,70 +1,71
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # summary controller for pylons
3 # summary controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 18, 2010
21 Created on April 18, 2010
22 summary controller for pylons
22 summary controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from pylons import tmpl_context as c, request,url
25 from pylons import tmpl_context as c, request, url
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.lib.utils import OrderedDict
28 from pylons_app.lib.utils import OrderedDict
29 from pylons_app.model.hg_model import HgModel
29 from pylons_app.model.hg_model import HgModel
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31 from pylons_app.lib.celerylib import run_task
31 from pylons_app.lib.celerylib import run_task
32 from pylons_app.lib.celerylib.tasks import get_commits_stats
32 from pylons_app.lib.celerylib.tasks import get_commits_stats
33 import logging
33 import logging
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37 class SummaryController(BaseController):
37 class SummaryController(BaseController):
38
38
39 @LoginRequired()
39 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
41 'repository.admin')
42 def __before__(self):
42 def __before__(self):
43 super(SummaryController, self).__before__()
43 super(SummaryController, self).__before__()
44
44
45 def index(self):
45 def index(self):
46 hg_model = HgModel()
46 hg_model = HgModel()
47 c.repo_info = hg_model.get_repo(c.repo_name)
47 c.repo_info = hg_model.get_repo(c.repo_name)
48 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
48 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
49 e = request.environ
49 e = request.environ
50 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
50 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
51 'protocol': e.get('wsgi.url_scheme'),
51 'protocol': e.get('wsgi.url_scheme'),
52 'user':str(c.hg_app_user.username),
52 'user':str(c.hg_app_user.username),
53 'host':e.get('HTTP_HOST'),
53 'host':e.get('HTTP_HOST'),
54 'repo_name':c.repo_name, }
54 'repo_name':c.repo_name, }
55 c.clone_repo_url = uri
55 c.clone_repo_url = uri
56 c.repo_tags = OrderedDict()
56 c.repo_tags = OrderedDict()
57 for name, hash in c.repo_info.tags.items()[:10]:
57 for name, hash in c.repo_info.tags.items()[:10]:
58 c.repo_tags[name] = c.repo_info.get_changeset(hash)
58 c.repo_tags[name] = c.repo_info.get_changeset(hash)
59
59
60 c.repo_branches = OrderedDict()
60 c.repo_branches = OrderedDict()
61 for name, hash in c.repo_info.branches.items()[:10]:
61 for name, hash in c.repo_info.branches.items()[:10]:
62 c.repo_branches[name] = c.repo_info.get_changeset(hash)
62 c.repo_branches[name] = c.repo_info.get_changeset(hash)
63
63
64 task = run_task(get_commits_stats,c.repo_info.name)
64 task = run_task(get_commits_stats, c.repo_info.name)
65 c.ts_min = task.result[0]
65 c.ts_min = task.result[0]
66 c.ts_max = task.result[1]
66 c.ts_max = task.result[1]
67 c.commit_data = task.result[2]
67 c.commit_data = task.result[2]
68 c.overview_data = task.result[3]
68
69
69 return render('summary/summary.html')
70 return render('summary/summary.html')
70
71
@@ -1,208 +1,223
1 from celery.decorators import task
1 from celery.decorators import task
2 from celery.task.sets import subtask
2 from celery.task.sets import subtask
3 from celeryconfig import PYLONS_CONFIG as config
3 from celeryconfig import PYLONS_CONFIG as config
4 from datetime import datetime, timedelta
4 from datetime import datetime, timedelta
5 from pylons.i18n.translation import _
5 from pylons.i18n.translation import _
6 from pylons_app.lib.celerylib import run_task
6 from pylons_app.lib.celerylib import run_task
7 from pylons_app.lib.helpers import person
7 from pylons_app.lib.helpers import person
8 from pylons_app.lib.smtp_mailer import SmtpMailer
8 from pylons_app.lib.smtp_mailer import SmtpMailer
9 from pylons_app.lib.utils import OrderedDict
9 from pylons_app.lib.utils import OrderedDict
10 from time import mktime
10 from time import mktime
11 from vcs.backends.hg import MercurialRepository
11 from vcs.backends.hg import MercurialRepository
12 import calendar
12 import calendar
13 import traceback
13 import traceback
14 import json
14
15
15 __all__ = ['whoosh_index', 'get_commits_stats',
16 __all__ = ['whoosh_index', 'get_commits_stats',
16 'reset_user_password', 'send_email']
17 'reset_user_password', 'send_email']
17
18
18 def get_session():
19 def get_session():
19 from sqlalchemy import engine_from_config
20 from sqlalchemy import engine_from_config
20 from sqlalchemy.orm import sessionmaker, scoped_session
21 from sqlalchemy.orm import sessionmaker, scoped_session
21 engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.')
22 engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.')
22 sa = scoped_session(sessionmaker(bind=engine))
23 sa = scoped_session(sessionmaker(bind=engine))
23 return sa
24 return sa
24
25
25 def get_hg_settings():
26 def get_hg_settings():
26 from pylons_app.model.db import HgAppSettings
27 from pylons_app.model.db import HgAppSettings
27 try:
28 try:
28 sa = get_session()
29 sa = get_session()
29 ret = sa.query(HgAppSettings).all()
30 ret = sa.query(HgAppSettings).all()
30 finally:
31 finally:
31 sa.remove()
32 sa.remove()
32
33
33 if not ret:
34 if not ret:
34 raise Exception('Could not get application settings !')
35 raise Exception('Could not get application settings !')
35 settings = {}
36 settings = {}
36 for each in ret:
37 for each in ret:
37 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
38 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
38
39
39 return settings
40 return settings
40
41
41 def get_hg_ui_settings():
42 def get_hg_ui_settings():
42 from pylons_app.model.db import HgAppUi
43 from pylons_app.model.db import HgAppUi
43 try:
44 try:
44 sa = get_session()
45 sa = get_session()
45 ret = sa.query(HgAppUi).all()
46 ret = sa.query(HgAppUi).all()
46 finally:
47 finally:
47 sa.remove()
48 sa.remove()
48
49
49 if not ret:
50 if not ret:
50 raise Exception('Could not get application ui settings !')
51 raise Exception('Could not get application ui settings !')
51 settings = {}
52 settings = {}
52 for each in ret:
53 for each in ret:
53 k = each.ui_key
54 k = each.ui_key
54 v = each.ui_value
55 v = each.ui_value
55 if k == '/':
56 if k == '/':
56 k = 'root_path'
57 k = 'root_path'
57
58
58 if k.find('.') != -1:
59 if k.find('.') != -1:
59 k = k.replace('.', '_')
60 k = k.replace('.', '_')
60
61
61 if each.ui_section == 'hooks':
62 if each.ui_section == 'hooks':
62 v = each.ui_active
63 v = each.ui_active
63
64
64 settings[each.ui_section + '_' + k] = v
65 settings[each.ui_section + '_' + k] = v
65
66
66 return settings
67 return settings
67
68
68 @task
69 @task
69 def whoosh_index(repo_location, full_index):
70 def whoosh_index(repo_location, full_index):
70 log = whoosh_index.get_logger()
71 log = whoosh_index.get_logger()
71 from pylons_app.lib.indexers import DaemonLock
72 from pylons_app.lib.indexers import DaemonLock
72 from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon, LockHeld
73 from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon, LockHeld
73 try:
74 try:
74 l = DaemonLock()
75 l = DaemonLock()
75 WhooshIndexingDaemon(repo_location=repo_location)\
76 WhooshIndexingDaemon(repo_location=repo_location)\
76 .run(full_index=full_index)
77 .run(full_index=full_index)
77 l.release()
78 l.release()
78 return 'Done'
79 return 'Done'
79 except LockHeld:
80 except LockHeld:
80 log.info('LockHeld')
81 log.info('LockHeld')
81 return 'LockHeld'
82 return 'LockHeld'
82
83
83 @task
84 @task
84 def get_commits_stats(repo):
85 def get_commits_stats(repo):
85 log = get_commits_stats.get_logger()
86 log = get_commits_stats.get_logger()
86 aggregate = OrderedDict()
87 aggregate = OrderedDict()
88 overview_aggregate = OrderedDict()
87 repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '')
89 repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '')
88 repo = MercurialRepository(repos_path + repo)
90 repo = MercurialRepository(repos_path + repo)
89 #graph range
91 #graph range
90 td = datetime.today() + timedelta(days=1)
92 td = datetime.today() + timedelta(days=1)
91 y, m, d = td.year, td.month, td.day
93 y, m, d = td.year, td.month, td.day
92 ts_min = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
94
95 ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month,
93 d, 0, 0, 0, 0, 0, 0,))
96 d, 0, 0, 0, 0, 0, 0,))
94 ts_max = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
97 ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
98 d, 0, 0, 0, 0, 0, 0,))
95
99
100 ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
101
96 def author_key_cleaner(k):
102 def author_key_cleaner(k):
97 k = person(k)
103 k = person(k)
98 k = k.replace('"', "'") #for js data compatibilty
104 k = k.replace('"', "") #for js data compatibilty
99 return k
105 return k
100
106
101 for cs in repo[:200]:#added limit 200 until fix #29 is made
107 for cs in repo[:1000]:#added limit 200 until fix #29 is made
102 k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1],
108 k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1],
103 cs.date.timetuple()[2])
109 cs.date.timetuple()[2])
104 timetupple = [int(x) for x in k.split('-')]
110 timetupple = [int(x) for x in k.split('-')]
105 timetupple.extend([0 for _ in xrange(6)])
111 timetupple.extend([0 for _ in xrange(6)])
106 k = mktime(timetupple)
112 k = mktime(timetupple)
107 if aggregate.has_key(author_key_cleaner(cs.author)):
113 if aggregate.has_key(author_key_cleaner(cs.author)):
108 if aggregate[author_key_cleaner(cs.author)].has_key(k):
114 if aggregate[author_key_cleaner(cs.author)].has_key(k):
109 aggregate[author_key_cleaner(cs.author)][k]["commits"] += 1
115 aggregate[author_key_cleaner(cs.author)][k]["commits"] += 1
110 aggregate[author_key_cleaner(cs.author)][k]["added"] += len(cs.added)
116 aggregate[author_key_cleaner(cs.author)][k]["added"] += len(cs.added)
111 aggregate[author_key_cleaner(cs.author)][k]["changed"] += len(cs.changed)
117 aggregate[author_key_cleaner(cs.author)][k]["changed"] += len(cs.changed)
112 aggregate[author_key_cleaner(cs.author)][k]["removed"] += len(cs.removed)
118 aggregate[author_key_cleaner(cs.author)][k]["removed"] += len(cs.removed)
113
119
114 else:
120 else:
115 #aggregate[author_key_cleaner(cs.author)].update(dates_range)
121 #aggregate[author_key_cleaner(cs.author)].update(dates_range)
116 if k >= ts_min and k <= ts_max:
122 if k >= ts_min_y and k <= ts_max_y:
117 aggregate[author_key_cleaner(cs.author)][k] = {}
123 aggregate[author_key_cleaner(cs.author)][k] = {}
118 aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1
124 aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1
119 aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added)
125 aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added)
120 aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed)
126 aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed)
121 aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed)
127 aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed)
122
128
123 else:
129 else:
124 if k >= ts_min and k <= ts_max:
130 if k >= ts_min_y and k <= ts_max_y:
125 aggregate[author_key_cleaner(cs.author)] = OrderedDict()
131 aggregate[author_key_cleaner(cs.author)] = OrderedDict()
126 #aggregate[author_key_cleaner(cs.author)].update(dates_range)
132 #aggregate[author_key_cleaner(cs.author)].update(dates_range)
127 aggregate[author_key_cleaner(cs.author)][k] = {}
133 aggregate[author_key_cleaner(cs.author)][k] = {}
128 aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1
134 aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1
129 aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added)
135 aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added)
130 aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed)
136 aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed)
131 aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed)
137 aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed)
132
138
133 d = ''
134 tmpl0 = u""""%s":%s"""
135 tmpl1 = u"""{label:"%s",data:%s,schema:["commits"]},"""
136 for author in aggregate:
137
139
138 d += tmpl0 % (author,
140 if overview_aggregate.has_key(k):
139 tmpl1 \
141 overview_aggregate[k] += 1
140 % (author,
142 else:
141 [{"time":x,
143 overview_aggregate[k] = 1
142 "commits":aggregate[author][x]['commits'],
144
143 "added":aggregate[author][x]['added'],
145 overview_data = []
144 "changed":aggregate[author][x]['changed'],
146 for k, v in overview_aggregate.items():
145 "removed":aggregate[author][x]['removed'],
147 overview_data.append([k, v])
146 } for x in aggregate[author]]))
148 data = {}
147 if d == '':
149 for author in aggregate:
148 d = '"%s":{label:"%s",data:[[0,1],]}' \
150 data[author] = {"label":author,
149 % (author_key_cleaner(repo.contact),
151 "data":[{"time":x,
150 author_key_cleaner(repo.contact))
152 "commits":aggregate[author][x]['commits'],
151 return (ts_min, ts_max, d)
153 "added":aggregate[author][x]['added'],
154 "changed":aggregate[author][x]['changed'],
155 "removed":aggregate[author][x]['removed'],
156 } for x in aggregate[author]],
157 "schema":["commits"]
158 }
159
160 if not data:
161 data[author_key_cleaner(repo.contact)] = {
162 "label":author_key_cleaner(repo.contact),
163 "data":[0, 1],
164 "schema":["commits"],
165 }
166
167 return (ts_min_m, ts_max_y, json.dumps(data), json.dumps(overview_data))
152
168
153 @task
169 @task
154 def reset_user_password(user_email):
170 def reset_user_password(user_email):
155 log = reset_user_password.get_logger()
171 log = reset_user_password.get_logger()
156 from pylons_app.lib import auth
172 from pylons_app.lib import auth
157 from pylons_app.model.db import User
173 from pylons_app.model.db import User
158
174
159 try:
175 try:
160
161 try:
176 try:
162 sa = get_session()
177 sa = get_session()
163 user = sa.query(User).filter(User.email == user_email).scalar()
178 user = sa.query(User).filter(User.email == user_email).scalar()
164 new_passwd = auth.PasswordGenerator().gen_password(8,
179 new_passwd = auth.PasswordGenerator().gen_password(8,
165 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
180 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
166 user.password = auth.get_crypt_password(new_passwd)
181 user.password = auth.get_crypt_password(new_passwd)
167 sa.add(user)
182 sa.add(user)
168 sa.commit()
183 sa.commit()
169 log.info('change password for %s', user_email)
184 log.info('change password for %s', user_email)
170 if new_passwd is None:
185 if new_passwd is None:
171 raise Exception('unable to generate new password')
186 raise Exception('unable to generate new password')
172
187
173 except:
188 except:
174 log.error(traceback.format_exc())
189 log.error(traceback.format_exc())
175 sa.rollback()
190 sa.rollback()
176
191
177 run_task(send_email, user_email,
192 run_task(send_email, user_email,
178 "Your new hg-app password",
193 "Your new hg-app password",
179 'Your new hg-app password:%s' % (new_passwd))
194 'Your new hg-app password:%s' % (new_passwd))
180 log.info('send new password mail to %s', user_email)
195 log.info('send new password mail to %s', user_email)
181
196
182
197
183 except:
198 except:
184 log.error('Failed to update user password')
199 log.error('Failed to update user password')
185 log.error(traceback.format_exc())
200 log.error(traceback.format_exc())
186 return True
201 return True
187
202
188 @task
203 @task
189 def send_email(recipients, subject, body):
204 def send_email(recipients, subject, body):
190 log = send_email.get_logger()
205 log = send_email.get_logger()
191 email_config = dict(config.items('DEFAULT'))
206 email_config = dict(config.items('DEFAULT'))
192 mail_from = email_config.get('app_email_from')
207 mail_from = email_config.get('app_email_from')
193 user = email_config.get('smtp_username')
208 user = email_config.get('smtp_username')
194 passwd = email_config.get('smtp_password')
209 passwd = email_config.get('smtp_password')
195 mail_server = email_config.get('smtp_server')
210 mail_server = email_config.get('smtp_server')
196 mail_port = email_config.get('smtp_port')
211 mail_port = email_config.get('smtp_port')
197 tls = email_config.get('smtp_use_tls')
212 tls = email_config.get('smtp_use_tls')
198 ssl = False
213 ssl = False
199
214
200 try:
215 try:
201 m = SmtpMailer(mail_from, user, passwd, mail_server,
216 m = SmtpMailer(mail_from, user, passwd, mail_server,
202 mail_port, ssl, tls)
217 mail_port, ssl, tls)
203 m.send(recipients, subject, body)
218 m.send(recipients, subject, body)
204 except:
219 except:
205 log.error('Mail sending failed')
220 log.error('Mail sending failed')
206 log.error(traceback.format_exc())
221 log.error(traceback.format_exc())
207 return False
222 return False
208 return True
223 return True
@@ -1,301 +1,356
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Mercurial Repository Overview')}
4 ${_('Mercurial Repository Overview')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <script type="text/javascript">
20 <script type="text/javascript">
21 var E = YAHOO.util.Event;
21 var E = YAHOO.util.Event;
22 var D = YAHOO.util.Dom;
22 var D = YAHOO.util.Dom;
23
23
24 E.onDOMReady(function(e){
24 E.onDOMReady(function(e){
25 id = 'clone_url';
25 id = 'clone_url';
26 E.addListener(id,'click',function(e){
26 E.addListener(id,'click',function(e){
27 D.get('clone_url').select();
27 D.get('clone_url').select();
28 })
28 })
29 })
29 })
30 </script>
30 </script>
31 <div class="box box-left">
31 <div class="box box-left">
32 <!-- box / title -->
32 <!-- box / title -->
33 <div class="title">
33 <div class="title">
34 ${self.breadcrumbs()}
34 ${self.breadcrumbs()}
35 </div>
35 </div>
36 <!-- end box / title -->
36 <!-- end box / title -->
37 <div class="form">
37 <div class="form">
38 <div class="fields">
38 <div class="fields">
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label">
41 <div class="label">
42 <label>${_('Name')}:</label>
42 <label>${_('Name')}:</label>
43 </div>
43 </div>
44 <div class="input-short">
44 <div class="input-short">
45 <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span>
45 <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span>
46 </div>
46 </div>
47 </div>
47 </div>
48
48
49
49
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label>${_('Description')}:</label>
52 <label>${_('Description')}:</label>
53 </div>
53 </div>
54 <div class="input-short">
54 <div class="input-short">
55 ${c.repo_info.description}
55 ${c.repo_info.description}
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59
59
60 <div class="field">
60 <div class="field">
61 <div class="label">
61 <div class="label">
62 <label>${_('Contact')}:</label>
62 <label>${_('Contact')}:</label>
63 </div>
63 </div>
64 <div class="input-short">
64 <div class="input-short">
65 <div class="gravatar">
65 <div class="gravatar">
66 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
66 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
67 </div>
67 </div>
68 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
68 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
69 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
69 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
70 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
70 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 <div class="field">
74 <div class="field">
75 <div class="label">
75 <div class="label">
76 <label>${_('Last change')}:</label>
76 <label>${_('Last change')}:</label>
77 </div>
77 </div>
78 <div class="input-short">
78 <div class="input-short">
79 ${h.age(c.repo_info.last_change)} - ${h.rfc822date(c.repo_info.last_change)}
79 ${h.age(c.repo_info.last_change)} - ${h.rfc822date(c.repo_info.last_change)}
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label>${_('Clone url')}:</label>
85 <label>${_('Clone url')}:</label>
86 </div>
86 </div>
87 <div class="input-short">
87 <div class="input-short">
88 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
88 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92 <div class="field">
92 <div class="field">
93 <div class="label">
93 <div class="label">
94 <label>${_('Download')}:</label>
94 <label>${_('Download')}:</label>
95 </div>
95 </div>
96 <div class="input-short">
96 <div class="input-short">
97 %for cnt,archive in enumerate(c.repo_info._get_archives()):
97 %for cnt,archive in enumerate(c.repo_info._get_archives()):
98 %if cnt >=1:
98 %if cnt >=1:
99 |
99 |
100 %endif
100 %endif
101 ${h.link_to(c.repo_info.name+'.'+archive['type'],
101 ${h.link_to(c.repo_info.name+'.'+archive['type'],
102 h.url('files_archive_home',repo_name=c.repo_info.name,
102 h.url('files_archive_home',repo_name=c.repo_info.name,
103 revision='tip',fileformat=archive['extension']),class_="archive_icon")}
103 revision='tip',fileformat=archive['extension']),class_="archive_icon")}
104 %endfor
104 %endfor
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="field">
108 <div class="field">
109 <div class="label">
109 <div class="label">
110 <label>${_('Feeds')}:</label>
110 <label>${_('Feeds')}:</label>
111 </div>
111 </div>
112 <div class="input-short">
112 <div class="input-short">
113 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
113 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
114 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
114 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
115 </div>
115 </div>
116 </div>
116 </div>
117 </div>
117 </div>
118 </div>
118 </div>
119 </div>
119 </div>
120
120
121 <div class="box box-right" style="min-height:455px">
121 <div class="box box-right" style="min-height:455px">
122 <!-- box / title -->
122 <!-- box / title -->
123 <div class="title">
123 <div class="title">
124 <h5>${_('Last month commit activity')}</h5>
124 <h5>${_('Last month commit activity')}</h5>
125 </div>
125 </div>
126
126
127 <div class="table">
127 <div class="table">
128 <div id="commit_history" style="width:560px;height:300px;float:left"></div>
128 <div id="commit_history" style="width:560px;height:300px;float:left"></div>
129 <div id="legend_data" style="clear:both;margin-top:10px">
129 <div style="clear: both;height: 10px"></div>
130 <div id="overview" style="width:560px;height:100px;float:left"></div>
131
132 <div id="legend_data" style="clear:both;margin-top:10px;">
130 <div id="legend_container"></div>
133 <div id="legend_container"></div>
131 <div id="legend_choices">
134 <div id="legend_choices">
132 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
135 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
133 </div>
136 </div>
134 </div>
137 </div>
135 <script type="text/javascript">
138 <script type="text/javascript">
136
139
137 (function () {
140 (function () {
138 var datasets = {${c.commit_data|n}};
141 var datasets = ${c.commit_data|n};
142 var overview_data = ${c.overview_data|n};
143
139 var i = 0;
144 var i = 0;
140 var choiceContainer = YAHOO.util.Dom.get("legend_choices");
145 var choiceContainer = YAHOO.util.Dom.get("legend_choices");
141 var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables");
146 var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables");
142 for(var key in datasets) {
147 for(var key in datasets) {
143 datasets[key].color = i;
148 datasets[key].color = i;
144 i++;
149 i++;
145 choiceContainerTable.innerHTML += '<tr><td>'+
150 choiceContainerTable.innerHTML += '<tr><td>'+
146 '<input type="checkbox" name="' + key +'" checked="checked" />'
151 '<input type="checkbox" name="' + key +'" checked="checked" />'
147 +datasets[key].label+
152 +datasets[key].label+
148 '</td></tr>';
153 '</td></tr>';
149 };
154 };
150
155
151
152 function plotAccordingToChoices() {
156 function plotAccordingToChoices() {
153 var data = [];
157 var data = [];
154
158
155 var inputs = choiceContainer.getElementsByTagName("input");
159 var inputs = choiceContainer.getElementsByTagName("input");
156 for(var i=0; i<inputs.length; i++) {
160 for(var i=0; i<inputs.length; i++) {
157 var key = inputs[i].name;
161 var key = inputs[i].name;
158 if (key && datasets[key]){
162 if (key && datasets[key]){
159 if(!inputs[i].checked){
163 if(!inputs[i].checked){
160 data.push({label:key,data:[[0,1],]});
164 data.push({label:key,data:[[0,1],]});
161 }
165 }
162 else{
166 else{
163 data.push(datasets[key]);
167 data.push(datasets[key]);
164 }
168 }
165
166 }
169 }
167
168 };
170 };
169
171
170 if (data.length > 0){
172 if (data.length > 0){
173 var options = {
174 bars: {show:true,align:'center',lineWidth:4},
175 legend: {show:true, container:"legend_container"},
176 points: {show:true,radius:0,fill:false},
177 yaxis: {tickDecimals:0,},
178 xaxis: {mode: "time",
179 timeformat: "%d/%m",
180 min:${c.ts_min},
181 max:${c.ts_max},
182
183 },
184 grid: {hoverable: true,
185 clickable: true,
186 autoHighlight:true,
187 color: "#999"},
188 selection: {mode: "x"}
189 };
171
190
172 var plot = YAHOO.widget.Flot("commit_history", data,
191 //main plot
173 { bars: { show: true, align:'center',lineWidth:4 },
192 var plot = YAHOO.widget.Flot("commit_history",data,options);
174 points: { show: true, radius:0,fill:true },
193
175 legend:{show:true, container:"legend_container"},
194 //overview
176 selection: { mode: "xy" },
195 var overview = YAHOO.widget.Flot("overview", [overview_data], {
177 yaxis: {tickDecimals:0},
196 legend:{show:false},
178 xaxis: { mode: "time", timeformat: "%d",tickSize:[1, "day"],min:${c.ts_min},max:${c.ts_max} },
197 bars: {show:true,
179 grid: { hoverable: true, clickable: true,autoHighlight:true },
198 barWidth: 2,
180 });
199 },
181
200 shadowSize: 0,
201 xaxis: {mode: "time",
202 timeformat: "%d/%m/%y",
203 },
204 yaxis: {ticks: 3, min: 0,},
205 grid: {color: "#999",},
206 selection: {mode: "x"}
207 });
208
209 var ranges = {"xaxis":{"from":${c.ts_min},
210 "to":${c.ts_max},},}
211 overview.setSelection(ranges);
212
182 function showTooltip(x, y, contents) {
213 function showTooltip(x, y, contents) {
183 var div=document.getElementById('tooltip');
214 var div=document.getElementById('tooltip');
184 if(!div) {
215 if(!div) {
185 div = document.createElement('div');
216 div = document.createElement('div');
186 div.id="tooltip";
217 div.id="tooltip";
187 div.style.position="absolute";
218 div.style.position="absolute";
188 div.style.border='1px solid #fdd';
219 div.style.border='1px solid #fdd';
189 div.style.padding='2px';
220 div.style.padding='2px';
190 div.style.backgroundColor='#fee';
221 div.style.backgroundColor='#fee';
191 document.body.appendChild(div);
222 document.body.appendChild(div);
192 }
223 }
193 YAHOO.util.Dom.setStyle(div, 'opacity', 0);
224 YAHOO.util.Dom.setStyle(div, 'opacity', 0);
194 div.innerHTML = contents;
225 div.innerHTML = contents;
195 div.style.top=(y + 5) + "px";
226 div.style.top=(y + 5) + "px";
196 div.style.left=(x + 5) + "px";
227 div.style.left=(x + 5) + "px";
197
228
198 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
229 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
199 anim.animate();
230 anim.animate();
200 }
231 }
201
232
202 var previousPoint = null;
233 var previousPoint = null;
203 plot.subscribe("plothover", function (o) {
234
235 function plothover(o) {
204 var pos = o.pos;
236 var pos = o.pos;
205 var item = o.item;
237 var item = o.item;
206
238
207 //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2);
239 //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2);
208 //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2);
240 //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2);
209 if (item) {
241 if (item) {
210 if (previousPoint != item.datapoint) {
242 if (previousPoint != item.datapoint) {
211 previousPoint = item.datapoint;
243 previousPoint = item.datapoint;
212
244
213 var tooltip = YAHOO.util.Dom.get("tooltip");
245 var tooltip = YAHOO.util.Dom.get("tooltip");
214 if(tooltip) {
246 if(tooltip) {
215 tooltip.parentNode.removeChild(tooltip);
247 tooltip.parentNode.removeChild(tooltip);
216 }
248 }
217 var x = item.datapoint.x.toFixed(2);
249 var x = item.datapoint.x.toFixed(2);
218 var y = item.datapoint.y.toFixed(2);
250 var y = item.datapoint.y.toFixed(2);
219
251
220 if (!item.series.label){
252 if (!item.series.label){
221 item.series.label = 'commits';
253 item.series.label = 'commits';
222 }
254 }
223 var d = new Date(x*1000);
255 var d = new Date(x*1000);
224 var fd = d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate();
256 var fd = d.toDateString()
225 var nr_commits = parseInt(y);
257 var nr_commits = parseInt(y);
226
258
227 var cur_data = datasets[item.series.label].data[item.dataIndex];
259 var cur_data = datasets[item.series.label].data[item.dataIndex];
228 var added = cur_data.added;
260 var added = cur_data.added;
229 var changed = cur_data.changed;
261 var changed = cur_data.changed;
230 var removed = cur_data.removed;
262 var removed = cur_data.removed;
231
263
232 var nr_commits_suffix = " ${_('commits')} ";
264 var nr_commits_suffix = " ${_('commits')} ";
233 var added_suffix = " ${_('files added')} ";
265 var added_suffix = " ${_('files added')} ";
234 var changed_suffix = " ${_('files changed')} ";
266 var changed_suffix = " ${_('files changed')} ";
235 var removed_suffix = " ${_('files removed')} ";
267 var removed_suffix = " ${_('files removed')} ";
236
268
237
269
238 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
270 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
239 if(added==1){added_suffix=" ${_('file added')} ";}
271 if(added==1){added_suffix=" ${_('file added')} ";}
240 if(changed==1){changed_suffix=" ${_('file changed')} ";}
272 if(changed==1){changed_suffix=" ${_('file changed')} ";}
241 if(removed==1){removed_suffix=" ${_('file removed')} ";}
273 if(removed==1){removed_suffix=" ${_('file removed')} ";}
242
274
243 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
275 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
244 +'<br/>'+
276 +'<br/>'+
245 nr_commits + nr_commits_suffix+'<br/>'+
277 nr_commits + nr_commits_suffix+'<br/>'+
246 added + added_suffix +'<br/>'+
278 added + added_suffix +'<br/>'+
247 changed + changed_suffix + '<br/>'+
279 changed + changed_suffix + '<br/>'+
248 removed + removed_suffix + '<br/>');
280 removed + removed_suffix + '<br/>');
249 }
281 }
250 }
282 }
251 else {
283 else {
252 var tooltip = YAHOO.util.Dom.get("tooltip");
284 var tooltip = YAHOO.util.Dom.get("tooltip");
253
285
254 if(tooltip) {
286 if(tooltip) {
255 tooltip.parentNode.removeChild(tooltip);
287 tooltip.parentNode.removeChild(tooltip);
256 }
288 }
257 previousPoint = null;
289 previousPoint = null;
258 }
290 }
259 });
291 }
292
293 plot.subscribe("plothover", plothover);
294
295 function plotselected(ranges) {
296 // do the zooming
297 plot = YAHOO.widget.Flot("commit_history", data,
298 YAHOO.lang.merge(options, {
299 xaxis: { min: ranges.xaxis.from,
300 max: ranges.xaxis.to,
301 mode:"time",
302 timeformat: "%d/%m",
303 }
304 }));
305 plot.subscribe("plotselected", plotselected);
306 plot.subscribe("plothover", plothover);
260
307
308 // don't fire event on the overview to prevent eternal loop
309 overview.setSelection(ranges, true);
310 }
311 plot.subscribe("plotselected", plotselected);
312
313 overview.subscribe("plotselected", function (ranges) {
314 plot.setSelection(ranges);
315 });
261 }
316 }
262 }
317 }
263
318
264 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotAccordingToChoices);
319 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotAccordingToChoices);
265
320
266 plotAccordingToChoices();
321 plotAccordingToChoices();
267 })();
322 })();
268 </script>
323 </script>
269
324
270 </div>
325 </div>
271 </div>
326 </div>
272
327
273 <div class="box">
328 <div class="box">
274 <div class="title">
329 <div class="title">
275 <div class="breadcrumbs">${h.link_to(_('Last ten changes'),h.url('changelog_home',repo_name=c.repo_name))}</div>
330 <div class="breadcrumbs">${h.link_to(_('Last ten changes'),h.url('changelog_home',repo_name=c.repo_name))}</div>
276 </div>
331 </div>
277 <div class="table">
332 <div class="table">
278 <%include file='../shortlog/shortlog_data.html'/>
333 <%include file='../shortlog/shortlog_data.html'/>
279 ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
334 ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
280 </div>
335 </div>
281 </div>
336 </div>
282 <div class="box">
337 <div class="box">
283 <div class="title">
338 <div class="title">
284 <div class="breadcrumbs">${h.link_to(_('Last ten tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
339 <div class="breadcrumbs">${h.link_to(_('Last ten tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
285 </div>
340 </div>
286 <div class="table">
341 <div class="table">
287 <%include file='../tags/tags_data.html'/>
342 <%include file='../tags/tags_data.html'/>
288 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
343 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
289 </div>
344 </div>
290 </div>
345 </div>
291 <div class="box">
346 <div class="box">
292 <div class="title">
347 <div class="title">
293 <div class="breadcrumbs">${h.link_to(_('Last ten branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
348 <div class="breadcrumbs">${h.link_to(_('Last ten branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
294 </div>
349 </div>
295 <div class="table">
350 <div class="table">
296 <%include file='../branches/branches_data.html'/>
351 <%include file='../branches/branches_data.html'/>
297 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
352 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
298 </div>
353 </div>
299 </div>
354 </div>
300
355
301 </%def> No newline at end of file
356 </%def>
@@ -1,49 +1,49
1 from pylons_app import get_version
1 from pylons_app import get_version
2 try:
2 try:
3 from setuptools import setup, find_packages
3 from setuptools import setup, find_packages
4 except ImportError:
4 except ImportError:
5 from ez_setup import use_setuptools
5 from ez_setup import use_setuptools
6 use_setuptools()
6 use_setuptools()
7 from setuptools import setup, find_packages
7 from setuptools import setup, find_packages
8
8
9 setup(
9 setup(
10 name='HgApp-%s' % get_version(),
10 name='HgApp-%s' % get_version(),
11 version=get_version(),
11 version=get_version(),
12 description='Mercurial repository serving and browsing app',
12 description='Mercurial repository serving and browsing app',
13 keywords='mercurial web hgwebdir replacement serving hgweb',
13 keywords='mercurial web hgwebdir replacement serving hgweb',
14 license='BSD',
14 license='BSD',
15 author='marcin kuzminski',
15 author='marcin kuzminski',
16 author_email='marcin@python-works.com',
16 author_email='marcin@python-works.com',
17 url='http://hg.python-works.com',
17 url='http://hg.python-works.com',
18 install_requires=[
18 install_requires=[
19 "Pylons>=1.0.0",
19 "Pylons>=1.0.0",
20 "SQLAlchemy>=0.6",
20 "SQLAlchemy>=0.6",
21 "babel",
21 "babel",
22 "Mako>=0.3.2",
22 "Mako>=0.3.2",
23 "vcs>=0.1.5",
23 "vcs>=0.1.5",
24 "pygments>=1.3.0",
24 "pygments>=1.3.0",
25 "mercurial>=1.6",
25 "mercurial>=1.6",
26 "pysqlite",
26 "pysqlite",
27 "whoosh==1.0.0b16",
27 "whoosh==1.0.0b17",
28 "py-bcrypt",
28 "py-bcrypt",
29 "celery",
29 "celery",
30 ],
30 ],
31 setup_requires=["PasteScript>=1.6.3"],
31 setup_requires=["PasteScript>=1.6.3"],
32 packages=find_packages(exclude=['ez_setup']),
32 packages=find_packages(exclude=['ez_setup']),
33 include_package_data=True,
33 include_package_data=True,
34 test_suite='nose.collector',
34 test_suite='nose.collector',
35 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
35 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
36 message_extractors={'pylons_app': [
36 message_extractors={'pylons_app': [
37 ('**.py', 'python', None),
37 ('**.py', 'python', None),
38 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
38 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
39 ('public/**', 'ignore', None)]},
39 ('public/**', 'ignore', None)]},
40 zip_safe=False,
40 zip_safe=False,
41 paster_plugins=['PasteScript', 'Pylons'],
41 paster_plugins=['PasteScript', 'Pylons'],
42 entry_points="""
42 entry_points="""
43 [paste.app_factory]
43 [paste.app_factory]
44 main = pylons_app.config.middleware:make_app
44 main = pylons_app.config.middleware:make_app
45
45
46 [paste.app_install]
46 [paste.app_install]
47 main = pylons.util:PylonsInstaller
47 main = pylons.util:PylonsInstaller
48 """,
48 """,
49 )
49 )
General Comments 0
You need to be logged in to leave comments. Login now