Show More
@@ -0,0 +1,74 b'' | |||
|
1 | # List of modules to import when celery starts. | |
|
2 | import sys | |
|
3 | import os | |
|
4 | import ConfigParser | |
|
5 | root = os.getcwd() | |
|
6 | ||
|
7 | PYLONS_CONFIG_NAME = 'development.ini' | |
|
8 | ||
|
9 | sys.path.append(root) | |
|
10 | config = ConfigParser.ConfigParser({'here':root}) | |
|
11 | config.read('%s/%s' % (root, PYLONS_CONFIG_NAME)) | |
|
12 | PYLONS_CONFIG = config | |
|
13 | ||
|
14 | CELERY_IMPORTS = ("pylons_app.lib.celerylib.tasks",) | |
|
15 | ||
|
16 | ## Result store settings. | |
|
17 | CELERY_RESULT_BACKEND = "database" | |
|
18 | CELERY_RESULT_DBURI = dict(config.items('app:main'))['sqlalchemy.db1.url'] | |
|
19 | CELERY_RESULT_SERIALIZER = 'json' | |
|
20 | ||
|
21 | ||
|
22 | BROKER_CONNECTION_MAX_RETRIES = 30 | |
|
23 | ||
|
24 | ## Broker settings. | |
|
25 | BROKER_HOST = "localhost" | |
|
26 | BROKER_PORT = 5672 | |
|
27 | BROKER_VHOST = "rabbitmqhost" | |
|
28 | BROKER_USER = "rabbitmq" | |
|
29 | BROKER_PASSWORD = "qweqwe" | |
|
30 | ||
|
31 | ## Worker settings | |
|
32 | ## If you're doing mostly I/O you can have more processes, | |
|
33 | ## but if mostly spending CPU, try to keep it close to the | |
|
34 | ## number of CPUs on your machine. If not set, the number of CPUs/cores | |
|
35 | ## available will be used. | |
|
36 | CELERYD_CONCURRENCY = 2 | |
|
37 | # CELERYD_LOG_FILE = "celeryd.log" | |
|
38 | CELERYD_LOG_LEVEL = "DEBUG" | |
|
39 | CELERYD_MAX_TASKS_PER_CHILD = 1 | |
|
40 | ||
|
41 | #Tasks will never be sent to the queue, but executed locally instead. | |
|
42 | CELERY_ALWAYS_EAGER = False | |
|
43 | ||
|
44 | #=============================================================================== | |
|
45 | # EMAIL SETTINGS | |
|
46 | #=============================================================================== | |
|
47 | pylons_email_config = dict(config.items('DEFAULT')) | |
|
48 | ||
|
49 | CELERY_SEND_TASK_ERROR_EMAILS = True | |
|
50 | ||
|
51 | #List of (name, email_address) tuples for the admins that should receive error e-mails. | |
|
52 | ADMINS = [('Administrator', pylons_email_config.get('email_to'))] | |
|
53 | ||
|
54 | #The e-mail address this worker sends e-mails from. Default is "celery@localhost". | |
|
55 | SERVER_EMAIL = pylons_email_config.get('error_email_from') | |
|
56 | ||
|
57 | #The mail server to use. Default is "localhost". | |
|
58 | MAIL_HOST = pylons_email_config.get('smtp_server') | |
|
59 | ||
|
60 | #Username (if required) to log on to the mail server with. | |
|
61 | MAIL_HOST_USER = pylons_email_config.get('smtp_username') | |
|
62 | ||
|
63 | #Password (if required) to log on to the mail server with. | |
|
64 | MAIL_HOST_PASSWORD = pylons_email_config.get('smtp_password') | |
|
65 | ||
|
66 | MAIL_PORT = pylons_email_config.get('smtp_port') | |
|
67 | ||
|
68 | ||
|
69 | #=============================================================================== | |
|
70 | # INSTRUCTIONS FOR RABBITMQ | |
|
71 | #=============================================================================== | |
|
72 | # rabbitmqctl add_user rabbitmq qweqwe | |
|
73 | # rabbitmqctl add_vhost rabbitmqhost | |
|
74 | # rabbitmqctl set_permissions -p rabbitmqhost rabbitmq ".*" ".*" ".*" |
@@ -0,0 +1,66 b'' | |||
|
1 | from pylons_app.lib.pidlock import DaemonLock, LockHeld | |
|
2 | from vcs.utils.lazy import LazyProperty | |
|
3 | from decorator import decorator | |
|
4 | import logging | |
|
5 | import os | |
|
6 | import sys | |
|
7 | import traceback | |
|
8 | from hashlib import md5 | |
|
9 | log = logging.getLogger(__name__) | |
|
10 | ||
|
11 | class ResultWrapper(object): | |
|
12 | def __init__(self, task): | |
|
13 | self.task = task | |
|
14 | ||
|
15 | @LazyProperty | |
|
16 | def result(self): | |
|
17 | return self.task | |
|
18 | ||
|
19 | def run_task(task, *args, **kwargs): | |
|
20 | try: | |
|
21 | t = task.delay(*args, **kwargs) | |
|
22 | log.info('running task %s', t.task_id) | |
|
23 | return t | |
|
24 | except Exception, e: | |
|
25 | print e | |
|
26 | if e.errno == 111: | |
|
27 | log.debug('Unnable to connect. Sync execution') | |
|
28 | else: | |
|
29 | log.error(traceback.format_exc()) | |
|
30 | #pure sync version | |
|
31 | return ResultWrapper(task(*args, **kwargs)) | |
|
32 | ||
|
33 | ||
|
34 | class LockTask(object): | |
|
35 | """LockTask decorator""" | |
|
36 | ||
|
37 | def __init__(self, func): | |
|
38 | self.func = func | |
|
39 | ||
|
40 | def __call__(self, func): | |
|
41 | return decorator(self.__wrapper, func) | |
|
42 | ||
|
43 | def __wrapper(self, func, *fargs, **fkwargs): | |
|
44 | params = [] | |
|
45 | params.extend(fargs) | |
|
46 | params.extend(fkwargs.values()) | |
|
47 | lockkey = 'task_%s' % \ | |
|
48 | md5(str(self.func) + '-' + '-'.join(map(str, params))).hexdigest() | |
|
49 | log.info('running task with lockkey %s', lockkey) | |
|
50 | try: | |
|
51 | l = DaemonLock(lockkey) | |
|
52 | return func(*fargs, **fkwargs) | |
|
53 | l.release() | |
|
54 | except LockHeld: | |
|
55 | log.info('LockHeld') | |
|
56 | return 'Task with key %s already running' % lockkey | |
|
57 | ||
|
58 | ||
|
59 | ||
|
60 | ||
|
61 | ||
|
62 | ||
|
63 | ||
|
64 | ||
|
65 | ||
|
66 |
@@ -0,0 +1,270 b'' | |||
|
1 | from celery.decorators import task | |
|
2 | from celery.task.sets import subtask | |
|
3 | from celeryconfig import PYLONS_CONFIG as config | |
|
4 | from pylons.i18n.translation import _ | |
|
5 | from pylons_app.lib.celerylib import run_task, LockTask | |
|
6 | from pylons_app.lib.helpers import person | |
|
7 | from pylons_app.lib.smtp_mailer import SmtpMailer | |
|
8 | from pylons_app.lib.utils import OrderedDict | |
|
9 | from operator import itemgetter | |
|
10 | from vcs.backends.hg import MercurialRepository | |
|
11 | from time import mktime | |
|
12 | import traceback | |
|
13 | import json | |
|
14 | ||
|
15 | __all__ = ['whoosh_index', 'get_commits_stats', | |
|
16 | 'reset_user_password', 'send_email'] | |
|
17 | ||
|
18 | def get_session(): | |
|
19 | from sqlalchemy import engine_from_config | |
|
20 | from sqlalchemy.orm import sessionmaker, scoped_session | |
|
21 | engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.') | |
|
22 | sa = scoped_session(sessionmaker(bind=engine)) | |
|
23 | return sa | |
|
24 | ||
|
25 | def get_hg_settings(): | |
|
26 | from pylons_app.model.db import HgAppSettings | |
|
27 | try: | |
|
28 | sa = get_session() | |
|
29 | ret = sa.query(HgAppSettings).all() | |
|
30 | finally: | |
|
31 | sa.remove() | |
|
32 | ||
|
33 | if not ret: | |
|
34 | raise Exception('Could not get application settings !') | |
|
35 | settings = {} | |
|
36 | for each in ret: | |
|
37 | settings['hg_app_' + each.app_settings_name] = each.app_settings_value | |
|
38 | ||
|
39 | return settings | |
|
40 | ||
|
41 | def get_hg_ui_settings(): | |
|
42 | from pylons_app.model.db import HgAppUi | |
|
43 | try: | |
|
44 | sa = get_session() | |
|
45 | ret = sa.query(HgAppUi).all() | |
|
46 | finally: | |
|
47 | sa.remove() | |
|
48 | ||
|
49 | if not ret: | |
|
50 | raise Exception('Could not get application ui settings !') | |
|
51 | settings = {} | |
|
52 | for each in ret: | |
|
53 | k = each.ui_key | |
|
54 | v = each.ui_value | |
|
55 | if k == '/': | |
|
56 | k = 'root_path' | |
|
57 | ||
|
58 | if k.find('.') != -1: | |
|
59 | k = k.replace('.', '_') | |
|
60 | ||
|
61 | if each.ui_section == 'hooks': | |
|
62 | v = each.ui_active | |
|
63 | ||
|
64 | settings[each.ui_section + '_' + k] = v | |
|
65 | ||
|
66 | return settings | |
|
67 | ||
|
68 | @task | |
|
69 | def whoosh_index(repo_location, full_index): | |
|
70 | log = whoosh_index.get_logger() | |
|
71 | from pylons_app.lib.pidlock import DaemonLock | |
|
72 | from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon, LockHeld | |
|
73 | try: | |
|
74 | l = DaemonLock() | |
|
75 | WhooshIndexingDaemon(repo_location=repo_location)\ | |
|
76 | .run(full_index=full_index) | |
|
77 | l.release() | |
|
78 | return 'Done' | |
|
79 | except LockHeld: | |
|
80 | log.info('LockHeld') | |
|
81 | return 'LockHeld' | |
|
82 | ||
|
83 | ||
|
84 | @task | |
|
85 | @LockTask('get_commits_stats') | |
|
86 | def get_commits_stats(repo_name, ts_min_y, ts_max_y): | |
|
87 | author_key_cleaner = lambda k: person(k).replace('"', "") #for js data compatibilty | |
|
88 | ||
|
89 | from pylons_app.model.db import Statistics, Repository | |
|
90 | log = get_commits_stats.get_logger() | |
|
91 | commits_by_day_author_aggregate = {} | |
|
92 | commits_by_day_aggregate = {} | |
|
93 | repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') | |
|
94 | repo = MercurialRepository(repos_path + repo_name) | |
|
95 | ||
|
96 | skip_date_limit = True | |
|
97 | parse_limit = 350 #limit for single task changeset parsing | |
|
98 | last_rev = 0 | |
|
99 | last_cs = None | |
|
100 | timegetter = itemgetter('time') | |
|
101 | ||
|
102 | sa = get_session() | |
|
103 | ||
|
104 | dbrepo = sa.query(Repository)\ | |
|
105 | .filter(Repository.repo_name == repo_name).scalar() | |
|
106 | cur_stats = sa.query(Statistics)\ | |
|
107 | .filter(Statistics.repository == dbrepo).scalar() | |
|
108 | if cur_stats: | |
|
109 | last_rev = cur_stats.stat_on_revision | |
|
110 | ||
|
111 | if last_rev == repo.revisions[-1]: | |
|
112 | #pass silently without any work | |
|
113 | return True | |
|
114 | ||
|
115 | if cur_stats: | |
|
116 | commits_by_day_aggregate = OrderedDict( | |
|
117 | json.loads( | |
|
118 | cur_stats.commit_activity_combined)) | |
|
119 | commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity) | |
|
120 | ||
|
121 | for cnt, rev in enumerate(repo.revisions[last_rev:]): | |
|
122 | last_cs = cs = repo.get_changeset(rev) | |
|
123 | k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1], | |
|
124 | cs.date.timetuple()[2]) | |
|
125 | timetupple = [int(x) for x in k.split('-')] | |
|
126 | timetupple.extend([0 for _ in xrange(6)]) | |
|
127 | k = mktime(timetupple) | |
|
128 | if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)): | |
|
129 | try: | |
|
130 | l = [timegetter(x) for x in commits_by_day_author_aggregate\ | |
|
131 | [author_key_cleaner(cs.author)]['data']] | |
|
132 | time_pos = l.index(k) | |
|
133 | except ValueError: | |
|
134 | time_pos = False | |
|
135 | ||
|
136 | if time_pos >= 0 and time_pos is not False: | |
|
137 | ||
|
138 | datadict = commits_by_day_author_aggregate\ | |
|
139 | [author_key_cleaner(cs.author)]['data'][time_pos] | |
|
140 | ||
|
141 | datadict["commits"] += 1 | |
|
142 | datadict["added"] += len(cs.added) | |
|
143 | datadict["changed"] += len(cs.changed) | |
|
144 | datadict["removed"] += len(cs.removed) | |
|
145 | #print datadict | |
|
146 | ||
|
147 | else: | |
|
148 | #print 'ELSE !!!!' | |
|
149 | if k >= ts_min_y and k <= ts_max_y or skip_date_limit: | |
|
150 | ||
|
151 | datadict = {"time":k, | |
|
152 | "commits":1, | |
|
153 | "added":len(cs.added), | |
|
154 | "changed":len(cs.changed), | |
|
155 | "removed":len(cs.removed), | |
|
156 | } | |
|
157 | commits_by_day_author_aggregate\ | |
|
158 | [author_key_cleaner(cs.author)]['data'].append(datadict) | |
|
159 | ||
|
160 | else: | |
|
161 | #print k, 'nokey ADDING' | |
|
162 | if k >= ts_min_y and k <= ts_max_y or skip_date_limit: | |
|
163 | commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = { | |
|
164 | "label":author_key_cleaner(cs.author), | |
|
165 | "data":[{"time":k, | |
|
166 | "commits":1, | |
|
167 | "added":len(cs.added), | |
|
168 | "changed":len(cs.changed), | |
|
169 | "removed":len(cs.removed), | |
|
170 | }], | |
|
171 | "schema":["commits"], | |
|
172 | } | |
|
173 | ||
|
174 | # #gather all data by day | |
|
175 | if commits_by_day_aggregate.has_key(k): | |
|
176 | commits_by_day_aggregate[k] += 1 | |
|
177 | else: | |
|
178 | commits_by_day_aggregate[k] = 1 | |
|
179 | ||
|
180 | if cnt >= parse_limit: | |
|
181 | #don't fetch to much data since we can freeze application | |
|
182 | break | |
|
183 | ||
|
184 | overview_data = [] | |
|
185 | for k, v in commits_by_day_aggregate.items(): | |
|
186 | overview_data.append([k, v]) | |
|
187 | overview_data = sorted(overview_data, key=itemgetter(0)) | |
|
188 | ||
|
189 | if not commits_by_day_author_aggregate: | |
|
190 | commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = { | |
|
191 | "label":author_key_cleaner(repo.contact), | |
|
192 | "data":[0, 1], | |
|
193 | "schema":["commits"], | |
|
194 | } | |
|
195 | ||
|
196 | stats = cur_stats if cur_stats else Statistics() | |
|
197 | stats.commit_activity = json.dumps(commits_by_day_author_aggregate) | |
|
198 | stats.commit_activity_combined = json.dumps(overview_data) | |
|
199 | stats.repository = dbrepo | |
|
200 | stats.stat_on_revision = last_cs.revision | |
|
201 | stats.languages = json.dumps({'_TOTAL_':0, '':0}) | |
|
202 | ||
|
203 | try: | |
|
204 | sa.add(stats) | |
|
205 | sa.commit() | |
|
206 | except: | |
|
207 | log.error(traceback.format_exc()) | |
|
208 | sa.rollback() | |
|
209 | return False | |
|
210 | ||
|
211 | run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y) | |
|
212 | ||
|
213 | return True | |
|
214 | ||
|
215 | @task | |
|
216 | def reset_user_password(user_email): | |
|
217 | log = reset_user_password.get_logger() | |
|
218 | from pylons_app.lib import auth | |
|
219 | from pylons_app.model.db import User | |
|
220 | ||
|
221 | try: | |
|
222 | try: | |
|
223 | sa = get_session() | |
|
224 | user = sa.query(User).filter(User.email == user_email).scalar() | |
|
225 | new_passwd = auth.PasswordGenerator().gen_password(8, | |
|
226 | auth.PasswordGenerator.ALPHABETS_BIG_SMALL) | |
|
227 | if user: | |
|
228 | user.password = auth.get_crypt_password(new_passwd) | |
|
229 | sa.add(user) | |
|
230 | sa.commit() | |
|
231 | log.info('change password for %s', user_email) | |
|
232 | if new_passwd is None: | |
|
233 | raise Exception('unable to generate new password') | |
|
234 | ||
|
235 | except: | |
|
236 | log.error(traceback.format_exc()) | |
|
237 | sa.rollback() | |
|
238 | ||
|
239 | run_task(send_email, user_email, | |
|
240 | "Your new hg-app password", | |
|
241 | 'Your new hg-app password:%s' % (new_passwd)) | |
|
242 | log.info('send new password mail to %s', user_email) | |
|
243 | ||
|
244 | ||
|
245 | except: | |
|
246 | log.error('Failed to update user password') | |
|
247 | log.error(traceback.format_exc()) | |
|
248 | return True | |
|
249 | ||
|
250 | @task | |
|
251 | def send_email(recipients, subject, body): | |
|
252 | log = send_email.get_logger() | |
|
253 | email_config = dict(config.items('DEFAULT')) | |
|
254 | mail_from = email_config.get('app_email_from') | |
|
255 | user = email_config.get('smtp_username') | |
|
256 | passwd = email_config.get('smtp_password') | |
|
257 | mail_server = email_config.get('smtp_server') | |
|
258 | mail_port = email_config.get('smtp_port') | |
|
259 | tls = email_config.get('smtp_use_tls') | |
|
260 | ssl = False | |
|
261 | ||
|
262 | try: | |
|
263 | m = SmtpMailer(mail_from, user, passwd, mail_server, | |
|
264 | mail_port, ssl, tls) | |
|
265 | m.send(recipients, subject, body) | |
|
266 | except: | |
|
267 | log.error('Mail sending failed') | |
|
268 | log.error(traceback.format_exc()) | |
|
269 | return False | |
|
270 | return True |
@@ -0,0 +1,118 b'' | |||
|
1 | import logging | |
|
2 | import smtplib | |
|
3 | import mimetypes | |
|
4 | from email.mime.multipart import MIMEMultipart | |
|
5 | from email.mime.image import MIMEImage | |
|
6 | from email.mime.audio import MIMEAudio | |
|
7 | from email.mime.base import MIMEBase | |
|
8 | from email.mime.text import MIMEText | |
|
9 | from email.utils import formatdate | |
|
10 | from email import encoders | |
|
11 | ||
|
12 | class SmtpMailer(object): | |
|
13 | """simple smtp mailer class | |
|
14 | ||
|
15 | mailer = SmtpMailer(mail_from, user, passwd, mail_server, mail_port, ssl, tls) | |
|
16 | mailer.send(recipients, subject, body, attachment_files) | |
|
17 | ||
|
18 | :param recipients might be a list of string or single string | |
|
19 | :param attachment_files is a dict of {filename:location} | |
|
20 | it tries to guess the mimetype and attach the file | |
|
21 | """ | |
|
22 | ||
|
23 | def __init__(self, mail_from, user, passwd, mail_server, | |
|
24 | mail_port=None, ssl=False, tls=False): | |
|
25 | ||
|
26 | self.mail_from = mail_from | |
|
27 | self.mail_server = mail_server | |
|
28 | self.mail_port = mail_port | |
|
29 | self.user = user | |
|
30 | self.passwd = passwd | |
|
31 | self.ssl = ssl | |
|
32 | self.tls = tls | |
|
33 | self.debug = False | |
|
34 | ||
|
35 | def send(self, recipients=[], subject='', body='', attachment_files={}): | |
|
36 | ||
|
37 | if isinstance(recipients, basestring): | |
|
38 | recipients = [recipients] | |
|
39 | if self.ssl: | |
|
40 | smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port) | |
|
41 | else: | |
|
42 | smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port) | |
|
43 | ||
|
44 | if self.tls: | |
|
45 | smtp_serv.starttls() | |
|
46 | ||
|
47 | if self.debug: | |
|
48 | smtp_serv.set_debuglevel(1) | |
|
49 | ||
|
50 | smtp_serv.ehlo("mailer") | |
|
51 | ||
|
52 | #if server requires authorization you must provide login and password | |
|
53 | smtp_serv.login(self.user, self.passwd) | |
|
54 | ||
|
55 | date_ = formatdate(localtime=True) | |
|
56 | msg = MIMEMultipart() | |
|
57 | msg['From'] = self.mail_from | |
|
58 | msg['To'] = ','.join(recipients) | |
|
59 | msg['Date'] = date_ | |
|
60 | msg['Subject'] = subject | |
|
61 | msg.preamble = 'You will not see this in a MIME-aware mail reader.\n' | |
|
62 | ||
|
63 | msg.attach(MIMEText(body)) | |
|
64 | ||
|
65 | if attachment_files: | |
|
66 | self.__atach_files(msg, attachment_files) | |
|
67 | ||
|
68 | smtp_serv.sendmail(self.mail_from, recipients, msg.as_string()) | |
|
69 | logging.info('MAIL SEND TO: %s' % recipients) | |
|
70 | smtp_serv.quit() | |
|
71 | ||
|
72 | ||
|
73 | def __atach_files(self, msg, attachment_files): | |
|
74 | if isinstance(attachment_files, dict): | |
|
75 | for f_name, msg_file in attachment_files.items(): | |
|
76 | ctype, encoding = mimetypes.guess_type(f_name) | |
|
77 | logging.info("guessing file %s type based on %s" , ctype, f_name) | |
|
78 | if ctype is None or encoding is not None: | |
|
79 | # No guess could be made, or the file is encoded (compressed), so | |
|
80 | # use a generic bag-of-bits type. | |
|
81 | ctype = 'application/octet-stream' | |
|
82 | maintype, subtype = ctype.split('/', 1) | |
|
83 | if maintype == 'text': | |
|
84 | # Note: we should handle calculating the charset | |
|
85 | file_part = MIMEText(self.get_content(msg_file), | |
|
86 | _subtype=subtype) | |
|
87 | elif maintype == 'image': | |
|
88 | file_part = MIMEImage(self.get_content(msg_file), | |
|
89 | _subtype=subtype) | |
|
90 | elif maintype == 'audio': | |
|
91 | file_part = MIMEAudio(self.get_content(msg_file), | |
|
92 | _subtype=subtype) | |
|
93 | else: | |
|
94 | file_part = MIMEBase(maintype, subtype) | |
|
95 | file_part.set_payload(self.get_content(msg_file)) | |
|
96 | # Encode the payload using Base64 | |
|
97 | encoders.encode_base64(msg) | |
|
98 | # Set the filename parameter | |
|
99 | file_part.add_header('Content-Disposition', 'attachment', | |
|
100 | filename=f_name) | |
|
101 | file_part.add_header('Content-Type', ctype, name=f_name) | |
|
102 | msg.attach(file_part) | |
|
103 | else: | |
|
104 | raise Exception('Attachment files should be' | |
|
105 | 'a dict in format {"filename":"filepath"}') | |
|
106 | ||
|
107 | def get_content(self, msg_file): | |
|
108 | ''' | |
|
109 | Get content based on type, if content is a string do open first | |
|
110 | else just read because it's a probably open file object | |
|
111 | @param msg_file: | |
|
112 | ''' | |
|
113 | if isinstance(msg_file, str): | |
|
114 | return open(msg_file, "rb").read() | |
|
115 | else: | |
|
116 | #just for safe seek to 0 | |
|
117 | msg_file.seek(0) | |
|
118 | return msg_file.read() |
@@ -0,0 +1,267 b'' | |||
|
1 | """caching_query.py | |
|
2 | ||
|
3 | Represent persistence structures which allow the usage of | |
|
4 | Beaker caching with SQLAlchemy. | |
|
5 | ||
|
6 | The three new concepts introduced here are: | |
|
7 | ||
|
8 | * CachingQuery - a Query subclass that caches and | |
|
9 | retrieves results in/from Beaker. | |
|
10 | * FromCache - a query option that establishes caching | |
|
11 | parameters on a Query | |
|
12 | * RelationshipCache - a variant of FromCache which is specific | |
|
13 | to a query invoked during a lazy load. | |
|
14 | * _params_from_query - extracts value parameters from | |
|
15 | a Query. | |
|
16 | ||
|
17 | The rest of what's here are standard SQLAlchemy and | |
|
18 | Beaker constructs. | |
|
19 | ||
|
20 | """ | |
|
21 | from sqlalchemy.orm.interfaces import MapperOption | |
|
22 | from sqlalchemy.orm.query import Query | |
|
23 | from sqlalchemy.sql import visitors | |
|
24 | ||
|
25 | class CachingQuery(Query): | |
|
26 | """A Query subclass which optionally loads full results from a Beaker | |
|
27 | cache region. | |
|
28 | ||
|
29 | The CachingQuery stores additional state that allows it to consult | |
|
30 | a Beaker cache before accessing the database: | |
|
31 | ||
|
32 | * A "region", which is a cache region argument passed to a | |
|
33 | Beaker CacheManager, specifies a particular cache configuration | |
|
34 | (including backend implementation, expiration times, etc.) | |
|
35 | * A "namespace", which is a qualifying name that identifies a | |
|
36 | group of keys within the cache. A query that filters on a name | |
|
37 | might use the name "by_name", a query that filters on a date range | |
|
38 | to a joined table might use the name "related_date_range". | |
|
39 | ||
|
40 | When the above state is present, a Beaker cache is retrieved. | |
|
41 | ||
|
42 | The "namespace" name is first concatenated with | |
|
43 | a string composed of the individual entities and columns the Query | |
|
44 | requests, i.e. such as ``Query(User.id, User.name)``. | |
|
45 | ||
|
46 | The Beaker cache is then loaded from the cache manager based | |
|
47 | on the region and composed namespace. The key within the cache | |
|
48 | itself is then constructed against the bind parameters specified | |
|
49 | by this query, which are usually literals defined in the | |
|
50 | WHERE clause. | |
|
51 | ||
|
52 | The FromCache and RelationshipCache mapper options below represent | |
|
53 | the "public" method of configuring this state upon the CachingQuery. | |
|
54 | ||
|
55 | """ | |
|
56 | ||
|
57 | def __init__(self, manager, *args, **kw): | |
|
58 | self.cache_manager = manager | |
|
59 | Query.__init__(self, *args, **kw) | |
|
60 | ||
|
61 | def __iter__(self): | |
|
62 | """override __iter__ to pull results from Beaker | |
|
63 | if particular attributes have been configured. | |
|
64 | ||
|
65 | Note that this approach does *not* detach the loaded objects from | |
|
66 | the current session. If the cache backend is an in-process cache | |
|
67 | (like "memory") and lives beyond the scope of the current session's | |
|
68 | transaction, those objects may be expired. The method here can be | |
|
69 | modified to first expunge() each loaded item from the current | |
|
70 | session before returning the list of items, so that the items | |
|
71 | in the cache are not the same ones in the current Session. | |
|
72 | ||
|
73 | """ | |
|
74 | if hasattr(self, '_cache_parameters'): | |
|
75 | return self.get_value(createfunc=lambda: list(Query.__iter__(self))) | |
|
76 | else: | |
|
77 | return Query.__iter__(self) | |
|
78 | ||
|
79 | def invalidate(self): | |
|
80 | """Invalidate the value represented by this Query.""" | |
|
81 | ||
|
82 | cache, cache_key = _get_cache_parameters(self) | |
|
83 | cache.remove(cache_key) | |
|
84 | ||
|
85 | def get_value(self, merge=True, createfunc=None): | |
|
86 | """Return the value from the cache for this query. | |
|
87 | ||
|
88 | Raise KeyError if no value present and no | |
|
89 | createfunc specified. | |
|
90 | ||
|
91 | """ | |
|
92 | cache, cache_key = _get_cache_parameters(self) | |
|
93 | ret = cache.get_value(cache_key, createfunc=createfunc) | |
|
94 | if merge: | |
|
95 | ret = self.merge_result(ret, load=False) | |
|
96 | return ret | |
|
97 | ||
|
98 | def set_value(self, value): | |
|
99 | """Set the value in the cache for this query.""" | |
|
100 | ||
|
101 | cache, cache_key = _get_cache_parameters(self) | |
|
102 | cache.put(cache_key, value) | |
|
103 | ||
|
104 | def query_callable(manager): | |
|
105 | def query(*arg, **kw): | |
|
106 | return CachingQuery(manager, *arg, **kw) | |
|
107 | return query | |
|
108 | ||
|
109 | def _get_cache_parameters(query): | |
|
110 | """For a query with cache_region and cache_namespace configured, | |
|
111 | return the correspoinding Cache instance and cache key, based | |
|
112 | on this query's current criterion and parameter values. | |
|
113 | ||
|
114 | """ | |
|
115 | if not hasattr(query, '_cache_parameters'): | |
|
116 | raise ValueError("This Query does not have caching parameters configured.") | |
|
117 | ||
|
118 | region, namespace, cache_key = query._cache_parameters | |
|
119 | ||
|
120 | namespace = _namespace_from_query(namespace, query) | |
|
121 | ||
|
122 | if cache_key is None: | |
|
123 | # cache key - the value arguments from this query's parameters. | |
|
124 | args = _params_from_query(query) | |
|
125 | cache_key = " ".join([str(x) for x in args]) | |
|
126 | ||
|
127 | # get cache | |
|
128 | cache = query.cache_manager.get_cache_region(namespace, region) | |
|
129 | ||
|
130 | # optional - hash the cache_key too for consistent length | |
|
131 | # import uuid | |
|
132 | # cache_key= str(uuid.uuid5(uuid.NAMESPACE_DNS, cache_key)) | |
|
133 | ||
|
134 | return cache, cache_key | |
|
135 | ||
|
136 | def _namespace_from_query(namespace, query): | |
|
137 | # cache namespace - the token handed in by the | |
|
138 | # option + class we're querying against | |
|
139 | namespace = " ".join([namespace] + [str(x) for x in query._entities]) | |
|
140 | ||
|
141 | # memcached wants this | |
|
142 | namespace = namespace.replace(' ', '_') | |
|
143 | ||
|
144 | return namespace | |
|
145 | ||
|
146 | def _set_cache_parameters(query, region, namespace, cache_key): | |
|
147 | ||
|
148 | if hasattr(query, '_cache_parameters'): | |
|
149 | region, namespace, cache_key = query._cache_parameters | |
|
150 | raise ValueError("This query is already configured " | |
|
151 | "for region %r namespace %r" % | |
|
152 | (region, namespace) | |
|
153 | ) | |
|
154 | query._cache_parameters = region, namespace, cache_key | |
|
155 | ||
|
156 | class FromCache(MapperOption): | |
|
157 | """Specifies that a Query should load results from a cache.""" | |
|
158 | ||
|
159 | propagate_to_loaders = False | |
|
160 | ||
|
161 | def __init__(self, region, namespace, cache_key=None): | |
|
162 | """Construct a new FromCache. | |
|
163 | ||
|
164 | :param region: the cache region. Should be a | |
|
165 | region configured in the Beaker CacheManager. | |
|
166 | ||
|
167 | :param namespace: the cache namespace. Should | |
|
168 | be a name uniquely describing the target Query's | |
|
169 | lexical structure. | |
|
170 | ||
|
171 | :param cache_key: optional. A string cache key | |
|
172 | that will serve as the key to the query. Use this | |
|
173 | if your query has a huge amount of parameters (such | |
|
174 | as when using in_()) which correspond more simply to | |
|
175 | some other identifier. | |
|
176 | ||
|
177 | """ | |
|
178 | self.region = region | |
|
179 | self.namespace = namespace | |
|
180 | self.cache_key = cache_key | |
|
181 | ||
|
182 | def process_query(self, query): | |
|
183 | """Process a Query during normal loading operation.""" | |
|
184 | ||
|
185 | _set_cache_parameters(query, self.region, self.namespace, self.cache_key) | |
|
186 | ||
|
187 | class RelationshipCache(MapperOption): | |
|
188 | """Specifies that a Query as called within a "lazy load" | |
|
189 | should load results from a cache.""" | |
|
190 | ||
|
191 | propagate_to_loaders = True | |
|
192 | ||
|
193 | def __init__(self, region, namespace, attribute): | |
|
194 | """Construct a new RelationshipCache. | |
|
195 | ||
|
196 | :param region: the cache region. Should be a | |
|
197 | region configured in the Beaker CacheManager. | |
|
198 | ||
|
199 | :param namespace: the cache namespace. Should | |
|
200 | be a name uniquely describing the target Query's | |
|
201 | lexical structure. | |
|
202 | ||
|
203 | :param attribute: A Class.attribute which | |
|
204 | indicates a particular class relationship() whose | |
|
205 | lazy loader should be pulled from the cache. | |
|
206 | ||
|
207 | """ | |
|
208 | self.region = region | |
|
209 | self.namespace = namespace | |
|
210 | self._relationship_options = { | |
|
211 | (attribute.property.parent.class_, attribute.property.key) : self | |
|
212 | } | |
|
213 | ||
|
214 | def process_query_conditionally(self, query): | |
|
215 | """Process a Query that is used within a lazy loader. | |
|
216 | ||
|
217 | (the process_query_conditionally() method is a SQLAlchemy | |
|
218 | hook invoked only within lazyload.) | |
|
219 | ||
|
220 | """ | |
|
221 | if query._current_path: | |
|
222 | mapper, key = query._current_path[-2:] | |
|
223 | ||
|
224 | for cls in mapper.class_.__mro__: | |
|
225 | if (cls, key) in self._relationship_options: | |
|
226 | relationship_option = self._relationship_options[(cls, key)] | |
|
227 | _set_cache_parameters( | |
|
228 | query, | |
|
229 | relationship_option.region, | |
|
230 | relationship_option.namespace, | |
|
231 | None) | |
|
232 | ||
|
233 | def and_(self, option): | |
|
234 | """Chain another RelationshipCache option to this one. | |
|
235 | ||
|
236 | While many RelationshipCache objects can be specified on a single | |
|
237 | Query separately, chaining them together allows for a more efficient | |
|
238 | lookup during load. | |
|
239 | ||
|
240 | """ | |
|
241 | self._relationship_options.update(option._relationship_options) | |
|
242 | return self | |
|
243 | ||
|
244 | ||
|
245 | def _params_from_query(query): | |
|
246 | """Pull the bind parameter values from a query. | |
|
247 | ||
|
248 | This takes into account any scalar attribute bindparam set up. | |
|
249 | ||
|
250 | E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7))) | |
|
251 | would return [5, 7]. | |
|
252 | ||
|
253 | """ | |
|
254 | v = [] | |
|
255 | def visit_bindparam(bind): | |
|
256 | value = query._params.get(bind.key, bind.value) | |
|
257 | ||
|
258 | # lazyloader may dig a callable in here, intended | |
|
259 | # to late-evaluate params after autoflush is called. | |
|
260 | # convert to a scalar value. | |
|
261 | if callable(value): | |
|
262 | value = value() | |
|
263 | ||
|
264 | v.append(value) | |
|
265 | if query._criterion is not None: | |
|
266 | visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam}) | |
|
267 | return v |
@@ -0,0 +1,54 b'' | |||
|
1 | ## -*- coding: utf-8 -*- | |
|
2 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
|
3 | <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml"> | |
|
4 | <head> | |
|
5 | <title>${_('Reset You password to hg-app')}</title> | |
|
6 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |
|
7 | <link rel="icon" href="/images/hgicon.png" type="image/png" /> | |
|
8 | <meta name="robots" content="index, nofollow"/> | |
|
9 | ||
|
10 | <!-- stylesheets --> | |
|
11 | <link rel="stylesheet" type="text/css" href="/css/reset.css" /> | |
|
12 | <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" /> | |
|
13 | <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" /> | |
|
14 | ||
|
15 | <!-- scripts --> | |
|
16 | ||
|
17 | </head> | |
|
18 | <body> | |
|
19 | <div id="register"> | |
|
20 | ||
|
21 | <div class="title"> | |
|
22 | <h5>${_('Reset You password to hg-app')}</h5> | |
|
23 | <div class="corner tl"></div> | |
|
24 | <div class="corner tr"></div> | |
|
25 | </div> | |
|
26 | <div class="inner"> | |
|
27 | ${h.form(url('password_reset'))} | |
|
28 | <div class="form"> | |
|
29 | <!-- fields --> | |
|
30 | <div class="fields"> | |
|
31 | ||
|
32 | <div class="field"> | |
|
33 | <div class="label"> | |
|
34 | <label for="email">${_('Email address')}:</label> | |
|
35 | </div> | |
|
36 | <div class="input"> | |
|
37 | ${h.text('email')} | |
|
38 | </div> | |
|
39 | </div> | |
|
40 | ||
|
41 | <div class="buttons"> | |
|
42 | <div class="nohighlight"> | |
|
43 | ${h.submit('send','Reset my password',class_="ui-button ui-widget ui-state-default ui-corner-all")} | |
|
44 | <div class="activation_msg">${_('Your new password will be send to matching email address')}</div> | |
|
45 | </div> | |
|
46 | </div> | |
|
47 | </div> | |
|
48 | </div> | |
|
49 | ${h.end_form()} | |
|
50 | </div> | |
|
51 | </div> | |
|
52 | </body> | |
|
53 | </html> | |
|
54 |
@@ -1,61 +1,69 b'' | |||
|
1 | 1 | ------------------------------------- |
|
2 | 2 | Pylons based replacement for hgwebdir |
|
3 | 3 | ------------------------------------- |
|
4 | 4 | |
|
5 | 5 | Fully customizable, with authentication, permissions. Based on vcs library. |
|
6 | 6 | |
|
7 | 7 | **Overview** |
|
8 | 8 | |
|
9 | 9 | - has it's own middleware to handle mercurial protocol request each request can |
|
10 | 10 | be logged and authenticated + threaded performance unlikely to hgweb |
|
11 | 11 | - full permissions per project read/write/admin access even on mercurial request |
|
12 | 12 | - mako templates let's you cusmotize look and feel of application. |
|
13 | 13 | - diffs annotations and source code all colored by pygments. |
|
14 | - mercurial branch graph and yui-flot powered graphs | |
|
14 | - mercurial branch graph and yui-flot powered graphs with zooming | |
|
15 | 15 | - admin interface for performing user/permission managments as well as repository |
|
16 | 16 | managment. |
|
17 | - full text search of source codes with indexing daemons using whoosh | |
|
18 | (no external search servers required all in one application) | |
|
19 | - async tasks for speed and performance using celery (works without them too) | |
|
17 | 20 | - Additional settings for mercurial web, (hooks editable from admin |
|
18 | 21 | panel !) also manage paths, archive, remote messages |
|
19 | 22 | - backup scripts can do backup of whole app and send it over scp to desired location |
|
20 | 23 | - setup project descriptions and info inside built in db for easy, non |
|
21 | 24 | file-system operations |
|
22 | 25 | - added cache with invalidation on push/repo managment for high performance and |
|
23 | 26 | always upto date data. |
|
24 | 27 | - rss / atom feeds, gravatar support |
|
25 | 28 | - based on pylons 1.0 / sqlalchemy 0.6 |
|
26 | 29 | |
|
27 | 30 | **Incoming** |
|
28 | 31 | |
|
29 | 32 | - code review based on hg-review (when it's stable) |
|
30 | - git support (when vcs can handle it) | |
|
31 | - full text search of source codes with indexing daemons using whoosh | |
|
32 | (no external search servers required all in one application) | |
|
33 | - manage hg ui() per repo, add hooks settings, per repo, and not globally | |
|
34 | - other cools stuff that i can figure out | |
|
33 | - git support (when vcs can handle it - almost there !) | |
|
34 | - commit based wikis | |
|
35 | - in server forks | |
|
36 | - clonning from remote repositories into hg-app | |
|
37 | - other cools stuff that i can figure out (or You can help me figure out) | |
|
35 | 38 | |
|
36 | 39 | .. note:: |
|
37 | 40 | This software is still in beta mode. |
|
38 | 41 | I don't guarantee that it'll work correctly. |
|
39 | 42 | |
|
40 | 43 | |
|
41 | 44 | ------------- |
|
42 | 45 | Installation |
|
43 | 46 | ------------- |
|
44 | 47 | .. note:: |
|
45 | 48 | I recomend to install tip version of vcs while the app is in beta mode. |
|
46 | 49 | |
|
47 | 50 | |
|
48 | 51 | - create new virtualenv and activate it - highly recommend that you use separate |
|
49 | 52 | virtual-env for whole application |
|
50 |
- download hg app from default |
|
|
53 | - download hg app from default branch from bitbucket and run | |
|
51 | 54 | 'python setup.py install' this will install all required dependencies needed |
|
52 | 55 | - run paster setup-app production.ini it should create all needed tables |
|
53 | and an admin account. | |
|
56 | and an admin account make sure You specify correct path to repositories. | |
|
54 | 57 | - remember that the given path for mercurial repositories must be write |
|
55 | 58 | accessible for the application |
|
56 | 59 | - run paster serve development.ini - or you can use manage-hg_app script. |
|
57 | 60 | the app should be available at the 127.0.0.1:5000 |
|
58 | 61 | - use admin account you created to login. |
|
59 | 62 | - default permissions on each repository is read, and owner is admin. So remember |
|
60 | 63 | to update these. |
|
64 | - in order to use full power of async tasks, You must install message broker | |
|
65 | preferrably rabbitmq and start celeryd daemon. The app should gain some speed | |
|
66 | than. For installation instructions | |
|
67 | You can visit: http://ask.github.com/celery/getting-started/index.html. All | |
|
68 | needed configs are inside hg-app ie. celeryconfig.py | |
|
61 | 69 | No newline at end of file |
@@ -1,155 +1,160 b'' | |||
|
1 | 1 | ################################################################################ |
|
2 | 2 | ################################################################################ |
|
3 |
# |
|
|
3 | # hg-app - Pylons environment configuration # | |
|
4 | 4 | # # |
|
5 | 5 | # The %(here)s variable will be replaced with the parent directory of this file# |
|
6 | 6 | ################################################################################ |
|
7 | 7 | |
|
8 | 8 | [DEFAULT] |
|
9 | 9 | debug = true |
|
10 | ############################################ | |
|
11 | ## Uncomment and replace with the address ## | |
|
12 | ## which should receive any error reports ## | |
|
13 | ############################################ | |
|
10 | ################################################################################ | |
|
11 | ## Uncomment and replace with the address which should receive ## | |
|
12 | ## any error reports after application crash ## | |
|
13 | ## Additionally those settings will be used by hg-app mailing system ## | |
|
14 | ################################################################################ | |
|
14 | 15 | #email_to = admin@localhost |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | #app_email_from = hg-app-noreply@localhost | |
|
18 | #error_message = | |
|
19 | ||
|
15 | 20 | #smtp_server = mail.server.com |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | 21 | #smtp_username = |
|
18 |
#smtp_password = |
|
|
19 | #error_message = 'mercurial crash !' | |
|
22 | #smtp_password = | |
|
23 | #smtp_port = | |
|
24 | #smtp_use_tls = | |
|
20 | 25 | |
|
21 | 26 | [server:main] |
|
22 | 27 | ##nr of threads to spawn |
|
23 | 28 | threadpool_workers = 5 |
|
24 | 29 | |
|
25 | 30 | ##max request before |
|
26 |
threadpool_max_requests = |
|
|
31 | threadpool_max_requests = 6 | |
|
27 | 32 | |
|
28 | 33 | ##option to use threads of process |
|
29 |
use_threadpool = |
|
|
34 | use_threadpool = false | |
|
30 | 35 | |
|
31 | 36 | use = egg:Paste#http |
|
32 | 37 | host = 127.0.0.1 |
|
33 | 38 | port = 5000 |
|
34 | 39 | |
|
35 | 40 | [app:main] |
|
36 | 41 | use = egg:pylons_app |
|
37 | 42 | full_stack = true |
|
38 | 43 | static_files = true |
|
39 | 44 | lang=en |
|
40 | 45 | cache_dir = %(here)s/data |
|
41 | 46 | |
|
42 | 47 | #################################### |
|
43 | 48 | ### BEAKER CACHE #### |
|
44 | 49 | #################################### |
|
45 | 50 | beaker.cache.data_dir=/%(here)s/data/cache/data |
|
46 | 51 | beaker.cache.lock_dir=/%(here)s/data/cache/lock |
|
47 | 52 | beaker.cache.regions=super_short_term,short_term,long_term |
|
48 | 53 | beaker.cache.long_term.type=memory |
|
49 | 54 | beaker.cache.long_term.expire=36000 |
|
50 | 55 | beaker.cache.short_term.type=memory |
|
51 | 56 | beaker.cache.short_term.expire=60 |
|
52 | 57 | beaker.cache.super_short_term.type=memory |
|
53 | 58 | beaker.cache.super_short_term.expire=10 |
|
54 | 59 | |
|
55 | 60 | #################################### |
|
56 | 61 | ### BEAKER SESSION #### |
|
57 | 62 | #################################### |
|
58 | 63 | ## Type of storage used for the session, current types are |
|
59 |
## |
|
|
64 | ## "dbm", "file", "memcached", "database", and "memory". | |
|
60 | 65 | ## The storage uses the Container API |
|
61 | 66 | ##that is also used by the cache system. |
|
62 | 67 | beaker.session.type = file |
|
63 | 68 | |
|
64 | 69 | beaker.session.key = hg-app |
|
65 | 70 | beaker.session.secret = g654dcno0-9873jhgfreyu |
|
66 | 71 | beaker.session.timeout = 36000 |
|
67 | 72 | |
|
68 | 73 | ##auto save the session to not to use .save() |
|
69 | 74 | beaker.session.auto = False |
|
70 | 75 | |
|
71 | 76 | ##true exire at browser close |
|
72 | 77 | #beaker.session.cookie_expires = 3600 |
|
73 | 78 | |
|
74 | 79 | |
|
75 | 80 | ################################################################################ |
|
76 | 81 | ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ## |
|
77 | 82 | ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ## |
|
78 | 83 | ## execute malicious code after an exception is raised. ## |
|
79 | 84 | ################################################################################ |
|
80 | 85 | #set debug = false |
|
81 | 86 | |
|
82 | 87 | ################################## |
|
83 | 88 | ### LOGVIEW CONFIG ### |
|
84 | 89 | ################################## |
|
85 | 90 | logview.sqlalchemy = #faa |
|
86 | 91 | logview.pylons.templating = #bfb |
|
87 | 92 | logview.pylons.util = #eee |
|
88 | 93 | |
|
89 | 94 | ######################################################### |
|
90 | 95 | ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ### |
|
91 | 96 | ######################################################### |
|
92 | 97 | sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db |
|
93 | 98 | #sqlalchemy.db1.echo = False |
|
94 | 99 | #sqlalchemy.db1.pool_recycle = 3600 |
|
95 | 100 | sqlalchemy.convert_unicode = true |
|
96 | 101 | |
|
97 | 102 | ################################ |
|
98 | 103 | ### LOGGING CONFIGURATION #### |
|
99 | 104 | ################################ |
|
100 | 105 | [loggers] |
|
101 | 106 | keys = root, routes, pylons_app, sqlalchemy |
|
102 | 107 | |
|
103 | 108 | [handlers] |
|
104 | 109 | keys = console |
|
105 | 110 | |
|
106 | 111 | [formatters] |
|
107 | 112 | keys = generic,color_formatter |
|
108 | 113 | |
|
109 | 114 | ############# |
|
110 | 115 | ## LOGGERS ## |
|
111 | 116 | ############# |
|
112 | 117 | [logger_root] |
|
113 | 118 | level = NOTSET |
|
114 | 119 | handlers = console |
|
115 | 120 | |
|
116 | 121 | [logger_routes] |
|
117 | 122 | level = DEBUG |
|
118 | 123 | handlers = console |
|
119 | 124 | qualname = routes.middleware |
|
120 | 125 | # "level = DEBUG" logs the route matched and routing variables. |
|
121 | 126 | |
|
122 | 127 | [logger_pylons_app] |
|
123 | 128 | level = DEBUG |
|
124 | 129 | handlers = console |
|
125 | 130 | qualname = pylons_app |
|
126 | 131 | propagate = 0 |
|
127 | 132 | |
|
128 | 133 | [logger_sqlalchemy] |
|
129 | 134 | level = ERROR |
|
130 | 135 | handlers = console |
|
131 | 136 | qualname = sqlalchemy.engine |
|
132 | 137 | propagate = 0 |
|
133 | 138 | |
|
134 | 139 | ############## |
|
135 | 140 | ## HANDLERS ## |
|
136 | 141 | ############## |
|
137 | 142 | |
|
138 | 143 | [handler_console] |
|
139 | 144 | class = StreamHandler |
|
140 | 145 | args = (sys.stderr,) |
|
141 | 146 | level = NOTSET |
|
142 | 147 | formatter = color_formatter |
|
143 | 148 | |
|
144 | 149 | ################ |
|
145 | 150 | ## FORMATTERS ## |
|
146 | 151 | ################ |
|
147 | 152 | |
|
148 | 153 | [formatter_generic] |
|
149 | 154 | format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
150 | 155 | datefmt = %Y-%m-%d %H:%M:%S |
|
151 | 156 | |
|
152 | 157 | [formatter_color_formatter] |
|
153 | 158 | class=pylons_app.lib.colored_formatter.ColorFormatter |
|
154 | 159 | format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
155 | 160 | datefmt = %Y-%m-%d %H:%M:%S No newline at end of file |
@@ -1,155 +1,160 b'' | |||
|
1 | 1 | ################################################################################ |
|
2 | 2 | ################################################################################ |
|
3 |
# |
|
|
3 | # hg-app - Pylons environment configuration # | |
|
4 | 4 | # # |
|
5 | 5 | # The %(here)s variable will be replaced with the parent directory of this file# |
|
6 | 6 | ################################################################################ |
|
7 | 7 | |
|
8 | 8 | [DEFAULT] |
|
9 | 9 | debug = true |
|
10 | ############################################ | |
|
11 | ## Uncomment and replace with the address ## | |
|
12 | ## which should receive any error reports ## | |
|
13 | ############################################ | |
|
10 | ################################################################################ | |
|
11 | ## Uncomment and replace with the address which should receive ## | |
|
12 | ## any error reports after application crash ## | |
|
13 | ## Additionally those settings will be used by hg-app mailing system ## | |
|
14 | ################################################################################ | |
|
14 | 15 | #email_to = admin@localhost |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | #app_email_from = hg-app-noreply@localhost | |
|
18 | #error_message = | |
|
19 | ||
|
15 | 20 | #smtp_server = mail.server.com |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | 21 | #smtp_username = |
|
18 | 22 | #smtp_password = |
|
19 | #error_message = 'mercurial crash !' | |
|
23 | #smtp_port = | |
|
24 | #smtp_use_tls = false | |
|
20 | 25 | |
|
21 | 26 | [server:main] |
|
22 | 27 | ##nr of threads to spawn |
|
23 | 28 | threadpool_workers = 5 |
|
24 | 29 | |
|
25 | ##max request before | |
|
30 | ##max request before thread respawn | |
|
26 | 31 | threadpool_max_requests = 2 |
|
27 | 32 | |
|
28 | 33 | ##option to use threads of process |
|
29 | 34 | use_threadpool = true |
|
30 | 35 | |
|
31 | 36 | use = egg:Paste#http |
|
32 | 37 | host = 127.0.0.1 |
|
33 | 38 | port = 8001 |
|
34 | 39 | |
|
35 | 40 | [app:main] |
|
36 | 41 | use = egg:pylons_app |
|
37 | 42 | full_stack = true |
|
38 | 43 | static_files = false |
|
39 | 44 | lang=en |
|
40 | 45 | cache_dir = %(here)s/data |
|
41 | 46 | |
|
42 | 47 | #################################### |
|
43 | 48 | ### BEAKER CACHE #### |
|
44 | 49 | #################################### |
|
45 | 50 | beaker.cache.data_dir=/%(here)s/data/cache/data |
|
46 | 51 | beaker.cache.lock_dir=/%(here)s/data/cache/lock |
|
47 | 52 | beaker.cache.regions=super_short_term,short_term,long_term |
|
48 | 53 | beaker.cache.long_term.type=memory |
|
49 | 54 | beaker.cache.long_term.expire=36000 |
|
50 | 55 | beaker.cache.short_term.type=memory |
|
51 | 56 | beaker.cache.short_term.expire=60 |
|
52 | 57 | beaker.cache.super_short_term.type=memory |
|
53 | 58 | beaker.cache.super_short_term.expire=10 |
|
54 | 59 | |
|
55 | 60 | #################################### |
|
56 | 61 | ### BEAKER SESSION #### |
|
57 | 62 | #################################### |
|
58 | 63 | ## Type of storage used for the session, current types are |
|
59 | 64 | ## dbm, file, memcached, database, and memory. |
|
60 | 65 | ## The storage uses the Container API |
|
61 | 66 | ##that is also used by the cache system. |
|
62 | 67 | beaker.session.type = file |
|
63 | 68 | |
|
64 | 69 | beaker.session.key = hg-app |
|
65 | 70 | beaker.session.secret = g654dcno0-9873jhgfreyu |
|
66 | 71 | beaker.session.timeout = 36000 |
|
67 | 72 | |
|
68 | 73 | ##auto save the session to not to use .save() |
|
69 | 74 | beaker.session.auto = False |
|
70 | 75 | |
|
71 | 76 | ##true exire at browser close |
|
72 | 77 | #beaker.session.cookie_expires = 3600 |
|
73 | 78 | |
|
74 | 79 | |
|
75 | 80 | ################################################################################ |
|
76 | 81 | ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ## |
|
77 | 82 | ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ## |
|
78 | 83 | ## execute malicious code after an exception is raised. ## |
|
79 | 84 | ################################################################################ |
|
80 | 85 | set debug = false |
|
81 | 86 | |
|
82 | 87 | ################################## |
|
83 | 88 | ### LOGVIEW CONFIG ### |
|
84 | 89 | ################################## |
|
85 | 90 | logview.sqlalchemy = #faa |
|
86 | 91 | logview.pylons.templating = #bfb |
|
87 | 92 | logview.pylons.util = #eee |
|
88 | 93 | |
|
89 | 94 | ######################################################### |
|
90 | 95 | ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ### |
|
91 | 96 | ######################################################### |
|
92 | 97 | sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db |
|
93 | 98 | #sqlalchemy.db1.echo = False |
|
94 | 99 | #sqlalchemy.db1.pool_recycle = 3600 |
|
95 | 100 | sqlalchemy.convert_unicode = true |
|
96 | 101 | |
|
97 | 102 | ################################ |
|
98 | 103 | ### LOGGING CONFIGURATION #### |
|
99 | 104 | ################################ |
|
100 | 105 | [loggers] |
|
101 | 106 | keys = root, routes, pylons_app, sqlalchemy |
|
102 | 107 | |
|
103 | 108 | [handlers] |
|
104 | 109 | keys = console |
|
105 | 110 | |
|
106 | 111 | [formatters] |
|
107 | 112 | keys = generic,color_formatter |
|
108 | 113 | |
|
109 | 114 | ############# |
|
110 | 115 | ## LOGGERS ## |
|
111 | 116 | ############# |
|
112 | 117 | [logger_root] |
|
113 | 118 | level = INFO |
|
114 | 119 | handlers = console |
|
115 | 120 | |
|
116 | 121 | [logger_routes] |
|
117 | 122 | level = INFO |
|
118 | 123 | handlers = console |
|
119 | 124 | qualname = routes.middleware |
|
120 | 125 | # "level = DEBUG" logs the route matched and routing variables. |
|
121 | 126 | |
|
122 | 127 | [logger_pylons_app] |
|
123 | 128 | level = DEBUG |
|
124 | 129 | handlers = console |
|
125 | 130 | qualname = pylons_app |
|
126 | 131 | propagate = 0 |
|
127 | 132 | |
|
128 | 133 | [logger_sqlalchemy] |
|
129 | 134 | level = ERROR |
|
130 | 135 | handlers = console |
|
131 | 136 | qualname = sqlalchemy.engine |
|
132 | 137 | propagate = 0 |
|
133 | 138 | |
|
134 | 139 | ############## |
|
135 | 140 | ## HANDLERS ## |
|
136 | 141 | ############## |
|
137 | 142 | |
|
138 | 143 | [handler_console] |
|
139 | 144 | class = StreamHandler |
|
140 | 145 | args = (sys.stderr,) |
|
141 | 146 | level = NOTSET |
|
142 | 147 | formatter = color_formatter |
|
143 | 148 | |
|
144 | 149 | ################ |
|
145 | 150 | ## FORMATTERS ## |
|
146 | 151 | ################ |
|
147 | 152 | |
|
148 | 153 | [formatter_generic] |
|
149 | 154 | format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
150 | 155 | datefmt = %Y-%m-%d %H:%M:%S |
|
151 | 156 | |
|
152 | 157 | [formatter_color_formatter] |
|
153 | 158 | class=pylons_app.lib.colored_formatter.ColorFormatter |
|
154 | 159 | format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
155 | 160 | datefmt = %Y-%m-%d %H:%M:%S No newline at end of file |
@@ -1,34 +1,35 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # Hg app, a web based mercurial repository managment based on pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on April 9, 2010 |
|
22 | 22 | Hg app, a web based mercurial repository managment based on pylons |
|
23 | versioning implementation: http://semver.org/ | |
|
23 | 24 | @author: marcink |
|
24 | 25 | """ |
|
25 | 26 | |
|
26 |
VERSION = (0, 8, |
|
|
27 | VERSION = (0, 8, 3, 'beta') | |
|
27 | 28 | |
|
28 | 29 | __version__ = '.'.join((str(each) for each in VERSION[:4])) |
|
29 | 30 | |
|
30 | 31 | def get_version(): |
|
31 | 32 | """ |
|
32 | 33 | Returns shorter version (digit parts only) as string. |
|
33 | 34 | """ |
|
34 | 35 | return '.'.join((str(each) for each in VERSION[:3])) |
@@ -1,156 +1,161 b'' | |||
|
1 | 1 | ################################################################################ |
|
2 | 2 | ################################################################################ |
|
3 | 3 | # hg-app - Pylons environment configuration # |
|
4 | 4 | # # |
|
5 | 5 | # The %(here)s variable will be replaced with the parent directory of this file# |
|
6 | 6 | ################################################################################ |
|
7 | 7 | |
|
8 | 8 | [DEFAULT] |
|
9 | 9 | debug = true |
|
10 | ############################################ | |
|
11 | ## Uncomment and replace with the address ## | |
|
12 | ## which should receive any error reports ## | |
|
13 | ############################################ | |
|
10 | ################################################################################ | |
|
11 | ## Uncomment and replace with the address which should receive ## | |
|
12 | ## any error reports after application crash ## | |
|
13 | ## Additionally those settings will be used by hg-app mailing system ## | |
|
14 | ################################################################################ | |
|
14 | 15 | #email_to = admin@localhost |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | #app_email_from = hg-app-noreply@localhost | |
|
18 | #error_message = | |
|
19 | ||
|
15 | 20 | #smtp_server = mail.server.com |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | 21 | #smtp_username = |
|
18 | 22 | #smtp_password = |
|
19 | #error_message = 'hp-app crash !' | |
|
23 | #smtp_port = | |
|
24 | #smtp_use_tls = false | |
|
20 | 25 | |
|
21 | 26 | [server:main] |
|
22 | 27 | ##nr of threads to spawn |
|
23 | 28 | threadpool_workers = 5 |
|
24 | 29 | |
|
25 | 30 | ##max request before thread respawn |
|
26 | 31 | threadpool_max_requests = 2 |
|
27 | 32 | |
|
28 | 33 | ##option to use threads of process |
|
29 | 34 | use_threadpool = true |
|
30 | 35 | |
|
31 | 36 | use = egg:Paste#http |
|
32 | 37 | host = 127.0.0.1 |
|
33 | 38 | port = 8001 |
|
34 | 39 | |
|
35 | 40 | [app:main] |
|
36 | 41 | use = egg:pylons_app |
|
37 | 42 | full_stack = true |
|
38 | 43 | static_files = false |
|
39 | 44 | lang=en |
|
40 | 45 | cache_dir = %(here)s/data |
|
41 | 46 | app_instance_uuid = ${app_instance_uuid} |
|
42 | 47 | |
|
43 | 48 | #################################### |
|
44 | 49 | ### BEAKER CACHE #### |
|
45 | 50 | #################################### |
|
46 | 51 | beaker.cache.data_dir=/%(here)s/data/cache/data |
|
47 | 52 | beaker.cache.lock_dir=/%(here)s/data/cache/lock |
|
48 | 53 | beaker.cache.regions=super_short_term,short_term,long_term |
|
49 | 54 | beaker.cache.long_term.type=memory |
|
50 | 55 | beaker.cache.long_term.expire=36000 |
|
51 | 56 | beaker.cache.short_term.type=memory |
|
52 | 57 | beaker.cache.short_term.expire=60 |
|
53 | 58 | beaker.cache.super_short_term.type=memory |
|
54 | 59 | beaker.cache.super_short_term.expire=10 |
|
55 | 60 | |
|
56 | 61 | #################################### |
|
57 | 62 | ### BEAKER SESSION #### |
|
58 | 63 | #################################### |
|
59 | 64 | ## Type of storage used for the session, current types are |
|
60 | 65 | ## dbm, file, memcached, database, and memory. |
|
61 | 66 | ## The storage uses the Container API |
|
62 | 67 | ##that is also used by the cache system. |
|
63 | 68 | beaker.session.type = file |
|
64 | 69 | |
|
65 | 70 | beaker.session.key = hg-app |
|
66 | 71 | beaker.session.secret = ${app_instance_secret} |
|
67 | 72 | beaker.session.timeout = 36000 |
|
68 | 73 | |
|
69 | 74 | ##auto save the session to not to use .save() |
|
70 | 75 | beaker.session.auto = False |
|
71 | 76 | |
|
72 | 77 | ##true exire at browser close |
|
73 | 78 | #beaker.session.cookie_expires = 3600 |
|
74 | 79 | |
|
75 | 80 | |
|
76 | 81 | ################################################################################ |
|
77 | 82 | ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ## |
|
78 | 83 | ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ## |
|
79 | 84 | ## execute malicious code after an exception is raised. ## |
|
80 | 85 | ################################################################################ |
|
81 | 86 | set debug = false |
|
82 | 87 | |
|
83 | 88 | ################################## |
|
84 | 89 | ### LOGVIEW CONFIG ### |
|
85 | 90 | ################################## |
|
86 | 91 | logview.sqlalchemy = #faa |
|
87 | 92 | logview.pylons.templating = #bfb |
|
88 | 93 | logview.pylons.util = #eee |
|
89 | 94 | |
|
90 | 95 | ######################################################### |
|
91 | 96 | ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ### |
|
92 | 97 | ######################################################### |
|
93 | 98 | sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db |
|
94 | 99 | #sqlalchemy.db1.echo = False |
|
95 | 100 | #sqlalchemy.db1.pool_recycle = 3600 |
|
96 | 101 | sqlalchemy.convert_unicode = true |
|
97 | 102 | |
|
98 | 103 | ################################ |
|
99 | 104 | ### LOGGING CONFIGURATION #### |
|
100 | 105 | ################################ |
|
101 | 106 | [loggers] |
|
102 | 107 | keys = root, routes, pylons_app, sqlalchemy |
|
103 | 108 | |
|
104 | 109 | [handlers] |
|
105 | 110 | keys = console |
|
106 | 111 | |
|
107 | 112 | [formatters] |
|
108 | 113 | keys = generic,color_formatter |
|
109 | 114 | |
|
110 | 115 | ############# |
|
111 | 116 | ## LOGGERS ## |
|
112 | 117 | ############# |
|
113 | 118 | [logger_root] |
|
114 | 119 | level = INFO |
|
115 | 120 | handlers = console |
|
116 | 121 | |
|
117 | 122 | [logger_routes] |
|
118 | 123 | level = INFO |
|
119 | 124 | handlers = console |
|
120 | 125 | qualname = routes.middleware |
|
121 | 126 | # "level = DEBUG" logs the route matched and routing variables. |
|
122 | 127 | |
|
123 | 128 | [logger_pylons_app] |
|
124 | 129 | level = DEBUG |
|
125 | 130 | handlers = console |
|
126 | 131 | qualname = pylons_app |
|
127 | 132 | propagate = 0 |
|
128 | 133 | |
|
129 | 134 | [logger_sqlalchemy] |
|
130 | 135 | level = ERROR |
|
131 | 136 | handlers = console |
|
132 | 137 | qualname = sqlalchemy.engine |
|
133 | 138 | propagate = 0 |
|
134 | 139 | |
|
135 | 140 | ############## |
|
136 | 141 | ## HANDLERS ## |
|
137 | 142 | ############## |
|
138 | 143 | |
|
139 | 144 | [handler_console] |
|
140 | 145 | class = StreamHandler |
|
141 | 146 | args = (sys.stderr,) |
|
142 | 147 | level = NOTSET |
|
143 | 148 | formatter = color_formatter |
|
144 | 149 | |
|
145 | 150 | ################ |
|
146 | 151 | ## FORMATTERS ## |
|
147 | 152 | ################ |
|
148 | 153 | |
|
149 | 154 | [formatter_generic] |
|
150 | 155 | format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
151 | 156 | datefmt = %Y-%m-%d %H:%M:%S |
|
152 | 157 | |
|
153 | 158 | [formatter_color_formatter] |
|
154 | 159 | class=pylons_app.lib.colored_formatter.ColorFormatter |
|
155 | 160 | format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
156 | 161 | datefmt = %Y-%m-%d %H:%M:%S No newline at end of file |
@@ -1,74 +1,79 b'' | |||
|
1 | 1 | """Pylons environment configuration""" |
|
2 | 2 | from mako.lookup import TemplateLookup |
|
3 | 3 | from pylons.configuration import PylonsConfig |
|
4 | 4 | from pylons.error import handle_mako_error |
|
5 | 5 | from pylons_app.config.routing import make_map |
|
6 | 6 | from pylons_app.lib.auth import set_available_permissions, set_base_path |
|
7 | 7 | from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config |
|
8 | 8 | from pylons_app.model import init_model |
|
9 | 9 | from pylons_app.model.hg_model import _get_repos_cached_initial |
|
10 | 10 | from sqlalchemy import engine_from_config |
|
11 | 11 | import logging |
|
12 | 12 | import os |
|
13 | 13 | import pylons_app.lib.app_globals as app_globals |
|
14 | 14 | import pylons_app.lib.helpers |
|
15 | 15 | |
|
16 | 16 | log = logging.getLogger(__name__) |
|
17 | 17 | |
|
18 | 18 | def load_environment(global_conf, app_conf, initial=False): |
|
19 | 19 | """Configure the Pylons environment via the ``pylons.config`` |
|
20 | 20 | object |
|
21 | 21 | """ |
|
22 | 22 | config = PylonsConfig() |
|
23 | 23 | |
|
24 | 24 | # Pylons paths |
|
25 | 25 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
|
26 | 26 | paths = dict(root=root, |
|
27 | 27 | controllers=os.path.join(root, 'controllers'), |
|
28 | 28 | static_files=os.path.join(root, 'public'), |
|
29 | 29 | templates=[os.path.join(root, 'templates')]) |
|
30 | 30 | |
|
31 | 31 | # Initialize config with the basic options |
|
32 | 32 | config.init_app(global_conf, app_conf, package='pylons_app', paths=paths) |
|
33 | 33 | |
|
34 | 34 | config['routes.map'] = make_map(config) |
|
35 | 35 | config['pylons.app_globals'] = app_globals.Globals(config) |
|
36 | 36 | config['pylons.h'] = pylons_app.lib.helpers |
|
37 | 37 | |
|
38 | 38 | # Setup cache object as early as possible |
|
39 | 39 | import pylons |
|
40 | 40 | pylons.cache._push_object(config['pylons.app_globals'].cache) |
|
41 | 41 | |
|
42 | 42 | # Create the Mako TemplateLookup, with the default auto-escaping |
|
43 | 43 | config['pylons.app_globals'].mako_lookup = TemplateLookup( |
|
44 | 44 | directories=paths['templates'], |
|
45 | 45 | error_handler=handle_mako_error, |
|
46 | 46 | module_directory=os.path.join(app_conf['cache_dir'], 'templates'), |
|
47 | 47 | input_encoding='utf-8', default_filters=['escape'], |
|
48 | 48 | imports=['from webhelpers.html import escape']) |
|
49 | 49 | |
|
50 | 50 | #sets the c attribute access when don't existing attribute are accessed |
|
51 | 51 | config['pylons.strict_tmpl_context'] = True |
|
52 |
test = os.path.split(config['__file__'])[-1] == 'test |
|
|
52 | test = os.path.split(config['__file__'])[-1] == 'test.ini' | |
|
53 | if test: | |
|
54 | from pylons_app.lib.utils import create_test_env, create_test_index | |
|
55 | create_test_env('/tmp', config) | |
|
56 | create_test_index('/tmp/*', True) | |
|
57 | ||
|
53 | 58 | #MULTIPLE DB configs |
|
54 | 59 | # Setup the SQLAlchemy database engine |
|
55 | 60 | if config['debug'] and not test: |
|
56 | 61 | #use query time debugging. |
|
57 | 62 | from pylons_app.lib.timerproxy import TimerProxy |
|
58 | 63 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.', |
|
59 | 64 | proxy=TimerProxy()) |
|
60 | 65 | else: |
|
61 | 66 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') |
|
62 | 67 | |
|
63 | 68 | init_model(sa_engine_db1) |
|
64 | 69 | #init baseui |
|
65 | 70 | config['pylons.app_globals'].baseui = make_ui('db') |
|
66 | 71 | |
|
67 | 72 | repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial)) |
|
68 | 73 | set_available_permissions(config) |
|
69 | 74 | set_base_path(config) |
|
70 | 75 | set_hg_app_config(config) |
|
71 | 76 | # CONFIGURATION OPTIONS HERE (note: all config options will override |
|
72 | 77 | # any Pylons config options) |
|
73 | 78 | |
|
74 | 79 | return config |
@@ -1,167 +1,171 b'' | |||
|
1 | 1 | """Routes configuration |
|
2 | 2 | |
|
3 | 3 | The more specific and detailed routes should be defined first so they |
|
4 | 4 | may take precedent over the more generic routes. For more information |
|
5 | 5 | refer to the routes manual at http://routes.groovie.org/docs/ |
|
6 | 6 | """ |
|
7 | 7 | from __future__ import with_statement |
|
8 | 8 | from routes import Mapper |
|
9 | 9 | from pylons_app.lib.utils import check_repo_fast as cr |
|
10 | 10 | |
|
11 | 11 | def make_map(config): |
|
12 | 12 | """Create, configure and return the routes Mapper""" |
|
13 | 13 | map = Mapper(directory=config['pylons.paths']['controllers'], |
|
14 | 14 | always_scan=config['debug']) |
|
15 | 15 | map.minimization = False |
|
16 | 16 | map.explicit = False |
|
17 | 17 | |
|
18 | 18 | # The ErrorController route (handles 404/500 error pages); it should |
|
19 | 19 | # likely stay at the top, ensuring it can always be resolved |
|
20 | 20 | map.connect('/error/{action}', controller='error') |
|
21 | 21 | map.connect('/error/{action}/{id}', controller='error') |
|
22 | 22 | |
|
23 | 23 | # CUSTOM ROUTES HERE |
|
24 | 24 | map.connect('hg_home', '/', controller='hg', action='index') |
|
25 | 25 | |
|
26 | 26 | def check_repo(environ, match_dict): |
|
27 | 27 | """ |
|
28 | 28 | check for valid repository for proper 404 handling |
|
29 | 29 | @param environ: |
|
30 | 30 | @param match_dict: |
|
31 | 31 | """ |
|
32 | 32 | repo_name = match_dict.get('repo_name') |
|
33 | 33 | return not cr(repo_name, config['base_path']) |
|
34 | 34 | |
|
35 | 35 | #REST REPO MAP |
|
36 | 36 | with map.submapper(path_prefix='/_admin', controller='admin/repos') as m: |
|
37 | 37 | m.connect("repos", "/repos", |
|
38 | 38 | action="create", conditions=dict(method=["POST"])) |
|
39 | 39 | m.connect("repos", "/repos", |
|
40 | 40 | action="index", conditions=dict(method=["GET"])) |
|
41 | 41 | m.connect("formatted_repos", "/repos.{format}", |
|
42 | 42 | action="index", |
|
43 | 43 | conditions=dict(method=["GET"])) |
|
44 | 44 | m.connect("new_repo", "/repos/new", |
|
45 | 45 | action="new", conditions=dict(method=["GET"])) |
|
46 | 46 | m.connect("formatted_new_repo", "/repos/new.{format}", |
|
47 | 47 | action="new", conditions=dict(method=["GET"])) |
|
48 | 48 | m.connect("/repos/{repo_name:.*}", |
|
49 | 49 | action="update", conditions=dict(method=["PUT"], |
|
50 | 50 | function=check_repo)) |
|
51 | 51 | m.connect("/repos/{repo_name:.*}", |
|
52 | 52 | action="delete", conditions=dict(method=["DELETE"], |
|
53 | 53 | function=check_repo)) |
|
54 | 54 | m.connect("edit_repo", "/repos/{repo_name:.*}/edit", |
|
55 | 55 | action="edit", conditions=dict(method=["GET"], |
|
56 | 56 | function=check_repo)) |
|
57 | 57 | m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit", |
|
58 | 58 | action="edit", conditions=dict(method=["GET"], |
|
59 | 59 | function=check_repo)) |
|
60 | 60 | m.connect("repo", "/repos/{repo_name:.*}", |
|
61 | 61 | action="show", conditions=dict(method=["GET"], |
|
62 | 62 | function=check_repo)) |
|
63 | 63 | m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}", |
|
64 | 64 | action="show", conditions=dict(method=["GET"], |
|
65 | 65 | function=check_repo)) |
|
66 | 66 | #ajax delete repo perm user |
|
67 | 67 | m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}", |
|
68 | 68 | action="delete_perm_user", conditions=dict(method=["DELETE"], |
|
69 | 69 | function=check_repo)) |
|
70 | 70 | |
|
71 | 71 | map.resource('user', 'users', controller='admin/users', path_prefix='/_admin') |
|
72 | 72 | map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin') |
|
73 | 73 | |
|
74 | 74 | #REST SETTINGS MAP |
|
75 | 75 | with map.submapper(path_prefix='/_admin', controller='admin/settings') as m: |
|
76 | 76 | m.connect("admin_settings", "/settings", |
|
77 | 77 | action="create", conditions=dict(method=["POST"])) |
|
78 | 78 | m.connect("admin_settings", "/settings", |
|
79 | 79 | action="index", conditions=dict(method=["GET"])) |
|
80 | 80 | m.connect("formatted_admin_settings", "/settings.{format}", |
|
81 | 81 | action="index", conditions=dict(method=["GET"])) |
|
82 | 82 | m.connect("admin_new_setting", "/settings/new", |
|
83 | 83 | action="new", conditions=dict(method=["GET"])) |
|
84 | 84 | m.connect("formatted_admin_new_setting", "/settings/new.{format}", |
|
85 | 85 | action="new", conditions=dict(method=["GET"])) |
|
86 | 86 | m.connect("/settings/{setting_id}", |
|
87 | 87 | action="update", conditions=dict(method=["PUT"])) |
|
88 | 88 | m.connect("/settings/{setting_id}", |
|
89 | 89 | action="delete", conditions=dict(method=["DELETE"])) |
|
90 | 90 | m.connect("admin_edit_setting", "/settings/{setting_id}/edit", |
|
91 | 91 | action="edit", conditions=dict(method=["GET"])) |
|
92 | 92 | m.connect("formatted_admin_edit_setting", "/settings/{setting_id}.{format}/edit", |
|
93 | 93 | action="edit", conditions=dict(method=["GET"])) |
|
94 | 94 | m.connect("admin_setting", "/settings/{setting_id}", |
|
95 | 95 | action="show", conditions=dict(method=["GET"])) |
|
96 | 96 | m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}", |
|
97 | 97 | action="show", conditions=dict(method=["GET"])) |
|
98 | 98 | m.connect("admin_settings_my_account", "/my_account", |
|
99 | 99 | action="my_account", conditions=dict(method=["GET"])) |
|
100 | 100 | m.connect("admin_settings_my_account_update", "/my_account_update", |
|
101 | 101 | action="my_account_update", conditions=dict(method=["PUT"])) |
|
102 | 102 | m.connect("admin_settings_create_repository", "/create_repository", |
|
103 | 103 | action="create_repository", conditions=dict(method=["GET"])) |
|
104 | 104 | |
|
105 | 105 | #ADMIN |
|
106 | 106 | with map.submapper(path_prefix='/_admin', controller='admin/admin') as m: |
|
107 | 107 | m.connect('admin_home', '', action='index')#main page |
|
108 | 108 | m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}', |
|
109 | 109 | action='add_repo') |
|
110 | 110 | #SEARCH |
|
111 | 111 | map.connect('search', '/_admin/search', controller='search') |
|
112 | 112 | |
|
113 | #LOGIN/LOGOUT | |
|
113 | #LOGIN/LOGOUT/REGISTER/SIGN IN | |
|
114 | 114 | map.connect('login_home', '/_admin/login', controller='login') |
|
115 | 115 | map.connect('logout_home', '/_admin/logout', controller='login', action='logout') |
|
116 | 116 | map.connect('register', '/_admin/register', controller='login', action='register') |
|
117 | map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset') | |
|
117 | 118 | |
|
118 | 119 | #FEEDS |
|
119 | 120 | map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss', |
|
120 | 121 | controller='feed', action='rss', |
|
121 | 122 | conditions=dict(function=check_repo)) |
|
122 | 123 | map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom', |
|
123 | 124 | controller='feed', action='atom', |
|
124 | 125 | conditions=dict(function=check_repo)) |
|
125 | 126 | |
|
126 | 127 | |
|
127 | 128 | #OTHERS |
|
128 | 129 | map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}', |
|
129 | 130 | controller='changeset', revision='tip', |
|
130 | 131 | conditions=dict(function=check_repo)) |
|
131 | 132 | map.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}', |
|
132 | controller='changeset',action='raw_changeset', revision='tip', | |
|
133 | controller='changeset', action='raw_changeset', revision='tip', | |
|
133 | 134 | conditions=dict(function=check_repo)) |
|
134 | 135 | map.connect('summary_home', '/{repo_name:.*}/summary', |
|
135 | 136 | controller='summary', conditions=dict(function=check_repo)) |
|
136 | 137 | map.connect('shortlog_home', '/{repo_name:.*}/shortlog', |
|
137 | 138 | controller='shortlog', conditions=dict(function=check_repo)) |
|
138 | 139 | map.connect('branches_home', '/{repo_name:.*}/branches', |
|
139 | 140 | controller='branches', conditions=dict(function=check_repo)) |
|
140 | 141 | map.connect('tags_home', '/{repo_name:.*}/tags', |
|
141 | 142 | controller='tags', conditions=dict(function=check_repo)) |
|
142 | 143 | map.connect('changelog_home', '/{repo_name:.*}/changelog', |
|
143 | 144 | controller='changelog', conditions=dict(function=check_repo)) |
|
144 | 145 | map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}', |
|
145 | 146 | controller='files', revision='tip', f_path='', |
|
146 | 147 | conditions=dict(function=check_repo)) |
|
147 | 148 | map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}', |
|
148 | 149 | controller='files', action='diff', revision='tip', f_path='', |
|
149 | 150 | conditions=dict(function=check_repo)) |
|
150 | map.connect('files_raw_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}', | |
|
151 | map.connect('files_rawfile_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}', | |
|
151 | 152 | controller='files', action='rawfile', revision='tip', f_path='', |
|
152 | 153 | conditions=dict(function=check_repo)) |
|
154 | map.connect('files_raw_home', '/{repo_name:.*}/raw/{revision}/{f_path:.*}', | |
|
155 | controller='files', action='raw', revision='tip', f_path='', | |
|
156 | conditions=dict(function=check_repo)) | |
|
153 | 157 | map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}', |
|
154 | 158 | controller='files', action='annotate', revision='tip', f_path='', |
|
155 | 159 | conditions=dict(function=check_repo)) |
|
156 | 160 | map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}', |
|
157 | 161 | controller='files', action='archivefile', revision='tip', |
|
158 | 162 | conditions=dict(function=check_repo)) |
|
159 | 163 | map.connect('repo_settings_update', '/{repo_name:.*}/settings', |
|
160 | 164 | controller='settings', action="update", |
|
161 | 165 | conditions=dict(method=["PUT"], function=check_repo)) |
|
162 | 166 | map.connect('repo_settings_home', '/{repo_name:.*}/settings', |
|
163 | 167 | controller='settings', action='index', |
|
164 | 168 | conditions=dict(function=check_repo)) |
|
165 | 169 | |
|
166 | 170 | |
|
167 | 171 | return map |
@@ -1,286 +1,298 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # settings controller for pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on July 14, 2010 |
|
22 | 22 | settings controller for pylons |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | from formencode import htmlfill |
|
26 | 26 | from pylons import request, session, tmpl_context as c, url, app_globals as g, \ |
|
27 | 27 | config |
|
28 | 28 | from pylons.controllers.util import abort, redirect |
|
29 | 29 | from pylons.i18n.translation import _ |
|
30 | 30 | from pylons_app.lib import helpers as h |
|
31 | 31 | from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \ |
|
32 | 32 | HasPermissionAnyDecorator |
|
33 | 33 | from pylons_app.lib.base import BaseController, render |
|
34 | 34 | from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \ |
|
35 | 35 | set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui |
|
36 | 36 | from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi |
|
37 | 37 | from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \ |
|
38 | 38 | ApplicationUiSettingsForm |
|
39 | 39 | from pylons_app.model.hg_model import HgModel |
|
40 | 40 | from pylons_app.model.user_model import UserModel |
|
41 | from pylons_app.lib.celerylib import tasks, run_task | |
|
41 | 42 | import formencode |
|
42 | 43 | import logging |
|
43 | 44 | import traceback |
|
44 | 45 | |
|
45 | 46 | log = logging.getLogger(__name__) |
|
46 | 47 | |
|
47 | 48 | |
|
48 | 49 | class SettingsController(BaseController): |
|
49 | 50 | """REST Controller styled on the Atom Publishing Protocol""" |
|
50 | 51 | # To properly map this controller, ensure your config/routing.py |
|
51 | 52 | # file has a resource setup: |
|
52 | 53 | # map.resource('setting', 'settings', controller='admin/settings', |
|
53 | 54 | # path_prefix='/admin', name_prefix='admin_') |
|
54 | 55 | |
|
55 | 56 | |
|
56 | 57 | @LoginRequired() |
|
57 | 58 | def __before__(self): |
|
58 | 59 | c.admin_user = session.get('admin_user') |
|
59 | 60 | c.admin_username = session.get('admin_username') |
|
60 | 61 | super(SettingsController, self).__before__() |
|
61 | 62 | |
|
62 | 63 | |
|
63 | 64 | @HasPermissionAllDecorator('hg.admin') |
|
64 | 65 | def index(self, format='html'): |
|
65 | 66 | """GET /admin/settings: All items in the collection""" |
|
66 | 67 | # url('admin_settings') |
|
67 | 68 | |
|
68 | 69 | defaults = get_hg_settings() |
|
69 | 70 | defaults.update(get_hg_ui_settings()) |
|
70 | 71 | return htmlfill.render( |
|
71 | 72 | render('admin/settings/settings.html'), |
|
72 | 73 | defaults=defaults, |
|
73 | 74 | encoding="UTF-8", |
|
74 | 75 | force_defaults=False |
|
75 | 76 | ) |
|
76 | 77 | |
|
77 | 78 | @HasPermissionAllDecorator('hg.admin') |
|
78 | 79 | def create(self): |
|
79 | 80 | """POST /admin/settings: Create a new item""" |
|
80 | 81 | # url('admin_settings') |
|
81 | 82 | |
|
82 | 83 | @HasPermissionAllDecorator('hg.admin') |
|
83 | 84 | def new(self, format='html'): |
|
84 | 85 | """GET /admin/settings/new: Form to create a new item""" |
|
85 | 86 | # url('admin_new_setting') |
|
86 | 87 | |
|
87 | 88 | @HasPermissionAllDecorator('hg.admin') |
|
88 | 89 | def update(self, setting_id): |
|
89 | 90 | """PUT /admin/settings/setting_id: Update an existing item""" |
|
90 | 91 | # Forms posted to this method should contain a hidden field: |
|
91 | 92 | # <input type="hidden" name="_method" value="PUT" /> |
|
92 | 93 | # Or using helpers: |
|
93 | 94 | # h.form(url('admin_setting', setting_id=ID), |
|
94 | 95 | # method='put') |
|
95 | 96 | # url('admin_setting', setting_id=ID) |
|
96 | 97 | if setting_id == 'mapping': |
|
97 | 98 | rm_obsolete = request.POST.get('destroy', False) |
|
98 | 99 | log.debug('Rescanning directories with destroy=%s', rm_obsolete) |
|
99 | 100 | |
|
100 | 101 | initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) |
|
101 | 102 | repo2db_mapper(initial, rm_obsolete) |
|
102 | 103 | invalidate_cache('cached_repo_list') |
|
103 | 104 | h.flash(_('Repositories sucessfully rescanned'), category='success') |
|
104 | 105 | |
|
106 | if setting_id == 'whoosh': | |
|
107 | repo_location = get_hg_ui_settings()['paths_root_path'] | |
|
108 | full_index = request.POST.get('full_index', False) | |
|
109 | task = run_task(tasks.whoosh_index, repo_location, full_index) | |
|
110 | ||
|
111 | h.flash(_('Whoosh reindex task scheduled'), category='success') | |
|
105 | 112 | if setting_id == 'global': |
|
106 | 113 | |
|
107 | 114 | application_form = ApplicationSettingsForm()() |
|
108 | 115 | try: |
|
109 | 116 | form_result = application_form.to_python(dict(request.POST)) |
|
110 | 117 | |
|
111 | 118 | try: |
|
112 | 119 | hgsettings1 = self.sa.query(HgAppSettings)\ |
|
113 | 120 | .filter(HgAppSettings.app_settings_name == 'title').one() |
|
114 | 121 | hgsettings1.app_settings_value = form_result['hg_app_title'] |
|
115 | 122 | |
|
116 | 123 | hgsettings2 = self.sa.query(HgAppSettings)\ |
|
117 | 124 | .filter(HgAppSettings.app_settings_name == 'realm').one() |
|
118 | 125 | hgsettings2.app_settings_value = form_result['hg_app_realm'] |
|
119 | 126 | |
|
120 | 127 | |
|
121 | 128 | self.sa.add(hgsettings1) |
|
122 | 129 | self.sa.add(hgsettings2) |
|
123 | 130 | self.sa.commit() |
|
124 | 131 | set_hg_app_config(config) |
|
125 | 132 | h.flash(_('Updated application settings'), |
|
126 | 133 | category='success') |
|
127 | 134 | |
|
128 | 135 | except: |
|
129 | 136 | log.error(traceback.format_exc()) |
|
130 | 137 | h.flash(_('error occured during updating application settings'), |
|
131 | 138 | category='error') |
|
132 | 139 | |
|
133 | 140 | self.sa.rollback() |
|
134 | 141 | |
|
135 | 142 | |
|
136 | 143 | except formencode.Invalid as errors: |
|
137 | 144 | return htmlfill.render( |
|
138 | 145 | render('admin/settings/settings.html'), |
|
139 | 146 | defaults=errors.value, |
|
140 | 147 | errors=errors.error_dict or {}, |
|
141 | 148 | prefix_error=False, |
|
142 | 149 | encoding="UTF-8") |
|
143 | 150 | |
|
144 | 151 | if setting_id == 'mercurial': |
|
145 | 152 | application_form = ApplicationUiSettingsForm()() |
|
146 | 153 | try: |
|
147 | 154 | form_result = application_form.to_python(dict(request.POST)) |
|
148 | 155 | |
|
149 | 156 | try: |
|
150 | 157 | |
|
151 | 158 | hgsettings1 = self.sa.query(HgAppUi)\ |
|
152 | 159 | .filter(HgAppUi.ui_key == 'push_ssl').one() |
|
153 | 160 | hgsettings1.ui_value = form_result['web_push_ssl'] |
|
154 | 161 | |
|
155 | 162 | hgsettings2 = self.sa.query(HgAppUi)\ |
|
156 | 163 | .filter(HgAppUi.ui_key == '/').one() |
|
157 | 164 | hgsettings2.ui_value = form_result['paths_root_path'] |
|
158 | 165 | |
|
159 | 166 | |
|
160 | 167 | #HOOKS |
|
161 | 168 | hgsettings3 = self.sa.query(HgAppUi)\ |
|
162 | 169 | .filter(HgAppUi.ui_key == 'changegroup.update').one() |
|
163 | 170 | hgsettings3.ui_active = bool(form_result['hooks_changegroup_update']) |
|
164 | 171 | |
|
165 | 172 | hgsettings4 = self.sa.query(HgAppUi)\ |
|
166 | 173 | .filter(HgAppUi.ui_key == 'changegroup.repo_size').one() |
|
167 | 174 | hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size']) |
|
168 | 175 | |
|
169 | 176 | |
|
170 | 177 | |
|
171 | 178 | |
|
172 | 179 | self.sa.add(hgsettings1) |
|
173 | 180 | self.sa.add(hgsettings2) |
|
174 | 181 | self.sa.add(hgsettings3) |
|
175 | 182 | self.sa.add(hgsettings4) |
|
176 | 183 | self.sa.commit() |
|
177 | 184 | |
|
178 | 185 | h.flash(_('Updated mercurial settings'), |
|
179 | 186 | category='success') |
|
180 | 187 | |
|
181 | 188 | except: |
|
182 | 189 | log.error(traceback.format_exc()) |
|
183 | 190 | h.flash(_('error occured during updating application settings'), |
|
184 | 191 | category='error') |
|
185 | 192 | |
|
186 | 193 | self.sa.rollback() |
|
187 | 194 | |
|
188 | 195 | |
|
189 | 196 | except formencode.Invalid as errors: |
|
190 | 197 | return htmlfill.render( |
|
191 | 198 | render('admin/settings/settings.html'), |
|
192 | 199 | defaults=errors.value, |
|
193 | 200 | errors=errors.error_dict or {}, |
|
194 | 201 | prefix_error=False, |
|
195 | 202 | encoding="UTF-8") |
|
196 | 203 | |
|
197 | 204 | |
|
198 | 205 | |
|
199 | 206 | return redirect(url('admin_settings')) |
|
200 | 207 | |
|
201 | 208 | @HasPermissionAllDecorator('hg.admin') |
|
202 | 209 | def delete(self, setting_id): |
|
203 | 210 | """DELETE /admin/settings/setting_id: Delete an existing item""" |
|
204 | 211 | # Forms posted to this method should contain a hidden field: |
|
205 | 212 | # <input type="hidden" name="_method" value="DELETE" /> |
|
206 | 213 | # Or using helpers: |
|
207 | 214 | # h.form(url('admin_setting', setting_id=ID), |
|
208 | 215 | # method='delete') |
|
209 | 216 | # url('admin_setting', setting_id=ID) |
|
210 | 217 | |
|
211 | 218 | @HasPermissionAllDecorator('hg.admin') |
|
212 | 219 | def show(self, setting_id, format='html'): |
|
213 | 220 | """GET /admin/settings/setting_id: Show a specific item""" |
|
214 | 221 | # url('admin_setting', setting_id=ID) |
|
215 | 222 | |
|
216 | 223 | @HasPermissionAllDecorator('hg.admin') |
|
217 | 224 | def edit(self, setting_id, format='html'): |
|
218 | 225 | """GET /admin/settings/setting_id/edit: Form to edit an existing item""" |
|
219 | 226 | # url('admin_edit_setting', setting_id=ID) |
|
220 | 227 | |
|
221 | 228 | |
|
222 | 229 | def my_account(self): |
|
223 | 230 | """ |
|
224 | 231 | GET /_admin/my_account Displays info about my account |
|
225 | 232 | """ |
|
226 | 233 | # url('admin_settings_my_account') |
|
227 | 234 | c.user = self.sa.query(User).get(c.hg_app_user.user_id) |
|
228 | 235 | c.user_repos = [] |
|
229 | 236 | for repo in c.cached_repo_list.values(): |
|
230 | 237 | if repo.dbrepo.user.username == c.user.username: |
|
231 | 238 | c.user_repos.append(repo) |
|
232 | 239 | |
|
233 | 240 | if c.user.username == 'default': |
|
234 | 241 | h.flash(_("You can't edit this user since it's" |
|
235 | 242 | " crucial for entire application"), category='warning') |
|
236 | 243 | return redirect(url('users')) |
|
237 | 244 | |
|
238 | 245 | defaults = c.user.__dict__ |
|
239 | 246 | return htmlfill.render( |
|
240 | 247 | render('admin/users/user_edit_my_account.html'), |
|
241 | 248 | defaults=defaults, |
|
242 | 249 | encoding="UTF-8", |
|
243 | 250 | force_defaults=False |
|
244 | 251 | ) |
|
245 | 252 | |
|
246 | 253 | def my_account_update(self): |
|
247 | 254 | """PUT /_admin/my_account_update: Update an existing item""" |
|
248 | 255 | # Forms posted to this method should contain a hidden field: |
|
249 | 256 | # <input type="hidden" name="_method" value="PUT" /> |
|
250 | 257 | # Or using helpers: |
|
251 | 258 | # h.form(url('admin_settings_my_account_update'), |
|
252 | 259 | # method='put') |
|
253 | 260 | # url('admin_settings_my_account_update', id=ID) |
|
254 | 261 | user_model = UserModel() |
|
255 | 262 | uid = c.hg_app_user.user_id |
|
256 |
_form = UserForm(edit=True, old_data={'user_id':uid |
|
|
263 | _form = UserForm(edit=True, old_data={'user_id':uid, | |
|
264 | 'email':c.hg_app_user.email})() | |
|
257 | 265 | form_result = {} |
|
258 | 266 | try: |
|
259 | 267 | form_result = _form.to_python(dict(request.POST)) |
|
260 | 268 | user_model.update_my_account(uid, form_result) |
|
261 | 269 | h.flash(_('Your account was updated succesfully'), |
|
262 | 270 | category='success') |
|
263 | 271 | |
|
264 | 272 | except formencode.Invalid as errors: |
|
265 |
|
|
|
273 | c.user = self.sa.query(User).get(c.hg_app_user.user_id) | |
|
274 | c.user_repos = [] | |
|
275 | for repo in c.cached_repo_list.values(): | |
|
276 | if repo.dbrepo.user.username == c.user.username: | |
|
277 | c.user_repos.append(repo) | |
|
266 | 278 | return htmlfill.render( |
|
267 | 279 | render('admin/users/user_edit_my_account.html'), |
|
268 | 280 | defaults=errors.value, |
|
269 | 281 | errors=errors.error_dict or {}, |
|
270 | 282 | prefix_error=False, |
|
271 | 283 | encoding="UTF-8") |
|
272 | 284 | except Exception: |
|
273 | 285 | log.error(traceback.format_exc()) |
|
274 | 286 | h.flash(_('error occured during update of user %s') \ |
|
275 | 287 | % form_result.get('username'), category='error') |
|
276 | 288 | |
|
277 | 289 | return redirect(url('my_account')) |
|
278 | 290 | |
|
279 | 291 | @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') |
|
280 | 292 | def create_repository(self): |
|
281 | 293 | """GET /_admin/create_repository: Form to create a new item""" |
|
282 | 294 | new_repo = request.GET.get('repo', '') |
|
283 | 295 | c.new_repo = h.repo_name_slug(new_repo) |
|
284 | 296 | |
|
285 | 297 | return render('admin/repos/repo_add_create_repository.html') |
|
286 | 298 |
@@ -1,162 +1,166 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # users controller for pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on April 4, 2010 |
|
22 | 22 | users controller for pylons |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | |
|
26 | 26 | from formencode import htmlfill |
|
27 | 27 | from pylons import request, session, tmpl_context as c, url |
|
28 | 28 | from pylons.controllers.util import abort, redirect |
|
29 | 29 | from pylons.i18n.translation import _ |
|
30 | 30 | from pylons_app.lib import helpers as h |
|
31 | 31 | from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
32 | 32 | from pylons_app.lib.base import BaseController, render |
|
33 | 33 | from pylons_app.model.db import User, UserLog |
|
34 | 34 | from pylons_app.model.forms import UserForm |
|
35 | 35 | from pylons_app.model.user_model import UserModel, DefaultUserException |
|
36 | 36 | import formencode |
|
37 | 37 | import logging |
|
38 | 38 | import traceback |
|
39 | 39 | |
|
40 | 40 | log = logging.getLogger(__name__) |
|
41 | 41 | |
|
42 | 42 | class UsersController(BaseController): |
|
43 | 43 | """REST Controller styled on the Atom Publishing Protocol""" |
|
44 | 44 | # To properly map this controller, ensure your config/routing.py |
|
45 | 45 | # file has a resource setup: |
|
46 | 46 | # map.resource('user', 'users') |
|
47 | 47 | |
|
48 | 48 | @LoginRequired() |
|
49 | 49 | @HasPermissionAllDecorator('hg.admin') |
|
50 | 50 | def __before__(self): |
|
51 | 51 | c.admin_user = session.get('admin_user') |
|
52 | 52 | c.admin_username = session.get('admin_username') |
|
53 | 53 | super(UsersController, self).__before__() |
|
54 | 54 | |
|
55 | 55 | |
|
56 | 56 | def index(self, format='html'): |
|
57 | 57 | """GET /users: All items in the collection""" |
|
58 | 58 | # url('users') |
|
59 | 59 | |
|
60 | 60 | c.users_list = self.sa.query(User).all() |
|
61 | 61 | return render('admin/users/users.html') |
|
62 | 62 | |
|
63 | 63 | def create(self): |
|
64 | 64 | """POST /users: Create a new item""" |
|
65 | 65 | # url('users') |
|
66 | 66 | |
|
67 | 67 | user_model = UserModel() |
|
68 | 68 | login_form = UserForm()() |
|
69 | 69 | try: |
|
70 | 70 | form_result = login_form.to_python(dict(request.POST)) |
|
71 | 71 | user_model.create(form_result) |
|
72 | 72 | h.flash(_('created user %s') % form_result['username'], |
|
73 | 73 | category='success') |
|
74 | 74 | except formencode.Invalid as errors: |
|
75 | 75 | return htmlfill.render( |
|
76 | 76 | render('admin/users/user_add.html'), |
|
77 | 77 | defaults=errors.value, |
|
78 | 78 | errors=errors.error_dict or {}, |
|
79 | 79 | prefix_error=False, |
|
80 | 80 | encoding="UTF-8") |
|
81 | 81 | except Exception: |
|
82 | 82 | log.error(traceback.format_exc()) |
|
83 | 83 | h.flash(_('error occured during creation of user %s') \ |
|
84 | 84 | % request.POST.get('username'), category='error') |
|
85 | 85 | return redirect(url('users')) |
|
86 | 86 | |
|
87 | 87 | def new(self, format='html'): |
|
88 | 88 | """GET /users/new: Form to create a new item""" |
|
89 | 89 | # url('new_user') |
|
90 | 90 | return render('admin/users/user_add.html') |
|
91 | 91 | |
|
92 | 92 | def update(self, id): |
|
93 | 93 | """PUT /users/id: Update an existing item""" |
|
94 | 94 | # Forms posted to this method should contain a hidden field: |
|
95 | 95 | # <input type="hidden" name="_method" value="PUT" /> |
|
96 | 96 | # Or using helpers: |
|
97 | 97 | # h.form(url('user', id=ID), |
|
98 | 98 | # method='put') |
|
99 | 99 | # url('user', id=ID) |
|
100 | 100 | user_model = UserModel() |
|
101 | _form = UserForm(edit=True, old_data={'user_id':id})() | |
|
101 | c.user = user_model.get_user(id) | |
|
102 | ||
|
103 | _form = UserForm(edit=True, old_data={'user_id':id, | |
|
104 | 'email':c.user.email})() | |
|
102 | 105 | form_result = {} |
|
103 | 106 | try: |
|
104 | 107 | form_result = _form.to_python(dict(request.POST)) |
|
105 | 108 | user_model.update(id, form_result) |
|
106 | 109 | h.flash(_('User updated succesfully'), category='success') |
|
107 | 110 | |
|
108 | 111 | except formencode.Invalid as errors: |
|
109 | c.user = user_model.get_user(id) | |
|
110 | 112 | return htmlfill.render( |
|
111 | 113 | render('admin/users/user_edit.html'), |
|
112 | 114 | defaults=errors.value, |
|
113 | 115 | errors=errors.error_dict or {}, |
|
114 | 116 | prefix_error=False, |
|
115 | 117 | encoding="UTF-8") |
|
116 | 118 | except Exception: |
|
117 | 119 | log.error(traceback.format_exc()) |
|
118 | 120 | h.flash(_('error occured during update of user %s') \ |
|
119 | 121 | % form_result.get('username'), category='error') |
|
120 | 122 | |
|
121 | 123 | return redirect(url('users')) |
|
122 | 124 | |
|
123 | 125 | def delete(self, id): |
|
124 | 126 | """DELETE /users/id: Delete an existing item""" |
|
125 | 127 | # Forms posted to this method should contain a hidden field: |
|
126 | 128 | # <input type="hidden" name="_method" value="DELETE" /> |
|
127 | 129 | # Or using helpers: |
|
128 | 130 | # h.form(url('user', id=ID), |
|
129 | 131 | # method='delete') |
|
130 | 132 | # url('user', id=ID) |
|
131 | 133 | user_model = UserModel() |
|
132 | 134 | try: |
|
133 | 135 | user_model.delete(id) |
|
134 | 136 | h.flash(_('sucessfully deleted user'), category='success') |
|
135 | 137 | except DefaultUserException as e: |
|
136 | 138 | h.flash(str(e), category='warning') |
|
137 | 139 | except Exception: |
|
138 | 140 | h.flash(_('An error occured during deletion of user'), |
|
139 | 141 | category='error') |
|
140 | 142 | return redirect(url('users')) |
|
141 | 143 | |
|
142 | 144 | def show(self, id, format='html'): |
|
143 | 145 | """GET /users/id: Show a specific item""" |
|
144 | 146 | # url('user', id=ID) |
|
145 | 147 | |
|
146 | 148 | |
|
147 | 149 | def edit(self, id, format='html'): |
|
148 | 150 | """GET /users/id/edit: Form to edit an existing item""" |
|
149 | 151 | # url('edit_user', id=ID) |
|
150 | 152 | c.user = self.sa.query(User).get(id) |
|
153 | if not c.user: | |
|
154 | return redirect(url('users')) | |
|
151 | 155 | if c.user.username == 'default': |
|
152 | 156 | h.flash(_("You can't edit this user since it's" |
|
153 | 157 | " crucial for entire application"), category='warning') |
|
154 | 158 | return redirect(url('users')) |
|
155 | 159 | |
|
156 | 160 | defaults = c.user.__dict__ |
|
157 | 161 | return htmlfill.render( |
|
158 | 162 | render('admin/users/user_edit.html'), |
|
159 | 163 | defaults=defaults, |
|
160 | 164 | encoding="UTF-8", |
|
161 | 165 | force_defaults=False |
|
162 | 166 | ) |
@@ -1,199 +1,207 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # files controller for pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on April 21, 2010 |
|
22 | 22 | files controller for pylons |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | from mercurial import archival |
|
26 | 26 | from pylons import request, response, session, tmpl_context as c, url |
|
27 | 27 | from pylons.controllers.util import redirect |
|
28 | 28 | from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
29 | 29 | from pylons_app.lib.base import BaseController, render |
|
30 | 30 | from pylons_app.lib.utils import EmptyChangeset |
|
31 | 31 | from pylons_app.model.hg_model import HgModel |
|
32 | 32 | from vcs.exceptions import RepositoryError, ChangesetError |
|
33 | 33 | from vcs.nodes import FileNode |
|
34 | 34 | from vcs.utils import diffs as differ |
|
35 | 35 | import logging |
|
36 | 36 | import pylons_app.lib.helpers as h |
|
37 | 37 | import tempfile |
|
38 | 38 | |
|
39 | 39 | log = logging.getLogger(__name__) |
|
40 | 40 | |
|
41 | 41 | class FilesController(BaseController): |
|
42 | 42 | |
|
43 | 43 | @LoginRequired() |
|
44 | 44 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
45 | 45 | 'repository.admin') |
|
46 | 46 | def __before__(self): |
|
47 | 47 | super(FilesController, self).__before__() |
|
48 | c.file_size_limit = 250 * 1024 #limit of file size to display | |
|
48 | 49 | |
|
49 | 50 | def index(self, repo_name, revision, f_path): |
|
50 | 51 | hg_model = HgModel() |
|
51 | 52 | c.repo = repo = hg_model.get_repo(c.repo_name) |
|
52 | 53 | revision = request.POST.get('at_rev', None) or revision |
|
53 | 54 | |
|
54 | 55 | def get_next_rev(cur): |
|
55 | 56 | max_rev = len(c.repo.revisions) - 1 |
|
56 | 57 | r = cur + 1 |
|
57 | 58 | if r > max_rev: |
|
58 | 59 | r = max_rev |
|
59 | 60 | return r |
|
60 | 61 | |
|
61 | 62 | def get_prev_rev(cur): |
|
62 | 63 | r = cur - 1 |
|
63 | 64 | return r |
|
64 | 65 | |
|
65 | 66 | c.f_path = f_path |
|
66 | 67 | |
|
67 | 68 | |
|
68 | 69 | try: |
|
69 | 70 | cur_rev = repo.get_changeset(revision).revision |
|
70 | 71 | prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id |
|
71 | 72 | next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id |
|
72 | 73 | |
|
73 | 74 | c.url_prev = url('files_home', repo_name=c.repo_name, |
|
74 | 75 | revision=prev_rev, f_path=f_path) |
|
75 | 76 | c.url_next = url('files_home', repo_name=c.repo_name, |
|
76 | 77 | revision=next_rev, f_path=f_path) |
|
77 | 78 | |
|
78 | 79 | c.changeset = repo.get_changeset(revision) |
|
79 | ||
|
80 | 80 | |
|
81 | 81 | c.cur_rev = c.changeset.raw_id |
|
82 | 82 | c.rev_nr = c.changeset.revision |
|
83 | 83 | c.files_list = c.changeset.get_node(f_path) |
|
84 | 84 | c.file_history = self._get_history(repo, c.files_list, f_path) |
|
85 | 85 | |
|
86 | 86 | except (RepositoryError, ChangesetError): |
|
87 | 87 | c.files_list = None |
|
88 | 88 | |
|
89 | 89 | return render('files/files.html') |
|
90 | 90 | |
|
91 | 91 | def rawfile(self, repo_name, revision, f_path): |
|
92 | 92 | hg_model = HgModel() |
|
93 | 93 | c.repo = hg_model.get_repo(c.repo_name) |
|
94 | 94 | file_node = c.repo.get_changeset(revision).get_node(f_path) |
|
95 | 95 | response.content_type = file_node.mimetype |
|
96 | 96 | response.content_disposition = 'attachment; filename=%s' \ |
|
97 | 97 | % f_path.split('/')[-1] |
|
98 | 98 | return file_node.content |
|
99 | ||
|
100 | def raw(self, repo_name, revision, f_path): | |
|
101 | hg_model = HgModel() | |
|
102 | c.repo = hg_model.get_repo(c.repo_name) | |
|
103 | file_node = c.repo.get_changeset(revision).get_node(f_path) | |
|
104 | response.content_type = 'text/plain' | |
|
105 | ||
|
106 | return file_node.content | |
|
99 | 107 | |
|
100 | 108 | def annotate(self, repo_name, revision, f_path): |
|
101 | 109 | hg_model = HgModel() |
|
102 | 110 | c.repo = hg_model.get_repo(c.repo_name) |
|
103 | 111 | cs = c.repo.get_changeset(revision) |
|
104 | 112 | c.file = cs.get_node(f_path) |
|
105 | 113 | c.file_msg = cs.get_file_message(f_path) |
|
106 | 114 | c.cur_rev = cs.raw_id |
|
107 | 115 | c.rev_nr = cs.revision |
|
108 | 116 | c.f_path = f_path |
|
109 | 117 | |
|
110 | 118 | return render('files/files_annotate.html') |
|
111 | 119 | |
|
112 | 120 | def archivefile(self, repo_name, revision, fileformat): |
|
113 | 121 | archive_specs = { |
|
114 | 122 | '.tar.bz2': ('application/x-tar', 'tbz2'), |
|
115 | 123 | '.tar.gz': ('application/x-tar', 'tgz'), |
|
116 | 124 | '.zip': ('application/zip', 'zip'), |
|
117 | 125 | } |
|
118 | 126 | if not archive_specs.has_key(fileformat): |
|
119 | 127 | return 'Unknown archive type %s' % fileformat |
|
120 | 128 | |
|
121 | 129 | def read_in_chunks(file_object, chunk_size=1024 * 40): |
|
122 | 130 | """Lazy function (generator) to read a file piece by piece. |
|
123 | 131 | Default chunk size: 40k.""" |
|
124 | 132 | while True: |
|
125 | 133 | data = file_object.read(chunk_size) |
|
126 | 134 | if not data: |
|
127 | 135 | break |
|
128 | 136 | yield data |
|
129 | 137 | |
|
130 | 138 | archive = tempfile.TemporaryFile() |
|
131 | 139 | repo = HgModel().get_repo(repo_name).repo |
|
132 | 140 | fname = '%s-%s%s' % (repo_name, revision, fileformat) |
|
133 | 141 | archival.archive(repo, archive, revision, archive_specs[fileformat][1], |
|
134 | 142 | prefix='%s-%s' % (repo_name, revision)) |
|
135 | 143 | response.content_type = archive_specs[fileformat][0] |
|
136 | 144 | response.content_disposition = 'attachment; filename=%s' % fname |
|
137 | 145 | archive.seek(0) |
|
138 | 146 | return read_in_chunks(archive) |
|
139 | 147 | |
|
140 | 148 | def diff(self, repo_name, f_path): |
|
141 | 149 | hg_model = HgModel() |
|
142 | 150 | diff1 = request.GET.get('diff1') |
|
143 | 151 | diff2 = request.GET.get('diff2') |
|
144 | 152 | c.action = request.GET.get('diff') |
|
145 | 153 | c.no_changes = diff1 == diff2 |
|
146 | 154 | c.f_path = f_path |
|
147 | 155 | c.repo = hg_model.get_repo(c.repo_name) |
|
148 | 156 | |
|
149 | 157 | try: |
|
150 | 158 | if diff1 not in ['', None, 'None', '0' * 12]: |
|
151 | 159 | c.changeset_1 = c.repo.get_changeset(diff1) |
|
152 | 160 | node1 = c.changeset_1.get_node(f_path) |
|
153 | 161 | else: |
|
154 | 162 | c.changeset_1 = EmptyChangeset() |
|
155 | 163 | node1 = FileNode('.', '') |
|
156 | 164 | if diff2 not in ['', None, 'None', '0' * 12]: |
|
157 | 165 | c.changeset_2 = c.repo.get_changeset(diff2) |
|
158 | 166 | node2 = c.changeset_2.get_node(f_path) |
|
159 | 167 | else: |
|
160 | 168 | c.changeset_2 = EmptyChangeset() |
|
161 | 169 | node2 = FileNode('.', '') |
|
162 | 170 | except RepositoryError: |
|
163 | 171 | return redirect(url('files_home', |
|
164 | 172 | repo_name=c.repo_name, f_path=f_path)) |
|
165 | 173 | |
|
166 | 174 | c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.raw_id) |
|
167 | 175 | c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.raw_id) |
|
168 | 176 | f_udiff = differ.get_udiff(node1, node2) |
|
169 | 177 | |
|
170 | 178 | diff = differ.DiffProcessor(f_udiff) |
|
171 | 179 | |
|
172 | 180 | if c.action == 'download': |
|
173 | 181 | diff_name = '%s_vs_%s.diff' % (diff1, diff2) |
|
174 | 182 | response.content_type = 'text/plain' |
|
175 | 183 | response.content_disposition = 'attachment; filename=%s' \ |
|
176 | 184 | % diff_name |
|
177 | 185 | return diff.raw_diff() |
|
178 | 186 | |
|
179 | 187 | elif c.action == 'raw': |
|
180 | 188 | c.cur_diff = '<pre class="raw">%s</pre>' % h.escape(diff.raw_diff()) |
|
181 | 189 | elif c.action == 'diff': |
|
182 | 190 | c.cur_diff = diff.as_html() |
|
183 | 191 | else: |
|
184 | 192 | #default option |
|
185 | 193 | c.cur_diff = diff.as_html() |
|
186 | 194 | |
|
187 | 195 | if not c.cur_diff: c.no_changes = True |
|
188 | 196 | return render('files/file_diff.html') |
|
189 | 197 | |
|
190 | 198 | def _get_history(self, repo, node, f_path): |
|
191 | 199 | from vcs.nodes import NodeKind |
|
192 | 200 | if not node.kind is NodeKind.FILE: |
|
193 | 201 | return [] |
|
194 | 202 | changesets = node.history |
|
195 | 203 | hist_l = [] |
|
196 | 204 | for chs in changesets: |
|
197 | 205 | n_desc = 'r%s:%s' % (chs.revision, chs._short) |
|
198 | 206 | hist_l.append((chs._short, n_desc,)) |
|
199 | 207 | return hist_l |
@@ -1,118 +1,144 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # login controller for pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | Created on April 22, 2010 |
|
23 | 23 | login controller for pylons |
|
24 | 24 | @author: marcink |
|
25 | 25 | """ |
|
26 | 26 | from formencode import htmlfill |
|
27 | 27 | from pylons import request, response, session, tmpl_context as c, url |
|
28 | 28 | from pylons.controllers.util import abort, redirect |
|
29 | 29 | from pylons_app.lib.auth import AuthUser, HasPermissionAnyDecorator |
|
30 | 30 | from pylons_app.lib.base import BaseController, render |
|
31 | from pylons_app.model.forms import LoginForm, RegisterForm | |
|
31 | import pylons_app.lib.helpers as h | |
|
32 | from pylons.i18n.translation import _ | |
|
33 | from pylons_app.model.forms import LoginForm, RegisterForm, PasswordResetForm | |
|
32 | 34 | from pylons_app.model.user_model import UserModel |
|
33 | 35 | import formencode |
|
34 | 36 | import logging |
|
35 | 37 | |
|
36 | 38 | log = logging.getLogger(__name__) |
|
37 | 39 | |
|
38 | 40 | class LoginController(BaseController): |
|
39 | 41 | |
|
40 | 42 | def __before__(self): |
|
41 | 43 | super(LoginController, self).__before__() |
|
42 | 44 | |
|
43 | 45 | def index(self): |
|
44 | 46 | #redirect if already logged in |
|
45 | c.came_from = request.GET.get('came_from',None) | |
|
47 | c.came_from = request.GET.get('came_from', None) | |
|
46 | 48 | |
|
47 | 49 | if c.hg_app_user.is_authenticated: |
|
48 | 50 | return redirect(url('hg_home')) |
|
49 | 51 | |
|
50 | 52 | if request.POST: |
|
51 | 53 | #import Login Form validator class |
|
52 | 54 | login_form = LoginForm() |
|
53 | 55 | try: |
|
54 | 56 | c.form_result = login_form.to_python(dict(request.POST)) |
|
55 | 57 | username = c.form_result['username'] |
|
56 | 58 | user = UserModel().get_user_by_name(username) |
|
57 | 59 | auth_user = AuthUser() |
|
58 | 60 | auth_user.username = user.username |
|
59 | 61 | auth_user.is_authenticated = True |
|
60 | 62 | auth_user.is_admin = user.admin |
|
61 | 63 | auth_user.user_id = user.user_id |
|
62 | 64 | auth_user.name = user.name |
|
63 | 65 | auth_user.lastname = user.lastname |
|
64 | 66 | session['hg_app_user'] = auth_user |
|
65 | 67 | session.save() |
|
66 | 68 | log.info('user %s is now authenticated', username) |
|
67 | 69 | |
|
68 | 70 | user.update_lastlogin() |
|
69 | 71 | |
|
70 | 72 | if c.came_from: |
|
71 | 73 | return redirect(c.came_from) |
|
72 | 74 | else: |
|
73 | 75 | return redirect(url('hg_home')) |
|
74 | 76 | |
|
75 | 77 | except formencode.Invalid as errors: |
|
76 | 78 | return htmlfill.render( |
|
77 | 79 | render('/login.html'), |
|
78 | 80 | defaults=errors.value, |
|
79 | 81 | errors=errors.error_dict or {}, |
|
80 | 82 | prefix_error=False, |
|
81 | 83 | encoding="UTF-8") |
|
82 | 84 | |
|
83 | 85 | return render('/login.html') |
|
84 | 86 | |
|
85 |
@HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate', |
|
|
87 | @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate', | |
|
86 | 88 | 'hg.register.manual_activate') |
|
87 | 89 | def register(self): |
|
88 | 90 | user_model = UserModel() |
|
89 | 91 | c.auto_active = False |
|
90 | 92 | for perm in user_model.get_default().user_perms: |
|
91 | 93 | if perm.permission.permission_name == 'hg.register.auto_activate': |
|
92 | 94 | c.auto_active = True |
|
93 | 95 | break |
|
94 | 96 | |
|
95 | 97 | if request.POST: |
|
96 | 98 | |
|
97 | 99 | register_form = RegisterForm()() |
|
98 | 100 | try: |
|
99 | 101 | form_result = register_form.to_python(dict(request.POST)) |
|
100 | 102 | form_result['active'] = c.auto_active |
|
101 | 103 | user_model.create_registration(form_result) |
|
104 | h.flash(_('You have successfully registered into hg-app'), | |
|
105 | category='success') | |
|
102 | 106 | return redirect(url('login_home')) |
|
103 | 107 | |
|
104 | 108 | except formencode.Invalid as errors: |
|
105 | 109 | return htmlfill.render( |
|
106 | 110 | render('/register.html'), |
|
107 | 111 | defaults=errors.value, |
|
108 | 112 | errors=errors.error_dict or {}, |
|
109 | 113 | prefix_error=False, |
|
110 | 114 | encoding="UTF-8") |
|
111 | 115 | |
|
112 | 116 | return render('/register.html') |
|
113 | ||
|
117 | ||
|
118 | def password_reset(self): | |
|
119 | user_model = UserModel() | |
|
120 | if request.POST: | |
|
121 | ||
|
122 | password_reset_form = PasswordResetForm()() | |
|
123 | try: | |
|
124 | form_result = password_reset_form.to_python(dict(request.POST)) | |
|
125 | user_model.reset_password(form_result) | |
|
126 | h.flash(_('Your new password was sent'), | |
|
127 | category='success') | |
|
128 | return redirect(url('login_home')) | |
|
129 | ||
|
130 | except formencode.Invalid as errors: | |
|
131 | return htmlfill.render( | |
|
132 | render('/password_reset.html'), | |
|
133 | defaults=errors.value, | |
|
134 | errors=errors.error_dict or {}, | |
|
135 | prefix_error=False, | |
|
136 | encoding="UTF-8") | |
|
137 | ||
|
138 | return render('/password_reset.html') | |
|
139 | ||
|
114 | 140 | def logout(self): |
|
115 | 141 | session['hg_app_user'] = AuthUser() |
|
116 | 142 | session.save() |
|
117 | 143 | log.info('Logging out and setting user as Empty') |
|
118 | 144 | redirect(url('hg_home')) |
@@ -1,113 +1,98 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # search controller for pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on Aug 7, 2010 |
|
22 | 22 | search controller for pylons |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | from pylons import request, response, session, tmpl_context as c, url |
|
26 | 26 | from pylons.controllers.util import abort, redirect |
|
27 | 27 | from pylons_app.lib.auth import LoginRequired |
|
28 | 28 | from pylons_app.lib.base import BaseController, render |
|
29 |
from pylons_app.lib.indexers import |
|
|
30 |
from webhelpers. |
|
|
31 | from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter, \ | |
|
32 | ContextFragmenter | |
|
29 | from pylons_app.lib.indexers import IDX_LOCATION, SCHEMA, IDX_NAME, ResultWrapper | |
|
30 | from webhelpers.paginate import Page | |
|
31 | from webhelpers.util import update_params | |
|
33 | 32 | from pylons.i18n.translation import _ |
|
34 | 33 | from whoosh.index import open_dir, EmptyIndexError |
|
35 | 34 | from whoosh.qparser import QueryParser, QueryParserError |
|
36 | 35 | from whoosh.query import Phrase |
|
37 | 36 | import logging |
|
38 | 37 | import traceback |
|
39 | 38 | |
|
40 | 39 | log = logging.getLogger(__name__) |
|
41 | 40 | |
|
42 | 41 | class SearchController(BaseController): |
|
43 | 42 | |
|
44 | 43 | @LoginRequired() |
|
45 | 44 | def __before__(self): |
|
46 | 45 | super(SearchController, self).__before__() |
|
47 | 46 | |
|
48 | ||
|
49 | 47 | def index(self): |
|
50 | 48 | c.formated_results = [] |
|
51 | 49 | c.runtime = '' |
|
52 | search_items = set() | |
|
53 | 50 | c.cur_query = request.GET.get('q', None) |
|
54 | 51 | if c.cur_query: |
|
55 | 52 | cur_query = c.cur_query.lower() |
|
56 | 53 | |
|
57 | ||
|
58 | 54 | if c.cur_query: |
|
55 | p = int(request.params.get('page', 1)) | |
|
56 | highlight_items = set() | |
|
59 | 57 | try: |
|
60 | 58 | idx = open_dir(IDX_LOCATION, indexname=IDX_NAME) |
|
61 | 59 | searcher = idx.searcher() |
|
62 | ||
|
60 | ||
|
63 | 61 | qp = QueryParser("content", schema=SCHEMA) |
|
64 | 62 | try: |
|
65 | 63 | query = qp.parse(unicode(cur_query)) |
|
66 | 64 | |
|
67 | 65 | if isinstance(query, Phrase): |
|
68 |
|
|
|
66 | highlight_items.update(query.words) | |
|
69 | 67 | else: |
|
70 | 68 | for i in query.all_terms(): |
|
71 |
|
|
|
72 | ||
|
73 | log.debug(query) | |
|
74 | log.debug(search_items) | |
|
75 | results = searcher.search(query) | |
|
76 | c.runtime = '%s results (%.3f seconds)' \ | |
|
77 | % (len(results), results.runtime) | |
|
69 | if i[0] == 'content': | |
|
70 | highlight_items.add(i[1]) | |
|
78 | 71 | |
|
79 | analyzer = ANALYZER | |
|
80 | formatter = HtmlFormatter('span', | |
|
81 | between='\n<span class="break">...</span>\n') | |
|
82 | ||
|
83 | #how the parts are splitted within the same text part | |
|
84 | fragmenter = SimpleFragmenter(200) | |
|
85 | #fragmenter = ContextFragmenter(search_items) | |
|
72 | matcher = query.matcher(searcher) | |
|
86 | 73 | |
|
87 | for res in results: | |
|
88 | d = {} | |
|
89 | d.update(res) | |
|
90 | hl = highlight(escape(res['content']), search_items, | |
|
91 | analyzer=analyzer, | |
|
92 | fragmenter=fragmenter, | |
|
93 | formatter=formatter, | |
|
94 | top=5) | |
|
95 | f_path = res['path'][res['path'].find(res['repository']) \ | |
|
96 | + len(res['repository']):].lstrip('/') | |
|
97 | d.update({'content_short':hl, | |
|
98 | 'f_path':f_path}) | |
|
99 |
|
|
|
100 | c.formated_results.append(d) | |
|
101 |
|
|
|
74 | log.debug(query) | |
|
75 | log.debug(highlight_items) | |
|
76 | results = searcher.search(query) | |
|
77 | res_ln = len(results) | |
|
78 | c.runtime = '%s results (%.3f seconds)' \ | |
|
79 | % (res_ln, results.runtime) | |
|
80 | ||
|
81 | def url_generator(**kw): | |
|
82 | return update_params("?q=%s" % c.cur_query, **kw) | |
|
83 | ||
|
84 | c.formated_results = Page( | |
|
85 | ResultWrapper(searcher, matcher, highlight_items), | |
|
86 | page=p, item_count=res_ln, | |
|
87 | items_per_page=10, url=url_generator) | |
|
88 | ||
|
102 | 89 | except QueryParserError: |
|
103 | 90 | c.runtime = _('Invalid search query. Try quoting it.') |
|
104 | ||
|
91 | searcher.close() | |
|
105 | 92 | except (EmptyIndexError, IOError): |
|
106 | 93 | log.error(traceback.format_exc()) |
|
107 | 94 | log.error('Empty Index data') |
|
108 | 95 | c.runtime = _('There is no index to search in. Please run whoosh indexer') |
|
109 | ||
|
110 | ||
|
111 | ||
|
96 | ||
|
112 | 97 | # Return a rendered template |
|
113 | 98 | return render('/search/search.html') |
@@ -1,139 +1,96 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # summary controller for pylons |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on April 18, 2010 |
|
22 | 22 | summary controller for pylons |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | from datetime import datetime, timedelta | |
|
26 | from pylons import tmpl_context as c, request | |
|
25 | from pylons import tmpl_context as c, request, url | |
|
27 | 26 | from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
28 | 27 | from pylons_app.lib.base import BaseController, render |
|
29 | from pylons_app.lib.helpers import person | |
|
30 | 28 | from pylons_app.lib.utils import OrderedDict |
|
31 | 29 | from pylons_app.model.hg_model import HgModel |
|
30 | from pylons_app.model.db import Statistics | |
|
31 | from webhelpers.paginate import Page | |
|
32 | from pylons_app.lib.celerylib import run_task | |
|
33 | from pylons_app.lib.celerylib.tasks import get_commits_stats | |
|
34 | from datetime import datetime, timedelta | |
|
32 | 35 | from time import mktime |
|
33 | from webhelpers.paginate import Page | |
|
34 | 36 | import calendar |
|
35 | 37 | import logging |
|
36 | 38 | |
|
37 | 39 | log = logging.getLogger(__name__) |
|
38 | 40 | |
|
39 | 41 | class SummaryController(BaseController): |
|
40 | 42 | |
|
41 | 43 | @LoginRequired() |
|
42 | 44 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
43 | 45 | 'repository.admin') |
|
44 | 46 | def __before__(self): |
|
45 | 47 | super(SummaryController, self).__before__() |
|
46 | 48 | |
|
47 | 49 | def index(self): |
|
48 | 50 | hg_model = HgModel() |
|
49 | 51 | c.repo_info = hg_model.get_repo(c.repo_name) |
|
50 | 52 | c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20) |
|
51 | 53 | e = request.environ |
|
52 | 54 | uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % { |
|
53 | 55 | 'protocol': e.get('wsgi.url_scheme'), |
|
54 | 56 | 'user':str(c.hg_app_user.username), |
|
55 | 57 | 'host':e.get('HTTP_HOST'), |
|
56 | 58 | 'repo_name':c.repo_name, } |
|
57 | 59 | c.clone_repo_url = uri |
|
58 | 60 | c.repo_tags = OrderedDict() |
|
59 | 61 | for name, hash in c.repo_info.tags.items()[:10]: |
|
60 | 62 | c.repo_tags[name] = c.repo_info.get_changeset(hash) |
|
61 | 63 | |
|
62 | 64 | c.repo_branches = OrderedDict() |
|
63 | 65 | for name, hash in c.repo_info.branches.items()[:10]: |
|
64 | 66 | c.repo_branches[name] = c.repo_info.get_changeset(hash) |
|
67 | ||
|
68 | td = datetime.today() + timedelta(days=1) | |
|
69 | y, m, d = td.year, td.month, td.day | |
|
70 | ||
|
71 | ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month, | |
|
72 | d, 0, 0, 0, 0, 0, 0,)) | |
|
73 | ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month, | |
|
74 | d, 0, 0, 0, 0, 0, 0,)) | |
|
75 | ||
|
76 | ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,)) | |
|
77 | ||
|
78 | run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y) | |
|
79 | c.ts_min = ts_min_m | |
|
80 | c.ts_max = ts_max_y | |
|
81 | ||
|
82 | ||
|
83 | stats = self.sa.query(Statistics)\ | |
|
84 | .filter(Statistics.repository == c.repo_info.dbrepo)\ | |
|
85 | .scalar() | |
|
65 | 86 | |
|
66 | c.commit_data = self.__get_commit_stats(c.repo_info) | |
|
87 | if stats: | |
|
88 | c.commit_data = stats.commit_activity | |
|
89 | c.overview_data = stats.commit_activity_combined | |
|
90 | else: | |
|
91 | import json | |
|
92 | c.commit_data = json.dumps({}) | |
|
93 | c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 0] ]) | |
|
67 | 94 | |
|
68 | 95 | return render('summary/summary.html') |
|
69 | 96 | |
|
70 | ||
|
71 | ||
|
72 | def __get_commit_stats(self, repo): | |
|
73 | aggregate = OrderedDict() | |
|
74 | ||
|
75 | #graph range | |
|
76 | td = datetime.today() + timedelta(days=1) | |
|
77 | y, m, d = td.year, td.month, td.day | |
|
78 | c.ts_min = mktime((y, (td - timedelta(days=calendar.mdays[m])).month, | |
|
79 | d, 0, 0, 0, 0, 0, 0,)) | |
|
80 | c.ts_max = mktime((y, m, d, 0, 0, 0, 0, 0, 0,)) | |
|
81 | ||
|
82 | def author_key_cleaner(k): | |
|
83 | k = person(k) | |
|
84 | k = k.replace('"', "'") #for js data compatibilty | |
|
85 | return k | |
|
86 | ||
|
87 | for cs in repo[:200]:#added limit 200 until fix #29 is made | |
|
88 | k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1], | |
|
89 | cs.date.timetuple()[2]) | |
|
90 | timetupple = [int(x) for x in k.split('-')] | |
|
91 | timetupple.extend([0 for _ in xrange(6)]) | |
|
92 | k = mktime(timetupple) | |
|
93 | if aggregate.has_key(author_key_cleaner(cs.author)): | |
|
94 | if aggregate[author_key_cleaner(cs.author)].has_key(k): | |
|
95 | aggregate[author_key_cleaner(cs.author)][k]["commits"] += 1 | |
|
96 | aggregate[author_key_cleaner(cs.author)][k]["added"] += len(cs.added) | |
|
97 | aggregate[author_key_cleaner(cs.author)][k]["changed"] += len(cs.changed) | |
|
98 | aggregate[author_key_cleaner(cs.author)][k]["removed"] += len(cs.removed) | |
|
99 | ||
|
100 | else: | |
|
101 | #aggregate[author_key_cleaner(cs.author)].update(dates_range) | |
|
102 | if k >= c.ts_min and k <= c.ts_max: | |
|
103 | aggregate[author_key_cleaner(cs.author)][k] = {} | |
|
104 | aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1 | |
|
105 | aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added) | |
|
106 | aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed) | |
|
107 | aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed) | |
|
108 | ||
|
109 | else: | |
|
110 | if k >= c.ts_min and k <= c.ts_max: | |
|
111 | aggregate[author_key_cleaner(cs.author)] = OrderedDict() | |
|
112 | #aggregate[author_key_cleaner(cs.author)].update(dates_range) | |
|
113 | aggregate[author_key_cleaner(cs.author)][k] = {} | |
|
114 | aggregate[author_key_cleaner(cs.author)][k]["commits"] = 1 | |
|
115 | aggregate[author_key_cleaner(cs.author)][k]["added"] = len(cs.added) | |
|
116 | aggregate[author_key_cleaner(cs.author)][k]["changed"] = len(cs.changed) | |
|
117 | aggregate[author_key_cleaner(cs.author)][k]["removed"] = len(cs.removed) | |
|
118 | ||
|
119 | d = '' | |
|
120 | tmpl0 = u""""%s":%s""" | |
|
121 | tmpl1 = u"""{label:"%s",data:%s,schema:["commits"]},""" | |
|
122 | for author in aggregate: | |
|
123 | ||
|
124 | d += tmpl0 % (author, | |
|
125 | tmpl1 \ | |
|
126 | % (author, | |
|
127 | [{"time":x, | |
|
128 | "commits":aggregate[author][x]['commits'], | |
|
129 | "added":aggregate[author][x]['added'], | |
|
130 | "changed":aggregate[author][x]['changed'], | |
|
131 | "removed":aggregate[author][x]['removed'], | |
|
132 | } for x in aggregate[author]])) | |
|
133 | if d == '': | |
|
134 | d = '"%s":{label:"%s",data:[[0,1],]}' \ | |
|
135 | % (author_key_cleaner(repo.contact), | |
|
136 | author_key_cleaner(repo.contact)) | |
|
137 | return d | |
|
138 | ||
|
139 |
@@ -1,454 +1,481 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # authentication and permission libraries |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on April 4, 2010 |
|
22 | 22 | |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | from beaker.cache import cache_region |
|
26 | 26 | from pylons import config, session, url, request |
|
27 | 27 | from pylons.controllers.util import abort, redirect |
|
28 | 28 | from pylons_app.lib.utils import get_repo_slug |
|
29 | 29 | from pylons_app.model import meta |
|
30 | 30 | from pylons_app.model.db import User, RepoToPerm, Repository, Permission, \ |
|
31 | 31 | UserToPerm |
|
32 | 32 | from sqlalchemy.exc import OperationalError |
|
33 | 33 | from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound |
|
34 | 34 | import bcrypt |
|
35 | 35 | from decorator import decorator |
|
36 | 36 | import logging |
|
37 | import random | |
|
37 | 38 | |
|
38 | 39 | log = logging.getLogger(__name__) |
|
39 | 40 | |
|
41 | class PasswordGenerator(object): | |
|
42 | """This is a simple class for generating password from | |
|
43 | different sets of characters | |
|
44 | usage: | |
|
45 | passwd_gen = PasswordGenerator() | |
|
46 | #print 8-letter password containing only big and small letters of alphabet | |
|
47 | print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL) | |
|
48 | """ | |
|
49 | ALPHABETS_NUM = r'''1234567890'''#[0] | |
|
50 | ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1] | |
|
51 | ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2] | |
|
52 | ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3] | |
|
53 | ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4] | |
|
54 | ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5] | |
|
55 | ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL | |
|
56 | ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6] | |
|
57 | ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7] | |
|
58 | ||
|
59 | def __init__(self, passwd=''): | |
|
60 | self.passwd = passwd | |
|
61 | ||
|
62 | def gen_password(self, len, type): | |
|
63 | self.passwd = ''.join([random.choice(type) for _ in xrange(len)]) | |
|
64 | return self.passwd | |
|
65 | ||
|
66 | ||
|
40 | 67 | def get_crypt_password(password): |
|
41 | 68 | """Cryptographic function used for password hashing based on sha1 |
|
42 | 69 | @param password: password to hash |
|
43 | 70 | """ |
|
44 | 71 | return bcrypt.hashpw(password, bcrypt.gensalt(10)) |
|
45 | 72 | |
|
46 | 73 | def check_password(password, hashed): |
|
47 | 74 | return bcrypt.hashpw(password, hashed) == hashed |
|
48 | 75 | |
|
49 | 76 | @cache_region('super_short_term', 'cached_user') |
|
50 | 77 | def get_user_cached(username): |
|
51 | 78 | sa = meta.Session |
|
52 | 79 | try: |
|
53 | 80 | user = sa.query(User).filter(User.username == username).one() |
|
54 | 81 | finally: |
|
55 | 82 | meta.Session.remove() |
|
56 | 83 | return user |
|
57 | 84 | |
|
58 | 85 | def authfunc(environ, username, password): |
|
59 | 86 | try: |
|
60 | 87 | user = get_user_cached(username) |
|
61 | 88 | except (NoResultFound, MultipleResultsFound, OperationalError) as e: |
|
62 | 89 | log.error(e) |
|
63 | 90 | user = None |
|
64 | 91 | |
|
65 | 92 | if user: |
|
66 | 93 | if user.active: |
|
67 | 94 | if user.username == username and check_password(password, user.password): |
|
68 | 95 | log.info('user %s authenticated correctly', username) |
|
69 | 96 | return True |
|
70 | 97 | else: |
|
71 | 98 | log.error('user %s is disabled', username) |
|
72 | 99 | |
|
73 | 100 | return False |
|
74 | 101 | |
|
75 | 102 | class AuthUser(object): |
|
76 | 103 | """ |
|
77 | 104 | A simple object that handles a mercurial username for authentication |
|
78 | 105 | """ |
|
79 | 106 | def __init__(self): |
|
80 | 107 | self.username = 'None' |
|
81 | 108 | self.name = '' |
|
82 | 109 | self.lastname = '' |
|
83 | 110 | self.email = '' |
|
84 | 111 | self.user_id = None |
|
85 | 112 | self.is_authenticated = False |
|
86 | 113 | self.is_admin = False |
|
87 | 114 | self.permissions = {} |
|
88 | 115 | |
|
89 | 116 | |
|
90 | 117 | def set_available_permissions(config): |
|
91 | 118 | """ |
|
92 | 119 | This function will propagate pylons globals with all available defined |
|
93 | 120 | permission given in db. We don't wannt to check each time from db for new |
|
94 | 121 | permissions since adding a new permission also requires application restart |
|
95 | 122 | ie. to decorate new views with the newly created permission |
|
96 | 123 | @param config: |
|
97 | 124 | """ |
|
98 | 125 | log.info('getting information about all available permissions') |
|
99 | 126 | try: |
|
100 | 127 | sa = meta.Session |
|
101 | 128 | all_perms = sa.query(Permission).all() |
|
102 | 129 | finally: |
|
103 | 130 | meta.Session.remove() |
|
104 | 131 | |
|
105 | 132 | config['available_permissions'] = [x.permission_name for x in all_perms] |
|
106 | 133 | |
|
107 | 134 | def set_base_path(config): |
|
108 | 135 | config['base_path'] = config['pylons.app_globals'].base_path |
|
109 | 136 | |
|
110 | 137 | def fill_data(user): |
|
111 | 138 | """ |
|
112 | 139 | Fills user data with those from database and log out user if not present |
|
113 | 140 | in database |
|
114 | 141 | @param user: |
|
115 | 142 | """ |
|
116 | 143 | sa = meta.Session |
|
117 | 144 | dbuser = sa.query(User).get(user.user_id) |
|
118 | 145 | if dbuser: |
|
119 | 146 | user.username = dbuser.username |
|
120 | 147 | user.is_admin = dbuser.admin |
|
121 | 148 | user.name = dbuser.name |
|
122 | 149 | user.lastname = dbuser.lastname |
|
123 | 150 | user.email = dbuser.email |
|
124 | 151 | else: |
|
125 | 152 | user.is_authenticated = False |
|
126 | 153 | meta.Session.remove() |
|
127 | 154 | return user |
|
128 | 155 | |
|
129 | 156 | def fill_perms(user): |
|
130 | 157 | """ |
|
131 | 158 | Fills user permission attribute with permissions taken from database |
|
132 | 159 | @param user: |
|
133 | 160 | """ |
|
134 | 161 | |
|
135 | 162 | sa = meta.Session |
|
136 | 163 | user.permissions['repositories'] = {} |
|
137 | 164 | user.permissions['global'] = set() |
|
138 | 165 | |
|
139 | 166 | #=========================================================================== |
|
140 | 167 | # fetch default permissions |
|
141 | 168 | #=========================================================================== |
|
142 | 169 | default_perms = sa.query(RepoToPerm, Repository, Permission)\ |
|
143 | 170 | .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\ |
|
144 | 171 | .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\ |
|
145 | 172 | .filter(RepoToPerm.user == sa.query(User).filter(User.username == |
|
146 | 173 | 'default').scalar()).all() |
|
147 | 174 | |
|
148 | 175 | if user.is_admin: |
|
149 | 176 | #======================================================================= |
|
150 | 177 | # #admin have all default rights set to admin |
|
151 | 178 | #======================================================================= |
|
152 | 179 | user.permissions['global'].add('hg.admin') |
|
153 | 180 | |
|
154 | 181 | for perm in default_perms: |
|
155 | 182 | p = 'repository.admin' |
|
156 | 183 | user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p |
|
157 | 184 | |
|
158 | 185 | else: |
|
159 | 186 | #======================================================================= |
|
160 | 187 | # set default permissions |
|
161 | 188 | #======================================================================= |
|
162 | 189 | |
|
163 | 190 | #default global |
|
164 | 191 | default_global_perms = sa.query(UserToPerm)\ |
|
165 | 192 | .filter(UserToPerm.user == sa.query(User).filter(User.username == |
|
166 | 193 | 'default').one()) |
|
167 | 194 | |
|
168 | 195 | for perm in default_global_perms: |
|
169 | 196 | user.permissions['global'].add(perm.permission.permission_name) |
|
170 | 197 | |
|
171 | 198 | #default repositories |
|
172 | 199 | for perm in default_perms: |
|
173 | 200 | if perm.Repository.private and not perm.Repository.user_id == user.user_id: |
|
174 | 201 | #disable defaults for private repos, |
|
175 | 202 | p = 'repository.none' |
|
176 | 203 | elif perm.Repository.user_id == user.user_id: |
|
177 | 204 | #set admin if owner |
|
178 | 205 | p = 'repository.admin' |
|
179 | 206 | else: |
|
180 | 207 | p = perm.Permission.permission_name |
|
181 | 208 | |
|
182 | 209 | user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p |
|
183 | 210 | |
|
184 | 211 | #======================================================================= |
|
185 | 212 | # #overwrite default with user permissions if any |
|
186 | 213 | #======================================================================= |
|
187 | 214 | user_perms = sa.query(RepoToPerm, Permission, Repository)\ |
|
188 | 215 | .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\ |
|
189 | 216 | .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\ |
|
190 | 217 | .filter(RepoToPerm.user_id == user.user_id).all() |
|
191 | 218 | |
|
192 | 219 | for perm in user_perms: |
|
193 | 220 | if perm.Repository.user_id == user.user_id:#set admin if owner |
|
194 | 221 | p = 'repository.admin' |
|
195 | 222 | else: |
|
196 | 223 | p = perm.Permission.permission_name |
|
197 | 224 | user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p |
|
198 | 225 | meta.Session.remove() |
|
199 | 226 | return user |
|
200 | 227 | |
|
201 | 228 | def get_user(session): |
|
202 | 229 | """ |
|
203 | 230 | Gets user from session, and wraps permissions into user |
|
204 | 231 | @param session: |
|
205 | 232 | """ |
|
206 | 233 | user = session.get('hg_app_user', AuthUser()) |
|
207 | 234 | if user.is_authenticated: |
|
208 | 235 | user = fill_data(user) |
|
209 | 236 | user = fill_perms(user) |
|
210 | 237 | session['hg_app_user'] = user |
|
211 | 238 | session.save() |
|
212 | 239 | return user |
|
213 | 240 | |
|
214 | 241 | #=============================================================================== |
|
215 | 242 | # CHECK DECORATORS |
|
216 | 243 | #=============================================================================== |
|
217 | 244 | class LoginRequired(object): |
|
218 | 245 | """Must be logged in to execute this function else redirect to login page""" |
|
219 | 246 | |
|
220 | 247 | def __call__(self, func): |
|
221 | 248 | return decorator(self.__wrapper, func) |
|
222 | 249 | |
|
223 | 250 | def __wrapper(self, func, *fargs, **fkwargs): |
|
224 | 251 | user = session.get('hg_app_user', AuthUser()) |
|
225 | 252 | log.debug('Checking login required for user:%s', user.username) |
|
226 | 253 | if user.is_authenticated: |
|
227 | 254 | log.debug('user %s is authenticated', user.username) |
|
228 | 255 | return func(*fargs, **fkwargs) |
|
229 | 256 | else: |
|
230 | 257 | log.warn('user %s not authenticated', user.username) |
|
231 | 258 | |
|
232 | 259 | p = request.environ.get('PATH_INFO') |
|
233 | 260 | if request.environ.get('QUERY_STRING'): |
|
234 | p+='?'+request.environ.get('QUERY_STRING') | |
|
235 | log.debug('redirecting to login page with %s',p) | |
|
236 | return redirect(url('login_home',came_from=p)) | |
|
261 | p += '?' + request.environ.get('QUERY_STRING') | |
|
262 | log.debug('redirecting to login page with %s', p) | |
|
263 | return redirect(url('login_home', came_from=p)) | |
|
237 | 264 | |
|
238 | 265 | class PermsDecorator(object): |
|
239 | 266 | """Base class for decorators""" |
|
240 | 267 | |
|
241 | 268 | def __init__(self, *required_perms): |
|
242 | 269 | available_perms = config['available_permissions'] |
|
243 | 270 | for perm in required_perms: |
|
244 | 271 | if perm not in available_perms: |
|
245 | 272 | raise Exception("'%s' permission is not defined" % perm) |
|
246 | 273 | self.required_perms = set(required_perms) |
|
247 | 274 | self.user_perms = None |
|
248 | 275 | |
|
249 | 276 | def __call__(self, func): |
|
250 | 277 | return decorator(self.__wrapper, func) |
|
251 | 278 | |
|
252 | 279 | |
|
253 | 280 | def __wrapper(self, func, *fargs, **fkwargs): |
|
254 | 281 | # _wrapper.__name__ = func.__name__ |
|
255 | 282 | # _wrapper.__dict__.update(func.__dict__) |
|
256 | 283 | # _wrapper.__doc__ = func.__doc__ |
|
257 | 284 | |
|
258 | 285 | self.user_perms = session.get('hg_app_user', AuthUser()).permissions |
|
259 | 286 | log.debug('checking %s permissions %s for %s', |
|
260 | 287 | self.__class__.__name__, self.required_perms, func.__name__) |
|
261 | 288 | |
|
262 | 289 | if self.check_permissions(): |
|
263 | 290 | log.debug('Permission granted for %s', func.__name__) |
|
264 | 291 | |
|
265 | 292 | return func(*fargs, **fkwargs) |
|
266 | 293 | |
|
267 | 294 | else: |
|
268 | 295 | log.warning('Permission denied for %s', func.__name__) |
|
269 | 296 | #redirect with forbidden ret code |
|
270 | 297 | return abort(403) |
|
271 | 298 | |
|
272 | 299 | |
|
273 | 300 | |
|
274 | 301 | def check_permissions(self): |
|
275 | 302 | """Dummy function for overriding""" |
|
276 | 303 | raise Exception('You have to write this function in child class') |
|
277 | 304 | |
|
278 | 305 | class HasPermissionAllDecorator(PermsDecorator): |
|
279 | 306 | """Checks for access permission for all given predicates. All of them |
|
280 | 307 | have to be meet in order to fulfill the request |
|
281 | 308 | """ |
|
282 | 309 | |
|
283 | 310 | def check_permissions(self): |
|
284 | 311 | if self.required_perms.issubset(self.user_perms.get('global')): |
|
285 | 312 | return True |
|
286 | 313 | return False |
|
287 | 314 | |
|
288 | 315 | |
|
289 | 316 | class HasPermissionAnyDecorator(PermsDecorator): |
|
290 | 317 | """Checks for access permission for any of given predicates. In order to |
|
291 | 318 | fulfill the request any of predicates must be meet |
|
292 | 319 | """ |
|
293 | 320 | |
|
294 | 321 | def check_permissions(self): |
|
295 | 322 | if self.required_perms.intersection(self.user_perms.get('global')): |
|
296 | 323 | return True |
|
297 | 324 | return False |
|
298 | 325 | |
|
299 | 326 | class HasRepoPermissionAllDecorator(PermsDecorator): |
|
300 | 327 | """Checks for access permission for all given predicates for specific |
|
301 | 328 | repository. All of them have to be meet in order to fulfill the request |
|
302 | 329 | """ |
|
303 | 330 | |
|
304 | 331 | def check_permissions(self): |
|
305 | 332 | repo_name = get_repo_slug(request) |
|
306 | 333 | try: |
|
307 | 334 | user_perms = set([self.user_perms['repositories'][repo_name]]) |
|
308 | 335 | except KeyError: |
|
309 | 336 | return False |
|
310 | 337 | if self.required_perms.issubset(user_perms): |
|
311 | 338 | return True |
|
312 | 339 | return False |
|
313 | 340 | |
|
314 | 341 | |
|
315 | 342 | class HasRepoPermissionAnyDecorator(PermsDecorator): |
|
316 | 343 | """Checks for access permission for any of given predicates for specific |
|
317 | 344 | repository. In order to fulfill the request any of predicates must be meet |
|
318 | 345 | """ |
|
319 | 346 | |
|
320 | 347 | def check_permissions(self): |
|
321 | 348 | repo_name = get_repo_slug(request) |
|
322 | 349 | |
|
323 | 350 | try: |
|
324 | 351 | user_perms = set([self.user_perms['repositories'][repo_name]]) |
|
325 | 352 | except KeyError: |
|
326 | 353 | return False |
|
327 | 354 | if self.required_perms.intersection(user_perms): |
|
328 | 355 | return True |
|
329 | 356 | return False |
|
330 | 357 | #=============================================================================== |
|
331 | 358 | # CHECK FUNCTIONS |
|
332 | 359 | #=============================================================================== |
|
333 | 360 | |
|
334 | 361 | class PermsFunction(object): |
|
335 | 362 | """Base function for other check functions""" |
|
336 | 363 | |
|
337 | 364 | def __init__(self, *perms): |
|
338 | 365 | available_perms = config['available_permissions'] |
|
339 | 366 | |
|
340 | 367 | for perm in perms: |
|
341 | 368 | if perm not in available_perms: |
|
342 | 369 | raise Exception("'%s' permission in not defined" % perm) |
|
343 | 370 | self.required_perms = set(perms) |
|
344 | 371 | self.user_perms = None |
|
345 | 372 | self.granted_for = '' |
|
346 | 373 | self.repo_name = None |
|
347 | 374 | |
|
348 | 375 | def __call__(self, check_Location=''): |
|
349 | 376 | user = session.get('hg_app_user', False) |
|
350 | 377 | if not user: |
|
351 | 378 | return False |
|
352 | 379 | self.user_perms = user.permissions |
|
353 | 380 | self.granted_for = user.username |
|
354 | 381 | log.debug('checking %s %s', self.__class__.__name__, self.required_perms) |
|
355 | 382 | |
|
356 | 383 | if self.check_permissions(): |
|
357 | 384 | log.debug('Permission granted for %s @%s', self.granted_for, |
|
358 | 385 | check_Location) |
|
359 | 386 | return True |
|
360 | 387 | |
|
361 | 388 | else: |
|
362 | 389 | log.warning('Permission denied for %s @%s', self.granted_for, |
|
363 | 390 | check_Location) |
|
364 | 391 | return False |
|
365 | 392 | |
|
366 | 393 | def check_permissions(self): |
|
367 | 394 | """Dummy function for overriding""" |
|
368 | 395 | raise Exception('You have to write this function in child class') |
|
369 | 396 | |
|
370 | 397 | class HasPermissionAll(PermsFunction): |
|
371 | 398 | def check_permissions(self): |
|
372 | 399 | if self.required_perms.issubset(self.user_perms.get('global')): |
|
373 | 400 | return True |
|
374 | 401 | return False |
|
375 | 402 | |
|
376 | 403 | class HasPermissionAny(PermsFunction): |
|
377 | 404 | def check_permissions(self): |
|
378 | 405 | if self.required_perms.intersection(self.user_perms.get('global')): |
|
379 | 406 | return True |
|
380 | 407 | return False |
|
381 | 408 | |
|
382 | 409 | class HasRepoPermissionAll(PermsFunction): |
|
383 | 410 | |
|
384 | 411 | def __call__(self, repo_name=None, check_Location=''): |
|
385 | 412 | self.repo_name = repo_name |
|
386 | 413 | return super(HasRepoPermissionAll, self).__call__(check_Location) |
|
387 | 414 | |
|
388 | 415 | def check_permissions(self): |
|
389 | 416 | if not self.repo_name: |
|
390 | 417 | self.repo_name = get_repo_slug(request) |
|
391 | 418 | |
|
392 | 419 | try: |
|
393 | 420 | self.user_perms = set([self.user_perms['repositories']\ |
|
394 | 421 | [self.repo_name]]) |
|
395 | 422 | except KeyError: |
|
396 | 423 | return False |
|
397 | 424 | self.granted_for = self.repo_name |
|
398 | 425 | if self.required_perms.issubset(self.user_perms): |
|
399 | 426 | return True |
|
400 | 427 | return False |
|
401 | 428 | |
|
402 | 429 | class HasRepoPermissionAny(PermsFunction): |
|
403 | 430 | |
|
404 | 431 | def __call__(self, repo_name=None, check_Location=''): |
|
405 | 432 | self.repo_name = repo_name |
|
406 | 433 | return super(HasRepoPermissionAny, self).__call__(check_Location) |
|
407 | 434 | |
|
408 | 435 | def check_permissions(self): |
|
409 | 436 | if not self.repo_name: |
|
410 | 437 | self.repo_name = get_repo_slug(request) |
|
411 | 438 | |
|
412 | 439 | try: |
|
413 | 440 | self.user_perms = set([self.user_perms['repositories']\ |
|
414 | 441 | [self.repo_name]]) |
|
415 | 442 | except KeyError: |
|
416 | 443 | return False |
|
417 | 444 | self.granted_for = self.repo_name |
|
418 | 445 | if self.required_perms.intersection(self.user_perms): |
|
419 | 446 | return True |
|
420 | 447 | return False |
|
421 | 448 | |
|
422 | 449 | #=============================================================================== |
|
423 | 450 | # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH |
|
424 | 451 | #=============================================================================== |
|
425 | 452 | |
|
426 | 453 | class HasPermissionAnyMiddleware(object): |
|
427 | 454 | def __init__(self, *perms): |
|
428 | 455 | self.required_perms = set(perms) |
|
429 | 456 | |
|
430 | 457 | def __call__(self, user, repo_name): |
|
431 | 458 | usr = AuthUser() |
|
432 | 459 | usr.user_id = user.user_id |
|
433 | 460 | usr.username = user.username |
|
434 | 461 | usr.is_admin = user.admin |
|
435 | 462 | |
|
436 | 463 | try: |
|
437 | 464 | self.user_perms = set([fill_perms(usr)\ |
|
438 | 465 | .permissions['repositories'][repo_name]]) |
|
439 | 466 | except: |
|
440 | 467 | self.user_perms = set() |
|
441 | 468 | self.granted_for = '' |
|
442 | 469 | self.username = user.username |
|
443 | 470 | self.repo_name = repo_name |
|
444 | 471 | return self.check_permissions() |
|
445 | 472 | |
|
446 | 473 | def check_permissions(self): |
|
447 | 474 | log.debug('checking mercurial protocol ' |
|
448 | 475 | 'permissions for user:%s repository:%s', |
|
449 | 476 | self.username, self.repo_name) |
|
450 | 477 | if self.required_perms.intersection(self.user_perms): |
|
451 | 478 | log.debug('permission granted') |
|
452 | 479 | return True |
|
453 | 480 | log.debug('permission denied') |
|
454 | 481 | return False |
@@ -1,263 +1,265 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # database managment for hg app |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | Created on April 10, 2010 |
|
23 | 23 | database managment and creation for hg app |
|
24 | 24 | @author: marcink |
|
25 | 25 | """ |
|
26 | 26 | |
|
27 | 27 | from os.path import dirname as dn, join as jn |
|
28 | 28 | import os |
|
29 | 29 | import sys |
|
30 | 30 | import uuid |
|
31 | 31 | ROOT = dn(dn(dn(os.path.realpath(__file__)))) |
|
32 | 32 | sys.path.append(ROOT) |
|
33 | 33 | |
|
34 | 34 | from pylons_app.lib.auth import get_crypt_password |
|
35 | 35 | from pylons_app.lib.utils import ask_ok |
|
36 | 36 | from pylons_app.model import init_model |
|
37 | 37 | from pylons_app.model.db import User, Permission, HgAppUi, HgAppSettings, \ |
|
38 | 38 | UserToPerm |
|
39 | 39 | from pylons_app.model import meta |
|
40 | 40 | from sqlalchemy.engine import create_engine |
|
41 | 41 | import logging |
|
42 | 42 | |
|
43 | 43 | log = logging.getLogger(__name__) |
|
44 | 44 | |
|
45 | 45 | class DbManage(object): |
|
46 | def __init__(self, log_sql, dbname,tests=False): | |
|
46 | def __init__(self, log_sql, dbname, tests=False): | |
|
47 | 47 | self.dbname = dbname |
|
48 | 48 | self.tests = tests |
|
49 | 49 | dburi = 'sqlite:////%s' % jn(ROOT, self.dbname) |
|
50 | 50 | engine = create_engine(dburi, echo=log_sql) |
|
51 | 51 | init_model(engine) |
|
52 | 52 | self.sa = meta.Session |
|
53 | 53 | self.db_exists = False |
|
54 | 54 | |
|
55 | 55 | def check_for_db(self, override): |
|
56 | 56 | log.info('checking for exisiting db') |
|
57 | 57 | if os.path.isfile(jn(ROOT, self.dbname)): |
|
58 | 58 | self.db_exists = True |
|
59 | 59 | log.info('database exisist') |
|
60 | 60 | if not override: |
|
61 | 61 | raise Exception('database already exists') |
|
62 | 62 | |
|
63 | 63 | def create_tables(self, override=False): |
|
64 | 64 | """ |
|
65 | 65 | Create a auth database |
|
66 | 66 | """ |
|
67 | 67 | self.check_for_db(override) |
|
68 | 68 | if override: |
|
69 | 69 | log.info("database exisist and it's going to be destroyed") |
|
70 | 70 | if self.tests: |
|
71 | destroy=True | |
|
71 | destroy = True | |
|
72 | 72 | else: |
|
73 | 73 | destroy = ask_ok('Are you sure to destroy old database ? [y/n]') |
|
74 | 74 | if not destroy: |
|
75 | 75 | sys.exit() |
|
76 | 76 | if self.db_exists and destroy: |
|
77 | 77 | os.remove(jn(ROOT, self.dbname)) |
|
78 | 78 | checkfirst = not override |
|
79 | 79 | meta.Base.metadata.create_all(checkfirst=checkfirst) |
|
80 | 80 | log.info('Created tables for %s', self.dbname) |
|
81 | 81 | |
|
82 | 82 | def admin_prompt(self): |
|
83 | 83 | if not self.tests: |
|
84 | 84 | import getpass |
|
85 | 85 | username = raw_input('Specify admin username:') |
|
86 | 86 | password = getpass.getpass('Specify admin password:') |
|
87 | self.create_user(username, password, True) | |
|
87 | email = raw_input('Specify admin email:') | |
|
88 | self.create_user(username, password, email, True) | |
|
88 | 89 | else: |
|
89 | 90 | log.info('creating admin and regular test users') |
|
90 | self.create_user('test_admin', 'test', True) | |
|
91 | self.create_user('test_regular', 'test', False) | |
|
91 | self.create_user('test_admin', 'test', 'test_admin@mail.com', True) | |
|
92 | self.create_user('test_regular', 'test', 'test_regular@mail.com', False) | |
|
93 | self.create_user('test_regular2', 'test', 'test_regular2@mail.com', False) | |
|
92 | 94 | |
|
93 | 95 | |
|
94 | 96 | |
|
95 | def config_prompt(self,test_repo_path=''): | |
|
97 | def config_prompt(self, test_repo_path=''): | |
|
96 | 98 | log.info('Setting up repositories config') |
|
97 | 99 | |
|
98 | 100 | if not self.tests and not test_repo_path: |
|
99 | 101 | path = raw_input('Specify valid full path to your repositories' |
|
100 | 102 | ' you can change this later in application settings:') |
|
101 | 103 | else: |
|
102 | 104 | path = test_repo_path |
|
103 | 105 | |
|
104 | 106 | if not os.path.isdir(path): |
|
105 | log.error('You entered wrong path: %s',path) | |
|
107 | log.error('You entered wrong path: %s', path) | |
|
106 | 108 | sys.exit() |
|
107 | 109 | |
|
108 | 110 | hooks1 = HgAppUi() |
|
109 | 111 | hooks1.ui_section = 'hooks' |
|
110 | 112 | hooks1.ui_key = 'changegroup.update' |
|
111 | 113 | hooks1.ui_value = 'hg update >&2' |
|
112 | 114 | |
|
113 | 115 | hooks2 = HgAppUi() |
|
114 | 116 | hooks2.ui_section = 'hooks' |
|
115 | 117 | hooks2.ui_key = 'changegroup.repo_size' |
|
116 | 118 | hooks2.ui_value = 'python:pylons_app.lib.hooks.repo_size' |
|
117 | 119 | |
|
118 | 120 | web1 = HgAppUi() |
|
119 | 121 | web1.ui_section = 'web' |
|
120 | 122 | web1.ui_key = 'push_ssl' |
|
121 | 123 | web1.ui_value = 'false' |
|
122 | 124 | |
|
123 | 125 | web2 = HgAppUi() |
|
124 | 126 | web2.ui_section = 'web' |
|
125 | 127 | web2.ui_key = 'allow_archive' |
|
126 | 128 | web2.ui_value = 'gz zip bz2' |
|
127 | 129 | |
|
128 | 130 | web3 = HgAppUi() |
|
129 | 131 | web3.ui_section = 'web' |
|
130 | 132 | web3.ui_key = 'allow_push' |
|
131 | 133 | web3.ui_value = '*' |
|
132 | 134 | |
|
133 | 135 | web4 = HgAppUi() |
|
134 | 136 | web4.ui_section = 'web' |
|
135 | 137 | web4.ui_key = 'baseurl' |
|
136 | 138 | web4.ui_value = '/' |
|
137 | 139 | |
|
138 | 140 | paths = HgAppUi() |
|
139 | 141 | paths.ui_section = 'paths' |
|
140 | 142 | paths.ui_key = '/' |
|
141 | 143 | paths.ui_value = os.path.join(path, '*') |
|
142 | 144 | |
|
143 | 145 | |
|
144 | 146 | hgsettings1 = HgAppSettings() |
|
145 | 147 | |
|
146 | 148 | hgsettings1.app_settings_name = 'realm' |
|
147 | 149 | hgsettings1.app_settings_value = 'hg-app authentication' |
|
148 | 150 | |
|
149 | 151 | hgsettings2 = HgAppSettings() |
|
150 | 152 | hgsettings2.app_settings_name = 'title' |
|
151 | 153 | hgsettings2.app_settings_value = 'hg-app' |
|
152 | 154 | |
|
153 | 155 | try: |
|
154 | 156 | self.sa.add(hooks1) |
|
155 | 157 | self.sa.add(hooks2) |
|
156 | 158 | self.sa.add(web1) |
|
157 | 159 | self.sa.add(web2) |
|
158 | 160 | self.sa.add(web3) |
|
159 | 161 | self.sa.add(web4) |
|
160 | 162 | self.sa.add(paths) |
|
161 | 163 | self.sa.add(hgsettings1) |
|
162 | 164 | self.sa.add(hgsettings2) |
|
163 | 165 | self.sa.commit() |
|
164 | 166 | except: |
|
165 | 167 | self.sa.rollback() |
|
166 | 168 | raise |
|
167 | 169 | log.info('created ui config') |
|
168 | 170 | |
|
169 | def create_user(self, username, password, admin=False): | |
|
171 | def create_user(self, username, password, email='', admin=False): | |
|
170 | 172 | log.info('creating administrator user %s', username) |
|
171 | 173 | new_user = User() |
|
172 | 174 | new_user.username = username |
|
173 | 175 | new_user.password = get_crypt_password(password) |
|
174 | 176 | new_user.name = 'Hg' |
|
175 | 177 | new_user.lastname = 'Admin' |
|
176 |
new_user.email = |
|
|
178 | new_user.email = email | |
|
177 | 179 | new_user.admin = admin |
|
178 | 180 | new_user.active = True |
|
179 | 181 | |
|
180 | 182 | try: |
|
181 | 183 | self.sa.add(new_user) |
|
182 | 184 | self.sa.commit() |
|
183 | 185 | except: |
|
184 | 186 | self.sa.rollback() |
|
185 | 187 | raise |
|
186 | 188 | |
|
187 | 189 | def create_default_user(self): |
|
188 | 190 | log.info('creating default user') |
|
189 | 191 | #create default user for handling default permissions. |
|
190 | 192 | def_user = User() |
|
191 | 193 | def_user.username = 'default' |
|
192 | 194 | def_user.password = get_crypt_password(str(uuid.uuid1())[:8]) |
|
193 | 195 | def_user.name = 'default' |
|
194 | 196 | def_user.lastname = 'default' |
|
195 | 197 | def_user.email = 'default@default.com' |
|
196 | 198 | def_user.admin = False |
|
197 | 199 | def_user.active = False |
|
198 | 200 | try: |
|
199 | 201 | self.sa.add(def_user) |
|
200 | 202 | self.sa.commit() |
|
201 | 203 | except: |
|
202 | 204 | self.sa.rollback() |
|
203 | 205 | raise |
|
204 | 206 | |
|
205 | 207 | def create_permissions(self): |
|
206 | 208 | #module.(access|create|change|delete)_[name] |
|
207 | 209 | #module.(read|write|owner) |
|
208 | 210 | perms = [('repository.none', 'Repository no access'), |
|
209 | 211 | ('repository.read', 'Repository read access'), |
|
210 | 212 | ('repository.write', 'Repository write access'), |
|
211 | 213 | ('repository.admin', 'Repository admin access'), |
|
212 | 214 | ('hg.admin', 'Hg Administrator'), |
|
213 | 215 | ('hg.create.repository', 'Repository create'), |
|
214 | 216 | ('hg.create.none', 'Repository creation disabled'), |
|
215 | 217 | ('hg.register.none', 'Register disabled'), |
|
216 | 218 | ('hg.register.manual_activate', 'Register new user with hg-app without manual activation'), |
|
217 | 219 | ('hg.register.auto_activate', 'Register new user with hg-app without auto activation'), |
|
218 | 220 | ] |
|
219 | 221 | |
|
220 | 222 | for p in perms: |
|
221 | 223 | new_perm = Permission() |
|
222 | 224 | new_perm.permission_name = p[0] |
|
223 | 225 | new_perm.permission_longname = p[1] |
|
224 | 226 | try: |
|
225 | 227 | self.sa.add(new_perm) |
|
226 | 228 | self.sa.commit() |
|
227 | 229 | except: |
|
228 | 230 | self.sa.rollback() |
|
229 | 231 | raise |
|
230 | 232 | |
|
231 | 233 | def populate_default_permissions(self): |
|
232 | 234 | log.info('creating default user permissions') |
|
233 | 235 | |
|
234 | 236 | default_user = self.sa.query(User)\ |
|
235 | 237 | .filter(User.username == 'default').scalar() |
|
236 | 238 | |
|
237 | 239 | reg_perm = UserToPerm() |
|
238 | 240 | reg_perm.user = default_user |
|
239 | 241 | reg_perm.permission = self.sa.query(Permission)\ |
|
240 | 242 | .filter(Permission.permission_name == 'hg.register.manual_activate')\ |
|
241 | 243 | .scalar() |
|
242 | 244 | |
|
243 | 245 | create_repo_perm = UserToPerm() |
|
244 | 246 | create_repo_perm.user = default_user |
|
245 | 247 | create_repo_perm.permission = self.sa.query(Permission)\ |
|
246 | 248 | .filter(Permission.permission_name == 'hg.create.repository')\ |
|
247 | 249 | .scalar() |
|
248 | 250 | |
|
249 | 251 | default_repo_perm = UserToPerm() |
|
250 | 252 | default_repo_perm.user = default_user |
|
251 | 253 | default_repo_perm.permission = self.sa.query(Permission)\ |
|
252 | 254 | .filter(Permission.permission_name == 'repository.read')\ |
|
253 | 255 | .scalar() |
|
254 | 256 | |
|
255 | 257 | try: |
|
256 | 258 | self.sa.add(reg_perm) |
|
257 | 259 | self.sa.add(create_repo_perm) |
|
258 | 260 | self.sa.add(default_repo_perm) |
|
259 | 261 | self.sa.commit() |
|
260 | 262 | except: |
|
261 | 263 | self.sa.rollback() |
|
262 | 264 | raise |
|
263 | 265 |
@@ -1,369 +1,374 b'' | |||
|
1 | 1 | """Helper functions |
|
2 | 2 | |
|
3 | 3 | Consists of functions to typically be used within templates, but also |
|
4 | 4 | available to Controllers. This module is available to both as 'h'. |
|
5 | 5 | """ |
|
6 | 6 | from pygments.formatters import HtmlFormatter |
|
7 | 7 | from pygments import highlight as code_highlight |
|
8 | 8 | from pylons import url, app_globals as g |
|
9 | 9 | from pylons.i18n.translation import _, ungettext |
|
10 | 10 | from vcs.utils.annotate import annotate_highlight |
|
11 | 11 | from webhelpers.html import literal, HTML, escape |
|
12 | 12 | from webhelpers.html.tools import * |
|
13 | 13 | from webhelpers.html.builder import make_tag |
|
14 | 14 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ |
|
15 | 15 | end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \ |
|
16 | 16 | link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \ |
|
17 | 17 | password, textarea, title, ul, xml_declaration, radio |
|
18 | 18 | from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \ |
|
19 | 19 | mail_to, strip_links, strip_tags, tag_re |
|
20 | 20 | from webhelpers.number import format_byte_size, format_bit_size |
|
21 | 21 | from webhelpers.pylonslib import Flash as _Flash |
|
22 | 22 | from webhelpers.pylonslib.secure_form import secure_form |
|
23 | 23 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ |
|
24 | 24 | convert_misc_entities, lchop, plural, rchop, remove_formatting, \ |
|
25 | 25 | replace_whitespace, urlify, truncate, wrap_paragraphs |
|
26 | 26 | |
|
27 | 27 | #Custom helpers here :) |
|
28 | 28 | class _Link(object): |
|
29 | 29 | ''' |
|
30 | 30 | Make a url based on label and url with help of url_for |
|
31 | 31 | @param label:name of link if not defined url is used |
|
32 | 32 | @param url: the url for link |
|
33 | 33 | ''' |
|
34 | 34 | |
|
35 | 35 | def __call__(self, label='', *url_, **urlargs): |
|
36 | 36 | if label is None or '': |
|
37 | 37 | label = url |
|
38 | 38 | link_fn = link_to(label, url(*url_, **urlargs)) |
|
39 | 39 | return link_fn |
|
40 | 40 | |
|
41 | 41 | link = _Link() |
|
42 | 42 | |
|
43 | 43 | class _GetError(object): |
|
44 | 44 | |
|
45 | 45 | def __call__(self, field_name, form_errors): |
|
46 | 46 | tmpl = """<span class="error_msg">%s</span>""" |
|
47 | 47 | if form_errors and form_errors.has_key(field_name): |
|
48 | 48 | return literal(tmpl % form_errors.get(field_name)) |
|
49 | 49 | |
|
50 | 50 | get_error = _GetError() |
|
51 | 51 | |
|
52 | 52 | def recursive_replace(str, replace=' '): |
|
53 | 53 | """ |
|
54 | 54 | Recursive replace of given sign to just one instance |
|
55 | 55 | @param str: given string |
|
56 | 56 | @param replace:char to find and replace multiple instances |
|
57 | 57 | |
|
58 | 58 | Examples:: |
|
59 | 59 | >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-') |
|
60 | 60 | 'Mighty-Mighty-Bo-sstones' |
|
61 | 61 | """ |
|
62 | 62 | |
|
63 | 63 | if str.find(replace * 2) == -1: |
|
64 | 64 | return str |
|
65 | 65 | else: |
|
66 | 66 | str = str.replace(replace * 2, replace) |
|
67 | 67 | return recursive_replace(str, replace) |
|
68 | 68 | |
|
69 | 69 | class _ToolTip(object): |
|
70 | 70 | |
|
71 | 71 | def __call__(self, tooltip_title, trim_at=50): |
|
72 | 72 | """ |
|
73 | 73 | Special function just to wrap our text into nice formatted autowrapped |
|
74 | 74 | text |
|
75 | 75 | @param tooltip_title: |
|
76 | 76 | """ |
|
77 | 77 | |
|
78 | 78 | return wrap_paragraphs(escape(tooltip_title), trim_at)\ |
|
79 | 79 | .replace('\n', '<br/>') |
|
80 | 80 | |
|
81 | 81 | def activate(self): |
|
82 | 82 | """ |
|
83 | 83 | Adds tooltip mechanism to the given Html all tooltips have to have |
|
84 | 84 | set class tooltip and set attribute tooltip_title. |
|
85 | 85 | Then a tooltip will be generated based on that |
|
86 | 86 | All with yui js tooltip |
|
87 | 87 | """ |
|
88 | 88 | |
|
89 | 89 | js = ''' |
|
90 | 90 | YAHOO.util.Event.onDOMReady(function(){ |
|
91 | 91 | function toolTipsId(){ |
|
92 | 92 | var ids = []; |
|
93 | 93 | var tts = YAHOO.util.Dom.getElementsByClassName('tooltip'); |
|
94 | 94 | |
|
95 | 95 | for (var i = 0; i < tts.length; i++) { |
|
96 | 96 | //if element doesn not have and id autgenerate one for tooltip |
|
97 | 97 | |
|
98 | 98 | if (!tts[i].id){ |
|
99 | 99 | tts[i].id='tt'+i*100; |
|
100 | 100 | } |
|
101 | 101 | ids.push(tts[i].id); |
|
102 | 102 | } |
|
103 | 103 | return ids |
|
104 | 104 | }; |
|
105 | 105 | var myToolTips = new YAHOO.widget.Tooltip("tooltip", { |
|
106 | 106 | context: toolTipsId(), |
|
107 | 107 | monitorresize:false, |
|
108 | 108 | xyoffset :[0,0], |
|
109 | 109 | autodismissdelay:300000, |
|
110 | 110 | hidedelay:5, |
|
111 | 111 | showdelay:20, |
|
112 | 112 | }); |
|
113 | 113 | |
|
114 | 114 | //Mouse Over event disabled for new repositories since they dont |
|
115 | 115 | //have last commit message |
|
116 | 116 | myToolTips.contextMouseOverEvent.subscribe( |
|
117 | 117 | function(type, args) { |
|
118 | 118 | var context = args[0]; |
|
119 | 119 | var txt = context.getAttribute('tooltip_title'); |
|
120 | 120 | if(txt){ |
|
121 | 121 | return true; |
|
122 | 122 | } |
|
123 | 123 | else{ |
|
124 | 124 | return false; |
|
125 | 125 | } |
|
126 | 126 | }); |
|
127 | 127 | |
|
128 | 128 | |
|
129 | 129 | // Set the text for the tooltip just before we display it. Lazy method |
|
130 | 130 | myToolTips.contextTriggerEvent.subscribe( |
|
131 | 131 | function(type, args) { |
|
132 | 132 | |
|
133 | 133 | |
|
134 | 134 | var context = args[0]; |
|
135 | 135 | |
|
136 | 136 | var txt = context.getAttribute('tooltip_title'); |
|
137 | 137 | this.cfg.setProperty("text", txt); |
|
138 | 138 | |
|
139 | 139 | |
|
140 | 140 | // positioning of tooltip |
|
141 | 141 | var tt_w = this.element.clientWidth; |
|
142 | 142 | var tt_h = this.element.clientHeight; |
|
143 | 143 | |
|
144 | 144 | var context_w = context.offsetWidth; |
|
145 | 145 | var context_h = context.offsetHeight; |
|
146 | 146 | |
|
147 | 147 | var pos_x = YAHOO.util.Dom.getX(context); |
|
148 | 148 | var pos_y = YAHOO.util.Dom.getY(context); |
|
149 | 149 | |
|
150 | 150 | var display_strategy = 'top'; |
|
151 | 151 | var xy_pos = [0,0]; |
|
152 | 152 | switch (display_strategy){ |
|
153 | 153 | |
|
154 | 154 | case 'top': |
|
155 | 155 | var cur_x = (pos_x+context_w/2)-(tt_w/2); |
|
156 | 156 | var cur_y = pos_y-tt_h-4; |
|
157 | 157 | xy_pos = [cur_x,cur_y]; |
|
158 | 158 | break; |
|
159 | 159 | case 'bottom': |
|
160 | 160 | var cur_x = (pos_x+context_w/2)-(tt_w/2); |
|
161 | 161 | var cur_y = pos_y+context_h+4; |
|
162 | 162 | xy_pos = [cur_x,cur_y]; |
|
163 | 163 | break; |
|
164 | 164 | case 'left': |
|
165 | 165 | var cur_x = (pos_x-tt_w-4); |
|
166 | 166 | var cur_y = pos_y-((tt_h/2)-context_h/2); |
|
167 | 167 | xy_pos = [cur_x,cur_y]; |
|
168 | 168 | break; |
|
169 | 169 | case 'right': |
|
170 | 170 | var cur_x = (pos_x+context_w+4); |
|
171 | 171 | var cur_y = pos_y-((tt_h/2)-context_h/2); |
|
172 | 172 | xy_pos = [cur_x,cur_y]; |
|
173 | 173 | break; |
|
174 | 174 | default: |
|
175 | 175 | var cur_x = (pos_x+context_w/2)-(tt_w/2); |
|
176 | 176 | var cur_y = pos_y-tt_h-4; |
|
177 | 177 | xy_pos = [cur_x,cur_y]; |
|
178 | 178 | break; |
|
179 | 179 | |
|
180 | 180 | } |
|
181 | 181 | |
|
182 | 182 | this.cfg.setProperty("xy",xy_pos); |
|
183 | 183 | |
|
184 | 184 | }); |
|
185 | 185 | |
|
186 | 186 | //Mouse out |
|
187 | 187 | myToolTips.contextMouseOutEvent.subscribe( |
|
188 | 188 | function(type, args) { |
|
189 | 189 | var context = args[0]; |
|
190 | 190 | |
|
191 | 191 | }); |
|
192 | 192 | }); |
|
193 | 193 | ''' |
|
194 | 194 | return literal(js) |
|
195 | 195 | |
|
196 | 196 | tooltip = _ToolTip() |
|
197 | 197 | |
|
198 | 198 | class _FilesBreadCrumbs(object): |
|
199 | 199 | |
|
200 | 200 | def __call__(self, repo_name, rev, paths): |
|
201 | 201 | url_l = [link_to(repo_name, url('files_home', |
|
202 | 202 | repo_name=repo_name, |
|
203 | 203 | revision=rev, f_path=''))] |
|
204 | 204 | paths_l = paths.split('/') |
|
205 | 205 | |
|
206 | 206 | for cnt, p in enumerate(paths_l, 1): |
|
207 | 207 | if p != '': |
|
208 | 208 | url_l.append(link_to(p, url('files_home', |
|
209 | 209 | repo_name=repo_name, |
|
210 | 210 | revision=rev, |
|
211 | 211 | f_path='/'.join(paths_l[:cnt])))) |
|
212 | 212 | |
|
213 | 213 | return literal('/'.join(url_l)) |
|
214 | 214 | |
|
215 | 215 | files_breadcrumbs = _FilesBreadCrumbs() |
|
216 | 216 | |
|
217 | 217 | def pygmentize(filenode, **kwargs): |
|
218 | 218 | """ |
|
219 | 219 | pygmentize function using pygments |
|
220 | 220 | @param filenode: |
|
221 | 221 | """ |
|
222 | 222 | return literal(code_highlight(filenode.content, |
|
223 | 223 | filenode.lexer, HtmlFormatter(**kwargs))) |
|
224 | 224 | |
|
225 | 225 | def pygmentize_annotation(filenode, **kwargs): |
|
226 | 226 | """ |
|
227 | 227 | pygmentize function for annotation |
|
228 | 228 | @param filenode: |
|
229 | 229 | """ |
|
230 | 230 | |
|
231 | 231 | color_dict = {} |
|
232 | 232 | def gen_color(): |
|
233 | 233 | """generator for getting 10k of evenly distibuted colors using hsv color |
|
234 | 234 | and golden ratio. |
|
235 | 235 | """ |
|
236 | 236 | import colorsys |
|
237 | 237 | n = 10000 |
|
238 | 238 | golden_ratio = 0.618033988749895 |
|
239 | 239 | h = 0.22717784590367374 |
|
240 | 240 | #generate 10k nice web friendly colors in the same order |
|
241 | 241 | for c in xrange(n): |
|
242 | 242 | h += golden_ratio |
|
243 | 243 | h %= 1 |
|
244 | 244 | HSV_tuple = [h, 0.95, 0.95] |
|
245 | 245 | RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple) |
|
246 | 246 | yield map(lambda x:str(int(x * 256)), RGB_tuple) |
|
247 | 247 | |
|
248 | 248 | cgenerator = gen_color() |
|
249 | 249 | |
|
250 | 250 | def get_color_string(cs): |
|
251 | 251 | if color_dict.has_key(cs): |
|
252 | 252 | col = color_dict[cs] |
|
253 | 253 | else: |
|
254 | 254 | col = color_dict[cs] = cgenerator.next() |
|
255 | 255 | return "color: rgb(%s)! important;" % (', '.join(col)) |
|
256 | 256 | |
|
257 | 257 | def url_func(changeset): |
|
258 | 258 | tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \ |
|
259 | 259 | " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>" |
|
260 | 260 | |
|
261 | 261 | tooltip_html = tooltip_html % (changeset.author, |
|
262 | 262 | changeset.date, |
|
263 | 263 | tooltip(changeset.message)) |
|
264 | 264 | lnk_format = 'r%-5s:%s' % (changeset.revision, |
|
265 | 265 | changeset.raw_id) |
|
266 | 266 | uri = link_to( |
|
267 | 267 | lnk_format, |
|
268 | 268 | url('changeset_home', repo_name=changeset.repository.name, |
|
269 | 269 | revision=changeset.raw_id), |
|
270 | 270 | style=get_color_string(changeset.raw_id), |
|
271 | 271 | class_='tooltip', |
|
272 | 272 | tooltip_title=tooltip_html |
|
273 | 273 | ) |
|
274 | 274 | |
|
275 | 275 | uri += '\n' |
|
276 | 276 | return uri |
|
277 | 277 | return literal(annotate_highlight(filenode, url_func, **kwargs)) |
|
278 | 278 | |
|
279 | 279 | def repo_name_slug(value): |
|
280 | """ | |
|
281 | Return slug of name of repository | |
|
280 | """Return slug of name of repository | |
|
281 | This function is called on each creation/modification | |
|
282 | of repository to prevent bad names in repo | |
|
282 | 283 | """ |
|
283 |
slug = |
|
|
284 | for c in """=[]\;'"<>,/~!@#$%^&*()+{}|:""": | |
|
284 | slug = remove_formatting(value) | |
|
285 | slug = strip_tags(slug) | |
|
286 | ||
|
287 | for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """: | |
|
285 | 288 | slug = slug.replace(c, '-') |
|
286 | 289 | slug = recursive_replace(slug, '-') |
|
290 | slug = collapse(slug, '-') | |
|
287 | 291 | return slug |
|
288 | 292 | |
|
289 | 293 | def get_changeset_safe(repo, rev): |
|
290 | 294 | from vcs.backends.base import BaseRepository |
|
291 | 295 | from vcs.exceptions import RepositoryError |
|
292 | 296 | if not isinstance(repo, BaseRepository): |
|
293 | 297 | raise Exception('You must pass an Repository ' |
|
294 | 298 | 'object as first argument got %s', type(repo)) |
|
295 | 299 | |
|
296 | 300 | try: |
|
297 | 301 | cs = repo.get_changeset(rev) |
|
298 | 302 | except RepositoryError: |
|
299 | 303 | from pylons_app.lib.utils import EmptyChangeset |
|
300 | 304 | cs = EmptyChangeset() |
|
301 | 305 | return cs |
|
302 | 306 | |
|
303 | 307 | |
|
304 | 308 | flash = _Flash() |
|
305 | 309 | |
|
306 | 310 | |
|
307 | 311 | #=============================================================================== |
|
308 | 312 | # MERCURIAL FILTERS available via h. |
|
309 | 313 | #=============================================================================== |
|
310 | 314 | from mercurial import util |
|
311 | 315 | from mercurial.templatefilters import age as _age, person as _person |
|
312 | 316 | |
|
313 | 317 | age = lambda x:_age(x) |
|
314 | 318 | capitalize = lambda x: x.capitalize() |
|
315 | 319 | date = lambda x: util.datestr(x) |
|
316 | 320 | email = util.email |
|
317 | 321 | email_or_none = lambda x: util.email(x) if util.email(x) != x else None |
|
318 | 322 | person = lambda x: _person(x) |
|
319 | 323 | hgdate = lambda x: "%d %d" % x |
|
320 | 324 | isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2') |
|
321 | 325 | isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2') |
|
322 | 326 | localdate = lambda x: (x[0], util.makedate()[1]) |
|
323 | 327 | rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2") |
|
328 | rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S") | |
|
324 | 329 | rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2") |
|
325 | 330 | time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2") |
|
326 | 331 | |
|
327 | 332 | |
|
328 | 333 | #=============================================================================== |
|
329 | 334 | # PERMS |
|
330 | 335 | #=============================================================================== |
|
331 | 336 | from pylons_app.lib.auth import HasPermissionAny, HasPermissionAll, \ |
|
332 | 337 | HasRepoPermissionAny, HasRepoPermissionAll |
|
333 | 338 | |
|
334 | 339 | #=============================================================================== |
|
335 | 340 | # GRAVATAR URL |
|
336 | 341 | #=============================================================================== |
|
337 | 342 | import hashlib |
|
338 | 343 | import urllib |
|
339 | 344 | from pylons import request |
|
340 | 345 | |
|
341 | 346 | def gravatar_url(email_address, size=30): |
|
342 | 347 | ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME') |
|
343 | 348 | default = 'identicon' |
|
344 | 349 | baseurl_nossl = "http://www.gravatar.com/avatar/" |
|
345 | 350 | baseurl_ssl = "https://secure.gravatar.com/avatar/" |
|
346 | 351 | baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl |
|
347 | 352 | |
|
348 | 353 | |
|
349 | 354 | # construct the url |
|
350 | 355 | gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?" |
|
351 | 356 | gravatar_url += urllib.urlencode({'d':default, 's':str(size)}) |
|
352 | 357 | |
|
353 | 358 | return gravatar_url |
|
354 | 359 | |
|
355 | 360 | def safe_unicode(str): |
|
356 | 361 | """safe unicode function. In case of UnicodeDecode error we try to return |
|
357 | 362 | unicode with errors replace, if this failes we return unicode with |
|
358 | 363 | string_escape decoding """ |
|
359 | 364 | |
|
360 | 365 | try: |
|
361 | 366 | u_str = unicode(str) |
|
362 | 367 | except UnicodeDecodeError: |
|
363 | 368 | try: |
|
364 | 369 | u_str = unicode(str, 'utf-8', 'replace') |
|
365 | 370 | except UnicodeDecodeError: |
|
366 | 371 | #incase we have a decode error just represent as byte string |
|
367 | 372 | u_str = unicode(str(str).encode('string_escape')) |
|
368 | 373 | |
|
369 | 374 | return u_str |
@@ -1,41 +1,139 b'' | |||
|
1 | import sys | |
|
1 | from os.path import dirname as dn, join as jn | |
|
2 | from pylons_app.config.environment import load_environment | |
|
3 | from pylons_app.model.hg_model import HgModel | |
|
4 | from shutil import rmtree | |
|
5 | from webhelpers.html.builder import escape | |
|
6 | from vcs.utils.lazy import LazyProperty | |
|
7 | ||
|
8 | from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter | |
|
9 | from whoosh.fields import TEXT, ID, STORED, Schema, FieldType | |
|
10 | from whoosh.index import create_in, open_dir | |
|
11 | from whoosh.formats import Characters | |
|
12 | from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter | |
|
13 | ||
|
2 | 14 | import os |
|
3 | from pidlock import LockHeld, DaemonLock | |
|
15 | import sys | |
|
4 | 16 | import traceback |
|
5 | 17 | |
|
6 | from os.path import dirname as dn | |
|
7 | from os.path import join as jn | |
|
8 | ||
|
9 | 18 | #to get the pylons_app import |
|
10 | 19 | sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) |
|
11 | 20 | |
|
12 | from pylons_app.config.environment import load_environment | |
|
13 | from pylons_app.model.hg_model import HgModel | |
|
14 | from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter | |
|
15 | from whoosh.fields import TEXT, ID, STORED, Schema | |
|
16 | from whoosh.index import create_in, open_dir | |
|
17 | from shutil import rmtree | |
|
18 | 21 | |
|
19 | 22 | #LOCATION WE KEEP THE INDEX |
|
20 | 23 | IDX_LOCATION = jn(dn(dn(dn(dn(os.path.abspath(__file__))))), 'data', 'index') |
|
21 | 24 | |
|
22 | 25 | #EXTENSIONS WE WANT TO INDEX CONTENT OFF |
|
23 |
INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', |
|
|
24 |
'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl', |
|
|
25 |
'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp', |
|
|
26 |
'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3', |
|
|
27 |
'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql', |
|
|
28 |
'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml','xsl','xslt', |
|
|
26 | INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', | |
|
27 | 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl', | |
|
28 | 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp', | |
|
29 | 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3', | |
|
30 | 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql', | |
|
31 | 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', | |
|
29 | 32 | 'yaws'] |
|
30 | 33 | |
|
31 | 34 | #CUSTOM ANALYZER wordsplit + lowercase filter |
|
32 | 35 | ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter() |
|
33 | 36 | |
|
37 | ||
|
34 | 38 | #INDEX SCHEMA DEFINITION |
|
35 | 39 | SCHEMA = Schema(owner=TEXT(), |
|
36 | 40 | repository=TEXT(stored=True), |
|
37 | 41 | path=ID(stored=True, unique=True), |
|
38 |
content= |
|
|
39 | modtime=STORED(),extension=TEXT(stored=True)) | |
|
42 | content=FieldType(format=Characters(ANALYZER), | |
|
43 | scorable=True, stored=True), | |
|
44 | modtime=STORED(), extension=TEXT(stored=True)) | |
|
45 | ||
|
46 | ||
|
47 | IDX_NAME = 'HG_INDEX' | |
|
48 | FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n') | |
|
49 | FRAGMENTER = SimpleFragmenter(200) | |
|
50 | ||
|
51 | class ResultWrapper(object): | |
|
52 | def __init__(self, searcher, matcher, highlight_items): | |
|
53 | self.searcher = searcher | |
|
54 | self.matcher = matcher | |
|
55 | self.highlight_items = highlight_items | |
|
56 | self.fragment_size = 200 / 2 | |
|
57 | ||
|
58 | @LazyProperty | |
|
59 | def doc_ids(self): | |
|
60 | docs_id = [] | |
|
61 | while self.matcher.is_active(): | |
|
62 | docnum = self.matcher.id() | |
|
63 | chunks = [offsets for offsets in self.get_chunks()] | |
|
64 | docs_id.append([docnum, chunks]) | |
|
65 | self.matcher.next() | |
|
66 | return docs_id | |
|
67 | ||
|
68 | def __str__(self): | |
|
69 | return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids)) | |
|
70 | ||
|
71 | def __repr__(self): | |
|
72 | return self.__str__() | |
|
73 | ||
|
74 | def __len__(self): | |
|
75 | return len(self.doc_ids) | |
|
76 | ||
|
77 | def __iter__(self): | |
|
78 | """ | |
|
79 | Allows Iteration over results,and lazy generate content | |
|
80 | ||
|
81 | *Requires* implementation of ``__getitem__`` method. | |
|
82 | """ | |
|
83 | for docid in self.doc_ids: | |
|
84 | yield self.get_full_content(docid) | |
|
40 | 85 | |
|
41 | IDX_NAME = 'HG_INDEX' No newline at end of file | |
|
86 | def __getslice__(self, i, j): | |
|
87 | """ | |
|
88 | Slicing of resultWrapper | |
|
89 | """ | |
|
90 | slice = [] | |
|
91 | for docid in self.doc_ids[i:j]: | |
|
92 | slice.append(self.get_full_content(docid)) | |
|
93 | return slice | |
|
94 | ||
|
95 | ||
|
96 | def get_full_content(self, docid): | |
|
97 | res = self.searcher.stored_fields(docid[0]) | |
|
98 | f_path = res['path'][res['path'].find(res['repository']) \ | |
|
99 | + len(res['repository']):].lstrip('/') | |
|
100 | ||
|
101 | content_short = self.get_short_content(res, docid[1]) | |
|
102 | res.update({'content_short':content_short, | |
|
103 | 'content_short_hl':self.highlight(content_short), | |
|
104 | 'f_path':f_path}) | |
|
105 | ||
|
106 | return res | |
|
107 | ||
|
108 | def get_short_content(self, res, chunks): | |
|
109 | ||
|
110 | return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks]) | |
|
111 | ||
|
112 | def get_chunks(self): | |
|
113 | """ | |
|
114 | Smart function that implements chunking the content | |
|
115 | but not overlap chunks so it doesn't highlight the same | |
|
116 | close occurences twice. | |
|
117 | @param matcher: | |
|
118 | @param size: | |
|
119 | """ | |
|
120 | memory = [(0, 0)] | |
|
121 | for span in self.matcher.spans(): | |
|
122 | start = span.startchar or 0 | |
|
123 | end = span.endchar or 0 | |
|
124 | start_offseted = max(0, start - self.fragment_size) | |
|
125 | end_offseted = end + self.fragment_size | |
|
126 | ||
|
127 | if start_offseted < memory[-1][1]: | |
|
128 | start_offseted = memory[-1][1] | |
|
129 | memory.append((start_offseted, end_offseted,)) | |
|
130 | yield (start_offseted, end_offseted,) | |
|
131 | ||
|
132 | def highlight(self, content, top=5): | |
|
133 | hl = highlight(escape(content), | |
|
134 | self.highlight_items, | |
|
135 | analyzer=ANALYZER, | |
|
136 | fragmenter=FRAGMENTER, | |
|
137 | formatter=FORMATTER, | |
|
138 | top=top) | |
|
139 | return hl |
@@ -1,226 +1,238 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # whoosh indexer daemon for hg-app |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on Jan 26, 2010 |
|
22 | 22 | |
|
23 | 23 | @author: marcink |
|
24 | 24 | A deamon will read from task table and run tasks |
|
25 | 25 | """ |
|
26 | 26 | import sys |
|
27 | 27 | import os |
|
28 | 28 | from os.path import dirname as dn |
|
29 | 29 | from os.path import join as jn |
|
30 | 30 | |
|
31 | 31 | #to get the pylons_app import |
|
32 | 32 | project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) |
|
33 | 33 | sys.path.append(project_path) |
|
34 | 34 | |
|
35 | from pidlock import LockHeld, DaemonLock | |
|
36 | import traceback | |
|
37 | from pylons_app.config.environment import load_environment | |
|
35 | from pylons_app.lib.pidlock import LockHeld, DaemonLock | |
|
38 | 36 | from pylons_app.model.hg_model import HgModel |
|
39 | 37 | from pylons_app.lib.helpers import safe_unicode |
|
40 | 38 | from whoosh.index import create_in, open_dir |
|
41 | 39 | from shutil import rmtree |
|
42 |
from pylons_app.lib.indexers import |
|
|
43 | SCHEMA, IDX_NAME | |
|
40 | from pylons_app.lib.indexers import INDEX_EXTENSIONS, IDX_LOCATION, SCHEMA, IDX_NAME | |
|
44 | 41 | |
|
45 | 42 | import logging |
|
46 | import logging.config | |
|
47 | logging.config.fileConfig(jn(project_path, 'development.ini')) | |
|
43 | ||
|
48 | 44 | log = logging.getLogger('whooshIndexer') |
|
45 | # create logger | |
|
46 | log.setLevel(logging.DEBUG) | |
|
47 | log.propagate = False | |
|
48 | # create console handler and set level to debug | |
|
49 | ch = logging.StreamHandler() | |
|
50 | ch.setLevel(logging.DEBUG) | |
|
51 | ||
|
52 | # create formatter | |
|
53 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
|
54 | ||
|
55 | # add formatter to ch | |
|
56 | ch.setFormatter(formatter) | |
|
57 | ||
|
58 | # add ch to logger | |
|
59 | log.addHandler(ch) | |
|
49 | 60 | |
|
50 | 61 | def scan_paths(root_location): |
|
51 | 62 | return HgModel.repo_scan('/', root_location, None, True) |
|
52 | 63 | |
|
53 | 64 | class WhooshIndexingDaemon(object): |
|
54 | 65 | """Deamon for atomic jobs""" |
|
55 | 66 | |
|
56 | 67 | def __init__(self, indexname='HG_INDEX', repo_location=None): |
|
57 | 68 | self.indexname = indexname |
|
58 | 69 | self.repo_location = repo_location |
|
59 | 70 | self.initial = False |
|
60 | 71 | if not os.path.isdir(IDX_LOCATION): |
|
61 | 72 | os.mkdir(IDX_LOCATION) |
|
62 | 73 | log.info('Cannot run incremental index since it does not' |
|
63 | 74 | ' yet exist running full build') |
|
64 | 75 | self.initial = True |
|
65 | 76 | |
|
66 | 77 | def get_paths(self, root_dir): |
|
67 | 78 | """recursive walk in root dir and return a set of all path in that dir |
|
68 | 79 | excluding files in .hg dir""" |
|
69 | 80 | index_paths_ = set() |
|
70 | 81 | for path, dirs, files in os.walk(root_dir): |
|
71 | 82 | if path.find('.hg') == -1: |
|
72 | 83 | for f in files: |
|
73 | 84 | index_paths_.add(jn(path, f)) |
|
74 | 85 | |
|
75 | 86 | return index_paths_ |
|
76 | 87 | |
|
77 | 88 | def add_doc(self, writer, path, repo): |
|
78 | 89 | """Adding doc to writer""" |
|
79 | 90 | |
|
80 | 91 | ext = unicode(path.split('/')[-1].split('.')[-1].lower()) |
|
81 | 92 | #we just index the content of choosen files |
|
82 | 93 | if ext in INDEX_EXTENSIONS: |
|
83 | 94 | log.debug(' >> %s [WITH CONTENT]' % path) |
|
84 | 95 | fobj = open(path, 'rb') |
|
85 | 96 | content = fobj.read() |
|
86 | 97 | fobj.close() |
|
87 | 98 | u_content = safe_unicode(content) |
|
88 | 99 | else: |
|
89 | 100 | log.debug(' >> %s' % path) |
|
90 | 101 | #just index file name without it's content |
|
91 | 102 | u_content = u'' |
|
92 | 103 | |
|
93 | 104 | |
|
94 | 105 | |
|
95 | 106 | try: |
|
96 | 107 | os.stat(path) |
|
97 | 108 | writer.add_document(owner=unicode(repo.contact), |
|
98 | 109 | repository=u"%s" % repo.name, |
|
99 | 110 | path=u"%s" % path, |
|
100 | 111 | content=u_content, |
|
101 | 112 | modtime=os.path.getmtime(path), |
|
102 | 113 | extension=ext) |
|
103 | 114 | except OSError, e: |
|
104 | 115 | import errno |
|
105 | 116 | if e.errno == errno.ENOENT: |
|
106 | 117 | log.debug('path %s does not exist or is a broken symlink' % path) |
|
107 | 118 | else: |
|
108 | 119 | raise e |
|
109 | 120 | |
|
110 | 121 | |
|
111 | 122 | def build_index(self): |
|
112 | 123 | if os.path.exists(IDX_LOCATION): |
|
113 | 124 | log.debug('removing previos index') |
|
114 | 125 | rmtree(IDX_LOCATION) |
|
115 | 126 | |
|
116 | 127 | if not os.path.exists(IDX_LOCATION): |
|
117 | 128 | os.mkdir(IDX_LOCATION) |
|
118 | 129 | |
|
119 | 130 | idx = create_in(IDX_LOCATION, SCHEMA, indexname=IDX_NAME) |
|
120 | 131 | writer = idx.writer() |
|
121 | 132 | |
|
122 | 133 | for cnt, repo in enumerate(scan_paths(self.repo_location).values()): |
|
123 | 134 | log.debug('building index @ %s' % repo.path) |
|
124 | 135 | |
|
125 | 136 | for idx_path in self.get_paths(repo.path): |
|
126 | 137 | self.add_doc(writer, idx_path, repo) |
|
127 | 138 | writer.commit(merge=True) |
|
128 | 139 | |
|
129 | 140 | log.debug('>>> FINISHED BUILDING INDEX <<<') |
|
130 | 141 | |
|
131 | 142 | |
|
132 | 143 | def update_index(self): |
|
133 | 144 | log.debug('STARTING INCREMENTAL INDEXING UPDATE') |
|
134 | 145 | |
|
135 | 146 | idx = open_dir(IDX_LOCATION, indexname=self.indexname) |
|
136 | 147 | # The set of all paths in the index |
|
137 | 148 | indexed_paths = set() |
|
138 | 149 | # The set of all paths we need to re-index |
|
139 | 150 | to_index = set() |
|
140 | 151 | |
|
141 | 152 | reader = idx.reader() |
|
142 | 153 | writer = idx.writer() |
|
143 | 154 | |
|
144 | 155 | # Loop over the stored fields in the index |
|
145 | 156 | for fields in reader.all_stored_fields(): |
|
146 | 157 | indexed_path = fields['path'] |
|
147 | 158 | indexed_paths.add(indexed_path) |
|
148 | 159 | |
|
149 | 160 | if not os.path.exists(indexed_path): |
|
150 | 161 | # This file was deleted since it was indexed |
|
151 | 162 | log.debug('removing from index %s' % indexed_path) |
|
152 | 163 | writer.delete_by_term('path', indexed_path) |
|
153 | 164 | |
|
154 | 165 | else: |
|
155 | 166 | # Check if this file was changed since it |
|
156 | 167 | # was indexed |
|
157 | 168 | indexed_time = fields['modtime'] |
|
158 | 169 | |
|
159 | 170 | mtime = os.path.getmtime(indexed_path) |
|
160 | 171 | |
|
161 | 172 | if mtime > indexed_time: |
|
162 | 173 | |
|
163 | 174 | # The file has changed, delete it and add it to the list of |
|
164 | 175 | # files to reindex |
|
165 | 176 | log.debug('adding to reindex list %s' % indexed_path) |
|
166 | 177 | writer.delete_by_term('path', indexed_path) |
|
167 | 178 | to_index.add(indexed_path) |
|
168 | 179 | #writer.commit() |
|
169 | 180 | |
|
170 | 181 | # Loop over the files in the filesystem |
|
171 | 182 | # Assume we have a function that gathers the filenames of the |
|
172 | 183 | # documents to be indexed |
|
173 | 184 | for repo in scan_paths(self.repo_location).values(): |
|
174 | 185 | for path in self.get_paths(repo.path): |
|
175 | 186 | if path in to_index or path not in indexed_paths: |
|
176 | 187 | # This is either a file that's changed, or a new file |
|
177 | 188 | # that wasn't indexed before. So index it! |
|
178 | 189 | self.add_doc(writer, path, repo) |
|
179 | 190 | log.debug('reindexing %s' % path) |
|
180 | 191 | |
|
181 | 192 | writer.commit(merge=True) |
|
182 | 193 | #idx.optimize() |
|
183 | 194 | log.debug('>>> FINISHED <<<') |
|
184 | 195 | |
|
185 | 196 | def run(self, full_index=False): |
|
186 | 197 | """Run daemon""" |
|
187 | 198 | if full_index or self.initial: |
|
188 | 199 | self.build_index() |
|
189 | 200 | else: |
|
190 | 201 | self.update_index() |
|
191 | 202 | |
|
192 | 203 | if __name__ == "__main__": |
|
193 | 204 | arg = sys.argv[1:] |
|
194 | 205 | if len(arg) != 2: |
|
195 | 206 | sys.stderr.write('Please specify indexing type [full|incremental]' |
|
196 | 207 | 'and path to repositories as script args \n') |
|
197 | 208 | sys.exit() |
|
198 | 209 | |
|
199 | 210 | |
|
200 | 211 | if arg[0] == 'full': |
|
201 | 212 | full_index = True |
|
202 | 213 | elif arg[0] == 'incremental': |
|
203 | 214 | # False means looking just for changes |
|
204 | 215 | full_index = False |
|
205 | 216 | else: |
|
206 | 217 | sys.stdout.write('Please use [full|incremental]' |
|
207 | 218 | ' as script first arg \n') |
|
208 | 219 | sys.exit() |
|
209 | 220 | |
|
210 | 221 | if not os.path.isdir(arg[1]): |
|
211 | 222 | sys.stderr.write('%s is not a valid path \n' % arg[1]) |
|
212 | 223 | sys.exit() |
|
213 | 224 | else: |
|
214 | 225 | if arg[1].endswith('/'): |
|
215 | 226 | repo_location = arg[1] + '*' |
|
216 | 227 | else: |
|
217 | 228 | repo_location = arg[1] + '/*' |
|
218 | 229 | |
|
219 | 230 | try: |
|
220 | 231 | l = DaemonLock() |
|
221 | 232 | WhooshIndexingDaemon(repo_location=repo_location)\ |
|
222 | 233 | .run(full_index=full_index) |
|
223 | 234 | l.release() |
|
235 | reload(logging) | |
|
224 | 236 | except LockHeld: |
|
225 | 237 | sys.exit(1) |
|
226 | 238 |
@@ -1,127 +1,127 b'' | |||
|
1 | 1 | import os, time |
|
2 | 2 | import sys |
|
3 | 3 | from warnings import warn |
|
4 | 4 | |
|
5 | 5 | class LockHeld(Exception):pass |
|
6 | 6 | |
|
7 | 7 | |
|
8 | 8 | class DaemonLock(object): |
|
9 |
|
|
|
9 | """daemon locking | |
|
10 | 10 | USAGE: |
|
11 | 11 | try: |
|
12 | 12 | l = lock() |
|
13 | 13 | main() |
|
14 | 14 | l.release() |
|
15 | 15 | except LockHeld: |
|
16 | 16 | sys.exit(1) |
|
17 | ''' | |
|
17 | """ | |
|
18 | 18 | |
|
19 | 19 | def __init__(self, file=None, callbackfn=None, |
|
20 | 20 | desc='daemon lock', debug=False): |
|
21 | 21 | |
|
22 | 22 | self.pidfile = file if file else os.path.join(os.path.dirname(__file__), |
|
23 | 23 | 'running.lock') |
|
24 | 24 | self.callbackfn = callbackfn |
|
25 | 25 | self.desc = desc |
|
26 | 26 | self.debug = debug |
|
27 | 27 | self.held = False |
|
28 | 28 | #run the lock automatically ! |
|
29 | 29 | self.lock() |
|
30 | 30 | |
|
31 | 31 | def __del__(self): |
|
32 | 32 | if self.held: |
|
33 | 33 | |
|
34 | 34 | # warn("use lock.release instead of del lock", |
|
35 | 35 | # category = DeprecationWarning, |
|
36 | 36 | # stacklevel = 2) |
|
37 | 37 | |
|
38 | 38 | # ensure the lock will be removed |
|
39 | 39 | self.release() |
|
40 | 40 | |
|
41 | 41 | |
|
42 | 42 | def lock(self): |
|
43 |
|
|
|
43 | """ | |
|
44 | 44 | locking function, if lock is present it will raise LockHeld exception |
|
45 |
|
|
|
45 | """ | |
|
46 | 46 | lockname = '%s' % (os.getpid()) |
|
47 | 47 | |
|
48 | 48 | self.trylock() |
|
49 | 49 | self.makelock(lockname, self.pidfile) |
|
50 | 50 | return True |
|
51 | 51 | |
|
52 | 52 | def trylock(self): |
|
53 | 53 | running_pid = False |
|
54 | 54 | try: |
|
55 | 55 | pidfile = open(self.pidfile, "r") |
|
56 | 56 | pidfile.seek(0) |
|
57 | 57 | running_pid = pidfile.readline() |
|
58 | 58 | if self.debug: |
|
59 | 59 | print 'lock file present running_pid: %s, checking for execution'\ |
|
60 | 60 | % running_pid |
|
61 | 61 | # Now we check the PID from lock file matches to the current |
|
62 | 62 | # process PID |
|
63 | 63 | if running_pid: |
|
64 | 64 | if os.path.exists("/proc/%s" % running_pid): |
|
65 | 65 | print "You already have an instance of the program running" |
|
66 | 66 | print "It is running as process %s" % running_pid |
|
67 | 67 | raise LockHeld |
|
68 | 68 | else: |
|
69 | 69 | print "Lock File is there but the program is not running" |
|
70 | 70 | print "Removing lock file for the: %s" % running_pid |
|
71 | 71 | self.release() |
|
72 | 72 | except IOError, e: |
|
73 | 73 | if e.errno != 2: |
|
74 | 74 | raise |
|
75 | 75 | |
|
76 | 76 | |
|
77 | 77 | def release(self): |
|
78 |
|
|
|
78 | """ | |
|
79 | 79 | releases the pid by removing the pidfile |
|
80 |
|
|
|
80 | """ | |
|
81 | 81 | if self.callbackfn: |
|
82 | 82 | #execute callback function on release |
|
83 | 83 | if self.debug: |
|
84 | 84 | print 'executing callback function %s' % self.callbackfn |
|
85 | 85 | self.callbackfn() |
|
86 | 86 | try: |
|
87 | 87 | if self.debug: |
|
88 | 88 | print 'removing pidfile %s' % self.pidfile |
|
89 | 89 | os.remove(self.pidfile) |
|
90 | 90 | self.held = False |
|
91 | 91 | except OSError, e: |
|
92 | 92 | if self.debug: |
|
93 | 93 | print 'removing pidfile failed %s' % e |
|
94 | 94 | pass |
|
95 | 95 | |
|
96 | 96 | def makelock(self, lockname, pidfile): |
|
97 |
|
|
|
97 | """ | |
|
98 | 98 | this function will make an actual lock |
|
99 | 99 | @param lockname: acctual pid of file |
|
100 | 100 | @param pidfile: the file to write the pid in |
|
101 |
|
|
|
101 | """ | |
|
102 | 102 | if self.debug: |
|
103 | 103 | print 'creating a file %s and pid: %s' % (pidfile, lockname) |
|
104 | 104 | pidfile = open(self.pidfile, "wb") |
|
105 | 105 | pidfile.write(lockname) |
|
106 | 106 | pidfile.close |
|
107 | 107 | self.held = True |
|
108 | 108 | |
|
109 | 109 | |
|
110 | 110 | def main(): |
|
111 | 111 | print 'func is running' |
|
112 | 112 | cnt = 20 |
|
113 | 113 | while 1: |
|
114 | 114 | print cnt |
|
115 | 115 | if cnt == 0: |
|
116 | 116 | break |
|
117 | 117 | time.sleep(1) |
|
118 | 118 | cnt -= 1 |
|
119 | 119 | |
|
120 | 120 | |
|
121 | 121 | if __name__ == "__main__": |
|
122 | 122 | try: |
|
123 | 123 | l = DaemonLock(desc='test lock') |
|
124 | 124 | main() |
|
125 | 125 | l.release() |
|
126 | 126 | except LockHeld: |
|
127 | 127 | sys.exit(1) |
@@ -1,57 +1,59 b'' | |||
|
1 | 1 | from sqlalchemy.interfaces import ConnectionProxy |
|
2 | 2 | import time |
|
3 | import logging | |
|
4 | log = logging.getLogger('timerproxy') | |
|
3 | from sqlalchemy import log | |
|
5 | 4 | BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38) |
|
6 | 5 | |
|
7 | 6 | def color_sql(sql): |
|
8 | 7 | COLOR_SEQ = "\033[1;%dm" |
|
9 | 8 | COLOR_SQL = YELLOW |
|
10 | 9 | normal = '\x1b[0m' |
|
11 | 10 | return COLOR_SEQ % COLOR_SQL + sql + normal |
|
12 | 11 | |
|
13 | 12 | def one_space_trim(s): |
|
14 | 13 | if s.find(" ") == -1: |
|
15 | 14 | return s |
|
16 | 15 | else: |
|
17 | 16 | s = s.replace(' ', ' ') |
|
18 | 17 | return one_space_trim(s) |
|
19 | 18 | |
|
20 | 19 | def format_sql(sql): |
|
21 | 20 | sql = color_sql(sql) |
|
22 | 21 | sql = sql.replace('\n', '') |
|
23 | 22 | sql = one_space_trim(sql) |
|
24 | 23 | sql = sql\ |
|
25 | .replace(',',',\n\t')\ | |
|
24 | .replace(',', ',\n\t')\ | |
|
26 | 25 | .replace('SELECT', '\n\tSELECT \n\t')\ |
|
27 | 26 | .replace('UPDATE', '\n\tUPDATE \n\t')\ |
|
28 | 27 | .replace('DELETE', '\n\tDELETE \n\t')\ |
|
29 | 28 | .replace('FROM', '\n\tFROM')\ |
|
30 | 29 | .replace('ORDER BY', '\n\tORDER BY')\ |
|
31 | 30 | .replace('LIMIT', '\n\tLIMIT')\ |
|
32 | 31 | .replace('WHERE', '\n\tWHERE')\ |
|
33 | 32 | .replace('AND', '\n\tAND')\ |
|
34 | 33 | .replace('LEFT', '\n\tLEFT')\ |
|
35 | 34 | .replace('INNER', '\n\tINNER')\ |
|
36 | 35 | .replace('INSERT', '\n\tINSERT')\ |
|
37 | 36 | .replace('DELETE', '\n\tDELETE') |
|
38 | 37 | return sql |
|
39 | 38 | |
|
40 | 39 | |
|
41 | 40 | class TimerProxy(ConnectionProxy): |
|
41 | ||
|
42 | def __init__(self): | |
|
43 | super(TimerProxy, self).__init__() | |
|
44 | self.logging_name = 'timerProxy' | |
|
45 | self.log = log.instance_logger(self, True) | |
|
46 | ||
|
42 | 47 | def cursor_execute(self, execute, cursor, statement, parameters, context, executemany): |
|
48 | ||
|
43 | 49 | now = time.time() |
|
44 | 50 | try: |
|
45 | log.info(">>>>> STARTING QUERY >>>>>") | |
|
51 | self.log.info(">>>>> STARTING QUERY >>>>>") | |
|
46 | 52 | return execute(cursor, statement, parameters, context) |
|
47 | 53 | finally: |
|
48 | 54 | total = time.time() - now |
|
49 | 55 | try: |
|
50 | log.info(format_sql("Query: %s" % statement % parameters)) | |
|
56 | self.log.info(format_sql("Query: %s" % statement % parameters)) | |
|
51 | 57 | except TypeError: |
|
52 | log.info(format_sql("Query: %s %s" % (statement, parameters))) | |
|
53 | log.info("<<<<< TOTAL TIME: %f <<<<<" % total) | |
|
54 | ||
|
55 | ||
|
56 | ||
|
57 | ||
|
58 | self.log.info(format_sql("Query: %s %s" % (statement, parameters))) | |
|
59 | self.log.info("<<<<< TOTAL TIME: %f <<<<<" % total) |
@@ -1,364 +1,438 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # Utilities for hg app |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # This program is free software; you can redistribute it and/or |
|
6 | 6 | # modify it under the terms of the GNU General Public License |
|
7 | 7 | # as published by the Free Software Foundation; version 2 |
|
8 | 8 | # of the License or (at your opinion) any later version of the license. |
|
9 | 9 | # |
|
10 | 10 | # This program is distributed in the hope that it will be useful, |
|
11 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | 13 | # GNU General Public License for more details. |
|
14 | 14 | # |
|
15 | 15 | # You should have received a copy of the GNU General Public License |
|
16 | 16 | # along with this program; if not, write to the Free Software |
|
17 | 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
18 | 18 | # MA 02110-1301, USA. |
|
19 | 19 | |
|
20 | 20 | """ |
|
21 | 21 | Created on April 18, 2010 |
|
22 | 22 | Utilities for hg app |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | from beaker.cache import cache_region |
|
26 | 26 | from mercurial import ui, config, hg |
|
27 | 27 | from mercurial.error import RepoError |
|
28 | 28 | from pylons_app.model import meta |
|
29 | 29 | from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings |
|
30 | 30 | from vcs.backends.base import BaseChangeset |
|
31 | 31 | from vcs.utils.lazy import LazyProperty |
|
32 | 32 | import logging |
|
33 | 33 | import os |
|
34 | ||
|
34 | 35 | log = logging.getLogger(__name__) |
|
35 | 36 | |
|
36 | 37 | |
|
37 | 38 | def get_repo_slug(request): |
|
38 | 39 | return request.environ['pylons.routes_dict'].get('repo_name') |
|
39 | 40 | |
|
40 | 41 | def is_mercurial(environ): |
|
41 | 42 | """ |
|
42 | 43 | Returns True if request's target is mercurial server - header |
|
43 | 44 | ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. |
|
44 | 45 | """ |
|
45 | 46 | http_accept = environ.get('HTTP_ACCEPT') |
|
46 | 47 | if http_accept and http_accept.startswith('application/mercurial'): |
|
47 | 48 | return True |
|
48 | 49 | return False |
|
49 | 50 | |
|
50 | 51 | def check_repo_dir(paths): |
|
51 | 52 | repos_path = paths[0][1].split('/') |
|
52 | 53 | if repos_path[-1] in ['*', '**']: |
|
53 | 54 | repos_path = repos_path[:-1] |
|
54 | 55 | if repos_path[0] != '/': |
|
55 | 56 | repos_path[0] = '/' |
|
56 | 57 | if not os.path.isdir(os.path.join(*repos_path)): |
|
57 | 58 | raise Exception('Not a valid repository in %s' % paths[0][1]) |
|
58 | 59 | |
|
59 | 60 | def check_repo_fast(repo_name, base_path): |
|
60 | 61 | if os.path.isdir(os.path.join(base_path, repo_name)):return False |
|
61 | 62 | return True |
|
62 | 63 | |
|
63 | 64 | def check_repo(repo_name, base_path, verify=True): |
|
64 | 65 | |
|
65 | 66 | repo_path = os.path.join(base_path, repo_name) |
|
66 | 67 | |
|
67 | 68 | try: |
|
68 | 69 | if not check_repo_fast(repo_name, base_path): |
|
69 | 70 | return False |
|
70 | 71 | r = hg.repository(ui.ui(), repo_path) |
|
71 | 72 | if verify: |
|
72 | 73 | hg.verify(r) |
|
73 | 74 | #here we hnow that repo exists it was verified |
|
74 | 75 | log.info('%s repo is already created', repo_name) |
|
75 | 76 | return False |
|
76 | 77 | except RepoError: |
|
77 | 78 | #it means that there is no valid repo there... |
|
78 | 79 | log.info('%s repo is free for creation', repo_name) |
|
79 | 80 | return True |
|
80 | 81 | |
|
81 | 82 | def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): |
|
82 | 83 | while True: |
|
83 | 84 | ok = raw_input(prompt) |
|
84 | 85 | if ok in ('y', 'ye', 'yes'): return True |
|
85 | 86 | if ok in ('n', 'no', 'nop', 'nope'): return False |
|
86 | 87 | retries = retries - 1 |
|
87 | 88 | if retries < 0: raise IOError |
|
88 | 89 | print complaint |
|
89 | 90 | |
|
90 | 91 | @cache_region('super_short_term', 'cached_hg_ui') |
|
91 | 92 | def get_hg_ui_cached(): |
|
92 | 93 | try: |
|
93 | 94 | sa = meta.Session |
|
94 | 95 | ret = sa.query(HgAppUi).all() |
|
95 | 96 | finally: |
|
96 | 97 | meta.Session.remove() |
|
97 | 98 | return ret |
|
98 | 99 | |
|
99 | 100 | |
|
100 | 101 | def get_hg_settings(): |
|
101 | 102 | try: |
|
102 | 103 | sa = meta.Session |
|
103 | 104 | ret = sa.query(HgAppSettings).all() |
|
104 | 105 | finally: |
|
105 | 106 | meta.Session.remove() |
|
106 | 107 | |
|
107 | 108 | if not ret: |
|
108 | 109 | raise Exception('Could not get application settings !') |
|
109 | 110 | settings = {} |
|
110 | 111 | for each in ret: |
|
111 | 112 | settings['hg_app_' + each.app_settings_name] = each.app_settings_value |
|
112 | 113 | |
|
113 | 114 | return settings |
|
114 | 115 | |
|
115 | 116 | def get_hg_ui_settings(): |
|
116 | 117 | try: |
|
117 | 118 | sa = meta.Session |
|
118 | 119 | ret = sa.query(HgAppUi).all() |
|
119 | 120 | finally: |
|
120 | 121 | meta.Session.remove() |
|
121 | 122 | |
|
122 | 123 | if not ret: |
|
123 | 124 | raise Exception('Could not get application ui settings !') |
|
124 | 125 | settings = {} |
|
125 | 126 | for each in ret: |
|
126 | 127 | k = each.ui_key |
|
127 | 128 | v = each.ui_value |
|
128 | 129 | if k == '/': |
|
129 | 130 | k = 'root_path' |
|
130 | 131 | |
|
131 | 132 | if k.find('.') != -1: |
|
132 | 133 | k = k.replace('.', '_') |
|
133 | 134 | |
|
134 | 135 | if each.ui_section == 'hooks': |
|
135 | 136 | v = each.ui_active |
|
136 | 137 | |
|
137 | 138 | settings[each.ui_section + '_' + k] = v |
|
138 | 139 | |
|
139 | 140 | return settings |
|
140 | 141 | |
|
141 | 142 | #propagated from mercurial documentation |
|
142 | 143 | ui_sections = ['alias', 'auth', |
|
143 | 144 | 'decode/encode', 'defaults', |
|
144 | 145 | 'diff', 'email', |
|
145 | 146 | 'extensions', 'format', |
|
146 | 147 | 'merge-patterns', 'merge-tools', |
|
147 | 148 | 'hooks', 'http_proxy', |
|
148 | 149 | 'smtp', 'patch', |
|
149 | 150 | 'paths', 'profiling', |
|
150 | 151 | 'server', 'trusted', |
|
151 | 152 | 'ui', 'web', ] |
|
152 | 153 | |
|
153 | 154 | def make_ui(read_from='file', path=None, checkpaths=True): |
|
154 | 155 | """ |
|
155 | 156 | A function that will read python rc files or database |
|
156 | 157 | and make an mercurial ui object from read options |
|
157 | 158 | |
|
158 | 159 | @param path: path to mercurial config file |
|
159 | 160 | @param checkpaths: check the path |
|
160 | 161 | @param read_from: read from 'file' or 'db' |
|
161 | 162 | """ |
|
162 | 163 | |
|
163 | 164 | baseui = ui.ui() |
|
164 | 165 | |
|
165 | 166 | if read_from == 'file': |
|
166 | 167 | if not os.path.isfile(path): |
|
167 | 168 | log.warning('Unable to read config file %s' % path) |
|
168 | 169 | return False |
|
169 | 170 | log.debug('reading hgrc from %s', path) |
|
170 | 171 | cfg = config.config() |
|
171 | 172 | cfg.read(path) |
|
172 | 173 | for section in ui_sections: |
|
173 | 174 | for k, v in cfg.items(section): |
|
174 | 175 | baseui.setconfig(section, k, v) |
|
175 | 176 | log.debug('settings ui from file[%s]%s:%s', section, k, v) |
|
176 | 177 | if checkpaths:check_repo_dir(cfg.items('paths')) |
|
177 | 178 | |
|
178 | 179 | |
|
179 | 180 | elif read_from == 'db': |
|
180 | 181 | hg_ui = get_hg_ui_cached() |
|
181 | 182 | for ui_ in hg_ui: |
|
182 | 183 | if ui_.ui_active: |
|
183 | 184 | log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value) |
|
184 | 185 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) |
|
185 | 186 | |
|
186 | 187 | |
|
187 | 188 | return baseui |
|
188 | 189 | |
|
189 | 190 | |
|
190 | 191 | def set_hg_app_config(config): |
|
191 | 192 | hgsettings = get_hg_settings() |
|
192 | 193 | |
|
193 | 194 | for k, v in hgsettings.items(): |
|
194 | 195 | config[k] = v |
|
195 | 196 | |
|
196 | 197 | def invalidate_cache(name, *args): |
|
197 | 198 | """Invalidates given name cache""" |
|
198 | 199 | |
|
199 | 200 | from beaker.cache import region_invalidate |
|
200 | 201 | log.info('INVALIDATING CACHE FOR %s', name) |
|
201 | 202 | |
|
202 | 203 | """propagate our arguments to make sure invalidation works. First |
|
203 | 204 | argument has to be the name of cached func name give to cache decorator |
|
204 | 205 | without that the invalidation would not work""" |
|
205 | 206 | tmp = [name] |
|
206 | 207 | tmp.extend(args) |
|
207 | 208 | args = tuple(tmp) |
|
208 | 209 | |
|
209 | 210 | if name == 'cached_repo_list': |
|
210 | 211 | from pylons_app.model.hg_model import _get_repos_cached |
|
211 | 212 | region_invalidate(_get_repos_cached, None, *args) |
|
212 | 213 | |
|
213 | 214 | if name == 'full_changelog': |
|
214 | 215 | from pylons_app.model.hg_model import _full_changelog_cached |
|
215 | 216 | region_invalidate(_full_changelog_cached, None, *args) |
|
216 | 217 | |
|
217 | 218 | class EmptyChangeset(BaseChangeset): |
|
218 | 219 | |
|
219 | 220 | revision = -1 |
|
220 | 221 | message = '' |
|
222 | author = '' | |
|
221 | 223 | |
|
222 | 224 | @LazyProperty |
|
223 | 225 | def raw_id(self): |
|
224 | 226 | """ |
|
225 | 227 | Returns raw string identifing this changeset, useful for web |
|
226 | 228 | representation. |
|
227 | 229 | """ |
|
228 | 230 | return '0' * 12 |
|
229 | 231 | |
|
230 | 232 | |
|
231 | 233 | def repo2db_mapper(initial_repo_list, remove_obsolete=False): |
|
232 | 234 | """ |
|
233 | 235 | maps all found repositories into db |
|
234 | 236 | """ |
|
235 | 237 | from pylons_app.model.repo_model import RepoModel |
|
236 | 238 | |
|
237 | 239 | sa = meta.Session |
|
238 | 240 | user = sa.query(User).filter(User.admin == True).first() |
|
239 | 241 | |
|
240 | 242 | rm = RepoModel() |
|
241 | 243 | |
|
242 | 244 | for name, repo in initial_repo_list.items(): |
|
243 | 245 | if not sa.query(Repository).filter(Repository.repo_name == name).scalar(): |
|
244 | 246 | log.info('repository %s not found creating default', name) |
|
245 | 247 | |
|
246 | 248 | form_data = { |
|
247 | 249 | 'repo_name':name, |
|
248 | 250 | 'description':repo.description if repo.description != 'unknown' else \ |
|
249 | 251 | 'auto description for %s' % name, |
|
250 | 252 | 'private':False |
|
251 | 253 | } |
|
252 | 254 | rm.create(form_data, user, just_db=True) |
|
253 | 255 | |
|
254 | 256 | |
|
255 | 257 | if remove_obsolete: |
|
256 | 258 | #remove from database those repositories that are not in the filesystem |
|
257 | 259 | for repo in sa.query(Repository).all(): |
|
258 | 260 | if repo.repo_name not in initial_repo_list.keys(): |
|
259 | 261 | sa.delete(repo) |
|
260 | 262 | sa.commit() |
|
261 | 263 | |
|
262 | 264 | |
|
263 | 265 | meta.Session.remove() |
|
264 | 266 | |
|
265 | 267 | from UserDict import DictMixin |
|
266 | 268 | |
|
267 | 269 | class OrderedDict(dict, DictMixin): |
|
268 | 270 | |
|
269 | 271 | def __init__(self, *args, **kwds): |
|
270 | 272 | if len(args) > 1: |
|
271 | 273 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) |
|
272 | 274 | try: |
|
273 | 275 | self.__end |
|
274 | 276 | except AttributeError: |
|
275 | 277 | self.clear() |
|
276 | 278 | self.update(*args, **kwds) |
|
277 | 279 | |
|
278 | 280 | def clear(self): |
|
279 | 281 | self.__end = end = [] |
|
280 | 282 | end += [None, end, end] # sentinel node for doubly linked list |
|
281 | 283 | self.__map = {} # key --> [key, prev, next] |
|
282 | 284 | dict.clear(self) |
|
283 | 285 | |
|
284 | 286 | def __setitem__(self, key, value): |
|
285 | 287 | if key not in self: |
|
286 | 288 | end = self.__end |
|
287 | 289 | curr = end[1] |
|
288 | 290 | curr[2] = end[1] = self.__map[key] = [key, curr, end] |
|
289 | 291 | dict.__setitem__(self, key, value) |
|
290 | 292 | |
|
291 | 293 | def __delitem__(self, key): |
|
292 | 294 | dict.__delitem__(self, key) |
|
293 | 295 | key, prev, next = self.__map.pop(key) |
|
294 | 296 | prev[2] = next |
|
295 | 297 | next[1] = prev |
|
296 | 298 | |
|
297 | 299 | def __iter__(self): |
|
298 | 300 | end = self.__end |
|
299 | 301 | curr = end[2] |
|
300 | 302 | while curr is not end: |
|
301 | 303 | yield curr[0] |
|
302 | 304 | curr = curr[2] |
|
303 | 305 | |
|
304 | 306 | def __reversed__(self): |
|
305 | 307 | end = self.__end |
|
306 | 308 | curr = end[1] |
|
307 | 309 | while curr is not end: |
|
308 | 310 | yield curr[0] |
|
309 | 311 | curr = curr[1] |
|
310 | 312 | |
|
311 | 313 | def popitem(self, last=True): |
|
312 | 314 | if not self: |
|
313 | 315 | raise KeyError('dictionary is empty') |
|
314 | 316 | if last: |
|
315 | 317 | key = reversed(self).next() |
|
316 | 318 | else: |
|
317 | 319 | key = iter(self).next() |
|
318 | 320 | value = self.pop(key) |
|
319 | 321 | return key, value |
|
320 | 322 | |
|
321 | 323 | def __reduce__(self): |
|
322 | 324 | items = [[k, self[k]] for k in self] |
|
323 | 325 | tmp = self.__map, self.__end |
|
324 | 326 | del self.__map, self.__end |
|
325 | 327 | inst_dict = vars(self).copy() |
|
326 | 328 | self.__map, self.__end = tmp |
|
327 | 329 | if inst_dict: |
|
328 | 330 | return (self.__class__, (items,), inst_dict) |
|
329 | 331 | return self.__class__, (items,) |
|
330 | 332 | |
|
331 | 333 | def keys(self): |
|
332 | 334 | return list(self) |
|
333 | 335 | |
|
334 | 336 | setdefault = DictMixin.setdefault |
|
335 | 337 | update = DictMixin.update |
|
336 | 338 | pop = DictMixin.pop |
|
337 | 339 | values = DictMixin.values |
|
338 | 340 | items = DictMixin.items |
|
339 | 341 | iterkeys = DictMixin.iterkeys |
|
340 | 342 | itervalues = DictMixin.itervalues |
|
341 | 343 | iteritems = DictMixin.iteritems |
|
342 | 344 | |
|
343 | 345 | def __repr__(self): |
|
344 | 346 | if not self: |
|
345 | 347 | return '%s()' % (self.__class__.__name__,) |
|
346 | 348 | return '%s(%r)' % (self.__class__.__name__, self.items()) |
|
347 | 349 | |
|
348 | 350 | def copy(self): |
|
349 | 351 | return self.__class__(self) |
|
350 | 352 | |
|
351 | 353 | @classmethod |
|
352 | 354 | def fromkeys(cls, iterable, value=None): |
|
353 | 355 | d = cls() |
|
354 | 356 | for key in iterable: |
|
355 | 357 | d[key] = value |
|
356 | 358 | return d |
|
357 | 359 | |
|
358 | 360 | def __eq__(self, other): |
|
359 | 361 | if isinstance(other, OrderedDict): |
|
360 | 362 | return len(self) == len(other) and self.items() == other.items() |
|
361 | 363 | return dict.__eq__(self, other) |
|
362 | 364 | |
|
363 | 365 | def __ne__(self, other): |
|
364 | 366 | return not self == other |
|
367 | ||
|
368 | ||
|
369 | #=============================================================================== | |
|
370 | # TEST FUNCTIONS | |
|
371 | #=============================================================================== | |
|
372 | def create_test_index(repo_location, full_index): | |
|
373 | """Makes default test index | |
|
374 | @param repo_location: | |
|
375 | @param full_index: | |
|
376 | """ | |
|
377 | from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon | |
|
378 | from pylons_app.lib.pidlock import DaemonLock, LockHeld | |
|
379 | from pylons_app.lib.indexers import IDX_LOCATION | |
|
380 | import shutil | |
|
381 | ||
|
382 | if os.path.exists(IDX_LOCATION): | |
|
383 | shutil.rmtree(IDX_LOCATION) | |
|
384 | ||
|
385 | try: | |
|
386 | l = DaemonLock() | |
|
387 | WhooshIndexingDaemon(repo_location=repo_location)\ | |
|
388 | .run(full_index=full_index) | |
|
389 | l.release() | |
|
390 | except LockHeld: | |
|
391 | pass | |
|
392 | ||
|
393 | def create_test_env(repos_test_path, config): | |
|
394 | """Makes a fresh database and | |
|
395 | install test repository into tmp dir | |
|
396 | """ | |
|
397 | from pylons_app.lib.db_manage import DbManage | |
|
398 | import tarfile | |
|
399 | import shutil | |
|
400 | from os.path import dirname as dn, join as jn, abspath | |
|
401 | ||
|
402 | log = logging.getLogger('TestEnvCreator') | |
|
403 | # create logger | |
|
404 | log.setLevel(logging.DEBUG) | |
|
405 | log.propagate = True | |
|
406 | # create console handler and set level to debug | |
|
407 | ch = logging.StreamHandler() | |
|
408 | ch.setLevel(logging.DEBUG) | |
|
409 | ||
|
410 | # create formatter | |
|
411 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
|
412 | ||
|
413 | # add formatter to ch | |
|
414 | ch.setFormatter(formatter) | |
|
415 | ||
|
416 | # add ch to logger | |
|
417 | log.addHandler(ch) | |
|
418 | ||
|
419 | #PART ONE create db | |
|
420 | log.debug('making test db') | |
|
421 | dbname = config['sqlalchemy.db1.url'].split('/')[-1] | |
|
422 | dbmanage = DbManage(log_sql=True, dbname=dbname, tests=True) | |
|
423 | dbmanage.create_tables(override=True) | |
|
424 | dbmanage.config_prompt(repos_test_path) | |
|
425 | dbmanage.create_default_user() | |
|
426 | dbmanage.admin_prompt() | |
|
427 | dbmanage.create_permissions() | |
|
428 | dbmanage.populate_default_permissions() | |
|
429 | ||
|
430 | #PART TWO make test repo | |
|
431 | log.debug('making test vcs repo') | |
|
432 | if os.path.isdir('/tmp/vcs_test'): | |
|
433 | shutil.rmtree('/tmp/vcs_test') | |
|
434 | ||
|
435 | cur_dir = dn(dn(abspath(__file__))) | |
|
436 | tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz")) | |
|
437 | tar.extractall('/tmp') | |
|
438 | tar.close() |
@@ -1,30 +1,23 b'' | |||
|
1 | 1 | """The application's model objects""" |
|
2 | 2 | import logging |
|
3 | import sqlalchemy as sa | |
|
4 | from sqlalchemy import orm | |
|
5 | 3 | from pylons_app.model import meta |
|
6 | from pylons_app.model.meta import Session | |
|
7 | 4 | log = logging.getLogger(__name__) |
|
8 | 5 | |
|
9 | # Add these two imports: | |
|
10 | import datetime | |
|
11 | from sqlalchemy import schema, types | |
|
12 | ||
|
13 | 6 | def init_model(engine): |
|
14 | 7 | """Call me before using any of the tables or classes in the model""" |
|
15 | 8 | log.info("INITIALIZING DB MODELS") |
|
16 | 9 | meta.Base.metadata.bind = engine |
|
17 | 10 | #meta.Base2.metadata.bind = engine2 |
|
18 | 11 | |
|
19 | 12 | #THIS IS A TEST FOR EXECUTING SCRIPT AND LOAD PYLONS APPLICATION GLOBALS |
|
20 | 13 | #from paste.deploy import appconfig |
|
21 | 14 | #from pylons import config |
|
22 | 15 | #from sqlalchemy import engine_from_config |
|
23 | 16 | #from pylons_app.config.environment import load_environment |
|
24 | 17 | # |
|
25 | 18 | #conf = appconfig('config:development.ini', relative_to = './../../') |
|
26 | 19 | #load_environment(conf.global_conf, conf.local_conf) |
|
27 | 20 | # |
|
28 | 21 | #engine = engine_from_config(config, 'sqlalchemy.') |
|
29 | 22 | #init_model(engine) |
|
30 | 23 | # DO SOMETHING |
@@ -1,125 +1,134 b'' | |||
|
1 | 1 | from pylons_app.model.meta import Base |
|
2 | 2 | from sqlalchemy import * |
|
3 | 3 | from sqlalchemy.orm import relation, backref |
|
4 | 4 | from sqlalchemy.orm.session import Session |
|
5 | 5 | from vcs.utils.lazy import LazyProperty |
|
6 | 6 | import logging |
|
7 | 7 | |
|
8 | 8 | log = logging.getLogger(__name__) |
|
9 | 9 | |
|
10 | 10 | class HgAppSettings(Base): |
|
11 | 11 | __tablename__ = 'hg_app_settings' |
|
12 | 12 | __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True}) |
|
13 | 13 | app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
14 | 14 | app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
15 | 15 | app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
16 | 16 | |
|
17 | 17 | class HgAppUi(Base): |
|
18 | 18 | __tablename__ = 'hg_app_ui' |
|
19 | 19 | __table_args__ = {'useexisting':True} |
|
20 | 20 | ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
21 | 21 | ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
22 | 22 | ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
23 | 23 | ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
24 | 24 | ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | class User(Base): |
|
28 | 28 | __tablename__ = 'users' |
|
29 | __table_args__ = (UniqueConstraint('username'), {'useexisting':True}) | |
|
29 | __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True}) | |
|
30 | 30 | user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
31 | 31 | username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
32 | 32 | password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
33 | 33 | active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None) |
|
34 | 34 | admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False) |
|
35 | 35 | name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
36 | 36 | lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
37 | 37 | email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
38 | 38 | last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None) |
|
39 | 39 | |
|
40 | 40 | user_log = relation('UserLog') |
|
41 | 41 | user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id") |
|
42 | 42 | |
|
43 | 43 | @LazyProperty |
|
44 | 44 | def full_contact(self): |
|
45 | 45 | return '%s %s <%s>' % (self.name, self.lastname, self.email) |
|
46 | 46 | |
|
47 | 47 | def __repr__(self): |
|
48 | 48 | return "<User('id:%s:%s')>" % (self.user_id, self.username) |
|
49 | 49 | |
|
50 | 50 | def update_lastlogin(self): |
|
51 | 51 | """Update user lastlogin""" |
|
52 | 52 | import datetime |
|
53 | 53 | |
|
54 | 54 | try: |
|
55 | 55 | session = Session.object_session(self) |
|
56 | 56 | self.last_login = datetime.datetime.now() |
|
57 | 57 | session.add(self) |
|
58 | 58 | session.commit() |
|
59 | log.debug('updated user %s lastlogin',self.username) | |
|
59 | log.debug('updated user %s lastlogin', self.username) | |
|
60 | 60 | except Exception: |
|
61 | 61 | session.rollback() |
|
62 | 62 | |
|
63 | 63 | |
|
64 | 64 | class UserLog(Base): |
|
65 | 65 | __tablename__ = 'user_logs' |
|
66 | 66 | __table_args__ = {'useexisting':True} |
|
67 | 67 | user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
68 | 68 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) |
|
69 | 69 | user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
70 | 70 | repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None) |
|
71 | 71 | action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
72 | 72 | action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None) |
|
73 | 73 | |
|
74 | 74 | user = relation('User') |
|
75 | 75 | |
|
76 | 76 | class Repository(Base): |
|
77 | 77 | __tablename__ = 'repositories' |
|
78 | 78 | __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},) |
|
79 | 79 | repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
80 | 80 | repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) |
|
81 | 81 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None) |
|
82 | 82 | private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) |
|
83 | 83 | description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
84 | 84 | |
|
85 | 85 | user = relation('User') |
|
86 | 86 | repo_to_perm = relation('RepoToPerm', cascade='all') |
|
87 | 87 | |
|
88 | 88 | def __repr__(self): |
|
89 | 89 | return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name) |
|
90 | 90 | |
|
91 | 91 | class Permission(Base): |
|
92 | 92 | __tablename__ = 'permissions' |
|
93 | 93 | __table_args__ = {'useexisting':True} |
|
94 | 94 | permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
95 | 95 | permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
96 | 96 | permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
97 | 97 | |
|
98 | 98 | def __repr__(self): |
|
99 | 99 | return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name) |
|
100 | 100 | |
|
101 | 101 | class RepoToPerm(Base): |
|
102 | 102 | __tablename__ = 'repo_to_perm' |
|
103 | 103 | __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True}) |
|
104 | 104 | repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
105 | 105 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) |
|
106 | 106 | permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) |
|
107 | 107 | repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) |
|
108 | 108 | |
|
109 | 109 | user = relation('User') |
|
110 | 110 | permission = relation('Permission') |
|
111 | 111 | repository = relation('Repository') |
|
112 | 112 | |
|
113 | 113 | class UserToPerm(Base): |
|
114 | 114 | __tablename__ = 'user_to_perm' |
|
115 | 115 | __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True}) |
|
116 | 116 | user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
117 | 117 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) |
|
118 | 118 | permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) |
|
119 | 119 | |
|
120 | 120 | user = relation('User') |
|
121 | 121 | permission = relation('Permission') |
|
122 | 122 | |
|
123 | ||
|
123 | class Statistics(Base): | |
|
124 | __tablename__ = 'statistics' | |
|
125 | __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True}) | |
|
126 | stat_id = Column("stat_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
|
127 | repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None) | |
|
128 | stat_on_revision = Column("stat_on_revision", INTEGER(), nullable=False) | |
|
129 | commit_activity = Column("commit_activity", BLOB(), nullable=False)#JSON data | |
|
130 | commit_activity_combined = Column("commit_activity_combined", BLOB(), nullable=False)#JSON data | |
|
131 | languages = Column("languages", BLOB(), nullable=False)#JSON data | |
|
132 | ||
|
133 | repository = relation('Repository') | |
|
124 | 134 | |
|
125 |
@@ -1,315 +1,351 b'' | |||
|
1 | 1 | """ this is forms validation classes |
|
2 | 2 | http://formencode.org/module-formencode.validators.html |
|
3 | 3 | for list off all availible validators |
|
4 | 4 | |
|
5 | 5 | we can create our own validators |
|
6 | 6 | |
|
7 | 7 | The table below outlines the options which can be used in a schema in addition to the validators themselves |
|
8 | 8 | pre_validators [] These validators will be applied before the schema |
|
9 | 9 | chained_validators [] These validators will be applied after the schema |
|
10 | 10 | allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present |
|
11 | 11 | filter_extra_fields False If True, then keys that aren't associated with a validator are removed |
|
12 | 12 | if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value. |
|
13 | 13 | ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already |
|
14 | 14 | |
|
15 | 15 | |
|
16 | 16 | <name> = formencode.validators.<name of validator> |
|
17 | 17 | <name> must equal form name |
|
18 | 18 | list=[1,2,3,4,5] |
|
19 | 19 | for SELECT use formencode.All(OneOf(list), Int()) |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | from formencode import All |
|
23 | 23 | from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ |
|
24 | 24 | Email, Bool, StringBoolean |
|
25 | 25 | from pylons import session |
|
26 | 26 | from pylons.i18n.translation import _ |
|
27 | 27 | from pylons_app.lib.auth import check_password, get_crypt_password |
|
28 | 28 | from pylons_app.model import meta |
|
29 | 29 | from pylons_app.model.user_model import UserModel |
|
30 | 30 | from pylons_app.model.db import User, Repository |
|
31 | 31 | from sqlalchemy.exc import OperationalError |
|
32 | 32 | from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound |
|
33 | 33 | from webhelpers.pylonslib.secure_form import authentication_token |
|
34 | 34 | import formencode |
|
35 | 35 | import logging |
|
36 | 36 | import os |
|
37 | 37 | import pylons_app.lib.helpers as h |
|
38 | 38 | log = logging.getLogger(__name__) |
|
39 | 39 | |
|
40 | 40 | |
|
41 | 41 | #this is needed to translate the messages using _() in validators |
|
42 | 42 | class State_obj(object): |
|
43 | 43 | _ = staticmethod(_) |
|
44 | 44 | |
|
45 | 45 | #=============================================================================== |
|
46 | 46 | # VALIDATORS |
|
47 | 47 | #=============================================================================== |
|
48 | 48 | class ValidAuthToken(formencode.validators.FancyValidator): |
|
49 | 49 | messages = {'invalid_token':_('Token mismatch')} |
|
50 | 50 | |
|
51 | 51 | def validate_python(self, value, state): |
|
52 | 52 | |
|
53 | 53 | if value != authentication_token(): |
|
54 | 54 | raise formencode.Invalid(self.message('invalid_token', state, |
|
55 | 55 | search_number=value), value, state) |
|
56 | 56 | |
|
57 | 57 | def ValidUsername(edit, old_data): |
|
58 | 58 | class _ValidUsername(formencode.validators.FancyValidator): |
|
59 | 59 | |
|
60 | 60 | def validate_python(self, value, state): |
|
61 | 61 | if value in ['default', 'new_user']: |
|
62 | 62 | raise formencode.Invalid(_('Invalid username'), value, state) |
|
63 | 63 | #check if user is uniq |
|
64 | 64 | sa = meta.Session |
|
65 | 65 | old_un = None |
|
66 | 66 | if edit: |
|
67 | 67 | old_un = sa.query(User).get(old_data.get('user_id')).username |
|
68 | 68 | |
|
69 | 69 | if old_un != value or not edit: |
|
70 | 70 | if sa.query(User).filter(User.username == value).scalar(): |
|
71 | 71 | raise formencode.Invalid(_('This username already exists') , |
|
72 | 72 | value, state) |
|
73 | 73 | meta.Session.remove() |
|
74 | 74 | |
|
75 | 75 | return _ValidUsername |
|
76 | 76 | |
|
77 | 77 | class ValidPassword(formencode.validators.FancyValidator): |
|
78 | 78 | |
|
79 | 79 | def to_python(self, value, state): |
|
80 | 80 | if value: |
|
81 | 81 | return get_crypt_password(value) |
|
82 | 82 | |
|
83 | 83 | class ValidAuth(formencode.validators.FancyValidator): |
|
84 | 84 | messages = { |
|
85 | 85 | 'invalid_password':_('invalid password'), |
|
86 | 86 | 'invalid_login':_('invalid user name'), |
|
87 | 87 | 'disabled_account':_('Your acccount is disabled') |
|
88 | 88 | |
|
89 | 89 | } |
|
90 | 90 | #error mapping |
|
91 | 91 | e_dict = {'username':messages['invalid_login'], |
|
92 | 92 | 'password':messages['invalid_password']} |
|
93 | 93 | e_dict_disable = {'username':messages['disabled_account']} |
|
94 | 94 | |
|
95 | 95 | def validate_python(self, value, state): |
|
96 | 96 | password = value['password'] |
|
97 | 97 | username = value['username'] |
|
98 | 98 | user = UserModel().get_user_by_name(username) |
|
99 | 99 | if user is None: |
|
100 | 100 | raise formencode.Invalid(self.message('invalid_password', |
|
101 | 101 | state=State_obj), value, state, |
|
102 | 102 | error_dict=self.e_dict) |
|
103 | 103 | if user: |
|
104 | 104 | if user.active: |
|
105 |
if user.username == username and check_password(password, |
|
|
105 | if user.username == username and check_password(password, | |
|
106 | 106 | user.password): |
|
107 | 107 | return value |
|
108 | 108 | else: |
|
109 | 109 | log.warning('user %s not authenticated', username) |
|
110 | 110 | raise formencode.Invalid(self.message('invalid_password', |
|
111 | 111 | state=State_obj), value, state, |
|
112 | 112 | error_dict=self.e_dict) |
|
113 | 113 | else: |
|
114 | 114 | log.warning('user %s is disabled', username) |
|
115 | 115 | raise formencode.Invalid(self.message('disabled_account', |
|
116 | 116 | state=State_obj), |
|
117 | 117 | value, state, |
|
118 | 118 | error_dict=self.e_dict_disable) |
|
119 | 119 | |
|
120 | 120 | class ValidRepoUser(formencode.validators.FancyValidator): |
|
121 | 121 | |
|
122 | 122 | def to_python(self, value, state): |
|
123 | 123 | try: |
|
124 | 124 | self.user_db = meta.Session.query(User)\ |
|
125 | 125 | .filter(User.active == True)\ |
|
126 | 126 | .filter(User.username == value).one() |
|
127 | 127 | except Exception: |
|
128 | 128 | raise formencode.Invalid(_('This username is not valid'), |
|
129 | 129 | value, state) |
|
130 | 130 | finally: |
|
131 | 131 | meta.Session.remove() |
|
132 | 132 | |
|
133 | 133 | return self.user_db.user_id |
|
134 | 134 | |
|
135 | 135 | def ValidRepoName(edit, old_data): |
|
136 | 136 | class _ValidRepoName(formencode.validators.FancyValidator): |
|
137 | 137 | |
|
138 | 138 | def to_python(self, value, state): |
|
139 | 139 | slug = h.repo_name_slug(value) |
|
140 | 140 | if slug in ['_admin']: |
|
141 | 141 | raise formencode.Invalid(_('This repository name is disallowed'), |
|
142 | 142 | value, state) |
|
143 | 143 | if old_data.get('repo_name') != value or not edit: |
|
144 | 144 | sa = meta.Session |
|
145 | 145 | if sa.query(Repository).filter(Repository.repo_name == slug).scalar(): |
|
146 | 146 | raise formencode.Invalid(_('This repository already exists') , |
|
147 | 147 | value, state) |
|
148 | 148 | meta.Session.remove() |
|
149 | 149 | return slug |
|
150 | 150 | |
|
151 | 151 | |
|
152 | 152 | return _ValidRepoName |
|
153 | 153 | |
|
154 | 154 | class ValidPerms(formencode.validators.FancyValidator): |
|
155 | 155 | messages = {'perm_new_user_name':_('This username is not valid')} |
|
156 | 156 | |
|
157 | 157 | def to_python(self, value, state): |
|
158 | 158 | perms_update = [] |
|
159 | 159 | perms_new = [] |
|
160 | 160 | #build a list of permission to update and new permission to create |
|
161 | 161 | for k, v in value.items(): |
|
162 | 162 | if k.startswith('perm_'): |
|
163 | 163 | if k.startswith('perm_new_user'): |
|
164 | 164 | new_perm = value.get('perm_new_user', False) |
|
165 | 165 | new_user = value.get('perm_new_user_name', False) |
|
166 | 166 | if new_user and new_perm: |
|
167 | 167 | if (new_user, new_perm) not in perms_new: |
|
168 | 168 | perms_new.append((new_user, new_perm)) |
|
169 | 169 | else: |
|
170 | 170 | usr = k[5:] |
|
171 | 171 | if usr == 'default': |
|
172 | 172 | if value['private']: |
|
173 | 173 | #set none for default when updating to private repo |
|
174 | 174 | v = 'repository.none' |
|
175 | 175 | perms_update.append((usr, v)) |
|
176 | 176 | value['perms_updates'] = perms_update |
|
177 | 177 | value['perms_new'] = perms_new |
|
178 | 178 | sa = meta.Session |
|
179 | 179 | for k, v in perms_new: |
|
180 | 180 | try: |
|
181 | 181 | self.user_db = sa.query(User)\ |
|
182 | 182 | .filter(User.active == True)\ |
|
183 | 183 | .filter(User.username == k).one() |
|
184 | 184 | except Exception: |
|
185 | 185 | msg = self.message('perm_new_user_name', |
|
186 | 186 | state=State_obj) |
|
187 | 187 | raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg}) |
|
188 | 188 | return value |
|
189 | 189 | |
|
190 | 190 | class ValidSettings(formencode.validators.FancyValidator): |
|
191 | 191 | |
|
192 | 192 | def to_python(self, value, state): |
|
193 | 193 | #settings form can't edit user |
|
194 | 194 | if value.has_key('user'): |
|
195 | 195 | del['value']['user'] |
|
196 | 196 | |
|
197 | 197 | return value |
|
198 | 198 | |
|
199 | 199 | class ValidPath(formencode.validators.FancyValidator): |
|
200 | 200 | def to_python(self, value, state): |
|
201 | 201 | isdir = os.path.isdir(value.replace('*', '')) |
|
202 | 202 | if (value.endswith('/*') or value.endswith('/**')) and isdir: |
|
203 | 203 | return value |
|
204 | 204 | elif not isdir: |
|
205 | 205 | msg = _('This is not a valid path') |
|
206 | 206 | else: |
|
207 | 207 | msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)') |
|
208 | 208 | |
|
209 | 209 | raise formencode.Invalid(msg, value, state, |
|
210 | 210 | error_dict={'paths_root_path':msg}) |
|
211 | ||
|
211 | ||
|
212 | def UniqSystemEmail(old_data): | |
|
213 | class _UniqSystemEmail(formencode.validators.FancyValidator): | |
|
214 | def to_python(self, value, state): | |
|
215 | if old_data.get('email') != value: | |
|
216 | sa = meta.Session | |
|
217 | try: | |
|
218 | user = sa.query(User).filter(User.email == value).scalar() | |
|
219 | if user: | |
|
220 | raise formencode.Invalid(_("That e-mail address is already taken") , | |
|
221 | value, state) | |
|
222 | finally: | |
|
223 | meta.Session.remove() | |
|
224 | ||
|
225 | return value | |
|
226 | ||
|
227 | return _UniqSystemEmail | |
|
228 | ||
|
229 | class ValidSystemEmail(formencode.validators.FancyValidator): | |
|
230 | def to_python(self, value, state): | |
|
231 | sa = meta.Session | |
|
232 | try: | |
|
233 | user = sa.query(User).filter(User.email == value).scalar() | |
|
234 | if user is None: | |
|
235 | raise formencode.Invalid(_("That e-mail address doesn't exist.") , | |
|
236 | value, state) | |
|
237 | finally: | |
|
238 | meta.Session.remove() | |
|
239 | ||
|
240 | return value | |
|
241 | ||
|
212 | 242 | #=============================================================================== |
|
213 | 243 | # FORMS |
|
214 | 244 | #=============================================================================== |
|
215 | 245 | class LoginForm(formencode.Schema): |
|
216 | 246 | allow_extra_fields = True |
|
217 | 247 | filter_extra_fields = True |
|
218 | 248 | username = UnicodeString( |
|
219 | 249 | strip=True, |
|
220 | 250 | min=3, |
|
221 | 251 | not_empty=True, |
|
222 | 252 | messages={ |
|
223 | 253 | 'empty':_('Please enter a login'), |
|
224 | 254 | 'tooShort':_('Enter a value %(min)i characters long or more')} |
|
225 | 255 | ) |
|
226 | 256 | |
|
227 | 257 | password = UnicodeString( |
|
228 | 258 | strip=True, |
|
229 | 259 | min=3, |
|
230 | 260 | not_empty=True, |
|
231 | 261 | messages={ |
|
232 | 262 | 'empty':_('Please enter a password'), |
|
233 | 263 | 'tooShort':_('Enter a value %(min)i characters long or more')} |
|
234 | 264 | ) |
|
235 | 265 | |
|
236 | 266 | |
|
237 | 267 | #chained validators have access to all data |
|
238 | 268 | chained_validators = [ValidAuth] |
|
239 | 269 | |
|
240 | 270 | def UserForm(edit=False, old_data={}): |
|
241 | 271 | class _UserForm(formencode.Schema): |
|
242 | 272 | allow_extra_fields = True |
|
243 | 273 | filter_extra_fields = True |
|
244 | 274 | username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data)) |
|
245 | 275 | if edit: |
|
246 | 276 | new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword) |
|
247 | 277 | admin = StringBoolean(if_missing=False) |
|
248 | 278 | else: |
|
249 | 279 | password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword) |
|
250 | 280 | active = StringBoolean(if_missing=False) |
|
251 | 281 | name = UnicodeString(strip=True, min=3, not_empty=True) |
|
252 | 282 | lastname = UnicodeString(strip=True, min=3, not_empty=True) |
|
253 | email = Email(not_empty=True) | |
|
283 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) | |
|
254 | 284 | |
|
255 | 285 | return _UserForm |
|
256 | 286 | |
|
257 | 287 | RegisterForm = UserForm |
|
258 | ||
|
259 | ||
|
288 | ||
|
289 | def PasswordResetForm(): | |
|
290 | class _PasswordResetForm(formencode.Schema): | |
|
291 | allow_extra_fields = True | |
|
292 | filter_extra_fields = True | |
|
293 | email = All(ValidSystemEmail(), Email(not_empty=True)) | |
|
294 | return _PasswordResetForm | |
|
295 | ||
|
260 | 296 | def RepoForm(edit=False, old_data={}): |
|
261 | 297 | class _RepoForm(formencode.Schema): |
|
262 | 298 | allow_extra_fields = True |
|
263 | 299 | filter_extra_fields = False |
|
264 | 300 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) |
|
265 | 301 | description = UnicodeString(strip=True, min=3, not_empty=True) |
|
266 | 302 | private = StringBoolean(if_missing=False) |
|
267 | 303 | |
|
268 | 304 | if edit: |
|
269 | 305 | user = All(Int(not_empty=True), ValidRepoUser) |
|
270 | 306 | |
|
271 | 307 | chained_validators = [ValidPerms] |
|
272 | 308 | return _RepoForm |
|
273 | 309 | |
|
274 | 310 | def RepoSettingsForm(edit=False, old_data={}): |
|
275 | 311 | class _RepoForm(formencode.Schema): |
|
276 | 312 | allow_extra_fields = True |
|
277 | 313 | filter_extra_fields = False |
|
278 | 314 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) |
|
279 | 315 | description = UnicodeString(strip=True, min=3, not_empty=True) |
|
280 | 316 | private = StringBoolean(if_missing=False) |
|
281 | 317 | |
|
282 | 318 | chained_validators = [ValidPerms, ValidSettings] |
|
283 | 319 | return _RepoForm |
|
284 | 320 | |
|
285 | 321 | |
|
286 | 322 | def ApplicationSettingsForm(): |
|
287 | 323 | class _ApplicationSettingsForm(formencode.Schema): |
|
288 | 324 | allow_extra_fields = True |
|
289 | 325 | filter_extra_fields = False |
|
290 | 326 | hg_app_title = UnicodeString(strip=True, min=3, not_empty=True) |
|
291 | 327 | hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True) |
|
292 | 328 | |
|
293 | 329 | return _ApplicationSettingsForm |
|
294 | 330 | |
|
295 | 331 | def ApplicationUiSettingsForm(): |
|
296 | 332 | class _ApplicationUiSettingsForm(formencode.Schema): |
|
297 | 333 | allow_extra_fields = True |
|
298 | 334 | filter_extra_fields = False |
|
299 | 335 | web_push_ssl = OneOf(['true', 'false'], if_missing='false') |
|
300 | 336 | paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True)) |
|
301 | 337 | hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) |
|
302 | 338 | hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False) |
|
303 | 339 | |
|
304 | 340 | return _ApplicationUiSettingsForm |
|
305 | 341 | |
|
306 | 342 | def DefaultPermissionsForm(perms_choices, register_choices, create_choices): |
|
307 | 343 | class _DefaultPermissionsForm(formencode.Schema): |
|
308 | 344 | allow_extra_fields = True |
|
309 | 345 | filter_extra_fields = True |
|
310 | 346 | overwrite_default = OneOf(['true', 'false'], if_missing='false') |
|
311 | 347 | default_perm = OneOf(perms_choices) |
|
312 | 348 | default_register = OneOf(register_choices) |
|
313 | 349 | default_create = OneOf(create_choices) |
|
314 | 350 | |
|
315 | 351 | return _DefaultPermissionsForm |
@@ -1,174 +1,169 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # Model for hg app |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | 5 | # |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | """ |
|
21 | 21 | Created on April 9, 2010 |
|
22 | 22 | Model for hg app |
|
23 | 23 | @author: marcink |
|
24 | 24 | """ |
|
25 | 25 | from beaker.cache import cache_region |
|
26 | 26 | from mercurial import ui |
|
27 | 27 | from mercurial.hgweb.hgwebdir_mod import findrepos |
|
28 | 28 | from pylons.i18n.translation import _ |
|
29 | 29 | from pylons_app.lib.auth import HasRepoPermissionAny |
|
30 | 30 | from pylons_app.model import meta |
|
31 | 31 | from pylons_app.model.db import Repository, User |
|
32 | 32 | from pylons_app.lib import helpers as h |
|
33 | 33 | from vcs.exceptions import RepositoryError, VCSError |
|
34 | 34 | import logging |
|
35 | 35 | import os |
|
36 | 36 | import sys |
|
37 | 37 | log = logging.getLogger(__name__) |
|
38 | 38 | |
|
39 | 39 | try: |
|
40 | 40 | from vcs.backends.hg import MercurialRepository |
|
41 | 41 | except ImportError: |
|
42 | 42 | sys.stderr.write('You have to import vcs module') |
|
43 | 43 | raise Exception('Unable to import vcs') |
|
44 | 44 | |
|
45 | 45 | def _get_repos_cached_initial(app_globals, initial): |
|
46 | """ | |
|
47 | return cached dict with repos | |
|
46 | """return cached dict with repos | |
|
48 | 47 | """ |
|
49 | 48 | g = app_globals |
|
50 | 49 | return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial) |
|
51 | 50 | |
|
52 | 51 | @cache_region('long_term', 'cached_repo_list') |
|
53 | 52 | def _get_repos_cached(): |
|
54 | """ | |
|
55 | return cached dict with repos | |
|
53 | """return cached dict with repos | |
|
56 | 54 | """ |
|
57 | 55 | log.info('getting all repositories list') |
|
58 | 56 | from pylons import app_globals as g |
|
59 | 57 | return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) |
|
60 | 58 | |
|
61 | 59 | @cache_region('super_short_term', 'cached_repos_switcher_list') |
|
62 | 60 | def _get_repos_switcher_cached(cached_repo_list): |
|
63 | 61 | repos_lst = [] |
|
64 |
for repo in |
|
|
65 |
if HasRepoPermissionAny('repository.write', 'repository.read', |
|
|
66 | repos_lst.append(repo) | |
|
62 | for repo in [x for x in cached_repo_list.values()]: | |
|
63 | if HasRepoPermissionAny('repository.write', 'repository.read', | |
|
64 | 'repository.admin')(repo.name.lower(), 'main page check'): | |
|
65 | repos_lst.append((repo.name, repo.dbrepo.private,)) | |
|
67 | 66 | |
|
68 | return repos_lst | |
|
67 | return sorted(repos_lst, key=lambda k:k[0]) | |
|
69 | 68 | |
|
70 | 69 | @cache_region('long_term', 'full_changelog') |
|
71 | 70 | def _full_changelog_cached(repo_name): |
|
72 | 71 | log.info('getting full changelog for %s', repo_name) |
|
73 | 72 | return list(reversed(list(HgModel().get_repo(repo_name)))) |
|
74 | 73 | |
|
75 | 74 | class HgModel(object): |
|
76 | """ | |
|
77 | Mercurial Model | |
|
75 | """Mercurial Model | |
|
78 | 76 | """ |
|
79 | 77 | |
|
80 | 78 | def __init__(self): |
|
81 |
|
|
|
82 | Constructor | |
|
83 | """ | |
|
79 | pass | |
|
84 | 80 | |
|
85 | 81 | @staticmethod |
|
86 | 82 | def repo_scan(repos_prefix, repos_path, baseui, initial=False): |
|
87 | 83 | """ |
|
88 | 84 | Listing of repositories in given path. This path should not be a |
|
89 | 85 | repository itself. Return a dictionary of repository objects |
|
90 | 86 | :param repos_path: path to directory it could take syntax with |
|
91 | 87 | * or ** for deep recursive displaying repositories |
|
92 | 88 | """ |
|
93 | 89 | sa = meta.Session() |
|
94 | 90 | def check_repo_dir(path): |
|
95 | """ | |
|
96 | Checks the repository | |
|
91 | """Checks the repository | |
|
97 | 92 | :param path: |
|
98 | 93 | """ |
|
99 | 94 | repos_path = path.split('/') |
|
100 | 95 | if repos_path[-1] in ['*', '**']: |
|
101 | 96 | repos_path = repos_path[:-1] |
|
102 | 97 | if repos_path[0] != '/': |
|
103 | 98 | repos_path[0] = '/' |
|
104 | 99 | if not os.path.isdir(os.path.join(*repos_path)): |
|
105 |
raise RepositoryError('Not a valid repository in %s' % path |
|
|
100 | raise RepositoryError('Not a valid repository in %s' % path) | |
|
106 | 101 | if not repos_path.endswith('*'): |
|
107 | 102 | raise VCSError('You need to specify * or ** at the end of path ' |
|
108 | 103 | 'for recursive scanning') |
|
109 | 104 | |
|
110 | 105 | check_repo_dir(repos_path) |
|
111 | 106 | log.info('scanning for repositories in %s', repos_path) |
|
112 | 107 | repos = findrepos([(repos_prefix, repos_path)]) |
|
113 | 108 | if not isinstance(baseui, ui.ui): |
|
114 | 109 | baseui = ui.ui() |
|
115 | 110 | |
|
116 | 111 | repos_list = {} |
|
117 | 112 | for name, path in repos: |
|
118 | 113 | try: |
|
119 | 114 | #name = name.split('/')[-1] |
|
120 | 115 | if repos_list.has_key(name): |
|
121 | 116 | raise RepositoryError('Duplicate repository name %s found in' |
|
122 | 117 | ' %s' % (name, path)) |
|
123 | 118 | else: |
|
124 | 119 | |
|
125 | 120 | repos_list[name] = MercurialRepository(path, baseui=baseui) |
|
126 | 121 | repos_list[name].name = name |
|
127 | 122 | |
|
128 | 123 | dbrepo = None |
|
129 | 124 | if not initial: |
|
130 | 125 | dbrepo = sa.query(Repository)\ |
|
131 | 126 | .filter(Repository.repo_name == name).scalar() |
|
132 | 127 | |
|
133 | 128 | if dbrepo: |
|
134 | 129 | log.info('Adding db instance to cached list') |
|
135 | 130 | repos_list[name].dbrepo = dbrepo |
|
136 | 131 | repos_list[name].description = dbrepo.description |
|
137 | 132 | if dbrepo.user: |
|
138 | 133 | repos_list[name].contact = dbrepo.user.full_contact |
|
139 | 134 | else: |
|
140 | 135 | repos_list[name].contact = sa.query(User)\ |
|
141 | 136 | .filter(User.admin == True).first().full_contact |
|
142 | 137 | except OSError: |
|
143 | 138 | continue |
|
144 | 139 | meta.Session.remove() |
|
145 | 140 | return repos_list |
|
146 | 141 | |
|
147 | 142 | def get_repos(self): |
|
148 | 143 | for name, repo in _get_repos_cached().items(): |
|
149 | 144 | if repo._get_hidden(): |
|
150 | 145 | #skip hidden web repository |
|
151 | 146 | continue |
|
152 | 147 | |
|
153 | 148 | last_change = repo.last_change |
|
154 | 149 | tip = h.get_changeset_safe(repo, 'tip') |
|
155 | 150 | |
|
156 | 151 | tmp_d = {} |
|
157 | 152 | tmp_d['name'] = repo.name |
|
158 | 153 | tmp_d['name_sort'] = tmp_d['name'].lower() |
|
159 | 154 | tmp_d['description'] = repo.description |
|
160 | 155 | tmp_d['description_sort'] = tmp_d['description'] |
|
161 | 156 | tmp_d['last_change'] = last_change |
|
162 | 157 | tmp_d['last_change_sort'] = last_change[1] - last_change[0] |
|
163 | 158 | tmp_d['tip'] = tip.raw_id |
|
164 | 159 | tmp_d['tip_sort'] = tip.revision |
|
165 | 160 | tmp_d['rev'] = tip.revision |
|
166 | 161 | tmp_d['contact'] = repo.contact |
|
167 | 162 | tmp_d['contact_sort'] = tmp_d['contact'] |
|
168 | 163 | tmp_d['repo_archives'] = list(repo._get_archives()) |
|
169 | 164 | tmp_d['last_msg'] = tip.message |
|
170 | 165 | tmp_d['repo'] = repo |
|
171 | 166 | yield tmp_d |
|
172 | 167 | |
|
173 | 168 | def get_repo(self, repo_name): |
|
174 | 169 | return _get_repos_cached()[repo_name] |
@@ -1,15 +1,58 b'' | |||
|
1 | 1 | """SQLAlchemy Metadata and Session object""" |
|
2 | 2 | from sqlalchemy.ext.declarative import declarative_base |
|
3 | 3 | from sqlalchemy.orm import scoped_session, sessionmaker |
|
4 | from pylons_app.model import caching_query | |
|
5 | from beaker import cache | |
|
6 | import os | |
|
7 | from os.path import join as jn, dirname as dn, abspath | |
|
8 | import time | |
|
9 | ||
|
10 | # Beaker CacheManager. A home base for cache configurations. | |
|
11 | cache_manager = cache.CacheManager() | |
|
4 | 12 | |
|
5 | 13 | __all__ = ['Base', 'Session'] |
|
6 | 14 | # |
|
7 | 15 | # SQLAlchemy session manager. Updated by model.init_model() |
|
8 | 16 | # |
|
9 |
Session = scoped_session( |
|
|
10 | # | |
|
17 | Session = scoped_session( | |
|
18 | sessionmaker( | |
|
19 | query_cls=caching_query.query_callable(cache_manager) | |
|
20 | ) | |
|
21 | ) | |
|
11 | 22 | |
|
12 | 23 | # The declarative Base |
|
13 | 24 | Base = declarative_base() |
|
14 | 25 | #For another db... |
|
15 | 26 | #Base2 = declarative_base() |
|
27 | ||
|
28 | #=============================================================================== | |
|
29 | # CACHE OPTIONS | |
|
30 | #=============================================================================== | |
|
31 | cache_dir = jn(dn(dn(dn(abspath(__file__)))), 'data', 'cache') | |
|
32 | if not os.path.isdir(cache_dir): | |
|
33 | os.mkdir(cache_dir) | |
|
34 | # set start_time to current time | |
|
35 | # to re-cache everything | |
|
36 | # upon application startup | |
|
37 | start_time = time.time() | |
|
38 | # configure the "sqlalchemy" cache region. | |
|
39 | cache_manager.regions['sql_cache_short'] = { | |
|
40 | 'type':'memory', | |
|
41 | 'data_dir':cache_dir, | |
|
42 | 'expire':10, | |
|
43 | 'start_time':start_time | |
|
44 | } | |
|
45 | cache_manager.regions['sql_cache_med'] = { | |
|
46 | 'type':'memory', | |
|
47 | 'data_dir':cache_dir, | |
|
48 | 'expire':360, | |
|
49 | 'start_time':start_time | |
|
50 | } | |
|
51 | cache_manager.regions['sql_cache_long'] = { | |
|
52 | 'type':'file', | |
|
53 | 'data_dir':cache_dir, | |
|
54 | 'expire':3600, | |
|
55 | 'start_time':start_time | |
|
56 | } | |
|
57 | #to use cache use this in query | |
|
58 | #.options(FromCache("sqlalchemy_cache_type", "cachekey")) |
@@ -1,131 +1,135 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # Model for users |
|
4 | 4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
5 | ||
|
5 | # | |
|
6 | 6 | # This program is free software; you can redistribute it and/or |
|
7 | 7 | # modify it under the terms of the GNU General Public License |
|
8 | 8 | # as published by the Free Software Foundation; version 2 |
|
9 | 9 | # of the License or (at your opinion) any later version of the license. |
|
10 | 10 | # |
|
11 | 11 | # This program is distributed in the hope that it will be useful, |
|
12 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 | 14 | # GNU General Public License for more details. |
|
15 | 15 | # |
|
16 | 16 | # You should have received a copy of the GNU General Public License |
|
17 | 17 | # along with this program; if not, write to the Free Software |
|
18 | 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 | 19 | # MA 02110-1301, USA. |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | Created on April 9, 2010 |
|
23 | 23 | Model for users |
|
24 | 24 | @author: marcink |
|
25 | 25 | """ |
|
26 | ||
|
26 | from pylons_app.lib import auth | |
|
27 | from pylons.i18n.translation import _ | |
|
28 | from pylons_app.lib.celerylib import tasks, run_task | |
|
27 | 29 | from pylons_app.model.db import User |
|
28 | 30 | from pylons_app.model.meta import Session |
|
29 | from pylons.i18n.translation import _ | |
|
31 | import traceback | |
|
30 | 32 | import logging |
|
31 | 33 | log = logging.getLogger(__name__) |
|
32 | 34 | |
|
33 | 35 | class DefaultUserException(Exception):pass |
|
34 | 36 | |
|
35 | 37 | class UserModel(object): |
|
36 | 38 | |
|
37 | 39 | def __init__(self): |
|
38 | 40 | self.sa = Session() |
|
39 | 41 | |
|
40 | 42 | def get_default(self): |
|
41 | 43 | return self.sa.query(User).filter(User.username == 'default').scalar() |
|
42 | 44 | |
|
43 | 45 | def get_user(self, id): |
|
44 | 46 | return self.sa.query(User).get(id) |
|
45 | 47 | |
|
46 | def get_user_by_name(self,name): | |
|
48 | def get_user_by_name(self, name): | |
|
47 | 49 | return self.sa.query(User).filter(User.username == name).scalar() |
|
48 | 50 | |
|
49 | 51 | def create(self, form_data): |
|
50 | 52 | try: |
|
51 | 53 | new_user = User() |
|
52 | 54 | for k, v in form_data.items(): |
|
53 | 55 | setattr(new_user, k, v) |
|
54 | 56 | |
|
55 | 57 | self.sa.add(new_user) |
|
56 | 58 | self.sa.commit() |
|
57 |
except |
|
|
58 | log.error(e) | |
|
59 | except: | |
|
60 | log.error(traceback.format_exc()) | |
|
59 | 61 | self.sa.rollback() |
|
60 | 62 | raise |
|
61 | 63 | |
|
62 | 64 | def create_registration(self, form_data): |
|
63 | 65 | try: |
|
64 | 66 | new_user = User() |
|
65 | 67 | for k, v in form_data.items(): |
|
66 | 68 | if k != 'admin': |
|
67 | 69 | setattr(new_user, k, v) |
|
68 | 70 | |
|
69 | 71 | self.sa.add(new_user) |
|
70 | 72 | self.sa.commit() |
|
71 |
except |
|
|
72 | log.error(e) | |
|
73 | except: | |
|
74 | log.error(traceback.format_exc()) | |
|
73 | 75 | self.sa.rollback() |
|
74 | 76 | raise |
|
75 | 77 | |
|
76 | 78 | def update(self, uid, form_data): |
|
77 | 79 | try: |
|
78 | 80 | new_user = self.sa.query(User).get(uid) |
|
79 | 81 | if new_user.username == 'default': |
|
80 | 82 | raise DefaultUserException( |
|
81 | 83 | _("You can't Edit this user since it's" |
|
82 | 84 | " crucial for entire application")) |
|
83 | 85 | for k, v in form_data.items(): |
|
84 | 86 | if k == 'new_password' and v != '': |
|
85 | 87 | new_user.password = v |
|
86 | 88 | else: |
|
87 | 89 | setattr(new_user, k, v) |
|
88 | 90 | |
|
89 | 91 | self.sa.add(new_user) |
|
90 | 92 | self.sa.commit() |
|
91 |
except |
|
|
92 | log.error(e) | |
|
93 | except: | |
|
94 | log.error(traceback.format_exc()) | |
|
93 | 95 | self.sa.rollback() |
|
94 | 96 | raise |
|
95 | 97 | |
|
96 | 98 | def update_my_account(self, uid, form_data): |
|
97 | 99 | try: |
|
98 | 100 | new_user = self.sa.query(User).get(uid) |
|
99 | 101 | if new_user.username == 'default': |
|
100 | 102 | raise DefaultUserException( |
|
101 | 103 | _("You can't Edit this user since it's" |
|
102 | 104 | " crucial for entire application")) |
|
103 | 105 | for k, v in form_data.items(): |
|
104 | 106 | if k == 'new_password' and v != '': |
|
105 | 107 | new_user.password = v |
|
106 | 108 | else: |
|
107 | 109 | if k not in ['admin', 'active']: |
|
108 | 110 | setattr(new_user, k, v) |
|
109 | 111 | |
|
110 | 112 | self.sa.add(new_user) |
|
111 | 113 | self.sa.commit() |
|
112 |
except |
|
|
113 | log.error(e) | |
|
114 | except: | |
|
115 | log.error(traceback.format_exc()) | |
|
114 | 116 | self.sa.rollback() |
|
115 | 117 | raise |
|
116 | 118 | |
|
117 | 119 | def delete(self, id): |
|
118 | ||
|
119 | 120 | try: |
|
120 | 121 | |
|
121 | 122 | user = self.sa.query(User).get(id) |
|
122 | 123 | if user.username == 'default': |
|
123 | 124 | raise DefaultUserException( |
|
124 | 125 | _("You can't remove this user since it's" |
|
125 | 126 | " crucial for entire application")) |
|
126 | 127 | self.sa.delete(user) |
|
127 | 128 | self.sa.commit() |
|
128 |
except |
|
|
129 | log.error(e) | |
|
129 | except: | |
|
130 | log.error(traceback.format_exc()) | |
|
130 | 131 | self.sa.rollback() |
|
131 | 132 | raise |
|
133 | ||
|
134 | def reset_password(self, data): | |
|
135 | run_task(tasks.reset_user_password, data['email']) |
@@ -1,3633 +1,3660 b'' | |||
|
1 | 1 | /* ----------------------------------------------------------- |
|
2 | 2 | main stylesheet |
|
3 | 3 | ----------------------------------------------------------- */ |
|
4 | 4 | |
|
5 | 5 | html |
|
6 | 6 | { |
|
7 | 7 | height: 100%; |
|
8 | 8 | } |
|
9 | 9 | |
|
10 | 10 | body |
|
11 | 11 | { |
|
12 | 12 | margin: 0; |
|
13 | 13 | padding: 0; |
|
14 | 14 | height: 100%; |
|
15 | 15 | background: #d1d1d1 url("../images/background.png") repeat; |
|
16 | 16 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
17 | 17 | font-size: 11px; |
|
18 | 18 | } |
|
19 | 19 | |
|
20 | 20 | /* ----------------------------------------------------------- |
|
21 | 21 | images |
|
22 | 22 | ----------------------------------------------------------- */ |
|
23 | 23 | |
|
24 | 24 | img |
|
25 | 25 | { |
|
26 | 26 | border: none; |
|
27 | 27 | } |
|
28 | 28 | |
|
29 | 29 | img.icon{ |
|
30 | 30 | vertical-align: bottom; |
|
31 | 31 | |
|
32 | 32 | } |
|
33 | 33 | /* ----------------------------------------------------------- |
|
34 | 34 | anchors |
|
35 | 35 | ----------------------------------------------------------- */ |
|
36 | 36 | |
|
37 | 37 | a |
|
38 | 38 | { |
|
39 | 39 | color: #0066CC; |
|
40 | 40 | text-decoration: none; |
|
41 | 41 | cursor: pointer; |
|
42 | 42 | } |
|
43 | 43 | |
|
44 | 44 | a:hover |
|
45 | 45 | { |
|
46 | 46 | color: #000000; |
|
47 | 47 | text-decoration: underline; |
|
48 | 48 | } |
|
49 | 49 | |
|
50 | 50 | /* ----------------------------------------------------------- |
|
51 | 51 | headings |
|
52 | 52 | ----------------------------------------------------------- */ |
|
53 | 53 | |
|
54 | 54 | h1, h2, h3, h4, h5, h6 |
|
55 | 55 | { |
|
56 | 56 | color: #292929; |
|
57 | 57 | font-weight: bold; |
|
58 | 58 | } |
|
59 | 59 | |
|
60 | 60 | h1 |
|
61 | 61 | { |
|
62 | 62 | font-size: 22px; |
|
63 | 63 | } |
|
64 | 64 | |
|
65 | 65 | h2 |
|
66 | 66 | { |
|
67 | 67 | font-size: 20px; |
|
68 | 68 | } |
|
69 | 69 | |
|
70 | 70 | h3 |
|
71 | 71 | { |
|
72 | 72 | font-size: 18px; |
|
73 | 73 | } |
|
74 | 74 | |
|
75 | 75 | h4 |
|
76 | 76 | { |
|
77 | 77 | font-size: 16px; |
|
78 | 78 | } |
|
79 | 79 | |
|
80 | 80 | h5 |
|
81 | 81 | { |
|
82 | 82 | font-size: 14px; |
|
83 | 83 | } |
|
84 | 84 | |
|
85 | 85 | h6 |
|
86 | 86 | { |
|
87 | 87 | font-size: 11px; |
|
88 | 88 | } |
|
89 | 89 | |
|
90 | 90 | /* ----------------------------------------------------------- |
|
91 | 91 | lists |
|
92 | 92 | ----------------------------------------------------------- */ |
|
93 | 93 | |
|
94 | 94 | ul.circle { list-style-type: circle; } |
|
95 | 95 | ul.disc { list-style-type: disc; } |
|
96 | 96 | ul.square { list-style-type: square; } |
|
97 | 97 | ol.lower-roman { list-style-type: lower-roman; } |
|
98 | 98 | ol.upper-roman { list-style-type: upper-roman; } |
|
99 | 99 | ol.lower-alpha { list-style-type: lower-alpha; } |
|
100 | 100 | ol.upper-alpha { list-style-type: upper-alpha; } |
|
101 | 101 | ol.decimal { list-style-type: decimal; } |
|
102 | 102 | |
|
103 | 103 | /* ----------------------------------------------------------- |
|
104 | 104 | colors |
|
105 | 105 | ----------------------------------------------------------- */ |
|
106 | 106 | |
|
107 | 107 | div.color |
|
108 | 108 | { |
|
109 | 109 | margin: 7px 0 0 60px; |
|
110 | 110 | padding: 1px 1px 1px 0px; |
|
111 | 111 | clear: both; |
|
112 | 112 | overflow: hidden; |
|
113 | 113 | position: absolute; |
|
114 | 114 | background: #FFFFFF; |
|
115 | 115 | } |
|
116 | 116 | |
|
117 | 117 | div.color a |
|
118 | 118 | { |
|
119 | 119 | margin: 0 0 0 1px; |
|
120 | 120 | padding: 0; |
|
121 | 121 | width: 15px; |
|
122 | 122 | height: 15px; |
|
123 | 123 | display: block; |
|
124 | 124 | float: left; |
|
125 | 125 | } |
|
126 | 126 | |
|
127 | 127 | div.color a.blue |
|
128 | 128 | { |
|
129 | 129 | background: #376ea6; |
|
130 | 130 | } |
|
131 | 131 | |
|
132 | 132 | div.color a.green |
|
133 | 133 | { |
|
134 | 134 | background: #85924b; |
|
135 | 135 | } |
|
136 | 136 | |
|
137 | 137 | div.color a.brown |
|
138 | 138 | { |
|
139 | 139 | background: #9b6e42; |
|
140 | 140 | } |
|
141 | 141 | |
|
142 | 142 | div.color a.purple |
|
143 | 143 | { |
|
144 | 144 | background: #88528b; |
|
145 | 145 | } |
|
146 | 146 | |
|
147 | 147 | div.color a.red |
|
148 | 148 | { |
|
149 | 149 | background: #bd3220; |
|
150 | 150 | } |
|
151 | 151 | |
|
152 | 152 | div.color a.greyblue |
|
153 | 153 | { |
|
154 | 154 | background: #566e86; |
|
155 | 155 | } |
|
156 | 156 | |
|
157 | 157 | /* ----------------------------------------------------------- |
|
158 | 158 | options |
|
159 | 159 | ----------------------------------------------------------- */ |
|
160 | 160 | |
|
161 | 161 | div.options |
|
162 | 162 | { |
|
163 | 163 | margin: 7px 0 0 162px; |
|
164 | 164 | padding: 0; |
|
165 | 165 | clear: both; |
|
166 | 166 | overflow: hidden; |
|
167 | 167 | position: absolute; |
|
168 | 168 | background: #FFFFFF; |
|
169 | 169 | } |
|
170 | 170 | |
|
171 | 171 | div.options a |
|
172 | 172 | { |
|
173 | 173 | margin: 0; |
|
174 | 174 | padding: 3px 8px 3px 8px; |
|
175 | 175 | height: 1%; |
|
176 | 176 | display: block; |
|
177 | 177 | text-decoration: none; |
|
178 | 178 | } |
|
179 | 179 | |
|
180 | 180 | div.options a:hover |
|
181 | 181 | { |
|
182 | 182 | text-decoration: none; |
|
183 | 183 | } |
|
184 | 184 | |
|
185 | 185 | /* ----------------------------------------------------------- |
|
186 | 186 | header |
|
187 | 187 | ----------------------------------------------------------- */ |
|
188 | 188 | |
|
189 | 189 | #header |
|
190 | 190 | { |
|
191 | 191 | margin: 0; |
|
192 | 192 | padding: 0 30px 0 30px; |
|
193 | 193 | background: #b0b0b0 url("../images/header_background.png") repeat; |
|
194 | 194 | } |
|
195 | 195 | |
|
196 | 196 | |
|
197 | 197 | /* ----------------------------------------------------------- |
|
198 | 198 | header -> user |
|
199 | 199 | ----------------------------------------------------------- */ |
|
200 | 200 | |
|
201 | 201 | #header ul#logged-user |
|
202 | 202 | { |
|
203 | 203 | margin: 0; |
|
204 | 204 | padding: 0; |
|
205 | 205 | float: right; |
|
206 | 206 | } |
|
207 | 207 | |
|
208 | 208 | #header ul#logged-user li |
|
209 | 209 | { |
|
210 | 210 | margin: 0; |
|
211 | 211 | padding: 10px 12px 10px 12px; |
|
212 | 212 | list-style: none; |
|
213 | 213 | float: left; |
|
214 | 214 | border-left: 1px solid #bbbbbb; |
|
215 | 215 | border-right: 1px solid #a5a5a5; |
|
216 | 216 | } |
|
217 | 217 | |
|
218 | 218 | #header ul#logged-user li.first |
|
219 | 219 | { |
|
220 | 220 | border-left: none; |
|
221 | 221 | margin:-6px; |
|
222 | 222 | } |
|
223 | 223 | #header ul#logged-user li.first div.account |
|
224 | 224 | { |
|
225 | 225 | padding-top: 4px; |
|
226 | 226 | float: left; |
|
227 | 227 | } |
|
228 | 228 | |
|
229 | 229 | |
|
230 | 230 | #header ul#logged-user li.last |
|
231 | 231 | { |
|
232 | 232 | border-right: none; |
|
233 | 233 | } |
|
234 | 234 | |
|
235 | 235 | #header ul#logged-user li a |
|
236 | 236 | { |
|
237 | 237 | color: #4e4e4e; |
|
238 | 238 | font-weight: bold; |
|
239 | 239 | text-decoration: none; |
|
240 | 240 | } |
|
241 | 241 | |
|
242 | 242 | #header ul#logged-user li a:hover |
|
243 | 243 | { |
|
244 | 244 | color: #376ea6; |
|
245 | 245 | text-decoration: underline; |
|
246 | 246 | } |
|
247 | 247 | |
|
248 | 248 | #header ul#logged-user li.highlight a |
|
249 | 249 | { |
|
250 | 250 | color: #ffffff; |
|
251 | 251 | } |
|
252 | 252 | |
|
253 | 253 | #header ul#logged-user li.highlight a:hover |
|
254 | 254 | { |
|
255 | 255 | color: #376ea6; |
|
256 | 256 | } |
|
257 | 257 | |
|
258 | 258 | #header #header-inner |
|
259 | 259 | { |
|
260 | 260 | margin: 0; |
|
261 | 261 | padding: 0; |
|
262 | 262 | height: 40px; |
|
263 | 263 | clear: both; |
|
264 | 264 | position: relative; |
|
265 | 265 | background: #003367 url("../images/colors/blue/header_inner.png") repeat-x; |
|
266 | 266 | border-bottom: 6px solid #ffffff; |
|
267 | 267 | } |
|
268 | 268 | |
|
269 | 269 | /* ----------------------------------------------------------- |
|
270 | 270 | header -> home |
|
271 | 271 | ----------------------------------------------------------- */ |
|
272 | 272 | |
|
273 | 273 | #header #header-inner #home |
|
274 | 274 | { |
|
275 | 275 | float: left; |
|
276 | 276 | } |
|
277 | 277 | |
|
278 | 278 | #header #header-inner #home a |
|
279 | 279 | { |
|
280 | 280 | margin: 0; |
|
281 | 281 | padding: 0; |
|
282 | 282 | height: 40px; |
|
283 | 283 | width: 46px; |
|
284 | 284 | display: block; |
|
285 | 285 | background: url("../images/colors/blue/button_home.png"); |
|
286 | 286 | background-position: 0 0; |
|
287 | 287 | } |
|
288 | 288 | |
|
289 | 289 | #header #header-inner #home a:hover |
|
290 | 290 | { |
|
291 | 291 | background-position: 0 -40px; |
|
292 | 292 | } |
|
293 | 293 | |
|
294 | 294 | /* ----------------------------------------------------------- |
|
295 | 295 | header -> logo |
|
296 | 296 | ----------------------------------------------------------- */ |
|
297 | 297 | |
|
298 | 298 | #header #header-inner #logo |
|
299 | 299 | { |
|
300 | 300 | float: left; |
|
301 | 301 | } |
|
302 | 302 | |
|
303 | 303 | #header #header-inner #logo h1 |
|
304 | 304 | { |
|
305 | 305 | margin: 13px 0 0 13px; |
|
306 | 306 | padding: 0; |
|
307 | 307 | color: #FFFFFF; |
|
308 | 308 | font-size: 14px; |
|
309 | 309 | text-transform: uppercase; |
|
310 | 310 | } |
|
311 | 311 | |
|
312 | 312 | #header #header-inner #logo a |
|
313 | 313 | { |
|
314 | 314 | color: #ffffff; |
|
315 | 315 | text-decoration: none; |
|
316 | 316 | } |
|
317 | 317 | |
|
318 | 318 | #header #header-inner #logo a:hover |
|
319 | 319 | { |
|
320 | 320 | color: #dabf29; |
|
321 | 321 | } |
|
322 | 322 | |
|
323 | 323 | /* ----------------------------------------------------------- |
|
324 | 324 | header -> quick |
|
325 | 325 | ----------------------------------------------------------- */ |
|
326 | 326 | #header #header-inner #quick, |
|
327 | 327 | #header #header-inner #quick ul |
|
328 | 328 | { |
|
329 | 329 | margin: 10px 5px 0 0; |
|
330 | 330 | padding: 0; |
|
331 | 331 | position: relative; |
|
332 | 332 | float: right; |
|
333 | 333 | list-style-type: none; |
|
334 | 334 | list-style-position: outside; |
|
335 | 335 | } |
|
336 | 336 | |
|
337 | 337 | #header #header-inner #quick li |
|
338 | 338 | { |
|
339 | 339 | margin: 0 5px 0 0; |
|
340 | 340 | padding: 0; |
|
341 | 341 | position: relative; |
|
342 | 342 | float: left; |
|
343 | 343 | } |
|
344 | 344 | |
|
345 | 345 | #header #header-inner #quick li a |
|
346 | 346 | { |
|
347 | 347 | top: 0; |
|
348 | 348 | left: 0; |
|
349 | 349 | padding: 0; |
|
350 | 350 | height: 1%; |
|
351 | 351 | display: block; |
|
352 | 352 | clear: both; |
|
353 | 353 | overflow: hidden; |
|
354 | 354 | background: #336699 url("../../resources/images/colors/blue/quick_l.png") no-repeat top left; |
|
355 | 355 | color: #FFFFFF; |
|
356 | 356 | font-weight: bold; |
|
357 | 357 | text-decoration: none; |
|
358 | 358 | } |
|
359 | 359 | |
|
360 | 360 | #header #header-inner #quick li span |
|
361 | 361 | { |
|
362 | 362 | top: 0; |
|
363 | 363 | right: 0; |
|
364 | 364 | margin: 0; |
|
365 | 365 | padding: 10px 12px 8px 10px; |
|
366 | 366 | height: 1%; |
|
367 | 367 | display: block; |
|
368 | 368 | float: left; |
|
369 | 369 | background: url("../../resources/images/colors/blue/quick_r.png") no-repeat top right; |
|
370 | 370 | border-left: 1px solid #3f6f9f; |
|
371 | 371 | } |
|
372 | 372 | |
|
373 | 373 | #header #header-inner #quick li span.normal |
|
374 | 374 | { |
|
375 | 375 | padding: 10px 12px 8px 12px; |
|
376 | 376 | border: none; |
|
377 | 377 | } |
|
378 | 378 | |
|
379 | 379 | #header #header-inner #quick li span.icon |
|
380 | 380 | { |
|
381 | 381 | top: 0; |
|
382 | 382 | left: 0; |
|
383 | 383 | padding: 8px 8px 4px 8px; |
|
384 | 384 | background: url("../../resources/images/colors/blue/quick_l.png") no-repeat top left; |
|
385 | 385 | border-left: none; |
|
386 | 386 | border-right: 1px solid #2e5c89; |
|
387 | 387 | } |
|
388 | 388 | |
|
389 | 389 | #header #header-inner #quick li a:hover |
|
390 | 390 | { |
|
391 | 391 | background: #4e4e4e url("../../resources/images/colors/blue/quick_l_selected.png") no-repeat top left; |
|
392 | 392 | } |
|
393 | 393 | |
|
394 | 394 | #header #header-inner #quick li a:hover span |
|
395 | 395 | { |
|
396 | 396 | background: url("../../resources/images/colors/blue/quick_r_selected.png") no-repeat top right; |
|
397 | 397 | border-left: 1px solid #545454; |
|
398 | 398 | } |
|
399 | 399 | |
|
400 | 400 | #header #header-inner #quick li a:hover span.normal |
|
401 | 401 | { |
|
402 | 402 | border: none; |
|
403 | 403 | } |
|
404 | 404 | |
|
405 | 405 | #header #header-inner #quick li a:hover span.icon |
|
406 | 406 | { |
|
407 | 407 | background: url("../../resources/images/colors/blue/quick_l_selected.png") no-repeat top left; |
|
408 | 408 | border-left: none; |
|
409 | 409 | border-right: 1px solid #464646; |
|
410 | 410 | } |
|
411 | 411 | |
|
412 | 412 | #header #header-inner #quick ul |
|
413 | 413 | { |
|
414 | 414 | top: 29px; |
|
415 | 415 | right: 0; |
|
416 | 416 | margin: 0; |
|
417 | 417 | padding: 0; |
|
418 | 418 | width: 200px; |
|
419 | 419 | display: none; |
|
420 | 420 | position: absolute; |
|
421 | 421 | background: #FFFFFF; |
|
422 | 422 | border: 1px solid #666; |
|
423 | 423 | border-top: 1px solid #003367; |
|
424 | 424 | z-index: 100; |
|
425 | 425 | } |
|
426 | 426 | |
|
427 | 427 | #header #header-inner #quick ul.repo_switcher{ |
|
428 | 428 | max-height:275px; |
|
429 | 429 | overflow-x:hidden; |
|
430 | 430 | overflow-y:auto; |
|
431 | 431 | white-space:nowrap; |
|
432 | 432 | } |
|
433 | 433 | |
|
434 | 434 | #header #header-inner #quick li ul li |
|
435 | 435 | { |
|
436 | 436 | border-bottom: 1px solid #dddddd; |
|
437 | 437 | } |
|
438 | 438 | |
|
439 | 439 | #header #header-inner #quick li ul li.last |
|
440 | 440 | { |
|
441 | 441 | border: none; |
|
442 | 442 | } |
|
443 | 443 | |
|
444 | 444 | #header #header-inner #quick li ul li a |
|
445 | 445 | { |
|
446 | 446 | margin: 0; |
|
447 | 447 | padding: 7px 9px 7px 9px; |
|
448 | 448 | height: 1%; |
|
449 | 449 | width: 182px; |
|
450 | 450 | height: auto; |
|
451 | 451 | display: block; |
|
452 | 452 | float: left; |
|
453 | 453 | background: #FFFFFF; |
|
454 | 454 | color: #0066CC; |
|
455 | 455 | font-weight: normal; |
|
456 | 456 | } |
|
457 | 457 | |
|
458 | 458 | #header #header-inner #quick li ul li a.childs |
|
459 | 459 | { |
|
460 | 460 | margin: 0; |
|
461 | 461 | padding: 7px 9px 7px 24px; |
|
462 | 462 | width: 167px; |
|
463 | 463 | background: #FFFFFF url("../../resources/images/plus.png") no-repeat 8px 9px; |
|
464 | 464 | } |
|
465 | 465 | |
|
466 | 466 | #header #header-inner #quick li ul li a:hover |
|
467 | 467 | { |
|
468 | 468 | color: #000000; |
|
469 | 469 | background: #FFFFFF; |
|
470 | 470 | } |
|
471 | 471 | |
|
472 | 472 | #header #header-inner #quick li ul li a.childs:hover |
|
473 | 473 | { |
|
474 | 474 | background: #FFFFFF url("../../resources/images/minus.png") no-repeat 8px 9px; |
|
475 | 475 | } |
|
476 | 476 | |
|
477 | 477 | #header #header-inner #quick ul ul |
|
478 | 478 | { |
|
479 | 479 | top: auto; |
|
480 | 480 | } |
|
481 | 481 | |
|
482 | 482 | #header #header-inner #quick li ul ul |
|
483 | 483 | { |
|
484 | 484 | right: 200px; |
|
485 | 485 | max-height: 275px; |
|
486 | 486 | overflow: auto; |
|
487 | 487 | overflow-x: hidden; |
|
488 | 488 | white-space:nowrap; |
|
489 | 489 | } |
|
490 | 490 | |
|
491 | 491 | #header #header-inner #quick li:hover ul ul, |
|
492 | 492 | #header #header-inner #quick li:hover ul ul ul, |
|
493 | 493 | #header #header-inner #quick li:hover ul ul ul ul |
|
494 | 494 | { |
|
495 | 495 | display: none; |
|
496 | 496 | } |
|
497 | 497 | |
|
498 | 498 | #header #header-inner #quick li:hover ul, |
|
499 | 499 | #header #header-inner #quick li li:hover ul, |
|
500 | 500 | #header #header-inner #quick li li li:hover ul, |
|
501 | 501 | #header #header-inner #quick li li li li:hover ul |
|
502 | 502 | { |
|
503 | 503 | display: block; |
|
504 | 504 | } |
|
505 | 505 | |
|
506 | 506 | |
|
507 | 507 | /*ICONS*/ |
|
508 | #header #header-inner #quick li ul li a.journal, | |
|
509 | #header #header-inner #quick li ul li a.journal:hover | |
|
510 | { | |
|
511 | background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFFFFF; | |
|
512 | margin:0; | |
|
513 | padding:12px 9px 7px 24px; | |
|
514 | width:167px; | |
|
515 | ||
|
516 | } | |
|
517 | #header #header-inner #quick li ul li a.private_repo, | |
|
518 | #header #header-inner #quick li ul li a.private_repo:hover | |
|
519 | { | |
|
520 | background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFFFFF; | |
|
521 | margin:0; | |
|
522 | padding:12px 9px 7px 24px; | |
|
523 | width:167px; | |
|
524 | ||
|
525 | } | |
|
526 | #header #header-inner #quick li ul li a.public_repo, | |
|
527 | #header #header-inner #quick li ul li a.public_repo:hover | |
|
528 | { | |
|
529 | background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFFFFF; | |
|
530 | margin:0; | |
|
531 | padding:12px 9px 7px 24px; | |
|
532 | width:167px; | |
|
533 | ||
|
534 | } | |
|
508 | 535 | |
|
509 | 536 | #header #header-inner #quick li ul li a.repos, |
|
510 | 537 | #header #header-inner #quick li ul li a.repos:hover |
|
511 | 538 | { |
|
512 | 539 | background:url("../images/icons/folder_edit.png") no-repeat scroll 4px 9px #FFFFFF; |
|
513 | 540 | margin:0; |
|
514 | 541 | padding:12px 9px 7px 24px; |
|
515 | 542 | width:167px; |
|
516 | 543 | |
|
517 | 544 | } |
|
518 | 545 | #header #header-inner #quick li ul li a.users, |
|
519 | 546 | #header #header-inner #quick li ul li a.users:hover |
|
520 | 547 | { |
|
521 | 548 | background: #FFFFFF url("../images/icons/user_edit.png") no-repeat 4px 9px; |
|
522 | 549 | margin:0; |
|
523 | 550 | padding:12px 9px 7px 24px; |
|
524 | 551 | width:167px; |
|
525 | 552 | } |
|
526 | 553 | #header #header-inner #quick li ul li a.settings, |
|
527 | 554 | #header #header-inner #quick li ul li a.settings:hover |
|
528 | 555 | { |
|
529 | 556 | background: #FFFFFF url("../images/icons/cog.png") no-repeat 4px 9px; |
|
530 | 557 | margin:0; |
|
531 | 558 | padding:12px 9px 7px 24px; |
|
532 | 559 | width:167px; |
|
533 | 560 | } |
|
534 | 561 | |
|
535 | 562 | #header #header-inner #quick li ul li a.permissions, |
|
536 | 563 | #header #header-inner #quick li ul li a.permissions:hover |
|
537 | 564 | { |
|
538 | 565 | |
|
539 | 566 | background: #FFFFFF url("../images/icons/key.png") no-repeat 4px 9px; |
|
540 | 567 | margin:0; |
|
541 | 568 | padding:12px 9px 7px 24px; |
|
542 | 569 | width:167px; |
|
543 | 570 | } |
|
544 | 571 | |
|
545 | 572 | #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover |
|
546 | 573 | { |
|
547 | 574 | |
|
548 | 575 | background: #FFFFFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px; |
|
549 | 576 | margin:0; |
|
550 | 577 | padding:12px 9px 7px 24px; |
|
551 | 578 | width:167px; |
|
552 | 579 | } |
|
553 | 580 | |
|
554 | 581 | #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover |
|
555 | 582 | { |
|
556 | 583 | |
|
557 | 584 | background: #FFFFFF url("../images/icons/tag_blue.png") no-repeat 4px 9px; |
|
558 | 585 | margin:0; |
|
559 | 586 | padding:12px 9px 7px 24px; |
|
560 | 587 | width:167px; |
|
561 | 588 | } |
|
562 | 589 | /* ----------------------------------------------------------- |
|
563 | 590 | header corners |
|
564 | 591 | ----------------------------------------------------------- */ |
|
565 | 592 | |
|
566 | 593 | #header #header-inner div.corner |
|
567 | 594 | { |
|
568 | 595 | height: 6px; |
|
569 | 596 | width: 6px; |
|
570 | 597 | position: absolute; |
|
571 | 598 | background: url("../images/colors/blue/header_inner_corners.png") no-repeat; |
|
572 | 599 | } |
|
573 | 600 | |
|
574 | 601 | #header #header-inner div.tl |
|
575 | 602 | { |
|
576 | 603 | top: 0; |
|
577 | 604 | left: 0; |
|
578 | 605 | background-position: 0 0; |
|
579 | 606 | } |
|
580 | 607 | |
|
581 | 608 | #header #header-inner div.tr |
|
582 | 609 | { |
|
583 | 610 | top: 0; |
|
584 | 611 | right: 0; |
|
585 | 612 | background-position: -6px 0; |
|
586 | 613 | } |
|
587 | 614 | |
|
588 | 615 | /* ----------------------------------------------------------- |
|
589 | 616 | content |
|
590 | 617 | ----------------------------------------------------------- */ |
|
591 | 618 | |
|
592 | 619 | #content |
|
593 | 620 | { |
|
594 | 621 | margin: 10px 0 0 0; |
|
595 | 622 | padding: 0; |
|
596 | 623 | min-height: 100%; |
|
597 | 624 | clear: both; |
|
598 | 625 | overflow: hidden; |
|
599 | 626 | background: url("../images/content.png") repeat-y top left; |
|
600 | 627 | } |
|
601 | 628 | |
|
602 | 629 | /* ----------------------------------------------------------- |
|
603 | 630 | content -> left |
|
604 | 631 | ----------------------------------------------------------- */ |
|
605 | 632 | |
|
606 | 633 | #content #left |
|
607 | 634 | { |
|
608 | 635 | left: 0; |
|
609 | 636 | width: 280px; |
|
610 | 637 | position: absolute; |
|
611 | 638 | } |
|
612 | 639 | |
|
613 | 640 | /* ----------------------------------------------------------- |
|
614 | 641 | content -> left -> menu |
|
615 | 642 | ----------------------------------------------------------- */ |
|
616 | 643 | |
|
617 | 644 | #content #left #menu |
|
618 | 645 | { |
|
619 | 646 | margin: 5px 10px 0 60px; |
|
620 | 647 | padding: 0; |
|
621 | 648 | clear: both; |
|
622 | 649 | overflow: hidden; |
|
623 | 650 | } |
|
624 | 651 | |
|
625 | 652 | /* ----------------------------------------------------------- |
|
626 | 653 | content -> left -> menu / heading |
|
627 | 654 | ----------------------------------------------------------- */ |
|
628 | 655 | |
|
629 | 656 | #content #left #menu h6 |
|
630 | 657 | { |
|
631 | 658 | margin: 5px 0 0 0; |
|
632 | 659 | padding: 0; |
|
633 | 660 | clear: both; |
|
634 | 661 | overflow: hidden; |
|
635 | 662 | background: #dfdfdf url("../images/menu.png") repeat-x; |
|
636 | 663 | color: #6e6e6e; |
|
637 | 664 | } |
|
638 | 665 | |
|
639 | 666 | #content #left #menu h6 a |
|
640 | 667 | { |
|
641 | 668 | margin: 0; |
|
642 | 669 | padding: 0; |
|
643 | 670 | height: 1%; |
|
644 | 671 | display: block; |
|
645 | 672 | clear: both; |
|
646 | 673 | overflow: hidden; |
|
647 | 674 | background: url("../images/menu_l.png") no-repeat top left; |
|
648 | 675 | color: #6e6e6e; |
|
649 | 676 | text-decoration: none; |
|
650 | 677 | } |
|
651 | 678 | |
|
652 | 679 | #content #left #menu h6 span |
|
653 | 680 | { |
|
654 | 681 | margin: 0; |
|
655 | 682 | padding: 9px 10px 10px 10px; |
|
656 | 683 | height: 1%; |
|
657 | 684 | display: block; |
|
658 | 685 | background: url("../images/menu_r.png") no-repeat top right; |
|
659 | 686 | } |
|
660 | 687 | |
|
661 | 688 | #content #left #menu h6.selected |
|
662 | 689 | { |
|
663 | 690 | background: #00376e url("../images/colors/blue/menu_selected.png") repeat-x; |
|
664 | 691 | color: #FFFFFF; |
|
665 | 692 | } |
|
666 | 693 | |
|
667 | 694 | #content #left #menu h6.selected a |
|
668 | 695 | { |
|
669 | 696 | background: url("../images/colors/blue/menu_l_selected.png") no-repeat top left; |
|
670 | 697 | color: #ffffff; |
|
671 | 698 | } |
|
672 | 699 | |
|
673 | 700 | #content #left #menu h6.selected span |
|
674 | 701 | { |
|
675 | 702 | background: url("../images/colors/blue/menu_r_selected.png") no-repeat top right; |
|
676 | 703 | } |
|
677 | 704 | |
|
678 | 705 | /* ----------------------------------------------------------- |
|
679 | 706 | content -> left -> menu / links |
|
680 | 707 | ----------------------------------------------------------- */ |
|
681 | 708 | |
|
682 | 709 | #content #left #menu ul |
|
683 | 710 | { |
|
684 | 711 | margin: 0; |
|
685 | 712 | padding: 0; |
|
686 | 713 | background: #376ea6; |
|
687 | 714 | } |
|
688 | 715 | |
|
689 | 716 | #content #left #menu ul.opened |
|
690 | 717 | { |
|
691 | 718 | display: block; |
|
692 | 719 | } |
|
693 | 720 | |
|
694 | 721 | #content #left #menu ul.closed |
|
695 | 722 | { |
|
696 | 723 | display: none; |
|
697 | 724 | } |
|
698 | 725 | |
|
699 | 726 | #content #left #menu li |
|
700 | 727 | { |
|
701 | 728 | margin: 0; |
|
702 | 729 | padding: 0; |
|
703 | 730 | clear: both; |
|
704 | 731 | overflow: hidden; |
|
705 | 732 | list-style: none; |
|
706 | 733 | border-bottom: 1px solid #5f8bb7; |
|
707 | 734 | color: #ffffff; |
|
708 | 735 | } |
|
709 | 736 | |
|
710 | 737 | #content #left #menu li a |
|
711 | 738 | { |
|
712 | 739 | margin: 0 0 0 6px; |
|
713 | 740 | padding: 8px 0 8px 18px; |
|
714 | 741 | height: 1%; |
|
715 | 742 | display: block; |
|
716 | 743 | float: left; |
|
717 | 744 | background: url("../images/colors/colors/blue/menu_arrow.png") no-repeat 0 9px; |
|
718 | 745 | color: #ffffff; |
|
719 | 746 | text-decoration: none; |
|
720 | 747 | } |
|
721 | 748 | |
|
722 | 749 | #content #left #menu li a:hover |
|
723 | 750 | { |
|
724 | 751 | color: #b9dcff; |
|
725 | 752 | } |
|
726 | 753 | |
|
727 | 754 | /* ----------------------------------------------------------- |
|
728 | 755 | content -> left -> menu / collapsible |
|
729 | 756 | ----------------------------------------------------------- */ |
|
730 | 757 | |
|
731 | 758 | #content #left #menu li.collapsible |
|
732 | 759 | { |
|
733 | 760 | background: url("../images/colors/blue/menu_border.png") no-repeat top left; |
|
734 | 761 | } |
|
735 | 762 | |
|
736 | 763 | #content #left #menu li.collapsible a |
|
737 | 764 | { |
|
738 | 765 | margin: 0 0 0 6px; |
|
739 | 766 | padding: 8px 0 8px 0; |
|
740 | 767 | height: 1%; |
|
741 | 768 | display: block; |
|
742 | 769 | background: transparent; |
|
743 | 770 | float: left; |
|
744 | 771 | font-weight: bold; |
|
745 | 772 | } |
|
746 | 773 | |
|
747 | 774 | #content #left #menu li.collapsible a.plus |
|
748 | 775 | { |
|
749 | 776 | margin: 0; |
|
750 | 777 | padding: 8px 0 9px 24px; |
|
751 | 778 | height: 10px; |
|
752 | 779 | width: 10px; |
|
753 | 780 | display: block; |
|
754 | 781 | float: left; |
|
755 | 782 | background: url("../images/menu_plus.png") no-repeat 5px 10px; |
|
756 | 783 | border: none; |
|
757 | 784 | } |
|
758 | 785 | |
|
759 | 786 | #content #left #menu li.collapsible a.minus |
|
760 | 787 | { |
|
761 | 788 | margin: 0; |
|
762 | 789 | padding: 8px 0 9px 24px; |
|
763 | 790 | height: 10px; |
|
764 | 791 | width: 10px; |
|
765 | 792 | display: block; |
|
766 | 793 | float: left; |
|
767 | 794 | background: url("../images/menu_minus.png") no-repeat 5px 10px; |
|
768 | 795 | border: none; |
|
769 | 796 | } |
|
770 | 797 | |
|
771 | 798 | #content #left #menu li ul |
|
772 | 799 | { |
|
773 | 800 | margin: 0; |
|
774 | 801 | padding: 0; |
|
775 | 802 | border-left: 18px solid #285889; |
|
776 | 803 | } |
|
777 | 804 | |
|
778 | 805 | #content #left #menu li ul.expanded |
|
779 | 806 | { |
|
780 | 807 | display: block; |
|
781 | 808 | } |
|
782 | 809 | |
|
783 | 810 | #content #left #menu li ul.collapsed |
|
784 | 811 | { |
|
785 | 812 | display: none; |
|
786 | 813 | } |
|
787 | 814 | |
|
788 | 815 | #content #left #menu li ul li |
|
789 | 816 | { |
|
790 | 817 | margin: 0; |
|
791 | 818 | padding: 0; |
|
792 | 819 | clear: both; |
|
793 | 820 | overflow: hidden; |
|
794 | 821 | list-style: none; |
|
795 | 822 | border-bottom: 1px solid #5f8bb7; |
|
796 | 823 | color: #ffffff; |
|
797 | 824 | } |
|
798 | 825 | |
|
799 | 826 | #content #left #menu li.collapsible ul li a |
|
800 | 827 | { |
|
801 | 828 | font-weight: normal; |
|
802 | 829 | } |
|
803 | 830 | |
|
804 | 831 | #content #left #menu li.last |
|
805 | 832 | { |
|
806 | 833 | border-bottom: none; |
|
807 | 834 | } |
|
808 | 835 | |
|
809 | 836 | /* ----------------------------------------------------------- |
|
810 | 837 | content -> left -> date picker |
|
811 | 838 | ----------------------------------------------------------- */ |
|
812 | 839 | |
|
813 | 840 | #content #left #date-picker |
|
814 | 841 | { |
|
815 | 842 | margin: 10px 10px 0 60px; |
|
816 | 843 | padding: 0; |
|
817 | 844 | clear: both; |
|
818 | 845 | overflow: hidden; |
|
819 | 846 | } |
|
820 | 847 | |
|
821 | 848 | #content #left #date-picker .ui-datepicker |
|
822 | 849 | { |
|
823 | 850 | width: auto; |
|
824 | 851 | padding: 0; |
|
825 | 852 | clear: both; |
|
826 | 853 | overflow: hidden; |
|
827 | 854 | background: #FFFFFF; |
|
828 | 855 | border: 1px solid #d1d1d1; |
|
829 | 856 | } |
|
830 | 857 | |
|
831 | 858 | #content #left #date-picker .ui-datepicker .ui-datepicker-header |
|
832 | 859 | { |
|
833 | 860 | padding: 5px 0; |
|
834 | 861 | } |
|
835 | 862 | |
|
836 | 863 | #content #left #date-picker .ui-datepicker .ui-datepicker-prev |
|
837 | 864 | { |
|
838 | 865 | top: 5px; |
|
839 | 866 | left: 4px; |
|
840 | 867 | } |
|
841 | 868 | |
|
842 | 869 | #content #left #date-picker .ui-datepicker .ui-datepicker-next |
|
843 | 870 | { |
|
844 | 871 | top: 5px; |
|
845 | 872 | right: 4px; |
|
846 | 873 | } |
|
847 | 874 | |
|
848 | 875 | #content #left #date-picker .ui-datepicker .ui-datepicker-prev-hover |
|
849 | 876 | { |
|
850 | 877 | top: 5px; |
|
851 | 878 | left: 4px; |
|
852 | 879 | } |
|
853 | 880 | |
|
854 | 881 | #content #left #date-picker .ui-datepicker .ui-datepicker-next-hover |
|
855 | 882 | { |
|
856 | 883 | top: 5px; |
|
857 | 884 | right: 4px; |
|
858 | 885 | } |
|
859 | 886 | |
|
860 | 887 | /* ----------------------------------------------------------- |
|
861 | 888 | content -> right |
|
862 | 889 | ----------------------------------------------------------- */ |
|
863 | 890 | |
|
864 | 891 | #content #right |
|
865 | 892 | { |
|
866 | 893 | margin: 0 60px 10px 290px; |
|
867 | 894 | } |
|
868 | 895 | |
|
869 | 896 | /* ----------------------------------------------------------- |
|
870 | 897 | content -> right -> box |
|
871 | 898 | ----------------------------------------------------------- */ |
|
872 | 899 | |
|
873 | 900 | #content div.box |
|
874 | 901 | { |
|
875 | 902 | margin: 0 0 10px 0; |
|
876 | 903 | padding: 0 0 10px 0; |
|
877 | 904 | clear: both; |
|
878 | 905 | overflow: hidden; |
|
879 | 906 | background: #ffffff; |
|
880 | 907 | } |
|
881 | 908 | |
|
882 | 909 | #content div.box-left |
|
883 | 910 | { |
|
884 | 911 | margin: 0 0 10px; |
|
885 | 912 | width: 49%; |
|
886 | 913 | clear: none; |
|
887 | 914 | float: left; |
|
888 | 915 | } |
|
889 | 916 | |
|
890 | 917 | #content div.box-right |
|
891 | 918 | { |
|
892 | 919 | margin: 0 0 10px; |
|
893 | 920 | width: 49%; |
|
894 | 921 | clear: none; |
|
895 | 922 | float: right; |
|
896 | 923 | } |
|
897 | 924 | |
|
898 | 925 | /* ----------------------------------------------------------- |
|
899 | 926 | content -> right -> box / title |
|
900 | 927 | ----------------------------------------------------------- */ |
|
901 | 928 | |
|
902 | 929 | #content div.box div.title |
|
903 | 930 | { |
|
904 | 931 | margin: 0 0 20px 0; |
|
905 | 932 | padding: 0; |
|
906 | 933 | clear: both; |
|
907 | 934 | overflow: hidden; |
|
908 | 935 | background: #336699 url("../images/colors/blue/title.png") repeat-x; |
|
909 | 936 | } |
|
910 | 937 | |
|
911 | 938 | #content div.box div.title h5 |
|
912 | 939 | { |
|
913 | 940 | margin: 0; |
|
914 | 941 | padding: 11px 0 11px 10px; |
|
915 | 942 | float: left; |
|
916 | 943 | border: none; |
|
917 | 944 | color: #ffffff; |
|
918 | 945 | text-transform: uppercase; |
|
919 | 946 | } |
|
920 | 947 | |
|
921 | 948 | #content div.box div.title ul.links |
|
922 | 949 | { |
|
923 | 950 | margin: 0; |
|
924 | 951 | padding: 0; |
|
925 | 952 | float: right; |
|
926 | 953 | } |
|
927 | 954 | |
|
928 | 955 | #content div.box div.title ul.links li |
|
929 | 956 | { |
|
930 | 957 | margin: 0; |
|
931 | 958 | padding: 0; |
|
932 | 959 | list-style: none; |
|
933 | 960 | float: left; |
|
934 | 961 | } |
|
935 | 962 | |
|
936 | 963 | #content div.box div.title ul.links li a |
|
937 | 964 | { |
|
938 | 965 | margin: 0; |
|
939 | 966 | padding: 13px 16px 12px 16px; |
|
940 | 967 | height: 1%; |
|
941 | 968 | display: block; |
|
942 | 969 | float: left; |
|
943 | 970 | background: url("../images/colors/blue/title_link.png") no-repeat top left; |
|
944 | 971 | border-left: 1px solid #316293; |
|
945 | 972 | color: #ffffff; |
|
946 | 973 | font-size: 11px; |
|
947 | 974 | font-weight: bold; |
|
948 | 975 | text-decoration: none; |
|
949 | 976 | } |
|
950 | 977 | |
|
951 | 978 | #content div.box div.title ul.links li a:hover |
|
952 | 979 | { |
|
953 | 980 | color: #bfe3ff; |
|
954 | 981 | } |
|
955 | 982 | |
|
956 | 983 | #content div.box div.title ul.links li.ui-tabs-selected a |
|
957 | 984 | { |
|
958 | 985 | background: url("../../../resources/images/colors/blue/title_tab_selected.png") no-repeat bottom center; |
|
959 | 986 | color: #bfe3ff; |
|
960 | 987 | } |
|
961 | 988 | |
|
962 | 989 | /* ----------------------------------------------------------- |
|
963 | 990 | content -> right -> box / headings |
|
964 | 991 | ----------------------------------------------------------- */ |
|
965 | 992 | |
|
966 | 993 | #content div.box h1, |
|
967 | 994 | #content div.box h2, |
|
968 | 995 | #content div.box h3, |
|
969 | 996 | #content div.box h4, |
|
970 | 997 | #content div.box h5, |
|
971 | 998 | #content div.box h6 |
|
972 | 999 | { |
|
973 | 1000 | margin: 10px 20px 10px 20px; |
|
974 | 1001 | padding: 0 0 15px 0; |
|
975 | 1002 | clear: both; |
|
976 | 1003 | overflow: hidden; |
|
977 | 1004 | border-bottom: 1px solid #DDDDDD; |
|
978 | 1005 | } |
|
979 | 1006 | |
|
980 | 1007 | /* ----------------------------------------------------------- |
|
981 | 1008 | content -> right -> box / paragraphs |
|
982 | 1009 | ----------------------------------------------------------- */ |
|
983 | 1010 | |
|
984 | 1011 | #content div.box p |
|
985 | 1012 | { |
|
986 | 1013 | margin: 0 24px 10px 24px; |
|
987 | 1014 | padding: 0; |
|
988 | 1015 | color: #5f5f5f; |
|
989 | 1016 | font-size: 12px; |
|
990 | 1017 | line-height: 150%; |
|
991 | 1018 | } |
|
992 | 1019 | |
|
993 | 1020 | #content div.box blockquote |
|
994 | 1021 | { |
|
995 | 1022 | margin: 0 34px 0 34px; |
|
996 | 1023 | padding: 0 0 0 14px; |
|
997 | 1024 | border-left: 4px solid #DDDDDD; |
|
998 | 1025 | color: #5f5f5f; |
|
999 | 1026 | font-size: 11px; |
|
1000 | 1027 | line-height: 150%; |
|
1001 | 1028 | } |
|
1002 | 1029 | |
|
1003 | 1030 | #content div.box blockquote p |
|
1004 | 1031 | { |
|
1005 | 1032 | margin: 10px 0 10px 0; |
|
1006 | 1033 | padding: 0; |
|
1007 | 1034 | } |
|
1008 | 1035 | |
|
1009 | 1036 | /* ----------------------------------------------------------- |
|
1010 | 1037 | content -> right -> box / lists |
|
1011 | 1038 | ----------------------------------------------------------- */ |
|
1012 | 1039 | |
|
1013 | 1040 | #content div.box dl |
|
1014 | 1041 | { |
|
1015 | 1042 | margin: 10px 24px 10px 24px; |
|
1016 | 1043 | } |
|
1017 | 1044 | |
|
1018 | 1045 | #content div.box dt |
|
1019 | 1046 | { |
|
1020 | 1047 | margin: 0; |
|
1021 | 1048 | font-size: 12px; |
|
1022 | 1049 | } |
|
1023 | 1050 | |
|
1024 | 1051 | #content div.box dd |
|
1025 | 1052 | { |
|
1026 | 1053 | margin: 0; |
|
1027 | 1054 | padding: 8px 0 8px 15px; |
|
1028 | 1055 | font-size: 12px; |
|
1029 | 1056 | } |
|
1030 | 1057 | |
|
1031 | 1058 | #content div.box ul.left |
|
1032 | 1059 | { |
|
1033 | 1060 | float: left; |
|
1034 | 1061 | } |
|
1035 | 1062 | |
|
1036 | 1063 | #content div.box ol.left |
|
1037 | 1064 | { |
|
1038 | 1065 | float: left; |
|
1039 | 1066 | } |
|
1040 | 1067 | |
|
1041 | 1068 | #content div.box li |
|
1042 | 1069 | { |
|
1043 | 1070 | padding: 4px 0 4px 0; |
|
1044 | 1071 | font-size: 12px; |
|
1045 | 1072 | } |
|
1046 | 1073 | |
|
1047 | 1074 | #content div.box ol.lower-roman, |
|
1048 | 1075 | #content div.box ol.upper-roman |
|
1049 | 1076 | { |
|
1050 | 1077 | margin: 10px 24px 10px 44px; |
|
1051 | 1078 | } |
|
1052 | 1079 | |
|
1053 | 1080 | #content div.box ol.lower-alpha, |
|
1054 | 1081 | #content div.box ol.upper-alpha |
|
1055 | 1082 | { |
|
1056 | 1083 | margin: 10px 24px 10px 44px; |
|
1057 | 1084 | } |
|
1058 | 1085 | |
|
1059 | 1086 | #content div.box ol.decimal |
|
1060 | 1087 | { |
|
1061 | 1088 | margin: 10px 24px 10px 44px; |
|
1062 | 1089 | } |
|
1063 | 1090 | |
|
1064 | 1091 | #content div.box ul.disc, |
|
1065 | 1092 | #content div.box ul.circle |
|
1066 | 1093 | { |
|
1067 | 1094 | margin: 10px 24px 10px 38px; |
|
1068 | 1095 | } |
|
1069 | 1096 | |
|
1070 | 1097 | #content div.box ul.square |
|
1071 | 1098 | { |
|
1072 | 1099 | margin: 10px 24px 10px 40px; |
|
1073 | 1100 | } |
|
1074 | 1101 | |
|
1075 | 1102 | /* ----------------------------------------------------------- |
|
1076 | 1103 | content -> right -> box / images |
|
1077 | 1104 | ----------------------------------------------------------- */ |
|
1078 | 1105 | |
|
1079 | 1106 | #content div.box img.left |
|
1080 | 1107 | { |
|
1081 | 1108 | margin: 10px 10px 10px 0; |
|
1082 | 1109 | border: none; |
|
1083 | 1110 | float: left; |
|
1084 | 1111 | } |
|
1085 | 1112 | |
|
1086 | 1113 | #content div.box img.right |
|
1087 | 1114 | { |
|
1088 | 1115 | margin: 10px 0 10px 10px; |
|
1089 | 1116 | border: none; |
|
1090 | 1117 | float: right; |
|
1091 | 1118 | } |
|
1092 | 1119 | |
|
1093 | 1120 | /* ----------------------------------------------------------- |
|
1094 | 1121 | content -> right -> box / messages |
|
1095 | 1122 | ----------------------------------------------------------- */ |
|
1096 | 1123 | |
|
1097 | 1124 | #content div.box div.messages |
|
1098 | 1125 | { |
|
1099 | 1126 | margin: 0 20px 0 20px; |
|
1100 | 1127 | padding: 0; |
|
1101 | 1128 | clear: both; |
|
1102 | 1129 | overflow: hidden; |
|
1103 | 1130 | } |
|
1104 | 1131 | |
|
1105 | 1132 | #content div.box div.message |
|
1106 | 1133 | { |
|
1107 | 1134 | margin: 0 0 0px 0; |
|
1108 | 1135 | padding: 0 0 10px 0; |
|
1109 | 1136 | clear: both; |
|
1110 | 1137 | overflow: hidden; |
|
1111 | 1138 | } |
|
1112 | 1139 | |
|
1113 | 1140 | #content div.box div.message div.image |
|
1114 | 1141 | { |
|
1115 | 1142 | margin: 9px 0 0 5px; |
|
1116 | 1143 | padding: 6px; |
|
1117 | 1144 | float: left; |
|
1118 | 1145 | } |
|
1119 | 1146 | |
|
1120 | 1147 | #content div.box div.message div.image img |
|
1121 | 1148 | { |
|
1122 | 1149 | margin: 0; |
|
1123 | 1150 | vertical-align: middle; |
|
1124 | 1151 | } |
|
1125 | 1152 | |
|
1126 | 1153 | #content div.box div.message div.text |
|
1127 | 1154 | { |
|
1128 | 1155 | margin: 0; |
|
1129 | 1156 | padding: 9px 6px 9px 6px; |
|
1130 | 1157 | float: left; |
|
1131 | 1158 | } |
|
1132 | 1159 | |
|
1133 | 1160 | #content div.box div.message div.dismiss |
|
1134 | 1161 | { |
|
1135 | 1162 | margin: 0; |
|
1136 | 1163 | padding: 0; |
|
1137 | 1164 | float: right; |
|
1138 | 1165 | } |
|
1139 | 1166 | |
|
1140 | 1167 | #content div.box div.message div.dismiss a |
|
1141 | 1168 | { |
|
1142 | 1169 | margin: 15px 14px 0 0; |
|
1143 | 1170 | padding: 0; |
|
1144 | 1171 | height: 16px; |
|
1145 | 1172 | width: 16px; |
|
1146 | 1173 | display: block; |
|
1147 | 1174 | background: url("../images/icons/cross.png") no-repeat; |
|
1148 | 1175 | } |
|
1149 | 1176 | |
|
1150 | 1177 | #content div.box div.message div.text h1, |
|
1151 | 1178 | #content div.box div.message div.text h2, |
|
1152 | 1179 | #content div.box div.message div.text h3, |
|
1153 | 1180 | #content div.box div.message div.text h4, |
|
1154 | 1181 | #content div.box div.message div.text h5, |
|
1155 | 1182 | #content div.box div.message div.text h6 |
|
1156 | 1183 | { |
|
1157 | 1184 | margin: 0; |
|
1158 | 1185 | padding: 0px; |
|
1159 | 1186 | border: none; |
|
1160 | 1187 | } |
|
1161 | 1188 | |
|
1162 | 1189 | #content div.box div.message div.text span |
|
1163 | 1190 | { |
|
1164 | 1191 | margin: 0; |
|
1165 | 1192 | padding: 5px 0 0 0; |
|
1166 | 1193 | height: 1%; |
|
1167 | 1194 | display: block; |
|
1168 | 1195 | } |
|
1169 | 1196 | |
|
1170 | 1197 | #content div.box div.message-error |
|
1171 | 1198 | { |
|
1172 | 1199 | height: 1%; |
|
1173 | 1200 | clear: both; |
|
1174 | 1201 | overflow: hidden; |
|
1175 | 1202 | background: #FBE3E4; |
|
1176 | 1203 | border: 1px solid #FBC2C4; |
|
1177 | 1204 | color: #860006; |
|
1178 | 1205 | } |
|
1179 | 1206 | |
|
1180 | 1207 | #content div.box div.message-error h6 |
|
1181 | 1208 | { |
|
1182 | 1209 | color: #860006; |
|
1183 | 1210 | } |
|
1184 | 1211 | |
|
1185 | 1212 | #content div.box div.message-warning |
|
1186 | 1213 | { |
|
1187 | 1214 | height: 1%; |
|
1188 | 1215 | clear: both; |
|
1189 | 1216 | overflow: hidden; |
|
1190 | 1217 | background: #FFF6BF; |
|
1191 | 1218 | border: 1px solid #FFD324; |
|
1192 | 1219 | color: #5f5200; |
|
1193 | 1220 | } |
|
1194 | 1221 | |
|
1195 | 1222 | #content div.box div.message-warning h6 |
|
1196 | 1223 | { |
|
1197 | 1224 | color: #5f5200; |
|
1198 | 1225 | } |
|
1199 | 1226 | |
|
1200 | 1227 | #content div.box div.message-notice |
|
1201 | 1228 | { |
|
1202 | 1229 | height: 1%; |
|
1203 | 1230 | clear: both; |
|
1204 | 1231 | overflow: hidden; |
|
1205 | 1232 | background: #8FBDE0; |
|
1206 | 1233 | border: 1px solid #6BACDE; |
|
1207 | 1234 | color: #003863; |
|
1208 | 1235 | } |
|
1209 | 1236 | |
|
1210 | 1237 | #content div.box div.message-notice h6 |
|
1211 | 1238 | { |
|
1212 | 1239 | color: #003863; |
|
1213 | 1240 | } |
|
1214 | 1241 | |
|
1215 | 1242 | #content div.box div.message-success |
|
1216 | 1243 | { |
|
1217 | 1244 | height: 1%; |
|
1218 | 1245 | clear: both; |
|
1219 | 1246 | overflow: hidden; |
|
1220 | 1247 | background: #E6EFC2; |
|
1221 | 1248 | border: 1px solid #C6D880; |
|
1222 | 1249 | color: #4e6100; |
|
1223 | 1250 | } |
|
1224 | 1251 | |
|
1225 | 1252 | #content div.box div.message-success h6 |
|
1226 | 1253 | { |
|
1227 | 1254 | color: #4e6100; |
|
1228 | 1255 | } |
|
1229 | 1256 | |
|
1230 | 1257 | /* ----------------------------------------------------------- |
|
1231 | 1258 | content -> right -> box / forms |
|
1232 | 1259 | ----------------------------------------------------------- */ |
|
1233 | 1260 | |
|
1234 | 1261 | #content div.box div.form |
|
1235 | 1262 | { |
|
1236 | 1263 | margin: 0; |
|
1237 | 1264 | padding: 0 20px 10px 20px; |
|
1238 | 1265 | clear: both; |
|
1239 | 1266 | overflow: hidden; |
|
1240 | 1267 | } |
|
1241 | 1268 | |
|
1242 | 1269 | #content div.box div.form div.fields |
|
1243 | 1270 | { |
|
1244 | 1271 | margin: 0; |
|
1245 | 1272 | padding: 0; |
|
1246 | 1273 | clear: both; |
|
1247 | 1274 | overflow: hidden; |
|
1248 | 1275 | } |
|
1249 | 1276 | |
|
1250 | 1277 | #content div.box div.form div.fields div.field |
|
1251 | 1278 | { |
|
1252 | 1279 | margin: 0; |
|
1253 | 1280 | padding: 10px 0 10px 0; |
|
1254 | 1281 | height: 1%; |
|
1255 | 1282 | border-bottom: 1px solid #DDDDDD; |
|
1256 | 1283 | clear: both; |
|
1257 | 1284 | overflow: hidden; |
|
1258 | 1285 | } |
|
1259 | 1286 | |
|
1260 | 1287 | #content div.box div.form div.fields div.field-first |
|
1261 | 1288 | { |
|
1262 | 1289 | padding: 0 0 10px 0; |
|
1263 | 1290 | } |
|
1264 | 1291 | |
|
1265 | 1292 | #content div.box div.form div.fields div.field span.error-message |
|
1266 | 1293 | { |
|
1267 | 1294 | margin: 8px 0 0 0; |
|
1268 | 1295 | padding: 0; |
|
1269 | 1296 | height: 1%; |
|
1270 | 1297 | display: block; |
|
1271 | 1298 | color: #FF0000; |
|
1272 | 1299 | } |
|
1273 | 1300 | |
|
1274 | 1301 | #content div.box div.form div.fields div.field span.success |
|
1275 | 1302 | { |
|
1276 | 1303 | margin: 8px 0 0 0; |
|
1277 | 1304 | padding: 0; |
|
1278 | 1305 | height: 1%; |
|
1279 | 1306 | display: block; |
|
1280 | 1307 | color: #316309; |
|
1281 | 1308 | } |
|
1282 | 1309 | |
|
1283 | 1310 | /* ----------------------------------------------------------- |
|
1284 | 1311 | content -> right -> forms -> labels |
|
1285 | 1312 | ----------------------------------------------------------- */ |
|
1286 | 1313 | |
|
1287 | 1314 | #content div.box div.form div.fields div.field div.label |
|
1288 | 1315 | { |
|
1289 | 1316 | left: 310px; |
|
1290 | 1317 | margin: 0; |
|
1291 | 1318 | padding: 8px 0 0 5px; |
|
1292 | 1319 | width: auto; |
|
1293 | 1320 | position: absolute; |
|
1294 | 1321 | } |
|
1295 | 1322 | |
|
1296 | 1323 | #content div.box-left div.form div.fields div.field div.label, |
|
1297 | 1324 | #content div.box-right div.form div.fields div.field div.label |
|
1298 | 1325 | { |
|
1299 | 1326 | left: 0; |
|
1300 | 1327 | margin: 0; |
|
1301 | 1328 | padding: 0 0 8px 0; |
|
1302 | 1329 | width: auto; |
|
1303 | 1330 | position: relative; |
|
1304 | 1331 | clear: both; |
|
1305 | 1332 | overflow: hidden; |
|
1306 | 1333 | |
|
1307 | 1334 | } |
|
1308 | 1335 | |
|
1309 | 1336 | /* ----------------------------------------------------------- |
|
1310 | 1337 | content -> right -> forms -> label (select) |
|
1311 | 1338 | ----------------------------------------------------------- */ |
|
1312 | 1339 | |
|
1313 | 1340 | #content div.box div.form div.fields div.field div.label-select |
|
1314 | 1341 | { |
|
1315 | 1342 | padding: 2px 0 0 5px; |
|
1316 | 1343 | } |
|
1317 | 1344 | |
|
1318 | 1345 | #content div.box-left div.form div.fields div.field div.label-select, |
|
1319 | 1346 | #content div.box-right div.form div.fields div.field div.label-select |
|
1320 | 1347 | { |
|
1321 | 1348 | padding: 0 0 8px 0; |
|
1322 | 1349 | } |
|
1323 | 1350 | |
|
1324 | 1351 | /* ----------------------------------------------------------- |
|
1325 | 1352 | content -> right -> forms -> label (checkbox) |
|
1326 | 1353 | ----------------------------------------------------------- */ |
|
1327 | 1354 | |
|
1328 | 1355 | #content div.box div.form div.fields div.field div.label-checkbox |
|
1329 | 1356 | { |
|
1330 | 1357 | padding:0 0 0 5px !important; |
|
1331 | 1358 | } |
|
1332 | 1359 | |
|
1333 | 1360 | /* ----------------------------------------------------------- |
|
1334 | 1361 | content -> right -> forms -> label (radio) |
|
1335 | 1362 | ----------------------------------------------------------- */ |
|
1336 | 1363 | |
|
1337 | 1364 | #content div.box div.form div.fields div.field div.label-radio |
|
1338 | 1365 | { |
|
1339 | 1366 | padding:0 0 0 5px !important; |
|
1340 | 1367 | } |
|
1341 | 1368 | |
|
1342 | 1369 | /* ----------------------------------------------------------- |
|
1343 | 1370 | content -> right -> forms -> label (textarea) |
|
1344 | 1371 | ----------------------------------------------------------- */ |
|
1345 | 1372 | |
|
1346 | 1373 | #content div.box div.form div.fields div.field div.label-textarea |
|
1347 | 1374 | { |
|
1348 | 1375 | padding:0 0 0 5px !important; |
|
1349 | 1376 | } |
|
1350 | 1377 | |
|
1351 | 1378 | #content div.box-left div.form div.fields div.field div.label-textarea, |
|
1352 | 1379 | #content div.box-right div.form div.fields div.field div.label-textarea |
|
1353 | 1380 | { |
|
1354 | 1381 | padding: 0 0 8px 0 !important; |
|
1355 | 1382 | } |
|
1356 | 1383 | |
|
1357 | 1384 | /* ----------------------------------------------------------- |
|
1358 | 1385 | content -> right -> forms -> labels (label) |
|
1359 | 1386 | ----------------------------------------------------------- */ |
|
1360 | 1387 | |
|
1361 | 1388 | #content div.box div.form div.fields div.field div.label label |
|
1362 | 1389 | { |
|
1363 | 1390 | color: #393939; |
|
1364 | 1391 | font-weight: bold; |
|
1365 | 1392 | } |
|
1366 | 1393 | |
|
1367 | 1394 | #content div.box div.form div.fields div.field div.label span |
|
1368 | 1395 | { |
|
1369 | 1396 | margin: 0; |
|
1370 | 1397 | padding: 2px 0 0 0; |
|
1371 | 1398 | height: 1%; |
|
1372 | 1399 | display: block; |
|
1373 | 1400 | color: #363636; |
|
1374 | 1401 | } |
|
1375 | 1402 | |
|
1376 | 1403 | /* ----------------------------------------------------------- |
|
1377 | 1404 | content -> right -> forms -> input |
|
1378 | 1405 | ----------------------------------------------------------- */ |
|
1379 | 1406 | |
|
1380 | 1407 | #content div.box div.form div.fields div.field div.input |
|
1381 | 1408 | { |
|
1382 | 1409 | margin: 0 0 0 200px; |
|
1383 | 1410 | padding: 0; |
|
1384 | 1411 | } |
|
1385 | 1412 | |
|
1386 | 1413 | #content div.box-left div.form div.fields div.field div.input, |
|
1387 | 1414 | #content div.box-right div.form div.fields div.field div.input |
|
1388 | 1415 | { |
|
1389 | 1416 | margin: 0; |
|
1390 | 1417 | padding: 7px 7px 6px 7px; |
|
1391 | 1418 | clear: both; |
|
1392 | 1419 | overflow: hidden; |
|
1393 | 1420 | border-top: 1px solid #b3b3b3; |
|
1394 | 1421 | border-left: 1px solid #b3b3b3; |
|
1395 | 1422 | border-right: 1px solid #eaeaea; |
|
1396 | 1423 | border-bottom: 1px solid #eaeaea; |
|
1397 | 1424 | |
|
1398 | 1425 | } |
|
1399 | 1426 | |
|
1400 | 1427 | #content div.box div.form div.fields div.field div.input input |
|
1401 | 1428 | { |
|
1402 | 1429 | margin: 0; |
|
1403 | 1430 | padding: 7px 7px 6px 7px; |
|
1404 | 1431 | background: #FFFFFF; |
|
1405 | 1432 | border-top: 1px solid #b3b3b3; |
|
1406 | 1433 | border-left: 1px solid #b3b3b3; |
|
1407 | 1434 | border-right: 1px solid #eaeaea; |
|
1408 | 1435 | border-bottom: 1px solid #eaeaea; |
|
1409 | 1436 | color: #000000; |
|
1410 | 1437 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
1411 | 1438 | font-size: 11px; |
|
1412 | 1439 | float: left; |
|
1413 | 1440 | } |
|
1414 | 1441 | |
|
1415 | 1442 | #content div.box-left div.form div.fields div.field div.input input, |
|
1416 | 1443 | #content div.box-right div.form div.fields div.field div.input input |
|
1417 | 1444 | { |
|
1418 | 1445 | width: 100%; |
|
1419 | 1446 | padding: 0; |
|
1420 | 1447 | border: none; |
|
1421 | 1448 | } |
|
1422 | 1449 | |
|
1423 | 1450 | #content div.box div.form div.fields div.field div.input input.small |
|
1424 | 1451 | { |
|
1425 | 1452 | width: 30%; |
|
1426 | 1453 | } |
|
1427 | 1454 | |
|
1428 | 1455 | #content div.box div.form div.fields div.field div.input input.medium |
|
1429 | 1456 | { |
|
1430 | 1457 | width: 55%; |
|
1431 | 1458 | } |
|
1432 | 1459 | |
|
1433 | 1460 | #content div.box div.form div.fields div.field div.input input.large |
|
1434 | 1461 | { |
|
1435 | 1462 | width: 85%; |
|
1436 | 1463 | } |
|
1437 | 1464 | |
|
1438 | 1465 | #content div.box div.form div.fields div.field div.input input.date |
|
1439 | 1466 | { |
|
1440 | 1467 | width: 177px; |
|
1441 | 1468 | } |
|
1442 | 1469 | |
|
1443 | 1470 | #content div.box div.form div.fields div.field div.input input.button |
|
1444 | 1471 | { |
|
1445 | 1472 | margin: 0; |
|
1446 | 1473 | padding: 4px 8px 4px 8px; |
|
1447 | 1474 | background: #D4D0C8; |
|
1448 | 1475 | border-top: 1px solid #FFFFFF; |
|
1449 | 1476 | border-left: 1px solid #FFFFFF; |
|
1450 | 1477 | border-right: 1px solid #404040; |
|
1451 | 1478 | border-bottom: 1px solid #404040; |
|
1452 | 1479 | color: #000000; |
|
1453 | 1480 | } |
|
1454 | 1481 | |
|
1455 | 1482 | #content div.box div.form div.fields div.field div.input input.error |
|
1456 | 1483 | { |
|
1457 | 1484 | background: #FBE3E4; |
|
1458 | 1485 | border-top: 1px solid #e1b2b3; |
|
1459 | 1486 | border-left: 1px solid #e1b2b3; |
|
1460 | 1487 | border-right: 1px solid #FBC2C4; |
|
1461 | 1488 | border-bottom: 1px solid #FBC2C4; |
|
1462 | 1489 | } |
|
1463 | 1490 | |
|
1464 | 1491 | #content div.box div.form div.fields div.field div.input input.success |
|
1465 | 1492 | { |
|
1466 | 1493 | background: #E6EFC2; |
|
1467 | 1494 | border-top: 1px solid #cebb98; |
|
1468 | 1495 | border-left: 1px solid #cebb98; |
|
1469 | 1496 | border-right: 1px solid #c6d880; |
|
1470 | 1497 | border-bottom: 1px solid #c6d880; |
|
1471 | 1498 | } |
|
1472 | 1499 | |
|
1473 | 1500 | #content div.box div.form div.fields div.field div.input img.ui-datepicker-trigger |
|
1474 | 1501 | { |
|
1475 | 1502 | margin: 0 0 0 6px; |
|
1476 | 1503 | } |
|
1477 | 1504 | |
|
1478 | 1505 | /* ----------------------------------------------------------- |
|
1479 | 1506 | content -> right -> forms -> input (file styling) |
|
1480 | 1507 | ----------------------------------------------------------- */ |
|
1481 | 1508 | |
|
1482 | 1509 | #content div.box div.form div.fields div.field div.input a.ui-input-file |
|
1483 | 1510 | { |
|
1484 | 1511 | margin: 0 0 0 6px; |
|
1485 | 1512 | padding: 0; |
|
1486 | 1513 | width: 28px; |
|
1487 | 1514 | height: 28px; |
|
1488 | 1515 | display: inline; |
|
1489 | 1516 | position: absolute; |
|
1490 | 1517 | overflow: hidden; |
|
1491 | 1518 | cursor: pointer; |
|
1492 | 1519 | background: #e5e3e3 url("../images/button_browse.png") no-repeat; |
|
1493 | 1520 | border: none; |
|
1494 | 1521 | text-decoration: none; |
|
1495 | 1522 | } |
|
1496 | 1523 | |
|
1497 | 1524 | #content div.box div.form div.fields div.field div.input a:hover.ui-input-file |
|
1498 | 1525 | { |
|
1499 | 1526 | background: #e5e3e3 url("../images/button_browse_selected.png") no-repeat; |
|
1500 | 1527 | } |
|
1501 | 1528 | |
|
1502 | 1529 | /* ----------------------------------------------------------- |
|
1503 | 1530 | content -> right -> forms -> textarea |
|
1504 | 1531 | ----------------------------------------------------------- */ |
|
1505 | 1532 | |
|
1506 | 1533 | #content div.box div.form div.fields div.field div.textarea |
|
1507 | 1534 | { |
|
1508 | 1535 | margin: 0 0 0 200px; |
|
1509 | 1536 | padding: 10px; |
|
1510 | 1537 | border-top: 1px solid #b3b3b3; |
|
1511 | 1538 | border-left: 1px solid #b3b3b3; |
|
1512 | 1539 | border-right: 1px solid #eaeaea; |
|
1513 | 1540 | border-bottom: 1px solid #eaeaea; |
|
1514 | 1541 | } |
|
1515 | 1542 | |
|
1516 | 1543 | #content div.box div.form div.fields div.field div.textarea-editor |
|
1517 | 1544 | { |
|
1518 | 1545 | padding: 0; |
|
1519 | 1546 | border: 1px solid #dddddd; |
|
1520 | 1547 | } |
|
1521 | 1548 | |
|
1522 | 1549 | #content div.box-left div.form div.fields div.field div.textarea, |
|
1523 | 1550 | #content div.box-right div.form div.fields div.field div.textarea |
|
1524 | 1551 | { |
|
1525 | 1552 | margin: 0; |
|
1526 | 1553 | } |
|
1527 | 1554 | |
|
1528 | 1555 | #content div.box div.form div.fields div.field div.textarea textarea |
|
1529 | 1556 | { |
|
1530 | 1557 | margin: 0; |
|
1531 | 1558 | padding: 0; |
|
1532 | 1559 | width: 100%; |
|
1533 | 1560 | height: 220px; |
|
1534 | 1561 | overflow: hidden; |
|
1535 | 1562 | background: #FFFFFF; |
|
1536 | 1563 | border-width: 0; |
|
1537 | 1564 | color: #000000; |
|
1538 | 1565 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
1539 | 1566 | font-size: 11px; |
|
1540 | 1567 | outline: none; |
|
1541 | 1568 | } |
|
1542 | 1569 | |
|
1543 | 1570 | #content div.box-left div.form div.fields div.field div.textarea textarea, |
|
1544 | 1571 | #content div.box-right div.form div.fields div.field div.textarea textarea |
|
1545 | 1572 | { |
|
1546 | 1573 | width: 100%; |
|
1547 | 1574 | height: 100px; |
|
1548 | 1575 | } |
|
1549 | 1576 | |
|
1550 | 1577 | #content div.box div.form div.fields div.field div.textarea textarea.error |
|
1551 | 1578 | { |
|
1552 | 1579 | padding: 3px 10px 10px 23px; |
|
1553 | 1580 | background-color: #FBE3E4; |
|
1554 | 1581 | background-image: url("../../../resources/images/icons/exclamation.png"); |
|
1555 | 1582 | background-repeat: no-repeat; |
|
1556 | 1583 | background-position: 3px 3px; |
|
1557 | 1584 | border: 1px solid #FBC2C4; |
|
1558 | 1585 | } |
|
1559 | 1586 | |
|
1560 | 1587 | #content div.box div.form div.fields div.field div.textarea textarea.success |
|
1561 | 1588 | { |
|
1562 | 1589 | padding: 3px 10px 10px 23px; |
|
1563 | 1590 | background-color: #E6EFC2; |
|
1564 | 1591 | background-image: url("../../../resources/images/icons/accept.png"); |
|
1565 | 1592 | background-repeat: no-repeat; |
|
1566 | 1593 | background-position: 3px 3px; |
|
1567 | 1594 | border: 1px solid #C6D880; |
|
1568 | 1595 | } |
|
1569 | 1596 | |
|
1570 | 1597 | /* ----------------------------------------------------------- |
|
1571 | 1598 | content -> right -> forms -> textarea (tinymce editor) |
|
1572 | 1599 | ----------------------------------------------------------- */ |
|
1573 | 1600 | |
|
1574 | 1601 | #content div.box div.form div.fields div.field div.textarea table |
|
1575 | 1602 | { |
|
1576 | 1603 | margin: 0; |
|
1577 | 1604 | padding: 0; |
|
1578 | 1605 | width: 100%; |
|
1579 | 1606 | border: none; |
|
1580 | 1607 | } |
|
1581 | 1608 | |
|
1582 | 1609 | #content div.box div.form div.fields div.field div.textarea table td |
|
1583 | 1610 | { |
|
1584 | 1611 | padding: 0; |
|
1585 | 1612 | background: #DDDDDD; |
|
1586 | 1613 | border: none; |
|
1587 | 1614 | } |
|
1588 | 1615 | |
|
1589 | 1616 | #content div.box div.form div.fields div.field div.textarea table td table |
|
1590 | 1617 | { |
|
1591 | 1618 | margin: 0; |
|
1592 | 1619 | padding: 0; |
|
1593 | 1620 | width: auto; |
|
1594 | 1621 | border: none; |
|
1595 | 1622 | } |
|
1596 | 1623 | |
|
1597 | 1624 | #content div.box div.form div.fields div.field div.textarea table td table td |
|
1598 | 1625 | { |
|
1599 | 1626 | padding: 5px 5px 5px 0; |
|
1600 | 1627 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
1601 | 1628 | font-size: 11px; |
|
1602 | 1629 | } |
|
1603 | 1630 | |
|
1604 | 1631 | #content div.box div.form div.fields div.field div.textarea table td table td a |
|
1605 | 1632 | { |
|
1606 | 1633 | border: none; |
|
1607 | 1634 | } |
|
1608 | 1635 | |
|
1609 | 1636 | #content div.box div.form div.fields div.field div.textarea table td table td a.mceButtonActive |
|
1610 | 1637 | { |
|
1611 | 1638 | background: #b1b1b1; |
|
1612 | 1639 | } |
|
1613 | 1640 | |
|
1614 | 1641 | /* ----------------------------------------------------------- |
|
1615 | 1642 | content -> right -> forms -> select |
|
1616 | 1643 | ----------------------------------------------------------- */ |
|
1617 | 1644 | |
|
1618 | 1645 | #content div.box div.form div.fields div.field div.select |
|
1619 | 1646 | { |
|
1620 | 1647 | margin: 0 0 0 200px; |
|
1621 | 1648 | padding: 0; |
|
1622 | 1649 | } |
|
1623 | 1650 | |
|
1624 | 1651 | #content div.box div.form div.fields div.field div.select a:hover |
|
1625 | 1652 | { |
|
1626 | 1653 | color: #000000; |
|
1627 | 1654 | text-decoration: none; |
|
1628 | 1655 | } |
|
1629 | 1656 | |
|
1630 | 1657 | #content div.box div.form div.fields div.field div.select select |
|
1631 | 1658 | { |
|
1632 | 1659 | margin: 0; |
|
1633 | 1660 | } |
|
1634 | 1661 | |
|
1635 | 1662 | /* ----------------------------------------------------------- |
|
1636 | 1663 | content -> right -> forms -> select (jquery styling) |
|
1637 | 1664 | ----------------------------------------------------------- */ |
|
1638 | 1665 | |
|
1639 | 1666 | #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus |
|
1640 | 1667 | { |
|
1641 | 1668 | border: 1px solid #666666; |
|
1642 | 1669 | } |
|
1643 | 1670 | |
|
1644 | 1671 | #content div.box div.form div.fields div.field div.select a.ui-selectmenu |
|
1645 | 1672 | { |
|
1646 | 1673 | color: #565656; |
|
1647 | 1674 | text-decoration: none; |
|
1648 | 1675 | } |
|
1649 | 1676 | |
|
1650 | 1677 | #content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover |
|
1651 | 1678 | { |
|
1652 | 1679 | color: #000000; |
|
1653 | 1680 | text-decoration: none; |
|
1654 | 1681 | } |
|
1655 | 1682 | |
|
1656 | 1683 | #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus span.ui-icon |
|
1657 | 1684 | { |
|
1658 | 1685 | background-image: url(../images/ui/ui-icons_222222_256x240.png); |
|
1659 | 1686 | } |
|
1660 | 1687 | |
|
1661 | 1688 | /* ----------------------------------------------------------- |
|
1662 | 1689 | content -> right -> forms -> element focus |
|
1663 | 1690 | ----------------------------------------------------------- */ |
|
1664 | 1691 | |
|
1665 | 1692 | #content div.box div.form div.fields div.field input[type=text]:focus, |
|
1666 | 1693 | #content div.box div.form div.fields div.field input[type=password]:focus, |
|
1667 | 1694 | #content div.box div.form div.fields div.field input[type=file]:focus, |
|
1668 | 1695 | #content div.box div.form div.fields div.field textarea:focus, |
|
1669 | 1696 | #content div.box div.form div.fields div.field select:focus |
|
1670 | 1697 | { |
|
1671 | 1698 | background: #f6f6f6; |
|
1672 | 1699 | border-color: #666; |
|
1673 | 1700 | } |
|
1674 | 1701 | |
|
1675 | 1702 | /* ----------------------------------------------------------- |
|
1676 | 1703 | content -> right -> forms -> checkboxes |
|
1677 | 1704 | ----------------------------------------------------------- */ |
|
1678 | 1705 | |
|
1679 | 1706 | #content div.box div.form div.fields div.field div.checkboxes |
|
1680 | 1707 | { |
|
1681 | 1708 | margin: 0 0 0 200px; |
|
1682 | 1709 | padding: 0; |
|
1683 | 1710 | } |
|
1684 | 1711 | |
|
1685 | 1712 | #content div.box div.form div.fields div.field div.checkboxes div.checkbox |
|
1686 | 1713 | { |
|
1687 | 1714 | margin: 0; |
|
1688 | 1715 | padding: 2px 0 2px 0; |
|
1689 | 1716 | clear: both; |
|
1690 | 1717 | overflow: hidden; |
|
1691 | 1718 | } |
|
1692 | 1719 | |
|
1693 | 1720 | #content div.box div.form div.fields div.field div.checkboxes div.checkbox input |
|
1694 | 1721 | { |
|
1695 | 1722 | margin: 0; |
|
1696 | 1723 | float: left; |
|
1697 | 1724 | } |
|
1698 | 1725 | |
|
1699 | 1726 | #content div.box div.form div.fields div.field div.checkboxes div.checkbox label |
|
1700 | 1727 | { |
|
1701 | 1728 | margin: 3px 0 0 4px; |
|
1702 | 1729 | height: 1%; |
|
1703 | 1730 | display: block; |
|
1704 | 1731 | float: left; |
|
1705 | 1732 | } |
|
1706 | 1733 | |
|
1707 | 1734 | /* ----------------------------------------------------------- |
|
1708 | 1735 | content -> right -> forms -> radios |
|
1709 | 1736 | ----------------------------------------------------------- */ |
|
1710 | 1737 | |
|
1711 | 1738 | #content div.box div.form div.fields div.field div.radios |
|
1712 | 1739 | { |
|
1713 | 1740 | margin: 0 0 0 200px; |
|
1714 | 1741 | padding: 0; |
|
1715 | 1742 | } |
|
1716 | 1743 | |
|
1717 | 1744 | #content div.box div.form div.fields div.field div.radios div.radio |
|
1718 | 1745 | { |
|
1719 | 1746 | margin: 0; |
|
1720 | 1747 | padding: 2px 0 2px 0; |
|
1721 | 1748 | clear: both; |
|
1722 | 1749 | overflow: hidden; |
|
1723 | 1750 | } |
|
1724 | 1751 | |
|
1725 | 1752 | #content div.box div.form div.fields div.field div.radios div.radio input |
|
1726 | 1753 | { |
|
1727 | 1754 | margin: 0; |
|
1728 | 1755 | float: left; |
|
1729 | 1756 | } |
|
1730 | 1757 | |
|
1731 | 1758 | #content div.box div.form div.fields div.field div.radios div.radio label |
|
1732 | 1759 | { |
|
1733 | 1760 | margin: 3px 0 0 4px; |
|
1734 | 1761 | height: 1%; |
|
1735 | 1762 | display: block; |
|
1736 | 1763 | float: left; |
|
1737 | 1764 | } |
|
1738 | 1765 | /* ----------------------------------------------------------- |
|
1739 | 1766 | content -> right -> forms -> button |
|
1740 | 1767 | ----------------------------------------------------------- */ |
|
1741 | 1768 | |
|
1742 | 1769 | div.form div.fields div.field div.button |
|
1743 | 1770 | { |
|
1744 | 1771 | margin: 0; |
|
1745 | 1772 | padding: 0 0 0 8px; |
|
1746 | 1773 | float: left; |
|
1747 | 1774 | } |
|
1748 | 1775 | |
|
1749 | 1776 | div.form div.fields div.field div.button input |
|
1750 | 1777 | { |
|
1751 | 1778 | margin: 0; |
|
1752 | 1779 | color: #000000; |
|
1753 | 1780 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
1754 | 1781 | font-size: 11px; |
|
1755 | 1782 | font-weight: bold; |
|
1756 | 1783 | } |
|
1757 | 1784 | |
|
1758 | 1785 | div.form div.fields div.field div.button .ui-state-default |
|
1759 | 1786 | { |
|
1760 | 1787 | margin: 0; |
|
1761 | 1788 | padding: 6px 12px 6px 12px; |
|
1762 | 1789 | background: #e5e3e3 url("../images/button.png") repeat-x; |
|
1763 | 1790 | border-top: 1px solid #DDDDDD; |
|
1764 | 1791 | border-left: 1px solid #c6c6c6; |
|
1765 | 1792 | border-right: 1px solid #DDDDDD; |
|
1766 | 1793 | border-bottom: 1px solid #c6c6c6; |
|
1767 | 1794 | color: #515151; |
|
1768 | 1795 | outline: none; |
|
1769 | 1796 | } |
|
1770 | 1797 | |
|
1771 | 1798 | div.form div.fields div.field div.button .ui-state-hover |
|
1772 | 1799 | { |
|
1773 | 1800 | margin: 0; |
|
1774 | 1801 | padding: 6px 12px 6px 12px; |
|
1775 | 1802 | background: #b4b4b4 url("../images/button_selected.png") repeat-x; |
|
1776 | 1803 | border-top: 1px solid #cccccc; |
|
1777 | 1804 | border-left: 1px solid #bebebe; |
|
1778 | 1805 | border-right: 1px solid #b1b1b1; |
|
1779 | 1806 | border-bottom: 1px solid #afafaf; |
|
1780 | 1807 | color: #515151; |
|
1781 | 1808 | outline: none; |
|
1782 | 1809 | } |
|
1783 | 1810 | |
|
1784 | 1811 | div.form div.fields div.field div.highlight |
|
1785 | 1812 | { |
|
1786 | 1813 | display: inline; |
|
1787 | 1814 | } |
|
1788 | 1815 | |
|
1789 | 1816 | div.form div.fields div.field div.highlight .ui-state-default |
|
1790 | 1817 | { |
|
1791 | 1818 | margin: 0; |
|
1792 | 1819 | padding: 6px 12px 6px 12px; |
|
1793 | 1820 | background: #4e85bb url("../images/colors/blue/button_highlight.png") repeat-x; |
|
1794 | 1821 | border-top: 1px solid #5c91a4; |
|
1795 | 1822 | border-left: 1px solid #2a6f89; |
|
1796 | 1823 | border-right: 1px solid #2b7089; |
|
1797 | 1824 | border-bottom: 1px solid #1a6480; |
|
1798 | 1825 | color: #FFFFFF; |
|
1799 | 1826 | } |
|
1800 | 1827 | |
|
1801 | 1828 | div.form div.fields div.field div.highlight .ui-state-hover |
|
1802 | 1829 | { |
|
1803 | 1830 | margin: 0; |
|
1804 | 1831 | padding: 6px 12px 6px 12px; |
|
1805 | 1832 | background: #46a0c1 url("../images/colors/blue/button_highlight_selected.png") repeat-x; |
|
1806 | 1833 | border-top: 1px solid #78acbf; |
|
1807 | 1834 | border-left: 1px solid #34819e; |
|
1808 | 1835 | border-right: 1px solid #35829f; |
|
1809 | 1836 | border-bottom: 1px solid #257897; |
|
1810 | 1837 | color: #FFFFFF; |
|
1811 | 1838 | } |
|
1812 | 1839 | |
|
1813 | 1840 | |
|
1814 | 1841 | /* ----------------------------------------------------------- |
|
1815 | 1842 | content -> right -> forms -> buttons |
|
1816 | 1843 | ----------------------------------------------------------- */ |
|
1817 | 1844 | |
|
1818 | 1845 | #content div.box div.form div.fields div.buttons |
|
1819 | 1846 | { |
|
1820 | 1847 | margin: 10px 0 0 200px; |
|
1821 | 1848 | padding: 0; |
|
1822 | 1849 | } |
|
1823 | 1850 | |
|
1824 | 1851 | #content div.box-left div.form div.fields div.buttons, |
|
1825 | 1852 | #content div.box-right div.form div.fields div.buttons |
|
1826 | 1853 | { |
|
1827 | 1854 | margin: 10px 0 0 0; |
|
1828 | 1855 | } |
|
1829 | 1856 | |
|
1830 | 1857 | #content div.box div.form div.fields div.buttons input |
|
1831 | 1858 | { |
|
1832 | 1859 | margin: 0; |
|
1833 | 1860 | color: #000000; |
|
1834 | 1861 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
1835 | 1862 | font-size: 11px; |
|
1836 | 1863 | font-weight: bold; |
|
1837 | 1864 | } |
|
1838 | 1865 | /* ----------------------------------------------------------- |
|
1839 | 1866 | content -> right -> forms -> buttons |
|
1840 | 1867 | ----------------------------------------------------------- */ |
|
1841 | 1868 | |
|
1842 | 1869 | div.form div.fields div.buttons |
|
1843 | 1870 | { |
|
1844 | 1871 | margin: 10px 0 0 200px; |
|
1845 | 1872 | padding: 0; |
|
1846 | 1873 | } |
|
1847 | 1874 | |
|
1848 | 1875 | div.box-left div.form div.fields div.buttons, |
|
1849 | 1876 | div.box-right div.form div.fields div.buttons |
|
1850 | 1877 | { |
|
1851 | 1878 | margin: 10px 0 0 0; |
|
1852 | 1879 | } |
|
1853 | 1880 | |
|
1854 | 1881 | div.form div.fields div.buttons input |
|
1855 | 1882 | { |
|
1856 | 1883 | margin: 0; |
|
1857 | 1884 | color: #000000; |
|
1858 | 1885 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
1859 | 1886 | font-size: 11px; |
|
1860 | 1887 | font-weight: bold; |
|
1861 | 1888 | } |
|
1862 | 1889 | |
|
1863 | 1890 | /* ----------------------------------------------------------- |
|
1864 | 1891 | content -> right -> forms -> buttons (jquery styling) |
|
1865 | 1892 | ----------------------------------------------------------- */ |
|
1866 | 1893 | |
|
1867 | 1894 | #content div.box div.form div.fields div.buttons input.ui-state-default |
|
1868 | 1895 | { |
|
1869 | 1896 | margin: 0; |
|
1870 | 1897 | padding: 6px 12px 6px 12px; |
|
1871 | 1898 | background: #e5e3e3 url("../images/button.png") repeat-x; |
|
1872 | 1899 | border-top: 1px solid #DDDDDD; |
|
1873 | 1900 | border-left: 1px solid #c6c6c6; |
|
1874 | 1901 | border-right: 1px solid #DDDDDD; |
|
1875 | 1902 | border-bottom: 1px solid #c6c6c6; |
|
1876 | 1903 | color: #515151; |
|
1877 | 1904 | outline: none; |
|
1878 | 1905 | } |
|
1879 | 1906 | |
|
1880 | 1907 | #content div.box div.form div.fields div.buttons input.ui-state-hover |
|
1881 | 1908 | { |
|
1882 | 1909 | margin: 0; |
|
1883 | 1910 | padding: 6px 12px 6px 12px; |
|
1884 | 1911 | background: #b4b4b4 url("../images/button_selected.png") repeat-x; |
|
1885 | 1912 | border-top: 1px solid #cccccc; |
|
1886 | 1913 | border-left: 1px solid #bebebe; |
|
1887 | 1914 | border-right: 1px solid #b1b1b1; |
|
1888 | 1915 | border-bottom: 1px solid #afafaf; |
|
1889 | 1916 | color: #515151; |
|
1890 | 1917 | outline: none; |
|
1891 | 1918 | } |
|
1892 | 1919 | |
|
1893 | 1920 | #content div.box div.form div.fields div.buttons div.highlight |
|
1894 | 1921 | { |
|
1895 | 1922 | display: inline; |
|
1896 | 1923 | } |
|
1897 | 1924 | |
|
1898 | 1925 | #content div.box div.form div.fields div.buttons div.highlight input.ui-state-default |
|
1899 | 1926 | { |
|
1900 | 1927 | margin: 0; |
|
1901 | 1928 | padding: 6px 12px 6px 12px; |
|
1902 | 1929 | background: #4e85bb url("../images/colors/blue/button_highlight.png") repeat-x; |
|
1903 | 1930 | border-top: 1px solid #5c91a4; |
|
1904 | 1931 | border-left: 1px solid #2a6f89; |
|
1905 | 1932 | border-right: 1px solid #2b7089; |
|
1906 | 1933 | border-bottom: 1px solid #1a6480; |
|
1907 | 1934 | color: #FFFFFF; |
|
1908 | 1935 | } |
|
1909 | 1936 | |
|
1910 | 1937 | #content div.box div.form div.fields div.buttons div.highlight input.ui-state-hover |
|
1911 | 1938 | { |
|
1912 | 1939 | margin: 0; |
|
1913 | 1940 | padding: 6px 12px 6px 12px; |
|
1914 | 1941 | background: #46a0c1 url("../images/colors/blue/button_highlight_selected.png") repeat-x; |
|
1915 | 1942 | border-top: 1px solid #78acbf; |
|
1916 | 1943 | border-left: 1px solid #34819e; |
|
1917 | 1944 | border-right: 1px solid #35829f; |
|
1918 | 1945 | border-bottom: 1px solid #257897; |
|
1919 | 1946 | color: #FFFFFF; |
|
1920 | 1947 | } |
|
1921 | 1948 | |
|
1922 | 1949 | /* ----------------------------------------------------------- |
|
1923 | 1950 | content -> right -> box / tables |
|
1924 | 1951 | ----------------------------------------------------------- */ |
|
1925 | 1952 | |
|
1926 | 1953 | #content div.box div.table |
|
1927 | 1954 | { |
|
1928 | 1955 | margin: 0; |
|
1929 | 1956 | padding: 0 20px 10px 20px; |
|
1930 | 1957 | clear: both; |
|
1931 | 1958 | overflow: hidden; |
|
1932 | 1959 | } |
|
1933 | 1960 | |
|
1934 | 1961 | #content div.box table |
|
1935 | 1962 | { |
|
1936 | 1963 | margin: 0; |
|
1937 | 1964 | padding: 0; |
|
1938 | 1965 | width: 100%; |
|
1939 | 1966 | border-collapse: collapse; |
|
1940 | 1967 | } |
|
1941 | 1968 | |
|
1942 | 1969 | #content div.box table th |
|
1943 | 1970 | { |
|
1944 | 1971 | padding: 10px; |
|
1945 | 1972 | background: #eeeeee; |
|
1946 | 1973 | border-bottom: 1px solid #dddddd; |
|
1947 | 1974 | } |
|
1948 | 1975 | |
|
1949 | 1976 | #content div.box table th.left |
|
1950 | 1977 | { |
|
1951 | 1978 | text-align: left; |
|
1952 | 1979 | } |
|
1953 | 1980 | |
|
1954 | 1981 | #content div.box table th.right |
|
1955 | 1982 | { |
|
1956 | 1983 | text-align: right; |
|
1957 | 1984 | } |
|
1958 | 1985 | |
|
1959 | 1986 | #content div.box table th.center |
|
1960 | 1987 | { |
|
1961 | 1988 | text-align: center; |
|
1962 | 1989 | } |
|
1963 | 1990 | |
|
1964 | 1991 | #content div.box table th.selected |
|
1965 | 1992 | { |
|
1966 | 1993 | padding: 0; |
|
1967 | 1994 | vertical-align: middle; |
|
1968 | 1995 | } |
|
1969 | 1996 | |
|
1970 | 1997 | #content div.box table th.selected input |
|
1971 | 1998 | { |
|
1972 | 1999 | margin: 0; |
|
1973 | 2000 | } |
|
1974 | 2001 | |
|
1975 | 2002 | #content div.box table td |
|
1976 | 2003 | { |
|
1977 | 2004 | padding: 5px; |
|
1978 | 2005 | background: #ffffff; |
|
1979 | 2006 | border-bottom: 1px solid #cdcdcd; |
|
1980 | 2007 | vertical-align:middle; |
|
1981 | 2008 | } |
|
1982 | 2009 | |
|
1983 | 2010 | #content div.box table tr.selected td |
|
1984 | 2011 | { |
|
1985 | 2012 | background: #FFFFCC; |
|
1986 | 2013 | } |
|
1987 | 2014 | |
|
1988 | 2015 | #content div.box table td.selected |
|
1989 | 2016 | { |
|
1990 | 2017 | padding: 0; |
|
1991 | 2018 | width: 3%; |
|
1992 | 2019 | text-align: center; |
|
1993 | 2020 | vertical-align: middle; |
|
1994 | 2021 | } |
|
1995 | 2022 | |
|
1996 | 2023 | #content div.box table td.selected input |
|
1997 | 2024 | { |
|
1998 | 2025 | margin: 0; |
|
1999 | 2026 | } |
|
2000 | 2027 | |
|
2001 | 2028 | #content div.box table td.action |
|
2002 | 2029 | { |
|
2003 | 2030 | width: 45%; |
|
2004 | 2031 | text-align: left; |
|
2005 | 2032 | } |
|
2006 | 2033 | |
|
2007 | 2034 | #content div.box table td.user |
|
2008 | 2035 | { |
|
2009 | 2036 | width: 10%; |
|
2010 | 2037 | text-align: center; |
|
2011 | 2038 | } |
|
2012 | 2039 | |
|
2013 | 2040 | #content div.box table td.date |
|
2014 | 2041 | { |
|
2015 | 2042 | width: 33%; |
|
2016 | 2043 | text-align: center; |
|
2017 | 2044 | } |
|
2018 | 2045 | |
|
2019 | 2046 | #content div.box table td.address |
|
2020 | 2047 | { |
|
2021 | 2048 | width: 10%; |
|
2022 | 2049 | text-align: center; |
|
2023 | 2050 | } |
|
2024 | 2051 | |
|
2025 | 2052 | /* ----------------------------------------------------------- |
|
2026 | 2053 | content -> right -> box / table action |
|
2027 | 2054 | ----------------------------------------------------------- */ |
|
2028 | 2055 | |
|
2029 | 2056 | #content div.box div.action |
|
2030 | 2057 | { |
|
2031 | 2058 | margin: 10px 0 0 0; |
|
2032 | 2059 | padding: 0; |
|
2033 | 2060 | float: right; |
|
2034 | 2061 | background: #FFFFFF; |
|
2035 | 2062 | text-align: right; |
|
2036 | 2063 | } |
|
2037 | 2064 | |
|
2038 | 2065 | #content div.box div.action a:hover |
|
2039 | 2066 | { |
|
2040 | 2067 | color: #000000; |
|
2041 | 2068 | text-decoration: none; |
|
2042 | 2069 | } |
|
2043 | 2070 | |
|
2044 | 2071 | #content div.box div.action select |
|
2045 | 2072 | { |
|
2046 | 2073 | margin: 0; |
|
2047 | 2074 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
2048 | 2075 | font-size: 11px; |
|
2049 | 2076 | } |
|
2050 | 2077 | |
|
2051 | 2078 | #content div.box div.action div.button |
|
2052 | 2079 | { |
|
2053 | 2080 | margin: 6px 0 0 0; |
|
2054 | 2081 | padding: 0; |
|
2055 | 2082 | text-align: right; |
|
2056 | 2083 | } |
|
2057 | 2084 | |
|
2058 | 2085 | #content div.box div.action div.button input |
|
2059 | 2086 | { |
|
2060 | 2087 | margin: 0; |
|
2061 | 2088 | color: #000000; |
|
2062 | 2089 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
2063 | 2090 | font-size: 11px; |
|
2064 | 2091 | font-weight: bold; |
|
2065 | 2092 | } |
|
2066 | 2093 | |
|
2067 | 2094 | #content div.box div.action div.button input.ui-state-default |
|
2068 | 2095 | { |
|
2069 | 2096 | margin: 0; |
|
2070 | 2097 | padding: 6px 12px 6px 12px; |
|
2071 | 2098 | background: #e5e3e3 url("../images/button.png") repeat-x; |
|
2072 | 2099 | border-top: 1px solid #DDDDDD; |
|
2073 | 2100 | border-left: 1px solid #c6c6c6; |
|
2074 | 2101 | border-right: 1px solid #DDDDDD; |
|
2075 | 2102 | border-bottom: 1px solid #c6c6c6; |
|
2076 | 2103 | color: #515151; |
|
2077 | 2104 | } |
|
2078 | 2105 | |
|
2079 | 2106 | #content div.box div.action div.button input.ui-state-hover |
|
2080 | 2107 | { |
|
2081 | 2108 | margin: 0; |
|
2082 | 2109 | padding: 6px 12px 6px 12px; |
|
2083 | 2110 | background: #b4b4b4 url("../images/button_selected.png") repeat-x; |
|
2084 | 2111 | border-top: 1px solid #cccccc; |
|
2085 | 2112 | border-left: 1px solid #bebebe; |
|
2086 | 2113 | border-right: 1px solid #b1b1b1; |
|
2087 | 2114 | border-bottom: 1px solid #afafaf; |
|
2088 | 2115 | color: #515151; |
|
2089 | 2116 | } |
|
2090 | 2117 | |
|
2091 | 2118 | #content div.box div.action .ui-selectmenu |
|
2092 | 2119 | { |
|
2093 | 2120 | margin: 0; |
|
2094 | 2121 | padding: 0; |
|
2095 | 2122 | } |
|
2096 | 2123 | |
|
2097 | 2124 | #content div.box div.action a.ui-selectmenu-focus |
|
2098 | 2125 | { |
|
2099 | 2126 | border: 1px solid #666666; |
|
2100 | 2127 | } |
|
2101 | 2128 | |
|
2102 | 2129 | #content div.box div.action a.ui-selectmenu-focus span.ui-icon |
|
2103 | 2130 | { |
|
2104 | 2131 | background-image: url(../images/ui/ui-icons_222222_256x240.png); |
|
2105 | 2132 | } |
|
2106 | 2133 | |
|
2107 | 2134 | /* ----------------------------------------------------------- |
|
2108 | 2135 | content -> right -> pagination |
|
2109 | 2136 | ----------------------------------------------------------- */ |
|
2110 | 2137 | |
|
2111 | 2138 | #content div.box div.pagination |
|
2112 | 2139 | { |
|
2113 | 2140 | margin: 10px 0 0 0; |
|
2114 | 2141 | padding: 0; |
|
2115 | 2142 | height: 1%; |
|
2116 | 2143 | clear: both; |
|
2117 | 2144 | overflow: hidden; |
|
2118 | 2145 | } |
|
2119 | 2146 | |
|
2120 | 2147 | #content div.box div.pagination div.results |
|
2121 | 2148 | { |
|
2122 | 2149 | margin: 0; |
|
2123 | 2150 | padding: 0; |
|
2124 | 2151 | text-align: left; |
|
2125 | 2152 | float: left |
|
2126 | 2153 | } |
|
2127 | 2154 | |
|
2128 | 2155 | #content div.box div.pagination div.results span |
|
2129 | 2156 | { |
|
2130 | 2157 | margin: 0; |
|
2131 | 2158 | padding: 6px 8px 6px 8px; |
|
2132 | 2159 | height: 1%; |
|
2133 | 2160 | display: block; |
|
2134 | 2161 | float: left; |
|
2135 | 2162 | background: #ebebeb url("../images/pager.png") repeat-x; |
|
2136 | 2163 | border-top: 1px solid #dedede; |
|
2137 | 2164 | border-left: 1px solid #cfcfcf; |
|
2138 | 2165 | border-right: 1px solid #c4c4c4; |
|
2139 | 2166 | border-bottom: 1px solid #c4c4c4; |
|
2140 | 2167 | color: #4A4A4A; |
|
2141 | 2168 | font-weight: bold; |
|
2142 | 2169 | } |
|
2143 | 2170 | |
|
2144 | 2171 | #content div.box div.pagination ul.pager |
|
2145 | 2172 | { |
|
2146 | 2173 | margin: 0; |
|
2147 | 2174 | padding: 0; |
|
2148 | 2175 | float: right; |
|
2149 | 2176 | text-align: right; |
|
2150 | 2177 | } |
|
2151 | 2178 | |
|
2152 | 2179 | #content div.box div.pagination ul.pager li |
|
2153 | 2180 | { |
|
2154 | 2181 | margin: 0 0 0 4px; |
|
2155 | 2182 | padding: 0; |
|
2156 | 2183 | height: 1%; |
|
2157 | 2184 | float: left; |
|
2158 | 2185 | list-style: none; |
|
2159 | 2186 | background: #ebebeb url("../images/pager.png") repeat-x; |
|
2160 | 2187 | border-top: 1px solid #dedede; |
|
2161 | 2188 | border-left: 1px solid #cfcfcf; |
|
2162 | 2189 | border-right: 1px solid #c4c4c4; |
|
2163 | 2190 | border-bottom: 1px solid #c4c4c4; |
|
2164 | 2191 | color: #4A4A4A; |
|
2165 | 2192 | font-weight: bold; |
|
2166 | 2193 | } |
|
2167 | 2194 | |
|
2168 | 2195 | #content div.box div.pagination ul.pager li.separator |
|
2169 | 2196 | { |
|
2170 | 2197 | padding: 6px; |
|
2171 | 2198 | } |
|
2172 | 2199 | |
|
2173 | 2200 | #content div.box div.pagination ul.pager li.current |
|
2174 | 2201 | { |
|
2175 | 2202 | padding: 6px; |
|
2176 | 2203 | background: #b4b4b4 url("../images/pager_selected.png") repeat-x; |
|
2177 | 2204 | border-top: 1px solid #cccccc; |
|
2178 | 2205 | border-left: 1px solid #bebebe; |
|
2179 | 2206 | border-right: 1px solid #b1b1b1; |
|
2180 | 2207 | border-bottom: 1px solid #afafaf; |
|
2181 | 2208 | color: #515151; |
|
2182 | 2209 | } |
|
2183 | 2210 | |
|
2184 | 2211 | #content div.box div.pagination ul.pager li.disabled |
|
2185 | 2212 | { |
|
2186 | 2213 | padding: 6px; |
|
2187 | 2214 | color: #B4B4B4; |
|
2188 | 2215 | } |
|
2189 | 2216 | |
|
2190 | 2217 | #content div.box div.pagination ul.pager li a |
|
2191 | 2218 | { |
|
2192 | 2219 | margin: 0; |
|
2193 | 2220 | padding: 6px; |
|
2194 | 2221 | height: 1%; |
|
2195 | 2222 | display: block; |
|
2196 | 2223 | float: left; |
|
2197 | 2224 | color: #515151; |
|
2198 | 2225 | text-decoration: none; |
|
2199 | 2226 | } |
|
2200 | 2227 | |
|
2201 | 2228 | #content div.box div.pagination ul.pager li a:hover, |
|
2202 | 2229 | #content div.box div.pagination ul.pager li a:active |
|
2203 | 2230 | { |
|
2204 | 2231 | margin: -1px; |
|
2205 | 2232 | background: #b4b4b4 url("../images/pager_selected.png") repeat-x; |
|
2206 | 2233 | border-top: 1px solid #cccccc; |
|
2207 | 2234 | border-left: 1px solid #bebebe; |
|
2208 | 2235 | border-right: 1px solid #b1b1b1; |
|
2209 | 2236 | border-bottom: 1px solid #afafaf; |
|
2210 | 2237 | } |
|
2211 | 2238 | |
|
2212 | 2239 | /* ----------------------------------------------------------- |
|
2213 | 2240 | content -> webhelpers pagination |
|
2214 | 2241 | ----------------------------------------------------------- */ |
|
2215 | 2242 | |
|
2216 | 2243 | #content div.box div.pagination-wh |
|
2217 | 2244 | { |
|
2218 | 2245 | margin: 10px 0 0 0; |
|
2219 | 2246 | padding: 0; |
|
2220 | 2247 | height: 1%; |
|
2221 | 2248 | clear: both; |
|
2222 | 2249 | overflow: hidden; |
|
2223 | 2250 | text-align: right; |
|
2224 | 2251 | } |
|
2225 | 2252 | |
|
2226 | 2253 | #content div.box div.pagination-wh div.results |
|
2227 | 2254 | { |
|
2228 | 2255 | margin: 0; |
|
2229 | 2256 | padding: 0; |
|
2230 | 2257 | text-align: left; |
|
2231 | 2258 | float: left |
|
2232 | 2259 | } |
|
2233 | 2260 | |
|
2234 | 2261 | #content div.box div.pagination-wh div.results span |
|
2235 | 2262 | { |
|
2236 | 2263 | margin: 0; |
|
2237 | 2264 | padding: 6px 8px 6px 8px; |
|
2238 | 2265 | height: 1%; |
|
2239 | 2266 | display: block; |
|
2240 | 2267 | float: left; |
|
2241 | 2268 | background: #ebebeb url("../images/pager.png") repeat-x; |
|
2242 | 2269 | border-top: 1px solid #dedede; |
|
2243 | 2270 | border-left: 1px solid #cfcfcf; |
|
2244 | 2271 | border-right: 1px solid #c4c4c4; |
|
2245 | 2272 | border-bottom: 1px solid #c4c4c4; |
|
2246 | 2273 | color: #4A4A4A; |
|
2247 | 2274 | font-weight: bold; |
|
2248 | 2275 | } |
|
2249 | 2276 | |
|
2250 | 2277 | #content div.box div.pagination-left{ |
|
2251 | 2278 | float:left; |
|
2252 | 2279 | } |
|
2253 | 2280 | #content div.box div.pagination-right{ |
|
2254 | 2281 | float:right; |
|
2255 | 2282 | } |
|
2256 | 2283 | |
|
2257 | 2284 | #content div.box div.pagination-wh a, |
|
2258 | 2285 | #content div.box div.pagination-wh span.pager_dotdot |
|
2259 | 2286 | { |
|
2260 | 2287 | margin: 0 0 0 4px; |
|
2261 | 2288 | padding: 6px; |
|
2262 | 2289 | height: 1%; |
|
2263 | 2290 | float: left; |
|
2264 | 2291 | background: #ebebeb url("../images/pager.png") repeat-x; |
|
2265 | 2292 | border-top: 1px solid #dedede; |
|
2266 | 2293 | border-left: 1px solid #cfcfcf; |
|
2267 | 2294 | border-right: 1px solid #c4c4c4; |
|
2268 | 2295 | border-bottom: 1px solid #c4c4c4; |
|
2269 | 2296 | color: #4A4A4A; |
|
2270 | 2297 | font-weight: bold; |
|
2271 | 2298 | } |
|
2272 | 2299 | #content div.box div.pagination-wh span.pager_curpage |
|
2273 | 2300 | { |
|
2274 | 2301 | margin: 0 0 0 4px; |
|
2275 | 2302 | padding: 6px; |
|
2276 | 2303 | height: 1%; |
|
2277 | 2304 | float: left; |
|
2278 | 2305 | background: #b4b4b4 url("../images/pager_selected.png") repeat-x; |
|
2279 | 2306 | border-top: 1px solid #cccccc; |
|
2280 | 2307 | border-left: 1px solid #bebebe; |
|
2281 | 2308 | border-right: 1px solid #b1b1b1; |
|
2282 | 2309 | border-bottom: 1px solid #afafaf; |
|
2283 | 2310 | color: #515151; |
|
2284 | 2311 | font-weight: bold; |
|
2285 | 2312 | } |
|
2286 | 2313 | |
|
2287 | 2314 | #content div.box div.pagination-wh a.disabled |
|
2288 | 2315 | { |
|
2289 | 2316 | padding: 6px; |
|
2290 | 2317 | color: #B4B4B4; |
|
2291 | 2318 | } |
|
2292 | 2319 | |
|
2293 | 2320 | |
|
2294 | 2321 | #content div.box div.pagination-wh a:hover, |
|
2295 | 2322 | #content div.box div.pagination-wh a:active |
|
2296 | 2323 | { |
|
2297 | 2324 | background: #b4b4b4 url("../images/pager_selected.png") repeat-x; |
|
2298 | 2325 | border-top: 1px solid #cccccc; |
|
2299 | 2326 | border-left: 1px solid #bebebe; |
|
2300 | 2327 | border-right: 1px solid #b1b1b1; |
|
2301 | 2328 | border-bottom: 1px solid #afafaf; |
|
2302 | 2329 | text-decoration: none; |
|
2303 | 2330 | } |
|
2304 | 2331 | |
|
2305 | 2332 | |
|
2306 | 2333 | /* ----------------------------------------------------------- |
|
2307 | 2334 | content -> right -> traffic chart |
|
2308 | 2335 | ----------------------------------------------------------- */ |
|
2309 | 2336 | |
|
2310 | 2337 | #content div.box div.traffic |
|
2311 | 2338 | { |
|
2312 | 2339 | margin: 0; |
|
2313 | 2340 | padding: 0 20px 10px 20px; |
|
2314 | 2341 | clear: both; |
|
2315 | 2342 | overflow: hidden; |
|
2316 | 2343 | } |
|
2317 | 2344 | |
|
2318 | 2345 | #content div.box div.traffic div.legend |
|
2319 | 2346 | { |
|
2320 | 2347 | margin: 0 0 10px 0; |
|
2321 | 2348 | padding: 0 0 10px 0; |
|
2322 | 2349 | clear: both; |
|
2323 | 2350 | overflow: hidden; |
|
2324 | 2351 | border-bottom: 1px solid #dddddd; |
|
2325 | 2352 | } |
|
2326 | 2353 | |
|
2327 | 2354 | #content div.box div.traffic div.legend h6 |
|
2328 | 2355 | { |
|
2329 | 2356 | margin: 0; |
|
2330 | 2357 | padding: 0; |
|
2331 | 2358 | float: left; |
|
2332 | 2359 | border: none; |
|
2333 | 2360 | } |
|
2334 | 2361 | |
|
2335 | 2362 | #content div.box div.traffic div.legend ul |
|
2336 | 2363 | { |
|
2337 | 2364 | margin: 0; |
|
2338 | 2365 | padding: 0; |
|
2339 | 2366 | float: right; |
|
2340 | 2367 | } |
|
2341 | 2368 | |
|
2342 | 2369 | #content div.box div.traffic div.legend li |
|
2343 | 2370 | { |
|
2344 | 2371 | margin: 0; |
|
2345 | 2372 | padding: 0 8px 0 4px; |
|
2346 | 2373 | list-style: none; |
|
2347 | 2374 | float: left; |
|
2348 | 2375 | font-size: 11px; |
|
2349 | 2376 | } |
|
2350 | 2377 | |
|
2351 | 2378 | #content div.box div.traffic div.legend li.visits |
|
2352 | 2379 | { |
|
2353 | 2380 | border-left: 12px solid #edc240; |
|
2354 | 2381 | } |
|
2355 | 2382 | |
|
2356 | 2383 | #content div.box div.traffic div.legend li.pageviews |
|
2357 | 2384 | { |
|
2358 | 2385 | border-left: 12px solid #afd8f8; |
|
2359 | 2386 | } |
|
2360 | 2387 | |
|
2361 | 2388 | #content div.box div.traffic table |
|
2362 | 2389 | { |
|
2363 | 2390 | width: auto; |
|
2364 | 2391 | } |
|
2365 | 2392 | |
|
2366 | 2393 | #content div.box div.traffic table td |
|
2367 | 2394 | { |
|
2368 | 2395 | padding: 2px 3px 3px 3px; |
|
2369 | 2396 | background: transparent; |
|
2370 | 2397 | border: none; |
|
2371 | 2398 | } |
|
2372 | 2399 | |
|
2373 | 2400 | #content div.box div.traffic table td.legendLabel |
|
2374 | 2401 | { |
|
2375 | 2402 | padding: 0 3px 2px 3px; |
|
2376 | 2403 | } |
|
2377 | 2404 | |
|
2378 | 2405 | /* ----------------------------------------------------------- |
|
2379 | 2406 | footer |
|
2380 | 2407 | ----------------------------------------------------------- */ |
|
2381 | 2408 | |
|
2382 | 2409 | #footer |
|
2383 | 2410 | { |
|
2384 | 2411 | margin: 0; |
|
2385 | 2412 | padding: 5px 0 5px 0; |
|
2386 | 2413 | clear: both; |
|
2387 | 2414 | overflow: hidden; |
|
2388 | 2415 | background: #2a2a2a; |
|
2389 | 2416 | text-align: right; |
|
2390 | 2417 | } |
|
2391 | 2418 | |
|
2392 | 2419 | #footer p |
|
2393 | 2420 | { |
|
2394 | 2421 | margin: 0 80px 0 80px; |
|
2395 | 2422 | padding: 10px 0 10px 0; |
|
2396 | 2423 | color: #ffffff; |
|
2397 | 2424 | } |
|
2398 | 2425 | |
|
2399 | 2426 | /* ----------------------------------------------------------- |
|
2400 | 2427 | login |
|
2401 | 2428 | ----------------------------------------------------------- */ |
|
2402 | 2429 | |
|
2403 | 2430 | #login |
|
2404 | 2431 | { |
|
2405 | 2432 | margin: 10% auto 0 auto; |
|
2406 | 2433 | padding: 0; |
|
2407 | 2434 | width: 420px; |
|
2408 | 2435 | } |
|
2409 | 2436 | |
|
2410 | 2437 | /* ----------------------------------------------------------- |
|
2411 | 2438 | login -> colors |
|
2412 | 2439 | ----------------------------------------------------------- */ |
|
2413 | 2440 | |
|
2414 | 2441 | #login div.color |
|
2415 | 2442 | { |
|
2416 | 2443 | margin: 10px auto 0 auto; |
|
2417 | 2444 | padding: 3px 3px 3px 0; |
|
2418 | 2445 | clear: both; |
|
2419 | 2446 | overflow: hidden; |
|
2420 | 2447 | background: #FFFFFF; |
|
2421 | 2448 | } |
|
2422 | 2449 | |
|
2423 | 2450 | #login div.color a |
|
2424 | 2451 | { |
|
2425 | 2452 | margin: 0 0 0 3px; |
|
2426 | 2453 | padding: 0; |
|
2427 | 2454 | width: 20px; |
|
2428 | 2455 | height: 20px; |
|
2429 | 2456 | display: block; |
|
2430 | 2457 | float: left; |
|
2431 | 2458 | } |
|
2432 | 2459 | |
|
2433 | 2460 | /* ----------------------------------------------------------- |
|
2434 | 2461 | login -> title |
|
2435 | 2462 | ----------------------------------------------------------- */ |
|
2436 | 2463 | |
|
2437 | 2464 | #login div.title |
|
2438 | 2465 | { |
|
2439 | 2466 | margin: 0 auto; |
|
2440 | 2467 | padding: 0; |
|
2441 | 2468 | width: 420px; |
|
2442 | 2469 | clear: both; |
|
2443 | 2470 | overflow: hidden; |
|
2444 | 2471 | position: relative; |
|
2445 | 2472 | background: #003367 url("../images/colors/blue/header_inner.png") repeat-x; |
|
2446 | 2473 | } |
|
2447 | 2474 | |
|
2448 | 2475 | #login div.title h5 |
|
2449 | 2476 | { |
|
2450 | 2477 | margin: 10px; |
|
2451 | 2478 | padding: 0; |
|
2452 | 2479 | color: #ffffff; |
|
2453 | 2480 | } |
|
2454 | 2481 | |
|
2455 | 2482 | /* ----------------------------------------------------------- |
|
2456 | 2483 | login -> title / corners |
|
2457 | 2484 | ----------------------------------------------------------- */ |
|
2458 | 2485 | |
|
2459 | 2486 | #login div.title div.corner |
|
2460 | 2487 | { |
|
2461 | 2488 | height: 6px; |
|
2462 | 2489 | width: 6px; |
|
2463 | 2490 | position: absolute; |
|
2464 | 2491 | background: url("../images/colors/blue/login_corners.png") no-repeat; |
|
2465 | 2492 | } |
|
2466 | 2493 | |
|
2467 | 2494 | #login div.title div.tl |
|
2468 | 2495 | { |
|
2469 | 2496 | top: 0; |
|
2470 | 2497 | left: 0; |
|
2471 | 2498 | background-position: 0 0; |
|
2472 | 2499 | } |
|
2473 | 2500 | |
|
2474 | 2501 | #login div.title div.tr |
|
2475 | 2502 | { |
|
2476 | 2503 | top: 0; |
|
2477 | 2504 | right: 0; |
|
2478 | 2505 | background-position: -6px 0; |
|
2479 | 2506 | } |
|
2480 | 2507 | |
|
2481 | 2508 | #login div.inner |
|
2482 | 2509 | { |
|
2483 | 2510 | margin: 0 auto; |
|
2484 | 2511 | padding: 20px; |
|
2485 | 2512 | width: 380px; |
|
2486 | 2513 | background: #FFFFFF url("../images/login.png") no-repeat top left; |
|
2487 | 2514 | border-top: none; |
|
2488 | 2515 | border-bottom: none; |
|
2489 | 2516 | } |
|
2490 | 2517 | |
|
2491 | 2518 | /* ----------------------------------------------------------- |
|
2492 | 2519 | login -> form |
|
2493 | 2520 | ----------------------------------------------------------- */ |
|
2494 | 2521 | |
|
2495 | 2522 | #login div.form |
|
2496 | 2523 | { |
|
2497 | 2524 | margin: 0; |
|
2498 | 2525 | padding: 0; |
|
2499 | 2526 | clear: both; |
|
2500 | 2527 | overflow: hidden; |
|
2501 | 2528 | } |
|
2502 | 2529 | |
|
2503 | 2530 | #login div.form div.fields |
|
2504 | 2531 | { |
|
2505 | 2532 | margin: 0; |
|
2506 | 2533 | padding: 0; |
|
2507 | 2534 | clear: both; |
|
2508 | 2535 | overflow: hidden; |
|
2509 | 2536 | } |
|
2510 | 2537 | |
|
2511 | 2538 | #login div.form div.fields div.field |
|
2512 | 2539 | { |
|
2513 | 2540 | margin: 0; |
|
2514 | 2541 | padding: 0 0 10px 0; |
|
2515 | 2542 | clear: both; |
|
2516 | 2543 | overflow: hidden; |
|
2517 | 2544 | } |
|
2518 | 2545 | |
|
2519 | 2546 | #login div.form div.fields div.field span.error-message |
|
2520 | 2547 | { |
|
2521 | 2548 | margin: 8px 0 0 0; |
|
2522 | 2549 | padding: 0; |
|
2523 | 2550 | height: 1%; |
|
2524 | 2551 | display: block; |
|
2525 | 2552 | color: #FF0000; |
|
2526 | 2553 | } |
|
2527 | 2554 | |
|
2528 | 2555 | #login div.form div.fields div.field div.label |
|
2529 | 2556 | { |
|
2530 | 2557 | margin: 2px 10px 0 0; |
|
2531 | 2558 | padding: 5px 0 0 5px; |
|
2532 | 2559 | width: 173px; |
|
2533 | 2560 | float: left; |
|
2534 | 2561 | text-align: right; |
|
2535 | 2562 | } |
|
2536 | 2563 | |
|
2537 | 2564 | #login div.form div.fields div.field div.label label |
|
2538 | 2565 | { |
|
2539 | 2566 | color: #000000; |
|
2540 | 2567 | font-weight: bold; |
|
2541 | 2568 | } |
|
2542 | 2569 | |
|
2543 | 2570 | #login div.form div.fields div.field div.label span |
|
2544 | 2571 | { |
|
2545 | 2572 | margin: 0; |
|
2546 | 2573 | padding: 2px 0 0 0; |
|
2547 | 2574 | height: 1%; |
|
2548 | 2575 | display: block; |
|
2549 | 2576 | color: #363636; |
|
2550 | 2577 | } |
|
2551 | 2578 | |
|
2552 | 2579 | #login div.form div.fields div.field div.input |
|
2553 | 2580 | { |
|
2554 | 2581 | margin: 0; |
|
2555 | 2582 | padding: 0; |
|
2556 | 2583 | float: left; |
|
2557 | 2584 | } |
|
2558 | 2585 | |
|
2559 | 2586 | #login div.form div.fields div.field div.input input |
|
2560 | 2587 | { |
|
2561 | 2588 | margin: 0; |
|
2562 | 2589 | padding: 7px 7px 6px 7px; |
|
2563 | 2590 | width: 176px; |
|
2564 | 2591 | background: #FFFFFF; |
|
2565 | 2592 | border-top: 1px solid #b3b3b3; |
|
2566 | 2593 | border-left: 1px solid #b3b3b3; |
|
2567 | 2594 | border-right: 1px solid #eaeaea; |
|
2568 | 2595 | border-bottom: 1px solid #eaeaea; |
|
2569 | 2596 | color: #000000; |
|
2570 | 2597 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
2571 | 2598 | font-size: 11px; |
|
2572 | 2599 | } |
|
2573 | 2600 | |
|
2574 | 2601 | #login div.form div.fields div.field div.input input.error |
|
2575 | 2602 | { |
|
2576 | 2603 | background: #FBE3E4; |
|
2577 | 2604 | border-top: 1px solid #e1b2b3; |
|
2578 | 2605 | border-left: 1px solid #e1b2b3; |
|
2579 | 2606 | border-right: 1px solid #FBC2C4; |
|
2580 | 2607 | border-bottom: 1px solid #FBC2C4; |
|
2581 | 2608 | } |
|
2582 | 2609 | |
|
2583 | 2610 | #login div.form div.fields div.field div.input input.success |
|
2584 | 2611 | { |
|
2585 | 2612 | background: #E6EFC2; |
|
2586 | 2613 | border-top: 1px solid #cebb98; |
|
2587 | 2614 | border-left: 1px solid #cebb98; |
|
2588 | 2615 | border-right: 1px solid #c6d880; |
|
2589 | 2616 | border-bottom: 1px solid #c6d880; |
|
2590 | 2617 | } |
|
2591 | 2618 | |
|
2592 | 2619 | #login div.form div.fields div.field div.input div.link |
|
2593 | 2620 | { |
|
2594 | 2621 | margin: 6px 0 0 0; |
|
2595 | 2622 | padding: 0; |
|
2596 | 2623 | text-align: right; |
|
2597 | 2624 | } |
|
2598 | 2625 | |
|
2599 | 2626 | #login div.form div.fields div.field div.checkbox |
|
2600 | 2627 | { |
|
2601 | 2628 | margin: 0 0 0 184px; |
|
2602 | 2629 | padding: 0; |
|
2603 | 2630 | } |
|
2604 | 2631 | |
|
2605 | 2632 | #login div.form div.fields div.field div.checkbox label |
|
2606 | 2633 | { |
|
2607 | 2634 | color: #565656; |
|
2608 | 2635 | font-weight: bold; |
|
2609 | 2636 | } |
|
2610 | 2637 | |
|
2611 | 2638 | #login div.form div.fields div.buttons |
|
2612 | 2639 | { |
|
2613 | 2640 | margin: 0; |
|
2614 | 2641 | padding: 10px 0 0 0; |
|
2615 | 2642 | clear: both; |
|
2616 | 2643 | overflow: hidden; |
|
2617 | 2644 | border-top: 1px solid #DDDDDD; |
|
2618 | 2645 | text-align: right; |
|
2619 | 2646 | } |
|
2620 | 2647 | |
|
2621 | 2648 | #login div.form div.fields div.buttons input |
|
2622 | 2649 | { |
|
2623 | 2650 | margin: 0; |
|
2624 | 2651 | color: #000000; |
|
2625 | 2652 | font-size: 1.0em; |
|
2626 | 2653 | font-weight: bold; |
|
2627 | 2654 | font-family: Verdana, Helvetica, Sans-Serif; |
|
2628 | 2655 | } |
|
2629 | 2656 | |
|
2630 | 2657 | #login div.form div.fields div.buttons input.ui-state-default |
|
2631 | 2658 | { |
|
2632 | 2659 | margin: 0; |
|
2633 | 2660 | padding: 6px 12px 6px 12px; |
|
2634 | 2661 | background: #e5e3e3 url("../images/button.png") repeat-x; |
|
2635 | 2662 | border-top: 1px solid #DDDDDD; |
|
2636 | 2663 | border-left: 1px solid #c6c6c6; |
|
2637 | 2664 | border-right: 1px solid #DDDDDD; |
|
2638 | 2665 | border-bottom: 1px solid #c6c6c6; |
|
2639 | 2666 | color: #515151; |
|
2640 | 2667 | } |
|
2641 | 2668 | |
|
2642 | 2669 | #login div.form div.fields div.buttons input.ui-state-hover |
|
2643 | 2670 | { |
|
2644 | 2671 | margin: 0; |
|
2645 | 2672 | padding: 6px 12px 6px 12px; |
|
2646 | 2673 | background: #b4b4b4 url("../images/button_selected.png") repeat-x; |
|
2647 | 2674 | border-top: 1px solid #cccccc; |
|
2648 | 2675 | border-left: 1px solid #bebebe; |
|
2649 | 2676 | border-right: 1px solid #b1b1b1; |
|
2650 | 2677 | border-bottom: 1px solid #afafaf; |
|
2651 | 2678 | color: #515151; |
|
2652 | 2679 | } |
|
2653 | 2680 | |
|
2654 | 2681 | /* ----------------------------------------------------------- |
|
2655 | 2682 | login -> links |
|
2656 | 2683 | ----------------------------------------------------------- */ |
|
2657 | 2684 | |
|
2658 | 2685 | #login div.form div.links |
|
2659 | 2686 | { |
|
2660 | 2687 | margin: 10px 0 0 0; |
|
2661 | 2688 | padding: 0 0 2px 0; |
|
2662 | 2689 | clear: both; |
|
2663 | 2690 | overflow: hidden; |
|
2664 | 2691 | } |
|
2665 | 2692 | |
|
2666 | 2693 | /* ----------------------------------------------------------- |
|
2667 | 2694 | register |
|
2668 | 2695 | ----------------------------------------------------------- */ |
|
2669 | 2696 | |
|
2670 | 2697 | #register |
|
2671 | 2698 | { |
|
2672 | 2699 | margin: 10% auto 0 auto; |
|
2673 | 2700 | padding: 0; |
|
2674 | 2701 | width: 420px; |
|
2675 | 2702 | } |
|
2676 | 2703 | |
|
2677 | 2704 | /* ----------------------------------------------------------- |
|
2678 | 2705 | register -> colors |
|
2679 | 2706 | ----------------------------------------------------------- */ |
|
2680 | 2707 | |
|
2681 | 2708 | #register div.color |
|
2682 | 2709 | { |
|
2683 | 2710 | margin: 10px auto 0 auto; |
|
2684 | 2711 | padding: 3px 3px 3px 0; |
|
2685 | 2712 | clear: both; |
|
2686 | 2713 | overflow: hidden; |
|
2687 | 2714 | background: #FFFFFF; |
|
2688 | 2715 | } |
|
2689 | 2716 | |
|
2690 | 2717 | #register div.color a |
|
2691 | 2718 | { |
|
2692 | 2719 | margin: 0 0 0 3px; |
|
2693 | 2720 | padding: 0; |
|
2694 | 2721 | width: 20px; |
|
2695 | 2722 | height: 20px; |
|
2696 | 2723 | display: block; |
|
2697 | 2724 | float: left; |
|
2698 | 2725 | } |
|
2699 | 2726 | |
|
2700 | 2727 | /* ----------------------------------------------------------- |
|
2701 | 2728 | register -> title |
|
2702 | 2729 | ----------------------------------------------------------- */ |
|
2703 | 2730 | |
|
2704 | 2731 | #register div.title |
|
2705 | 2732 | { |
|
2706 | 2733 | margin: 0 auto; |
|
2707 | 2734 | padding: 0; |
|
2708 | 2735 | width: 420px; |
|
2709 | 2736 | clear: both; |
|
2710 | 2737 | overflow: hidden; |
|
2711 | 2738 | position: relative; |
|
2712 | 2739 | background: #003367 url("../images/colors/blue/header_inner.png") repeat-x; |
|
2713 | 2740 | } |
|
2714 | 2741 | |
|
2715 | 2742 | #register div.title h5 |
|
2716 | 2743 | { |
|
2717 | 2744 | margin: 10px; |
|
2718 | 2745 | padding: 0; |
|
2719 | 2746 | color: #ffffff; |
|
2720 | 2747 | } |
|
2721 | 2748 | |
|
2722 | 2749 | /* ----------------------------------------------------------- |
|
2723 | 2750 | register -> inner |
|
2724 | 2751 | ----------------------------------------------------------- */ |
|
2725 | 2752 | #register div.title div.corner |
|
2726 | 2753 | { |
|
2727 | 2754 | height: 6px; |
|
2728 | 2755 | width: 6px; |
|
2729 | 2756 | position: absolute; |
|
2730 | 2757 | background: url("../images/colors/blue/login_corners.png") no-repeat; |
|
2731 | 2758 | } |
|
2732 | 2759 | |
|
2733 | 2760 | #register div.title div.tl |
|
2734 | 2761 | { |
|
2735 | 2762 | top: 0; |
|
2736 | 2763 | left: 0; |
|
2737 | 2764 | background-position: 0 0; |
|
2738 | 2765 | } |
|
2739 | 2766 | |
|
2740 | 2767 | #register div.title div.tr |
|
2741 | 2768 | { |
|
2742 | 2769 | top: 0; |
|
2743 | 2770 | right: 0; |
|
2744 | 2771 | background-position: -6px 0; |
|
2745 | 2772 | |
|
2746 | 2773 | } |
|
2747 | 2774 | #register div.inner |
|
2748 | 2775 | { |
|
2749 | 2776 | margin: 0 auto; |
|
2750 | 2777 | padding: 20px; |
|
2751 | 2778 | width: 380px; |
|
2752 | 2779 | background: #FFFFFF; |
|
2753 | 2780 | border-top: none; |
|
2754 | 2781 | border-bottom: none; |
|
2755 | 2782 | } |
|
2756 | 2783 | |
|
2757 | 2784 | /* ----------------------------------------------------------- |
|
2758 | 2785 | register -> form |
|
2759 | 2786 | ----------------------------------------------------------- */ |
|
2760 | 2787 | |
|
2761 | 2788 | #register div.form |
|
2762 | 2789 | { |
|
2763 | 2790 | margin: 0; |
|
2764 | 2791 | padding: 0; |
|
2765 | 2792 | clear: both; |
|
2766 | 2793 | overflow: hidden; |
|
2767 | 2794 | } |
|
2768 | 2795 | |
|
2769 | 2796 | #register div.form div.fields |
|
2770 | 2797 | { |
|
2771 | 2798 | margin: 0; |
|
2772 | 2799 | padding: 0; |
|
2773 | 2800 | clear: both; |
|
2774 | 2801 | overflow: hidden; |
|
2775 | 2802 | } |
|
2776 | 2803 | |
|
2777 | 2804 | #register div.form div.fields div.field |
|
2778 | 2805 | { |
|
2779 | 2806 | margin: 0; |
|
2780 | 2807 | padding: 0 0 10px 0; |
|
2781 | 2808 | clear: both; |
|
2782 | 2809 | overflow: hidden; |
|
2783 | 2810 | } |
|
2784 | 2811 | |
|
2785 | 2812 | #register div.form div.fields div.field span.error-message |
|
2786 | 2813 | { |
|
2787 | 2814 | margin: 8px 0 0 0; |
|
2788 | 2815 | padding: 0; |
|
2789 | 2816 | height: 1%; |
|
2790 | 2817 | display: block; |
|
2791 | 2818 | color: #FF0000; |
|
2792 | 2819 | } |
|
2793 | 2820 | |
|
2794 | 2821 | #register div.form div.fields div.field div.label |
|
2795 | 2822 | { |
|
2796 | 2823 | margin: 2px 10px 0 0; |
|
2797 | 2824 | padding: 5px 0 0 5px; |
|
2798 | 2825 | width: 100px; |
|
2799 | 2826 | float: left; |
|
2800 | 2827 | text-align: right; |
|
2801 | 2828 | } |
|
2802 | 2829 | |
|
2803 | 2830 | #register div.form div.fields div.field div.label label |
|
2804 | 2831 | { |
|
2805 | 2832 | color: #000000; |
|
2806 | 2833 | font-weight: bold; |
|
2807 | 2834 | } |
|
2808 | 2835 | |
|
2809 | 2836 | #register div.form div.fields div.field div.label span |
|
2810 | 2837 | { |
|
2811 | 2838 | margin: 0; |
|
2812 | 2839 | padding: 2px 0 0 0; |
|
2813 | 2840 | height: 1%; |
|
2814 | 2841 | display: block; |
|
2815 | 2842 | color: #363636; |
|
2816 | 2843 | } |
|
2817 | 2844 | |
|
2818 | 2845 | #register div.form div.fields div.field div.input |
|
2819 | 2846 | { |
|
2820 | 2847 | margin: 0; |
|
2821 | 2848 | padding: 0; |
|
2822 | 2849 | float: left; |
|
2823 | 2850 | } |
|
2824 | 2851 | |
|
2825 | 2852 | #register div.form div.fields div.field div.input input |
|
2826 | 2853 | { |
|
2827 | 2854 | margin: 0; |
|
2828 | 2855 | padding: 7px 7px 6px 7px; |
|
2829 | 2856 | width: 245px; |
|
2830 | 2857 | background: #FFFFFF; |
|
2831 | 2858 | border-top: 1px solid #b3b3b3; |
|
2832 | 2859 | border-left: 1px solid #b3b3b3; |
|
2833 | 2860 | border-right: 1px solid #eaeaea; |
|
2834 | 2861 | border-bottom: 1px solid #eaeaea; |
|
2835 | 2862 | color: #000000; |
|
2836 | 2863 | font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; |
|
2837 | 2864 | font-size: 11px; |
|
2838 | 2865 | } |
|
2839 | 2866 | |
|
2840 | 2867 | #register div.form div.fields div.field div.input input.error |
|
2841 | 2868 | { |
|
2842 | 2869 | background: #FBE3E4; |
|
2843 | 2870 | border-top: 1px solid #e1b2b3; |
|
2844 | 2871 | border-left: 1px solid #e1b2b3; |
|
2845 | 2872 | border-right: 1px solid #FBC2C4; |
|
2846 | 2873 | border-bottom: 1px solid #FBC2C4; |
|
2847 | 2874 | } |
|
2848 | 2875 | |
|
2849 | 2876 | #register div.form div.fields div.field div.input input.success |
|
2850 | 2877 | { |
|
2851 | 2878 | background: #E6EFC2; |
|
2852 | 2879 | border-top: 1px solid #cebb98; |
|
2853 | 2880 | border-left: 1px solid #cebb98; |
|
2854 | 2881 | border-right: 1px solid #c6d880; |
|
2855 | 2882 | border-bottom: 1px solid #c6d880; |
|
2856 | 2883 | } |
|
2857 | 2884 | |
|
2858 | 2885 | #register div.form div.fields div.field div.input div.link |
|
2859 | 2886 | { |
|
2860 | 2887 | margin: 6px 0 0 0; |
|
2861 | 2888 | padding: 0; |
|
2862 | 2889 | text-align: right; |
|
2863 | 2890 | } |
|
2864 | 2891 | |
|
2865 | 2892 | #register div.form div.fields div.field div.checkbox |
|
2866 | 2893 | { |
|
2867 | 2894 | margin: 0 0 0 184px; |
|
2868 | 2895 | padding: 0; |
|
2869 | 2896 | } |
|
2870 | 2897 | |
|
2871 | 2898 | #register div.form div.fields div.field div.checkbox label |
|
2872 | 2899 | { |
|
2873 | 2900 | color: #565656; |
|
2874 | 2901 | font-weight: bold; |
|
2875 | 2902 | } |
|
2876 | 2903 | |
|
2877 | 2904 | #register div.form div.fields div.buttons |
|
2878 | 2905 | { |
|
2879 | 2906 | margin: 0; |
|
2880 |
padding: 10px 0 0 |
|
|
2907 | padding: 10px 0 0 114px; | |
|
2881 | 2908 | clear: both; |
|
2882 | 2909 | overflow: hidden; |
|
2883 | 2910 | border-top: 1px solid #DDDDDD; |
|
2884 | 2911 | text-align: left; |
|
2885 | 2912 | } |
|
2886 | 2913 | |
|
2887 | 2914 | #register div.form div.fields div.buttons input |
|
2888 | 2915 | { |
|
2889 | 2916 | margin: 0; |
|
2890 | 2917 | color: #000000; |
|
2891 | 2918 | font-size: 1.0em; |
|
2892 | 2919 | font-weight: bold; |
|
2893 | 2920 | font-family: Verdana, Helvetica, Sans-Serif; |
|
2894 | 2921 | } |
|
2895 | 2922 | |
|
2896 | 2923 | #register div.form div.fields div.buttons input.ui-state-default |
|
2897 | 2924 | { |
|
2898 | 2925 | margin: 0; |
|
2899 | 2926 | padding: 6px 12px 6px 12px; |
|
2900 | 2927 | background: #e5e3e3 url("../images/button.png") repeat-x; |
|
2901 | 2928 | border-top: 1px solid #DDDDDD; |
|
2902 | 2929 | border-left: 1px solid #c6c6c6; |
|
2903 | 2930 | border-right: 1px solid #DDDDDD; |
|
2904 | 2931 | border-bottom: 1px solid #c6c6c6; |
|
2905 | 2932 | color: #515151; |
|
2906 | 2933 | } |
|
2907 | 2934 | #register div.form div.fields div.buttons div.highlight input.ui-state-default |
|
2908 | 2935 | { |
|
2909 | 2936 | background:url("../images/colors/blue/button_highlight.png") repeat-x scroll 0 0 #4E85BB; |
|
2910 | 2937 | border-color:#5C91A4 #2B7089 #1A6480 #2A6F89; |
|
2911 | 2938 | border-style:solid; |
|
2912 | 2939 | border-width:1px; |
|
2913 | 2940 | color:#FFFFFF; |
|
2914 | 2941 | } |
|
2915 | 2942 | |
|
2916 | 2943 | |
|
2917 | 2944 | |
|
2918 | 2945 | #register div.form div.fields div.buttons input.ui-state-hover |
|
2919 | 2946 | { |
|
2920 | 2947 | margin: 0; |
|
2921 | 2948 | padding: 6px 12px 6px 12px; |
|
2922 | 2949 | background: #b4b4b4 url("../images/button_selected.png") repeat-x; |
|
2923 | 2950 | border-top: 1px solid #cccccc; |
|
2924 | 2951 | border-left: 1px solid #bebebe; |
|
2925 | 2952 | border-right: 1px solid #b1b1b1; |
|
2926 | 2953 | border-bottom: 1px solid #afafaf; |
|
2927 | 2954 | color: #515151; |
|
2928 | 2955 | } |
|
2929 | 2956 | |
|
2930 | 2957 | #register div.form div.activation_msg { |
|
2931 | 2958 | padding-top:4px; |
|
2932 | 2959 | padding-bottom:4px; |
|
2933 | 2960 | |
|
2934 | 2961 | } |
|
2935 | 2962 | |
|
2936 | 2963 | /* ----------------------------------------------------------- |
|
2937 | 2964 | SUMMARY |
|
2938 | 2965 | ----------------------------------------------------------- */ |
|
2939 | 2966 | |
|
2940 | 2967 | #clone_url{ |
|
2941 | 2968 | border: none; |
|
2942 | 2969 | } |
|
2943 | 2970 | /* ----------------------------------------------------------- |
|
2944 | 2971 | FILES |
|
2945 | 2972 | ----------------------------------------------------------- */ |
|
2946 | 2973 | |
|
2947 | 2974 | h3.files_location{ |
|
2948 | 2975 | font-size: 1.8em; |
|
2949 | 2976 | font-weight: bold; |
|
2950 | 2977 | margin: 10px 0 !important; |
|
2951 | 2978 | border-bottom: none !important; |
|
2952 | 2979 | } |
|
2953 | 2980 | |
|
2954 | 2981 | #files_data.dl{ |
|
2955 | 2982 | |
|
2956 | 2983 | |
|
2957 | 2984 | } |
|
2958 | 2985 | #files_data dl dt{ |
|
2959 | 2986 | float:left; |
|
2960 | 2987 | margin:0 !important; |
|
2961 | 2988 | padding:5px; |
|
2962 | 2989 | width:115px; |
|
2963 | 2990 | } |
|
2964 | 2991 | #files_data dl dd{ |
|
2965 | 2992 | margin:0 !important; |
|
2966 | 2993 | padding: 5px !important; |
|
2967 | 2994 | } |
|
2968 | 2995 | |
|
2969 | 2996 | |
|
2970 | 2997 | /* ----------------------------------------------------------- |
|
2971 | 2998 | CHANGESETS |
|
2972 | 2999 | ----------------------------------------------------------- */ |
|
2973 | 3000 | #changeset_content { |
|
2974 | 3001 | border:1px solid #CCCCCC; |
|
2975 | 3002 | padding:5px; |
|
2976 | 3003 | } |
|
2977 | 3004 | |
|
2978 | 3005 | #changeset_content .container .wrapper { |
|
2979 | 3006 | width: 600px; |
|
2980 | 3007 | } |
|
2981 | 3008 | |
|
2982 | 3009 | #changeset_content .container { |
|
2983 | 3010 | height: 120px; |
|
2984 | 3011 | } |
|
2985 | 3012 | |
|
2986 | 3013 | #changeset_content .container .left { |
|
2987 | 3014 | float: left; |
|
2988 | 3015 | width: 70%; |
|
2989 | 3016 | padding-left: 5px; |
|
2990 | 3017 | } |
|
2991 | 3018 | |
|
2992 | 3019 | #changeset_content .container .right { |
|
2993 | 3020 | float: right; |
|
2994 | 3021 | width: 25%; |
|
2995 | 3022 | text-align: right; |
|
2996 | 3023 | } |
|
2997 | 3024 | |
|
2998 | 3025 | #changeset_content .container .left .date { |
|
2999 | 3026 | font-weight: bold; |
|
3000 | 3027 | } |
|
3001 | 3028 | |
|
3002 | 3029 | #changeset_content .container .left .author { |
|
3003 | 3030 | |
|
3004 | 3031 | } |
|
3005 | 3032 | |
|
3006 | 3033 | #changeset_content .container .left .message { |
|
3007 | 3034 | font-style: italic; |
|
3008 | 3035 | color: #556CB5; |
|
3009 | 3036 | } |
|
3010 | 3037 | |
|
3011 | 3038 | .cs_files { |
|
3012 | 3039 | |
|
3013 | 3040 | } |
|
3014 | 3041 | |
|
3015 | 3042 | .cs_files .cs_added { |
|
3016 | 3043 | background: url("/images/icons/page_white_add.png") no-repeat scroll 3px; |
|
3017 | 3044 | /*background-color:#BBFFBB;*/ |
|
3018 | 3045 | height: 16px; |
|
3019 | 3046 | padding-left: 20px; |
|
3020 | 3047 | margin-top: 7px; |
|
3021 | 3048 | text-align: left; |
|
3022 | 3049 | } |
|
3023 | 3050 | |
|
3024 | 3051 | .cs_files .cs_changed { |
|
3025 | 3052 | background: url("/images/icons/page_white_edit.png") no-repeat scroll |
|
3026 | 3053 | 3px; |
|
3027 | 3054 | /*background-color: #FFDD88;*/ |
|
3028 | 3055 | height: 16px; |
|
3029 | 3056 | padding-left: 20px; |
|
3030 | 3057 | margin-top: 7px; |
|
3031 | 3058 | text-align: left; |
|
3032 | 3059 | } |
|
3033 | 3060 | |
|
3034 | 3061 | .cs_files .cs_removed { |
|
3035 | 3062 | background: url("/images/icons/page_white_delete.png") no-repeat scroll |
|
3036 | 3063 | 3px; |
|
3037 | 3064 | /*background-color: #FF8888;*/ |
|
3038 | 3065 | height: 16px; |
|
3039 | 3066 | padding-left: 20px; |
|
3040 | 3067 | margin-top: 7px; |
|
3041 | 3068 | text-align: left; |
|
3042 | 3069 | } |
|
3043 | 3070 | |
|
3044 | 3071 | /* ----------------------------------------------------------- |
|
3045 | 3072 | CHANGESETS - CANVAS |
|
3046 | 3073 | ----------------------------------------------------------- */ |
|
3047 | 3074 | |
|
3048 | 3075 | #graph { |
|
3049 | 3076 | overflow: hidden; |
|
3050 | 3077 | } |
|
3051 | 3078 | |
|
3052 | 3079 | #graph_nodes { |
|
3053 | 3080 | width: 160px; |
|
3054 | 3081 | float: left; |
|
3055 | 3082 | margin-left:-50px; |
|
3056 | 3083 | margin-top: 5px; |
|
3057 | 3084 | } |
|
3058 | 3085 | |
|
3059 | 3086 | #graph_content { |
|
3060 | 3087 | width: 800px; |
|
3061 | 3088 | float: left; |
|
3062 | 3089 | } |
|
3063 | 3090 | |
|
3064 | 3091 | #graph_content .container_header { |
|
3065 | 3092 | border: 1px solid #CCCCCC; |
|
3066 | 3093 | padding:10px; |
|
3067 | 3094 | } |
|
3068 | 3095 | |
|
3069 | 3096 | #graph_content .container .wrapper { |
|
3070 | 3097 | width: 600px; |
|
3071 | 3098 | } |
|
3072 | 3099 | |
|
3073 | 3100 | #graph_content .container { |
|
3074 | 3101 | border-bottom: 1px solid #CCCCCC; |
|
3075 | 3102 | border-left: 1px solid #CCCCCC; |
|
3076 | 3103 | border-right: 1px solid #CCCCCC; |
|
3077 | 3104 | min-height: 90px; |
|
3078 | 3105 | overflow: hidden; |
|
3079 | 3106 | font-size:1.2em; |
|
3080 | 3107 | } |
|
3081 | 3108 | |
|
3082 | 3109 | #graph_content .container .left { |
|
3083 | 3110 | float: left; |
|
3084 | 3111 | width: 70%; |
|
3085 | 3112 | padding-left: 5px; |
|
3086 | 3113 | } |
|
3087 | 3114 | |
|
3088 | 3115 | #graph_content .container .right { |
|
3089 | 3116 | float: right; |
|
3090 | 3117 | width: 25%; |
|
3091 | 3118 | text-align: right; |
|
3092 | 3119 | } |
|
3093 | 3120 | |
|
3094 | 3121 | #graph_content .container .left .date { |
|
3095 | 3122 | font-weight: bold; |
|
3096 | 3123 | } |
|
3097 | 3124 | |
|
3098 | 3125 | #graph_content .container .left .author { |
|
3099 | 3126 | |
|
3100 | 3127 | } |
|
3101 | 3128 | |
|
3102 | 3129 | #graph_content .container .left .message { |
|
3103 | 3130 | font-size: 100%; |
|
3104 | 3131 | padding-top: 3px; |
|
3105 | 3132 | } |
|
3106 | 3133 | |
|
3107 | 3134 | .right div { |
|
3108 | 3135 | clear: both; |
|
3109 | 3136 | } |
|
3110 | 3137 | |
|
3111 | 3138 | .right .changes .added,.changed,.removed { |
|
3112 | 3139 | border: 1px solid #DDDDDD; |
|
3113 | 3140 | display: block; |
|
3114 | 3141 | float: right; |
|
3115 | 3142 | font-size: 0.75em; |
|
3116 | 3143 | text-align: center; |
|
3117 | 3144 | min-width: 15px; |
|
3118 | 3145 | } |
|
3119 | 3146 | |
|
3120 | 3147 | .right .changes .added { |
|
3121 | 3148 | background: #BBFFBB; |
|
3122 | 3149 | } |
|
3123 | 3150 | |
|
3124 | 3151 | .right .changes .changed { |
|
3125 | 3152 | background: #FFDD88; |
|
3126 | 3153 | } |
|
3127 | 3154 | |
|
3128 | 3155 | .right .changes .removed { |
|
3129 | 3156 | background: #FF8888; |
|
3130 | 3157 | } |
|
3131 | 3158 | |
|
3132 | 3159 | .right .merge { |
|
3133 | 3160 | vertical-align: top; |
|
3134 | 3161 | font-size: 60%; |
|
3135 | 3162 | font-weight: bold; |
|
3136 | 3163 | } |
|
3137 | 3164 | |
|
3138 | 3165 | .right .merge img { |
|
3139 | 3166 | vertical-align: bottom; |
|
3140 | 3167 | } |
|
3141 | 3168 | |
|
3142 | 3169 | .right .parent { |
|
3143 | 3170 | font-size: 90%; |
|
3144 | 3171 | font-family: monospace; |
|
3145 | 3172 | } |
|
3146 | 3173 | |
|
3147 | 3174 | |
|
3148 | 3175 | |
|
3149 | 3176 | /* ----------------------------------------------------------- |
|
3150 | 3177 | FILE BROWSER |
|
3151 | 3178 | ----------------------------------------------------------- */ |
|
3152 | 3179 | div.browserblock { |
|
3153 | 3180 | overflow: hidden; |
|
3154 | 3181 | padding: 0px; |
|
3155 | 3182 | border: 1px solid #ccc; |
|
3156 | 3183 | background: #f8f8f8; |
|
3157 | 3184 | font-size: 100%; |
|
3158 | 3185 | line-height: 100%; |
|
3159 | 3186 | /* new */ |
|
3160 | 3187 | line-height: 125%; |
|
3161 | 3188 | } |
|
3162 | 3189 | |
|
3163 | 3190 | div.browserblock .browser-header { |
|
3164 | 3191 | border-bottom: 1px solid #CCCCCC; |
|
3165 | 3192 | background: #FFFFFF; |
|
3166 | 3193 | color: blue; |
|
3167 | 3194 | padding: 10px 0 10px 0; |
|
3168 | 3195 | } |
|
3169 | 3196 | |
|
3170 | 3197 | div.browserblock .browser-header span { |
|
3171 | 3198 | margin-left: 25px; |
|
3172 | 3199 | font-weight: bold; |
|
3173 | 3200 | } |
|
3174 | 3201 | |
|
3175 | 3202 | div.browserblock .browser-body { |
|
3176 | 3203 | background: #EEEEEE; |
|
3177 | 3204 | } |
|
3178 | 3205 | |
|
3179 | 3206 | table.code-browser { |
|
3180 | 3207 | border-collapse: collapse; |
|
3181 | 3208 | width: 100%; |
|
3182 | 3209 | } |
|
3183 | 3210 | |
|
3184 | 3211 | table.code-browser tr { |
|
3185 | 3212 | margin: 3px; |
|
3186 | 3213 | } |
|
3187 | 3214 | |
|
3188 | 3215 | table.code-browser thead th { |
|
3189 | 3216 | background-color: #EEEEEE; |
|
3190 | 3217 | height: 20px; |
|
3191 | 3218 | font-size: 1.1em; |
|
3192 | 3219 | font-weight: bold; |
|
3193 | 3220 | text-align: center; |
|
3194 | 3221 | text-align: left; |
|
3195 | 3222 | padding-left: 10px; |
|
3196 | 3223 | } |
|
3197 | 3224 | |
|
3198 | 3225 | table.code-browser tbody tr { |
|
3199 | 3226 | |
|
3200 | 3227 | } |
|
3201 | 3228 | |
|
3202 | 3229 | table.code-browser tbody td { |
|
3203 | 3230 | padding-left: 10px; |
|
3204 | 3231 | height: 20px; |
|
3205 | 3232 | } |
|
3206 | 3233 | table.code-browser .browser-file { |
|
3207 | 3234 | background: url("/images/icons/document_16.png") no-repeat scroll 3px; |
|
3208 | 3235 | height: 16px; |
|
3209 | 3236 | padding-left: 20px; |
|
3210 | 3237 | text-align: left; |
|
3211 | 3238 | } |
|
3212 | 3239 | |
|
3213 | 3240 | table.code-browser .browser-dir { |
|
3214 | 3241 | background: url("/images/icons/folder_16.png") no-repeat scroll 3px; |
|
3215 | 3242 | height: 16px; |
|
3216 | 3243 | padding-left: 20px; |
|
3217 | 3244 | text-align: left; |
|
3218 | 3245 | } |
|
3219 | 3246 | |
|
3220 | 3247 | /* ----------------------------------------------------------- |
|
3221 | 3248 | ADMIN - SETTINGS |
|
3222 | 3249 | ----------------------------------------------------------- */ |
|
3223 | 3250 | #path_unlock{ |
|
3224 | 3251 | color: red; |
|
3225 | 3252 | font-size: 1.2em; |
|
3226 | 3253 | padding-left: 4px; |
|
3227 | 3254 | } |
|
3228 | 3255 | |
|
3229 | 3256 | /* ----------------------------------------------------------- |
|
3230 | 3257 | INFOBOX |
|
3231 | 3258 | ----------------------------------------------------------- */ |
|
3232 | 3259 | .info_box *{ |
|
3233 | 3260 | background:url("../../images/pager.png") repeat-x scroll 0 0 #EBEBEB; |
|
3234 | 3261 | border-color:#DEDEDE #C4C4C4 #C4C4C4 #CFCFCF; |
|
3235 | 3262 | border-style:solid; |
|
3236 | 3263 | border-width:1px; |
|
3237 | 3264 | color:#4A4A4A; |
|
3238 | 3265 | display:block; |
|
3239 | 3266 | font-weight:bold; |
|
3240 | 3267 | height:1%; |
|
3241 | 3268 | padding:4px 6px; |
|
3242 | 3269 | display: inline; |
|
3243 | 3270 | } |
|
3244 | 3271 | .info_box span{ |
|
3245 | 3272 | margin-left:3px; |
|
3246 | 3273 | margin-right:3px; |
|
3247 | 3274 | } |
|
3248 | 3275 | .info_box input#at_rev { |
|
3249 | 3276 | padding:1px 3px 3px 2px; |
|
3250 | 3277 | text-align:center; |
|
3251 | 3278 | } |
|
3252 | 3279 | .info_box input#view { |
|
3253 | 3280 | padding:0px 3px 2px 2px; |
|
3254 | 3281 | text-align:center; |
|
3255 | 3282 | } |
|
3256 | 3283 | /* ----------------------------------------------------------- |
|
3257 | 3284 | YUI TOOLTIP |
|
3258 | 3285 | ----------------------------------------------------------- */ |
|
3259 | 3286 | .yui-overlay,.yui-panel-container { |
|
3260 | 3287 | visibility: hidden; |
|
3261 | 3288 | position: absolute; |
|
3262 | 3289 | z-index: 2; |
|
3263 | 3290 | } |
|
3264 | 3291 | |
|
3265 | 3292 | .yui-tt { |
|
3266 | 3293 | visibility: hidden; |
|
3267 | 3294 | position: absolute; |
|
3268 | 3295 | color: #666666; |
|
3269 | 3296 | background-color: #FFFFFF; |
|
3270 | 3297 | font-family: arial, helvetica, verdana, sans-serif; |
|
3271 | 3298 | padding: 8px; |
|
3272 | 3299 | border: 2px solid #556CB5; |
|
3273 | 3300 | font: 100% sans-serif; |
|
3274 | 3301 | width: auto; |
|
3275 | 3302 | opacity: 1.0; |
|
3276 | 3303 | } |
|
3277 | 3304 | |
|
3278 | 3305 | .yui-tt-shadow { |
|
3279 | 3306 | display: none; |
|
3280 | 3307 | } |
|
3281 | 3308 | |
|
3282 | 3309 | /* ----------------------------------------------------------- |
|
3283 | 3310 | YUI AUTOCOMPLETE |
|
3284 | 3311 | ----------------------------------------------------------- */ |
|
3285 | 3312 | |
|
3286 | 3313 | .ac{ |
|
3287 | 3314 | vertical-align: top; |
|
3288 | 3315 | |
|
3289 | 3316 | } |
|
3290 | 3317 | .ac .match { |
|
3291 | 3318 | font-weight:bold; |
|
3292 | 3319 | } |
|
3293 | 3320 | |
|
3294 | 3321 | .ac .yui-ac { |
|
3295 | 3322 | position: relative; |
|
3296 | 3323 | font-family: arial; |
|
3297 | 3324 | font-size: 100%; |
|
3298 | 3325 | } |
|
3299 | 3326 | |
|
3300 | 3327 | .ac .perm_ac{ |
|
3301 | 3328 | width:15em; |
|
3302 | 3329 | } |
|
3303 | 3330 | /* styles for input field */ |
|
3304 | 3331 | .ac .yui-ac-input { |
|
3305 | 3332 | width: 100%; |
|
3306 | 3333 | } |
|
3307 | 3334 | |
|
3308 | 3335 | /* styles for results container */ |
|
3309 | 3336 | .ac .yui-ac-container { |
|
3310 | 3337 | position: absolute; |
|
3311 | 3338 | top: 1.6em; |
|
3312 | 3339 | width: 100%; |
|
3313 | 3340 | } |
|
3314 | 3341 | |
|
3315 | 3342 | /* styles for header/body/footer wrapper within container */ |
|
3316 | 3343 | .ac .yui-ac-content { |
|
3317 | 3344 | position: absolute; |
|
3318 | 3345 | width: 100%; |
|
3319 | 3346 | border: 1px solid #808080; |
|
3320 | 3347 | background: #fff; |
|
3321 | 3348 | overflow: hidden; |
|
3322 | 3349 | z-index: 9050; |
|
3323 | 3350 | } |
|
3324 | 3351 | |
|
3325 | 3352 | /* styles for container shadow */ |
|
3326 | 3353 | .ac .yui-ac-shadow { |
|
3327 | 3354 | position: absolute; |
|
3328 | 3355 | margin: .3em; |
|
3329 | 3356 | width: 100%; |
|
3330 | 3357 | background: #000; |
|
3331 | 3358 | -moz-opacity: 0.10; |
|
3332 | 3359 | opacity: .10; |
|
3333 | 3360 | filter: alpha(opacity = 10); |
|
3334 | 3361 | z-index: 9049; |
|
3335 | 3362 | } |
|
3336 | 3363 | |
|
3337 | 3364 | /* styles for results list */ |
|
3338 | 3365 | .ac .yui-ac-content ul { |
|
3339 | 3366 | margin: 0; |
|
3340 | 3367 | padding: 0; |
|
3341 | 3368 | width: 100%; |
|
3342 | 3369 | } |
|
3343 | 3370 | |
|
3344 | 3371 | /* styles for result item */ |
|
3345 | 3372 | .ac .yui-ac-content li { |
|
3346 | 3373 | margin: 0; |
|
3347 | 3374 | padding: 2px 5px; |
|
3348 | 3375 | cursor: default; |
|
3349 | 3376 | white-space: nowrap; |
|
3350 | 3377 | } |
|
3351 | 3378 | |
|
3352 | 3379 | /* styles for prehighlighted result item */ |
|
3353 | 3380 | .ac .yui-ac-content li.yui-ac-prehighlight { |
|
3354 | 3381 | background: #B3D4FF; |
|
3355 | 3382 | } |
|
3356 | 3383 | |
|
3357 | 3384 | /* styles for highlighted result item */ |
|
3358 | 3385 | .ac .yui-ac-content li.yui-ac-highlight { |
|
3359 | 3386 | background: #556CB5; |
|
3360 | 3387 | color: #FFF; |
|
3361 | 3388 | } |
|
3362 | 3389 | |
|
3363 | 3390 | |
|
3364 | 3391 | /* ----------------------------------------------------------- |
|
3365 | 3392 | ACTION ICONS |
|
3366 | 3393 | ----------------------------------------------------------- */ |
|
3367 | 3394 | .add_icon { |
|
3368 | 3395 | background: url("/images/icons/add.png") no-repeat scroll 3px ; |
|
3369 | 3396 | height: 16px; |
|
3370 | 3397 | padding-left: 20px; |
|
3371 | 3398 | padding-top: 1px; |
|
3372 | 3399 | text-align: left; |
|
3373 | 3400 | } |
|
3374 | 3401 | |
|
3375 | 3402 | .edit_icon { |
|
3376 | 3403 | background: url("/images/icons/folder_edit.png") no-repeat scroll 3px; |
|
3377 | 3404 | height: 16px; |
|
3378 | 3405 | padding-left: 20px; |
|
3379 | 3406 | padding-top: 1px; |
|
3380 | 3407 | text-align: left; |
|
3381 | 3408 | } |
|
3382 | 3409 | |
|
3383 | 3410 | .delete_icon { |
|
3384 | 3411 | background: url("/images/icons/delete.png") no-repeat scroll 3px; |
|
3385 | 3412 | height: 16px; |
|
3386 | 3413 | padding-left: 20px; |
|
3387 | 3414 | padding-top: 1px; |
|
3388 | 3415 | text-align: left; |
|
3389 | 3416 | } |
|
3390 | 3417 | |
|
3391 | 3418 | .rss_icon { |
|
3392 | 3419 | background: url("/images/icons/rss_16.png") no-repeat scroll 3px; |
|
3393 | 3420 | height: 16px; |
|
3394 | 3421 | padding-left: 20px; |
|
3395 | 3422 | padding-top: 1px; |
|
3396 | 3423 | text-align: left; |
|
3397 | 3424 | } |
|
3398 | 3425 | |
|
3399 | 3426 | .atom_icon { |
|
3400 | 3427 | background: url("/images/icons/atom.png") no-repeat scroll 3px; |
|
3401 | 3428 | height: 16px; |
|
3402 | 3429 | padding-left: 20px; |
|
3403 | 3430 | padding-top: 1px; |
|
3404 | 3431 | text-align: left; |
|
3405 | 3432 | } |
|
3406 | 3433 | |
|
3407 | 3434 | .archive_icon { |
|
3408 | 3435 | background: url("/images/icons/compress.png") no-repeat scroll 3px; |
|
3409 | 3436 | height: 16px; |
|
3410 | 3437 | padding-left: 20px; |
|
3411 | 3438 | text-align: left; |
|
3412 | 3439 | padding-top: 1px; |
|
3413 | 3440 | } |
|
3414 | 3441 | |
|
3415 | 3442 | .action_button { |
|
3416 | 3443 | border: 0px; |
|
3417 | 3444 | display: block; |
|
3418 | 3445 | } |
|
3419 | 3446 | |
|
3420 | 3447 | .action_button:hover { |
|
3421 | 3448 | border: 0px; |
|
3422 | 3449 | font-style: italic; |
|
3423 | 3450 | cursor: pointer; |
|
3424 | 3451 | } |
|
3425 | 3452 | |
|
3426 | 3453 | /* ----------------------------------------------------------- |
|
3427 | 3454 | REPO SWITCHER |
|
3428 | 3455 | ----------------------------------------------------------- */ |
|
3429 | 3456 | |
|
3430 | 3457 | #switch_repos{ |
|
3431 | 3458 | position: absolute; |
|
3432 | 3459 | height: 25px; |
|
3433 | 3460 | z-index: 1; |
|
3434 | 3461 | } |
|
3435 | 3462 | #switch_repos select{ |
|
3436 | 3463 | min-width:150px; |
|
3437 | 3464 | max-height: 250px; |
|
3438 | 3465 | z-index: 1; |
|
3439 | 3466 | } |
|
3440 | 3467 | /* ----------------------------------------------------------- |
|
3441 | 3468 | BREADCRUMBS |
|
3442 | 3469 | ----------------------------------------------------------- */ |
|
3443 | 3470 | |
|
3444 | 3471 | .breadcrumbs{ |
|
3445 | 3472 | border:medium none; |
|
3446 | 3473 | color:#FFFFFF; |
|
3447 | 3474 | float:left; |
|
3448 | 3475 | margin:0; |
|
3449 | 3476 | padding:11px 0 11px 10px; |
|
3450 | 3477 | text-transform:uppercase; |
|
3451 | 3478 | font-weight: bold; |
|
3452 | 3479 | font-size: 14px; |
|
3453 | 3480 | } |
|
3454 | 3481 | .breadcrumbs a{ |
|
3455 | 3482 | color: #FFFFFF; |
|
3456 | 3483 | } |
|
3457 | 3484 | |
|
3458 | 3485 | |
|
3459 | 3486 | /* ----------------------------------------------------------- |
|
3460 | 3487 | FLASH MSG |
|
3461 | 3488 | ----------------------------------------------------------- */ |
|
3462 | 3489 | .flash_msg ul { |
|
3463 | 3490 | margin: 0; |
|
3464 | 3491 | padding: 0px 0px 10px 0px; |
|
3465 | 3492 | } |
|
3466 | 3493 | |
|
3467 | 3494 | .error_msg { |
|
3468 | 3495 | background-color: #FFCFCF; |
|
3469 | 3496 | background-image: url("/images/icons/error_msg.png"); |
|
3470 | 3497 | border: 1px solid #FF9595; |
|
3471 | 3498 | color: #CC3300; |
|
3472 | 3499 | } |
|
3473 | 3500 | |
|
3474 | 3501 | .warning_msg { |
|
3475 | 3502 | background-color: #FFFBCC; |
|
3476 | 3503 | background-image: url("/images/icons/warning_msg.png"); |
|
3477 | 3504 | border: 1px solid #FFF35E; |
|
3478 | 3505 | color: #C69E00; |
|
3479 | 3506 | } |
|
3480 | 3507 | |
|
3481 | 3508 | .success_msg { |
|
3482 | 3509 | background-color: #D5FFCF; |
|
3483 | 3510 | background-image: url("/images/icons/success_msg.png"); |
|
3484 | 3511 | border: 1px solid #97FF88; |
|
3485 | 3512 | color: #009900; |
|
3486 | 3513 | } |
|
3487 | 3514 | |
|
3488 | 3515 | .notice_msg { |
|
3489 | 3516 | background-color: #DCE3FF; |
|
3490 | 3517 | background-image: url("/images/icons/notice_msg.png"); |
|
3491 | 3518 | border: 1px solid #93A8FF; |
|
3492 | 3519 | color: #556CB5; |
|
3493 | 3520 | } |
|
3494 | 3521 | |
|
3495 | 3522 | .success_msg,.error_msg,.notice_msg,.warning_msg { |
|
3496 | 3523 | background-position: 10px center; |
|
3497 | 3524 | background-repeat: no-repeat; |
|
3498 | 3525 | font-size: 12px; |
|
3499 | 3526 | font-weight: bold; |
|
3500 | 3527 | min-height: 14px; |
|
3501 | 3528 | line-height: 14px; |
|
3502 | 3529 | margin-bottom: 0px; |
|
3503 | 3530 | margin-top: 0px; |
|
3504 | 3531 | padding: 6px 10px 6px 40px; |
|
3505 | 3532 | display: block; |
|
3506 | 3533 | overflow: auto; |
|
3507 | 3534 | } |
|
3508 | 3535 | |
|
3509 | 3536 | #msg_close { |
|
3510 | 3537 | background: transparent url("icons/cross_grey_small.png") no-repeat |
|
3511 | 3538 | scroll 0 0; |
|
3512 | 3539 | cursor: pointer; |
|
3513 | 3540 | height: 16px; |
|
3514 | 3541 | position: absolute; |
|
3515 | 3542 | right: 5px; |
|
3516 | 3543 | top: 5px; |
|
3517 | 3544 | width: 16px; |
|
3518 | 3545 | } |
|
3519 | 3546 | /* ----------------------------------------------------------- |
|
3520 | 3547 | YUI FLOT |
|
3521 | 3548 | ----------------------------------------------------------- */ |
|
3522 | 3549 | |
|
3523 | 3550 | div#commit_history{ |
|
3524 | 3551 | float: left; |
|
3525 | 3552 | } |
|
3526 | 3553 | div#legend_data{ |
|
3527 | 3554 | float:left; |
|
3528 | 3555 | |
|
3529 | 3556 | } |
|
3530 | 3557 | div#legend_container { |
|
3531 | 3558 | float: left; |
|
3532 | 3559 | } |
|
3533 | 3560 | |
|
3534 | 3561 | div#legend_container table,div#legend_choices table{ |
|
3535 | 3562 | width:auto !important; |
|
3536 | 3563 | } |
|
3537 | 3564 | |
|
3538 | 3565 | div#legend_container table td{ |
|
3539 | 3566 | border: none !important; |
|
3540 | 3567 | padding: 0px !important; |
|
3541 | 3568 | height: 20px !important; |
|
3542 | 3569 | } |
|
3543 | 3570 | |
|
3544 | 3571 | div#legend_choices table td{ |
|
3545 | 3572 | border: none !important; |
|
3546 | 3573 | padding: 0px !important; |
|
3547 | 3574 | height: 20px !important; |
|
3548 | 3575 | } |
|
3549 | 3576 | |
|
3550 | 3577 | div#legend_choices{ |
|
3551 | 3578 | float:left; |
|
3552 | 3579 | } |
|
3553 | 3580 | |
|
3554 | 3581 | /* ----------------------------------------------------------- |
|
3555 | 3582 | PERMISSIONS TABLE |
|
3556 | 3583 | ----------------------------------------------------------- */ |
|
3557 | 3584 | table#permissions_manage{ |
|
3558 | 3585 | width: 0 !important; |
|
3559 | 3586 | |
|
3560 | 3587 | } |
|
3561 | 3588 | table#permissions_manage span.private_repo_msg{ |
|
3562 | 3589 | font-size: 0.8em; |
|
3563 | 3590 | opacity:0.6; |
|
3564 | 3591 | |
|
3565 | 3592 | } |
|
3566 | 3593 | table#permissions_manage td.private_repo_msg{ |
|
3567 | 3594 | font-size: 0.8em; |
|
3568 | 3595 | |
|
3569 | 3596 | } |
|
3570 | 3597 | table#permissions_manage tr#add_perm_input td{ |
|
3571 | 3598 | vertical-align:middle; |
|
3572 | 3599 | |
|
3573 | 3600 | } |
|
3574 | 3601 | |
|
3575 | 3602 | /* ----------------------------------------------------------- |
|
3576 | 3603 | GRAVATARS |
|
3577 | 3604 | ----------------------------------------------------------- */ |
|
3578 | 3605 | div.gravatar{ |
|
3579 | 3606 | background-color:white; |
|
3580 | 3607 | border:1px solid #D0D0D0; |
|
3581 | 3608 | float:left; |
|
3582 | 3609 | margin-right:0.7em; |
|
3583 | 3610 | padding: 2px 2px 0px; |
|
3584 | 3611 | } |
|
3585 | 3612 | |
|
3586 | 3613 | /* ----------------------------------------------------------- |
|
3587 | 3614 | STYLING OF LAYOUT |
|
3588 | 3615 | ----------------------------------------------------------- */ |
|
3589 | 3616 | |
|
3590 | 3617 | |
|
3591 | 3618 | /* ----------------------------------------------------------- |
|
3592 | 3619 | GLOBAL WIDTH |
|
3593 | 3620 | ----------------------------------------------------------- */ |
|
3594 | 3621 | #header,#content,#footer{ |
|
3595 | 3622 | min-width: 1224px; |
|
3596 | 3623 | } |
|
3597 | 3624 | |
|
3598 | 3625 | /* ----------------------------------------------------------- |
|
3599 | 3626 | content |
|
3600 | 3627 | ----------------------------------------------------------- */ |
|
3601 | 3628 | |
|
3602 | 3629 | #content |
|
3603 | 3630 | { |
|
3604 | 3631 | margin: 10px 30px 0 30px; |
|
3605 | 3632 | padding: 0; |
|
3606 | 3633 | min-height: 100%; |
|
3607 | 3634 | clear: both; |
|
3608 | 3635 | overflow: hidden; |
|
3609 | 3636 | background: transparent; |
|
3610 | 3637 | } |
|
3611 | 3638 | |
|
3612 | 3639 | /* ----------------------------------------------------------- |
|
3613 | 3640 | content -> right -> forms -> labels |
|
3614 | 3641 | ----------------------------------------------------------- */ |
|
3615 | 3642 | |
|
3616 | 3643 | #content div.box div.form div.fields div.field div.label |
|
3617 | 3644 | { |
|
3618 | 3645 | left: 80px; |
|
3619 | 3646 | margin: 0; |
|
3620 | 3647 | padding: 8px 0 0 5px; |
|
3621 | 3648 | width: auto; |
|
3622 | 3649 | position: absolute; |
|
3623 | 3650 | } |
|
3624 | 3651 | |
|
3625 | 3652 | #content div.box-left div.form div.fields div.field div.label, |
|
3626 | 3653 | #content div.box-right div.form div.fields div.field div.label |
|
3627 | 3654 | { |
|
3628 | 3655 | left: 0; |
|
3629 | 3656 | margin: 0; |
|
3630 | 3657 | padding: 0 0 8px 0; |
|
3631 | 3658 | width: auto; |
|
3632 | 3659 | position: relative; |
|
3633 | 3660 | } No newline at end of file |
@@ -1,41 +1,41 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | %if c.users_log: |
|
3 | 3 | <table> |
|
4 | 4 | <tr> |
|
5 | 5 | <th class="left">${_('Username')}</th> |
|
6 | 6 | <th class="left">${_('Repository')}</th> |
|
7 | 7 | <th class="left">${_('Action')}</th> |
|
8 | 8 | <th class="left">${_('Date')}</th> |
|
9 | 9 | <th class="left">${_('From IP')}</th> |
|
10 | 10 | </tr> |
|
11 | 11 | |
|
12 | 12 | %for cnt,l in enumerate(c.users_log): |
|
13 | 13 | <tr class="parity${cnt%2}"> |
|
14 | <td>${l.user.username}</td> | |
|
15 | <td>${l.repository}</td> | |
|
14 | <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td> | |
|
15 | <td>${h.link_to(l.repository,h.url('summary_home',repo_name=l.repository))}</td> | |
|
16 | 16 | <td>${l.action}</td> |
|
17 | 17 | <td>${l.action_date}</td> |
|
18 | 18 | <td>${l.user_ip}</td> |
|
19 | 19 | </tr> |
|
20 | 20 | %endfor |
|
21 | 21 | </table> |
|
22 | 22 | |
|
23 | 23 | <script type="text/javascript"> |
|
24 | 24 | var data_div = 'user_log'; |
|
25 | 25 | YAHOO.util.Event.onDOMReady(function(){ |
|
26 | 26 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ |
|
27 | 27 | YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});}); |
|
28 | 28 | </script> |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | <div class="pagination-wh pagination-left"> |
|
32 | 32 | ${c.users_log.pager('$link_previous ~2~ $link_next', |
|
33 | 33 | onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{ |
|
34 | 34 | success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText; |
|
35 | 35 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ |
|
36 | 36 | YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');}); |
|
37 | 37 | YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")} |
|
38 | 38 | </div> |
|
39 | 39 | %else: |
|
40 | 40 | ${_('No actions yet')} |
|
41 | 41 | %endif |
@@ -1,68 +1,68 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.html"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 | 5 | ${_('Permissions administration')} |
|
6 | 6 | </%def> |
|
7 | 7 | |
|
8 | 8 | <%def name="breadcrumbs_links()"> |
|
9 | 9 | ${h.link_to(_('Admin'),h.url('admin_home'))} |
|
10 | 10 | » |
|
11 | 11 | ${_('Permissions')} |
|
12 | 12 | </%def> |
|
13 | 13 | |
|
14 | 14 | <%def name="page_nav()"> |
|
15 | 15 | ${self.menu('admin')} |
|
16 | 16 | </%def> |
|
17 | 17 | |
|
18 | 18 | <%def name="main()"> |
|
19 | 19 | <div class="box"> |
|
20 | 20 | <!-- box / title --> |
|
21 | 21 | <div class="title"> |
|
22 | 22 | ${self.breadcrumbs()} |
|
23 | 23 | </div> |
|
24 | 24 | <h3>${_('Default permissions')}</h3> |
|
25 | 25 | ${h.form(url('permission', id='default'),method='put')} |
|
26 | 26 | <div class="form"> |
|
27 | 27 | <!-- fields --> |
|
28 | 28 | <div class="fields"> |
|
29 | 29 | |
|
30 | 30 | <div class="field"> |
|
31 | 31 | <div class="label"> |
|
32 |
<label for="default_perm">${_(' |
|
|
32 | <label for="default_perm">${_('Repository permission')}:</label> | |
|
33 | 33 | </div> |
|
34 | 34 | <div class="select"> |
|
35 | 35 | ${h.select('default_perm','',c.perms_choices)} |
|
36 | 36 | |
|
37 | 37 | ${h.checkbox('overwrite_default','true')} |
|
38 | 38 | <label for="overwrite_default"> |
|
39 | 39 | <span class="tooltip" |
|
40 | 40 | tooltip_title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}"> |
|
41 | 41 | ${_('overwrite existing settings')}</span> </label> |
|
42 | 42 | </div> |
|
43 | 43 | </div> |
|
44 | 44 | <div class="field"> |
|
45 | 45 | <div class="label"> |
|
46 | 46 | <label for="default_register">${_('Registration')}:</label> |
|
47 | 47 | </div> |
|
48 | 48 | <div class="select"> |
|
49 | 49 | ${h.select('default_register','',c.register_choices)} |
|
50 | 50 | </div> |
|
51 | 51 | </div> |
|
52 | 52 | <div class="field"> |
|
53 | 53 | <div class="label"> |
|
54 |
<label for="default_create">${_(' |
|
|
54 | <label for="default_create">${_('Repository creation')}:</label> | |
|
55 | 55 | </div> |
|
56 | 56 | <div class="select"> |
|
57 | 57 | ${h.select('default_create','',c.create_choices)} |
|
58 | 58 | </div> |
|
59 | 59 | </div> |
|
60 | 60 | |
|
61 | 61 | <div class="buttons"> |
|
62 | 62 | ${h.submit('set','set',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
63 | 63 | </div> |
|
64 | 64 | </div> |
|
65 | 65 | </div> |
|
66 | 66 | ${h.end_form()} |
|
67 | 67 | </div> |
|
68 | 68 | </%def> |
@@ -1,145 +1,170 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.html"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 | 5 | ${_('Settings administration')} |
|
6 | 6 | </%def> |
|
7 | 7 | |
|
8 | 8 | <%def name="breadcrumbs_links()"> |
|
9 | 9 | ${h.link_to(_('Admin'),h.url('admin_home'))} » ${_('Settings')} |
|
10 | 10 | </%def> |
|
11 | 11 | |
|
12 | 12 | <%def name="page_nav()"> |
|
13 | 13 | ${self.menu('admin')} |
|
14 | 14 | </%def> |
|
15 | 15 | |
|
16 | 16 | <%def name="main()"> |
|
17 | 17 | <div class="box"> |
|
18 | 18 | <!-- box / title --> |
|
19 | 19 | <div class="title"> |
|
20 | 20 | ${self.breadcrumbs()} |
|
21 | 21 | </div> |
|
22 | 22 | <!-- end box / title --> |
|
23 | 23 | |
|
24 | 24 | <h3>${_('Remap and rescan repositories')}</h3> |
|
25 | 25 | ${h.form(url('admin_setting', setting_id='mapping'),method='put')} |
|
26 | 26 | <div class="form"> |
|
27 | 27 | <!-- fields --> |
|
28 | 28 | |
|
29 | 29 | <div class="fields"> |
|
30 | 30 | <div class="field"> |
|
31 | 31 | <div class="label label-checkbox"> |
|
32 | 32 | <label for="destroy">${_('rescan option')}:</label> |
|
33 | 33 | </div> |
|
34 | 34 | <div class="checkboxes"> |
|
35 | 35 | <div class="checkbox"> |
|
36 | 36 | ${h.checkbox('destroy',True)} |
|
37 | 37 | <label for="checkbox-1"> |
|
38 | 38 | <span class="tooltip" tooltip_title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}"> |
|
39 | 39 | ${_('destroy old data')}</span> </label> |
|
40 | 40 | </div> |
|
41 | 41 | </div> |
|
42 | 42 | </div> |
|
43 | 43 | |
|
44 | 44 | <div class="buttons"> |
|
45 | 45 | ${h.submit('rescan','rescan repositories',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
46 | 46 | </div> |
|
47 | 47 | </div> |
|
48 | 48 | </div> |
|
49 | 49 | ${h.end_form()} |
|
50 |
|
|
|
50 | ||
|
51 | <h3>${_('Whoosh indexing')}</h3> | |
|
52 | ${h.form(url('admin_setting', setting_id='whoosh'),method='put')} | |
|
53 | <div class="form"> | |
|
54 | <!-- fields --> | |
|
55 | ||
|
56 | <div class="fields"> | |
|
57 | <div class="field"> | |
|
58 | <div class="label label-checkbox"> | |
|
59 | <label for="destroy">${_('index build option')}:</label> | |
|
60 | </div> | |
|
61 | <div class="checkboxes"> | |
|
62 | <div class="checkbox"> | |
|
63 | ${h.checkbox('full_index',True)} | |
|
64 | <label for="checkbox-1">${_('build from scratch')}</label> | |
|
65 | </div> | |
|
66 | </div> | |
|
67 | </div> | |
|
68 | ||
|
69 | <div class="buttons"> | |
|
70 | ${h.submit('reindex','reindex',class_="ui-button ui-widget ui-state-default ui-corner-all")} | |
|
71 | </div> | |
|
72 | </div> | |
|
73 | </div> | |
|
74 | ${h.end_form()} | |
|
75 | ||
|
51 | 76 | <h3>${_('Global application settings')}</h3> |
|
52 | 77 | ${h.form(url('admin_setting', setting_id='global'),method='put')} |
|
53 | 78 | <div class="form"> |
|
54 | 79 | <!-- fields --> |
|
55 | 80 | |
|
56 | 81 | <div class="fields"> |
|
57 | 82 | |
|
58 | 83 | <div class="field"> |
|
59 | 84 | <div class="label"> |
|
60 | 85 | <label for="hg_app_title">${_('Application name')}:</label> |
|
61 | 86 | </div> |
|
62 | 87 | <div class="input"> |
|
63 | 88 | ${h.text('hg_app_title',size=30)} |
|
64 | 89 | </div> |
|
65 | 90 | </div> |
|
66 | 91 | |
|
67 | 92 | <div class="field"> |
|
68 | 93 | <div class="label"> |
|
69 | 94 | <label for="hg_app_realm">${_('Realm text')}:</label> |
|
70 | 95 | </div> |
|
71 | 96 | <div class="input"> |
|
72 | 97 | ${h.text('hg_app_realm',size=30)} |
|
73 | 98 | </div> |
|
74 | 99 | </div> |
|
75 | 100 | |
|
76 | 101 | <div class="buttons"> |
|
77 | 102 | ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
78 | 103 | </div> |
|
79 | 104 | </div> |
|
80 | 105 | </div> |
|
81 | 106 | ${h.end_form()} |
|
82 | 107 | |
|
83 | 108 | <h3>${_('Mercurial settings')}</h3> |
|
84 | 109 | ${h.form(url('admin_setting', setting_id='mercurial'),method='put')} |
|
85 | 110 | <div class="form"> |
|
86 | 111 | <!-- fields --> |
|
87 | 112 | |
|
88 | 113 | <div class="fields"> |
|
89 | 114 | |
|
90 | 115 | <div class="field"> |
|
91 | 116 | <div class="label label-checkbox"> |
|
92 | 117 | <label for="web_push_ssl">${_('Web')}:</label> |
|
93 | 118 | </div> |
|
94 | 119 | <div class="checkboxes"> |
|
95 | 120 | <div class="checkbox"> |
|
96 | 121 | ${h.checkbox('web_push_ssl','true')} |
|
97 | 122 | <label for="web_push_ssl">${_('require ssl for pushing')}</label> |
|
98 | 123 | </div> |
|
99 | 124 | </div> |
|
100 | 125 | </div> |
|
101 | 126 | |
|
102 | 127 | <div class="field"> |
|
103 | 128 | <div class="label label-checkbox"> |
|
104 | 129 | <label for="web_push_ssl">${_('Hooks')}:</label> |
|
105 | 130 | </div> |
|
106 | 131 | <div class="checkboxes"> |
|
107 | 132 | <div class="checkbox"> |
|
108 | 133 | ${h.checkbox('hooks_changegroup_update','True')} |
|
109 | 134 | <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label> |
|
110 | 135 | </div> |
|
111 | 136 | <div class="checkbox"> |
|
112 | 137 | ${h.checkbox('hooks_changegroup_repo_size','True')} |
|
113 | 138 | <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label> |
|
114 | 139 | </div> |
|
115 | 140 | </div> |
|
116 | 141 | </div> |
|
117 | 142 | |
|
118 | 143 | <div class="field"> |
|
119 | 144 | <div class="label"> |
|
120 | 145 | <label for="paths_root_path">${_('Repositories location')}:</label> |
|
121 | 146 | </div> |
|
122 | 147 | <div class="input"> |
|
123 | 148 | ${h.text('paths_root_path',size=30,readonly="readonly")} |
|
124 | 149 | <span id="path_unlock" class="tooltip" |
|
125 | 150 | tooltip_title="${h.tooltip(_('This a crucial application setting. If You really sure you need to change this, you must restart application in order to make this settings take effect. Click this label to unlock.'))}"> |
|
126 | 151 | ${_('unlock')}</span> |
|
127 | 152 | </div> |
|
128 | 153 | </div> |
|
129 | 154 | |
|
130 | 155 | <div class="buttons"> |
|
131 | 156 | ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
132 | 157 | </div> |
|
133 | 158 | </div> |
|
134 | 159 | </div> |
|
135 | 160 | ${h.end_form()} |
|
136 | 161 | |
|
137 | 162 | <script type="text/javascript"> |
|
138 | 163 | YAHOO.util.Event.onDOMReady(function(){ |
|
139 | 164 | YAHOO.util.Event.addListener('path_unlock','click',function(){ |
|
140 | 165 | YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly'); |
|
141 | 166 | }); |
|
142 | 167 | }); |
|
143 | 168 | </script> |
|
144 | 169 | </div> |
|
145 | 170 | </%def> |
@@ -1,242 +1,247 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
|
3 | 3 | <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml"> |
|
4 | 4 | <head> |
|
5 | 5 | <title>${next.title()}</title> |
|
6 | 6 | <link rel="icon" href="/images/hgicon.png" type="image/png" /> |
|
7 | 7 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
|
8 | 8 | <meta name="robots" content="index, nofollow"/> |
|
9 | 9 | <!-- stylesheets --> |
|
10 | 10 | ${self.css()} |
|
11 | 11 | <!-- scripts --> |
|
12 | 12 | ${self.js()} |
|
13 | 13 | </head> |
|
14 | 14 | <body> |
|
15 | 15 | <!-- header --> |
|
16 | 16 | <div id="header"> |
|
17 | 17 | <!-- user --> |
|
18 | 18 | <ul id="logged-user"> |
|
19 | 19 | <li class="first"> |
|
20 | 20 | <div class="gravatar"> |
|
21 | 21 | <img alt="gravatar" src="${h.gravatar_url(c.hg_app_user.email,24)}" /> |
|
22 | 22 | </div> |
|
23 | 23 | <div class="account"> |
|
24 | 24 | ${h.link_to('%s %s'%(c.hg_app_user.name,c.hg_app_user.lastname),h.url('admin_settings_my_account'))}<br/> |
|
25 | 25 | ${h.link_to(c.hg_app_user.username,h.url('admin_settings_my_account'))} |
|
26 | 26 | </div> |
|
27 | 27 | </li> |
|
28 | 28 | <li class="last highlight">${h.link_to(u'Logout',h.url('logout_home'))}</li> |
|
29 | 29 | </ul> |
|
30 | 30 | <!-- end user --> |
|
31 | 31 | <div id="header-inner"> |
|
32 | 32 | <div id="home"> |
|
33 | 33 | <a href="${h.url('hg_home')}"></a> |
|
34 | 34 | </div> |
|
35 | 35 | <!-- logo --> |
|
36 | 36 | <div id="logo"> |
|
37 | 37 | <h1><a href="${h.url('hg_home')}">${c.hg_app_name}</a></h1> |
|
38 | 38 | </div> |
|
39 | 39 | <!-- end logo --> |
|
40 | 40 | <!-- quick menu --> |
|
41 | 41 | ${self.page_nav()} |
|
42 | 42 | <!-- end quick --> |
|
43 | 43 | <div class="corner tl"></div> |
|
44 | 44 | <div class="corner tr"></div> |
|
45 | 45 | </div> |
|
46 | 46 | </div> |
|
47 | 47 | <!-- end header --> |
|
48 | 48 | |
|
49 | 49 | <!-- CONTENT --> |
|
50 | 50 | <div id="content"> |
|
51 | 51 | <div class="flash_msg"> |
|
52 | 52 | <% messages = h.flash.pop_messages() %> |
|
53 | 53 | % if messages: |
|
54 | 54 | <ul id="flash-messages"> |
|
55 | 55 | % for message in messages: |
|
56 | 56 | <li class="${message.category}_msg">${message}</li> |
|
57 | 57 | % endfor |
|
58 | 58 | </ul> |
|
59 | 59 | % endif |
|
60 | 60 | </div> |
|
61 | 61 | <div id="main"> |
|
62 | 62 | ${next.main()} |
|
63 | 63 | </div> |
|
64 | 64 | </div> |
|
65 | 65 | <!-- END CONTENT --> |
|
66 | 66 | |
|
67 | 67 | <!-- footer --> |
|
68 | 68 | <div id="footer"> |
|
69 | 69 | <p>Hg App ${c.hg_app_version} © 2010 by Marcin Kuzminski</p> |
|
70 | 70 | <script type="text/javascript">${h.tooltip.activate()}</script> |
|
71 | 71 | </div> |
|
72 | 72 | <!-- end footer --> |
|
73 | 73 | </body> |
|
74 | 74 | |
|
75 | 75 | </html> |
|
76 | 76 | |
|
77 | 77 | ### MAKO DEFS ### |
|
78 | 78 | <%def name="page_nav()"> |
|
79 | 79 | ${self.menu()} |
|
80 | 80 | </%def> |
|
81 | 81 | |
|
82 | 82 | <%def name="menu(current=None)"> |
|
83 | 83 | <% |
|
84 | 84 | def is_current(selected): |
|
85 | 85 | if selected == current: |
|
86 | 86 | return h.literal('class="current"') |
|
87 | 87 | %> |
|
88 | 88 | %if current not in ['home','admin']: |
|
89 | 89 | ##REGULAR MENU |
|
90 | 90 | <ul id="quick"> |
|
91 | 91 | <!-- repo switcher --> |
|
92 | 92 | <li> |
|
93 | 93 | <a id="repo_switcher" title="${_('Switch repository')}" href="#"> |
|
94 | 94 | <span class="icon"> |
|
95 | 95 | <img src="/images/icons/database.png" alt="${_('Products')}" /> |
|
96 | 96 | </span> |
|
97 | 97 | <span>↓</span> |
|
98 | 98 | </a> |
|
99 | 99 | <ul class="repo_switcher"> |
|
100 | %for repo in c.repo_switcher_list: | |
|
101 | <li>${h.link_to(repo,h.url('summary_home',repo_name=repo))}</li> | |
|
100 | %for repo,private in c.repo_switcher_list: | |
|
101 | %if private: | |
|
102 | <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="private_repo")}</li> | |
|
103 | %else: | |
|
104 | <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="public_repo")}</li> | |
|
105 | %endif | |
|
102 | 106 | %endfor |
|
103 | 107 | </ul> |
|
104 | 108 | </li> |
|
105 | 109 | |
|
106 | 110 | <li ${is_current('summary')}> |
|
107 | 111 | <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}"> |
|
108 | 112 | <span class="icon"> |
|
109 | 113 | <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" /> |
|
110 | 114 | </span> |
|
111 | 115 | <span>${_('Summary')}</span> |
|
112 | 116 | </a> |
|
113 | 117 | </li> |
|
114 | 118 | <li ${is_current('shortlog')}> |
|
115 | 119 | <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}"> |
|
116 | 120 | <span class="icon"> |
|
117 | 121 | <img src="/images/icons/application_double.png" alt="${_('Shortlog')}" /> |
|
118 | 122 | </span> |
|
119 | 123 | <span>${_('Shortlog')}</span> |
|
120 | 124 | </a> |
|
121 | 125 | </li> |
|
122 | 126 | <li ${is_current('changelog')}> |
|
123 | 127 | <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}"> |
|
124 | 128 | <span class="icon"> |
|
125 | 129 | <img src="/images/icons/time.png" alt="${_('Changelog')}" /> |
|
126 | 130 | </span> |
|
127 | 131 | <span>${_('Changelog')}</span> |
|
128 | 132 | </a> |
|
129 | 133 | </li> |
|
130 | 134 | |
|
131 | 135 | <li ${is_current('switch_to')}> |
|
132 | 136 | <a title="${_('Switch to')}" href="#"> |
|
133 | 137 | <span class="icon"> |
|
134 | 138 | <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" /> |
|
135 | 139 | </span> |
|
136 | 140 | <span>${_('Switch to')}</span> |
|
137 | 141 | </a> |
|
138 | 142 | <ul> |
|
139 | 143 | <li> |
|
140 | 144 | ${h.link_to(_('branches'),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')} |
|
141 | 145 | <ul> |
|
142 | 146 | %for cnt,branch in enumerate(c.repository_branches.items()): |
|
143 | 147 | <li>${h.link_to('%s - %s' % (branch[0],branch[1]),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li> |
|
144 | 148 | %endfor |
|
145 | 149 | </ul> |
|
146 | 150 | </li> |
|
147 | 151 | <li> |
|
148 | 152 | ${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')} |
|
149 | 153 | <ul> |
|
150 | 154 | %for cnt,tag in enumerate(c.repository_tags.items()): |
|
151 | 155 | <li>${h.link_to('%s - %s' % (tag[0],tag[1]),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li> |
|
152 | 156 | %endfor |
|
153 | 157 | </ul> |
|
154 | 158 | </li> |
|
155 | 159 | </ul> |
|
156 | 160 | </li> |
|
157 | 161 | <li ${is_current('files')}> |
|
158 | 162 | <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}"> |
|
159 | 163 | <span class="icon"> |
|
160 | 164 | <img src="/images/icons/file.png" alt="${_('Files')}" /> |
|
161 | 165 | </span> |
|
162 | 166 | <span>${_('Files')}</span> |
|
163 | 167 | </a> |
|
164 | 168 | </li> |
|
165 | 169 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): |
|
166 | 170 | <li ${is_current('settings')}> |
|
167 | 171 | <a title="${_('Settings')}" href="${h.url('repo_settings_home',repo_name=c.repo_name)}"> |
|
168 | 172 | <span class="icon"> |
|
169 | 173 | <img src="/images/icons/cog_edit.png" alt="${_('Settings')}" /> |
|
170 | 174 | </span> |
|
171 | 175 | <span>${_('Settings')}</span> |
|
172 | 176 | </a> |
|
173 | 177 | </li> |
|
174 | 178 | %endif |
|
175 | 179 | </ul> |
|
176 | 180 | %else: |
|
177 | 181 | ##ROOT MENU |
|
178 | 182 | <ul id="quick"> |
|
179 | 183 | <li> |
|
180 | 184 | <a title="${_('Home')}" href="${h.url('hg_home')}"> |
|
181 | 185 | <span class="icon"> |
|
182 | 186 | <img src="/images/icons/home_16.png" alt="${_('Home')}" /> |
|
183 | 187 | </span> |
|
184 | 188 | <span>${_('Home')}</span> |
|
185 | 189 | </a> |
|
186 | 190 | </li> |
|
187 | 191 | |
|
188 | 192 | <li> |
|
189 | 193 | <a title="${_('Search')}" href="${h.url('search')}"> |
|
190 | 194 | <span class="icon"> |
|
191 | 195 | <img src="/images/icons/search_16.png" alt="${_('Search')}" /> |
|
192 | 196 | </span> |
|
193 | 197 | <span>${_('Search')}</span> |
|
194 | 198 | </a> |
|
195 | 199 | </li> |
|
196 | 200 | |
|
197 | 201 | %if h.HasPermissionAll('hg.admin')('access admin main page'): |
|
198 | 202 | <li ${is_current('admin')}> |
|
199 | 203 | <a title="${_('Admin')}" href="${h.url('admin_home')}"> |
|
200 | 204 | <span class="icon"> |
|
201 | 205 | <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" /> |
|
202 | 206 | </span> |
|
203 | 207 | <span>${_('Admin')}</span> |
|
204 | 208 | </a> |
|
205 | 209 | <ul> |
|
210 | <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li> | |
|
206 | 211 | <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li> |
|
207 | 212 | <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li> |
|
208 | 213 | <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li> |
|
209 | 214 | <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li> |
|
210 | 215 | </ul> |
|
211 | 216 | </li> |
|
212 | 217 | %endif |
|
213 | 218 | |
|
214 | 219 | </ul> |
|
215 | 220 | %endif |
|
216 | 221 | </%def> |
|
217 | 222 | |
|
218 | 223 | |
|
219 | 224 | <%def name="css()"> |
|
220 | 225 | <link rel="stylesheet" type="text/css" href="/css/reset.css" /> |
|
221 | 226 | <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" /> |
|
222 | 227 | <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" /> |
|
223 | 228 | <link rel="stylesheet" type="text/css" href="/css/pygments.css" /> |
|
224 | 229 | <link rel="stylesheet" type="text/css" href="/css/diff.css" /> |
|
225 | 230 | </%def> |
|
226 | 231 | |
|
227 | 232 | <%def name="js()"> |
|
228 | 233 | ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script> |
|
229 | 234 | ##<script type="text/javascript" src="/js/yui/container/container.js"></script> |
|
230 | 235 | ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script> |
|
231 | 236 | ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script> |
|
232 | 237 | |
|
233 | 238 | <script type="text/javascript" src="/js/yui2.js"></script> |
|
234 | 239 | <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]--> |
|
235 | 240 | <script type="text/javascript" src="/js/yui.flot.js"></script> |
|
236 | 241 | </%def> |
|
237 | 242 | |
|
238 | 243 | <%def name="breadcrumbs()"> |
|
239 | 244 | <div class="breadcrumbs"> |
|
240 | 245 | ${self.breadcrumbs_links()} |
|
241 | 246 | </div> |
|
242 | 247 | </%def> No newline at end of file |
@@ -1,52 +1,61 b'' | |||
|
1 | 1 | <%inherit file="/base/base.html"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="title()"> |
|
4 | 4 | ${_('File annotate')} |
|
5 | 5 | </%def> |
|
6 | 6 | |
|
7 | 7 | <%def name="breadcrumbs_links()"> |
|
8 | 8 | ${h.link_to(u'Home',h.url('/'))} |
|
9 | 9 | » |
|
10 | 10 | ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} |
|
11 | 11 | » |
|
12 | 12 | ${_('annotate')} @ R${c.rev_nr}:${c.cur_rev} |
|
13 | 13 | </%def> |
|
14 | 14 | |
|
15 | 15 | <%def name="page_nav()"> |
|
16 | 16 | ${self.menu('files')} |
|
17 | 17 | </%def> |
|
18 | 18 | <%def name="main()"> |
|
19 | 19 | <div class="box"> |
|
20 | 20 | <!-- box / title --> |
|
21 | 21 | <div class="title"> |
|
22 | 22 | ${self.breadcrumbs()} |
|
23 | 23 | </div> |
|
24 | 24 | <div class="table"> |
|
25 | 25 | <div id="files_data"> |
|
26 |
<h |
|
|
26 | <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cur_rev,c.file.path)}</h3> | |
|
27 | 27 | <dl class="overview"> |
|
28 | 28 | <dt>${_('Last revision')}</dt> |
|
29 | 29 | <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,c.file.last_changeset._short), |
|
30 | 30 | h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset._short,f_path=c.f_path))} </dd> |
|
31 | 31 | <dt>${_('Size')}</dt> |
|
32 | 32 | <dd>${h.format_byte_size(c.file.size,binary=True)}</dd> |
|
33 | <dt>${_('Mimetype')}</dt> | |
|
34 | <dd>${c.file.mimetype}</dd> | |
|
33 | 35 | <dt>${_('Options')}</dt> |
|
34 | 36 | <dd>${h.link_to(_('show source'), |
|
35 | 37 | h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} |
|
38 | / ${h.link_to(_('show as raw'), | |
|
39 | h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
36 | 40 | / ${h.link_to(_('download as raw'), |
|
37 | h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
41 | h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
38 | 42 | </dd> |
|
39 | 43 | </dl> |
|
40 | 44 | <div id="body" class="codeblock"> |
|
41 | 45 | <div class="code-header"> |
|
42 | 46 | <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${c.file.last_changeset._short}</div> |
|
43 | 47 | <div class="commit">"${c.file_msg}"</div> |
|
44 | 48 | </div> |
|
45 | 49 | <div class="code-body"> |
|
46 | ${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")} | |
|
50 | % if c.file.size < c.file_size_limit: | |
|
51 | ${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")} | |
|
52 | %else: | |
|
53 | ${_('File is to big to display')} ${h.link_to(_('show as raw'), | |
|
54 | h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
55 | %endif | |
|
47 | 56 | </div> |
|
48 | 57 | </div> |
|
49 | 58 | </div> |
|
50 | 59 | </div> |
|
51 | 60 | </div> |
|
52 | 61 | </%def> No newline at end of file |
@@ -1,71 +1,78 b'' | |||
|
1 | 1 | <%def name="file_class(node)"> |
|
2 | 2 | %if node.is_file(): |
|
3 | 3 | <%return "browser-file" %> |
|
4 | 4 | %else: |
|
5 | 5 | <%return "browser-dir"%> |
|
6 | 6 | %endif |
|
7 | 7 | </%def> |
|
8 | 8 | <div id="body" class="browserblock"> |
|
9 | 9 | <div class="browser-header"> |
|
10 | 10 | ${h.form(h.url.current())} |
|
11 | 11 | <div class="info_box"> |
|
12 | 12 | <span >${_('view')}@rev</span> |
|
13 | 13 | <a href="${c.url_prev}">«</a> |
|
14 | 14 | ${h.text('at_rev',value=c.rev_nr,size=3)} |
|
15 | 15 | <a href="${c.url_next}">»</a> |
|
16 | 16 | ${h.submit('view','view')} |
|
17 | 17 | </div> |
|
18 | 18 | ${h.end_form()} |
|
19 | 19 | </div> |
|
20 | 20 | <div class="browser-body"> |
|
21 | 21 | <table class="code-browser"> |
|
22 | 22 | <thead> |
|
23 | 23 | <tr> |
|
24 | 24 | <th>${_('Name')}</th> |
|
25 | 25 | <th>${_('Size')}</th> |
|
26 | <th>${_('Mimetype')}</th> | |
|
26 | 27 | <th>${_('Revision')}</th> |
|
27 | 28 | <th>${_('Last modified')}</th> |
|
28 | 29 | <th>${_('Last commiter')}</th> |
|
29 | 30 | </tr> |
|
30 | 31 | </thead> |
|
31 | <tr class="parity0"> | |
|
32 | <td> | |
|
33 | % if c.files_list.parent: | |
|
34 | ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.files_list.parent.path),class_="browser-dir")} | |
|
35 | %endif | |
|
36 |
|
|
|
37 |
|
|
|
38 |
|
|
|
39 |
|
|
|
40 |
|
|
|
41 |
|
|
|
32 | ||
|
33 | % if c.files_list.parent: | |
|
34 | <tr class="parity0"> | |
|
35 | <td> | |
|
36 | ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.files_list.parent.path),class_="browser-dir")} | |
|
37 | </td> | |
|
38 | <td></td> | |
|
39 | <td></td> | |
|
40 | <td></td> | |
|
41 | <td></td> | |
|
42 | <td></td> | |
|
43 | </tr> | |
|
44 | %endif | |
|
45 | ||
|
42 | 46 | %for cnt,node in enumerate(c.files_list,1): |
|
43 | 47 | <tr class="parity${cnt%2}"> |
|
44 | 48 | <td> |
|
45 | 49 | ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=node.path),class_=file_class(node))} |
|
46 | 50 | </td> |
|
47 | 51 | <td> |
|
48 | %if node.is_file(): | |
|
49 | ${h.format_byte_size(node.size,binary=True)} | |
|
50 |
|
|
|
52 | ${h.format_byte_size(node.size,binary=True)} | |
|
53 | </td> | |
|
54 | <td> | |
|
55 | %if node.is_file(): | |
|
56 | ${node.mimetype} | |
|
57 | %endif | |
|
51 | 58 | </td> |
|
52 | 59 | <td> |
|
53 | 60 | %if node.is_file(): |
|
54 | 61 | ${node.last_changeset.revision} |
|
55 | 62 | %endif |
|
56 | 63 | </td> |
|
57 | 64 | <td> |
|
58 | 65 | %if node.is_file(): |
|
59 | 66 | ${h.age(node.last_changeset._ctx.date())} - ${node.last_changeset.date} |
|
60 | 67 | %endif |
|
61 | 68 | </td> |
|
62 | 69 | <td> |
|
63 | 70 | %if node.is_file(): |
|
64 | 71 | ${node.last_changeset.author} |
|
65 | 72 | %endif |
|
66 | 73 | </td> |
|
67 | 74 | </tr> |
|
68 | 75 | %endfor |
|
69 | 76 | </table> |
|
70 | 77 | </div> |
|
71 | 78 | </div> No newline at end of file |
@@ -1,48 +1,57 b'' | |||
|
1 | 1 | <dl> |
|
2 | 2 | <dt>${_('Last revision')}</dt> |
|
3 | 3 | <dd> |
|
4 | 4 | ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,c.files_list.last_changeset._short), |
|
5 | 5 | h.url('files_home',repo_name=c.repo_name,revision=c.files_list.last_changeset._short,f_path=c.f_path))} |
|
6 | 6 | </dd> |
|
7 | 7 | <dt>${_('Size')}</dt> |
|
8 | 8 | <dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd> |
|
9 | <dt>${_('Mimetype')}</dt> | |
|
10 | <dd>${c.files_list.mimetype}</dd> | |
|
9 | 11 | <dt>${_('Options')}</dt> |
|
10 | 12 | <dd>${h.link_to(_('show annotation'), |
|
11 |
h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} |
|
|
13 | h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
14 | / ${h.link_to(_('show as raw'), | |
|
15 | h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
12 | 16 | / ${h.link_to(_('download as raw'), |
|
13 | h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
17 | h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
14 | 18 | </dd> |
|
15 | 19 | <dt>${_('History')}</dt> |
|
16 | 20 | <dd> |
|
17 | 21 | <div> |
|
18 | 22 | ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} |
|
19 | 23 | ${h.hidden('diff2',c.files_list.last_changeset._short)} |
|
20 | 24 | ${h.select('diff1',c.files_list.last_changeset._short,c.file_history)} |
|
21 | 25 | ${h.submit('diff','diff to revision',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
22 | 26 | ${h.submit('show_rev','show at revision',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
23 | 27 | ${h.end_form()} |
|
24 | 28 | </div> |
|
25 | 29 | </dd> |
|
26 | 30 | </dl> |
|
27 | 31 | |
|
28 | 32 | |
|
29 | 33 | <div id="body" class="codeblock"> |
|
30 | 34 | <div class="code-header"> |
|
31 | 35 | <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${c.files_list.last_changeset._short}</div> |
|
32 | 36 | <div class="commit">"${c.files_list.last_changeset.message}"</div> |
|
33 | 37 | </div> |
|
34 | 38 | <div class="code-body"> |
|
35 | ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")} | |
|
39 | % if c.files_list.size < c.file_size_limit: | |
|
40 | ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")} | |
|
41 | %else: | |
|
42 | ${_('File is to big to display')} ${h.link_to(_('show as raw'), | |
|
43 | h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} | |
|
44 | %endif | |
|
36 | 45 | </div> |
|
37 | 46 | </div> |
|
38 | 47 | |
|
39 | 48 | <script type="text/javascript"> |
|
40 | 49 | YAHOO.util.Event.onDOMReady(function(){ |
|
41 | 50 | YAHOO.util.Event.addListener('show_rev','click',function(e){ |
|
42 | 51 | YAHOO.util.Event.preventDefault(e); |
|
43 | 52 | var cs = YAHOO.util.Dom.get('diff1').value; |
|
44 | 53 | var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs); |
|
45 | 54 | window.location = url; |
|
46 | 55 | }); |
|
47 | 56 | }); |
|
48 | 57 | </script> No newline at end of file |
@@ -1,78 +1,78 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
|
3 | 3 | <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml"> |
|
4 | 4 | <head> |
|
5 | 5 | <title>${_('Sign In to hg-app')}</title> |
|
6 | 6 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
|
7 | 7 | <link rel="icon" href="/images/hgicon.png" type="image/png" /> |
|
8 | 8 | <meta name="robots" content="index, nofollow"/> |
|
9 | 9 | |
|
10 | 10 | <!-- stylesheets --> |
|
11 | 11 | <link rel="stylesheet" type="text/css" href="/css/reset.css" /> |
|
12 | 12 | <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" /> |
|
13 | 13 | <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" /> |
|
14 | 14 | |
|
15 | 15 | <!-- scripts --> |
|
16 | 16 | |
|
17 | 17 | </head> |
|
18 | 18 | <body> |
|
19 | 19 | <div id="login"> |
|
20 | 20 | <!-- login --> |
|
21 | 21 | <div class="title"> |
|
22 | 22 | <h5>${_('Sign In to hg-app')}</h5> |
|
23 | 23 | <div class="corner tl"></div> |
|
24 | 24 | <div class="corner tr"></div> |
|
25 | 25 | </div> |
|
26 | 26 | <div class="inner"> |
|
27 | 27 | ${h.form(h.url.current(came_from=c.came_from))} |
|
28 | 28 | <div class="form"> |
|
29 | 29 | <!-- fields --> |
|
30 | 30 | |
|
31 | 31 | <div class="fields"> |
|
32 | 32 | <div class="field"> |
|
33 | 33 | <div class="label"> |
|
34 | 34 | <label for="username">${_('Username')}:</label> |
|
35 | 35 | </div> |
|
36 | 36 | <div class="input"> |
|
37 | 37 | ${h.text('username',class_='focus',size=40)} |
|
38 | 38 | </div> |
|
39 | 39 | |
|
40 | 40 | </div> |
|
41 | 41 | <div class="field"> |
|
42 | 42 | <div class="label"> |
|
43 | 43 | <label for="password">${_('Password')}:</label> |
|
44 | 44 | </div> |
|
45 | 45 | <div class="input"> |
|
46 | 46 | ${h.password('password',class_='focus',size=40)} |
|
47 | 47 | </div> |
|
48 | 48 | |
|
49 | 49 | </div> |
|
50 | 50 | ##<div class="field"> |
|
51 | 51 | ## <div class="checkbox"> |
|
52 | 52 | ## <input type="checkbox" id="remember" name="remember" /> |
|
53 | 53 | ## <label for="remember">Remember me</label> |
|
54 | 54 | ## </div> |
|
55 | 55 | ##</div> |
|
56 | 56 | <div class="buttons"> |
|
57 | 57 | ${h.submit('sign_in','Sign In',class_="ui-button ui-widget ui-state-default ui-corner-all")} |
|
58 | 58 | </div> |
|
59 | 59 | </div> |
|
60 | 60 | <!-- end fields --> |
|
61 | 61 | <!-- links --> |
|
62 | 62 | <div class="links"> |
|
63 |
${h.link_to(_('Forgot your password ?'),h.url(' |
|
|
63 | ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))} | |
|
64 | 64 | %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')(): |
|
65 | 65 | / |
|
66 | 66 | ${h.link_to(_("Don't have an account ?"),h.url('register'))} |
|
67 | 67 | %endif |
|
68 | 68 | </div> |
|
69 | 69 | |
|
70 | 70 | <!-- end links --> |
|
71 | 71 | </div> |
|
72 | 72 | ${h.end_form()} |
|
73 | 73 | </div> |
|
74 | 74 | <!-- end login --> |
|
75 | 75 | </div> |
|
76 | 76 | </body> |
|
77 | 77 | </html> |
|
78 | 78 |
@@ -1,69 +1,71 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.html"/> |
|
3 | 3 | <%def name="title()"> |
|
4 | 4 | ${_('Search')}: ${c.cur_query} |
|
5 | 5 | </%def> |
|
6 | 6 | <%def name="breadcrumbs()"> |
|
7 | 7 | ${c.hg_app_name} |
|
8 | 8 | </%def> |
|
9 | 9 | <%def name="page_nav()"> |
|
10 | 10 | ${self.menu('home')} |
|
11 | 11 | </%def> |
|
12 | 12 | <%def name="main()"> |
|
13 | 13 | |
|
14 | 14 | <div class="box"> |
|
15 | 15 | <!-- box / title --> |
|
16 | 16 | <div class="title"> |
|
17 | 17 | <h5>${_('Search')}</h5> |
|
18 | 18 | </div> |
|
19 | 19 | <!-- end box / title --> |
|
20 | 20 | ${h.form('search',method='get')} |
|
21 | 21 | <div class="form"> |
|
22 | 22 | <div class="fields"> |
|
23 | 23 | |
|
24 | 24 | <div class="field "> |
|
25 | 25 | <div class="label"> |
|
26 | 26 | <label for="q">${_('Search:')}</label> |
|
27 | 27 | </div> |
|
28 | 28 | <div class="input"> |
|
29 | 29 | ${h.text('q',c.cur_query,class_="small")} |
|
30 | 30 | <div class="button highlight"> |
|
31 | 31 | <input type="submit" value="${_('Search')}" class="ui-button ui-widget ui-state-default ui-corner-all"/> |
|
32 | 32 | </div> |
|
33 | 33 | <div style="font-weight: bold;clear:both;padding: 5px">${c.runtime}</div> |
|
34 | 34 | </div> |
|
35 | 35 | </div> |
|
36 | 36 | </div> |
|
37 | 37 | </div> |
|
38 | 38 | ${h.end_form()} |
|
39 | 39 | |
|
40 | 40 | %for cnt,sr in enumerate(c.formated_results): |
|
41 | 41 | %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'): |
|
42 | 42 | <div class="table"> |
|
43 | 43 | <div id="body${cnt}" class="codeblock"> |
|
44 | 44 | <div class="code-header"> |
|
45 | 45 | <div class="revision">${h.link_to(h.literal('%s » %s' % (sr['repository'],sr['f_path'])), |
|
46 | 46 | h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}</div> |
|
47 | 47 | </div> |
|
48 | 48 | <div class="code-body"> |
|
49 | <pre>${h.literal(sr['content_short'])}</pre> | |
|
49 | <pre>${h.literal(sr['content_short_hl'])}</pre> | |
|
50 | 50 | </div> |
|
51 | 51 | </div> |
|
52 | 52 | </div> |
|
53 | 53 | %else: |
|
54 | 54 | %if cnt == 0: |
|
55 | 55 | <div class="table"> |
|
56 | 56 | <div id="body${cnt}" class="codeblock"> |
|
57 | 57 | <div class="error">${_('Permission denied')}</div> |
|
58 | 58 | </div> |
|
59 | 59 | </div> |
|
60 | 60 | %endif |
|
61 | 61 | |
|
62 | %endif | |
|
62 | %endif | |
|
63 | 63 | %endfor |
|
64 | ||
|
65 | ||
|
66 | ||
|
64 | %if c.cur_query: | |
|
65 | <div class="pagination-wh pagination-left"> | |
|
66 | ${c.formated_results.pager('$link_previous ~2~ $link_next')} | |
|
67 | </div> | |
|
68 | %endif | |
|
67 | 69 | </div> |
|
68 | 70 | |
|
69 | 71 | </%def> |
@@ -1,63 +1,63 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | % if c.repo_changesets: |
|
3 | 3 | <table> |
|
4 | 4 | <tr> |
|
5 | 5 | <th class="left">${_('date')}</th> |
|
6 | 6 | <th class="left">${_('author')}</th> |
|
7 | 7 | <th class="left">${_('revision')}</th> |
|
8 | 8 | <th class="left">${_('commit message')}</th> |
|
9 | 9 | <th class="left">${_('branch')}</th> |
|
10 | 10 | <th class="left">${_('tags')}</th> |
|
11 | 11 | <th class="left">${_('links')}</th> |
|
12 | 12 | |
|
13 | 13 | </tr> |
|
14 | 14 | %for cnt,cs in enumerate(c.repo_changesets): |
|
15 | 15 | <tr class="parity${cnt%2}"> |
|
16 | <td>${h.age(cs._ctx.date())}</td> | |
|
16 | <td>${h.age(cs._ctx.date())} - ${h.rfc822date_notz(cs._ctx.date())} </td> | |
|
17 | 17 | <td title="${cs.author}">${h.person(cs.author)}</td> |
|
18 | 18 | <td>r${cs.revision}:${cs.raw_id}</td> |
|
19 | 19 | <td> |
|
20 | 20 | ${h.link_to(h.truncate(cs.message,60), |
|
21 | 21 | h.url('changeset_home',repo_name=c.repo_name,revision=cs._short), |
|
22 | 22 | title=cs.message)} |
|
23 | 23 | </td> |
|
24 | 24 | <td> |
|
25 | 25 | <span class="logtags"> |
|
26 | 26 | <span class="branchtag">${cs.branch}</span> |
|
27 | 27 | </span> |
|
28 | 28 | </td> |
|
29 | 29 | <td> |
|
30 | 30 | <span class="logtags"> |
|
31 | 31 | %for tag in cs.tags: |
|
32 | 32 | <span class="tagtag">${tag}</span> |
|
33 | 33 | %endfor |
|
34 | 34 | </span> |
|
35 | 35 | </td> |
|
36 | 36 | <td class="nowrap"> |
|
37 | 37 | ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))} |
|
38 | 38 | | |
|
39 | 39 | ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} |
|
40 | 40 | </td> |
|
41 | 41 | </tr> |
|
42 | 42 | %endfor |
|
43 | 43 | |
|
44 | 44 | </table> |
|
45 | 45 | |
|
46 | 46 | <script type="text/javascript"> |
|
47 | 47 | var data_div = 'shortlog_data'; |
|
48 | 48 | YAHOO.util.Event.onDOMReady(function(){ |
|
49 | 49 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ |
|
50 | 50 | YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});}); |
|
51 | 51 | </script> |
|
52 | 52 | |
|
53 | 53 | <div class="pagination-wh pagination-left"> |
|
54 | 54 | ${c.repo_changesets.pager('$link_previous ~2~ $link_next', |
|
55 | 55 | onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{ |
|
56 | 56 | success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText; |
|
57 | 57 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ |
|
58 | 58 | YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');}); |
|
59 | 59 | YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")} |
|
60 | 60 | </div> |
|
61 | 61 | %else: |
|
62 | 62 | ${_('There are no commits yet')} |
|
63 | 63 | %endif |
@@ -1,301 +1,508 b'' | |||
|
1 | 1 | <%inherit file="/base/base.html"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="title()"> |
|
4 | 4 | ${_('Mercurial Repository Overview')} |
|
5 | 5 | </%def> |
|
6 | 6 | |
|
7 | 7 | <%def name="breadcrumbs_links()"> |
|
8 | 8 | ${h.link_to(u'Home',h.url('/'))} |
|
9 | 9 | » |
|
10 | 10 | ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} |
|
11 | 11 | » |
|
12 | 12 | ${_('summary')} |
|
13 | 13 | </%def> |
|
14 | 14 | |
|
15 | 15 | <%def name="page_nav()"> |
|
16 | 16 | ${self.menu('summary')} |
|
17 | 17 | </%def> |
|
18 | 18 | |
|
19 | 19 | <%def name="main()"> |
|
20 | 20 | <script type="text/javascript"> |
|
21 | 21 | var E = YAHOO.util.Event; |
|
22 | 22 | var D = YAHOO.util.Dom; |
|
23 | 23 | |
|
24 | 24 | E.onDOMReady(function(e){ |
|
25 | 25 | id = 'clone_url'; |
|
26 | 26 | E.addListener(id,'click',function(e){ |
|
27 | 27 | D.get('clone_url').select(); |
|
28 | 28 | }) |
|
29 | 29 | }) |
|
30 | 30 | </script> |
|
31 | 31 | <div class="box box-left"> |
|
32 | 32 | <!-- box / title --> |
|
33 | 33 | <div class="title"> |
|
34 | 34 | ${self.breadcrumbs()} |
|
35 | 35 | </div> |
|
36 | 36 | <!-- end box / title --> |
|
37 | 37 | <div class="form"> |
|
38 | 38 | <div class="fields"> |
|
39 | 39 | |
|
40 | 40 | <div class="field"> |
|
41 | 41 | <div class="label"> |
|
42 | 42 | <label>${_('Name')}:</label> |
|
43 | 43 | </div> |
|
44 | 44 | <div class="input-short"> |
|
45 | 45 | <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span> |
|
46 | 46 | </div> |
|
47 | 47 | </div> |
|
48 | 48 | |
|
49 | 49 | |
|
50 | 50 | <div class="field"> |
|
51 | 51 | <div class="label"> |
|
52 | 52 | <label>${_('Description')}:</label> |
|
53 | 53 | </div> |
|
54 | 54 | <div class="input-short"> |
|
55 | 55 | ${c.repo_info.description} |
|
56 | 56 | </div> |
|
57 | 57 | </div> |
|
58 | 58 | |
|
59 | 59 | |
|
60 | 60 | <div class="field"> |
|
61 | 61 | <div class="label"> |
|
62 | 62 | <label>${_('Contact')}:</label> |
|
63 | 63 | </div> |
|
64 | 64 | <div class="input-short"> |
|
65 | 65 | <div class="gravatar"> |
|
66 | 66 | <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/> |
|
67 | 67 | </div> |
|
68 | 68 | ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/> |
|
69 | 69 | ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/> |
|
70 | 70 | ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a> |
|
71 | 71 | </div> |
|
72 | 72 | </div> |
|
73 | 73 | |
|
74 | 74 | <div class="field"> |
|
75 | 75 | <div class="label"> |
|
76 | 76 | <label>${_('Last change')}:</label> |
|
77 | 77 | </div> |
|
78 | 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 | ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author} | |
|
81 | ||
|
80 | 82 | </div> |
|
81 | 83 | </div> |
|
82 | 84 | |
|
83 | 85 | <div class="field"> |
|
84 | 86 | <div class="label"> |
|
85 | 87 | <label>${_('Clone url')}:</label> |
|
86 | 88 | </div> |
|
87 | 89 | <div class="input-short"> |
|
88 | 90 | <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/> |
|
89 | 91 | </div> |
|
90 | 92 | </div> |
|
91 | 93 | |
|
92 | 94 | <div class="field"> |
|
93 | 95 | <div class="label"> |
|
94 | 96 | <label>${_('Download')}:</label> |
|
95 | 97 | </div> |
|
96 | 98 | <div class="input-short"> |
|
97 | 99 | %for cnt,archive in enumerate(c.repo_info._get_archives()): |
|
98 | 100 | %if cnt >=1: |
|
99 | 101 | | |
|
100 | 102 | %endif |
|
101 | 103 | ${h.link_to(c.repo_info.name+'.'+archive['type'], |
|
102 | 104 | h.url('files_archive_home',repo_name=c.repo_info.name, |
|
103 | 105 | revision='tip',fileformat=archive['extension']),class_="archive_icon")} |
|
104 | 106 | %endfor |
|
105 | 107 | </div> |
|
106 | 108 | </div> |
|
107 | 109 | |
|
108 | 110 | <div class="field"> |
|
109 | 111 | <div class="label"> |
|
110 | 112 | <label>${_('Feeds')}:</label> |
|
111 | 113 | </div> |
|
112 | 114 | <div class="input-short"> |
|
113 | 115 | ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')} |
|
114 | 116 | ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')} |
|
115 | 117 | </div> |
|
116 | 118 | </div> |
|
117 | 119 | </div> |
|
118 | 120 | </div> |
|
119 | 121 | </div> |
|
120 | 122 | |
|
121 | 123 | <div class="box box-right" style="min-height:455px"> |
|
122 | 124 | <!-- box / title --> |
|
123 | 125 | <div class="title"> |
|
124 |
<h5>${_(' |
|
|
126 | <h5>${_('Commit activity by day / author')}</h5> | |
|
125 | 127 | </div> |
|
126 | 128 | |
|
127 | 129 | <div class="table"> |
|
128 | 130 | <div id="commit_history" style="width:560px;height:300px;float:left"></div> |
|
129 | <div id="legend_data"> | |
|
131 | <div style="clear: both;height: 10px"></div> | |
|
132 | <div id="overview" style="width:560px;height:100px;float:left"></div> | |
|
133 | ||
|
134 | <div id="legend_data" style="clear:both;margin-top:10px;"> | |
|
130 | 135 | <div id="legend_container"></div> |
|
131 | 136 | <div id="legend_choices"> |
|
132 | 137 | <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table> |
|
133 | 138 | </div> |
|
134 | 139 | </div> |
|
135 | 140 | <script type="text/javascript"> |
|
136 | ||
|
137 | (function () { | |
|
138 | var datasets = {${c.commit_data|n}}; | |
|
139 | var i = 0; | |
|
141 | /** | |
|
142 | * Plots summary graph | |
|
143 | * | |
|
144 | * @class SummaryPlot | |
|
145 | * @param {from} initial from for detailed graph | |
|
146 | * @param {to} initial to for detailed graph | |
|
147 | * @param {dataset} | |
|
148 | * @param {overview_dataset} | |
|
149 | */ | |
|
150 | function SummaryPlot(from,to,dataset,overview_dataset) { | |
|
151 | var initial_ranges = { | |
|
152 | "xaxis":{ | |
|
153 | "from":from, | |
|
154 | "to":to, | |
|
155 | }, | |
|
156 | }; | |
|
157 | var dataset = dataset; | |
|
158 | var overview_dataset = [overview_dataset]; | |
|
140 | 159 | var choiceContainer = YAHOO.util.Dom.get("legend_choices"); |
|
141 | 160 | var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables"); |
|
142 | for(var key in datasets) { | |
|
143 | datasets[key].color = i; | |
|
144 | i++; | |
|
145 | choiceContainerTable.innerHTML += '<tr><td>'+ | |
|
146 | '<input type="checkbox" name="' + key +'" checked="checked" />' | |
|
147 | +datasets[key].label+ | |
|
148 | '</td></tr>'; | |
|
149 | }; | |
|
161 | var plotContainer = YAHOO.util.Dom.get('commit_history'); | |
|
162 | var overviewContainer = YAHOO.util.Dom.get('overview'); | |
|
150 | 163 | |
|
151 | ||
|
152 | function plotAccordingToChoices() { | |
|
153 | var data = []; | |
|
154 | ||
|
155 | var inputs = choiceContainer.getElementsByTagName("input"); | |
|
156 | for(var i=0; i<inputs.length; i++) { | |
|
157 | var key = inputs[i].name; | |
|
158 | if (key && datasets[key]){ | |
|
159 | if(!inputs[i].checked){ | |
|
160 | data.push({label:key,data:[[0,1],]}); | |
|
161 | } | |
|
162 | else{ | |
|
163 | data.push(datasets[key]); | |
|
164 | } | |
|
165 | ||
|
166 | } | |
|
167 | ||
|
168 | }; | |
|
169 | ||
|
170 | if (data.length > 0){ | |
|
164 | var plot_options = { | |
|
165 | bars: {show:true,align:'center',lineWidth:4}, | |
|
166 | legend: {show:true, container:"legend_container"}, | |
|
167 | points: {show:true,radius:0,fill:false}, | |
|
168 | yaxis: {tickDecimals:0,}, | |
|
169 | xaxis: { | |
|
170 | mode: "time", | |
|
171 | timeformat: "%d/%m", | |
|
172 | min:from, | |
|
173 | max:to, | |
|
174 | }, | |
|
175 | grid: { | |
|
176 | hoverable: true, | |
|
177 | clickable: true, | |
|
178 | autoHighlight:true, | |
|
179 | color: "#999" | |
|
180 | }, | |
|
181 | //selection: {mode: "x"} | |
|
182 | }; | |
|
183 | var overview_options = { | |
|
184 | legend:{show:false}, | |
|
185 | bars: {show:true,barWidth: 2,}, | |
|
186 | shadowSize: 0, | |
|
187 | xaxis: {mode: "time", timeformat: "%d/%m/%y",}, | |
|
188 | yaxis: {ticks: 3, min: 0,}, | |
|
189 | grid: {color: "#999",}, | |
|
190 | selection: {mode: "x"} | |
|
191 | }; | |
|
171 | 192 | |
|
172 | var plot = YAHOO.widget.Flot("commit_history", data, | |
|
173 | { bars: { show: true, align:'center',lineWidth:4 }, | |
|
174 | points: { show: true, radius:0,fill:true }, | |
|
175 | legend:{show:true, container:"legend_container"}, | |
|
176 | selection: { mode: "xy" }, | |
|
177 | yaxis: {tickDecimals:0}, | |
|
178 | xaxis: { mode: "time", timeformat: "%d",tickSize:[1, "day"],min:${c.ts_min},max:${c.ts_max} }, | |
|
179 | grid: { hoverable: true, clickable: true,autoHighlight:true }, | |
|
180 | }); | |
|
181 | ||
|
182 | function showTooltip(x, y, contents) { | |
|
183 | var div=document.getElementById('tooltip'); | |
|
184 | if(!div) { | |
|
185 | div = document.createElement('div'); | |
|
186 | div.id="tooltip"; | |
|
187 | div.style.position="absolute"; | |
|
188 | div.style.border='1px solid #fdd'; | |
|
189 | div.style.padding='2px'; | |
|
190 | div.style.backgroundColor='#fee'; | |
|
191 | document.body.appendChild(div); | |
|
192 |
|
|
|
193 | YAHOO.util.Dom.setStyle(div, 'opacity', 0); | |
|
194 | div.innerHTML = contents; | |
|
195 | div.style.top=(y + 5) + "px"; | |
|
196 | div.style.left=(x + 5) + "px"; | |
|
197 | ||
|
198 | var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2); | |
|
199 | anim.animate(); | |
|
193 | /** | |
|
194 | *get dummy data needed in few places | |
|
195 | */ | |
|
196 | function getDummyData(label){ | |
|
197 | return {"label":label, | |
|
198 | "data":[{"time":0, | |
|
199 | "commits":0, | |
|
200 | "added":0, | |
|
201 | "changed":0, | |
|
202 | "removed":0, | |
|
203 | }], | |
|
204 | "schema":["commits"], | |
|
205 | "color":'#ffffff', | |
|
206 | } | |
|
207 | } | |
|
208 | ||
|
209 | /** | |
|
210 | * generate checkboxes accordindly to data | |
|
211 | * @param keys | |
|
212 | * @returns | |
|
213 | */ | |
|
214 | function generateCheckboxes(data) { | |
|
215 | //append checkboxes | |
|
216 | var i = 0; | |
|
217 | choiceContainerTable.innerHTML = ''; | |
|
218 | for(var pos in data) { | |
|
219 | ||
|
220 | data[pos].color = i; | |
|
221 | i++; | |
|
222 | if(data[pos].label != ''){ | |
|
223 | choiceContainerTable.innerHTML += '<tr><td>'+ | |
|
224 | '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />' | |
|
225 | +data[pos].label+ | |
|
226 | '</td></tr>'; | |
|
227 | } | |
|
228 | } | |
|
229 | } | |
|
230 | ||
|
231 | /** | |
|
232 | * ToolTip show | |
|
233 | */ | |
|
234 | function showTooltip(x, y, contents) { | |
|
235 | var div=document.getElementById('tooltip'); | |
|
236 | if(!div) { | |
|
237 | div = document.createElement('div'); | |
|
238 | div.id="tooltip"; | |
|
239 | div.style.position="absolute"; | |
|
240 | div.style.border='1px solid #fdd'; | |
|
241 | div.style.padding='2px'; | |
|
242 | div.style.backgroundColor='#fee'; | |
|
243 | document.body.appendChild(div); | |
|
244 | } | |
|
245 | YAHOO.util.Dom.setStyle(div, 'opacity', 0); | |
|
246 | div.innerHTML = contents; | |
|
247 | div.style.top=(y + 5) + "px"; | |
|
248 | div.style.left=(x + 5) + "px"; | |
|
249 | ||
|
250 | var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2); | |
|
251 | anim.animate(); | |
|
252 | } | |
|
253 | ||
|
254 | /** | |
|
255 | * This function will detect if selected period has some changesets for this user | |
|
256 | if it does this data is then pushed for displaying | |
|
257 | Additionally it will only display users that are selected by the checkbox | |
|
258 | */ | |
|
259 | function getDataAccordingToRanges(ranges) { | |
|
260 | ||
|
261 | var data = []; | |
|
262 | var keys = []; | |
|
263 | for(var key in dataset){ | |
|
264 | var push = false; | |
|
265 | //method1 slow !! | |
|
266 | ///* | |
|
267 | for(var ds in dataset[key].data){ | |
|
268 | commit_data = dataset[key].data[ds]; | |
|
269 | //console.log(key); | |
|
270 | //console.log(new Date(commit_data.time*1000)); | |
|
271 | //console.log(new Date(ranges.xaxis.from*1000)); | |
|
272 | //console.log(new Date(ranges.xaxis.to*1000)); | |
|
273 | if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){ | |
|
274 | push = true; | |
|
275 | break; | |
|
276 | } | |
|
200 | 277 | } |
|
278 | //*/ | |
|
279 | /*//method2 sorted commit data !!! | |
|
280 | var first_commit = dataset[key].data[0].time; | |
|
281 | var last_commit = dataset[key].data[dataset[key].data.length-1].time; | |
|
282 | ||
|
283 | console.log(first_commit); | |
|
284 | console.log(last_commit); | |
|
285 | ||
|
286 | if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){ | |
|
287 | push = true; | |
|
288 | } | |
|
289 | */ | |
|
290 | if(push){ | |
|
291 | data.push(dataset[key]); | |
|
292 | } | |
|
293 | } | |
|
294 | if(data.length >= 1){ | |
|
295 | return data; | |
|
296 | } | |
|
297 | else{ | |
|
298 | //just return dummy data for graph to plot itself | |
|
299 | return [getDummyData('')]; | |
|
300 | } | |
|
301 | ||
|
302 | } | |
|
303 | ||
|
304 | /** | |
|
305 | * redraw using new checkbox data | |
|
306 | */ | |
|
307 | function plotchoiced(e,args){ | |
|
308 | var cur_data = args[0]; | |
|
309 | var cur_ranges = args[1]; | |
|
310 | ||
|
311 | var new_data = []; | |
|
312 | var inputs = choiceContainer.getElementsByTagName("input"); | |
|
201 | 313 | |
|
202 | var previousPoint = null; | |
|
203 | plot.subscribe("plothover", function (o) { | |
|
204 | var pos = o.pos; | |
|
205 | var item = o.item; | |
|
206 | ||
|
207 | //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2); | |
|
208 | //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2); | |
|
209 | if (item) { | |
|
210 | if (previousPoint != item.datapoint) { | |
|
211 | previousPoint = item.datapoint; | |
|
212 | ||
|
213 | var tooltip = YAHOO.util.Dom.get("tooltip"); | |
|
214 | if(tooltip) { | |
|
215 | tooltip.parentNode.removeChild(tooltip); | |
|
216 | } | |
|
217 | var x = item.datapoint.x.toFixed(2); | |
|
218 | var y = item.datapoint.y.toFixed(2); | |
|
219 | ||
|
220 | if (!item.series.label){ | |
|
221 | item.series.label = 'commits'; | |
|
222 | } | |
|
223 | var d = new Date(x*1000); | |
|
224 | var fd = d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate(); | |
|
225 | var nr_commits = parseInt(y); | |
|
226 | ||
|
227 | var cur_data = datasets[item.series.label].data[item.dataIndex]; | |
|
228 | var added = cur_data.added; | |
|
229 | var changed = cur_data.changed; | |
|
230 | var removed = cur_data.removed; | |
|
231 | ||
|
232 | var nr_commits_suffix = " ${_('commits')} "; | |
|
233 | var added_suffix = " ${_('files added')} "; | |
|
234 | var changed_suffix = " ${_('files changed')} "; | |
|
235 | var removed_suffix = " ${_('files removed')} "; | |
|
314 | //show only checked labels | |
|
315 | for(var i=0; i<inputs.length; i++) { | |
|
316 | var checkbox_key = inputs[i].name; | |
|
317 | ||
|
318 | if(inputs[i].checked){ | |
|
319 | for(var d in cur_data){ | |
|
320 | if(cur_data[d].label == checkbox_key){ | |
|
321 | new_data.push(cur_data[d]); | |
|
322 | } | |
|
323 | } | |
|
324 | } | |
|
325 | else{ | |
|
326 | //push dummy data to not hide the label | |
|
327 | new_data.push(getDummyData(checkbox_key)); | |
|
328 | } | |
|
329 | } | |
|
330 | ||
|
331 | var new_options = YAHOO.lang.merge(plot_options, { | |
|
332 | xaxis: { | |
|
333 | min: cur_ranges.xaxis.from, | |
|
334 | max: cur_ranges.xaxis.to, | |
|
335 | mode:"time", | |
|
336 | timeformat: "%d/%m", | |
|
337 | } | |
|
338 | }); | |
|
339 | if (!new_data){ | |
|
340 | new_data = [[0,1]]; | |
|
341 | } | |
|
342 | // do the zooming | |
|
343 | plot = YAHOO.widget.Flot(plotContainer, new_data, new_options); | |
|
344 | ||
|
345 | plot.subscribe("plotselected", plotselected); | |
|
346 | ||
|
347 | //resubscribe plothover | |
|
348 | plot.subscribe("plothover", plothover); | |
|
349 | ||
|
350 | // don't fire event on the overview to prevent eternal loop | |
|
351 | overview.setSelection(cur_ranges, true); | |
|
352 | ||
|
353 | } | |
|
354 | ||
|
355 | /** | |
|
356 | * plot only selected items from overview | |
|
357 | * @param ranges | |
|
358 | * @returns | |
|
359 | */ | |
|
360 | function plotselected(ranges,cur_data) { | |
|
361 | //updates the data for new plot | |
|
362 | data = getDataAccordingToRanges(ranges); | |
|
363 | generateCheckboxes(data); | |
|
364 | ||
|
365 | var new_options = YAHOO.lang.merge(plot_options, { | |
|
366 | xaxis: { | |
|
367 | min: ranges.xaxis.from, | |
|
368 | max: ranges.xaxis.to, | |
|
369 | mode:"time", | |
|
370 | timeformat: "%d/%m", | |
|
371 | } | |
|
372 | }); | |
|
373 | // do the zooming | |
|
374 | plot = YAHOO.widget.Flot(plotContainer, data, new_options); | |
|
375 | ||
|
376 | plot.subscribe("plotselected", plotselected); | |
|
377 | ||
|
378 | //resubscribe plothover | |
|
379 | plot.subscribe("plothover", plothover); | |
|
380 | ||
|
381 | // don't fire event on the overview to prevent eternal loop | |
|
382 | overview.setSelection(ranges, true); | |
|
383 | ||
|
384 | //resubscribe choiced | |
|
385 | YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]); | |
|
386 | } | |
|
387 | ||
|
388 | var previousPoint = null; | |
|
236 | 389 | |
|
237 | ||
|
238 | if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";} | |
|
239 | if(added==1){added_suffix=" ${_('file added')} ";} | |
|
240 | if(changed==1){changed_suffix=" ${_('file changed')} ";} | |
|
241 | if(removed==1){removed_suffix=" ${_('file removed')} ";} | |
|
242 | ||
|
243 | showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd | |
|
244 | +'<br/>'+ | |
|
245 | nr_commits + nr_commits_suffix+'<br/>'+ | |
|
246 | added + added_suffix +'<br/>'+ | |
|
247 | changed + changed_suffix + '<br/>'+ | |
|
248 | removed + removed_suffix + '<br/>'); | |
|
249 | } | |
|
390 | function plothover(o) { | |
|
391 | var pos = o.pos; | |
|
392 | var item = o.item; | |
|
393 | ||
|
394 | //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2); | |
|
395 | //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2); | |
|
396 | if (item) { | |
|
397 | if (previousPoint != item.datapoint) { | |
|
398 | previousPoint = item.datapoint; | |
|
399 | ||
|
400 | var tooltip = YAHOO.util.Dom.get("tooltip"); | |
|
401 | if(tooltip) { | |
|
402 | tooltip.parentNode.removeChild(tooltip); | |
|
403 | } | |
|
404 | var x = item.datapoint.x.toFixed(2); | |
|
405 | var y = item.datapoint.y.toFixed(2); | |
|
406 | ||
|
407 | if (!item.series.label){ | |
|
408 | item.series.label = 'commits'; | |
|
250 | 409 | } |
|
251 | else { | |
|
252 | var tooltip = YAHOO.util.Dom.get("tooltip"); | |
|
253 | ||
|
254 | if(tooltip) { | |
|
255 | tooltip.parentNode.removeChild(tooltip); | |
|
256 | } | |
|
257 | previousPoint = null; | |
|
258 | } | |
|
259 |
|
|
|
410 | var d = new Date(x*1000); | |
|
411 | var fd = d.toDateString() | |
|
412 | var nr_commits = parseInt(y); | |
|
413 | ||
|
414 | var cur_data = dataset[item.series.label].data[item.dataIndex]; | |
|
415 | var added = cur_data.added; | |
|
416 | var changed = cur_data.changed; | |
|
417 | var removed = cur_data.removed; | |
|
418 | ||
|
419 | var nr_commits_suffix = " ${_('commits')} "; | |
|
420 | var added_suffix = " ${_('files added')} "; | |
|
421 | var changed_suffix = " ${_('files changed')} "; | |
|
422 | var removed_suffix = " ${_('files removed')} "; | |
|
260 | 423 | |
|
261 | } | |
|
424 | ||
|
425 | if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";} | |
|
426 | if(added==1){added_suffix=" ${_('file added')} ";} | |
|
427 | if(changed==1){changed_suffix=" ${_('file changed')} ";} | |
|
428 | if(removed==1){removed_suffix=" ${_('file removed')} ";} | |
|
429 | ||
|
430 | showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd | |
|
431 | +'<br/>'+ | |
|
432 | nr_commits + nr_commits_suffix+'<br/>'+ | |
|
433 | added + added_suffix +'<br/>'+ | |
|
434 | changed + changed_suffix + '<br/>'+ | |
|
435 | removed + removed_suffix + '<br/>'); | |
|
436 | } | |
|
437 | } | |
|
438 | else { | |
|
439 | var tooltip = YAHOO.util.Dom.get("tooltip"); | |
|
440 | ||
|
441 | if(tooltip) { | |
|
442 | tooltip.parentNode.removeChild(tooltip); | |
|
443 | } | |
|
444 | previousPoint = null; | |
|
445 | } | |
|
262 | 446 | } |
|
447 | ||
|
448 | /** | |
|
449 | * MAIN EXECUTION | |
|
450 | */ | |
|
451 | ||
|
452 | var data = getDataAccordingToRanges(initial_ranges); | |
|
453 | generateCheckboxes(data); | |
|
454 | ||
|
455 | //main plot | |
|
456 | var plot = YAHOO.widget.Flot(plotContainer,data,plot_options); | |
|
457 | ||
|
458 | //overview | |
|
459 | var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options); | |
|
460 | ||
|
461 | //show initial selection on overview | |
|
462 | overview.setSelection(initial_ranges); | |
|
463 | ||
|
464 | plot.subscribe("plotselected", plotselected); | |
|
465 | ||
|
466 | overview.subscribe("plotselected", function (ranges) { | |
|
467 | plot.setSelection(ranges); | |
|
468 | }); | |
|
469 | ||
|
470 | plot.subscribe("plothover", plothover); | |
|
263 | 471 | |
|
264 |
YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plot |
|
|
265 | ||
|
266 | plotAccordingToChoices(); | |
|
267 | })(); | |
|
268 | </script> | |
|
472 | YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]); | |
|
473 | } | |
|
474 | SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n}); | |
|
475 | </script> | |
|
269 | 476 | |
|
270 | 477 | </div> |
|
271 | 478 | </div> |
|
272 | 479 | |
|
273 | 480 | <div class="box"> |
|
274 | 481 | <div class="title"> |
|
275 | 482 | <div class="breadcrumbs">${h.link_to(_('Last ten changes'),h.url('changelog_home',repo_name=c.repo_name))}</div> |
|
276 | 483 | </div> |
|
277 | 484 | <div class="table"> |
|
278 | 485 | <%include file='../shortlog/shortlog_data.html'/> |
|
279 | 486 | ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))} |
|
280 | 487 | </div> |
|
281 | 488 | </div> |
|
282 | 489 | <div class="box"> |
|
283 | 490 | <div class="title"> |
|
284 | 491 | <div class="breadcrumbs">${h.link_to(_('Last ten tags'),h.url('tags_home',repo_name=c.repo_name))}</div> |
|
285 | 492 | </div> |
|
286 | 493 | <div class="table"> |
|
287 | 494 | <%include file='../tags/tags_data.html'/> |
|
288 | 495 | ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))} |
|
289 | 496 | </div> |
|
290 | 497 | </div> |
|
291 | 498 | <div class="box"> |
|
292 | 499 | <div class="title"> |
|
293 | 500 | <div class="breadcrumbs">${h.link_to(_('Last ten branches'),h.url('branches_home',repo_name=c.repo_name))}</div> |
|
294 | 501 | </div> |
|
295 | 502 | <div class="table"> |
|
296 | 503 | <%include file='../branches/branches_data.html'/> |
|
297 | 504 | ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))} |
|
298 | 505 | </div> |
|
299 | 506 | </div> |
|
300 | 507 | |
|
301 | 508 | </%def> No newline at end of file |
@@ -1,45 +1,51 b'' | |||
|
1 | 1 | """Pylons application test package |
|
2 | 2 | |
|
3 | 3 | This package assumes the Pylons environment is already loaded, such as |
|
4 | 4 | when this script is imported from the `nosetests --with-pylons=test.ini` |
|
5 | 5 | command. |
|
6 | 6 | |
|
7 | 7 | This module initializes the application via ``websetup`` (`paster |
|
8 | 8 | setup-app`) and provides the base testing objects. |
|
9 | 9 | """ |
|
10 | 10 | from unittest import TestCase |
|
11 | 11 | |
|
12 | 12 | from paste.deploy import loadapp |
|
13 | 13 | from paste.script.appinstall import SetupCommand |
|
14 | 14 | from pylons import config, url |
|
15 | 15 | from routes.util import URLGenerator |
|
16 | 16 | from webtest import TestApp |
|
17 | 17 | import os |
|
18 | 18 | from pylons_app.model import meta |
|
19 | import logging | |
|
20 | ||
|
21 | ||
|
22 | log = logging.getLogger(__name__) | |
|
23 | ||
|
19 | 24 | import pylons.test |
|
20 | 25 | |
|
21 | 26 | __all__ = ['environ', 'url', 'TestController'] |
|
22 | 27 | |
|
23 | 28 | # Invoke websetup with the current config file |
|
24 |
SetupCommand('setup-app').run([ |
|
|
29 | #SetupCommand('setup-app').run([config_file]) | |
|
30 | ||
|
25 | 31 | |
|
26 | 32 | environ = {} |
|
27 | 33 | |
|
28 | 34 | class TestController(TestCase): |
|
29 | 35 | |
|
30 | 36 | def __init__(self, *args, **kwargs): |
|
31 | 37 | wsgiapp = pylons.test.pylonsapp |
|
32 | 38 | config = wsgiapp.config |
|
33 | 39 | self.app = TestApp(wsgiapp) |
|
34 | 40 | url._push_object(URLGenerator(config['routes.map'], environ)) |
|
35 | 41 | self.sa = meta.Session |
|
42 | ||
|
36 | 43 | TestCase.__init__(self, *args, **kwargs) |
|
37 | ||
|
38 | 44 | |
|
39 | def log_user(self): | |
|
45 | def log_user(self, username='test_admin', password='test'): | |
|
40 | 46 | response = self.app.post(url(controller='login', action='index'), |
|
41 |
{'username': |
|
|
42 |
'password': |
|
|
47 | {'username':username, | |
|
48 | 'password':password}) | |
|
43 | 49 | assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status |
|
44 | 50 | assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user' |
|
45 |
return response.follow() |
|
|
51 | return response.follow() |
@@ -1,7 +1,9 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestAdminController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='admin/admin', action='index')) |
|
8 | assert 'Admin dashboard - journal' in response.body,'No proper title in dashboard' | |
|
7 | 9 | # Test response... |
@@ -1,43 +1,116 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | from pylons_app.model.db import User | |
|
2 | 3 | |
|
3 | 4 | class TestSettingsController(TestController): |
|
4 | 5 | |
|
5 | 6 | def test_index(self): |
|
6 | 7 | response = self.app.get(url('admin_settings')) |
|
7 | 8 | # Test response... |
|
8 | 9 | |
|
9 | 10 | def test_index_as_xml(self): |
|
10 | 11 | response = self.app.get(url('formatted_admin_settings', format='xml')) |
|
11 | 12 | |
|
12 | 13 | def test_create(self): |
|
13 | 14 | response = self.app.post(url('admin_settings')) |
|
14 | 15 | |
|
15 | 16 | def test_new(self): |
|
16 | 17 | response = self.app.get(url('admin_new_setting')) |
|
17 | 18 | |
|
18 | 19 | def test_new_as_xml(self): |
|
19 | 20 | response = self.app.get(url('formatted_admin_new_setting', format='xml')) |
|
20 | 21 | |
|
21 | 22 | def test_update(self): |
|
22 | 23 | response = self.app.put(url('admin_setting', setting_id=1)) |
|
23 | 24 | |
|
24 | 25 | def test_update_browser_fakeout(self): |
|
25 | 26 | response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put')) |
|
26 | 27 | |
|
27 | 28 | def test_delete(self): |
|
28 | 29 | response = self.app.delete(url('admin_setting', setting_id=1)) |
|
29 | 30 | |
|
30 | 31 | def test_delete_browser_fakeout(self): |
|
31 | 32 | response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete')) |
|
32 | 33 | |
|
33 | 34 | def test_show(self): |
|
34 | 35 | response = self.app.get(url('admin_setting', setting_id=1)) |
|
35 | 36 | |
|
36 | 37 | def test_show_as_xml(self): |
|
37 | 38 | response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml')) |
|
38 | 39 | |
|
39 | 40 | def test_edit(self): |
|
40 | 41 | response = self.app.get(url('admin_edit_setting', setting_id=1)) |
|
41 | 42 | |
|
42 | 43 | def test_edit_as_xml(self): |
|
43 | 44 | response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml')) |
|
45 | ||
|
46 | def test_my_account(self): | |
|
47 | self.log_user() | |
|
48 | response = self.app.get(url('admin_settings_my_account')) | |
|
49 | print response | |
|
50 | assert 'value="test_admin' in response.body | |
|
51 | ||
|
52 | ||
|
53 | ||
|
54 | def test_my_account_update(self): | |
|
55 | self.log_user() | |
|
56 | new_email = 'new@mail.pl' | |
|
57 | response = self.app.post(url('admin_settings_my_account_update'), params=dict( | |
|
58 | _method='put', | |
|
59 | username='test_admin', | |
|
60 | new_password='test', | |
|
61 | password='', | |
|
62 | name='NewName', | |
|
63 | lastname='NewLastname', | |
|
64 | email=new_email,)) | |
|
65 | response.follow() | |
|
66 | print response | |
|
67 | ||
|
68 | print 'x' * 100 | |
|
69 | print response.session | |
|
70 | assert 'Your account was updated succesfully' in response.session['flash'][0][1], 'no flash message about success of change' | |
|
71 | user = self.sa.query(User).filter(User.username == 'test_admin').one() | |
|
72 | assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email) | |
|
73 | ||
|
74 | def test_my_account_update_own_email_ok(self): | |
|
75 | self.log_user() | |
|
76 | ||
|
77 | new_email = 'new@mail.pl' | |
|
78 | response = self.app.post(url('admin_settings_my_account_update'), params=dict( | |
|
79 | _method='put', | |
|
80 | username='test_admin', | |
|
81 | new_password='test', | |
|
82 | name='NewName', | |
|
83 | lastname='NewLastname', | |
|
84 | email=new_email,)) | |
|
85 | print response | |
|
86 | ||
|
87 | def test_my_account_update_err_email_exists(self): | |
|
88 | self.log_user() | |
|
89 | ||
|
90 | new_email = 'test_regular@mail.com'#already exisitn email | |
|
91 | response = self.app.post(url('admin_settings_my_account_update'), params=dict( | |
|
92 | _method='put', | |
|
93 | username='test_admin', | |
|
94 | new_password='test', | |
|
95 | name='NewName', | |
|
96 | lastname='NewLastname', | |
|
97 | email=new_email,)) | |
|
98 | print response | |
|
99 | ||
|
100 | assert 'That e-mail address is already taken' in response.body, 'Missing error message about existing email' | |
|
101 | ||
|
102 | ||
|
103 | def test_my_account_update_err(self): | |
|
104 | self.log_user() | |
|
105 | ||
|
106 | new_email = 'newmail.pl' | |
|
107 | response = self.app.post(url('admin_settings_my_account_update'), params=dict( | |
|
108 | _method='put', | |
|
109 | username='test_regular2', | |
|
110 | new_password='test', | |
|
111 | name='NewName', | |
|
112 | lastname='NewLastname', | |
|
113 | email=new_email,)) | |
|
114 | print response | |
|
115 | assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email' | |
|
116 | assert 'This username already exists' in response.body, 'Missing error message about existing user' |
@@ -1,7 +1,8 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestBranchesController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='branches', action='index',repo_name='vcs_test')) |
|
7 | 8 | # Test response... |
@@ -1,7 +1,8 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestChangelogController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='changelog', action='index',repo_name='vcs_test')) |
|
7 | 8 | # Test response... |
@@ -1,13 +1,15 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestFeedController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_rss(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='feed', action='rss', |
|
7 | 8 | repo_name='vcs_test')) |
|
8 | 9 | # Test response... |
|
9 | 10 | |
|
10 | 11 | def test_atom(self): |
|
12 | self.log_user() | |
|
11 | 13 | response = self.app.get(url(controller='feed', action='atom', |
|
12 | 14 | repo_name='vcs_test')) |
|
13 | 15 | # Test response... No newline at end of file |
@@ -1,10 +1,11 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestFilesController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='files', action='index', |
|
7 | 8 | repo_name='vcs_test', |
|
8 | 9 | revision='tip', |
|
9 | 10 | f_path='/')) |
|
10 | 11 | # Test response... |
@@ -1,111 +1,139 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | from pylons_app.model.db import User |
|
3 | 3 | from pylons_app.lib.auth import check_password |
|
4 | 4 | |
|
5 | 5 | |
|
6 | 6 | class TestLoginController(TestController): |
|
7 | 7 | |
|
8 | 8 | def test_index(self): |
|
9 | 9 | response = self.app.get(url(controller='login', action='index')) |
|
10 | 10 | assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status |
|
11 | 11 | # Test response... |
|
12 | 12 | |
|
13 | 13 | def test_login_admin_ok(self): |
|
14 | 14 | response = self.app.post(url(controller='login', action='index'), |
|
15 | 15 | {'username':'test_admin', |
|
16 | 16 | 'password':'test'}) |
|
17 | 17 | assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status |
|
18 | 18 | assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user' |
|
19 | 19 | response = response.follow() |
|
20 | 20 | assert 'auto description for vcs_test' in response.body |
|
21 | 21 | |
|
22 | 22 | def test_login_regular_ok(self): |
|
23 | 23 | response = self.app.post(url(controller='login', action='index'), |
|
24 | 24 | {'username':'test_regular', |
|
25 | 25 | 'password':'test'}) |
|
26 | 26 | assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status |
|
27 | 27 | assert response.session['hg_app_user'].username == 'test_regular', 'wrong logged in user' |
|
28 | 28 | response = response.follow() |
|
29 | 29 | assert 'auto description for vcs_test' in response.body |
|
30 | 30 | assert '<a title="Admin" href="/_admin">' not in response.body |
|
31 | 31 | |
|
32 | 32 | def test_login_ok_came_from(self): |
|
33 | 33 | test_came_from = '/_admin/users' |
|
34 | 34 | response = self.app.post(url(controller='login', action='index', came_from=test_came_from), |
|
35 | 35 | {'username':'test_admin', |
|
36 | 36 | 'password':'test'}) |
|
37 | 37 | assert response.status == '302 Found', 'Wrong response code from came from redirection' |
|
38 | 38 | response = response.follow() |
|
39 | 39 | |
|
40 | 40 | assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status |
|
41 | 41 | assert 'Users administration' in response.body, 'No proper title in response' |
|
42 | 42 | |
|
43 | 43 | |
|
44 | 44 | def test_login_wrong(self): |
|
45 | 45 | response = self.app.post(url(controller='login', action='index'), |
|
46 | 46 | {'username':'error', |
|
47 | 47 | 'password':'test'}) |
|
48 | 48 | assert response.status == '200 OK', 'Wrong response from login page' |
|
49 | 49 | |
|
50 | 50 | assert 'invalid user name' in response.body, 'No error username message in response' |
|
51 | 51 | assert 'invalid password' in response.body, 'No error password message in response' |
|
52 | 52 | |
|
53 | 53 | |
|
54 | 54 | def test_register(self): |
|
55 | 55 | response = self.app.get(url(controller='login', action='register')) |
|
56 | 56 | assert 'Sign Up to hg-app' in response.body, 'wrong page for user registration' |
|
57 | 57 | |
|
58 | 58 | def test_register_err_same_username(self): |
|
59 | 59 | response = self.app.post(url(controller='login', action='register'), |
|
60 | 60 | {'username':'test_admin', |
|
61 | 61 | 'password':'test', |
|
62 | 62 | 'email':'goodmail@domain.com', |
|
63 | 63 | 'name':'test', |
|
64 | 64 | 'lastname':'test'}) |
|
65 | 65 | |
|
66 | 66 | assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status |
|
67 | 67 | assert 'This username already exists' in response.body |
|
68 | 68 | |
|
69 | 69 | def test_register_err_wrong_data(self): |
|
70 | 70 | response = self.app.post(url(controller='login', action='register'), |
|
71 | 71 | {'username':'xs', |
|
72 | 72 | 'password':'', |
|
73 | 73 | 'email':'goodmailm', |
|
74 | 74 | 'name':'test', |
|
75 | 75 | 'lastname':'test'}) |
|
76 | 76 | |
|
77 | 77 | assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status |
|
78 | 78 | assert 'An email address must contain a single @' in response.body |
|
79 | 79 | assert 'Enter a value 3 characters long or more' in response.body |
|
80 | 80 | assert 'Please enter a value<' in response.body |
|
81 | 81 | |
|
82 | 82 | |
|
83 | 83 | |
|
84 | 84 | def test_register_ok(self): |
|
85 |
username = 'test_regular |
|
|
85 | username = 'test_regular4' | |
|
86 | 86 | password = 'qweqwe' |
|
87 |
email = ' |
|
|
87 | email = 'marcin@test.com' | |
|
88 | 88 | name = 'testname' |
|
89 | 89 | lastname = 'testlastname' |
|
90 | 90 | |
|
91 | 91 | response = self.app.post(url(controller='login', action='register'), |
|
92 | 92 | {'username':username, |
|
93 | 93 | 'password':password, |
|
94 | 94 | 'email':email, |
|
95 | 95 | 'name':name, |
|
96 | 96 | 'lastname':lastname}) |
|
97 | ||
|
97 | print response.body | |
|
98 | 98 | assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status |
|
99 | assert 'You have successfully registered into hg-app' in response.session['flash'][0], 'No flash message about user registration' | |
|
99 | 100 | |
|
100 |
ret = self.sa.query(User).filter(User.username == 'test_regular |
|
|
101 | ret = self.sa.query(User).filter(User.username == 'test_regular4').one() | |
|
101 | 102 | assert ret.username == username , 'field mismatch %s %s' % (ret.username, username) |
|
102 | assert check_password(password,ret.password) == True , 'password mismatch' | |
|
103 | assert check_password(password, ret.password) == True , 'password mismatch' | |
|
103 | 104 | assert ret.email == email , 'field mismatch %s %s' % (ret.email, email) |
|
104 | 105 | assert ret.name == name , 'field mismatch %s %s' % (ret.name, name) |
|
105 | 106 | assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname) |
|
106 | 107 | |
|
107 | 108 | |
|
109 | def test_forgot_password_wrong_mail(self): | |
|
110 | response = self.app.post(url(controller='login', action='password_reset'), | |
|
111 | {'email':'marcin@wrongmail.org', }) | |
|
112 | ||
|
113 | assert "That e-mail address doesn't exist" in response.body, 'Missing error message about wrong email' | |
|
114 | ||
|
115 | def test_forgot_password(self): | |
|
116 | response = self.app.get(url(controller='login', action='password_reset')) | |
|
117 | assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status | |
|
118 | ||
|
119 | username = 'test_password_reset_1' | |
|
120 | password = 'qweqwe' | |
|
121 | email = 'marcin@python-works.com' | |
|
122 | name = 'passwd' | |
|
123 | lastname = 'reset' | |
|
124 | ||
|
125 | response = self.app.post(url(controller='login', action='register'), | |
|
126 | {'username':username, | |
|
127 | 'password':password, | |
|
128 | 'email':email, | |
|
129 | 'name':name, | |
|
130 | 'lastname':lastname}) | |
|
131 | #register new user for email test | |
|
132 | response = self.app.post(url(controller='login', action='password_reset'), | |
|
133 | {'email':email, }) | |
|
134 | print response.session['flash'] | |
|
135 | assert 'You have successfully registered into hg-app' in response.session['flash'][0], 'No flash message about user registration' | |
|
136 | assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset' | |
|
108 | 137 | |
|
109 | 138 | |
|
110 | 139 | |
|
111 |
@@ -1,29 +1,38 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | from pylons_app.lib.indexers import IDX_LOCATION |
|
3 | 3 | import os |
|
4 | 4 | from nose.plugins.skip import SkipTest |
|
5 | 5 | |
|
6 | 6 | class TestSearchController(TestController): |
|
7 | 7 | |
|
8 | 8 | def test_index(self): |
|
9 | 9 | self.log_user() |
|
10 | 10 | response = self.app.get(url(controller='search', action='index')) |
|
11 | 11 | print response.body |
|
12 | assert 'class="small" id="q" name="q" type="text"' in response.body,'Search box content error' | |
|
12 | assert 'class="small" id="q" name="q" type="text"' in response.body, 'Search box content error' | |
|
13 | 13 | # Test response... |
|
14 | 14 | |
|
15 | 15 | def test_empty_search(self): |
|
16 | 16 | |
|
17 | 17 | if os.path.isdir(IDX_LOCATION): |
|
18 | 18 | raise SkipTest('skipped due to existing index') |
|
19 | 19 | else: |
|
20 | 20 | self.log_user() |
|
21 | response = self.app.get(url(controller='search', action='index'),{'q':'vcs_test'}) | |
|
22 | assert 'There is no index to search in. Please run whoosh indexer' in response.body,'No error message about empty index' | |
|
21 | response = self.app.get(url(controller='search', action='index'), {'q':'vcs_test'}) | |
|
22 | assert 'There is no index to search in. Please run whoosh indexer' in response.body, 'No error message about empty index' | |
|
23 | 23 | |
|
24 | 24 | def test_normal_search(self): |
|
25 | 25 | self.log_user() |
|
26 |
response = self.app.get(url(controller='search', action='index'),{'q':'def |
|
|
26 | response = self.app.get(url(controller='search', action='index'), {'q':'def repo'}) | |
|
27 | 27 | print response.body |
|
28 |
assert ' |
|
|
28 | assert '10 results' in response.body, 'no message about proper search results' | |
|
29 | assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user' | |
|
29 | 30 | |
|
31 | ||
|
32 | def test_repo_search(self): | |
|
33 | self.log_user() | |
|
34 | response = self.app.get(url(controller='search', action='index'), {'q':'repository:vcs_test def test'}) | |
|
35 | print response.body | |
|
36 | assert '4 results' in response.body, 'no message about proper search results' | |
|
37 | assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user' | |
|
38 |
@@ -1,8 +1,9 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestSettingsController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='settings', action='index', |
|
7 | 8 | repo_name='vcs_test')) |
|
8 | 9 | # Test response... |
@@ -1,7 +1,8 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestShortlogController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='shortlog', action='index',repo_name='vcs_test')) |
|
7 | 8 | # Test response... |
@@ -1,7 +1,8 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestSummaryController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='summary', action='index',repo_name='vcs_test')) |
|
7 | 8 | # Test response... |
@@ -1,7 +1,8 b'' | |||
|
1 | 1 | from pylons_app.tests import * |
|
2 | 2 | |
|
3 | 3 | class TestTagsController(TestController): |
|
4 | 4 | |
|
5 | 5 | def test_index(self): |
|
6 | self.log_user() | |
|
6 | 7 | response = self.app.get(url(controller='tags', action='index',repo_name='vcs_test')) |
|
7 | 8 | # Test response... |
@@ -1,47 +1,32 b'' | |||
|
1 | 1 | """Setup the pylons_app application""" |
|
2 | 2 | |
|
3 |
from os.path import dirname as dn |
|
|
3 | from os.path import dirname as dn | |
|
4 | 4 | from pylons_app.config.environment import load_environment |
|
5 | 5 | from pylons_app.lib.db_manage import DbManage |
|
6 | import datetime | |
|
7 | from time import mktime | |
|
8 | 6 | import logging |
|
9 | 7 | import os |
|
10 | 8 | import sys |
|
11 | import tarfile | |
|
12 | 9 | |
|
13 | 10 | log = logging.getLogger(__name__) |
|
14 | 11 | |
|
15 | 12 | ROOT = dn(dn(os.path.realpath(__file__))) |
|
16 | 13 | sys.path.append(ROOT) |
|
17 | 14 | |
|
15 | ||
|
18 | 16 | def setup_app(command, conf, vars): |
|
19 | 17 | """Place any commands to setup pylons_app here""" |
|
20 | 18 | log_sql = True |
|
21 | 19 | tests = False |
|
22 | ||
|
23 | dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1] | |
|
24 | filename = os.path.split(conf.filename)[-1] | |
|
20 | REPO_TEST_PATH = None | |
|
25 | 21 | |
|
26 | if filename == 'tests.ini': | |
|
27 | uniq_suffix = str(int(mktime(datetime.datetime.now().timetuple()))) | |
|
28 | REPO_TEST_PATH = '/tmp/hg_app_test_%s' % uniq_suffix | |
|
29 | ||
|
30 | if not os.path.isdir(REPO_TEST_PATH): | |
|
31 | os.mkdir(REPO_TEST_PATH) | |
|
32 | cur_dir = dn(os.path.abspath(__file__)) | |
|
33 | tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz")) | |
|
34 | tar.extractall(REPO_TEST_PATH) | |
|
35 | tar.close() | |
|
36 | ||
|
37 | tests = True | |
|
22 | dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1] | |
|
38 | 23 | |
|
39 | 24 | dbmanage = DbManage(log_sql, dbname, tests) |
|
40 | 25 | dbmanage.create_tables(override=True) |
|
41 | 26 | dbmanage.config_prompt(REPO_TEST_PATH) |
|
42 | 27 | dbmanage.create_default_user() |
|
43 | 28 | dbmanage.admin_prompt() |
|
44 | 29 | dbmanage.create_permissions() |
|
45 | 30 | dbmanage.populate_default_permissions() |
|
46 | 31 | load_environment(conf.global_conf, conf.local_conf, initial=True) |
|
47 | 32 |
@@ -1,34 +1,34 b'' | |||
|
1 | 1 | [egg_info] |
|
2 | 2 | tag_build = dev |
|
3 | 3 | tag_svn_revision = true |
|
4 | 4 | |
|
5 | 5 | [easy_install] |
|
6 | 6 | find_links = http://www.pylonshq.com/download/ |
|
7 | 7 | |
|
8 | 8 | [nosetests] |
|
9 | 9 | verbose=True |
|
10 | 10 | verbosity=2 |
|
11 |
with-pylons=test |
|
|
11 | with-pylons=test.ini | |
|
12 | 12 | detailed-errors=1 |
|
13 | 13 | |
|
14 | 14 | # Babel configuration |
|
15 | 15 | [compile_catalog] |
|
16 | 16 | domain = pylons_app |
|
17 | 17 | directory = pylons_app/i18n |
|
18 | 18 | statistics = true |
|
19 | 19 | |
|
20 | 20 | [extract_messages] |
|
21 | 21 | add_comments = TRANSLATORS: |
|
22 | 22 | output_file = pylons_app/i18n/pylons_app.pot |
|
23 | 23 | width = 80 |
|
24 | 24 | |
|
25 | 25 | [init_catalog] |
|
26 | 26 | domain = pylons_app |
|
27 | 27 | input_file = pylons_app/i18n/pylons_app.pot |
|
28 | 28 | output_dir = pylons_app/i18n |
|
29 | 29 | |
|
30 | 30 | [update_catalog] |
|
31 | 31 | domain = pylons_app |
|
32 | 32 | input_file = pylons_app/i18n/pylons_app.pot |
|
33 | 33 | output_dir = pylons_app/i18n |
|
34 | 34 | previous = true |
@@ -1,48 +1,49 b'' | |||
|
1 | 1 | from pylons_app import get_version |
|
2 | 2 | try: |
|
3 | 3 | from setuptools import setup, find_packages |
|
4 | 4 | except ImportError: |
|
5 | 5 | from ez_setup import use_setuptools |
|
6 | 6 | use_setuptools() |
|
7 | 7 | from setuptools import setup, find_packages |
|
8 | 8 | |
|
9 | 9 | setup( |
|
10 | name='HgApp-%s'%get_version(), | |
|
10 | name='HgApp-%s' % get_version(), | |
|
11 | 11 | version=get_version(), |
|
12 | 12 | description='Mercurial repository serving and browsing app', |
|
13 | 13 | keywords='mercurial web hgwebdir replacement serving hgweb', |
|
14 | 14 | license='BSD', |
|
15 | 15 | author='marcin kuzminski', |
|
16 | 16 | author_email='marcin@python-works.com', |
|
17 | 17 | url='http://hg.python-works.com', |
|
18 | 18 | install_requires=[ |
|
19 | 19 | "Pylons>=1.0.0", |
|
20 | 20 | "SQLAlchemy>=0.6", |
|
21 | 21 | "babel", |
|
22 | 22 | "Mako>=0.3.2", |
|
23 |
"vcs>=0.1. |
|
|
23 | "vcs>=0.1.5", | |
|
24 | 24 | "pygments>=1.3.0", |
|
25 | 25 | "mercurial>=1.6", |
|
26 | 26 | "pysqlite", |
|
27 |
"whoosh==1.0.0b1 |
|
|
27 | "whoosh==1.0.0b17", | |
|
28 | 28 | "py-bcrypt", |
|
29 | "celery", | |
|
29 | 30 | ], |
|
30 | 31 | setup_requires=["PasteScript>=1.6.3"], |
|
31 | 32 | packages=find_packages(exclude=['ez_setup']), |
|
32 | 33 | include_package_data=True, |
|
33 | 34 | test_suite='nose.collector', |
|
34 | 35 | package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']}, |
|
35 | 36 | message_extractors={'pylons_app': [ |
|
36 | 37 | ('**.py', 'python', None), |
|
37 | 38 | ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}), |
|
38 | 39 | ('public/**', 'ignore', None)]}, |
|
39 | 40 | zip_safe=False, |
|
40 | 41 | paster_plugins=['PasteScript', 'Pylons'], |
|
41 | 42 | entry_points=""" |
|
42 | 43 | [paste.app_factory] |
|
43 | 44 | main = pylons_app.config.middleware:make_app |
|
44 | 45 | |
|
45 | 46 | [paste.app_install] |
|
46 | 47 | main = pylons.util:PylonsInstaller |
|
47 | 48 | """, |
|
48 | 49 | ) |
@@ -1,155 +1,160 b'' | |||
|
1 | 1 | ################################################################################ |
|
2 | 2 | ################################################################################ |
|
3 |
# |
|
|
3 | # hg-app - Pylons environment configuration # | |
|
4 | 4 | # # |
|
5 | 5 | # The %(here)s variable will be replaced with the parent directory of this file# |
|
6 | 6 | ################################################################################ |
|
7 | 7 | |
|
8 | 8 | [DEFAULT] |
|
9 | 9 | debug = true |
|
10 | ############################################ | |
|
11 | ## Uncomment and replace with the address ## | |
|
12 | ## which should receive any error reports ## | |
|
13 | ############################################ | |
|
10 | ################################################################################ | |
|
11 | ## Uncomment and replace with the address which should receive ## | |
|
12 | ## any error reports after application crash ## | |
|
13 | ## Additionally those settings will be used by hg-app mailing system ## | |
|
14 | ################################################################################ | |
|
14 | 15 | #email_to = admin@localhost |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | #app_email_from = hg-app-noreply@localhost | |
|
18 | #error_message = | |
|
19 | ||
|
15 | 20 | #smtp_server = mail.server.com |
|
16 | #error_email_from = paste_error@localhost | |
|
17 | 21 | #smtp_username = |
|
18 | 22 | #smtp_password = |
|
19 | #error_message = 'mercurial crash !' | |
|
23 | #smtp_port = | |
|
24 | #smtp_use_tls = false | |
|
20 | 25 | |
|
21 | 26 | [server:main] |
|
22 | 27 | ##nr of threads to spawn |
|
23 | 28 | threadpool_workers = 5 |
|
24 | 29 | |
|
25 | ##max request before | |
|
30 | ##max request before thread respawn | |
|
26 | 31 | threadpool_max_requests = 2 |
|
27 | 32 | |
|
28 | 33 | ##option to use threads of process |
|
29 | 34 | use_threadpool = true |
|
30 | 35 | |
|
31 | 36 | use = egg:Paste#http |
|
32 | 37 | host = 127.0.0.1 |
|
33 | 38 | port = 5000 |
|
34 | 39 | |
|
35 | 40 | [app:main] |
|
36 | 41 | use = egg:pylons_app |
|
37 | 42 | full_stack = true |
|
38 | 43 | static_files = true |
|
39 | 44 | lang=en |
|
40 | 45 | cache_dir = %(here)s/data |
|
41 | 46 | |
|
42 | 47 | #################################### |
|
43 | 48 | ### BEAKER CACHE #### |
|
44 | 49 | #################################### |
|
45 | 50 | beaker.cache.data_dir=/%(here)s/data/cache/data |
|
46 | 51 | beaker.cache.lock_dir=/%(here)s/data/cache/lock |
|
47 | 52 | beaker.cache.regions=super_short_term,short_term,long_term |
|
48 | 53 | beaker.cache.long_term.type=memory |
|
49 | 54 | beaker.cache.long_term.expire=36000 |
|
50 | 55 | beaker.cache.short_term.type=memory |
|
51 | 56 | beaker.cache.short_term.expire=60 |
|
52 | 57 | beaker.cache.super_short_term.type=memory |
|
53 | 58 | beaker.cache.super_short_term.expire=10 |
|
54 | 59 | |
|
55 | 60 | #################################### |
|
56 | 61 | ### BEAKER SESSION #### |
|
57 | 62 | #################################### |
|
58 | 63 | ## Type of storage used for the session, current types are |
|
59 |
## |
|
|
64 | ## "dbm", "file", "memcached", "database", and "memory". | |
|
60 | 65 | ## The storage uses the Container API |
|
61 | 66 | ##that is also used by the cache system. |
|
62 | 67 | beaker.session.type = file |
|
63 | 68 | |
|
64 | 69 | beaker.session.key = hg-app |
|
65 | 70 | beaker.session.secret = g654dcno0-9873jhgfreyu |
|
66 | 71 | beaker.session.timeout = 36000 |
|
67 | 72 | |
|
68 | 73 | ##auto save the session to not to use .save() |
|
69 | 74 | beaker.session.auto = False |
|
70 | 75 | |
|
71 | 76 | ##true exire at browser close |
|
72 | 77 | #beaker.session.cookie_expires = 3600 |
|
73 | 78 | |
|
74 | 79 | |
|
75 | 80 | ################################################################################ |
|
76 | 81 | ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ## |
|
77 | 82 | ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ## |
|
78 | 83 | ## execute malicious code after an exception is raised. ## |
|
79 | 84 | ################################################################################ |
|
80 | 85 | #set debug = false |
|
81 | 86 | |
|
82 | 87 | ################################## |
|
83 | 88 | ### LOGVIEW CONFIG ### |
|
84 | 89 | ################################## |
|
85 | 90 | logview.sqlalchemy = #faa |
|
86 | 91 | logview.pylons.templating = #bfb |
|
87 | 92 | logview.pylons.util = #eee |
|
88 | 93 | |
|
89 | 94 | ######################################################### |
|
90 | 95 | ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ### |
|
91 | 96 | ######################################################### |
|
92 | 97 | sqlalchemy.db1.url = sqlite:///%(here)s/test.db |
|
93 | 98 | #sqlalchemy.db1.echo = False |
|
94 | 99 | #sqlalchemy.db1.pool_recycle = 3600 |
|
95 | 100 | sqlalchemy.convert_unicode = true |
|
96 | 101 | |
|
97 | 102 | ################################ |
|
98 | 103 | ### LOGGING CONFIGURATION #### |
|
99 | 104 | ################################ |
|
100 | 105 | [loggers] |
|
101 | 106 | keys = root, routes, pylons_app, sqlalchemy |
|
102 | 107 | |
|
103 | 108 | [handlers] |
|
104 | 109 | keys = console |
|
105 | 110 | |
|
106 | 111 | [formatters] |
|
107 | 112 | keys = generic,color_formatter |
|
108 | 113 | |
|
109 | 114 | ############# |
|
110 | 115 | ## LOGGERS ## |
|
111 | 116 | ############# |
|
112 | 117 | [logger_root] |
|
113 | 118 | level = ERROR |
|
114 | 119 | handlers = console |
|
115 | 120 | |
|
116 | 121 | [logger_routes] |
|
117 | 122 | level = ERROR |
|
118 | 123 | handlers = console |
|
119 | 124 | qualname = routes.middleware |
|
120 | 125 | # "level = DEBUG" logs the route matched and routing variables. |
|
121 | 126 | |
|
122 | 127 | [logger_pylons_app] |
|
123 | 128 | level = ERROR |
|
124 | 129 | handlers = console |
|
125 | 130 | qualname = pylons_app |
|
126 | 131 | propagate = 0 |
|
127 | 132 | |
|
128 | 133 | [logger_sqlalchemy] |
|
129 | 134 | level = ERROR |
|
130 | 135 | handlers = console |
|
131 | 136 | qualname = sqlalchemy.engine |
|
132 | 137 | propagate = 0 |
|
133 | 138 | |
|
134 | 139 | ############## |
|
135 | 140 | ## HANDLERS ## |
|
136 | 141 | ############## |
|
137 | 142 | |
|
138 | 143 | [handler_console] |
|
139 | 144 | class = StreamHandler |
|
140 | 145 | args = (sys.stderr,) |
|
141 | 146 | level = NOTSET |
|
142 | 147 | formatter = color_formatter |
|
143 | 148 | |
|
144 | 149 | ################ |
|
145 | 150 | ## FORMATTERS ## |
|
146 | 151 | ################ |
|
147 | 152 | |
|
148 | 153 | [formatter_generic] |
|
149 | 154 | format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
150 | 155 | datefmt = %Y-%m-%d %H:%M:%S |
|
151 | 156 | |
|
152 | 157 | [formatter_color_formatter] |
|
153 | 158 | class=pylons_app.lib.colored_formatter.ColorFormatter |
|
154 | 159 | format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s |
|
155 | 160 | datefmt = %Y-%m-%d %H:%M:%S No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now