blob: 50b0e38f0a4b2882e06f3d05d06f79b60b7f387a [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import re
import json
import logging
from datetime import datetime, timedelta
from collections import defaultdict
from tg import expose, validate, flash, config, redirect
from tg.decorators import with_trailing_slash, without_trailing_slash
from ming.orm import session
import pymongo
import bson
import tg
from pylons import tmpl_context as c, app_globals as g
from pylons import request
from formencode import validators, Invalid
from webob.exc import HTTPNotFound
from allura.lib import helpers as h
from allura.lib import validators as v
from allura.lib.decorators import require_post
from allura.lib.security import require_access
from allura.lib.widgets import form_fields as ffw
from allura import model as M
from allura.command.show_models import dfs, build_model_inheritance_graph
import allura
from urlparse import urlparse
log = logging.getLogger(__name__)
class W:
page_list = ffw.PageList()
page_size = ffw.PageSize()
class SiteAdminController(object):
def __init__(self):
self.task_manager = TaskManagerController()
def _check_security(self):
with h.push_context(config.get('site_admin_project', 'allura'),
neighborhood=config.get('site_admin_project_nbhd', 'Projects')):
require_access(c.project, 'admin')
@expose('jinja:allura:templates/site_admin_index.html')
@with_trailing_slash
def index(self):
neighborhoods = []
for n in M.Neighborhood.query.find():
project_count = M.Project.query.find(dict(neighborhood_id=n._id)).count()
configured_count = M.Project.query.find(dict(neighborhood_id=n._id, database_configured=True)).count()
neighborhoods.append((n.name, project_count, configured_count))
neighborhoods.sort(key=lambda n:n[0])
return dict(neighborhoods=neighborhoods)
@expose('jinja:allura:templates/site_admin_api_tickets.html')
@without_trailing_slash
def api_tickets(self, **data):
import json
import dateutil.parser
if request.method == 'POST':
log.info('api_tickets: %s', data)
ok = True
for_user = M.User.by_username(data['for_user'])
if not for_user:
ok = False
flash('User not found')
caps = None
try:
caps = json.loads(data['caps'])
except ValueError:
ok = False
flash('JSON format error')
if type(caps) is not type({}):
ok = False
flash('Capabilities must be a JSON dictionary, mapping capability name to optional discriminator(s) (or "")')
try:
expires = dateutil.parser.parse(data['expires'])
except ValueError:
ok = False
flash('Date format error')
if ok:
tok = None
try:
tok = M.ApiTicket(user_id=for_user._id, capabilities=caps, expires=expires)
session(tok).flush()
log.info('New token: %s', tok)
flash('API Ticket created')
except:
log.exception('Could not create API ticket:')
flash('Error creating API ticket')
elif request.method == 'GET':
data = {'expires': datetime.utcnow() + timedelta(days=2)}
data['token_list'] = M.ApiTicket.query.find().sort('mod_date', pymongo.DESCENDING).all()
log.info(data['token_list'])
return data
def subscribe_artifact(self, url, user):
artifact_url = urlparse(url).path[1:-1].split("/")
neighborhood = M.Neighborhood.query.find({
"url_prefix": "/" + artifact_url[0] + "/"}).first()
if artifact_url[0] == "u":
project = M.Project.query.find({
"shortname": artifact_url[0] + "/" + artifact_url[1],
"neighborhood_id": neighborhood._id}).first()
else:
project = M.Project.query.find({
"shortname": artifact_url[1],
"neighborhood_id": neighborhood._id}).first()
appconf = M.AppConfig.query.find({
"options.mount_point": artifact_url[2],
"project_id": project._id}).first()
if appconf.url() == urlparse(url).path:
M.Mailbox.subscribe(
user_id=user._id,
app_config_id=appconf._id,
project_id=project._id)
return True
tool_package = h.get_tool_package(appconf.tool_name)
classes = set()
for depth, cls in dfs(M.Artifact, build_model_inheritance_graph()):
if cls.__module__.startswith(tool_package + '.'):
classes.add(cls)
for cls in classes:
for artifact in cls.query.find({"app_config_id": appconf._id}):
if artifact.url() == urlparse(url).path:
M.Mailbox.subscribe(
user_id=user._id,
app_config_id=appconf._id,
project_id=project._id,
artifact=artifact)
return True
return False
@expose('jinja:allura:templates/site_admin_add_subscribers.html')
@without_trailing_slash
def add_subscribers(self, **data):
if request.method == 'POST':
url = data['artifact_url']
user = M.User.by_username(data['for_user'])
if not user or user == M.User.anonymous():
flash('Invalid login', 'error')
return data
try:
ok = self.subscribe_artifact(url, user)
except:
log.warn("Can't subscribe to artifact", exc_info=True)
ok = False
if ok:
flash('User successfully subscribed to the artifact')
return {}
else:
flash('Artifact not found', 'error')
return data
@expose('jinja:allura:templates/site_admin_new_projects.html')
@without_trailing_slash
@validate(dict(page=validators.Int(if_empty=0),
limit=validators.Int(if_empty=100)))
def new_projects(self, page=0, limit=100, **kwargs):
c.page_list = W.page_list
c.page_size = W.page_size
limit, pagenum, start = g.handle_paging(limit, page, default=100)
count = 0
nb = M.Neighborhood.query.get(name='Users')
projects = (M.Project.query.find({'neighborhood_id': {'$ne': nb._id}})
.sort('_id', -1))
count = projects.count()
projects = projects.skip(start).limit(limit)
return {
'projects': projects,
'limit': limit,
'pagenum': pagenum,
'count': count
}
@expose('jinja:allura:templates/site_admin_reclone_repo.html')
@without_trailing_slash
@validate(dict(prefix=validators.NotEmpty(),
shortname=validators.NotEmpty(),
mount_point=validators.NotEmpty()))
def reclone_repo(self, prefix=None, shortname=None, mount_point=None, **data):
if request.method == 'POST':
if c.form_errors:
error_msg = 'Error: '
for msg in list(c.form_errors):
names = {'prefix': 'Neighborhood prefix', 'shortname': 'Project shortname', 'mount_point': 'Repository mount point'}
error_msg += '%s: %s ' % (names[msg], c.form_errors[msg])
flash(error_msg, 'error')
return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
nbhd = M.Neighborhood.query.get(url_prefix='/%s/' % prefix)
if not nbhd:
flash('Neighborhood with prefix %s not found' % prefix, 'error')
return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
c.project = M.Project.query.get(shortname=shortname, neighborhood_id=nbhd._id)
if not c.project:
flash('Project with shortname %s not found in neighborhood %s' % (shortname, nbhd.name), 'error')
return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
c.app = c.project.app_instance(mount_point)
if not c.app:
flash('Mount point %s not found on project %s' % (mount_point, c.project.shortname), 'error')
return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
source_url = c.app.config.options.get('init_from_url')
source_path = c.app.config.options.get('init_from_path')
if not (source_url or source_path):
flash('%s does not appear to be a cloned repo' % c.app, 'error')
return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
allura.tasks.repo_tasks.reclone_repo.post(prefix=prefix, shortname=shortname, mount_point=mount_point)
flash('Repository is being recloned')
else:
prefix = 'p'
shortname = ''
mount_point = ''
return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
class TaskManagerController(object):
def _check_security(self):
with h.push_context(config.get('site_admin_project', 'allura'),
neighborhood=config.get('site_admin_project_nbhd', 'Projects')):
require_access(c.project, 'admin')
@expose('jinja:allura:templates/site_admin_task_list.html')
@without_trailing_slash
def index(self, page_num=1, minutes=10, state=None, task_name=None, host=None):
now = datetime.utcnow()
try:
page_num = int(page_num)
except ValueError as e:
page_num = 1
try:
minutes = int(minutes)
except ValueError as e:
minutes = 1
start_dt = now - timedelta(minutes=(page_num-1)*minutes)
end_dt = now - timedelta(minutes=page_num*minutes)
start = bson.ObjectId.from_datetime(start_dt)
end = bson.ObjectId.from_datetime(end_dt)
query = {'_id': {'$gt': end}}
if page_num > 1:
query['_id']['$lt'] = start
if state:
query['state'] = state
if task_name:
query['task_name'] = re.compile(re.escape(task_name))
if host:
query['process'] = re.compile(re.escape(host))
tasks = list(M.monq_model.MonQTask.query.find(query).sort('_id', -1))
for task in tasks:
task.project = M.Project.query.get(_id=task.context.project_id)
task.user = M.User.query.get(_id=task.context.user_id)
newer_url = tg.url(params=dict(request.params, page_num=page_num - 1)).lstrip('/')
older_url = tg.url(params=dict(request.params, page_num=page_num + 1)).lstrip('/')
return dict(
tasks=tasks,
page_num=page_num,
minutes=minutes,
newer_url=newer_url,
older_url=older_url,
window_start=start_dt,
window_end=end_dt,
)
@expose('jinja:allura:templates/site_admin_task_view.html')
@without_trailing_slash
def view(self, task_id):
try:
task = M.monq_model.MonQTask.query.get(_id=bson.ObjectId(task_id))
except bson.errors.InvalidId as e:
task = None
if task:
task.project = M.Project.query.get(_id=task.context.project_id)
task.app_config = M.AppConfig.query.get(_id=task.context.app_config_id)
task.user = M.User.query.get(_id=task.context.user_id)
return dict(task=task)
@expose('jinja:allura:templates/site_admin_task_new.html')
@without_trailing_slash
def new(self, **kw):
"""Render the New Task form"""
return dict(
form_errors=c.form_errors or {},
form_values=c.form_values or {},
)
@expose()
@require_post()
@validate(v.CreateTaskSchema(), error_handler=new)
def create(self, task, task_args=None, user=None, path=None):
"""Post a new task"""
args = task_args.get("args", ())
kw = task_args.get("kwargs", {})
config_dict = path
if user:
config_dict['user'] = user
with h.push_config(c, **config_dict):
task = task.post(*args, **kw)
redirect('../view/%s' % task._id)
@expose()
@require_post()
def resubmit(self, task_id):
try:
task = M.monq_model.MonQTask.query.get(_id=bson.ObjectId(task_id))
except bson.errors.InvalidId as e:
task = None
if task is None:
raise HTTPNotFound()
task.state = 'ready'
redirect('../view/%s' % task._id)
@expose('json:')
def task_doc(self, task_name):
"""Return a task's docstring"""
error, doc = None, None
try:
task = v.TaskValidator.to_python(task_name)
doc = task.__doc__ or 'No doc string available'
except Invalid as e:
error = str(e)
return dict(doc=doc, error=error)