--- /dev/null
+.py[co]
+.mo
--- /dev/null
+
+PO_PATH = data/translations
+POTFILE = $(PO_PATH)/pakfire.pot
+POFILES = $(wildcard $(PO_PATH)/*/LC_MESSAGES/*.po)
+
+ALL_FILES = $(shell find . /usr/lib*/python*/site-packages/tornado -name "*.py" -or -name "*.html")
+
+.PHONY: pot
+pot: $(POTFILE)
+
+$(POTFILE): $(ALL_FILES)
+ xgettext --language=Python --from-code=UTF-8 --keyword=_:1,2 --keyword=N_ -d pakfire -o $(POTFILE) \
+ $(ALL_FILES)
+
+.PHONY: po
+po: $(POTFILE) $(patsubst %.po, %.mo, $(POFILES))
+
+%.po: $(POTFILE)
+ # Merge the POTFILE.
+ msgmerge $@ $(POTFILE) -o $@
+
+%.mo: %.po
+ # Compile the translations.
+ msgfmt $< -o $@
--- /dev/null
+#!/usr/bin/python
+
+import database
+import settings
+import uploads
+
+from build import Builds
+from builders import Builders
+from distribution import Distributions
+from messages import Messages
+from packages import Packages
+from repository import Repositories
+from sources import Sources
+from users import Users
+
+# Database access.
+MYSQL_SERVER = "mysql.ipfire.org"
+MYSQL_USER = "pakfire"
+MYSQL_DB = "pakfire"
+
+class Pakfire(object):
+ def __init__(self):
+ self.db = database.Connection(MYSQL_SERVER, MYSQL_DB, user=MYSQL_USER)
+
+ # Global pakfire settings (from database).
+ self.settings = settings.Settings(self)
+
+ self.builds = Builds(self)
+ self.builders = Builders(self)
+ self.distros = Distributions(self)
+ self.messages = Messages(self)
+ self.packages = Packages(self)
+ self.repos = Repositories(self)
+ self.sources = Sources(self)
+ self.uploads = uploads.Uploads(self)
+ self.users = Users(self)
+
+ def __del__(self):
+ if self.db:
+ self.db.close()
+ del self.db
+
+ def logger(self, message, text=None, build=None, pkg=None):
+ if not build and not pkg:
+ raise Exception, "need to give at least one parameter for log"
+
+ log_id = self.db.execute("INSERT INTO `log`(message, text) VALUES(%s, %s)", message, text)
+
+ query = "UPDATE `log` SET %s = %%s WHERE id = %%s"
+
+ if build:
+ self.db.execute(query % "build_id", build.id, log_id)
+
+ if build.host:
+ self.db.execute(query % "host_id", build.host.id, log_id)
+
+ pkg = getattr(build, "pkg", None)
+
+ if pkg:
+ self.db.execute(query % "pkg_id", pkg.id, log_id)
+ self.db.execute(query % "source_id", pkg.source.id, log_id)
+
+ return log_id
+
+ @property
+ def log(self, limit=100):
+ return self.db.query("SELECT * FROM log ORDER BY time DESC LIMIT %s", limit)
--- /dev/null
+#!/usr/bin/python
+
+
+class Object(object):
+ """
+ Main object where all other objects inherit from.
+
+ This is used to access the global instance of Pakfire
+ and hold the database connection.
+ """
+ def __init__(self, pakfire):
+ self.pakfire = pakfire
+
+ # Shortcut to the database.
+ self.db = self.pakfire.db
+
+ # Shortcut to settings.
+ if hasattr(self.pakfire, "settings"):
+ self.settings = self.pakfire.settings
--- /dev/null
+#!/usr/bin/python
+
+import datetime
+import logging
+import uuid
+import time
+import tornado.locale
+
+import base
+import packages
+
+from constants import *
+
+def Build(pakfire, id):
+ """
+ Proxy function that returns the right object depending on the type
+ of the build.
+ """
+ build = pakfire.db.get("SELECT type FROM builds WHERE id = %s", id)
+ if not build:
+ raise Exception, "Build not found"
+
+ for cls in (BinaryBuild, SourceBuild):
+ if not build.type == cls.type:
+ continue
+
+ return cls(pakfire, id)
+
+ raise Exception, "Unknown type: %s" % build.type
+
+
+class Builds(base.Object):
+ """
+ Object that represents all builds.
+ """
+
+ def get_by_id(self, id):
+ return Build(self.pakfire, id)
+
+ def get_by_uuid(self, uuid):
+ build = self.db.get("SELECT id FROM builds WHERE uuid = %s LIMIT 1", uuid)
+
+ if build:
+ return Build(self.pakfire, build.id)
+
+ def get_latest(self, state=None, builder=None, limit=10, type=None):
+ query = "SELECT id FROM builds"
+
+ where = []
+ if builder:
+ where.append("host = '%s'" % builder)
+
+ if state:
+ where.append("state = '%s'" % state)
+
+ if type:
+ where.append("type = '%s'" % type)
+
+ if where:
+ query += " WHERE %s" % " AND ".join(where)
+
+ query += " ORDER BY updated DESC LIMIT %s"
+
+ builds = self.db.query(query, limit)
+
+ return [Build(self.pakfire, b.id) for b in builds]
+
+ def get_by_pkgid(self, pkg_id):
+ builds = self.db.query("""SELECT builds.id as id FROM builds
+ LEFT JOIN builds_binary ON builds_binary.id = builds.build_id
+ WHERE builds_binary.pkg_id = %s AND type = 'binary'""", pkg_id)
+
+ return [Build(self.pakfire, b.id) for b in builds]
+
+ def get_by_host(self, host_id):
+ builds = self.db.query("SELECT id FROM builds WHERE host = %s", host_id)
+
+ return [Build(self.pakfire, b.id) for b in builds]
+
+ def get_by_source(self, source_id):
+ builds = self.db.query("""SELECT builds.id as id FROM builds
+ LEFT JOIN builds_source ON builds_source.id = builds.build_id
+ WHERE builds_source.source_id = %s""", source_id)
+
+ return [Build(self.pakfire, b.id) for b in builds]
+
+ def get_active(self, type=None, host_id=None):
+ running_states = ("dispatching", "running", "uploading",)
+
+ query = "SELECT id FROM builds WHERE (%s)" % \
+ " OR ".join(["state = '%s'" % s for s in running_states])
+
+ if type:
+ query += " AND type = '%s'" % type
+
+ if host_id:
+ query += " AND host = %s" % host_id
+
+ builds = self.db.query(query)
+
+ return [Build(self.pakfire, b.id) for b in builds]
+
+ def get_all_but_finished(self):
+ builds = self.db.query("SELECT id FROM builds WHERE"
+ " NOT state = 'finished' AND NOT state = 'permanently_failed'")
+
+ return [Build(self.pakfire, b.id) for b in builds]
+
+ def get_next(self, type=None, arches=None, limit=None, offset=None):
+ query = "SELECT builds.id as id, build_id FROM builds"
+
+ wheres = ["state = 'pending'", "start_not_before <= NOW()",]
+
+ if type:
+ wheres.append("type = '%s'" % type)
+
+ if type == "binary" and arches:
+ query += " LEFT JOIN builds_binary ON builds.build_id = builds_binary.id"
+ arches = ["builds_binary.arch='%s'" % a for a in arches]
+
+ wheres.append("(%s)" % " OR ".join(arches))
+ elif arches:
+ raise Exception, "Cannot use arches when type is not 'binary'"
+
+ if wheres:
+ query += " WHERE %s" % " AND ".join(wheres)
+
+ # Choose the oldest one at first.
+ query += " ORDER BY priority DESC, time_added ASC"
+
+ if limit:
+ if offset:
+ query += " LIMIT %s,%s" % (limit, offset)
+ else:
+ query += " LIMIT %s" % limit
+
+ builds = [Build(self.pakfire, b.id) for b in self.db.query(query)]
+
+ if limit == 1 and builds:
+ return builds[0]
+
+ return builds
+
+ def count(self, state=None):
+ query = "SELECT COUNT(*) as c FROM builds"
+
+ wheres = []
+ if state:
+ wheres.append("state = '%s'" % state)
+
+ if wheres:
+ query += " WHERE %s" % " AND ".join(wheres)
+
+ result = self.db.get(query)
+
+ return result.c
+
+ def average_build_time(self):
+ result = self.db.get("SELECT AVG(time_finished - time_started) as average"
+ " FROM builds WHERE type = 'binary' AND time_started IS NOT NULL"
+ " AND time_finished IS NOT NULL")
+
+ return result.average or 0
+
+
+class _Build(base.Object):
+ STATE2LOG = {
+ "pending" : LOG_BUILD_STATE_PENDING,
+ "dispatching" : LOG_BUILD_STATE_DISPATCHING,
+ "running" : LOG_BUILD_STATE_RUNNING,
+ "failed" : LOG_BUILD_STATE_FAILED,
+ "permanently_failed" : LOG_BUILD_STATE_PERM_FAILED,
+ "dependency_error" : LOG_BUILD_STATE_DEP_ERROR,
+ "waiting" : LOG_BUILD_STATE_WAITING,
+ "finished" : LOG_BUILD_STATE_FINISHED,
+ "unknown" : LOG_BUILD_STATE_UNKNOWN,
+ "uploading" : LOG_BUILD_STATE_UPLOADING,
+ }
+
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+ self.id = id
+
+ self._data = self.db.get("SELECT * FROM builds WHERE id = %s LIMIT 1", self.id)
+
+ def set(self, key, value):
+ self.db.execute("UPDATE builds SET %s = %%s WHERE id = %%s" % key, value, self.id)
+ self._data[key] = value
+
+ @property
+ def build_id(self):
+ return self._data.build_id
+
+ def get_state(self):
+ return self._data.state
+
+ def set_state(self, state):
+ try:
+ log = self.STATE2LOG[state]
+ except KeyErrror:
+ raise Exception, "Trying to set an invalid build state: %s" % state
+
+ # Setting state.
+ self.set("state", state)
+
+ # Inform everybody what happened to the build job.
+ if state == "finished":
+ self.db.execute("UPDATE builds SET time_finished = NOW()"
+ " WHERE id = %s", self.id)
+
+ self.send_finished_message()
+
+ elif state == "failed":
+ self.send_failed_message()
+
+ self.db.execute("UPDATE builds SET time_started = NULL,"
+ " time_finished = NULL WHERE id = %s LIMIT 1", self.id)
+
+ elif state == "pending":
+ self.retries += 1
+
+ elif state in ("dispatching", "running",):
+ self.db.execute("UPDATE builds SET time_started = NOW(),"
+ " time_finished = NULL WHERE id = %s LIMIT 1", self.id)
+
+ # Log the state change.
+ self.logger(log)
+
+ state = property(get_state, set_state)
+
+ @property
+ def finished(self):
+ return self.state == "finished"
+
+ def get_message(self):
+ return self._data.message
+
+ def set_message(self, message):
+ self.set("message", message)
+
+ message = property(get_message, set_message)
+
+ @property
+ def uuid(self):
+ return self._data.uuid
+
+ def get_host(self):
+ return self.pakfire.builders.get_by_id(self._data.host)
+
+ def set_host(self, host):
+ builder = self.pakfire.builders.get_by_name(host)
+
+ self.set("host", builder.id)
+
+ host = property(get_host, set_host)
+
+ def get_retries(self):
+ return self._data.retries
+
+ def set_retries(self, retries):
+ self.set("retries", retries)
+
+ retries = property(get_retries, set_retries)
+
+ @property
+ def time_added(self):
+ return self._data.time_added
+
+ @property
+ def time_started(self):
+ return self._data.time_started
+
+ @property
+ def time_finished(self):
+ return self._data.time_finished
+
+ @property
+ def duration(self):
+ if not self.time_finished or not self.time_started:
+ return
+
+ return self.time_finished - self.time_started
+
+ def get_priority(self):
+ return self._data.priority
+
+ def set_priority(self, value):
+ self.set("priority", value)
+
+ priority = property(get_priority, set_priority)
+
+ @property
+ def log(self):
+ return self.db.query("SELECT * FROM log WHERE build_id = %s ORDER BY time DESC, id DESC", self.id)
+
+ def logger(self, message, text=""):
+ self.pakfire.logger(message, text, build=self)
+
+ @property
+ def source(self):
+ return self.pkg.source
+
+ @property
+ def files(self):
+ files = []
+
+ for p in self.db.query("SELECT id, type FROM package_files WHERE build_id = %s", self.uuid):
+ for p_class in (packages.SourcePackageFile, packages.BinaryPackageFile, packages.LogFile):
+ if p.type == p_class.type:
+ p = p_class(self.pakfire, p.id)
+ break
+ else:
+ continue
+
+ files.append(p)
+
+ return sorted(files)
+
+ @property
+ def packagefiles(self):
+ return [f for f in self.files if isinstance(f, packages.PackageFile)]
+
+ @property
+ def logfiles(self):
+ return [f for f in self.files if isinstance(f, packages.LogFile)]
+
+ @property
+ def recipients(self):
+ return []
+
+ def send_finished_message(self):
+ info = {
+ "build_name" : self.name,
+ "build_host" : self.host.name,
+ "build_uuid" : self.uuid,
+ }
+
+ self.pakfire.messages.send_to_all(self.recipients, MSG_BUILD_FINISHED_SUBJECT,
+ MSG_BUILD_FINISHED, info)
+
+ def send_failed_message(self):
+ build_host = "--"
+ if self.host:
+ build_host = self.host.name
+
+ info = {
+ "build_name" : self.name,
+ "build_host" : build_host,
+ "build_uuid" : self.uuid,
+ }
+
+ self.pakfire.messages.send_to_all(self.recipients, MSG_BUILD_FAILED_SUBJECT,
+ MSG_BUILD_FAILED, info)
+
+ def keepalive(self):
+ """
+ This function is used to prevent build jobs from getting stuck on
+ something.
+ """
+
+ # Get the seconds since we are running.
+ try:
+ time_running = datetime.datetime.utcnow() - self.time_started
+ time_running = time_running.total_seconds()
+ except:
+ time_running = 0
+
+ if self.state == "dispatching":
+ # If the dispatching is running more than 15 minutes, we set the
+ # build to be failed.
+ if time_running >= 900:
+ self.state = "failed"
+
+ elif self.state in ("running", "uploading"):
+ # If the build is running/uploading more than 24 hours, we kill it.
+ if time_running >= 3600 * 24:
+ self.state = "failed"
+
+ elif self.state == "dependency_error":
+ # Resubmit job when it has waited for twelve hours.
+ if time_running >= 3600 * 12:
+ self.state = "pending"
+
+ elif self.state == "failed":
+ # Automatically resubmit jobs that failed after one day.
+ if time_running >= 3600 * 24:
+ self.state = "pending"
+
+ def schedule_rebuild(self, offset):
+ # You cannot do this if the build job has already finished.
+ if self.finished:
+ return
+
+ self.db.execute("UPDATE builds SET start_not_before = NOW() + %s"
+ " WHERE id = %s LIMIT 1", offset, self.id)
+ self.state = "pending"
+
+
+class BinaryBuild(_Build):
+ type = "binary"
+
+ def __init__(self, *args, **kwargs):
+ _Build.__init__(self, *args, **kwargs)
+
+ _data = self.db.get("SELECT * FROM builds_binary WHERE id = %s", self.build_id)
+ del _data["id"]
+ self._data.update(_data)
+
+ self.pkg = self.pakfire.packages.get_by_id(self.pkg_id)
+
+ @classmethod
+ def new(cls, pakfire, pkg, arch):
+ now = datetime.datetime.utcnow()
+
+ build_id = pakfire.db.execute("INSERT INTO builds_binary(pkg_id, arch)"
+ " VALUES(%s, %s)", pkg.id, arch)
+
+ id = pakfire.db.execute("INSERT INTO builds(uuid, build_id, time_added)"
+ " VALUES(%s, %s, %s)", uuid.uuid4(), build_id, now)
+
+ build = cls(pakfire, id)
+ build.logger(LOG_BUILD_CREATED)
+
+ return build
+
+ @property
+ def name(self):
+ return "%s.%s" % (self.pkg.friendly_name, self.arch)
+
+ @property
+ def arch(self):
+ return self._data.arch
+
+ @property
+ def distro(self):
+ return self.pkg.distro
+
+ @property
+ def pkg_id(self):
+ return self._data.pkg_id
+
+ @property
+ def source_build(self):
+ return self.pkg.source_build
+
+ @property
+ def recipients(self):
+ l = set()
+
+ # Get all recipients from the source build (like committer and author).
+ for r in self.source_build.recipients:
+ l.add(r)
+
+ # Add the package maintainer.
+ l.add(self.pkg.maintainer)
+
+ return l
+
+ def add_log(self, filename):
+ self.pkg.add_log(filename, self)
+
+ def schedule_test(self, offset):
+ pass # XXX TBD
+
+
+class SourceBuild(_Build):
+ type = "source"
+
+ def __init__(self, *args, **kwargs):
+ _Build.__init__(self, *args, **kwargs)
+
+ _data = self.db.get("SELECT * FROM builds_source WHERE id = %s", self.build_id)
+ del _data["id"]
+ self._data.update(_data)
+
+ @classmethod
+ def new(cls, pakfire, source_id, revision, author, committer, subject, body, date):
+ now = datetime.datetime.utcnow()
+
+ # Check if the revision does already exist. If so, just return.
+ if pakfire.db.query("SELECT id FROM builds_source WHERE revision = %s", revision):
+ logging.warning("There is already a source build job for rev %s" % revision)
+ return
+
+ build_id = pakfire.db.execute("INSERT INTO builds_source(source_id,"
+ " revision, author, committer, subject, body, date) VALUES(%s, %s, %s, %s,"
+ " %s, %s, %s)", source_id, revision, author, committer, subject, body, date)
+
+ id = pakfire.db.execute("INSERT INTO builds(uuid, type, build_id, time_added)"
+ " VALUES(%s, %s, %s, %s)", uuid.uuid4(), "source", build_id, now)
+
+ build = cls(pakfire, id)
+ build.logger(LOG_BUILD_CREATED)
+
+ # Source builds are immediately pending.
+ build.state = "pending"
+
+ return build
+
+ @property
+ def arch(self):
+ return "src"
+
+ @property
+ def name(self):
+ s = "%s:" % self.source.name
+
+ if self.commit_subject:
+ s += " %s" % self.commit_subject[:60]
+ if len(self.commit_subject) > 60:
+ s += "..."
+ else:
+ s += self.revision[:7]
+
+ return s
+
+ @property
+ def source(self):
+ return self.pakfire.sources.get_by_id(self._data.source_id)
+
+ @property
+ def revision(self):
+ return self._data.revision
+
+ @property
+ def commit_author(self):
+ return self._data.author
+
+ @property
+ def commit_committer(self):
+ return self._data.committer
+
+ @property
+ def commit_subject(self):
+ return self._data.subject
+
+ @property
+ def commit_body(self):
+ return self._data.body
+
+ @property
+ def commit_date(self):
+ return self._data.date
+
+ @property
+ def recipients(self):
+ l = [self.commit_author, self.commit_committer,]
+
+ return set(l)
+
+ def send_finished_message(self):
+ # We do not send finish messages on source build jobs.
+ pass
--- /dev/null
+#!/usr/bin/python
+
+import datetime
+import hashlib
+import logging
+import random
+import string
+import time
+
+import base
+
+class Builders(base.Object):
+ def get_all(self):
+ builders = self.db.query("SELECT id FROM builders WHERE deleted = 'N' ORDER BY name")
+
+ return [Builder(self.pakfire, b.id) for b in builders]
+
+ def get_by_id(self, id):
+ if not id:
+ return
+
+ return Builder(self.pakfire, id)
+
+ def get_by_name(self, name):
+ builder = self.db.get("SELECT id FROM builders WHERE name = %s LIMIT 1", name)
+
+ if builder:
+ return Builder(self.pakfire, builder.id)
+
+ def get_all_arches(self):
+ arches = set()
+
+ for result in self.db.query("SELECT DISTINCT arches FROM builders"):
+ if not result.arches:
+ continue
+
+ _arches = result.arches.split()
+
+ for arch in _arches:
+ arches.add(arch)
+
+ return sorted(arches)
+
+
+class Builder(base.Object):
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+
+ self.id = id
+
+ self.data = self.db.get("SELECT * FROM builders WHERE id = %s", self.id)
+
+ def __cmp__(self, other):
+ return cmp(self.id, other.id)
+
+ @classmethod
+ def new(cls, pakfire, name):
+ id = pakfire.db.execute("INSERT INTO builders(name) VALUES(%s)", name)
+
+ builder = cls(pakfire, id)
+ builder.regenerate_passphrase()
+
+ return builder
+
+ def set(self, key, value):
+ self.db.execute("UPDATE builders SET %s = %%s WHERE id = %%s LIMIT 1" % key,
+ value, self.id)
+ self.data[key] = value
+
+ def delete(self):
+ self.set("disabled", "Y")
+ self.set("deleted", "Y")
+
+ def regenerate_passphrase(self):
+ source = string.ascii_letters + string.digits
+ passphrase = "".join(random.sample(source * 30, 20))
+
+ self.set("passphrase", passphrase)
+
+ def validate_passphrase(self, passphrase):
+ return self.passphrase == passphrase
+
+ def update_info(self, loadavg, cpu_model, memory, arches):
+ self.set("loadavg", loadavg)
+ self.set("cpu_model", cpu_model)
+ self.set("memory", memory)
+ self.set("arches", arches)
+
+ def get_enabled(self):
+ return not self.disabled
+
+ def set_enabled(self, value):
+ if value:
+ value = "N"
+ else:
+ value = "Y"
+
+ self.set("disabled", value)
+
+ enabled = property(get_enabled, set_enabled)
+
+ @property
+ def disabled(self):
+ return self.data.disabled == "Y"
+
+ @property
+ def arches(self):
+ arches = ["noarch",]
+
+ if self.build_src:
+ arches.append("src")
+
+ if self.data.arches:
+ arches += self.data.arches.split()
+
+ return sorted(arches)
+
+ def get_build_src(self):
+ return self.data.build_src == "Y"
+
+ def set_build_src(self, value):
+ if value:
+ value = "Y"
+ else:
+ value = "N"
+
+ self.set("build_src", value)
+
+ build_src = property(get_build_src, set_build_src)
+
+ def get_build_bin(self):
+ return self.data.build_bin == "Y"
+
+ def set_build_bin(self, value):
+ if value:
+ value = "Y"
+ else:
+ value = "N"
+
+ self.set("build_bin", value)
+
+ build_bin = property(get_build_bin, set_build_bin)
+
+ def get_build_test(self):
+ return self.data.build_test == "Y"
+
+ def set_build_test(self, value):
+ if value:
+ value = "Y"
+ else:
+ value = "N"
+
+ self.set("build_test", value)
+
+ build_test = property(get_build_test, set_build_test)
+
+ def get_max_jobs(self):
+ return self.data.max_jobs
+
+ def set_max_jobs(self, value):
+ self.set("max_jobs", value)
+
+ max_jobs = property(get_max_jobs, set_max_jobs)
+
+ @property
+ def name(self):
+ return self.data.name
+
+ @property
+ def hostname(self):
+ return self.name
+
+ @property
+ def passphrase(self):
+ return self.data.passphrase
+
+ @property
+ def loadavg(self):
+ if not self.status == "ONLINE":
+ return 0
+
+ return self.data.loadavg
+
+ @property
+ def cpu_model(self):
+ return self.data.cpu_model
+
+ @property
+ def memory(self):
+ return self.data.memory * 1024
+
+ @property
+ def status(self):
+ if self.disabled:
+ return "DISABLED"
+
+ threshhold = datetime.datetime.utcnow() - datetime.timedelta(minutes=6)
+
+ if self.data.updated < threshhold:
+ return "OFFLINE"
+
+ return "ONLINE"
+
+ @property
+ def builds(self):
+ return self.pakfire.builds.get_by_host(self.id)
+
+ @property
+ def active_builds(self):
+ return self.pakfire.builds.get_active(host_id=self.id)
--- /dev/null
+#!/usr/bin/python
+
+N_ = lambda x: x
+
+# NEVER EVER CHANGE ONE OF THE IDS!
+LOG_BUILD_CREATED = 1
+LOG_BUILD_STATE_PENDING = 2
+LOG_BUILD_STATE_DISPATCHING = 3
+LOG_BUILD_STATE_RUNNING = 4
+LOG_BUILD_STATE_FAILED = 5
+LOG_BUILD_STATE_PERM_FAILED = 6
+LOG_BUILD_STATE_DEP_ERROR = 7
+LOG_BUILD_STATE_WAITING = 8
+LOG_BUILD_STATE_FINISHED = 9
+LOG_BUILD_STATE_UNKNOWN = 10
+LOG_BUILD_STATE_UPLOADING = 11
+
+LOG_PKG_CREATED = 20
+
+
+LOG2MSG = {
+ LOG_BUILD_CREATED : N_("Build job created"),
+ LOG_BUILD_STATE_PENDING : N_("Build job is now pending"),
+ LOG_BUILD_STATE_DISPATCHING : N_("Build job is dispatching"),
+ LOG_BUILD_STATE_RUNNING : N_("Build job is running"),
+ LOG_BUILD_STATE_FAILED : N_("Build job has failed"),
+ LOG_BUILD_STATE_PERM_FAILED : N_("Build job has permanently failed"),
+ LOG_BUILD_STATE_DEP_ERROR : N_("Build job has dependency errors"),
+ LOG_BUILD_STATE_WAITING : N_("Build job is waiting for the source package"),
+ LOG_BUILD_STATE_FINISHED : N_("Build job is finished"),
+ LOG_BUILD_STATE_UNKNOWN : N_("Build job has an unknown state"),
+ LOG_BUILD_STATE_UPLOADING : N_("Build job is uploading"),
+}
+
+UPLOADS_DIR = "/var/tmp/pakfire/uploads"
+
+MSG_BUILD_FAILED_SUBJECT = N_("[%(build_name)s] Build job failed.")
+MSG_BUILD_FAILED = N_("""\
+The build job "%(build_name)s" has failed.
+
+This could have a couple of reasons and needs to be investigated by you.
+
+Here is more information about the incident:
+
+ Build name: %(build_name)s
+ Build host: %(build_host)s
+
+Click on this link to get all details about the build:
+ http://pakfire.ipfire.org/build/%(build_uuid)s
+
+Sincerely,
+ The Pakfire Build Service""")
+
+
+MSG_BUILD_FINISHED_SUBJECT = N_("[%(build_name)s] Build job finished.")
+MSG_BUILD_FINISHED = N_("""\
+The build job "%(build_name)s" has finished.
+
+If you are the maintainer, it is up to you to push it to one or more repositories.
+
+Click on this link to get all details about the build:
+ http://pakfire.ipfire.org/build/%(build_uuid)s
+
+Sincerely,
+ The Pakfire Build Service""")
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import tornado.database
+
+class Connection(tornado.database.Connection):
+ def __init__(self, *args, **kwargs):
+ logging.debug("Creating new database connection: %s" % args[1])
+
+ tornado.database.Connection.__init__(self, *args, **kwargs)
+
+ def update(self, table, item_id, **items):
+ query = "UPDATE %s SET " % table
+
+ if not items:
+ return
+
+ keys = []
+ for k, v in items.items():
+ # Never update id
+ if k == "id":
+ continue
+
+ keys.append("`%s`='%s'" % (k, v))
+
+ query += ", ".join(keys)
+ query += " WHERE id=%s" % item_id
+
+ return self.execute(query)
+
+ def insert(self, table, **items):
+ query = "INSERT INTO %s" % table
+
+ keys = []
+ vals = []
+
+ for k, v in items.items():
+ # Never insert id
+ if k == "id":
+ continue
+
+ keys.append(k)
+ vals.append("`%s`" % v)
+
+ query += "(%s)"% ", ".join(keys)
+ query += " VALUES(%s)" % ", ".join(vals)
+
+ return self.execute(query)
+
+ def _execute(self, cursor, query, parameters):
+ logging.debug("Executing query: %s" % (query % parameters))
+
+ return tornado.database.Connection._execute(self, cursor, query, parameters)
+
--- /dev/null
+#!/usr/bin/python
+
+import base
+from repository import Repository
+
+class Distributions(base.Object):
+ def get_all(self):
+ distros = self.db.query("SELECT id FROM distributions ORDER BY name")
+
+ return [Distribution(self.pakfire, d.id) for d in distros]
+
+ def get_by_id(self, id):
+ distro = self.db.get("SELECT id FROM distributions WHERE id = %s LIMIT 1", id)
+
+ if distro:
+ return Distribution(self.pakfire, distro.id)
+
+ def get_by_name(self, name):
+ distro = self.db.get("SELECT id FROM distributions WHERE sname = %s LIMIT 1", name)
+
+ if distro:
+ return Distribution(self.pakfire, distro.id)
+
+
+class Distribution(base.Object):
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+ self.id = id
+
+ self._data = \
+ self.db.get("SELECT * FROM distributions WHERE id = %s", self.id)
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.name)
+
+ def set(self, key, value):
+ self.db.execute("UPDATE distributions SET %s = %%s WHERE id = %%s" % key,
+ value, self.id)
+ self._data[key] = value
+
+ @property
+ def info(self):
+ return {
+ "name" : self.name,
+ "sname" : self.sname,
+ "slogan" : self.slogan,
+ "arches" : self.arches,
+ "vendor" : self.vendor,
+ "description" : self.description,
+ }
+
+ @property
+ def name(self):
+ return self._data.name
+
+ @property
+ def sname(self):
+ return self._data.sname
+
+ @property
+ def slogan(self):
+ return self._data.slogan
+
+ @property
+ def arches(self):
+ arches = self._data.arches.split()
+
+ return sorted(arches)
+
+ @property
+ def vendor(self):
+ return self._data.vendor
+
+ @property
+ def description(self):
+ return self._data.description or ""
+
+ @property
+ def repositories(self):
+ repos = self.db.query("SELECT id FROM repositories WHERE distro_id = %s", self.id)
+
+ return sorted([Repository(self.pakfire, r.id) for r in repos])
+
+ def get_repo(self, name):
+ repo = self.db.get("SELECT id FROM repositories WHERE distro_id = %s AND name = %s",
+ self.id, name)
+
+ return Repository(self.pakfire, repo.id)
+
+ @property
+ def comprehensive_repositories(self):
+ return [r for r in self.repositories if r.comprehensive]
+
+ def add_repository(self, name, description):
+ return Repository.new(self.pakfire, self, name, description)
+
+ @property
+ def sources(self):
+ return self.pakfire.sources.get_by_distro(self)
+
+ @property
+ def log(self):
+ return self.db.query("SELECT * FROM log WHERE distro_id = %s ORDER BY time DESC", self.id)
+
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import tornado.ioloop
+
+import base
+
+managers = []
+
+class Manager(base.Object):
+ def __init__(self, pakfire):
+ base.Object.__init__(self, pakfire)
+
+ self.pc = tornado.ioloop.PeriodicCallback(self, self.timeout * 1000)
+
+ logging.info("%s was initialized." % self.__class__.__name__)
+
+ self()
+
+ def __call__(self):
+ logging.info("%s main method was called." % self.__class__.__name__)
+
+ timeout = self.do()
+
+ if timeout is None:
+ timeout = self.timeout
+
+ # Update callback_time.
+ self.pc.callback_time = timeout * 1000
+ logging.debug("Next call will be in ~%.2f seconds." % timeout)
+
+ @property
+ def timeout(self):
+ """
+ Return a new callback timeout in seconds.
+ """
+ raise NotImplementedError
+
+ def do(self):
+ raise NotImplementedError
+
+
+class MessagesManager(Manager):
+ @property
+ def messages(self):
+ """
+ Shortcut to messages object.
+ """
+ return self.pakfire.messages
+
+ @property
+ def timeout(self):
+ # If we have messages, we should run as soon as possible.
+ if self.messages.count:
+ return 0
+
+ # Otherwise we sleep for "mesages_interval"
+ return self.settings.get_int("messages_interval", 10)
+
+ def do(self):
+ logging.info("Sending a bunch of messages.")
+
+ # Send up to 10 messages and return.
+ self.messages.send_messages(limit=10)
+
+
+managers.append(MessagesManager)
+
+
+class SourceManager(Manager):
+ @property
+ def sources(self):
+ return self.pakfire.sources
+
+ @property
+ def timeout(self):
+ return self.settings.get_int("source_update_interval", 60)
+
+ def do(self):
+ for source in self.sources.get_all():
+ # If the repository is not yet cloned, we need to make a local
+ # clone to work with.
+ if not source.is_cloned():
+ source.clone()
+
+ # If we have cloned a new repository, we exit to not get over
+ # the time treshold.
+ return 0
+
+ # Otherwise we just fetch updates.
+ else:
+ source.fetch()
+
+ # Import all new revisions.
+ source.import_revisions()
+
+ # If there are revisions left, we exit and want be called immediately
+ # again.
+ if source._git_rev_list():
+ return 0
+
+
+managers.append(SourceManager)
+
+
+class BuildsManager(Manager):
+ @property
+ def timeout(self):
+ return self.settings.get_int("build_keepalive_interval", 900)
+
+ def do(self):
+ for build in self.pakfire.builds.get_all_but_finished():
+ logging.debug("Processing unfinished build: %s" % build.name)
+ build.keepalive()
+
+
+managers.append(BuildsManager)
+
+
+class UploadsManager(Manager):
+ @property
+ def timeout(self):
+ return self.settings.get_int("uploads_remove_interval", 3600)
+
+ def do(self):
+ self.pakfire.uploads.cleanup()
+
+
+managers.append(UploadsManager)
+
+#class NotificationManager(Manager):
+# @property
+# def timeout(self):
+# return Settings().get_int("notification_interval")
+#
+# def do(self):
+# tasks = self.tasks.get(type="notification", state="pending")
+#
+# for task in tasks:
+# logging.debug("Running task %s" % task)
+#
+# task.run()
+#
+#managers.append(NotificationManager)
+#
+#
+#class RepositoryUpdateManager(Manager):
+# @property
+# def timeout(self):
+# return Settings().get_int("repository_update_interval")
+#
+# def do(self):
+# tasks = self.tasks.get(type="repository_update", state="pending")
+#
+# for task in tasks:
+# logging.debug("Running task %s" % task)
+#
+# task.run()
+#
+#managers.append(RepositoryUpdateManager)
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import smtplib
+import subprocess
+import tornado.locale
+
+from email.mime.text import MIMEText
+
+import base
+
+class Messages(base.Object):
+ def add(self, to, subject, text, frm=None):
+ subject = "%s %s" % (self.pakfire.settings.get("email_subject_prefix"), subject)
+
+ # Get default sender from the settings.
+ if not frm:
+ frm = self.pakfire.settings.get("email_from")
+
+ self.db.execute("INSERT INTO user_messages(frm, `to`, subject, text)"
+ " VALUES(%s, %s, %s, %s)", frm, to, subject, text)
+
+ def get_all(self, limit=None):
+ query = "SELECT * FROM user_messages ORDER BY time_added ASC"
+ if limit:
+ query += " LIMIT %d" % limit
+
+ return self.db.query(query)
+
+ @property
+ def count(self):
+ ret = self.db.get("SELECT COUNT(*) as count FROM user_messages")
+
+ return ret.count
+
+ def delete(self, id):
+ self.db.execute("DELETE FROM user_messages WHERE id = %s", id)
+
+ def send_to_all(self, recipients, subject, body, format=None):
+ """
+ Sends an email to all recipients and does the translation.
+ """
+ if not format:
+ format = {}
+
+ for recipient in recipients:
+ if not recipient:
+ logging.warning("Ignoring empty recipient.")
+ continue
+
+ # We try to get more information about the user from the database
+ # like the locale.
+ user = self.pakfire.users.get_by_email(recipient)
+ if user:
+ # Get locale that the user prefers.
+ locale = tornado.locale.get(user.locale)
+ else:
+ # Get the default locale.
+ locale = tornado.locale.get()
+
+ # Translate the message.
+ _subject = locale.translate(subject) % format
+ _body = locale.translate(body) % format
+
+ # If we know the real name of the user we add the realname to
+ # the recipient field.
+ if user:
+ recipient = "%s <%s>" % (user.realname, user.email)
+
+ # Add the message to the queue that it is sent.
+ self.add(recipient, _subject, _body)
+
+ def send_messages(self, limit=10):
+ i = 0
+ for msg in self.get_all(limit=10):
+ try:
+ self.send_msg(msg)
+ except Exception, e:
+ logging.debug("There was an error sending mail: %s" % e)
+ raise
+ else:
+ # If everything was okay, we can delete the message in the database.
+ self.delete(msg.id)
+ i += 1
+
+ logging.debug("Successfully sent %s message(s). %s still in queue." \
+ % (i, self.count))
+
+ @staticmethod
+ def send_msg(msg):
+ if not msg.to:
+ logging.warning("Dropping message with empty recipient.")
+ return
+
+ logging.debug("Sending mail to %s: %s" % (msg.to, msg.subject))
+
+ # Preparing mail content.
+ mail = MIMEText(msg.text.encode("latin-1"))
+ mail["From"] = msg.frm.encode("latin-1")
+ mail["To"] = msg.to.encode("latin-1")
+ mail["Subject"] = msg.subject.encode("latin-1")
+ #mail["Content-type"] = "text/plain; charset=utf-8"
+
+ #smtp = smtplib.SMTP("localhost")
+ #smtp.sendmail(msg.frm, msg.to.split(", "), mail.as_string())
+ #smtp.quit()
+
+ # We use sendmail here to workaround problems with the mailserver
+ # communication.
+ # So, just call /usr/lib/sendmail, pipe the message in and see
+ # what sendmail tells us in return.
+ sendmail = ["/usr/lib/sendmail", "-t"]
+ p = subprocess.Popen(sendmail, bufsize=0, close_fds=True,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+
+ stdout, stderr = p.communicate(mail.as_string())
+
+ # Wait until sendmail has finished.
+ p.wait()
+
+ if p.returncode:
+ raise Exception, "Could not send mail: %s" % stderr
--- /dev/null
+#!/usr/bin/python
+
+from __future__ import division
+
+import hashlib
+import tarfile
+
+def friendly_size(s):
+ units = ("B", "K", "M", "G", "T")
+
+ i = 0
+ while s >= 1024 and i < len(units):
+ s /= 1024
+ i += 1
+
+ return "%.1f %s" % (s, units[i])
+
+def calc_hash1(filename):
+ f = open(filename)
+
+ h = hashlib.sha1()
+ while True:
+ buf = f.read(1024)
+ if not buf:
+ break
+
+ h.update(buf)
+
+ f.close()
+
+ return h.hexdigest()
+
+
+def guess_filetype(filename):
+ # XXX very cheap check. Need to do better here.
+ if tarfile.is_tarfile(filename):
+ return "pkg"
+
+ elif filename.endswith(".log"):
+ return "log"
+
+ return "unknown"
--- /dev/null
+#!/usr/bin/python
+
+import os
+import shutil
+import time
+import urlparse
+import uuid
+
+import base
+import build
+
+from constants import *
+
+class Packages(base.Object):
+ def get_by_id(self, id):
+ return Package(self.pakfire, id)
+
+ def get_all_names(self):
+ names = self.db.query("SELECT DISTINCT name FROM packages ORDER BY name")
+
+ return [n.name for n in names]
+
+ def get_by_name(self, name):
+ pkgs = self.db.query("SELECT id FROM packages WHERE name = %s ORDER BY id ASC", name)
+
+ return [Package(self.pakfire, pkg.id) for pkg in pkgs]
+
+ find_by_name = get_by_name
+
+ def search(self, query):
+ # Search for an exact match
+ pkgs = self.db.query("SELECT DISTINCT name, summary FROM packages"
+ " WHERE name = %s LIMIT 1", query)
+
+ if not pkgs:
+ query = "%%%s%%" % query
+ pkgs = self.db.query("SELECT DISTINCT name, summary FROM packages"
+ " WHERE name LIKE %s OR summary LIKE %s OR description LIKE %s"
+ " ORDER BY name", query, query, query)
+
+ return pkgs
+
+ def get_by_tuple(self, name, epoch, version, release):
+ pkg = self.db.get("""SELECT id FROM packages WHERE name = %s AND
+ epoch = %s AND version = %s AND `release` = %s LIMIT 1""",
+ name, epoch, version, release)
+
+ if pkg:
+ return Package(self.pakfire, pkg.id)
+
+ def get_with_file_by_uuid(self, uuid):
+ file = self.db.get("SELECT id, type, pkg_id FROM package_files WHERE uuid = %s LIMIT 1", uuid)
+
+ if not file:
+ return None, None
+
+ pkg = Package(self.pakfire, file.pkg_id)
+
+ if file.type == SourcePackageFile.type:
+ file = SourcePackageFile(self.pakfire, file.id)
+
+ elif file.type == BinaryPackageFile.type:
+ file = BinaryPackageFile(self.pakfire, file.id)
+
+ return pkg, file
+
+ def get_comments(self, limit=50):
+ comments = self.db.query("""SELECT * FROM package_comments
+ ORDER BY time DESC LIMIT %s""", limit)
+
+ return comments
+
+
+class Package(base.Object):
+ """
+ This class represents a package (like source package) that is passed to
+ the buildsystem.
+
+ New objects of this are created by the new() method.
+ """
+
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+ self.id = id
+
+ self._data = self.db.get("SELECT * FROM packages WHERE id = %s", self.id)
+
+ def __cmp__(self, other):
+ # Order alphabetically.
+ return cmp(self.friendly_name, other.friendly_name)
+
+ @classmethod
+ def new(cls, pakfire, file, build):
+ # Check if the package does already exist in the database.
+ pkg = pakfire.packages.get_by_tuple(file.name, file.epoch, file.version,
+ file.release)
+
+ if pkg:
+ return pkg
+
+ id = pakfire.db.execute("INSERT INTO packages(name, epoch, version,"
+ " `release`, groups, maintainer, license, url, summary, description,"
+ " supported_arches, source_build) VALUES(%s, %s, %s, %s, %s, %s, %s, %s,"
+ " %s, %s, %s, %s)", file.name, file.epoch, file.version, file.release,
+ " ".join(file.groups), file.maintainer, file.license, file.url,
+ file.summary, file.description, file.supported_arches, build.id)
+
+ pkg = cls(pakfire, id)
+ pkg.add_file(file, build)
+
+ # Create all needed build jobs.
+ pkg.create_builds()
+
+ return pkg
+
+ @property
+ def name(self):
+ return self._data.get("name")
+
+ @property
+ def epoch(self):
+ return self._data.get("epoch")
+
+ @property
+ def version(self):
+ return self._data.get("version")
+
+ @property
+ def release(self):
+ return self._data.get("release")
+
+ @property
+ def friendly_name(self):
+ return "%s-%s" % (self.name, self.friendly_version)
+
+ @property
+ def friendly_version(self):
+ s = "%s-%s" % (self.version, self.release)
+
+ if self.epoch:
+ s = "%d:%s" % (self.epoch, s)
+
+ return s
+
+ @property
+ def distro(self):
+ return self.source.distro
+
+ def get_state(self):
+ return self._data.get("state")
+
+ def set_state(self, state):
+ self.db.execute("UPDATE packages SET state = %s WHERE id = %s", state, self.id)
+ self._data["state"] = state
+
+ if state == "finished":
+ # Add package to all comprehensive repositories.
+ for repo in self.distro.comprehensive_repositories:
+ self.add_to_repository(repo)
+
+ state = property(get_state, set_state)
+
+ @property
+ def summary(self):
+ return self._data.get("summary")
+
+ @property
+ def description(self):
+ return self._data.get("description")
+
+ @property
+ def groups(self):
+ return self._data.get("groups")
+
+ @property
+ def url(self):
+ return self._data.get("url")
+
+ @property
+ def maintainer(self):
+ return self._data.get("maintainer")
+
+ @property
+ def license(self):
+ return self._data.get("license")
+
+ @property
+ def source(self):
+ return self.source_build.source
+
+ def get_files(self, type=None):
+ files = []
+
+ query = "SELECT id, type FROM package_files WHERE pkg_id = %s"
+ if type:
+ query += " AND type = '%s'" % type
+
+ for p in self.db.query(query, self.id):
+ for p_class in (SourcePackageFile, BinaryPackageFile, LogFile):
+ if p.type == p_class.type:
+ p = p_class(self.pakfire, p.id)
+ break
+ else:
+ continue
+
+ files.append(p)
+
+ return files
+
+ @property
+ def packagefiles(self):
+ return [f for f in self.get_files() if isinstance(f, PackageFile)]
+
+ @property
+ def logfiles(self):
+ return [f for f in self.get_files() if isinstance(f, LogFile)]
+
+ @property
+ def sourcefile(self):
+ sourcefiles = [f for f in self.get_files() if isinstance(f, SourcePackageFile)]
+
+ assert len(sourcefiles) <= 1
+
+ if sourcefiles:
+ return sourcefiles[0]
+
+ @property
+ def log(self):
+ return self.db.query("SELECT * FROM log WHERE pkg_id = %s AND"
+ " build_id IS NULL ORDER BY time DESC", self.id)
+
+ def add_file(self, pkg, build):
+ path = os.path.join(
+ self.name,
+ "%s-%s-%s" % (self.epoch, self.version, self.release),
+ build.arch,
+ os.path.basename(pkg.filename))
+ abspath = os.path.join(self.source.targetpath, path)
+
+ if os.path.exists(abspath):
+ # Check if file is already in the database and return the id.
+ file = self.db.get("SELECT id FROM package_files WHERE path = %s LIMIT 1", path)
+ if file:
+ return file.id
+
+ os.unlink(abspath)
+
+ # Save the data to a file.
+ dirname = os.path.dirname(abspath)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ # Copy file to target directory.
+ shutil.copy(pkg.filename, abspath)
+
+ id = self.db.execute("INSERT INTO package_files(path, pkg_id, source_id,"
+ " type, arch, summary, description, requires, provides, obsoletes,"
+ " conflicts, url, license, maintainer, size, hash1, build_host,"
+ " build_id, build_time, uuid) VALUES(%s, %s, %s, %s, %s, %s, %s, %s,"
+ " %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
+ path, self.id, build.source.id, pkg.type, pkg.arch, pkg.summary,
+ pkg.description, " ".join(pkg.requires), " ".join(pkg.provides),
+ " ".join(pkg.obsoletes), " ".join(pkg.conflicts), pkg.url, pkg.license,
+ pkg.maintainer, pkg.size, pkg.hash1, pkg.build_host, pkg.build_id,
+ pkg.build_time, pkg.uuid)
+
+ # Add package filelist
+ self.db.executemany("INSERT INTO filelists(pkgfile_id, name, size, hash1)"
+ " VALUES(%s, %s, %s, %s)", [(id, f, 0, "0"*40) for f in pkg.filelist])
+
+ return id
+
+ def add_log(self, filename, build):
+ path = os.path.join(
+ self.name,
+ "%s-%s-%s" % (self.epoch, self.version, self.release),
+ "logs/build.%s.%s.log" % (build.arch, build.retries))
+ abspath = os.path.join(self.source.targetpath, path)
+
+ # Save the data to a file.
+ dirname = os.path.dirname(abspath)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ # Move file to target directory.
+ shutil.copy(filename, abspath)
+
+ self.db.execute("INSERT INTO package_files(path, source_id, pkg_id,"
+ " type, arch, build_id) VALUES(%s, %s, %s, %s, %s, %s)", path,
+ self.source.id, self.id, "log", build.arch, build.uuid)
+
+ @property
+ def supported_arches(self):
+ supported = self._data.get("supported_arches") or ""
+ supported = supported.split()
+
+ arches = []
+
+ if "all" in supported:
+ # Inherit all supported architectures from the distribution.
+ arches += self.distro.arches
+ supported.remove("all")
+
+ excludes = [a[1:] for a in supported if a.startswith("-")]
+
+ if excludes:
+ _arches = []
+
+ for arch in arches:
+ if arch in excludes:
+ continue
+
+ _arches.append(arch)
+
+ arches = _arches
+
+ includes = [a[1:] for a in supported if not a.startswith("-")]
+
+ # Add explicitely included architectures.
+ arches += includes
+
+ return arches
+
+ def create_builds(self):
+ builds = []
+
+ for arch in self.supported_arches:
+ b = build.BinaryBuild.new(self.pakfire, self, arch)
+
+ builds.append(b)
+
+ return builds
+
+ @property
+ def builds(self):
+ if not hasattr(self, "_builds"):
+ self._builds = self.pakfire.builds.get_by_pkgid(self.id)
+
+ if self.source_build:
+ self._builds.append(self.source_build)
+
+ return self._builds
+
+ @property
+ def source_build(self):
+ return self.pakfire.builds.get_by_id(self._data.source_build)
+
+ def update(self):
+ if not self.state == "finished":
+ # Check if all builds are finished and set package state to finished, too.
+ for build in self.builds:
+ if not build.finished:
+ return
+
+ self.state = "finished"
+
+ @property
+ def repositories(self):
+ repos = self.db.query("SELECT repo_id FROM repository_packages WHERE pkg_id = %s", self.id)
+
+ return [self.pakfire.repos.get_by_id(r.id) for r in repos]
+
+ def add_to_repository(self, repo):
+ #repo.add_package(self)
+ repo.register_action("add", self.id)
+
+ @property
+ def comments(self):
+ comments = self.db.query("""SELECT * FROM package_comments
+ WHERE pkg_id = %s ORDER BY time DESC""", self.id)
+
+ return comments
+
+ def comment(self, user_id, text, vote):
+ id = self.db.execute("""INSERT INTO package_comments(pkg_id, user_id,
+ text, vote) VALUES(%s, %s, %s, %s)""", self.id, user_id, text, vote)
+
+ vote2credit = { "up" : 1, "down" : -1, "none" : 0, }
+ try:
+ self.credits = self.credits + vote2credit[vote]
+
+ except KeyError:
+ pass
+
+ return id
+
+ def get_credits(self):
+ return self._data.credits
+
+ def set_credits(self, credits):
+ self.db.execute("UPDATE packages SET credits = %s WHERE id = %s",
+ credits, self.id)
+ self._data["credits"] = credits
+
+ credits = property(get_credits, set_credits)
+
+ def get_actions(self):
+ return self.pakfire.repos.get_actions_by_pkgid(self.id)
+
+
+class File(base.Object):
+ type = None
+
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+ self.id = id
+
+ self._data = \
+ self.db.get("SELECT * FROM package_files WHERE id = %s", self.id)
+
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.name)
+
+ def __cmp__(self, other):
+ return cmp(self.name, other.name)
+
+ @property
+ def name(self):
+ return os.path.basename(self.path)
+
+ @property
+ def path(self):
+ path = self._data.get("path")
+
+ if path.startswith("/"):
+ path = path[1:]
+
+ return path
+
+ @property
+ def abspath(self):
+ return os.path.join(self.source.targetpath, self.path)
+
+ @property
+ def download(self):
+ path = self.abspath.split("/")
+
+ while path:
+ if path[0] == "packages":
+ break
+ path = path[1:]
+
+ return "%s/%s" % (self.pakfire.settings.get("baseurl"), "/".join(path))
+
+ @property
+ def source(self):
+ return self.pakfire.sources.get_by_id(self._data.source_id)
+
+
+class PackageFile(File):
+ @property
+ def filelist(self):
+ return self.db.query("SELECT name, size, hash1 FROM filelists"
+ " WHERE pkgfile_id = %s", self.id)
+
+ @property
+ def hash1(self):
+ return self._data.get("hash1")
+
+ @property
+ def uuid(self):
+ return self._data.get("uuid")
+
+ @property
+ def summary(self):
+ return self._data.get("summary")
+
+ @property
+ def description(self):
+ return self._data.get("description")
+
+ @property
+ def license(self):
+ return self._data.get("license")
+
+ @property
+ def size(self):
+ return self._data.get("size")
+
+ @property
+ def url(self):
+ return self._data.get("url")
+
+ @property
+ def maintainer(self):
+ return self._data.get("maintainer")
+
+ @property
+ def build_id(self):
+ return self._data.get("build_id")
+
+ @property
+ def build_host(self):
+ return self._data.get("build_host")
+
+ @property
+ def build_time(self):
+ return self._data.get("build_time")
+
+ @property
+ def build_date(self):
+ return time.strftime("%a, %d %b %Y %H:%M:%S +0000",
+ time.gmtime(self.build_time))
+
+ @property
+ def provides(self):
+ return self._data.get("provides").split()
+
+ @property
+ def requires(self):
+ requires = self._data.get("requires").split()
+
+ return sorted(requires)
+
+ @property
+ def obsoletes(self):
+ obsoletes = self._data.get("obsoletes").split()
+
+ return sorted(obsoletes)
+
+ @property
+ def conflicts(self):
+ conflicts = self._data.get("conflicts").split()
+
+ return sorted(conflicts)
+
+
+class BinaryPackageFile(PackageFile):
+ type = "binary"
+
+
+class SourcePackageFile(PackageFile):
+ type = "source"
+
+
+class LogFile(File):
+ type = "log"
+
--- /dev/null
+#!/usr/bin/python
+
+import base
+import packages
+
+class Repositories(base.Object):
+ def get_all(self):
+ repos = self.db.query("SELECT id FROM repositories")
+
+ return [Repository(self.pakfire, r.id) for r in repos]
+
+ def get_by_id(self, repo_id):
+ repo = self.db.get("SELECT id FROM repositories WHERE id = %s", repo_id)
+
+ if repo:
+ return Repository(self.pakfire, repo.id)
+
+ def get_needs_update(self, limit=None):
+ query = "SELECT id FROM repositories WHERE needs_update = 'Y'"
+ query += " ORDER BY last_update ASC"
+
+ # Append limit if any
+ if limit:
+ query += " LIMIT %d" % limit
+
+ repos = self.db.query(query)
+
+ return [Repository(self.pakfire, r.id) for r in repos]
+
+ def get_action_by_id(self, action_id):
+ action = self.db.get("SELECT id, repo_id FROM repository_actions WHERE id = %s"
+ " LIMIT 1" , action_id)
+
+ assert action
+
+ repo = self.get_by_id(action.repo_id)
+
+ return RepoAction(self.pakfire, repo, action.id)
+
+ def get_actions_by_pkgid(self, pkgid):
+ actions = self.db.query("SELECT id FROM repository_actions WHERE pkg_id = %s", pkg_id)
+
+ return [RepoAction(self.pakfire, self, a.id) for a in actions]
+
+
+class Repository(base.Object):
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+ self.id = id
+
+ self.data = self.db.get("SELECT * FROM repositories WHERE id = %s", self.id)
+
+ def __cmp__(self, other):
+ if not self.upstream_id or not other.upstream_id:
+ return 0
+
+ if self.id == other.upstream_id:
+ return -1
+
+ elif self.upstream_id == other.id:
+ return 1
+
+ def next(self):
+ repo = self.db.get("SELECT id FROM repositories WHERE upstream = %s"
+ " LIMIT 1", self.id)
+
+ if repo:
+ return self.pakfire.repos.get_by_id(repo.id)
+
+ def last(self):
+ if self.upstream_id:
+ return self.pakfire.repos.get_by_id(self.upstream_id)
+
+ @property
+ def upstream(self):
+ return self.last()
+
+ @property
+ def is_upstream(self):
+ return not self.upstream
+
+ @classmethod
+ def new(cls, pakfire, distro, name, description):
+ id = pakfire.db.execute("INSERT INTO repositories(distro_id, name, description)"
+ " VALUES(%s, %s, %s)", distro.id, name, description)
+
+ return cls(pakfire, id)
+
+ @property
+ def distro(self):
+ return self.pakfire.distros.get_by_id(self.data.distro_id)
+
+ @property
+ def info(self):
+ return {
+ "id" : self.id,
+ "distro" : self.distro.info,
+ "name" : self.name,
+ "arches" : self.arches,
+ }
+
+ @property
+ def comprehensive(self):
+ return not self.upstream_id
+
+ @property
+ def name(self):
+ return self.data.name
+
+ @property
+ def description(self):
+ return self.data.description
+
+ @property
+ def upstream_id(self):
+ return self.data.upstream
+
+ @property
+ def arches(self):
+ return self.distro.arches
+
+ def get_needs_update(self):
+ return self.data.needs_update
+
+ def set_needs_update(self, val):
+ if val:
+ val = "Y"
+ else:
+ val = "N"
+
+ self.db.execute("UPDATE repositories SET needs_update = %s WHERE id = %s",
+ val, self.id)
+
+ needs_update = property(get_needs_update, set_needs_update)
+
+ @property
+ def credits_needed(self):
+ return self.data.credits_needed
+
+ def add_package(self, pkg_id):
+ id = self.db.execute("INSERT INTO repository_packages(repo_id, pkg_id)"
+ " VALUES(%s, %s)", self.id, pkg_id)
+
+ # Mark, that the repository was altered and needs an update.
+ self.needs_update = True
+
+ return id
+
+ def get_packages(self, _query=None):
+ query = "SELECT pkg_id FROM repository_packages WHERE repo_id = %s"
+ if _query:
+ query = "%s %s" % (query, _query)
+
+ pkgs = self.db.query(query, self.id)
+
+ return sorted([packages.Package(self.pakfire, p.pkg_id) for p in pkgs])
+
+ @property
+ def packages(self):
+ return self.get_packages()
+
+ @property
+ def waiting_packages(self):
+ return self.get_packages("AND state = 'waiting'")
+
+ @property
+ def pushed_packages(self):
+ return self.get_packages("AND state = 'pushed'")
+
+ @property
+ def log(self):
+ return self.db.query("SELECT * FROM log WHERE repo_id = %s ORDER BY time DESC", self.id)
+
+ def register_action(self, action, pkg_id, old_pkg_id=None):
+ assert action in ("add", "remove",)
+ assert pkg_id is not None
+
+ id = self.db.execute("INSERT INTO repository_actions(action, repo_id, pkg_id)"
+ " VALUES(%s, %s, %s)", action, self.id, pkg_id)
+
+ # On upstream repositories, all actions are done immediately.
+ if self.is_upstream:
+ action = RepoAction(self.pakfire, self, id)
+ action.run()
+
+ return id
+
+ def has_actions(self):
+ actions = self.db.get("SELECT COUNT(*) as c FROM repository_actions"
+ " WHERE repo_id = %s", self.id)
+
+ if actions.c:
+ return True
+
+ return False
+
+ def get_actions(self):
+ actions = self.db.query("SELECT id FROM repository_actions"
+ " WHERE repo_id = %s ORDER BY time_added ASC", self.id)
+
+ return [RepoAction(self.pakfire, self, a.id) for a in actions]
+
+
+class RepoAction(base.Object):
+ def __init__(self, pakfire, repo, id):
+ base.Object.__init__(self, pakfire)
+
+ self.repo = repo
+ self.id = id
+
+ self.data = self.db.get("SELECT * FROM repository_actions WHERE id = %s"
+ " LIMIT 1", self.id)
+ assert self.data
+ assert self.data.repo_id == self.repo.id
+
+ @property
+ def action(self):
+ return self.data.action
+
+ @property
+ def pkg_id(self):
+ return self.data.pkg_id
+
+ @property
+ def pkg(self):
+ return self.pakfire.packages.get_by_id(self.pkg_id)
+
+ @property
+ def credits_needed(self):
+ return self.repo.credits_needed - self.pkg.credits
+
+ @property
+ def time_added(self):
+ return self.data.time_added
+
+ def delete(self, who=None):
+ """
+ Delete ourself from the database.
+ """
+ if who and not self.have_permission(who):
+ raise Exception, "Insufficient permissions"
+
+ self.db.execute("DELETE FROM repository_actions WHERE id = %s LIMIT 1", self.id)
+
+ def have_permission(self, who):
+ """
+ Check if "who" has the permission to perform this action.
+ """
+ if who is None:
+ return True
+
+ # Admins are always allowed to do all actions.
+ if who.is_admin():
+ return True
+
+ # If the maintainer matches, he is also allowed.
+ if who.email == self.pkg.maintainer_email:
+ return True
+
+ # Everybody else is denied.
+ return False
+
+ def is_doable(self):
+ return self.credits_needed == 0
+
+ def run(self, who=None):
+ if who and not self.have_permission(who):
+ raise Exception, "Insufficient permissions"
+
+ if self.action == "add":
+ self.repo.add_package(self.pkg_id)
+
+ elif self.action == "remove":
+ self.db.excute("DELETE FROM repository_packages WHERE repo_id = %s"
+ " AND pkg_id = %s LIMIT 1", self.repo.id, self.pkg_id)
+
+ else:
+ raise Exception, "Invalid action"
+
+ # If the action was started by an upstream repository, we add it so
+ # the next one.
+ next = self.repo.next()
+ if next:
+ next.register_action(self.action, self.pkg_id)
+
+ # Delete ourself.
+ self.delete(who)
--- /dev/null
+#!/usr/bin/python
+
+import base
+
+class Settings(base.Object):
+ def query(self, key):
+ return self.db.get("SELECT * FROM settings WHERE k = %s", key)
+
+ def get(self, key, default=None):
+ result = self.query(key)
+ if not result:
+ return default
+
+ return "%s" % result.v
+
+ def get_id(self, key):
+ return self.query(key)["id"]
+
+ def get_int(self, key, default=None):
+ value = self.get(key, default)
+
+ if value is None:
+ return None
+
+ return int(value)
+
+ def get_float(self, key, default=None):
+ value = self.get(key, default)
+
+ if value is None:
+ return None
+
+ return float(value)
+
+ def set(self, key, value):
+ id = self.get(key)
+
+ if not id:
+ self.db.execute("INSERT INTO settings(k, v) VALUES(%s, %s)", key, value)
+ else:
+ self.db.execute("UPDATE settings SET v = %s WHERE id = %s", value, id)
+
+ def get_all(self):
+ attrs = {}
+
+ for s in self.db.query("SELECT * FROM settings"):
+ attrs[s.k] = s.v
+
+ return attrs
--- /dev/null
+#!/usr/bin/python
+
+import datetime
+import logging
+import os
+import subprocess
+
+import base
+import build
+
+class Sources(base.Object):
+ def get_all(self):
+ sources = self.db.query("SELECT id FROM sources ORDER BY id")
+
+ return [Source(self.pakfire, s.id) for s in sources]
+
+ def get_by_id(self, id):
+ source = self.db.get("SELECT id FROM sources WHERE id = %s", id)
+
+ if source:
+ return Source(self.pakfire, source.id)
+
+ def get_by_distro(self, distro):
+ sources = self.db.query("SELECT id FROM sources WHERE distro_id = %s", distro.id)
+
+ return [Source(self.pakfire, s.id) for s in sources]
+
+ def update_revision(self, source_id, revision):
+ query = "UPDATE sources SET revision = %s WHERE id = %s"
+
+ return self.db.execute(query, revision, source_id)
+
+
+class Source(base.Object):
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+
+ self.id = id
+
+ self._data = self.db.get("SELECT * FROM sources WHERE id = %s", self.id)
+
+ def __cmp__(self, other):
+ return cmp(self.id, other.id)
+
+ @property
+ def info(self):
+ return {
+ "id" : self.id,
+ "name" : self.name,
+ "url" : self.url,
+ "path" : self.path,
+ "targetpath" : self.targetpath,
+ "revision" : self.revision,
+ "branch" : self.branch,
+ }
+
+ def _git(self, cmd, path=None):
+ if not path:
+ path = self.path
+
+ cmd = "cd %s && git %s" % (path, cmd)
+
+ logging.debug("Running command: %s" % cmd)
+
+ return subprocess.check_output(["/bin/sh", "-c", cmd])
+
+ def _git_rev_list(self, revision=None):
+ if not revision:
+ revision = self.revision
+
+ command = "rev-list %s..origin/%s" % (revision, self.branch)
+
+ # Get all merge commits.
+ merges = self._git("%s --merges" % command)
+ merges = merges.splitlines()
+
+ revisions = []
+ for commit in self._git(command).splitlines():
+ # Check if commit is a normal commit or merge commit.
+ merge = commit in merges
+
+ revisions.append((commit, merge))
+
+ return [r for r in reversed(revisions)]
+
+ def is_cloned(self):
+ """
+ Say if the repository is already cloned.
+ """
+ return os.path.exists(self.path)
+
+ def clone(self):
+ if self.is_cloned():
+ return
+
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ self._git("clone --bare %s %s" % (self.url, basename), path=dirname)
+
+ def fetch(self):
+ if not self.is_cloned():
+ raise Exception, "Repository was not cloned, yet."
+
+ self._git("fetch")
+
+ def import_revisions(self):
+ # Get all pending revisions.
+ revisions = self._git_rev_list()
+
+ for revision, merge in revisions:
+ # If the revision is not a merge, we do import it.
+ if not merge:
+ self._import_revision(revision)
+
+ # Save revision in database.
+ self.db.execute("UPDATE sources SET revision = %s WHERE id = %s",
+ revision, self.id)
+ self._data["revision"] = revision
+
+ def _import_revision(self, revision):
+ logging.debug("Going to import revision: %s" % revision)
+
+ rev_author = self._git("log -1 --format=\"%%an <%%ae>\" %s" % revision)
+ rev_committer = self._git("log -1 --format=\"%%cn <%%ce>\" %s" % revision)
+ rev_subject = self._git("log -1 --format=\"%%s\" %s" % revision)
+ rev_body = self._git("log -1 --format=\"%%b\" %s" % revision)
+ rev_date = self._git("log -1 --format=\"%%at\" %s" % revision)
+ rev_date = datetime.datetime.utcfromtimestamp(float(rev_date))
+
+ # Convert strings properly. No idea why I have to do that.
+ rev_author = rev_author.decode("latin-1").strip()
+ rev_committer = rev_committer.decode("latin-1").strip()
+ rev_subject = rev_subject.decode("latin-1").strip()
+ rev_body = rev_body.decode("latin-1").rstrip()
+
+ # Create a new source build object.
+ build.SourceBuild.new(self.pakfire, self.id, revision, rev_author,
+ rev_committer, rev_subject, rev_body, rev_date)
+
+ @property
+ def name(self):
+ return self._data.name
+
+ @property
+ def url(self):
+ return self._data.url
+
+ @property
+ def path(self):
+ return self._data.path
+
+ @property
+ def targetpath(self):
+ return self._data.targetpath
+
+ @property
+ def revision(self):
+ return self._data.revision
+
+ @property
+ def branch(self):
+ return self._data.branch
+
+ @property
+ def builds(self):
+ return self.pakfire.builds.get_by_source(self.id)
+
+ @property
+ def distro(self):
+ return self.pakfire.distros.get_by_id(self._data.distro_id)
--- /dev/null
+#!/usr/bin/python
+
+import hashlib
+import logging
+import os
+import pakfire.packages
+import uuid
+
+import base
+import misc
+import packages
+
+from constants import *
+
+class Uploads(base.Object):
+ def get_by_uuid(self, _uuid):
+ upload = self.db.get("SELECT id FROM uploads WHERE uuid = %s", _uuid)
+
+ return Upload(self.pakfire, upload.id)
+
+ def new(self, *args, **kwargs):
+ return Upload.new(self.pakfire, *args, **kwargs)
+
+ def get_all(self):
+ uploads = self.db.query("SELECT id FROM uploads")
+
+ return [Upload(self.pakfire, u.id) for u in uploads]
+
+ def cleanup(self):
+ for upload in self.get_all():
+ upload.cleanup()
+
+
+class Upload(base.Object):
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+
+ self.id = id
+ self.data = self.db.get("SELECT * FROM uploads WHERE id = %s", self.id)
+
+ @classmethod
+ def new(cls, pakfire, builder, filename, size, hash):
+ _uuid = uuid.uuid4()
+
+ id = pakfire.db.execute("INSERT INTO uploads(uuid, builder, filename, size, hash)"
+ " VALUES(%s, %s, %s, %s, %s)", _uuid, builder.id, filename, size, hash)
+
+ upload = cls(pakfire, id)
+
+ # Create space to where we save the data.
+ dirname = os.path.dirname(upload.path)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ # Create empty file.
+ f = open(upload.path, "w")
+ f.close()
+
+ return upload
+
+ @property
+ def uuid(self):
+ return self.data.uuid
+
+ @property
+ def hash(self):
+ return self.data.hash
+
+ @property
+ def filename(self):
+ return self.data.filename
+
+ @property
+ def path(self):
+ return os.path.join(UPLOADS_DIR, self.uuid, self.filename)
+
+ @property
+ def builder(self):
+ return self.pakfire.builders.get_by_id(self.data.builder)
+
+ def append(self, data):
+ logging.debug("Writing %s bytes to %s" % (len(data), self.path))
+
+ f = open(self.path, "ab")
+ f.write(data)
+ f.close()
+
+ def validate(self):
+ # Calculate a hash to validate the upload.
+ hash = misc.calc_hash1(self.path)
+
+ ret = self.hash == hash
+
+ if not ret:
+ logging.error("Hash did not match: %s != %s" % (self.hash, hash))
+
+ return ret
+
+ def remove(self):
+ # Remove the uploaded data.
+ if os.path.exists(self.path):
+ os.unlink(self.path)
+
+ # Delete the upload from the database.
+ self.db.execute("DELETE FROM uploads WHERE id = %s", self.id)
+
+ def time_start(self):
+ return self.data.time_start
+
+ def commit(self, build):
+ # Find out what kind of file this is.
+ filetype = misc.guess_filetype(self.path)
+
+ # If the filetype is unhandled, we remove the file and raise an
+ # exception.
+ if filetype == "unknown":
+ self.remove()
+ raise Exception, "Cannot handle unknown file."
+
+ # If file is a package we open it and insert its information to the
+ # database.
+ if filetype == "pkg":
+ logging.debug("%s is a package file." % self.path)
+ file = pakfire.packages.open(None, None, self.path)
+
+ if file.type == "source":
+ packages.Package.new(self.pakfire, file, build)
+
+ elif file.type == "binary":
+ build.pkg.add_file(file, build)
+
+ elif filetype == "log":
+ build.add_log(self.path)
+
+ # Finally, remove the upload.
+ self.remove()
+
+ def cleanup(self):
+ # Get the seconds since we are running.
+ try:
+ time_running = datetime.datetime.utcnow() - self.time_start
+ time_running = time_running.total_seconds()
+ except:
+ time_running = 0
+
+ # Remove uploads that are older than 24 hours.
+ if time_running >= 3600 * 24:
+ self.remove()
--- /dev/null
+#!/usr/bin/python
+
+import hashlib
+import logging
+import random
+import string
+import urllib
+
+import tornado.locale
+
+import base
+
+class Users(base.Object):
+ def auth(self, name, passphrase):
+ # If either name or passphrase is None, we don't check at all.
+ if None in (name, passphrase):
+ return
+
+ user = self.db.get("""SELECT id FROM users WHERE name = %s
+ AND passphrase = SHA1(%s) AND activated = 'Y' AND deleted = 'N'""",
+ name, passphrase)
+
+ if user:
+ return User(self.pakfire, user.id)
+
+ def register(self, name, passphrase, email, realname, locale=None):
+ return User.new(self.pakfire, name, passphrase, email, realname, locale)
+
+ def name_is_used(self, name):
+ users = self.db.query("SELECT id FROM users WHERE name = %s", name)
+
+ if users:
+ return True
+
+ return False
+
+ def email_is_used(self, email):
+ users = self.db.query("SELECT id FROM users WHERE email = %s", email)
+
+ if users:
+ return True
+
+ return False
+
+ def get_all(self):
+ users = self.db.query("""SELECT id FROM users WHERE activated = 'Y' AND
+ deleted = 'N' ORDER BY realname, name""")
+
+ return [User(self.pakfire, u.id) for u in users]
+
+ def get_by_id(self, id):
+ user = self.db.get("SELECT id FROM users WHERE id = %s LIMIT 1", id)
+
+ if user:
+ return User(self.pakfire, user.id)
+
+ def get_by_name(self, name):
+ user = self.db.get("SELECT id FROM users WHERE name = %s LIMIT 1", name)
+
+ if user:
+ return User(self.pakfire, user.id)
+
+ def get_by_email(self, email):
+ user = self.db.get("SELECT id FROM users WHERE email = %s LIMIT 1", email)
+
+ if user:
+ return User(self.pakfire, user.id)
+
+
+class User(base.Object):
+ def __init__(self, pakfire, id):
+ base.Object.__init__(self, pakfire)
+ self.id = id
+
+ self.data = self.db.get("SELECT * FROM users WHERE id = %s" % self.id)
+
+ def __cmp__(self, other):
+ return cmp(self.id, other.id)
+
+ @classmethod
+ def new(cls, pakfire, name, passphrase, email, realname, locale=None):
+
+ id = pakfire.db.execute("""INSERT INTO users(name, passphrase, email, realname)
+ VALUES(%s, SHA1(%s), %s, %s)""", name, passphrase, email, realname)
+
+ user = cls(pakfire, id)
+
+ # If we have a guessed locale, we save it (for sending emails).
+ if locale:
+ user.locale = locale
+
+ user.send_activation_mail()
+
+ return user
+
+ def delete(self):
+ self.db.execute("UPDATE users SET deleted = 'Y' WHERE id = %s", self.id)
+
+ def activate(self):
+ self.db.execute("UPDATE users SET activated = 'Y' WHERE id = %s", self.id)
+
+ def set_passphrase(self, passphrase):
+ """
+ Update the passphrase the users uses to log on.
+ """
+ self.db.execute("UPDATE users SET passphrase = SHA1(%s) WHERE id = %s",
+ passphrase, self.id)
+
+ passphrase = property(lambda x: None, set_passphrase)
+
+ @property
+ def activation_code(self):
+ return self.data.activation_code
+
+ def get_realname(self):
+ if not self.data.realname:
+ return self.name
+
+ return self.data.realname
+
+ def set_realname(self, realname):
+ self.db.execute("UPDATE users SET realname = %s WHERE id = %s",
+ realname, self.id)
+ self.data["realname"] = realname
+
+ realname = property(get_realname, set_realname)
+
+ @property
+ def name(self):
+ return self.data.name
+
+ def get_email(self):
+ return self.data.email
+
+ def set_email(self, email):
+ if email == self.email:
+ return
+
+ self.db.execute("""UPDATE users SET email = %s, activated = 'N'
+ WHERE id = %s""", email, self.id)
+
+ self.data.update({
+ "email" : email,
+ "activated" : "N",
+ })
+
+ # Inform the user, that he or she has to re-activate the account.
+ self.send_activation_mail()
+
+ email = property(get_email, set_email)
+
+ def get_state(self):
+ return self.data.state
+
+ def set_state(self, state):
+ self.db.execute("UPDATE users SET state = %s WHERE id = %s", state,
+ self.id)
+ self.data["state"] = state
+
+ state = property(get_state, set_state)
+
+ def get_locale(self):
+ return self.data.locale or ""
+
+ def set_locale(self, locale):
+ self.db.execute("UPDATE users SET locale = %s WHERE id = %s", locale,
+ self.id)
+ self.data["locale"] = locale
+
+ locale = property(get_locale, set_locale)
+
+ @property
+ def activated(self):
+ return self.data.activated == "Y"
+
+ @property
+ def registered(self):
+ return self.data.registered
+
+ def gravatar_icon(self, size=128):
+ # construct the url
+ gravatar_url = "http://www.gravatar.com/avatar/" + \
+ hashlib.md5(self.email.lower()).hexdigest() + "?"
+ gravatar_url += urllib.urlencode({'d': "mm", 's': str(size)})
+
+ return gravatar_url
+
+ def is_admin(self):
+ return self.state == "admin"
+
+ def is_tester(self):
+ return self.state == "tester"
+
+ def send_activation_mail(self):
+ logging.debug("Sending activation mail to %s" % self.email)
+
+ # Generate a random activation code.
+ source = string.ascii_letters + string.digits
+ self.data["activation_code"] = "".join(random.sample(source * 20, 20))
+ self.db.execute("UPDATE users SET activation_code = %s WHERE id = %s",
+ self.activation_code, self.id)
+
+ # Get the saved locale from the user.
+ locale = tornado.locale.get(self.locale)
+ _ = locale.translate
+
+ subject = _("Account Activation")
+
+ message = _("You, or somebody using you email address, has registered an account on the Pakfire Build Service.")
+ message += "\n"*2
+ message += _("To activate your account, please click on the link below.")
+ message += "\n"*2
+ message += " http://pakfire.ipfire.org/user/%(name)s/activate/%(activation_code)s" \
+ % { "name" : self.name, "activation_code" : self.activation_code, }
+ message += "\n"*2
+ message += "Sincerely,\n The Pakfire Build Service"
+
+ self.pakfire.messages.add("%s <%s>" % (self.realname, self.email), subject, message)
+
+ @property
+ def comments(self, limit=5):
+ comments = self.db.query("""SELECT * FROM package_comments
+ WHERE user_id = %s ORDER BY time DESC LIMIT %s""", self.id, limit)
+
+ return comments
+
+ @property
+ def log(self):
+ log = self.db.query("SELECT * FROM log WHERE user_id = %s ORDER BY time DESC",
+ self.id)
+
+ return log
--- /dev/null
+
+body {
+ margin: 0;
+ padding: 0;
+ background: #EEEBEC url(../images/img01.jpg) repeat-x left top;
+ font-family: "Verdana", "Deja-Vu Sans", "Bitstream Vera Sans", sans-serif;
+ font-size: 14px;
+ color: #7F7F81;
+}
+
+h1, h2, h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: normal;
+ color: #515151;
+}
+
+h1 {
+ font-size: 1.6em;
+ padding-bottom: 0.2em;
+}
+
+h2 {
+ font-size: 1.4em;
+}
+
+h3 {
+ font-size: 1.4em;
+}
+
+p, ul, ol {
+ margin-top: 0;
+ line-height: 180%;
+}
+
+ul, ol {
+}
+
+a {
+ text-decoration: underline;
+ color: #D90000;
+}
+
+a:hover {
+ text-decoration: none;
+}
+
+img.border {
+ border: 6px solid #E1F1F6;
+}
+
+img.alignleft {
+ float: left;
+ margin-right: 25px;
+}
+
+img.alignright {
+ float: right;
+}
+
+img.aligncenter {
+ margin: 0px auto;
+}
+
+#wrapper {
+ background: url(../images/img01.jpg) repeat-x left top;
+}
+
+/* Header */
+
+#header {
+ width: 980px;
+ height: 51px;
+ margin: 0px auto;
+}
+
+#logo {
+ width: 980px;
+ height: 123px;
+ margin: 0px auto;
+}
+
+#logo p {
+ padding: 55px 0px 0px 30px;
+ color: #a1a1a1;
+}
+
+#logo h1 {
+ float: left;
+ padding: 35px 10px 0px 30px;
+ letter-spacing: -1px;
+ font-size: 42px;
+ color: #FFFFFF;
+}
+
+#logo a {
+ text-decoration: none;
+ color: #FFFFFF;
+}
+
+#user {
+ float: right;
+ padding-top: 0.2em;
+ padding-bottom: 0.2em;
+ padding-right: 1em;
+ padding-left: 1em;
+ margin-right: 0.5em;
+ background-color: white;
+ text-align: right;
+}
+
+#user span {
+ font-weight: bold;
+}
+
+#user a {
+ text-decoration: underline;
+ color: #515151;
+}
+
+#user a:hover {
+ text-decoration: none;
+}
+
+/* Menu */
+
+#menu {
+ width: 980px;
+ height: 28px;
+ margin: 0 auto;
+ padding: 0px 0px 0px 0px;
+}
+
+#menu ul {
+ margin: 0;
+ padding: 0px 0px 0px 4px;
+ list-style: none;
+ line-height: normal;
+}
+
+#menu li {
+ float: left;
+}
+
+#menu a {
+ display: block;
+ float: left;
+ height: 22px;
+ padding: 6px 25px 0px 25px;
+ background: #515151;
+ text-decoration: none;
+ font-size: 12px;
+ font-weight: normal;
+ color: #FFFFFF;
+ border: none;
+}
+
+#menu a:hover {
+ background: #66000f;
+ text-decoration: underline;
+}
+
+#menu li.current_page_item {
+ background: url(../images/img09.jpg) no-repeat left top;
+}
+
+#menu .current_page_item a {
+ background: url(../images/img10.jpg) no-repeat right top;
+}
+
+#menu a:hover {
+ text-decoration: none;
+}
+
+#menu li.search {
+ float: right;
+}
+
+#search {
+ margin-top: 2px;
+ margin-bottom: 2px;
+ border: 1px solid #e1e1e1;
+ width: 200px;
+ height: 20px;
+}
+
+/* Page */
+
+#page {
+ width: 980px;
+ margin: 0 auto;
+ padding: 0;
+ background: #FFFFFF;
+}
+
+#page-bgtop {
+}
+
+#page-bgbtm {
+ margin: 0px;
+ padding: 20px 20px 0px 40px;
+}
+
+/* Content */
+
+#content {
+ float: left;
+ width: 720px;
+ padding: 0px 20px 0px 0px;
+ border-right: 1px dotted #d1d1d1;
+}
+
+/* Sidebar */
+
+#sidebar {
+ float: right;
+ width: 150px;
+ padding: 0px 0px 0px 0px;
+}
+
+#sidebar ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#sidebar li {
+ margin: 0;
+ padding: 0;
+}
+
+#sidebar li ul {
+ margin: 0px 15px;
+ padding-bottom: 30px;
+}
+
+#sidebar li li {
+ padding-left: 15px;
+ line-height: 35px;
+ border-bottom: 1px solid #CCC2A9;
+}
+
+#sidebar li li span {
+ display: block;
+ margin-top: -20px;
+ padding: 0;
+ font-size: 11px;
+ font-style: italic;
+}
+
+#sidebar p {
+ margin: 0 0px;
+ padding: 0px 20px 20px 20px;
+ text-align: justify;
+}
+
+#sidebar a {
+ border: none;
+ color: #7F7F81;
+}
+
+#sidebar a:hover {
+ text-decoration: underline;
+ color: #D90000;
+}
+
+div.submenu ul {
+ margin: 0;
+ padding: 0px 0px 0px 4px;
+ list-style: none;
+ line-height: normal;
+}
+
+div.submenu li {
+ float: left;
+ padding-right: 1px;
+}
+
+div.submenu a {
+ display: block;
+ float: left;
+ height: 18px;
+ padding: 3px 20px 0px 20px;
+ background: #515151;
+ text-decoration: none;
+ font-size: 12px;
+ font-weight: normal;
+ color: #FFFFFF;
+ border: none;
+}
+
+div.submenu a:hover {
+ text-decoration: underline;
+ background: #ffffff;
+ color: #515151;
+}
+
+/* Footer */
+
+#footer-wrapper {
+ overflow: hidden;
+ padding: 0px 0px 0px 0px;
+ background: #2D2722 url(../images/img02.jpg) repeat-x left top;
+}
+
+#footer {
+ clear: both;
+ width: 980px;
+ height: 60px;
+ margin: 20px auto 0px auto;
+}
+
+#footer p {
+ margin: 0;
+ padding: 30px 0px 0px 0px;
+ line-height: normal;
+ font-size: 10px;
+ text-align: center;
+ color: #61544B;
+}
+
+#footer a {
+ color: #61544B;
+}
+
+ul.style1 {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+ line-height: normal;
+ background: url(../images/img04.jpg) repeat left top;
+ text-align: right;
+}
+
+ul.style1 li {
+ height: 23px;
+ padding: 7px 20px 0px 20px;
+ font-size: 12px;
+}
+
+ul.style1 a {
+ float: left;
+ color: #6E6E6E;
+}
+
+#two-columns {
+ overflow: hidden;
+ width: 920px;
+ margin: 0px auto;
+ padding: 30px 30px 0px 30px;
+}
+
+#two-columns h2 {
+ padding: 0px 0px 20px 0px;
+ font-size: 24px;
+ color: #3B3B3B;
+}
+
+#column1 {
+ float: left;
+ width: 310px;
+}
+
+#column2 {
+ float: right;
+ width: 560px;
+}
+
+p.pkg-summary {
+ margin: 1em;
+ padding: 1em;
+ background-color: #e1e1e1;
+ border: 1px solid #515151;
+ font-style: italic;
+}
+
+ul.alphabet {
+ padding: 1em 1em 1em 1em;
+ list-style: none;
+}
+
+ul.alphabet li {
+ float: left;
+}
+
+ul.alphabet a {
+ display: block;
+ padding-left: 4px;
+}
+
+ul.builds {
+ list-style: none;
+ margin-top: 1em;
+}
+
+ul.builds li {
+ height: 40px;
+}
+
+ul.builds a.build {
+ margin-left: -2em;
+ padding-left: 3em;
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+ul.builds a.failed {
+ background: url("../images/icons/build-failed.png") no-repeat 0 50%;
+}
+
+ul.builds a.pending {
+ background: url("../images/icons/build-pending.png") no-repeat 0 50%;
+}
+
+ul.builds a.dependency_error {
+ background: url("../images/icons/build-dependency_error.png") no-repeat 0 50%;
+}
+
+ul.builds a.finished {
+ background: url("../images/icons/build-finished.png") no-repeat 0 50%;
+}
+
+ul.builds a.running {
+ background: url("../images/icons/build-running.png") no-repeat 0 50%;
+}
+
+ul.builds a.unknown {
+ background: url("../images/icons/build-unknown.png") no-repeat 0 50%;
+}
+
+ul.builds a.uploading {
+ background: url("../images/icons/build-uploading.png") no-repeat 0 50%;
+}
+
+ul.builds a.dispatching {
+ background: url("../images/icons/build-dispatching.png") no-repeat 0 50%;
+}
+
+ul.builds a.waiting {
+ background: url("../images/icons/build-waiting.png") no-repeat 0 50%;
+}
+
+ul.builders {
+ list-style: none;
+}
+
+ul.builders li {
+ height: 40px;
+}
+
+ul.builders a.builder {
+ margin-left: -2em;
+ padding-left: 3em;
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+ul.builders a.offline {
+ background: url("../images/icons/slave-offline.png") no-repeat 0 50%;
+}
+
+ul.builders a.online {
+ background: url("../images/icons/slave-online.png") no-repeat 0 50%;
+}
+
+ul.builders a.disabled {
+ background: url("../images/icons/builder-disabled.png") no-repeat 0 50%;
+}
+
+/* Comments */
+
+div.comments {
+
+}
+
+div.comments div.comment {
+ border: 1px solid #e1e1e1;
+}
+
+div.comments div.comment span {
+ padding-left: 1em;
+ padding-bottom: 0.2em;
+ font-style: italic;
+}
+
+div.comments div.comment p.text {
+ padding: 1em;
+ color: #515151;
+}
+
+div.comments div.none {
+ border-left: 10px solid #e1e1e1;
+}
+
+div.comments div.up {
+ border-left: 10px solid #99FF66;
+}
+
+div.comments div.down {
+ border-left: 10px solid #ff3333;
+}
+
+div.add-comment {
+ background: #e1e1e1;
+ border: 1px solid #515151;
+ margin: 1em;
+ padding: 1em;
+}
+
+/* Repo actions */
+div.repo-actions {
+}
+
+div.repo-actions div.action {
+
+}
+
+div.repo-actions div.action {
+
+}
+
+div.repo-actions div.action p.buttons {
+ border: 1px solid black;
+}
+
+div.repo-actions div.add {
+ background-color: #99ff66;
+}
+
+table td.buttons {
+ text-align: right;
+}
+
+p.important {
+ color: white;
+ padding: 0.2em;
+ background-color: #ff3333;
+}
+
+p.focus {
+ text-align: center;
+ font-size: 3em;
+ font-weight: bold;
+}
+
+p.buttons {
+ text-align: right;
+}
+
+img.avatar {
+ float: right;
+ border: 1px solid #e1e1e1;
+ padding: 0.5em;
+}
+
+/* FORMS */
+
+table.form {
+}
+
+table.form td {
+ height: 2em;
+}
+
+table.form2 {
+ width: 80%;
+}
+
+table.form2 td.col1 {
+ width: 40%
+}
+
+table.form2 td.col2 {
+ width: 60%;
+}
+
+table.form3 {
+ width: 100%;
+}
+
+table.form3 td.col1 {
+ width: 30%;
+}
+
+table.form3 td.col2 {
+ width: 30%;
+}
+
+table.form3 td.col3 {
+ width: 40%;
+ font-style: italic;
+}
+
+table.file-list {
+ width: 80%;
+}
+
+table.file-list td.name {
+ width: 70%;
+}
+
+table.file-list td.actions {
+ width: 30%;
+}
--- /dev/null
+build-failed.png
\ No newline at end of file
--- /dev/null
+build-finished.png
\ No newline at end of file
--- /dev/null
+http://www.iconfinder.com/search/?q=iconset%3ATabs_Color_Social
+
+http://www.iconfinder.com/search/?q=iconset%3Agnome-desktop-icons-png
--- /dev/null
+/*!
+ * jQuery JavaScript Library v1.6
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon May 2 13:50:00 2011 -0400
+ */
+(function(a,b){function cw(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function ct(a){if(!ch[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ci||(ci=c.createElement("iframe"),ci.frameBorder=ci.width=ci.height=0),c.body.appendChild(ci);if(!cj||!ci.createElement)cj=(ci.contentWindow||ci.contentDocument).document,cj.write("<!doctype><html><body></body></html>");b=cj.createElement(a),cj.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ci)}ch[a]=d}return ch[a]}function cs(a,b){var c={};f.each(cn.concat.apply([],cn.slice(0,b)),function(){c[this]=a});return c}function cr(){co=b}function cq(){setTimeout(cr,0);return co=f.now()}function cg(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cf(){try{return new a.XMLHttpRequest}catch(b){}}function b_(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function b$(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bZ(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):bZ(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bZ(a+"["+e+"]",b[e],c,d);else d(a,b)}function bY(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bY(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bY(a,c,d,e,"*",g));return l}function bX(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?bv:bw,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval(b.text||b.textContent||b.innerHTML||""),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bf(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){name="data-"+c.replace(j,"$1-$2").toLowerCase(),d=a.getAttribute(name);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(e){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?g=[null,a,null]:g=i.exec(a);if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(c,d){d&&d instanceof e&&!(d instanceof a)&&(d=a(d));return e.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test("Â ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b,d,e,f,g,h,i,j,k,l,m,n,o,p,q;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",b=a.getElementsByTagName("*"),d=a.getElementsByTagName("a")[0];if(!b||!b.length||!d)return{};e=c.createElement("select"),f=e.appendChild(c.createElement("option")),g=a.getElementsByTagName("input")[0],i={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.55$/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:g.value==="on",optSelected:f.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},g.checked=!0,i.noCloneChecked=g.cloneNode(!0).checked,e.disabled=!0,i.optDisabled=!f.disabled;try{delete a.test}catch(r){i.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function click(){i.noCloneEvent=!1,a.detachEvent("onclick",click)}),a.cloneNode(!0).fireEvent("onclick")),g=c.createElement("input"),g.value="t",g.setAttribute("type","radio"),i.radioValue=g.value==="t",g.setAttribute("checked","checked"),a.appendChild(g),j=c.createDocumentFragment(),j.appendChild(a.firstChild),i.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",k=c.createElement("body"),l={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(p in l)k.style[p]=l[p];k.appendChild(a),c.documentElement.appendChild(k),i.appendChecked=g.checked,i.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,i.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",i.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",m=a.getElementsByTagName("td"),q=m[0].offsetHeight===0,m[0].style.display="",m[1].style.display="none",i.reliableHiddenOffsets=q&&m[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(h=c.createElement("div"),h.style.width="0",h.style.marginRight="0",a.appendChild(h),i.reliableMarginRight=(parseInt(c.defaultView.getComputedStyle(h,null).marginRight,10)||0)===0),k.innerHTML="",c.documentElement.removeChild(k);if(a.attachEvent)for(p in{submit:1,change:1,focusin:1})o="on"+p,q=o in a,q||(a.setAttribute(o,"return;"),q=typeof a[o]=="function"),i[p+"Bubbles"]=q;return i}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[c]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function l(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark";while(g--)if(tmp=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,tmp.done(l);l();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:data-|aria-)/,u=/\:/,v;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||"set"in c&&c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b=a.selectedIndex,c=[],d=a.options,e=a.type==="select-one";if(b<0)return null;for(var g=e?b:0,h=e?b+1:d.length;g<h;g++){var i=d[g];if(i.selected&&(f.support.optDisabled?!i.disabled:i.getAttribute("disabled")===null)&&(!i.parentNode.disabled||!f.nodeName(i.parentNode,"optgroup"))){value=f(i).val();if(e)return value;c.push(value)}}if(e&&!c.length&&d.length)return f(d[b]).val();return c},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex",readonly:"readOnly"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c]||(v&&(f.nodeName(a,"form")||u.test(c))?v:b);if(d!==b){if(d===null||d===!1&&!t.test(c)){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;d===!0&&!t.test(c)&&(d=c),a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.getAttribute("value");a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),f.support.getSetAttribute||(f.attrFix=f.extend(f.attrFix,{"for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder"}),v=f.attrHooks.name=f.attrHooks.value=f.valHooks.button={get:function(a,c){var d;if(c==="value"&&!f.nodeName(a,"button"))return a.getAttribute(c);d=a.getAttributeNode(c);return d&&d.specified?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=Object.prototype.hasOwnProperty,x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&(a.type=a.data,f.event.handle.apply(this,arguments))}catch(d){}},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function J(a){var c=a.target,d,e;if(!!y.test(c.nodeName)&&!c.readOnly){d=f._data(c,"_change_data"),e=I(c),(a.type!=="focusout"||c.type!=="radio")&&f._data(c,"_change_data",e);if(d===b||e===d)return;if(d!=null||e)a.type="change",a.liveFired=b,f.event.trigger(a,arguments[1],c)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){return a.nodeName.toLowerCase()==="input"&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(a===b){g=!0;return 0}if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!be[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bf(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bl)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[];for(var i=0,j;(j=a[i])!=null;i++){typeof j=="number"&&(j+="");if(!j)continue;if(typeof j=="string")if(!ba.test(j))j=b.createTextNode(j);else{j=j.replace(Z,"<$1></$2>");var k=($.exec(j)||["",""])[1].toLowerCase(),l=be[k]||be._default,m=l[0],n=b.createElement("div");n.innerHTML=l[1]+j+l[2];while(m--)n=n.lastChild;if(!f.support.tbody){var o=_.test(j),p=k==="table"&&!o?n.firstChild&&n.firstChild.childNodes:l[1]==="<table>"&&!o?n.childNodes:[];for(var q=p.length-1;q>=0;--q)f.nodeName(p[q],"tbody")&&!p[q].childNodes.length&&p[q].parentNode.removeChild(p[q])}!f.support.leadingWhitespace&&Y.test(j)&&n.insertBefore(b.createTextNode(Y.exec(j)[0]),n.firstChild),j=n.childNodes}var r;if(!f.support.appendChecked)if(j[0]&&typeof (r=j.length)=="number")for(i=0;i<r;i++)bk(j[i]);else bk(j);j.nodeType?h.push(j):h=f.merge(h,j)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(i=0;h[i];i++)if(e&&f.nodeName(h[i],"script")&&(!h[i].type||h[i].type.toLowerCase()==="text/javascript"))e.push(h[i].parentNode?h[i].parentNode.removeChild(h[i]):h[i]);else{if(h[i].nodeType===1){var s=f.grep(h[i].getElementsByTagName("script"),g);h.splice.apply(h,[i+1,0].concat(s))}d.appendChild(h[i])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bm=/alpha\([^)]*\)/i,bn=/opacity=([^)]*)/,bo=/-([a-z])/ig,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz,bA=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bo,bA)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bB(a,b,d):f.swap(a,bu,function(){e=bB(a,b,d)});if(e<=0){e=bx(a,b,b),e==="0px"&&bz&&(e=bz(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV;try{bU=e.href}catch(bW){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bX(bS),ajaxTransport:bX(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?b$(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b_(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bY(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bY(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bZ(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var ca=f.now(),cb=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+ca++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cb.test(b.url)||e&&cb.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cb,l),b.url===j&&(e&&(k=k.replace(cb,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cc=a.ActiveXObject?function(){for(var a in ce)ce[a](0,1)}:!1,cd=0,ce;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cf()||cg()}:cf,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cc&&delete ce[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cd,cc&&(ce||(ce={},f(a).unload(cc)),ce[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ch={},ci,cj,ck=/^(?:toggle|show|hide)$/,cl=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cm,cn=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],co,cp=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cs("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",ct(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cs("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cs("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g];if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=ct(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block")),b.animatedProperties[g]=f.isArray(h)?h[1]:b.specialEasing&&b.specialEasing[g]||b.easing||"swing"}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],ck.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cl.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[g]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cs("show",1),slideUp:cs("hide",1),slideToggle:cs("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=co||cq(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cm&&(cp?(cm=1,g=function(){cm&&(cp(g),e.tick())},cp(g)):cm=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=co||cq(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a=f.timers,b=a.length;while(b--)a[b]()||a.splice(b,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cm),cm=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cu=/^t(?:able|d|h)$/i,cv=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cw(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cu.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cv.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cv.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cw(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cw(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
--- /dev/null
+
+function getCookie(name) {
+ var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+ return r ? r[1] : undefined;
+}
+
+jQuery.postJSON = function(url, args, callback) {
+ args._xsrf = getCookie("_xsrf");
+ $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
+ success: function(response) {
+ callback(eval("(" + response + ")"));
+ }});
+};
+
+$(function() {
+ var $search = $('#search');
+ original_val = $search.val();
+
+ $search.focus(function() {
+ if($(this).val() === original_val) {
+ $(this).val('');
+ }
+ })
+
+ .blur(function() {
+ if($(this).val() === '') {
+ $(this).val(original_val);
+ }
+ });
+});
+
+action_run = function(action_id) {
+ $.postJSON("/api/action/run", { "id" : action_id }, function() {});
+
+ $("#action-" + action_id).hide();
+}
+
+action_remove = function(action_id) {
+ $.postJSON("/api/action/remove", { "id" : action_id }, function() {});
+
+ $("#action-" + action_id).hide();
+}
--- /dev/null
+User-agent: *
+Disallow: /
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+ <head>
+ <title>{{ hostname }} - {% block title %}{{ _("No title given") }}{% end block %}</title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+
+ <!-- styling stuff -->
+ <link rel="stylesheet" type="text/css" href="{{ static_url("css/style.css") }}" />
+
+ <!-- javascript stuff -->
+ <script src="{{ static_url("js/jquery-1.6.min.js") }}"></script>
+ <script src="{{ static_url("js/pbs.js") }}"></script>
+ </head>
+ <body>
+ <div id="wrapper">
+ <div id="logo">
+ <div id="user">
+ {% if current_user %}
+ <a href="/profile"><span>{{ current_user.realname }}</span></a> |
+ <a href="/logout">{{ _("Logout") }}</a>
+ {% else %}
+ <a href="/login">{{ _("Login") }}</a> |
+ <a href="/register">{{ _("Register") }}</a>
+ {% end %}
+ </div>
+ <h1><a href="/">Pakfire</a> build service</h1>
+ <p>{{ _("A service by the %s.") % """<a href="http://www.ipfire.org/" target="_blank">IPFire Project</a>""" }}</p>
+ </div>
+ <div id="menu">
+ <ul>
+ <li>
+ <a href="/">{{ _("Index") }}</a>
+ </li>
+ <li>
+ <a href="/packages">{{ _("Packages") }}</a>
+ </li>
+ <li>
+ <a href="/distributions">{{ _("Distributions") }}</a>
+ </li>
+ <li>
+ <a href="/builds">{{ _("Build jobs") }}</a>
+ </li>
+ <li>
+ <a href="/builders">{{ _("Build servers") }}</a>
+ </li>
+ {% if current_user %}
+ <li>
+ <a href="/users">{{ _("Users") }}</a>
+ </li>
+ {% end %}
+ <li>
+ <a href="/log">{{ _("Log") }}</a>
+ </li>
+ <li class="search">
+ <form method="GET" action="/search">
+ <input id="search" type="text" name="q" value="{{ _("Search...") }}" />
+ </form>
+ </li>
+ </ul>
+ </div>
+ <!-- <div id="header">
+ <div id="search">
+ <form method="get" action="">
+ <fieldset>
+ <input type="text" name="s" id="search-text" size="15" value="enter keywords here..." />
+ <input type="submit" id="search-submit" value="GO" />
+ </fieldset>
+ </form>
+ </div>
+ </div> -->
+ <div id="page">
+ <div id="page-bgtop">
+ <div id="page-bgbtm">
+ <div id="content">
+ {% block body %}EMPTY BODY{% end block %}
+ </div>
+ <div id="sidebar">
+ {% block sidebar %}
+ <img src="{{ static_url("images/ipfire_tux_128x128.png") }}" alt="IPFire Logo" />
+ {% end block %}
+ </div>
+ <div style="clear: both;"> </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="two-columns">
+ <div id="column1">
+ <h2>{{ _("About Pakfire") }}</h2>
+ <p>
+ {{ _("Pakfire is the buildsystem that is used to build the IPFire Linux firewall distribution.") }}
+ {{ _("It also installs and updates packages on the IPFire systems.") }}
+ </p>
+ </div>
+ <div id="column2">
+ <h2>{{ _("Documentation") }}</h2>
+ <ul>
+ <li>
+ <a href="/documents">{{ _("Documentation index") }}</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div id="footer">
+ <p>Copyright (c) 2011 IPFire.org. {{ _("All rights reserved.") }}</p>
+ </div>
+ </body>
+</html>
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Build") }}: {{ build.name }}{% end block %}
+
+{% block body %}
+ {% if build.type == "binary" %}
+ <h1>{{ _("Build") }}: <a href="/package/{{ build.pkg.name }}/{{ build.pkg.epoch }}/{{ build.pkg.version }}/{{ build.pkg.release }}">{{ build.name }}</a></h1>
+ {% elif build.type == "source" %}
+ <h1>{{ _("Build") }}: {{ build.name }}</h1>
+ {% end %}
+
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("ID") }}</td>
+ <td class="col2">{{ build.uuid }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("State") }}</td>
+ <td class="col2">{{ build.state }}</td>
+ </tr>
+
+ {% if build.type == "binary" %}
+ <tr>
+ <td class="col1">{{ _("Package") }}</td>
+ <td class="col2">
+ <a href="/package/{{ build.pkg.name }}/{{ build.pkg.epoch }}/{{ build.pkg.version }}/{{ build.pkg.release }}">{{ build.pkg.friendly_name }}</a>
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Source build") }}</td>
+ <td class="col2">
+ <a href="/build/{{ build.source_build.uuid }}">{{ build.source_build.name }}</a>
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Architecture") }}</td>
+ <td class="col2">{{ build.arch }}</td>
+ </tr>
+ {% end %}
+
+ <tr>
+ <td class="col1">{{ _("Host") }}</td>
+ {% if build.host %}
+ <td class="col2">
+ <a href="/builder/{{ build.host.name }}">{{ build.host.name }}</a>
+ </td>
+ {% else %}
+ <td class="col2">{{ _("No host assigned, yet.") }}</td>
+ {% end %}
+ </tr>
+ <tr>
+ <td class="col1">
+ {{ _("Priority") }}
+ </td>
+ <td class="col2">
+ {% if build.priority >= 2 %}
+ {{ _("Very high") }}
+ {% elif build.priority == 1 %}
+ {{ _("High") }}
+ {% elif build.priority == 0 %}
+ {{ _("Medium") }}
+ {% elif build.priority == -1 %}
+ {{ _("Low") }}
+ {% elif build.priority <= -2 %}
+ {{ _("Very low") }}
+ {% end %}
+ </td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ {% if build.type == "source" %}
+ <h2>{{ _("Commit") }}: {{ escape(build.commit_subject) }}</h2>
+ <table class="form form2">
+ {% if build.commit_body %}
+ <tr>
+ <td colspan="2">
+ {{ escape(build.commit_body) }}
+ </td>
+ </tr>
+ {% end %}
+ <tr>
+ <td class="col1">{{ _("Author") }}</td>
+ <td class="col2">{{ escape(build.commit_author) }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Committer") }}</td>
+ <td class="col2">{{ escape(build.commit_committer) }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Date") }}</td>
+ <td class="col2">{{ locale.format_date(build.commit_date or 0, full_format=True) }}</td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+ {% end %}
+
+ <h3>{{ _("Time") }}</h3>
+
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("Job added") }}</td>
+ <td class="col2">{{ build.time_added }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Job started") }}</td>
+ <td class="col2">{{ build.time_started or _("Not started, yet.") }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Job finished") }}</td>
+ <td class="col2">{{ build.time_finished or _("Not finished, yet.") }}</td>
+ </tr>
+
+ {% if build.duration %}
+ <tr>
+ <td class="col1">{{ _("Duration") }}</td>
+ <td class="col2">{{ build.duration }}</td>
+ </tr>
+ {% end %}
+ </table>
+ <div style="clear: both;"> </div>
+
+ {% if build.packagefiles %}
+ <h3>{{ _("Package files") }}</h3>
+ {{ modules.FilesTable(build.packagefiles) }}
+ {% end %}
+
+ {% if build.logfiles %}
+ <h3>{{ _("Logfiles") }}</h3>
+ {{ modules.FilesTable(build.logfiles) }}
+ {% end %}
+
+ <h3>{{ _("Log") }}</h3>
+ {{ modules.LogTable(build.log) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ {% if build.state == "failed" %}
+ <li><a href="/build/schedule/{{ build.uuid }}?type=rebuild">{{ _("Re-submit build") }}</a></li>
+ <li><a href="?action=perm_failed">{{ _("Mark as permanently failed") }}</a></li>
+ {% end %}
+
+ {% if build.type == "binary" and build.state == "finished" %}
+ <li><a href="/build/schedule/{{ build.uuid }}?type=test">{{ _("Schedule test build") }}</a></li>
+ {% end %}
+
+ <li><a href="/build/priority/{{ build.uuid }}">{{ _("Modify priority") }}</a></li>
+ </ul>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Build job list") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Filter builds") }}</h1>
+ <form method="get" action="/builds">
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("State") }}</td>
+ <td class="col2">
+ <select name="state">
+ <option value="">{{ _("All") }}</option>
+ <option value="running">{{ _("Running") }}</option>
+ <option value="pending">{{ _("Pending") }}</option>
+ <option value="finished">{{ _("Finished") }}</option>
+ <option value="failed">{{ _("Failed") }}</option>
+ <option value="permanently_failed">{{ _("Permanently failed") }}</option>
+ <option value="dispatching">{{ _("Dispatching") }}</option>
+ <option value="uploading">{{ _("Uploading") }}</option>
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("Only show builds with given state.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Build host") }}</td>
+ <td class="col2">
+ <select name="builder">
+ <option value="">{{ _("Any") }}</option>
+ {% for builder in builders %}
+ <option value="{{ builder.id }}">{{ builder.name }}</option>
+ {% end %}
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("Display only builds by selected host.") }}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Submit") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="/builds">{{ _("Show all builds") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Build job list") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Build job list") }}</h1>
+ {{ modules.BuildTable(builds) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="/builds/filter">{{ _("Filter builds") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Edit build priority") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Edit build priority") }}: {{ build.name }}</h1>
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Priority") }}</td>
+ <td class="col2">
+ <select name="priority">
+ <option value="2" {% if build.priority >= 2 %}selected="selected"{% end %}>{{ _("Very high") }}</option>
+ <option value="1" {% if build.priority == 1 %}selected="selected"{% end %}>{{ _("High") }}</option>
+ <option value="0" {% if build.priority == 0 %}selected="selected"{% end %}>{{ _("Medium") }}</option>
+ <option value="-1" {% if build.priority == -1 %}selected="selected"{% end %}>{{ _("Low") }}</option>
+ <option value="-2" {% if build.priority <= -2 %}selected="selected"{% end %}>{{ _("Very low") }}</option>
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("Set the priority of the build process.") }}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <p>
+ <strong>{{ _("Beware") }}:</strong>
+ {{ _("Shuffeling build jobs can cause problems with the dependency solving.") }}
+ {{ _("Don't do this if you are not totally sure you won't break anything.") }}
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Save") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Schedule rebuild for %s") % build.name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Schedule rebuild for %s") % build.name }}</h1>
+ <p>
+ {{ _("At this place, you can submit failed build jobs to be built again.") }}
+ </p>
+ <p>
+ {{ _("The build job will be started when a build slot is available but not before the given time.") }}
+ </p>
+
+ {{ modules.BuildOffset() }}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Schedule test build for %s") % build.name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Schedule test build for %s") % build.name }}</h1>
+ <p>
+ {{ _("A test build is used to check if a package builds with the current package set.") }}
+ {{ _("In this way, developers are able to find quality issues fast and without actively searching for them.") }}
+ </p>
+ <p>
+ {{ _("As this build platform only has a limited amount of performance, test builds only have a very less priority.") }}
+ {{ _("However, you can manually request to run a test.") }}
+ </p>
+ <p>
+ {{ _("The build job will be started when a build slot is available but not before the given time.") }}
+ </p>
+
+ {{ modules.BuildOffset() }}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Delete builder %s") % builder.name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Builder") }}: {{ builder.name }}</h1>
+
+ <p>
+ {{ _("You are going to delete the build host <strong>%s</strong>.") % builder.name }}
+ </p>
+
+ <p>
+ <a href="/builder/delete/{{ builder.name }}?confirmed=1">{{ _("Delete %s") % builder.name }}</a>
+ <a href="/builder/{{ builder.name }}">{{ _("Back") }}</a>
+ </p>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Builder") }}: {{ builder.name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Builder") }}: {{ builder.name }}</h1>
+ <table class="form form2">
+ <!-- Status -->
+ <tr>
+ <td class="col1">{{ _("Status") }}</td>
+ <td class="col2">{{ builder.status }}</td>
+ </tr>
+
+ <!-- Load AVG -->
+ <tr>
+ <td class="col1">{{ _("Load average") }}</td>
+ <td class="col2">
+ {{ builder.loadavg }}
+ </td>
+ </tr>
+
+ <!-- Supported architectures -->
+ <tr>
+ <td class="col1">{{ _("Supported architectures") }}</td>
+ <td class="col2">
+ {{ locale.list(builder.arches) or _("Unknown") }}
+ </td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Configuration") }}</h2>
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("Builds source packages") }}</td>
+ <td class="col2">
+ {% if builder.build_src %}{{ _("Yes") }}{% else %}{{ _("No") }}{% end %}
+ </td>
+ </td>
+ <tr>
+ <td class="col1">{{ _("Builds binary packages") }}</td>
+ <td class="col2">
+ {% if builder.build_bin %}{{ _("Yes") }}{% else %}{{ _("No") }}{% end %}
+ </td>
+ </td>
+ <tr>
+ <td class="col1">{{ _("Runs tests") }}</td>
+ <td class="col2">
+ {% if builder.build_test %}{{ _("Yes") }}{% else %}{{ _("No") }}{% end %}
+ </td>
+ </td>
+ <tr>
+ <td class="col1">{{ _("Parallel build jobs") }}</td>
+ <td class="col2">{{ _("One job only.", "Up to %(num)s jobs.", builder.max_jobs) % { "num" : builder.max_jobs } }}</td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Host information") }}</h2>
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("CPU model") }}</td>
+ <td class="col2">{{ builder.cpu_model or _("Unknown") }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Memory") }}</td>
+ <td class="col2">{{ friendly_size(builder.memory) }}</td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ {% if builder.active_builds %}
+ <h2>{{ _("Currently running builds on this host") }}</h2>
+ {{ modules.BuildTable(builder.active_builds) }}
+ {% end %}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li>
+ <a href="/builds?state=&builder={{ builder.id }}">{{ _("Show all build jobs") }}</a>
+ </li>
+ {% if current_user and current_user.is_admin() %}
+ <li>
+ <a href="/builder/edit/{{ builder.name }}">{{ _("Edit builder") }}</a>
+ </li>
+ {% if not builder.enabled %}
+ <li>
+ <a href="/builder/renew/{{ builder.name }}">{{ _("Renew passphrase") }}</a>
+ </li>
+ {% end %}
+ <li>
+ <a href="/builder/delete/{{ builder.name }}">{{ _("Delete builder") }}</a>
+ </li>
+ {% end %}
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Edit builder %s") % builder.hostname }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Edit builder %s") % builder.hostname }}</h1>
+
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Name") }}</td>
+ <td class="col2">
+ {{ builder.hostname }}
+ </td>
+ <td class="col3">
+ {{ _("The hostname cannot be changed.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Enabled") }}</td>
+ <td class="col2">
+ <input type="checkbox" name="enabled" {% if builder.enabled %}checked="checked"{% end %} />
+ </td>
+ <td class="col3">
+ {{ _("The builder must be enabled in order to process build jobs.") }}
+ </td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Build job settings") }}</h2>
+ <p>
+ {{ _("These settings do only take effect if the builder is enabled.") }}
+ </p>
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Authorized to build source packages") }}</td>
+ <td class="col2">
+ <input type="checkbox" name="build_src" {%if builder.build_src %}checked="checked"{% end %} />
+ </td>
+ <td class="col3">
+ {{ _("Only a few build servers are allowed to build source packages.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Authorized to build binary packages") }}</td>
+ <td class="col2">
+ <input type="checkbox" name="build_bin" {%if builder.build_bin %}checked="checked"{% end %} />
+ </td>
+ <td class="col3"> </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Authorized to build test packages") }}</td>
+ <td class="col2">
+ <input type="checkbox" name="build_test" {%if builder.build_test %}checked="checked"{% end %} />
+ </td>
+ <td class="col3"> </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Maximum number of parallel build jobs") }}</td>
+ <td class="col2">
+ <select name="max_jobs">
+ {% for i in (1, 2, 3, 4, 5, 6, 7, 8,) %}
+ <option value="{{ i }}" {% if i == builder.max_jobs %}selected="selected"{% end %}>{{ i }}</option>
+ {% end %}
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("This is the number of build jobs that are started in parallel.") }}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Save") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Build servers") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Build servers") }}</h1>
+
+ <p>
+ {{ _("Builders are those, that do all the hard work.") }}
+ {{ _("Build jobs are scheduled to these hosts that they process and send back the result.") }}
+ </p>
+
+ <ul class="builders">
+ {% for builder in builders %}
+ <li>
+ <a class="builder {{ builder.status.lower() }}" href="/builder/{{ builder.name }}">{{ builder.name }}</a>
+ </li>
+ {% end %}
+ </ul>
+{% end block %}
+
+{% if current_user.is_admin() %}
+ {% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="/builder/new">{{ _("Create new builder") }}</a></li>
+ </ul>
+ {% end block %}
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Create new builder") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Create a new builder") }}</h1>
+
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Name") }}</td>
+ <td class="col2">
+ <input name="name" type="text" length="64" />
+ </td>
+ <td class="col3">
+ {{ _("Must be the canonical hostname of the machine.") }}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Save") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Builder") }}: {{ builder.name }}</h1>
+
+ <p>
+ {% if action == "new" %}
+ {{ _("The new host <strong>%s</strong> has been successfully created.") % builder.name }}
+ {% elif action == "update" %}
+ {{ _("The passphrase for <strong>%s</strong> has been regenerated.") % builder.name }}
+ {% end %}
+
+ {{ _("For authorization to the Pakfire Master Server there is a passphrase required which must be configured to the host.") }}
+ {{ _("This passphrase is:") }}
+ </p>
+
+ <p class="focus">{{ builder.passphrase }}</p>
+
+ <p>
+ <a href="/builder/{{ builder.name }}">{{ _("Next") }}</a>
+ </p>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Distribution") }}: {{ distro.name }}</a></h1>
+
+ <p class="pkg-summary">{{ distro.description }}</p>
+
+ <p>
+ {{ _("This distribution is available for %s and maintained by %s.") % (locale.list(distro.arches), distro.vendor) }}
+ </p>
+
+ <h2>{{ _("Binary repositories") }}</h2>
+ {{ modules.RepositoryTable(distro, distro.repositories) }}
+
+ <h2>{{ _("Sources") }}</h2>
+ {{ modules.SourceTable(distro, distro.sources) }}
+
+ <h2>{{ _("Log") }}</h2>
+ {{ modules.LogTable(distro.log) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="/distribution/edit/{{ distro.sname }}">{{ _("Edit distribution") }}</a></li>
+ <li><a href="/distribution/delete/{{ distro.sname }}">{{ _("Delete distribution") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Edit distribution %s") % distro.name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Edit distribution %s") % distro.name }}</h1>
+
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Name") }}</td>
+ <td class="col2">
+ <input type="text" name="name" value="{{ distro.name }}" />
+ </td>
+ <td class="col3">
+ {{ _("The fancy name of the distribution.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Identifier") }}</td>
+ <td class="col2">
+ {{ distro.sname }}
+ </td>
+ <td class="col3">
+ {{ _("Cannot be changed.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Vendor") }}</td>
+ <td class="col2">
+ <input type="text" name="vendor" value="{{ distro.vendor }}" />
+ </td>
+ <td class="col3">
+ {{ _("From whom is the distribution from?") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Slogan") }}</td>
+ <td class="col2">
+ <input type="text" name="slogan" value="{{ distro.slogan }}" />
+ </td>
+ <td class="col3">
+ {{ _("A short sentence that characterizes the distribution.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Architectures") }}</td>
+ <td class="col2">
+ <select name="arches" size="4" multiple>
+ {% for arch in arches %}
+ <option value="{{ arch }}" {% if arch in distro.arches %}selected="selected"{% end %}>{{ arch }}</option>
+ {% end %}
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("For which architectures should the distribution be built?") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Sources") }}</td>
+ <td class="col2">
+ <select name="sources" size="4" multiple>
+ {% for source in sources %}
+ <option value="{{ source.id }}" {% if source in distro.sources %}selected="selected"{% end %}>{{ source.name }}</option>
+ {% end %}
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("Which sources should be imported to the distribution?") }}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Save") }}" />
+ </td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+ </form>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Distributions") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Distributions") }}</h1>
+ <ul class="distros">
+ {% for distro in distros %}
+ <li>
+ <a href="/distribution/{{ distro.sname }}">{{ distro.name }}</a> - {{ distro.slogan }}
+ </li>
+ {% end %}
+ </ul>
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="">{{ _("Add distribution") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block sidebar %}
+ <ul>
+ <li><a href="/documents">{{ _("All Documents") }}</a></li>
+ </ul>
+ <div style="clear: both;"> </div>
+
+ <h1>{{ _("Topics") }}</h1>
+ <ul>
+ <li><a href="/documents/builds">{{ _("Builds") }}</a></li>
+ <li><a href="/documents/users">{{ _("Users") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "docs-base.html" %}
+
+{% block title %}{{ _("Legend of the build states") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Legend of the build states") }}</h1>
+ <p>
+ {{ _("Every build that is done by the Pakfire Build Service has to go through several states:") }}
+ </p>
+ <p>
+ {{ _("After checking out the source from the source repository a source package is created and submitted to the build server.") }}
+ {{ _("Starting from inserting a source file to the build service, there are binary build jobs created for every supported architecture.") }}
+ </p>
+ <p>
+ {{ _("These get assigned to a build host which has to compile or assemble the package and return it back to the build server.") }}
+ {{ _("In the table below, there are all states that a build job goes through:") }}
+ </p>
+ <ul class="builds">
+ <li><a class="build running" name="#">{{ _("Build is running") }}</a></li>
+ <li><a class="build failed" name="#">{{ _("Build has failed") }}</a></li>
+ <li><a class="build pending" name="#">{{ _("Build is waiting to be processed") }}</a></li>
+ <li><a class="build dependency_error" name="#">{{ _("There was a dependency error when the package was built") }}</a></li>
+ <li><a class="build waiting" name="#">{{ _("Build is waiting for source to go to pending state") }}</a></li>
+ <li><a class="build dispatching" name="#">{{ _("Files of this build are transferred to the build server") }}</a></li>
+ <li><a class="build uploading" name="#">{{ _("Files are being uploaded to the service") }}</a></li>
+ <li><a class="build unknown" name="#">{{ _("Build has an unknown state") }}</a></li>
+ </ul>
+{% end %}
--- /dev/null
+{% extends "docs-base.html" %}
+
+{% block title %}{{ _("Legend of the build states") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Documents") }}</h1>
+ <p>
+ {{ _("This is a collection of documents that should be read by everybody who is using this system.") }}
+ {{ _("To make this easy for you, the documents are grouped into two parts.") }}
+ </p>
+
+ <h2>{{ _("Documents for testers") }}</h2>
+ <ul>
+ <li><a href="/documents/users">{{ _("Users") }}</a></li>
+ </ul>
+
+ <h2>{{ _("Documents for developers") }}</h2>
+ <ul>
+ <li><a href="/documents/builds">{{ _("Builds") }}</a></li>
+ <li><a href="/documents/users">{{ _("Users") }}</a></li>
+ </ul>
+
+ <p>
+ {{ _("Technical documentation is available on the wiki:") }}
+ <a href="http://redmine.ipfire.org/projects/pakfire3/wiki" target="_blank">{{ _("Technical documentation") }}</a>.
+ </p>
+{% end %}
--- /dev/null
+{% extends "docs-base.html" %}
+
+{% block title %}{{ _("Legend of the build states") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Users") }}</h1>
+ <p>
+ {{ _("All users can join the Pakfire Build Service and are separated into three groups:") }}
+ </p>
+
+ <h2>{{ _("Developers") }}</h2>
+ <p>
+ {{ _("Developers manage this build service and have access to all parts of it.") }}
+ {{ _("They are responsible to keep the system running and able to push package updates to the repostories.") }}
+ </p>
+ <p class="buttons">
+ <a href="/documents/guidelines/developers">{{ _("Guidelines for developers") }}</a>
+ </p>
+
+ <h2>{{ _("Testers") }}</h2>
+ <p>
+ {{ _("Testers are like users but have the right to vote on packages, which is used to figure out the quality of the package.") }}
+ {{ _("Everyone can become a tester after he or she has proven to know the IPFire system very well.") }}
+ {{ _("On these people depends a very huge amount of the quality of the distribution that is made out of the feedback they give.") }}
+ </p>
+ <p class="buttons">
+ <a href="/documents/guidelines/testers">{{ _("Guidelines for testers") }}</a>
+ </p>
+
+ <h2>{{ _("Users") }}</h2>
+ <p>
+ {{ _("Everybody can join the Pakfire Build Service by registering an account.") }}
+ {{ _("After a successful activation you are able to leave comments on packages and give feedback to the developers about its status.") }}
+ </p>
+
+ {% if not current_user %}
+ <p class="buttons">
+ <a href="/register">{{ _("Register") }}</a>
+ </p>
+ {% end %}
+{% end %}
--- /dev/null
+{% extends "error.html" %}
+
+{% block body %}
+ 404
+{% end block %}
--- /dev/null
+{% extends "error.html" %}
+
+{% block body %}
+ 500
+
+ {% if exception %}
+ <pre>{{ exception }}</pre>
+ {% end %}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>
+ {{ _("File") }}:
+ <a href="/package/{{ pkg.name }}/{{ pkg.epoch }}/{{ pkg.version }}/{{ pkg.release }}">{{ file.name }}</a>
+ </h1>
+
+ <p class="pkg-summary">{{ file.summary or pkg.summary }}</p>
+
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("Package") }}</td>
+ <td class="col2">
+ <a href="/package/{{ pkg.name }}/{{ pkg.epoch }}/{{ pkg.version }}/{{ pkg.release }}">{{ pkg.friendly_name }}</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">{{ _("Description") }}</td>
+ </tr>
+ <tr>
+ <td colspan="2">{{ file.description or pkg.description }}</td>
+ </tr>
+
+ <tr>
+ <td class="col1">{{ _("URL") }}</td>
+ <td class="col2">
+ <a href="{{ pkg.url }}" target="_blank">{{ file.url or pkg.url }}</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="col1">{{ _("License") }}</td>
+ <td class="col2">{{ file.license or pkg.license}}</td>
+ </tr>
+
+ {% if file.maintainer %}
+ <tr>
+ <td class="col1">{{ _("Maintainer") }}</td>
+ <td class="col2">{{ escape(file.maintainer or pkg.maintainer) }}</td>
+ </tr>
+ {% end %}
+
+ <tr>
+ <td class="col1">{{ _("Size") }}</td>
+ <td class="col2">{{ friendly_size(file.size) }}</td>
+ </tr>
+
+ <tr>
+ <td class="col1">{{ _("Hash") }}</td>
+ <td class="col2">{{ file.hash1 }}</td>
+ </tr>
+
+ {% for i in file.provides %}
+ <tr>
+ <td class="col1">{{ _("Provides") }}</td>
+ <td class="col2">{{ i }}</td>
+ </tr>
+ {% end %}
+
+ <tr>
+ <td class="col1">{{ _("Requires") }}</td>
+ <td class="col2">{{ "<br />".join(file.requires) }}</td>
+ </tr>
+
+ {% for i in file.requires %}
+ <tr>
+ <td class="col1">{{ _("Requires") }}</td>
+ <td class="col2">{{ i }}</td>
+ </tr>
+ {% end %}
+
+ {% for i in file.obsoletes %}
+ <tr>
+ <td class="col1">{{ _("Obsoletes") }}</td>
+ <td class="col2">{{ i }}</td>
+ </tr>
+ {% end %}
+
+ {% for i in file.conflicts %}
+ <tr>
+ <td class="col1">{{ _("Conflicts") }}</td>
+ <td class="col2">{{ i }}</td>
+ </tr>
+ {% end %}
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Build information") }}</h2>
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("ID") }}</td>
+ <td class="col2">
+ <a href="/build/{{ file.build_id }}">{{ file.build_id }}</a>
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Host") }}</td>
+ <td class="col2">
+ <a href="/builder/{{ file.build_host }}">{{ file.build_host }}</a>
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Time") }}</td>
+ <td class="col2">{{ file.build_date }}</td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h3>{{ _("Files") }}</h3>
+ {{ modules.PackageFilesTable(file.filelist) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="{{ file.download }}">{{ _("Download file") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Welcome to the Pakfire Build Service") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Welcome to the Pakfire Build Service") }}</h1>
+ <p>
+ {{ _("This is a service that organizes development and packaging for the IPFire distribution.") }}
+ {{ _("It is used to build and track packages as well as assembling images.") }}
+ <a href="/documents">{{ _("Learn more...") }}</a>
+ </p>
+
+ {% if active_builds %}
+ <h2>{{ _("Ongoing builds") }}</h2>
+ {{ modules.BuildTable(active_builds) }}
+ {% end %}
+
+ {% if next_builds %}
+ <h2>{{ _("Queued builds") }}</h2>
+ {{ modules.BuildTable(next_builds) }}
+ {% end %}
+
+ <h2>{{ _("Lately updated builds") }}</h2>
+ {{ modules.BuildTable(latest_builds) }}
+
+ <h2>{{ _("Statistics") }}</h2>
+ <ul>
+ <li>
+ {{ _("There is currently one pending build job.", "There are currently %(num)s pending build jobs.", counter_pending) % { "num" : counter_pending } }}
+ </li>
+ <li>
+ {{ _("Average build time is %.1f minutes.") % (average_build_time / 60) }}
+ </li>
+ </ul>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Log") }}</h1>
+
+ {{ modules.LogTable(log) }}
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Login successful") }}</h1>
+ <p>
+ {{ _("Welcome, %s.") % escape(user.realname) }}
+ </p>
+
+ <p>
+ {{ _("Your login to the Pakfire Build Server was successful.") }}
+ </p>
+
+ <p>
+ <a href="/">{{ _("Go on") }}</a>
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Login") }}</h1>
+
+ {% if failed %}
+ <p class="important">
+ {{ _("Username and/or password was wrong. Login failed.") }}
+ </p>
+ {% end %}
+
+ <p>
+ {{ _("Please type your username and your password to the form to log in.") }}
+ {{ _("If you have no account, yet you can create a new one.") }}
+ <a href="/register">{{ _("Register a new account.") }}</a>
+ </p>
+
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table>
+ <tr>
+ <td>{{ _("Username") }}</td>
+ <td><input name="name" type="text" /></td>
+ </tr>
+ <tr>
+ <td>{{ _("Password") }}</td>
+ <td><input name="pass" type="password" /></td>
+ </tr>
+ <tr>
+ <td colspan="2" class="buttons">
+ <input type="submit" value="{{ _("Login") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Logout successful") }}</h1>
+
+ <p>
+ {{ _("You have successfully logged out from the Pakfire Build Server.") }}
+ {{ _("Have a nice day!") }}
+ </p>
+
+ <p>
+ <a href="/">{{ _("Go on") }}</a>
+ </p>
+{% end %}
--- /dev/null
+
+<table class="build-log">
+ {% for message in messages %}
+ <tr>
+ <td>
+ {{ message.time }}
+ </td>
+ <td>
+ {{ message.message }}
+ </td>
+ </tr>
+ {% end %}
+</table>
--- /dev/null
+<form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Start time") }}</td>
+ <td class="col2">
+ <select name="offset">
+ <option value="0"}>{{ _("As soon as possible") }}</option>
+ <option value="300">{{ _("After 5 minutes") }}</option>
+ <option value="900">{{ _("After 15 minutes") }}</option>
+ <option value="3600">{{ _("After one hour") }}</option>
+ <option value="86400">{{ _("After one day") }}</option>
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("Set the time after which the build job starts.") }}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Schedule build") }}" />
+ </td>
+ </tr>
+ </table>
+</form>
--- /dev/null
+<ul class="builds">
+ {% if builds %}
+ {% for build in builds %}
+ <li>
+ {% if build.type == "binary" %}
+ <a class="build {{ build.state }}"
+ href="/package/{{ build.pkg.name }}/{{ build.pkg.epoch }}/{{ build.pkg.version }}/{{ build.pkg.release }}"
+ >{{ build.pkg.friendly_name }}</a>.<a href="/build/{{ build.uuid }}">{{ build.arch }}</a>
+ {% elif build.type == "source" %}
+ <a class="build {{ build.state }}" href="/build/{{ build.uuid }}">{{ build.name }}</a>
+ {% else %}
+ {{ _("Unknown build type.") }}
+ {% end %}
+ </li>
+ {% end %}
+ {% else %}
+ <li>There are no builds to display.</li>
+ {% end %}
+</ul>
+<div style="clear: both;"> </div>
--- /dev/null
+<div class="comments">
+ {% if comments %}
+ {% for comment in comments %}
+ <div class="comment {{ comment.vote }}">
+ <p class="text">{{ escape(comment.text) }}</p>
+ <span>
+ {% if show_package %}
+ <a href="/package/{{ comment.pkg.name }}/{{ comment.pkg.epoch }}/{{ comment.pkg.version }}/{{ comment.pkg.release }}">{{ _("on %s") % comment.pkg.friendly_name }}</a> -
+ {% end %}
+ {% if show_user %}
+ <a href="/user/{{ comment.user.name }}">{{ _("by %s") % escape(comment.user.realname) }}</a> -
+ {% end %}
+ {{ locale.format_date(comment.time) }}
+ </span>
+ </div>
+ {% end %}
+ {% else %}
+ <p>{{ _("No comments so far.") }}</p>
+ {% end %}
+</div>
+<div style="clear: both;"> </div>
--- /dev/null
+<table class="file-list">
+ {% for file in files %}
+ <tr>
+ <td class="actions">
+ <a href="{{ file.download }}">{{ _("Download") }}</a>
+ {% if not file.type == "log" %}
+ | <a href="/file/{{ file.uuid }}">{{ _("Info") }}</a>
+ {% end %}
+ </td>
+ <td class="name">{{ file.name }}</td>
+ </tr>
+ {% end %}
+</table>
+<div style="clear: both;"> </div>
--- /dev/null
+<table class="log">
+ {% if messages %}
+ {% for message in messages %}
+ <tr>
+ <td>
+ {{ locale.format_date(message.time, relative=False, full_format=True) }}
+ </td>
+ <td class="links">
+ {% if hasattr(message, "build") %}
+ [<a href="/build/{{ message.build.uuid }}">{{ _("Build") }}: {{ message.build.name }}</a>]
+ {% elif hasattr(message, "pkg") %}
+ [{{ _("Package") }}: {{ message.pkg.friendly_name }}]
+ {% end %}
+ </td>
+ <td>
+ {{ escape(message.message) }}
+ </td>
+ </tr>
+ {% end %}
+ {% else %}
+ <tr>
+ <td colspan="3">
+ {{ _("No log entries, yet.") }}
+ </td>
+ </tr>
+ {% end %}
+</table>
+<div style="clear: both;"> </div>
--- /dev/null
+<table class="file-list">
+ {% for file in files %}
+ <tr>
+ <td>
+ {{ file.name }}
+ </td>
+ <td>
+ {{ friendly_size(file.size) }}
+ </td>
+ <td>
+ {{ file.hash1 }}
+ </td>
+ </tr>
+ {% end %}
+</table>
+<div style="clear: both;"> </div>
--- /dev/null
+<ul class="packages">
+ {% for pkg in packages %}
+ <li>
+ <a href="/package/{{ pkg.name }}/{{ pkg.epoch }}/{{ pkg.version }}/{{ pkg.release }}">{{ pkg.friendly_name }}</a>
+ </li>
+ {% end %}
+</ul>
--- /dev/null
+
+<a name="{{ letter }}"></a>
+<h3>{{ letter.upper() }}</h3>
+
+<table class="package-list">
+ {% for pkg in packages %}
+ <tr>
+ <td>
+ <a href="/package/{{ pkg }}">{{ pkg }}</a>
+ </td>
+ </tr>
+ {% end %}
+</table>
+<div style="clear: both;"> </div>
--- /dev/null
+<div class="repo-actions">
+ {% for action in actions %}
+ <div class="action {{ action.action }}" id="action-{{ action.id }}">
+ <a id="action-link-{{ action.id }}" href="#">{{ action.pkg.friendly_name }}</a>
+ {{ _("added %s") % locale.format_date(action.time_added) }}
+
+ {% if current_user and action.have_permission(current_user) %}
+ <p class="buttons">
+ {% if action.is_doable() %}
+ <a href="javascript:action_run({{ action.id }})">{{ _("Run") }}</a>
+ {% end %}
+ <a href="?action=remove">{{ _("Remove action") }}</a>
+ </p>
+ {% end %}
+
+ <div id="action-info-{{ action.id }}">
+ <p>
+ {% if action.credits_needed %}
+ {{ _("%(credits)s more credit needed.", "%(credits)s more credits needed.", action.credits_needed) % { "credits" : action.credits_needed } }}
+ {% else %}
+ {{ _("No more credits needed.") }}
+ {% end %}
+ <br />
+ {% if action.pkg.maintainer %}
+ {{ _("Maintainer: %s") % action.pkg.maintainer }}
+ <br />
+ {% end %}
+ <a href="/package/{{ action.pkg.name }}/{{ action.pkg.epoch }}/{{ action.pkg.version }}/{{ action.pkg.release }}">{{ _("Go to package description") }}</a>
+ <br />
+ </p>
+ </div>
+
+ <script>
+ $(function() {
+ $("#action-info-{{ action.id }}").hide();
+ $("#action-link-{{ action.id }}").toggle(
+ function() { $("#action-info-{{ action.id }}").show(); },
+ function() { $("#action-info-{{ action.id }}").hide(); }
+ );
+ });
+ </script>
+ </div>
+ {% end %}
+</div>
+<div style="clear: both;"> </div>
--- /dev/null
+<ul class="repositories">
+ {% for repo in repos %}
+ <li>
+ <a href="/distribution/{{ distro.sname }}/repository/{{ repo.name }}">{{ repo.name }}</a>
+ </li>
+ {% end %}
+</ul>
+<div style="clear: both;"> </div>
--- /dev/null
+<ul class="sources">
+ {% for source in sources %}
+ <li>
+ <a href="/source/{{ source.id }}">{{ source.name }}</a>
+ </li>
+ {% end %}
+</ul>
--- /dev/null
+<ul>
+ {% for user in users %}
+ <li><a href="/user/{{ user.name }}">{{ user.realname }}</a></li>
+ {% end %}
+</ul>
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Package") }} {{ pkg.name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Package") }}: {{ pkg.name }}</h1>
+
+ <p class="pkg-summary">
+ {{ pkg.summary }}
+ </p>
+ <p>
+ {{ _("There is one version of %(pkg)s.", "There are %(num)s different versions of %(pkg)s.", len(packages)) % { "num" : len(packages), "pkg" : pkg.name, } }}
+ </p>
+ {{ modules.PackageTable2(packages) }}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Package") }}: {{ pkg.friendly_name }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Package") }}: <a href="/package/{{ pkg.name }}">{{ pkg.name }}</a>-{{ pkg.friendly_version }}</h1>
+
+ <p class="pkg-summary">{{ pkg.description }}</p>
+
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("URL") }}</td>
+ <td class="col2">
+ <a href="{{ pkg.url }}" target="_blank">{{ pkg.url }}</a>
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("License") }}</td>
+ <td class="col2">{{ pkg.license }}</td>
+ </tr>
+
+ {% if pkg.maintainer %}
+ <tr>
+ <td class="col1">{{ _("Maintainer") }}</td>
+ <td class="col2">{{ escape(pkg.maintainer) }}</td>
+ </tr>
+ {% end %}
+
+ <tr>
+ <td class="col1">{{ _("Supported architectures") }}</td>
+ <td class="col2">{{ locale.list(pkg.supported_arches) }}</td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Comments") }}</h2>
+ <p>
+ {{ _("This package got a total credit count of %s credits.") % pkg.credits }}
+ </p>
+
+ {% if current_user %}
+ <p>
+ <a id="comment-toggle" href="#">{{ _("Add comment") }}</a>
+ <script>
+ /* Initially hide the comment area and show it if the user clicks "add comment". */
+ $(function() {
+ $(".add-comment").hide();
+ $("#comment-toggle").toggle(
+ function() { $(".add-comment").show(); },
+ function() { $(".add-comment").hide(); }
+ );
+ });
+ </script>
+ </p>
+ <div class="add-comment">
+ <h4>{{ _("Add comment") }}</h4>
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <input type="hidden" name="action" value="comment" />
+ <table class="form form2">
+ <tr>
+ <td colspan="2">
+ <textarea name="text" cols="90" rows="6"></textarea>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {% if current_user.is_tester() or current_user.is_admin() %}
+ {{ _("Vote") }}:
+ <input name="vote" type="radio" value="none" selected="selected" />{{ _("Not tested") }}
+ <input name="vote" type="radio" value="up" />{{ _("Works for me") }}
+ <input name="vote" type="radio" value="down" />{{ _("Doesn't work for me") }}
+ {% end %}
+ </td>
+ <td class="buttons">
+ <input name="submit" type="submit" value="{{ _("Submit") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+ </div>
+ {% else %}
+ <p>{{ _("You must be logged in to comment.") }}</p>
+ {% end %}
+ {{ modules.CommentsTable(pkg.comments) }}
+
+ {% if pkg.builds %}
+ <h2>{{ _("Build jobs") }}</h2>
+ {{ modules.BuildTable(pkg.builds) }}
+ {% end %}
+
+ {% if pkg.packagefiles %}
+ <h2>{{ _("Package files") }}</h2>
+ {{ modules.FilesTable(pkg.packagefiles) }}
+ {% end %}
+
+ {% if pkg.logfiles %}
+ <h2>{{ _("Logfiles") }}</h2>
+ {{ modules.FilesTable(pkg.logfiles) }}
+ {% end %}
+
+ <h2>{{ _("Log") }}</h2>
+ {{ modules.LogTable(pkg.log) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <a href="">{{ _("Package is broken") }}</a>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Package list") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Package list") }}</h1>
+ <p>
+ {{ _("This is an alphabetically ordered list of all packages in the distribution.") }}
+ {{ _("Click on a link to see further information about the package.") }}
+ </p>
+
+ <ul class="alphabet">
+ <li>{{ _("Quick selection:") }}</li>
+ {% for letter in sorted(packages.keys()) %}
+ <li><a href="#{{ letter }}">{{ letter.upper() }}</a></li>
+ {% end %}
+ </ul>
+ <div style="clear: both;"> </div>
+
+ {% for letter, pkgs in sorted(packages.items()) %}
+ {{ modules.PackageTable(letter, pkgs) }}
+ {% end %}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Activation failed") }}</h1>
+ <p>
+ {{ _("We are sorry.") }}
+ {{ _("The activation of your account has failed.") }}
+ {{ _("Possibly the registration code is wrong or your registration timed out.") }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Activation successful") }}</h1>
+ <p>
+ {{ _("Your account has been activated, %s.") % user.realname }}
+ {{ _("Have fun!") }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Registration failed") }}</h1>
+ <p>
+ {{ _("We are sorry.") }}
+ {{ _("We could not create your requested account.") }}
+ </p>
+
+ <ul>
+ {% for msg in messages %}
+ <li>{{ msg }}</li>
+ {% end %}
+ </ul>
+
+ <p>
+ {{ _("Use the back button on your web browser to go back to the previous page and correct your submission.") }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Registration successful") }}</h1>
+ <p>
+ {{ _("Your new account has been created, %s.") % user.realname }}
+ {{ _("To complete the activation, follow the instructions that were sent to you in an activation email.") }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Register new account") }}</h1>
+
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Name") }}:</td>
+ <td class="col2">
+ <input name="name" type="text" length="64" />
+ </td>
+ <td class="col3">
+ {{ _("Must be a unique name you login with.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Email") }}:</td>
+ <td class="col2">
+ <input name="email" type="text" length="100" />
+ </td>
+ <td class="col3">
+ {{ _("Type your email address.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Real name (optional)") }}:</td>
+ <td class="col2">
+ <input name="realname" type="text" length="200" />
+ </td>
+ <td class="col3">
+ {{ _("Type you firstname and your lastname here.") }}
+ </td>
+ </tr>
+ </table>
+
+ <h2>{{ _("Account security") }}</h2>
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Password") }}:</td>
+ <td class="col2">
+ <input name="pass1" type="password" />
+ </td>
+ <td class="col3">
+ {{ _("The password is used to secure the login and must be at least 8 characters.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Confirm") }}:</td>
+ <td class="col2">
+ <input name="pass2" type="password" />
+ </td>
+ <td class="col3"> </td>
+ </tr>
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Register") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>
+ {{ _("Repository") }}: {{ repo.name }} -
+ {{ _("from") }} <a href="/distribution/{{ distro.sname }}">{{ distro.name }}</a>
+ </h1>
+
+ <p class="pkg-summary">{{ repo.description }}</p>
+
+ <p>
+ {{ _("This repository contains %s packages and is available for %s.") % (len(repo.packages), locale.list(repo.arches)) }}
+ </p>
+
+ {% if repo.has_actions() %}
+ <h2>{{ _("Pending actions") }}</h2>
+ {{ modules.RepoActionsTable(repo) }}
+ {% end %}
+
+ <h2>{{ _("Waiting packages") }}</h2>
+ <p>
+ {{ _("These packages are waiting to be published.") }}
+ </p>
+ {{ modules.PackageTable2(repo.waiting_packages) }}
+
+ <h2>{{ _("Pushed packages") }}</h2>
+ {{ modules.PackageTable2(repo.pushed_packages) }}
+
+ <h2>{{ _("Log") }}</h2>
+ {{ modules.LogTable(repo.log) }}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Advanced search") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Advanced search") }}</h1>
+
+ <p>
+ XXX TO BE DONE
+ </p>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Search results for '%s'") % escape(query) }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Search results for '%s'") % escape(query) }}</h1>
+ <p>
+ {{ _("These are the results you searched for.") }}
+ </p>
+
+ <table class="form2">
+ {% for pkg in pkgs %}
+ <tr>
+ <td class="col1">
+ <a href="/package/{{ pkg.name }}">{{ pkg.name }}</a>
+ </td>
+ <td class="col2">{{ pkg.summary }}</td>
+ </tr>
+ {% end %}
+ </table>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Source") }}: {{ source.name }}</h1>
+
+ <table>
+ <!-- Status -->
+ <tr>
+ <td>{{ _("Revision") }}</td>
+ <td>{{ source.revision }}</td>
+ </tr>
+
+ <!-- Branch -->
+ <tr>
+ <td>{{ _("Branch") }}</td>
+ <td>
+ {{ source.branch }}
+ </td>
+ </tr>
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Latest builds") }}</h2>
+ {{ modules.BuildTable(source.builds) }}
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Sources repositories") }}{% end block %}
+
+{% block body %}
+ <h1>{{ _("Source repositories") }}</h1>
+ <ul class="sources">
+ {% for source in sources %}
+ <li>
+ <a href="/source/{{ source.id }}">{{ source.name }}</a>
+ </li>
+ {% end %}
+ </ul>
+
+ <div class="submenu">
+ <ul>
+ <li><a href="">{{ _("Add source repository") }}</a></li>
+ <li><a href="">{{ _("Blah 123") }}</a></li>
+ </ul>
+ </div>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Latest user comments") }}</h1>
+
+ {{ modules.CommentsTable(comments, show_package=True, show_user=True) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="/users">{{ _("Show all users") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Delete user %s") % user.realname }}</h1>
+
+ <p>
+ {% if current_user == user %}
+ {{ _("Do you really want to delete your own account?") }}
+ {{ _("You won't be able to login and use this service any more.") }}
+ {% else %}
+ {{ _("Do you really want to delete the user %s?") % user.realname }}
+ {% end %}
+ </p>
+
+ <p class="buttons">
+ <a href="/user/delete/{{ user.name }}?confirmed=1">{{ _("Delete %s") % user.realname }}</a>
+ |
+ <a href="/user/{{ user.name }}">{{ _("Back") }}</a>
+ </p>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Users") }}</h1>
+ <p>
+ {{ _("On this page you can see a list of all users that are known to the system.") }}
+ </p>
+
+ {% if admins %}
+ <h2>{{ _("Administrators") }}</h2>
+ {{ modules.UsersTable(admins) }}
+ {% end %}
+
+ {% if testers %}
+ <h2>{{ _("Testers") }}</h2>
+ {{ modules.UsersTable(testers) }}
+ {% end %}
+
+ {% if users %}
+ <h2>{{ _("Users") }}</h2>
+ {{ modules.UsersTable(users) }}
+ {% end %}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ <li><a href="/users/comments">{{ _("Latest comments") }}</a></li>
+ </ul>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Edit failed") }}</h1>
+ <p>
+ {{ _("We are sorry.") }}
+ {{ _("The user profile cannot be saved.") }}
+ </p>
+
+ <ul>
+ {% for msg in messages %}
+ <li>{{ msg }}</li>
+ {% end %}
+ </ul>
+
+ <p>
+ {{ _("Use the back button on your web browser to go back to the previous page and correct your submission.") }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Edit user %s") % user.realname }}</h1>
+
+ <form method="post" action="">
+ {{ xsrf_form_html() }}
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("Username") }}:</td>
+ <td class="col2">{{ user.name }}</td>
+ <td class="col3">{{ _("Cannot be changed.") }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Email") }}:</td>
+ <td class="col2">
+ <input name="email" type="text" length="100" value="{{ user.email }}" />
+ </td>
+ <td class="col3">
+ {{ _("If the email address is changed, your account will be disabled until you reconfirm the new email address.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Real name (optional)") }}:</td>
+ <td class="col2">
+ <input name="realname" type="text" length="200" value="{{ user.realname }}" />
+ </td>
+ <td class="col3">
+ {{ _("Your real name is used to identify you by others.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Password") }}:</td>
+ <td class="col2">
+ <input name="pass1" type="password" />
+ </td>
+ <td class="col3">
+ {{ _("The password is used to secure the login and must be at least 8 characters.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Confirm") }}:</td>
+ <td class="col2">
+ <input name="pass2" type="password" />
+ </td>
+ <td class="col3">
+ {{ _("Leave the password fields empty to keep the current password.") }}
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Preferred language") }}:</td>
+ <td class="col2">
+ <select name="locale">
+ <option value="">{{ _("Auto-detect") }}</option>
+ {% for l in supported_locales %}
+ <option value="{{ l.code }}" {% if l.code == user.locale %}selected{% end %}>
+ {{ l.name }}
+ </option>
+ {% end %}
+ </select>
+ </td>
+ <td class="col3">{{ _("Auto-detect will use the language transmitted by your browser.") }}</td>
+ </tr>
+
+ {% if current_user.is_admin() and not current_user == user %}
+ </table>
+
+ <h2>{{ _("Admin actions") }}</h2>
+ <table class="form form3">
+ <tr>
+ <td class="col1">{{ _("State") }}</td>
+ <td class="col2">
+ <select name="state">
+ <option value="user">{{ _("User") }}</option>
+ <option value="tester"
+ {% if user.is_tester() %}selected{% end %}>
+ {{ _("Tester") }}
+ </option>
+ <option value="admin"
+ {% if user.is_admin() %}selected{% end %}>
+ {{ _("Admin") }}
+ </option>
+ </select>
+ </td>
+ <td class="col3">
+ {{ _("Define the permissions of the user.") }}
+ </td>
+ </tr>
+ {% end %}
+ <tr>
+ <td colspan="3" class="buttons">
+ <input type="submit" value="{{ _("Save") }}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <h1>{{ _("Edit successful") }}</h1>
+ <p>
+ {{ _("The user profile was successfully altered.") }}
+ {{ _("But as you have changed the email address, you need to re-activate the account.") }}
+ {{ _("Have a look at you mailbox - you already do know what to do.") }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block body %}
+ <img class="avatar" src="{{ user.gravatar_icon() }}" alt="{{ user.name }}" />
+ <h1>{{ _("User") }}: {{ escape(user.realname) }}</h1>
+
+ <table class="form form2">
+ <tr>
+ <td class="col1">{{ _("Username") }}</td>
+ <td class="col2">{{ escape(user.name) }}</td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("Email") }}</td>
+ <td class="col2">
+ <a href="mailto:{{ escape(user.email) }}">{{ escape(user.email) }}</a>
+ </td>
+ </tr>
+ <tr>
+ <td class="col1">{{ _("State") }}</td>
+ <td class="col2">
+ {% if user.is_admin() %}
+ {{ _("Admin") }}
+ {% elif user.is_tester() %}
+ {{ _("Tester") }}
+ {% else %}
+ {{ _("User") }}
+ {% end %}
+ </td>
+ </tr>
+ {% if current_user == user or current_user.is_admin() %}
+ <tr>
+ <td class="col1">{{ _("Registered") }}</td>
+ <td class="col2">{{ locale.format_date(user.registered, full_format=True) }}</td>
+ </tr>
+ {% end %}
+ </table>
+ <div style="clear: both;"> </div>
+
+ <h2>{{ _("Log") }}</h2>
+ {{ modules.LogTable(user.log) }}
+
+ <h2>{{ _("Comments written by %s") % user.realname }}</h2>
+ {{ modules.CommentsTable(user.comments, show_package=True, show_user=False) }}
+{% end block %}
+
+{% block sidebar %}
+ <h1>{{ _("Actions") }}</h1>
+ <ul>
+ {% if current_user == user or current_user.is_admin() %}
+ <li>
+ <a href="/user/edit/{{ user.name }}">{{ _("Account settings") }}</a>
+ </li>
+ <li>
+ <a href="/user/delete/{{ user.name }}">{{ _("Delete account") }}</a>
+ </li>
+ {% end %}
+ </ul>
+{% end block %}
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Pakfire Build Service\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-05-15 22:25+0200\n"
+"PO-Revision-Date: 2011-05-15 22:56+0100\n"
+"Last-Translator: Stefan Schantl <stefan.schantl@ipfire.org>\n"
+"Language-Team: German <info@ipfire.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: GERMANY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: data/templates/build-detail.html:3
+#: data/templates/build-detail.html:7
+#: data/templates/build-detail.html:9
+#: data/templates/modules/log-table.html:10
+msgid "Build"
+msgstr "Build"
+
+#: data/templates/build-detail.html:14
+#: data/templates/file-detail.html:98
+msgid "ID"
+msgstr "ID"
+
+#: data/templates/build-detail.html:18
+#: data/templates/build-filter.html:10
+#: data/templates/user-profile.html:19
+#: data/templates/user-profile-edit.html:71
+msgid "State"
+msgstr "Status"
+
+#: data/templates/build-detail.html:24
+#: data/templates/file-detail.html:14
+#: data/templates/package-detail-list.html:3
+#: data/templates/package-detail-list.html:6
+#: data/templates/package-detail.html:3
+#: data/templates/package-detail.html:6
+#: data/templates/modules/log-table.html:12
+msgid "Package"
+msgstr "Paket"
+
+#: data/templates/build-detail.html:30
+msgid "Architecture"
+msgstr "Architektur"
+
+#: data/templates/build-detail.html:36
+#: data/templates/file-detail.html:104
+msgid "Host"
+msgstr "Host"
+
+#: data/templates/build-detail.html:42
+msgid "No host assigned, yet."
+msgstr "Noch kein Host zugewiesen."
+
+#: data/templates/build-detail.html:47
+#: data/templates/build-priority.html:11
+msgid "Priority"
+msgstr "Priorität"
+
+#: data/templates/build-detail.html:51
+#: data/templates/build-priority.html:14
+msgid "Very high"
+msgstr "Sehr Hoch"
+
+#: data/templates/build-detail.html:53
+#: data/templates/build-priority.html:15
+msgid "High"
+msgstr "Hoch"
+
+#: data/templates/build-detail.html:55
+#: data/templates/build-priority.html:16
+msgid "Medium"
+msgstr "Normal"
+
+#: data/templates/build-detail.html:57
+#: data/templates/build-priority.html:17
+msgid "Low"
+msgstr "Niedrig"
+
+#: data/templates/build-detail.html:59
+#: data/templates/build-priority.html:18
+msgid "Very low"
+msgstr "Sehr Niedrig"
+
+#: data/templates/build-detail.html:67
+msgid "Commit"
+msgstr "Commit"
+
+#: data/templates/build-detail.html:77
+msgid "Author"
+msgstr "Autor"
+
+#: data/templates/build-detail.html:81
+msgid "Committer"
+msgstr "Committer"
+
+#: data/templates/build-detail.html:85
+msgid "Date"
+msgstr "Datum"
+
+#: data/templates/build-detail.html:92
+#: data/templates/file-detail.html:110
+msgid "Time"
+msgstr "Zeit"
+
+#: data/templates/build-detail.html:96
+msgid "Job added"
+msgstr "Job hinzugefügt"
+
+#: data/templates/build-detail.html:100
+msgid "Job started"
+msgstr "Job gestartet"
+
+#: data/templates/build-detail.html:101
+msgid "Not started, yet."
+msgstr "Noch nicht gestartet."
+
+#: data/templates/build-detail.html:104
+msgid "Job finished"
+msgstr "Job beendet"
+
+#: data/templates/build-detail.html:105
+msgid "Not finished, yet."
+msgstr "Noch nicht beendet."
+
+#: data/templates/build-detail.html:110
+msgid "Duration"
+msgstr "Dauer"
+
+#: data/templates/build-detail.html:118
+#: data/templates/file-detail.html:115
+msgid "Files"
+msgstr "Dateien"
+
+#: data/templates/build-detail.html:122
+#: data/templates/package-detail.html:95
+#: data/templates/distro-detail.html:18
+#: data/templates/user-profile.html:39
+#: data/templates/repository-detail.html:24
+#: data/templates/log.html:4
+#: data/templates/base.html:51
+msgid "Log"
+msgstr "Log"
+
+#: data/templates/build-detail.html:127
+#: data/templates/file-detail.html:120
+#: data/templates/package-detail.html:100
+#: data/templates/user-list.html:26
+#: data/templates/build-list.html:11
+#: data/templates/builder-list.html:24
+#: data/templates/build-filter.html:51
+#: data/templates/user-comments.html:10
+#: data/templates/user-profile.html:47
+#: data/templates/distro-list.html:17
+#: data/templates/builder-detail.html:75
+msgid "Actions"
+msgstr "Aktionen"
+
+#: data/templates/build-detail.html:130
+msgid "Re-submit build"
+msgstr "Build neustarten"
+
+#: data/templates/build-detail.html:131
+msgid "Mark as permanently failed"
+msgstr "Als defekt markieren"
+
+#: data/templates/build-detail.html:135
+msgid "Schedule test build"
+msgstr "Test-Build planen"
+
+#: data/templates/build-detail.html:138
+msgid "Modify priority"
+msgstr "Priorität ändern"
+
+#: data/templates/file-detail.html:5
+msgid "File"
+msgstr "Datei"
+
+#: data/templates/file-detail.html:22
+msgid "Description"
+msgstr "Beschreibung"
+
+#: data/templates/file-detail.html:30
+#: data/templates/package-detail.html:13
+msgid "URL"
+msgstr "URL"
+
+#: data/templates/file-detail.html:38
+#: data/templates/package-detail.html:21
+msgid "License"
+msgstr "Lizenz"
+
+#: data/templates/file-detail.html:45
+#: data/templates/package-detail.html:28
+msgid "Maintainer"
+msgstr "Maintainer"
+
+#: data/templates/file-detail.html:52
+msgid "Size"
+msgstr "Größe"
+
+#: data/templates/file-detail.html:58
+msgid "Hash"
+msgstr "Prüfsumme"
+
+#: data/templates/file-detail.html:65
+msgid "Provides"
+msgstr "bietet"
+
+#: data/templates/file-detail.html:73
+msgid "Requires"
+msgstr "benötigt"
+
+#: data/templates/file-detail.html:81
+msgid "Obsoletes"
+msgstr "technisch überholt"
+
+#: data/templates/file-detail.html:89
+msgid "Conflicts"
+msgstr "Konflikte"
+
+#: data/templates/file-detail.html:95
+msgid "Build information"
+msgstr "Build-Informationen"
+
+#: data/templates/file-detail.html:122
+msgid "Download file"
+msgstr "Datei herunterladen"
+
+#: data/templates/source-detail.html:4
+msgid "Source"
+msgstr "Source"
+
+#: data/templates/source-detail.html:9
+msgid "Revision"
+msgstr "Revision"
+
+#: data/templates/source-detail.html:15
+msgid "Branch"
+msgstr "Branch"
+
+#: data/templates/source-detail.html:23
+#: data/templates/index.html:16
+msgid "Latest builds"
+msgstr "Letzte Builds"
+
+#: data/templates/package-detail-list.html:12
+#, python-format
+msgid "There is one version of %(pkg)s."
+msgid_plural "There are %(num)s different versions of %(pkg)s."
+msgstr[0] "Es existiert eine Version von %(pkg)s."
+msgstr[1] "Es existieren %(num)s verschiedene Versionen von %(pkg)s."
+
+#: data/templates/package-detail.html:35
+msgid "Comments"
+msgstr "Kommentare"
+
+#: data/templates/package-detail.html:37
+#, python-format
+msgid "This package got a total credit count of %s credits."
+msgstr "Dieses Paket hat insgesamt %s credits."
+
+#: data/templates/package-detail.html:42
+#: data/templates/package-detail.html:55
+msgid "Add comment"
+msgstr "Kommentieren"
+
+#: data/templates/package-detail.html:67
+msgid "Vote"
+msgstr "Bewertung"
+
+#: data/templates/package-detail.html:68
+msgid "None"
+msgstr "Keine"
+
+#: data/templates/package-detail.html:69
+msgid "Approve"
+msgstr "akzeptieren"
+
+#: data/templates/package-detail.html:70
+msgid "Disapprove"
+msgstr "ablehnen"
+
+#: data/templates/package-detail.html:81
+msgid "You must be logged in to comment."
+msgstr "Sie müssen eingeloggt sein um zu kommentieren."
+
+#: data/templates/package-detail.html:86
+msgid "Package files"
+msgstr "Paketdateien"
+
+#: data/templates/package-detail.html:91
+#: data/templates/base.html:40
+msgid "Build jobs"
+msgstr "Build-Jobs"
+
+#: data/templates/package-detail.html:102
+msgid "Package is broken"
+msgstr "Das Paket ist defekt"
+
+#: data/templates/user-list.html:4
+#: data/templates/user-list.html:20
+#: data/templates/docs-users.html:6
+#: data/templates/docs-users.html:30
+#: data/templates/docs-index.html:14
+#: data/templates/docs-index.html:20
+#: data/templates/base.html:47
+#: data/templates/docs-base.html:12
+msgid "Users"
+msgstr "Benutzer"
+
+#: data/templates/user-list.html:6
+msgid "On this page you can see a list of all users that are known to the system."
+msgstr "Auf dieser Seite sehen Sie einer Übersicht aller bekannten Benutzer."
+
+#: data/templates/user-list.html:10
+msgid "Administrators"
+msgstr "Administratoren"
+
+#: data/templates/user-list.html:15
+#: data/templates/docs-users.html:20
+msgid "Testers"
+msgstr "Tester"
+
+#: data/templates/user-list.html:28
+msgid "Latest comments"
+msgstr "Letzte Kommentare"
+
+#: data/templates/package-list.html:3
+#: data/templates/package-list.html:6
+msgid "Package list"
+msgstr "Paketliste"
+
+#: data/templates/package-list.html:8
+msgid "This is an alphabetically ordered list of all packages in the distribution."
+msgstr "Hier sehen Sie eine alphabetisch sortierte Liste aller Packete der Distribution."
+
+#: data/templates/package-list.html:9
+msgid "Click on a link to see further information about the package."
+msgstr "Klicken Sie auf den Link um mehr Details über das Paket zu sehen."
+
+#: data/templates/package-list.html:13
+msgid "Quick selection:"
+msgstr "Schnellauswahl:"
+
+#: data/templates/user-profile-need-activation.html:4
+msgid "Edit successful"
+msgstr "Bearbeitung erfolgreich"
+
+#: data/templates/user-profile-need-activation.html:6
+msgid "The user profile was successfully altered."
+msgstr "Das Benutzerprofil wurde erfolgreich geändert."
+
+#: data/templates/user-profile-need-activation.html:7
+msgid "But as you have changed the email address, you need to re-activate the account."
+msgstr "Durch die Änderung der E-Mail-Adresse muss der Benutzeraccount neu aktiviert werden."
+
+#: data/templates/user-profile-need-activation.html:8
+msgid "Have a look at you mailbox - you already do know what to do."
+msgstr "Prüfen Sie Ihre Mailbox - Sie wissen ja wie's geht."
+
+#: data/templates/distro-detail.html:4
+msgid "Distribution"
+msgstr "Distrubution"
+
+#: data/templates/distro-detail.html:9
+#, python-format
+msgid "This distribution is available for %s and maintained by %s."
+msgstr "Diese Distribution ist verfügbar für %s und wird gepflegt von %s."
+
+#: data/templates/distro-detail.html:12
+msgid "Binary repositories"
+msgstr "Binärrepositories"
+
+#: data/templates/distro-detail.html:15
+msgid "Sources"
+msgstr "Sourcen"
+
+#: data/templates/index.html:3
+msgid "Welcome to the Pakfire Build Service"
+msgstr "Willkommen beim Pakfire Build Service"
+
+#: data/templates/index.html:6
+msgid "Summary"
+msgstr "Zusammenfassung"
+
+#: data/templates/index.html:8
+msgid "Welcome to the Pakfire Build Server."
+msgstr "Willkommen beim Pakfire Build Server."
+
+#: data/templates/index.html:12
+msgid "Ongoing builds"
+msgstr "Laufende Builds"
+
+#: data/templates/index.html:19
+msgid "Statistics"
+msgstr "Statistiken"
+
+#: data/templates/index.html:22
+#, python-format
+msgid "There is currently one pending build job."
+msgid_plural "There are currently %(num)s pending build jobs."
+msgstr[0] "Ein ausstehender Build Job."
+msgstr[1] "%(num)s ausstehende Build Jobs."
+
+#: data/templates/index.html:25
+#, python-format
+msgid "Average build time is %.1f minutes."
+msgstr "Die durchschnittliche Buildzeit beträgt %.1f Minuten."
+
+#: data/templates/build-priority.html:3
+#: data/templates/build-priority.html:6
+msgid "Edit build priority"
+msgstr "Build Priorität ändern"
+
+#: data/templates/build-priority.html:22
+msgid "Set the priority of the build process."
+msgstr "Die Priorität des Build Prozesses einstellen."
+
+#: data/templates/build-priority.html:28
+msgid "Beware"
+msgstr "Vorsicht"
+
+#: data/templates/build-priority.html:29
+msgid "Shuffeling build jobs can cause problems with the dependency solving."
+msgstr "Eine Veränderung der Reihenfolge kann Probleme mit eventuellen Abhängigkeiten bewirken."
+
+#: data/templates/build-priority.html:30
+msgid "Don't do this if you are not totally sure you won't break anything."
+msgstr "Verändern Sie nichts, sofern Sie sich nicht absolut im Klaren sind was Sie tun."
+
+#: data/templates/build-list.html:3
+#: data/templates/build-list.html:6
+#: data/templates/build-filter.html:3
+msgid "Build job list"
+msgstr "Build-Job-Liste"
+
+#: data/templates/build-list.html:13
+#: data/templates/build-filter.html:6
+msgid "Filter builds"
+msgstr "Builds filtern"
+
+#: data/templates/user-delete.html:4
+#, python-format
+msgid "Delete user %s"
+msgstr "User %s löschen"
+
+#: data/templates/user-delete.html:8
+msgid "Do you really want to delete your own account?"
+msgstr "Möchten Sie wirklich Ihren eigenen Account löschen?"
+
+#: data/templates/user-delete.html:9
+msgid "You won't be able to login and use this service any more."
+msgstr "Sie werden sich nicht mehr einloggen oder den Dienst verwenden können."
+
+#: data/templates/user-delete.html:11
+#, python-format
+msgid "Do you really want to delete the user %s?"
+msgstr "Möchten Sie den Benutzer %s wirklich löschen?"
+
+#: data/templates/user-delete.html:16
+#: data/templates/builder-delete.html:13
+#, python-format
+msgid "Delete %s"
+msgstr "%s löschen"
+
+#: data/templates/user-delete.html:18
+#: data/templates/builder-delete.html:14
+msgid "Back"
+msgstr "Zurück"
+
+#: data/templates/logout.html:4
+msgid "Logout successful"
+msgstr "Logout erfolgreich"
+
+#: data/templates/logout.html:7
+msgid "You have successfully logged out from the Pakfire Build Server."
+msgstr "Sie haben sich erfolgreich vom Pakfire Build Server ausgeloggt."
+
+#: data/templates/logout.html:8
+msgid "Have a nice day!"
+msgstr "Einen schönen Tag noch!"
+
+#: data/templates/logout.html:12
+#: data/templates/login-successful.html:14
+msgid "Go on"
+msgstr "Weiter"
+
+#: data/templates/register-fail.html:4
+msgid "Registration failed"
+msgstr "Registrierung fehlgeschlagen"
+
+#: data/templates/register-fail.html:6
+#: data/templates/register-activation-fail.html:6
+#: data/templates/user-profile-edit-fail.html:6
+msgid "We are sorry."
+msgstr "Es tut uns Leid."
+
+#: data/templates/register-fail.html:7
+msgid "We could not create your requested account."
+msgstr "Der gewünschte Account konnte nicht erstellt werden."
+
+#: data/templates/register-fail.html:17
+#: data/templates/user-profile-edit-fail.html:17
+msgid "Use the back button on your web browser to go back to the previous page and correct your submission."
+msgstr "Bitte verwenden Sie die \"Zurück\" Funktion Ihres Browsers und überprüfen Sie Ihre Eingaben."
+
+#: data/templates/builder-list.html:3
+#: data/templates/builder-list.html:6
+#: data/templates/base.html:43
+msgid "Build servers"
+msgstr "Buildserver"
+
+#: data/templates/builder-list.html:9
+msgid "Builders are those, that do all the hard work."
+msgstr "Die Builder verrichten all die harte Arbeit."
+
+#: data/templates/builder-list.html:10
+msgid "Build jobs are scheduled to these hosts that they process and send back the result."
+msgstr "Build Aufträge werden einzelnen Hosts zugewiesen, diese werden abgearbeitet und das Resultat zurück gesendet."
+
+#: data/templates/builder-list.html:26
+#: data/templates/builder-new.html:3
+msgid "Create new builder"
+msgstr "Neuen Builder anlegen"
+
+#: data/templates/register-activation-fail.html:4
+msgid "Activation failed"
+msgstr "Aktivierung fehlgeschlagen"
+
+#: data/templates/register-activation-fail.html:7
+msgid "The activation of your account has failed."
+msgstr "Die Aktivierung Ihres Accounts schlug fehl."
+
+#: data/templates/register-activation-fail.html:8
+msgid "Possibly the registration code is wrong or your registration timed out."
+msgstr "Möglicherweise ist der Registrierungscode falsch oder abgelaufen."
+
+#: data/templates/docs-users.html:3
+#: data/templates/docs-index.html:3
+#: data/templates/docs-build.html:3
+#: data/templates/docs-build.html:6
+msgid "Legend of the build states"
+msgstr "Legende der Buildstatus"
+
+#: data/templates/docs-users.html:8
+msgid "All users can join the Pakfire Build Service and are separated into three groups:"
+msgstr "Jeder kann sich am Pakfire Build Service anmelden und wird einer der folgenden drei Gruppen zugewiesen:"
+
+#: data/templates/docs-users.html:11
+msgid "Developers"
+msgstr "Entwickler"
+
+#: data/templates/docs-users.html:13
+msgid "Developers manage this build service and have access to all parts of it."
+msgstr "Die Entwickler verwalten den Build Service und haben Zugriff auf all seine Bereichen."
+
+#: data/templates/docs-users.html:14
+msgid "They are responsible to keep the system running and able to push package updates to the repostories."
+msgstr "Sie sind verantwortlich für ein korrektes Arbeiten des Systems und dürfen Paket Updates in den einzelnen Repositorien veröffentlichen."
+
+#: data/templates/docs-users.html:17
+msgid "Guidelines for developers"
+msgstr "Leitfaden für Entwickler"
+
+#: data/templates/docs-users.html:22
+msgid "Testers are like users but have the right to vote on packages, which is used to figure out the quality of the package."
+msgstr "Tester werden wie Benutzer behandelt, haben jedoch die Berechtigung für Pakete abzustimmen um deren Qualität zu bestimmen."
+
+#: data/templates/docs-users.html:23
+msgid "Everyone can become a tester after he or she has proven to know the IPFire system very well."
+msgstr "Um Tester zu werden, sollte man sich mit IPFire gut auskennen. Dies ist für jeden nach einer kurzen Überprüfung möglich. "
+
+#: data/templates/docs-users.html:24
+msgid "On these people depends a very huge amount of the quality of the distribution that is made out of the feedback they give."
+msgstr "Diese Personen tragen durch ihr Feedback eine große Mitverantwortung an der Qualität der Distribution."
+
+#: data/templates/docs-users.html:27
+msgid "Guidelines for testers"
+msgstr "Leitfaden für Tester"
+
+#: data/templates/docs-users.html:32
+msgid "Everybody can join the Pakfire Build Service by registering an account."
+msgstr "Jeder kann sich am Pakfire Build Service nach einer Registrierung anmelden."
+
+#: data/templates/docs-users.html:33
+msgid "After a successful activation you are able to leave comments on packages and give feedback to the developers about its status."
+msgstr "Nach einer erfolgten Aktivierung sollten Sie in der Lage sein, Pakete zu bewerten bzw. Kommentare abzugeben, um die Entwickler über den Paket Status zu informieren."
+
+#: data/templates/docs-users.html:38
+#: data/templates/base.html:22
+msgid "Register"
+msgstr "Registrieren"
+
+#: data/templates/register-activation-success.html:4
+msgid "Activation successful"
+msgstr "Aktivierung erfolgreich"
+
+#: data/templates/register-activation-success.html:6
+#, python-format
+msgid "Your account has been activated, %s."
+msgstr "Ihr Account wurde aktiviert, %s."
+
+#: data/templates/register-activation-success.html:7
+msgid "Have fun!"
+msgstr "Viel Spaß!"
+
+#: data/templates/builder-new.html:6
+msgid "Create a new builder"
+msgstr "Einen neuen Builder anlegen"
+
+#: data/templates/builder-new.html:12
+#: data/templates/builder-edit.html:12
+#: data/templates/register.html:10
+msgid "Name"
+msgstr "Name"
+
+#: data/templates/builder-new.html:17
+msgid "Must be the canonical hostname of the machine."
+msgstr "Der kanonische Hostname des Systems."
+
+#: data/templates/build-filter.html:13
+msgid "All"
+msgstr "Alle"
+
+#: data/templates/build-filter.html:14
+msgid "Running"
+msgstr "Laufend"
+
+#: data/templates/build-filter.html:15
+msgid "Pending"
+msgstr "In der Warteschlange"
+
+#: data/templates/build-filter.html:16
+msgid "Finished"
+msgstr "Beendet"
+
+#: data/templates/build-filter.html:17
+msgid "Failed"
+msgstr "Fehlgeschlgen"
+
+#: data/templates/build-filter.html:18
+#, fuzzy
+msgid "Permanently failed"
+msgstr "Als defekt markieren"
+
+#: data/templates/build-filter.html:19
+msgid "Dispatching"
+msgstr "Vorbereitung"
+
+#: data/templates/build-filter.html:20
+msgid "Uploading"
+msgstr "Hochladen"
+
+#: data/templates/build-filter.html:24
+msgid "Only show builds with given state."
+msgstr "Nur Builds mit diesem Status anzeigen."
+
+#: data/templates/build-filter.html:28
+msgid "Build host"
+msgstr "Buildhost"
+
+#: data/templates/build-filter.html:31
+msgid "Any"
+msgstr "Alle"
+
+#: data/templates/build-filter.html:38
+msgid "Display only builds by selected host."
+msgstr "Nur Builds vom ausgewähltem Host anzeigen."
+
+#: data/templates/build-filter.html:53
+msgid "Show all builds"
+msgstr "Alle Builds anzeigen"
+
+#: data/templates/docs-index.html:6
+msgid "Documents"
+msgstr "Dokumente"
+
+#: data/templates/docs-index.html:8
+msgid "This is a collection of documents that should be read by everybody who is using this system."
+msgstr "Hier handelt es sich um eine Auswahl von Dokumenten, die von jedem der dieses System verwendet auch gelesen werden sollten."
+
+#: data/templates/docs-index.html:9
+msgid "To make this easy for you, the documents are grouped into two parts."
+msgstr "Um Ihnen das Lesen zu erleichtern, wurden die Dokumente in zwei Gruppen eingeteilt."
+
+#: data/templates/docs-index.html:12
+msgid "Documents for testers"
+msgstr "Dokumente für Tester"
+
+#: data/templates/docs-index.html:17
+msgid "Documents for developers"
+msgstr "Dokumente für Entwickler"
+
+#: data/templates/docs-index.html:19
+#: data/templates/docs-base.html:11
+msgid "Builds"
+msgstr "Builds"
+
+#: data/templates/docs-index.html:24
+msgid "Technical documentation is available on the wiki:"
+msgstr "Technische Dokumentation findet sich im Wiki:"
+
+#: data/templates/docs-index.html:25
+msgid "Technical documentation"
+msgstr "Technische Dokumentation"
+
+#: data/templates/builder-edit.html:3
+#: data/templates/builder-edit.html:6
+#, python-format
+msgid "Edit builder %s"
+msgstr "Builder %s bearbeiten"
+
+#: data/templates/builder-edit.html:17
+msgid "The hostname cannot be changed."
+msgstr "Der Hostname kann nicht geändert werden."
+
+#: data/templates/builder-edit.html:21
+msgid "Enabled"
+msgstr "Aktiviert"
+
+#: data/templates/builder-edit.html:26
+msgid "The builder must be enabled in order to process build jobs."
+msgstr "Der Buildserver muss aktiviert sein um Buildjobs zu erhalten."
+
+#: data/templates/builder-edit.html:32
+msgid "Build job settings"
+msgstr "Buildjob-Einstellungen"
+
+#: data/templates/builder-edit.html:34
+msgid "These settings do only take effect if the builder is enabled."
+msgstr "Diese Einstellungen haben nur eine Auswirkung wenn der Server aktiviert ist."
+
+#: data/templates/builder-edit.html:38
+msgid "Authorized to build source packages"
+msgstr "Autorisiert um Sourcepakete zu bauen."
+
+#: data/templates/builder-edit.html:43
+msgid "Only a few build servers are allowed to build source packages."
+msgstr "Nur eine begrenzte Anzahl an Buildserver sind berechtigt Sourcepakete zu bauen."
+
+#: data/templates/builder-edit.html:47
+msgid "Authorized to build binary packages"
+msgstr "Autorisiert um Binärpakete zu bauen."
+
+#: data/templates/builder-edit.html:54
+msgid "Authorized to build test packages"
+msgstr "Autorisiert um Testpakete zu bauen."
+
+#: data/templates/builder-delete.html:3
+#, python-format
+msgid "Delete builder %s"
+msgstr "Builder %s löschen"
+
+#: data/templates/builder-delete.html:6
+#: data/templates/builder-detail.html:3
+#: data/templates/builder-detail.html:6
+#: data/templates/builder-pass.html:4
+msgid "Builder"
+msgstr "Builder"
+
+#: data/templates/builder-delete.html:9
+#, python-format
+msgid "You are going to delete the build host <strong>%s</strong>."
+msgstr "Sie sind im Begriff den build host <strong>%s</strong> zu löschen."
+
+#: data/templates/user-comments.html:4
+msgid "Latest user comments"
+msgstr "Letzte Kommentare"
+
+#: data/templates/user-comments.html:12
+msgid "Show all users"
+msgstr "Alle Benutzer anzeigen"
+
+#: data/templates/user-profile.html:5
+#: data/templates/user-profile.html:26
+#: data/templates/user-profile-edit.html:74
+msgid "User"
+msgstr "Benutzer"
+
+#: data/templates/user-profile.html:9
+#: data/templates/login.html:22
+#: data/templates/user-profile-edit.html:10
+msgid "Username"
+msgstr "Benutzername"
+
+#: data/templates/user-profile.html:13
+#: data/templates/register.html:19
+#: data/templates/user-profile-edit.html:15
+msgid "Email"
+msgstr "Email"
+
+#: data/templates/user-profile.html:22
+#: data/templates/user-profile-edit.html:81
+msgid "Admin"
+msgstr "Admin"
+
+#: data/templates/user-profile.html:24
+#: data/templates/user-profile-edit.html:77
+msgid "Tester"
+msgstr "Tester"
+
+#: data/templates/user-profile.html:32
+msgid "Registered"
+msgstr "Registriert"
+
+#: data/templates/user-profile.html:42
+#, python-format
+msgid "Comments written by %s"
+msgstr "Kommentare von %s"
+
+#: data/templates/user-profile.html:51
+msgid "Account settings"
+msgstr "Accounteinstellungen"
+
+#: data/templates/user-profile.html:54
+msgid "Delete account"
+msgstr "Account löschen"
+
+#: data/templates/repository-detail.html:5
+msgid "Repository"
+msgstr "Repositorium"
+
+#: data/templates/repository-detail.html:6
+msgid "from"
+msgstr "von"
+
+#: data/templates/repository-detail.html:12
+#, python-format
+msgid "This repository contains %s packages and is available for %s."
+msgstr "Das Repositurium enthält %s Pakete und ist verfügbar für %s."
+
+#: data/templates/repository-detail.html:15
+msgid "Waiting packages"
+msgstr "Packete in der Warteschlange"
+
+#: data/templates/repository-detail.html:17
+msgid "These packages are waiting to be published."
+msgstr "Pakete die noch nicht veröffentlicht wurden."
+
+#: data/templates/repository-detail.html:21
+msgid "Pushed packages"
+msgstr "Veröffentlichte Pakete"
+
+#: data/templates/register-success.html:4
+msgid "Registration successful"
+msgstr "Registrierung erfolgreich"
+
+#: data/templates/register-success.html:6
+#, python-format
+msgid "Your new account has been created, %s."
+msgstr "Ihre Account wurde erstellt, %s."
+
+#: data/templates/register-success.html:7
+msgid "To complete the activation, follow the instructions that were sent to you in an activation email."
+msgstr "Um die Aktivierung abzuschließen, folgen Sie bitte den Instruktionen der E-Mail."
+
+#: data/templates/login.html:4
+#: data/templates/base.html:21
+msgid "Login"
+msgstr "Login"
+
+#: data/templates/login.html:8
+msgid "Username and/or password was wrong. Login failed."
+msgstr "Falscher Benutzername und/oder Passwort. Login fehlgeschlagen."
+
+#: data/templates/login.html:13
+msgid "Please type your username and your password to the form to log in."
+msgstr "Bitte tippen Sie ihren Benutzernamen und Ihr Passwort in die vorgesehen Felder."
+
+#: data/templates/login.html:14
+msgid "If you have no account, yet you can create a new one."
+msgstr "Falls Sie noch keinen Account besitzen, können Sie einen erstellen."
+
+#: data/templates/login.html:15
+msgid "Register a new account."
+msgstr "Einen neuen Account erstellen."
+
+#: data/templates/login.html:26
+#: data/templates/register.html:41
+#: data/templates/user-profile-edit.html:33
+msgid "Password"
+msgstr "Passwort"
+
+#: data/templates/register.html:4
+msgid "Register new account"
+msgstr "Einen neuen Account erstellen."
+
+#: data/templates/register.html:15
+msgid "Must be a unique name you login with."
+msgstr "Einmaliger Name für den Login."
+
+#: data/templates/register.html:24
+msgid "Type your email address."
+msgstr "Ihre E-Mail-Adresse."
+
+#: data/templates/register.html:28
+#: data/templates/user-profile-edit.html:24
+msgid "Real name (optional)"
+msgstr "Wahrer Name (optional)"
+
+#: data/templates/register.html:33
+msgid "Type you firstname and your lastname here."
+msgstr "Tippen Sie hier Ihren Vor- und Nachnamen ein."
+
+#: data/templates/register.html:38
+msgid "Account security"
+msgstr "Account-Sicherheit"
+
+#: data/templates/register.html:46
+#: data/templates/user-profile-edit.html:38
+msgid "The password is used to secure the login and must be at least 8 characters."
+msgstr "Das Passwort wird für einen sicheren Login benötigt und muss 8 Zeichen lang sein."
+
+#: data/templates/register.html:50
+#: data/templates/user-profile-edit.html:42
+msgid "Confirm"
+msgstr "Bestätigen"
+
+#: data/templates/login-successful.html:4
+msgid "Login successful"
+msgstr "Login erfolgreich"
+
+#: data/templates/login-successful.html:6
+#, python-format
+msgid "Welcome, %s."
+msgstr "Willkommen, %s."
+
+#: data/templates/login-successful.html:10
+msgid "Your login to the Pakfire Build Server was successful."
+msgstr "Ihr Login am Pakfire Build Server war erfolgreich."
+
+#: data/templates/user-profile-edit-fail.html:4
+msgid "Edit failed"
+msgstr "Bearbeitung fehlgeschlagen"
+
+#: data/templates/user-profile-edit-fail.html:7
+msgid "The user profile cannot be saved."
+msgstr "Das Benutzerprofil kann nicht gespeichert werden."
+
+#: data/templates/modules/files-table.html:8
+msgid "Info"
+msgstr "Info"
+
+#: data/templates/modules/files-table.html:9
+msgid "Download"
+msgstr "Herunterladen"
+
+#: data/templates/modules/comments-table.html:8
+#, python-format
+msgid "on %s"
+msgstr "zu %s"
+
+#: data/templates/modules/comments-table.html:11
+#, python-format
+msgid "by %s"
+msgstr "von %s"
+
+#: data/templates/modules/comments-table.html:18
+msgid "No comments so far."
+msgstr "Bisher keine Kommentare."
+
+#: data/templates/modules/log-table.html:23
+msgid "No log entries, yet."
+msgstr "Noch keine Logeinträge."
+
+#: data/templates/source-list.html:3
+msgid "Sources repositories"
+msgstr "Quellrepositorien"
+
+#: data/templates/source-list.html:6
+msgid "Source repositories"
+msgstr "Quell-Repositories"
+
+#: data/templates/source-list.html:17
+msgid "Add source repository"
+msgstr "Quellrepository hinzufügen"
+
+#: data/templates/source-list.html:18
+msgid "Blah 123"
+msgstr "Blah 123"
+
+#: data/templates/distro-list.html:3
+#: data/templates/distro-list.html:6
+#: data/templates/base.html:37
+msgid "Distributions"
+msgstr "Distributionen"
+
+#: data/templates/distro-list.html:19
+msgid "Add distribution"
+msgstr "Distribution hinzufügen"
+
+#: data/templates/user-profile-edit.html:4
+#, python-format
+msgid "Edit user %s"
+msgstr "Benutzer %s bearbeiten"
+
+#: data/templates/user-profile-edit.html:12
+msgid "Cannot be changed."
+msgstr "Kann nicht geändert werden."
+
+#: data/templates/user-profile-edit.html:20
+msgid "If the email address is changed, your account will be disabled until you reconfirm the new email address."
+msgstr "Nach einer Änderung der E-Mail-Adresse, wird der Account temporär deaktiviert, bis die neue Adresse bestätigt wurde."
+
+#: data/templates/user-profile-edit.html:29
+msgid "Your real name is used to identify you by others."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:47
+msgid "Leave the password fields empty to keep the current password."
+msgstr "Lassen Sie die Passwort Felder leer um das aktuelle Passwort zu behalten."
+
+#: data/templates/user-profile-edit.html:51
+msgid "Preferred language"
+msgstr "Bevorzugte Sprache"
+
+#: data/templates/user-profile-edit.html:54
+msgid "Auto-detect"
+msgstr "Automatisch erkennen"
+
+#: data/templates/user-profile-edit.html:62
+msgid "Auto-detect will use the language transmitted by your browser."
+msgstr "\"Auto-detect\" verwendet die vom Browser übertragene Spracheinstellung."
+
+#: data/templates/user-profile-edit.html:68
+msgid "Admin actions"
+msgstr "Administrator-Aktionen"
+
+#: data/templates/user-profile-edit.html:86
+msgid "Define the permissions of the user."
+msgstr "Rechte des Users festlegen."
+
+#: data/templates/base.html:4
+msgid "No title given"
+msgstr "Kein Titel angegeben"
+
+#: data/templates/base.html:19
+msgid "Logout"
+msgstr "Ausloggen"
+
+#: data/templates/base.html:26
+#, python-format
+msgid "A service by the %s."
+msgstr "Ein Service von %s."
+
+#: data/templates/base.html:31
+msgid "Index"
+msgstr "Startseite"
+
+#: data/templates/base.html:34
+msgid "Packages"
+msgstr "Pakete"
+
+#: data/templates/base.html:84
+msgid "About Pakfire"
+msgstr "Über Pakfire"
+
+#: data/templates/base.html:86
+msgid "Pakfire is the buildsystem that is used to build the IPFire Linux firewall distribution."
+msgstr "Pakfire ist das Buildsystem um die Linux Firewall Distribution IPFire zu bauen."
+
+#: data/templates/base.html:87
+msgid "It also installs and updates packages on the IPFire systems."
+msgstr "Es wird auch für die Installation und zur Aktualisierung von Paketen auf IPFire Systemen verwendet."
+
+#: data/templates/base.html:91
+msgid "Documentation"
+msgstr "Dokumentation"
+
+#: data/templates/base.html:94
+msgid "Documentation index"
+msgstr "Dokumentationsübersicht"
+
+#: data/templates/base.html:100
+msgid "All rights reserved."
+msgstr "Alle Rechte vorbehalten."
+
+#: data/templates/docs-build.html:8
+msgid "Every build that is done by the Pakfire Build Service has to go through several states:"
+msgstr "Jeder einzelne Build des Pakfire Build Services durchläuft mehrere Einzelschritte:"
+
+#: data/templates/docs-build.html:11
+msgid "After checking out the source from the source repository a source package is created and submitted to the build server."
+msgstr "Nach dem Dowload der letzen Änderungen und des Source Codes, wird ein Sourcepaket generiert und an den Pakfire Build Server übertragen."
+
+#: data/templates/docs-build.html:12
+msgid "Starting from inserting a source file to the build service, there are binary build jobs created for every supported architecture."
+msgstr "Nach dem Erhalt der Sorucepakete, erstellt der Buildservice einzelne Build Aufträge für die unterstützten Architekturen."
+
+#: data/templates/docs-build.html:15
+msgid "These get assigned to a build host which has to compile or assemble the package and return it back to the build server."
+msgstr "Diese werden auf die einzelnen Buildhosts verteilt, welche den Code compilieren und das fertige Paket an den Pakfire Build Server zurückschicken."
+
+#: data/templates/docs-build.html:16
+msgid "In the table below, there are all states that a build job goes through:"
+msgstr "In der folgenden Tabelle ist der jeweilige Status, den der Build durchläuft, aufgeführt:"
+
+#: data/templates/docs-build.html:19
+msgid "Build is running"
+msgstr "Build läuft"
+
+#: data/templates/docs-build.html:20
+msgid "Build has failed"
+msgstr "Build ist fehlgeschlagen"
+
+#: data/templates/docs-build.html:21
+msgid "Build is waiting to be processed"
+msgstr "Der Build wartet darauf bearbeitet zu werden"
+
+#: data/templates/docs-build.html:22
+msgid "There was a dependency error when the package was built"
+msgstr "Es gab einen Fehler bei der Auflösung der Abhängigkeiten."
+
+#: data/templates/docs-build.html:23
+msgid "Build is waiting for source to go to pending state"
+msgstr "Der Build wartet auf den Quellcode um in den nächsten Status überzugehen"
+
+#: data/templates/docs-build.html:24
+msgid "Files of this build are transferred to the build server"
+msgstr "Dateien des Builds wurden hochgeladen"
+
+#: data/templates/docs-build.html:25
+msgid "Files are being uploaded to the service"
+msgstr "Dateien werden gerade hochgeladen"
+
+#: data/templates/docs-build.html:26
+msgid "Build has an unknown state"
+msgstr "Der Build hat einen unbekannten Status"
+
+#: data/templates/builder-detail.html:10
+msgid "Status"
+msgstr "Status"
+
+#: data/templates/builder-detail.html:16
+msgid "Load average"
+msgstr "Durchschnittliche Last"
+
+#: data/templates/builder-detail.html:24
+msgid "Supported architectures"
+msgstr "Unterstützte Architekturen"
+
+#: data/templates/builder-detail.html:26
+#: data/templates/builder-detail.html:59
+msgid "Unknown"
+msgstr "Unbekannt"
+
+#: data/templates/builder-detail.html:32
+msgid "Configuration"
+msgstr "Konfiguration"
+
+#: data/templates/builder-detail.html:35
+msgid "Builds source packages"
+msgstr "Baut Sourcepakete"
+
+#: data/templates/builder-detail.html:37
+#: data/templates/builder-detail.html:43
+#: data/templates/builder-detail.html:49
+msgid "Yes"
+msgstr "Ja"
+
+#: data/templates/builder-detail.html:37
+#: data/templates/builder-detail.html:43
+#: data/templates/builder-detail.html:49
+msgid "No"
+msgstr "Nein"
+
+#: data/templates/builder-detail.html:41
+msgid "Builds binary packages"
+msgstr "Baut Binärpakete"
+
+#: data/templates/builder-detail.html:47
+msgid "Runs tests"
+msgstr "Bearbeitet Tests"
+
+#: data/templates/builder-detail.html:55
+msgid "Host information"
+msgstr "Host-Informationen"
+
+#: data/templates/builder-detail.html:58
+msgid "CPU model"
+msgstr "CPU-Modell"
+
+#: data/templates/builder-detail.html:62
+msgid "Memory"
+msgstr "Arbeitsspeicher"
+
+#: data/templates/builder-detail.html:69
+msgid "Currently running builds on this host"
+msgstr "Aktuell laufende Jobs auf diesem Host"
+
+#: data/templates/builder-detail.html:78
+msgid "Show all build jobs"
+msgstr "Alle Buildjobs anzeigen"
+
+#: data/templates/builder-detail.html:82
+msgid "Edit builder"
+msgstr "Builder bearbeiten"
+
+#: data/templates/builder-detail.html:86
+msgid "Renew passphrase"
+msgstr "Passphrase erneuern"
+
+#: data/templates/builder-detail.html:90
+msgid "Delete builder"
+msgstr "Builder löschen"
+
+#: data/templates/builder-pass.html:8
+#, python-format
+msgid "The new host <strong>%s</strong> has been successfully created."
+msgstr "Der neue Host <strong>%s</strong> wurde erfolgreich erstellt."
+
+#: data/templates/builder-pass.html:10
+#, python-format
+msgid "The passphrase for <strong>%s</strong> has been regenerated."
+msgstr "Die Passphrase für <strong>%s</strong> wurde erneut generiert."
+
+#: data/templates/builder-pass.html:13
+msgid "For authorization to the Pakfire Master Server there is a passphrase required which must be configured to the host."
+msgstr "Eine erforderliche Passphrase für das System, um sich am Pakfire Master Server anzumelden."
+
+#: data/templates/builder-pass.html:14
+msgid "This passphrase is:"
+msgstr "Die Passphrase lautet:"
+
+#: data/templates/builder-pass.html:20
+msgid "Next"
+msgstr "Weiter"
+
+#: data/templates/build-schedule.html:6
+#, python-format
+msgid "Schedule test build for %s"
+msgstr "Einen Testbuild für %s planen"
+
+#: data/templates/build-schedule.html:8
+msgid "A test build is used to check if a package builds with the current package set."
+msgstr "Testbuilds dienen um festzustellen, ob ein Paket mit den aktuellen Einstellungen korrekt baut."
+
+#: data/templates/build-schedule.html:9
+msgid "In this way, developers are able to find quality issues fast and without actively searching for them."
+msgstr "Mit dieser Methode sind die Entwickler in der Lage Qualitätsmängel schnell und ohne aufwändiger Suche zu finden."
+
+#: data/templates/build-schedule.html:12
+msgid "As this build platform only has a limited amount of performance, test builds only have a very less priority."
+msgstr "Da die verfügbaren Ressourcen dieser Plattform begrenzt sind, werden Testbuilds mit einer geringeren Priorität eingereiht."
+
+#: data/templates/build-schedule.html:13
+msgid "However, you can manually request to run a test."
+msgstr "Es ist auch möglich, manuell einen Test durchführen zu lassen."
+
+#: data/templates/build-schedule.html:16
+msgid "The build job will be started when a build slot is available but not before the given time."
+msgstr "Der Buildjob wird gestartet sobald ein Server zur Verfügung steht, jedoch niemals vor der angegebenen Zeit."
+
+#: data/templates/build-schedule.html:23
+msgid "Start time"
+msgstr "Startzeit"
+
+#: data/templates/build-schedule.html:26
+msgid "As soon as possible"
+msgstr "Sobald als möglich"
+
+#: data/templates/build-schedule.html:27
+msgid "After 5 minutes"
+msgstr "In 5 Minuten"
+
+#: data/templates/build-schedule.html:28
+msgid "After 15 minutes"
+msgstr "In 15 Minuten"
+
+#: data/templates/build-schedule.html:29
+msgid "After one hour"
+msgstr "In einer Stunde"
+
+#: data/templates/build-schedule.html:30
+msgid "After one day"
+msgstr "In einem Tag"
+
+#: data/templates/build-schedule.html:34
+msgid "Set the time after which the build job starts."
+msgstr "Zeitpunkt des Buildjobs einstellen."
+
+#: data/templates/docs-base.html:5
+msgid "All Documents"
+msgstr "Alle Dokumente"
+
+#: data/templates/docs-base.html:9
+msgid "Topics"
+msgstr "Themen"
+
+#: web/handlers_auth.py:53
+msgid "No username provided."
+msgstr "Kein Benutzername angegeben."
+
+#: web/handlers_auth.py:55
+msgid "The given username is already taken."
+msgstr "Der gewählte Benutzername ist bereits belegt."
+
+#: web/handlers_auth.py:58
+#: web/handlers_users.py:85
+msgid "No email address provided."
+msgstr "Keine Email-Adresse angegeben."
+
+#: web/handlers_auth.py:60
+#: web/handlers_users.py:87
+msgid "Email address is invalid."
+msgstr "Die Email-Adresse ist ungültig."
+
+#: web/handlers_auth.py:62
+msgid "The given email address is already used for another account."
+msgstr "Die eingegebene Email-Adresse wird bereits für einen anderen Account verwendet."
+
+#: web/handlers_auth.py:66
+msgid "No password provided."
+msgstr "Kein Passwort angegeben."
+
+#: web/handlers_auth.py:68
+#: web/handlers_users.py:91
+msgid "Password has less than 8 characters."
+msgstr "Das Passwort hat weniger als 8 Zeichen."
+
+#: web/handlers_auth.py:70
+#: web/handlers_users.py:93
+msgid "Passwords do not match."
+msgstr "Passwörter stimmen nicht überein."
+
+#: web/handlers_users.py:97
+msgid "The choosen locale is invalid."
+msgstr "Die gewählte Sprache ist ungültig."
+
+#: backend/users.py:207
+msgid "Account Activation"
+msgstr "Account aktivieren"
+
+#: backend/users.py:209
+msgid "You, or somebody using you email address, has registered an account on the Pakfire Build Service."
+msgstr "Sie, oder jemand, der Ihre E-Mail-Adresse benutzt, hat einen Account im Pakfire Build Service registriert."
+
+#: backend/users.py:211
+msgid "To activate your account, please click on the link below."
+msgstr "Um Ihren Account zu aktivieren, klicken Sie auf den Link."
+
+#: backend/constants.py:22
+msgid "Build job created"
+msgstr "Buildjob erstellt"
+
+#: backend/constants.py:23
+msgid "Build job is now pending"
+msgstr "Build ist noch ausstehend"
+
+#: backend/constants.py:24
+msgid "Build job is dispatching"
+msgstr "Build wird herruntergeladen"
+
+#: backend/constants.py:25
+msgid "Build job is running"
+msgstr "Build läuft"
+
+#: backend/constants.py:26
+msgid "Build job has failed"
+msgstr "Build ist fehlgeschlagen"
+
+#: backend/constants.py:27
+msgid "Build job has permanently failed"
+msgstr "Build ist defekt."
+
+#: backend/constants.py:28
+msgid "Build job has dependency errors"
+msgstr "Der Buildjob hat Abhängigkeitsprobleme"
+
+#: backend/constants.py:29
+msgid "Build job is waiting for the source package"
+msgstr "Der Build wartet auf den Quellcode"
+
+#: backend/constants.py:30
+msgid "Build job is finished"
+msgstr "Build erfolgreich"
+
+#: backend/constants.py:31
+msgid "Build job has an unknown state"
+msgstr "Build hat einen unbekannten Status"
+
+#: backend/constants.py:32
+msgid "Build job is uploading"
+msgstr "Build wird hochgeladen."
+
+#: backend/constants.py:37
+#, python-format
+msgid "Build job failed: %(build_name)s"
+msgstr "Build Job %(build_name)s fehlgeschlagen"
+
+#: backend/constants.py:38
+#, python-format
+msgid ""
+"The build job \"%(build_name)s\" has failed.\n"
+"\n"
+"This could have a couple of reasons and needs to be investigated by you.\n"
+"\n"
+"Here is more information about the incident:\n"
+"\n"
+" Build name: %(build_name)s\n"
+" Build host: %(build_host)s\n"
+"\n"
+"Click on this link to get all details about the build:\n"
+" http://pakfire.ipfire.org/build/%(build_uuid)s\n"
+"\n"
+"Sincerely,\n"
+" The Pakfire Build Service"
+msgstr ""
+"Der Build von \"%(build_name)s\" ist fehlgeschlagen.\n"
+"\n"
+"Dies kann aus mehreren Gründen passiert sein und muss von Ihnen begutachtet werden.\n"
+"\n"
+"Mehr Informationen zum Vorgang:\n"
+"\n"
+" Build Name: %(build_name)s\n"
+" Build Host: %(build_host)s\n"
+"\n"
+"Klicken Sie auf den folgenden Link um alle verfügbaren Informationen zum Build zu erhalten:\n"
+" http://pakfire.ipfire.org/build/%(build_uuid)s\n"
+"\n"
+"Mit freundlichen Grüßen,\n"
+" The Pakfire Build Service"
+
+#: backend/constants.py:55
+#, python-format
+msgid "Build job finished: %(build_name)s"
+msgstr "Build Job %(build_name)s erfolgreich"
+
+#: backend/constants.py:56
+#, python-format
+msgid ""
+"The build job \"%(build_name)s\" has finished.\n"
+"\n"
+"If you are the maintainer, it is up to you to push it to one or more repositories.\n"
+"\n"
+"Click on this link to get all details about the build:\n"
+" http://pakfire.ipfire.org/build/%(build_uuid)s\n"
+"\n"
+"Sincerely,\n"
+" The Pakfire Build Service"
+msgstr ""
+"Der Build von \"%(build_name)s\" wurde erfolgreich beendet.\n"
+"\n"
+"Wenn Sie der Maintainer des Paketes sind, obliegt es Ihnen dieses in einem oder mehreren Repositoren zu veröffentlichen.\n"
+"\n"
+"Klicken Sie auf den folgenden Link um alle verfügbaren Informationen zum Build zu erhalten:\n"
+" http://pakfire.ipfire.org/build/%(build_uuid)s\n"
+"\n"
+"Mit freundlichen Grüßen,\n"
+" The Pakfire Build Service"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "January"
+msgstr "Januar"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "February"
+msgstr "Februar"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "March"
+msgstr "März"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "April"
+msgstr "April"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "May"
+msgstr "Mai"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "June"
+msgstr "Juni"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "July"
+msgstr "Juli"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "August"
+msgstr "August"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "September"
+msgstr "September"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "October"
+msgstr "Oktober"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "November"
+msgstr "November"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "December"
+msgstr "Dezember"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Monday"
+msgstr "Montag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Tuesday"
+msgstr "Dienstag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Wednesday"
+msgstr "Mittwoch"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Thursday"
+msgstr "Donnerstag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Friday"
+msgstr "Freitag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Saturday"
+msgstr "Samstag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Sunday"
+msgstr "Sonntag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:274
+#, python-format
+msgid "1 second ago"
+msgid_plural "%(seconds)d seconds ago"
+msgstr[0] "vor einer Sekunde"
+msgstr[1] "vor %(seconds)d Sekunden"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:279
+#, python-format
+msgid "1 minute ago"
+msgid_plural "%(minutes)d minutes ago"
+msgstr[0] "vor einer Minute"
+msgstr[1] "vor %(minutes)d Minuten"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:283
+#, python-format
+msgid "1 hour ago"
+msgid_plural "%(hours)d hours ago"
+msgstr[0] "vor einer Stunde"
+msgstr[1] "vor %(hours)d Stunden"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:287
+#, python-format
+msgid "%(time)s"
+msgstr "%(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:290
+msgid "yesterday"
+msgstr "gestern"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:291
+#, python-format
+msgid "yesterday at %(time)s"
+msgstr "gestern um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:293
+#, python-format
+msgid "%(weekday)s"
+msgstr "%(weekday)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:294
+#, python-format
+msgid "%(weekday)s at %(time)s"
+msgstr "%(weekday)s um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:296
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:338
+#, python-format
+msgid "%(month_name)s %(day)s"
+msgstr "%(month_name)s %(day)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:297
+#, python-format
+msgid "%(month_name)s %(day)s at %(time)s"
+msgstr "%(month_name)s %(day)s um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:300
+#, python-format
+msgid "%(month_name)s %(day)s, %(year)s"
+msgstr "%(month_name)s %(day)s, %(year)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:301
+#, python-format
+msgid "%(month_name)s %(day)s, %(year)s at %(time)s"
+msgstr "%(month_name)s %(day)s, %(year)s um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:332
+#, python-format
+msgid "%(weekday)s, %(month_name)s %(day)s"
+msgstr "%(weekday)s, %(month_name)s %(day)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:353
+#, python-format
+msgid "%(commas)s and %(last)s"
+msgstr "%(commas)s und %(last)s"
+
+#, fuzzy
+#~ msgid ""
+#~ "A build job that was submitted to the Pakfire Build Service has failed."
+#~ msgstr "Willkommen beim Pakfire Build Service"
+
+#~ msgid "Show more builds."
+#~ msgstr "Mehr Builds anzeigen."
+
+#~ msgid "Disable builder"
+#~ msgstr "Builder deaktivieren"
+
+#~ msgid "Information"
+#~ msgstr "Informationen"
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Pakfire Build Service\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-05-07 16:21+0200\n"
+"PO-Revision-Date: 2011-05-07 18:02+0100\n"
+"Last-Translator: Stefan Schantl <stefan.schantl@ipfire.org>\n"
+"Language-Team: German <info@ipfire.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: GERMANY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: data/templates/build-detail.html:5
+#: data/templates/build-detail.html:7
+#: data/templates/modules/log-table.html:10
+msgid "Build"
+msgstr "Build"
+
+#: data/templates/build-detail.html:15
+#: data/templates/build-filter.html:10
+#: data/templates/user-profile.html:19
+#: data/templates/user-profile-edit.html:61
+msgid "State"
+msgstr "Status"
+
+#: data/templates/build-detail.html:22
+#: data/templates/file-detail.html:17
+#: data/templates/package-detail-list.html:3
+#: data/templates/package-detail-list.html:6
+#: data/templates/package-detail.html:4
+#: data/templates/modules/log-table.html:12
+msgid "Package"
+msgstr "Paket"
+
+#: data/templates/build-detail.html:30
+msgid "Architecture"
+msgstr "Architektur"
+
+#: data/templates/build-detail.html:37
+#: data/templates/file-detail.html:106
+msgid "Host"
+msgstr "Host"
+
+#: data/templates/build-detail.html:43
+msgid "No host assigned, yet."
+msgstr "Noch kein Host zugewiesen."
+
+#: data/templates/build-detail.html:48
+#: data/templates/file-detail.html:112
+msgid "Time"
+msgstr "Zeit"
+
+#: data/templates/build-detail.html:52
+msgid "Job added"
+msgstr "Job hinzugefügt"
+
+#: data/templates/build-detail.html:56
+msgid "Job started"
+msgstr "Job gestartet"
+
+#: data/templates/build-detail.html:57
+msgid "Not started, yet."
+msgstr "Noch nicht gestartet."
+
+#: data/templates/build-detail.html:60
+msgid "Job finished"
+msgstr "Job beendet"
+
+#: data/templates/build-detail.html:61
+msgid "Not finished, yet."
+msgstr "Noch nicht beendet."
+
+#: data/templates/build-detail.html:66
+msgid "Duration"
+msgstr "Dauer"
+
+#: data/templates/build-detail.html:73
+#: data/templates/file-detail.html:117
+msgid "Files"
+msgstr "Dateien"
+
+#: data/templates/build-detail.html:77
+#: data/templates/package-detail.html:91
+#: data/templates/distro-detail.html:18
+#: data/templates/user-profile.html:39
+#: data/templates/repository-detail.html:24
+#: data/templates/log.html:4
+#: data/templates/base.html:51
+msgid "Log"
+msgstr "Log"
+
+#: data/templates/build-detail.html:82
+#: data/templates/package-detail.html:96
+#: data/templates/user-list.html:26
+#: data/templates/build-list.html:11
+#: data/templates/builder-list.html:24
+#: data/templates/build-filter.html:49
+#: data/templates/user-comments.html:10
+#: data/templates/user-profile.html:47
+#: data/templates/distro-list.html:17
+#: data/templates/builder-detail.html:57
+msgid "Actions"
+msgstr "Aktionen"
+
+#: data/templates/build-detail.html:85
+msgid "Re-submit build"
+msgstr "Build neustarten"
+
+#: data/templates/build-detail.html:86
+msgid "Mark as permanently failed"
+msgstr "Als defekt markieren"
+
+#: data/templates/build-detail.html:88
+msgid "Schedule test build"
+msgstr "Test-Build planen"
+
+#: data/templates/file-detail.html:7
+#: data/templates/modules/files-table.html:10
+msgid "Download"
+msgstr "Herunterladen"
+
+#: data/templates/file-detail.html:10
+msgid "Information"
+msgstr "Informationen"
+
+#: data/templates/file-detail.html:25
+msgid "Description"
+msgstr "Beschreibung"
+
+#: data/templates/file-detail.html:33
+#: data/templates/package-detail.html:11
+msgid "URL"
+msgstr "URL"
+
+#: data/templates/file-detail.html:39
+#: data/templates/package-detail.html:17
+msgid "License"
+msgstr "Lizenz"
+
+#: data/templates/file-detail.html:46
+#: data/templates/package-detail.html:24
+msgid "Maintainer"
+msgstr "Maintainer"
+
+#: data/templates/file-detail.html:53
+msgid "Size"
+msgstr "Größe"
+
+#: data/templates/file-detail.html:59
+msgid "Hash"
+msgstr "Prüfsumme"
+
+#: data/templates/file-detail.html:66
+msgid "Provides"
+msgstr "bietet"
+
+#: data/templates/file-detail.html:74
+msgid "Requires"
+msgstr "benötigt"
+
+#: data/templates/file-detail.html:82
+msgid "Obsoletes"
+msgstr "technisch überholt"
+
+#: data/templates/file-detail.html:90
+msgid "Conflicts"
+msgstr "Konflikte"
+
+#: data/templates/file-detail.html:96
+msgid "Build information"
+msgstr "Build-Informationen"
+
+#: data/templates/file-detail.html:100
+msgid "ID"
+msgstr "ID"
+
+#: data/templates/source-detail.html:4
+msgid "Source"
+msgstr "Source"
+
+#: data/templates/source-detail.html:9
+msgid "Revision"
+msgstr "Revision"
+
+#: data/templates/source-detail.html:15
+msgid "Branch"
+msgstr "Branch"
+
+#: data/templates/source-detail.html:23
+#: data/templates/index.html:14
+#: data/templates/builder-detail.html:49
+msgid "Latest builds"
+msgstr "Letzte Builds"
+
+#: data/templates/package-detail-list.html:12
+#, python-format
+msgid "There is one version of %(pkg)s."
+msgid_plural "There are %(num)s different versions of %(pkg)s."
+msgstr[0] "Es existiert eine Version von %(pkg)s."
+msgstr[1] "Es existieren %(num)s verschiedene Versionen von %(pkg)s."
+
+#: data/templates/package-detail.html:31
+msgid "Comments"
+msgstr "Kommentare"
+
+#: data/templates/package-detail.html:33
+#, python-format
+msgid "This package got a total credit count of %s credits."
+msgstr "Dieses Paket hat insgesamt %s credits."
+
+#: data/templates/package-detail.html:38
+#: data/templates/package-detail.html:51
+msgid "Add comment"
+msgstr "Kommentieren"
+
+#: data/templates/package-detail.html:63
+msgid "Vote"
+msgstr "Bewertung"
+
+#: data/templates/package-detail.html:64
+msgid "None"
+msgstr "Keine"
+
+#: data/templates/package-detail.html:65
+msgid "Approve"
+msgstr "akzeptieren"
+
+#: data/templates/package-detail.html:66
+msgid "Disapprove"
+msgstr "ablehnen"
+
+#: data/templates/package-detail.html:77
+msgid "You must be logged in to comment."
+msgstr "Sie müssen eingeloggt sein um zu kommentieren."
+
+#: data/templates/package-detail.html:82
+msgid "Package files"
+msgstr "Paketdateien"
+
+#: data/templates/package-detail.html:87
+#: data/templates/base.html:40
+msgid "Build jobs"
+msgstr "Build-Jobs"
+
+#: data/templates/package-detail.html:98
+msgid "Package is broken"
+msgstr "Das Paket ist defekt"
+
+#: data/templates/user-list.html:4
+#: data/templates/user-list.html:20
+#: data/templates/base.html:47
+msgid "Users"
+msgstr "Benutzer"
+
+#: data/templates/user-list.html:6
+msgid "On this page you can see a list of all users that are known to the system."
+msgstr "Auf dieser Seite sehen Sie einer Übersicht aller bekannten Benutzer."
+
+#: data/templates/user-list.html:10
+msgid "Administrators"
+msgstr "Administratoren"
+
+#: data/templates/user-list.html:15
+msgid "Testers"
+msgstr "Tester"
+
+#: data/templates/user-list.html:28
+msgid "Latest comments"
+msgstr "Letzte Kommentare"
+
+#: data/templates/package-list.html:3
+#: data/templates/package-list.html:6
+msgid "Package list"
+msgstr "Paketliste"
+
+#: data/templates/package-list.html:8
+msgid "This is an alphabetically ordered list of all packages in the distribution."
+msgstr "Hier sehen Sie eine alphabetisch sortierte Liste aller Packete der Distribution."
+
+#: data/templates/package-list.html:9
+msgid "Click on a link to see further information about the package."
+msgstr "Klicken Sie auf den Link um mehr Details über das Paket zu sehen."
+
+#: data/templates/package-list.html:13
+msgid "Quick selection:"
+msgstr "Schnellauswahl:"
+
+#: data/templates/user-profile-need-activation.html:4
+msgid "Edit successful"
+msgstr "Bearbeitung erfolgreich"
+
+#: data/templates/user-profile-need-activation.html:6
+msgid "The user profile was successfully altered."
+msgstr "Das Benutzerprofil wurde erfolgreich geändert."
+
+#: data/templates/user-profile-need-activation.html:7
+msgid "But as you have changed the email address, you need to re-activate the account."
+msgstr "Durch die Änderung der E-Mail-Adresse muss der Benutzeraccount neu aktiviert werden."
+
+#: data/templates/user-profile-need-activation.html:8
+msgid "Have a look at you mailbox - you already do know what to do."
+msgstr "Prüfen Sie Ihre Mailbox - Sie wissen ja wie's geht."
+
+#: data/templates/distro-detail.html:4
+msgid "Distribution"
+msgstr "Distrubution"
+
+#: data/templates/distro-detail.html:9
+#, python-format
+msgid "This distribution is available for %s and maintained by %s."
+msgstr "Diese Distribution ist verfügbar für %s und wird gepflegt von %s."
+
+#: data/templates/distro-detail.html:12
+msgid "Binary repositories"
+msgstr "Binärrepositories"
+
+#: data/templates/distro-detail.html:15
+msgid "Sources"
+msgstr "Sourcen"
+
+#: data/templates/index.html:4
+msgid "Summary"
+msgstr "Zusammenfassung"
+
+#: data/templates/index.html:6
+msgid "Welcome to the Pakfire Build Server."
+msgstr "Willkommen beim Pakfire Build Server."
+
+#: data/templates/index.html:10
+msgid "Ongoing builds"
+msgstr "Laufende Builds"
+
+#: data/templates/index.html:17
+msgid "Show more builds."
+msgstr "Mehr Builds anzeigen."
+
+#: data/templates/build-list.html:3
+#: data/templates/build-list.html:6
+#: data/templates/build-filter.html:3
+msgid "Build job list"
+msgstr "Build-Job-Liste"
+
+#: data/templates/build-list.html:13
+#: data/templates/build-filter.html:6
+msgid "Filter builds"
+msgstr "Builds filtern"
+
+#: data/templates/user-delete.html:4
+#, python-format
+msgid "Delete user %s"
+msgstr "User %s löschen"
+
+#: data/templates/user-delete.html:8
+msgid "Do you really want to delete your own account?"
+msgstr "Möchten Sie wirklich Ihren eigenen Account löschen?"
+
+#: data/templates/user-delete.html:9
+msgid "You won't be able to login and use this service any more."
+msgstr "Sie werden sich nicht mehr einloggen oder den Dienst verwenden können."
+
+#: data/templates/user-delete.html:11
+#, python-format
+msgid "Do you really want to delete the user %s?"
+msgstr "Möchten Sie den Benutzer %s wirklich löschen?"
+
+#: data/templates/user-delete.html:16
+#: data/templates/builder-delete.html:11
+#, python-format
+msgid "Delete %s"
+msgstr "%s löschen"
+
+#: data/templates/user-delete.html:18
+#: data/templates/builder-delete.html:12
+msgid "Back"
+msgstr "Zurück"
+
+#: data/templates/logout.html:4
+msgid "Logout successful"
+msgstr "Logout erfolgreich"
+
+#: data/templates/logout.html:7
+msgid "You have successfully logged out from the Pakfire Build Server."
+msgstr "Sie haben sich erfolgreich vom Pakfire Build Server ausgeloggt."
+
+#: data/templates/logout.html:8
+msgid "Have a nice day!"
+msgstr "Einen schönen Tag noch!"
+
+#: data/templates/logout.html:12
+#: data/templates/login-successful.html:14
+msgid "Go on"
+msgstr "Weiter"
+
+#: data/templates/register-fail.html:4
+msgid "Registration failed"
+msgstr "Registrierung fehlgeschlagen"
+
+#: data/templates/register-fail.html:6
+#: data/templates/register-activation-fail.html:6
+#: data/templates/user-profile-edit-fail.html:6
+msgid "We are sorry."
+msgstr "Es tut uns Leid."
+
+#: data/templates/register-fail.html:7
+msgid "We could not create your requested account."
+msgstr "Der gewünschte Account konnte nicht erstellt werden."
+
+#: data/templates/register-fail.html:17
+#: data/templates/user-profile-edit-fail.html:17
+msgid "Use the back button on your web browser to go back to the previous page and correct your submission."
+msgstr ""
+
+#: data/templates/builder-list.html:3
+#: data/templates/builder-list.html:6
+#: data/templates/base.html:43
+msgid "Build servers"
+msgstr "Buildserver"
+
+#: data/templates/builder-list.html:9
+msgid "Builders are those, that do all the hard work."
+msgstr "Die Builder verrichten all die harte Arbeit."
+
+#: data/templates/builder-list.html:10
+msgid "Build jobs are scheduled to these hosts that they process and send back the result."
+msgstr ""
+
+#: data/templates/builder-list.html:26
+msgid "Create new builder"
+msgstr "Neuen Builder anlegen"
+
+#: data/templates/register-activation-fail.html:4
+msgid "Activation failed"
+msgstr "Aktivierung fehlgeschlagen"
+
+#: data/templates/register-activation-fail.html:7
+msgid "The activation of your account has failed."
+msgstr "Die Aktivierung Ihres Accounts schlug fehl."
+
+#: data/templates/register-activation-fail.html:8
+msgid "Possibly the registration code is wrong or your registration timed out."
+msgstr ""
+
+#: data/templates/register-activation-success.html:4
+msgid "Activation successful"
+msgstr "Aktivierung erfolgreich"
+
+#: data/templates/register-activation-success.html:6
+#, python-format
+msgid "Your account has been activated, %s."
+msgstr "Ihr Account wurde aktiviert, %s."
+
+#: data/templates/register-activation-success.html:7
+msgid "Have fun!"
+msgstr "Viel Spaß!"
+
+#: data/templates/builder-new.html:4
+msgid "Create a new builder"
+msgstr "Einen neuen Builder anlegen"
+
+#: data/templates/builder-new.html:10
+#: data/templates/register.html:10
+msgid "Name"
+msgstr "Name"
+
+#: data/templates/builder-new.html:15
+msgid "Must be the canonical hostname of the machine."
+msgstr ""
+
+#: data/templates/build-filter.html:13
+msgid "All"
+msgstr "Alle"
+
+#: data/templates/build-filter.html:14
+msgid "Running"
+msgstr ""
+
+#: data/templates/build-filter.html:15
+msgid "Pending"
+msgstr ""
+
+#: data/templates/build-filter.html:16
+msgid "Finished"
+msgstr ""
+
+#: data/templates/build-filter.html:17
+msgid "Dispatching"
+msgstr ""
+
+#: data/templates/build-filter.html:18
+msgid "Uploading"
+msgstr ""
+
+#: data/templates/build-filter.html:22
+msgid "Only show builds with given state."
+msgstr "Nur Builds mit diesem Status anzeigen."
+
+#: data/templates/build-filter.html:26
+msgid "Build host"
+msgstr "Buildhost"
+
+#: data/templates/build-filter.html:29
+msgid "Any"
+msgstr "Alle"
+
+#: data/templates/build-filter.html:36
+msgid "Display only builds by selected host."
+msgstr "Nur Builds vom ausgewähltem Host anzeigen."
+
+#: data/templates/build-filter.html:51
+msgid "Show all builds"
+msgstr "Alle Builds anzeigen"
+
+#: data/templates/builder-delete.html:4
+#: data/templates/builder-detail.html:4
+#: data/templates/builder-pass.html:4
+msgid "Builder"
+msgstr "Builder"
+
+#: data/templates/builder-delete.html:7
+#, python-format
+msgid "You are going to delete the build host <strong>%s</strong>."
+msgstr ""
+
+#: data/templates/user-comments.html:4
+msgid "Latest user comments"
+msgstr "Letzte Kommentare"
+
+#: data/templates/user-comments.html:12
+msgid "Show all users"
+msgstr "Alle Benutzer anzeigen"
+
+#: data/templates/user-profile.html:5
+#: data/templates/user-profile.html:26
+#: data/templates/user-profile-edit.html:64
+msgid "User"
+msgstr "Benutzer"
+
+#: data/templates/user-profile.html:9
+#: data/templates/login.html:22
+#: data/templates/user-profile-edit.html:10
+msgid "Username"
+msgstr "Benutzername"
+
+#: data/templates/user-profile.html:13
+#: data/templates/register.html:19
+#: data/templates/user-profile-edit.html:15
+msgid "Email"
+msgstr "Email"
+
+#: data/templates/user-profile.html:22
+#: data/templates/user-profile-edit.html:71
+msgid "Admin"
+msgstr "Admin"
+
+#: data/templates/user-profile.html:24
+#: data/templates/user-profile-edit.html:67
+msgid "Tester"
+msgstr "Tester"
+
+#: data/templates/user-profile.html:32
+msgid "Registered"
+msgstr "Registriert"
+
+#: data/templates/user-profile.html:42
+#, python-format
+msgid "Comments written by %s"
+msgstr "Kommentare von %s"
+
+#: data/templates/user-profile.html:51
+msgid "Account settings"
+msgstr "Accounteinstellungen"
+
+#: data/templates/user-profile.html:54
+msgid "Delete account"
+msgstr "Account löschen"
+
+#: data/templates/repository-detail.html:5
+msgid "Repository"
+msgstr "Repositorium"
+
+#: data/templates/repository-detail.html:6
+msgid "from"
+msgstr "von"
+
+#: data/templates/repository-detail.html:12
+#, python-format
+msgid "This repository contains %s packages and is available for %s."
+msgstr ""
+
+#: data/templates/repository-detail.html:15
+msgid "Waiting packages"
+msgstr ""
+
+#: data/templates/repository-detail.html:17
+msgid "These packages are waiting to be published."
+msgstr ""
+
+#: data/templates/repository-detail.html:21
+msgid "Pushed packages"
+msgstr ""
+
+#: data/templates/register-success.html:4
+msgid "Registration successful"
+msgstr ""
+
+#: data/templates/register-success.html:6
+#, python-format
+msgid "Your new account has been created, %s."
+msgstr ""
+
+#: data/templates/register-success.html:7
+msgid "To complete the activation, follow the instructions that were sent to you in an activation email."
+msgstr ""
+
+#: data/templates/login.html:4
+#: data/templates/base.html:21
+msgid "Login"
+msgstr "Login"
+
+#: data/templates/login.html:8
+msgid "Username and/or password was wrong. Login failed."
+msgstr ""
+
+#: data/templates/login.html:13
+msgid "Please type your username and your password to the form to log in."
+msgstr ""
+
+#: data/templates/login.html:14
+msgid "If you have no account, yet you can create a new one."
+msgstr ""
+
+#: data/templates/login.html:15
+msgid "Register a new account."
+msgstr ""
+
+#: data/templates/login.html:26
+#: data/templates/register.html:41
+#: data/templates/user-profile-edit.html:33
+msgid "Password"
+msgstr "Passwort"
+
+#: data/templates/register.html:4
+msgid "Register new account"
+msgstr ""
+
+#: data/templates/register.html:15
+msgid "Must be a unique name you login with."
+msgstr ""
+
+#: data/templates/register.html:24
+msgid "Type your email address."
+msgstr ""
+
+#: data/templates/register.html:28
+#: data/templates/user-profile-edit.html:24
+msgid "Real name (optional)"
+msgstr "Wahrer Name (optional)"
+
+#: data/templates/register.html:33
+#: data/templates/user-profile-edit.html:29
+msgid "Your real name is used to identify you by others."
+msgstr ""
+
+#: data/templates/register.html:38
+msgid "Account security"
+msgstr "Account-Sicherheit"
+
+#: data/templates/register.html:46
+#: data/templates/user-profile-edit.html:38
+msgid "The password is used to secure the login and must be at least 8 characters."
+msgstr ""
+
+#: data/templates/register.html:50
+#: data/templates/user-profile-edit.html:42
+msgid "Confirm"
+msgstr "Bestätigen"
+
+#: data/templates/login-successful.html:4
+msgid "Login successful"
+msgstr "Login erfolgreich"
+
+#: data/templates/login-successful.html:6
+#, python-format
+msgid "Welcome, %s."
+msgstr "Willkommen, %s."
+
+#: data/templates/login-successful.html:10
+msgid "Your login to the Pakfire Build Server was successful."
+msgstr ""
+
+#: data/templates/user-profile-edit-fail.html:4
+msgid "Edit failed"
+msgstr "Bearbeitung fehlgeschlagen"
+
+#: data/templates/user-profile-edit-fail.html:7
+msgid "The user profile cannot be saved."
+msgstr "Das Benutzerprofil kann nicht gespeichert werden."
+
+#: data/templates/modules/files-table.html:9
+msgid "Info"
+msgstr "Info"
+
+#: data/templates/modules/comments-table.html:8
+#, python-format
+msgid "on %s"
+msgstr "zu %s"
+
+#: data/templates/modules/comments-table.html:11
+#, python-format
+msgid "by %s"
+msgstr "von %s"
+
+#: data/templates/modules/comments-table.html:18
+msgid "No comments so far."
+msgstr "Bisher keine Kommentare."
+
+#: data/templates/modules/log-table.html:23
+msgid "No log entries, yet."
+msgstr "Noch keine Logeinträge."
+
+#: data/templates/source-list.html:3
+msgid "Sources repositories"
+msgstr "Quellrepositorien"
+
+#: data/templates/source-list.html:6
+msgid "Source repositories"
+msgstr "Quell-Repositories"
+
+#: data/templates/source-list.html:17
+msgid "Add source repository"
+msgstr "Quellrepository hinzufügen"
+
+#: data/templates/source-list.html:18
+#, fuzzy
+msgid "Blah 123"
+msgstr "Blah 123"
+
+#: data/templates/distro-list.html:3
+#: data/templates/distro-list.html:6
+#: data/templates/base.html:37
+msgid "Distributions"
+msgstr "Distributionen"
+
+#: data/templates/distro-list.html:19
+msgid "Add distribution"
+msgstr "Distribution hinzufügen"
+
+#: data/templates/user-profile-edit.html:4
+#, python-format
+msgid "Edit user %s"
+msgstr "Benutzer %s bearbeiten"
+
+#: data/templates/user-profile-edit.html:12
+msgid "Cannot be changed."
+msgstr "Kann nicht geändert werden."
+
+#: data/templates/user-profile-edit.html:20
+msgid "If the email address is changed, your account will be disabled until you reconfirm the new email address."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:47
+msgid "Leave the password fields empty to keep the current password."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:58
+msgid "Admin actions"
+msgstr "Administrator-Aktionen"
+
+#: data/templates/user-profile-edit.html:76
+msgid "Define the permissions of the user."
+msgstr "Rechte des Users festlegen."
+
+#: data/templates/base.html:4
+msgid "No title given"
+msgstr "Kein Titel angegeben"
+
+#: data/templates/base.html:19
+msgid "Logout"
+msgstr "Ausloggen"
+
+#: data/templates/base.html:22
+msgid "Register"
+msgstr "Registrieren"
+
+#: data/templates/base.html:26
+#, python-format
+msgid "A service by the %s."
+msgstr "Ein Service von %s."
+
+#: data/templates/base.html:31
+msgid "Index"
+msgstr "Startseite"
+
+#: data/templates/base.html:34
+msgid "Packages"
+msgstr "Pakete"
+
+#: data/templates/base.html:84
+msgid "About Pakfire"
+msgstr "Über Pakfire"
+
+#: data/templates/base.html:86
+msgid "Pakfire is the buildsystem that is used to build the IPFire Linux firewall distribution."
+msgstr ""
+
+#: data/templates/base.html:87
+msgid "It also installs and updates packages on the IPFire systems."
+msgstr ""
+
+#: data/templates/base.html:91
+msgid "Documentation"
+msgstr "Dokumentation"
+
+#: data/templates/base.html:94
+msgid "Documentation index"
+msgstr "Dokumentationsübersicht"
+
+#: data/templates/builder-detail.html:8
+msgid "Status"
+msgstr "Status"
+
+#: data/templates/builder-detail.html:14
+msgid "Load average"
+msgstr "Durchschnittliche Last"
+
+#: data/templates/builder-detail.html:22
+msgid "Supported architectures"
+msgstr "Unterstützte Architekturen"
+
+#: data/templates/builder-detail.html:24
+#: data/templates/builder-detail.html:34
+msgid "Unknown"
+msgstr "Unbekannt"
+
+#: data/templates/builder-detail.html:30
+msgid "Host information"
+msgstr "Host-Informationen"
+
+#: data/templates/builder-detail.html:33
+msgid "CPU model"
+msgstr "CPU-Modell"
+
+#: data/templates/builder-detail.html:37
+msgid "Memory"
+msgstr "Arbeitsspeicher"
+
+#: data/templates/builder-detail.html:44
+msgid "Currently running builds on this host"
+msgstr "Aktuell laufende Jobs auf diesem Host"
+
+#: data/templates/builder-detail.html:60
+msgid "Edit builder"
+msgstr "Builder bearbeiten"
+
+#: data/templates/builder-detail.html:64
+msgid "Disable builder"
+msgstr "Builder deaktivieren"
+
+#: data/templates/builder-detail.html:68
+msgid "Enable builder"
+msgstr "Builder aktivieren"
+
+#: data/templates/builder-detail.html:71
+msgid "Renew passphrase"
+msgstr "Passphrase erneuern"
+
+#: data/templates/builder-detail.html:75
+msgid "Delete builder"
+msgstr "Builder löschen"
+
+#: data/templates/builder-pass.html:8
+#, python-format
+msgid "The new host <strong>%s</strong> has been successfully created."
+msgstr ""
+
+#: data/templates/builder-pass.html:10
+#, python-format
+msgid "The passphrase for <strong>%s</strong> has been regenerated."
+msgstr ""
+
+#: data/templates/builder-pass.html:13
+msgid "For authorization to the Pakfire Master Server there is a passphrase required which must be configured to the host."
+msgstr ""
+
+#: data/templates/builder-pass.html:14
+msgid "This passphrase is:"
+msgstr "Die Passphrase lautet:"
+
+#: data/templates/builder-pass.html:20
+msgid "Next"
+msgstr "Weiter"
+
+#: web/handlers_auth.py:53
+msgid "No username provided."
+msgstr "Kein Benutzername angegeben."
+
+#: web/handlers_auth.py:55
+msgid "The given username is already taken."
+msgstr "Der gewählte Benutzername ist bereits belegt."
+
+#: web/handlers_auth.py:58
+#: web/handlers_users.py:76
+msgid "No email address provided."
+msgstr "Keine Email-Adresse angegeben."
+
+#: web/handlers_auth.py:60
+#: web/handlers_users.py:78
+msgid "Email address is invalid."
+msgstr "Die Email-Adresse ist ungültig."
+
+#: web/handlers_auth.py:62
+msgid "The given email address is already used for another account."
+msgstr "Die eingegebene Email-Adresse wird bereits für einen anderen Account verwendet."
+
+#: web/handlers_auth.py:66
+msgid "No password provided."
+msgstr "Kein Passwort angegeben."
+
+#: web/handlers_auth.py:68
+#: web/handlers_users.py:82
+msgid "Password has less than 8 characters."
+msgstr "Das Passwort hat weniger als 8 Zeichen."
+
+#: web/handlers_auth.py:70
+#: web/handlers_users.py:84
+msgid "Passwords do not match."
+msgstr "Passwörter stimmen nicht überein."
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "January"
+msgstr "Januar"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "February"
+msgstr "Februar"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "March"
+msgstr "März"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "April"
+msgstr "April"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "May"
+msgstr "Mai"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "June"
+msgstr "Juni"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "July"
+msgstr "Juli"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "August"
+msgstr "August"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "September"
+msgstr "September"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "October"
+msgstr "Oktober"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "November"
+msgstr "November"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "December"
+msgstr "Dezember"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Monday"
+msgstr "Montag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Tuesday"
+msgstr "Dienstag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Wednesday"
+msgstr "Mittwoch"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Thursday"
+msgstr "Donnerstag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Friday"
+msgstr "Freitag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Saturday"
+msgstr "Samstag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Sunday"
+msgstr "Sonntag"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:274
+#, python-format
+msgid "1 second ago"
+msgid_plural "%(seconds)d seconds ago"
+msgstr[0] "vor einer Sekunde"
+msgstr[1] "vor %(seconds)d Sekunden"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:279
+#, python-format
+msgid "1 minute ago"
+msgid_plural "%(minutes)d minutes ago"
+msgstr[0] "vor einer Minute"
+msgstr[1] "vor %(minutes)d Minuten"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:283
+#, python-format
+msgid "1 hour ago"
+msgid_plural "%(hours)d hours ago"
+msgstr[0] "vor einer Stunde"
+msgstr[1] "vor %(hours)d Stunden"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:287
+#, python-format
+msgid "%(time)s"
+msgstr "%(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:290
+msgid "yesterday"
+msgstr "gestern"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:291
+#, python-format
+msgid "yesterday at %(time)s"
+msgstr "gestern um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:293
+#, python-format
+msgid "%(weekday)s"
+msgstr "%(weekday)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:294
+#, python-format
+msgid "%(weekday)s at %(time)s"
+msgstr "%(weekday)s um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:296
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:338
+#, python-format
+msgid "%(month_name)s %(day)s"
+msgstr "%(month_name)s %(day)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:297
+#, python-format
+msgid "%(month_name)s %(day)s at %(time)s"
+msgstr "%(month_name)s %(day)s um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:300
+#, python-format
+msgid "%(month_name)s %(day)s, %(year)s"
+msgstr "%(month_name)s %(day)s, %(year)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:301
+#, python-format
+msgid "%(month_name)s %(day)s, %(year)s at %(time)s"
+msgstr "%(month_name)s %(day)s, %(year)s um %(time)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:332
+#, python-format
+msgid "%(weekday)s, %(month_name)s %(day)s"
+msgstr "%(weekday)s, %(month_name)s %(day)s"
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:353
+#, python-format
+msgid "%(commas)s and %(last)s"
+msgstr "%(commas)s und %(last)s"
+
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-05-15 22:58+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: data/templates/build-detail.html:3 data/templates/build-detail.html:7
+#: data/templates/build-detail.html:9 data/templates/modules/log-table.html:10
+msgid "Build"
+msgstr ""
+
+#: data/templates/build-detail.html:14 data/templates/file-detail.html:98
+msgid "ID"
+msgstr ""
+
+#: data/templates/build-detail.html:18 data/templates/build-filter.html:10
+#: data/templates/user-profile.html:19
+#: data/templates/user-profile-edit.html:71
+msgid "State"
+msgstr ""
+
+#: data/templates/build-detail.html:24 data/templates/file-detail.html:14
+#: data/templates/package-detail-list.html:3
+#: data/templates/package-detail-list.html:6
+#: data/templates/package-detail.html:3 data/templates/package-detail.html:6
+#: data/templates/modules/log-table.html:12
+msgid "Package"
+msgstr ""
+
+#: data/templates/build-detail.html:30
+msgid "Architecture"
+msgstr ""
+
+#: data/templates/build-detail.html:36 data/templates/file-detail.html:104
+msgid "Host"
+msgstr ""
+
+#: data/templates/build-detail.html:42
+msgid "No host assigned, yet."
+msgstr ""
+
+#: data/templates/build-detail.html:47 data/templates/build-priority.html:11
+msgid "Priority"
+msgstr ""
+
+#: data/templates/build-detail.html:51 data/templates/build-priority.html:14
+msgid "Very high"
+msgstr ""
+
+#: data/templates/build-detail.html:53 data/templates/build-priority.html:15
+msgid "High"
+msgstr ""
+
+#: data/templates/build-detail.html:55 data/templates/build-priority.html:16
+msgid "Medium"
+msgstr ""
+
+#: data/templates/build-detail.html:57 data/templates/build-priority.html:17
+msgid "Low"
+msgstr ""
+
+#: data/templates/build-detail.html:59 data/templates/build-priority.html:18
+msgid "Very low"
+msgstr ""
+
+#: data/templates/build-detail.html:67
+msgid "Commit"
+msgstr ""
+
+#: data/templates/build-detail.html:77
+msgid "Author"
+msgstr ""
+
+#: data/templates/build-detail.html:81
+msgid "Committer"
+msgstr ""
+
+#: data/templates/build-detail.html:85
+msgid "Date"
+msgstr ""
+
+#: data/templates/build-detail.html:92 data/templates/file-detail.html:110
+msgid "Time"
+msgstr ""
+
+#: data/templates/build-detail.html:96
+msgid "Job added"
+msgstr ""
+
+#: data/templates/build-detail.html:100
+msgid "Job started"
+msgstr ""
+
+#: data/templates/build-detail.html:101
+msgid "Not started, yet."
+msgstr ""
+
+#: data/templates/build-detail.html:104
+msgid "Job finished"
+msgstr ""
+
+#: data/templates/build-detail.html:105
+msgid "Not finished, yet."
+msgstr ""
+
+#: data/templates/build-detail.html:110
+msgid "Duration"
+msgstr ""
+
+#: data/templates/build-detail.html:118 data/templates/file-detail.html:115
+msgid "Files"
+msgstr ""
+
+#: data/templates/build-detail.html:122 data/templates/package-detail.html:95
+#: data/templates/distro-detail.html:18 data/templates/user-profile.html:39
+#: data/templates/repository-detail.html:24 data/templates/log.html:4
+#: data/templates/base.html:51
+msgid "Log"
+msgstr ""
+
+#: data/templates/build-detail.html:127 data/templates/file-detail.html:120
+#: data/templates/package-detail.html:100 data/templates/user-list.html:26
+#: data/templates/build-list.html:11 data/templates/builder-list.html:24
+#: data/templates/build-filter.html:51 data/templates/user-comments.html:10
+#: data/templates/user-profile.html:47 data/templates/distro-list.html:17
+#: data/templates/builder-detail.html:75
+msgid "Actions"
+msgstr ""
+
+#: data/templates/build-detail.html:130
+msgid "Re-submit build"
+msgstr ""
+
+#: data/templates/build-detail.html:131
+msgid "Mark as permanently failed"
+msgstr ""
+
+#: data/templates/build-detail.html:135
+msgid "Schedule test build"
+msgstr ""
+
+#: data/templates/build-detail.html:138
+msgid "Modify priority"
+msgstr ""
+
+#: data/templates/file-detail.html:5
+msgid "File"
+msgstr ""
+
+#: data/templates/file-detail.html:22
+msgid "Description"
+msgstr ""
+
+#: data/templates/file-detail.html:30 data/templates/package-detail.html:13
+msgid "URL"
+msgstr ""
+
+#: data/templates/file-detail.html:38 data/templates/package-detail.html:21
+msgid "License"
+msgstr ""
+
+#: data/templates/file-detail.html:45 data/templates/package-detail.html:28
+msgid "Maintainer"
+msgstr ""
+
+#: data/templates/file-detail.html:52
+msgid "Size"
+msgstr ""
+
+#: data/templates/file-detail.html:58
+msgid "Hash"
+msgstr ""
+
+#: data/templates/file-detail.html:65
+msgid "Provides"
+msgstr ""
+
+#: data/templates/file-detail.html:73
+msgid "Requires"
+msgstr ""
+
+#: data/templates/file-detail.html:81
+msgid "Obsoletes"
+msgstr ""
+
+#: data/templates/file-detail.html:89
+msgid "Conflicts"
+msgstr ""
+
+#: data/templates/file-detail.html:95
+msgid "Build information"
+msgstr ""
+
+#: data/templates/file-detail.html:122
+msgid "Download file"
+msgstr ""
+
+#: data/templates/source-detail.html:4
+msgid "Source"
+msgstr ""
+
+#: data/templates/source-detail.html:9
+msgid "Revision"
+msgstr ""
+
+#: data/templates/source-detail.html:15
+msgid "Branch"
+msgstr ""
+
+#: data/templates/source-detail.html:23 data/templates/index.html:16
+msgid "Latest builds"
+msgstr ""
+
+#: data/templates/package-detail-list.html:12
+#, python-format
+msgid "There is one version of %(pkg)s."
+msgid_plural "There are %(num)s different versions of %(pkg)s."
+msgstr[0] ""
+msgstr[1] ""
+
+#: data/templates/package-detail.html:35
+msgid "Comments"
+msgstr ""
+
+#: data/templates/package-detail.html:37
+#, python-format
+msgid "This package got a total credit count of %s credits."
+msgstr ""
+
+#: data/templates/package-detail.html:42 data/templates/package-detail.html:55
+msgid "Add comment"
+msgstr ""
+
+#: data/templates/package-detail.html:67
+msgid "Vote"
+msgstr ""
+
+#: data/templates/package-detail.html:68
+msgid "None"
+msgstr ""
+
+#: data/templates/package-detail.html:69
+msgid "Approve"
+msgstr ""
+
+#: data/templates/package-detail.html:70
+msgid "Disapprove"
+msgstr ""
+
+#: data/templates/package-detail.html:81
+msgid "You must be logged in to comment."
+msgstr ""
+
+#: data/templates/package-detail.html:86
+msgid "Package files"
+msgstr ""
+
+#: data/templates/package-detail.html:91 data/templates/base.html:40
+msgid "Build jobs"
+msgstr ""
+
+#: data/templates/package-detail.html:102
+msgid "Package is broken"
+msgstr ""
+
+#: data/templates/user-list.html:4 data/templates/user-list.html:20
+#: data/templates/docs-users.html:6 data/templates/docs-users.html:30
+#: data/templates/docs-index.html:14 data/templates/docs-index.html:20
+#: data/templates/base.html:47 data/templates/docs-base.html:12
+msgid "Users"
+msgstr ""
+
+#: data/templates/user-list.html:6
+msgid ""
+"On this page you can see a list of all users that are known to the system."
+msgstr ""
+
+#: data/templates/user-list.html:10
+msgid "Administrators"
+msgstr ""
+
+#: data/templates/user-list.html:15 data/templates/docs-users.html:20
+msgid "Testers"
+msgstr ""
+
+#: data/templates/user-list.html:28
+msgid "Latest comments"
+msgstr ""
+
+#: data/templates/package-list.html:3 data/templates/package-list.html:6
+msgid "Package list"
+msgstr ""
+
+#: data/templates/package-list.html:8
+msgid ""
+"This is an alphabetically ordered list of all packages in the distribution."
+msgstr ""
+
+#: data/templates/package-list.html:9
+msgid "Click on a link to see further information about the package."
+msgstr ""
+
+#: data/templates/package-list.html:13
+msgid "Quick selection:"
+msgstr ""
+
+#: data/templates/user-profile-need-activation.html:4
+msgid "Edit successful"
+msgstr ""
+
+#: data/templates/user-profile-need-activation.html:6
+msgid "The user profile was successfully altered."
+msgstr ""
+
+#: data/templates/user-profile-need-activation.html:7
+msgid ""
+"But as you have changed the email address, you need to re-activate the "
+"account."
+msgstr ""
+
+#: data/templates/user-profile-need-activation.html:8
+msgid "Have a look at you mailbox - you already do know what to do."
+msgstr ""
+
+#: data/templates/distro-detail.html:4
+msgid "Distribution"
+msgstr ""
+
+#: data/templates/distro-detail.html:9
+#, python-format
+msgid "This distribution is available for %s and maintained by %s."
+msgstr ""
+
+#: data/templates/distro-detail.html:12
+msgid "Binary repositories"
+msgstr ""
+
+#: data/templates/distro-detail.html:15
+msgid "Sources"
+msgstr ""
+
+#: data/templates/index.html:3
+msgid "Welcome to the Pakfire Build Service"
+msgstr ""
+
+#: data/templates/index.html:6
+msgid "Summary"
+msgstr ""
+
+#: data/templates/index.html:8
+msgid "Welcome to the Pakfire Build Server."
+msgstr ""
+
+#: data/templates/index.html:12
+msgid "Ongoing builds"
+msgstr ""
+
+#: data/templates/index.html:19
+msgid "Statistics"
+msgstr ""
+
+#: data/templates/index.html:22
+#, python-format
+msgid "There is currently one pending build job."
+msgid_plural "There are currently %(num)s pending build jobs."
+msgstr[0] ""
+msgstr[1] ""
+
+#: data/templates/index.html:25
+#, python-format
+msgid "Average build time is %.1f minutes."
+msgstr ""
+
+#: data/templates/build-priority.html:3 data/templates/build-priority.html:6
+msgid "Edit build priority"
+msgstr ""
+
+#: data/templates/build-priority.html:22
+msgid "Set the priority of the build process."
+msgstr ""
+
+#: data/templates/build-priority.html:28
+msgid "Beware"
+msgstr ""
+
+#: data/templates/build-priority.html:29
+msgid "Shuffeling build jobs can cause problems with the dependency solving."
+msgstr ""
+
+#: data/templates/build-priority.html:30
+msgid "Don't do this if you are not totally sure you won't break anything."
+msgstr ""
+
+#: data/templates/build-list.html:3 data/templates/build-list.html:6
+#: data/templates/build-filter.html:3
+msgid "Build job list"
+msgstr ""
+
+#: data/templates/build-list.html:13 data/templates/build-filter.html:6
+msgid "Filter builds"
+msgstr ""
+
+#: data/templates/user-delete.html:4
+#, python-format
+msgid "Delete user %s"
+msgstr ""
+
+#: data/templates/user-delete.html:8
+msgid "Do you really want to delete your own account?"
+msgstr ""
+
+#: data/templates/user-delete.html:9
+msgid "You won't be able to login and use this service any more."
+msgstr ""
+
+#: data/templates/user-delete.html:11
+#, python-format
+msgid "Do you really want to delete the user %s?"
+msgstr ""
+
+#: data/templates/user-delete.html:16 data/templates/builder-delete.html:13
+#, python-format
+msgid "Delete %s"
+msgstr ""
+
+#: data/templates/user-delete.html:18 data/templates/builder-delete.html:14
+msgid "Back"
+msgstr ""
+
+#: data/templates/logout.html:4
+msgid "Logout successful"
+msgstr ""
+
+#: data/templates/logout.html:7
+msgid "You have successfully logged out from the Pakfire Build Server."
+msgstr ""
+
+#: data/templates/logout.html:8
+msgid "Have a nice day!"
+msgstr ""
+
+#: data/templates/logout.html:12 data/templates/login-successful.html:14
+msgid "Go on"
+msgstr ""
+
+#: data/templates/register-fail.html:4
+msgid "Registration failed"
+msgstr ""
+
+#: data/templates/register-fail.html:6
+#: data/templates/register-activation-fail.html:6
+#: data/templates/user-profile-edit-fail.html:6
+msgid "We are sorry."
+msgstr ""
+
+#: data/templates/register-fail.html:7
+msgid "We could not create your requested account."
+msgstr ""
+
+#: data/templates/register-fail.html:17
+#: data/templates/user-profile-edit-fail.html:17
+msgid ""
+"Use the back button on your web browser to go back to the previous page and "
+"correct your submission."
+msgstr ""
+
+#: data/templates/builder-list.html:3 data/templates/builder-list.html:6
+#: data/templates/base.html:43
+msgid "Build servers"
+msgstr ""
+
+#: data/templates/builder-list.html:9
+msgid "Builders are those, that do all the hard work."
+msgstr ""
+
+#: data/templates/builder-list.html:10
+msgid ""
+"Build jobs are scheduled to these hosts that they process and send back the "
+"result."
+msgstr ""
+
+#: data/templates/builder-list.html:26 data/templates/builder-new.html:3
+msgid "Create new builder"
+msgstr ""
+
+#: data/templates/register-activation-fail.html:4
+msgid "Activation failed"
+msgstr ""
+
+#: data/templates/register-activation-fail.html:7
+msgid "The activation of your account has failed."
+msgstr ""
+
+#: data/templates/register-activation-fail.html:8
+msgid "Possibly the registration code is wrong or your registration timed out."
+msgstr ""
+
+#: data/templates/docs-users.html:3 data/templates/docs-index.html:3
+#: data/templates/docs-build.html:3 data/templates/docs-build.html:6
+msgid "Legend of the build states"
+msgstr ""
+
+#: data/templates/docs-users.html:8
+msgid ""
+"All users can join the Pakfire Build Service and are separated into three "
+"groups:"
+msgstr ""
+
+#: data/templates/docs-users.html:11
+msgid "Developers"
+msgstr ""
+
+#: data/templates/docs-users.html:13
+msgid ""
+"Developers manage this build service and have access to all parts of it."
+msgstr ""
+
+#: data/templates/docs-users.html:14
+msgid ""
+"They are responsible to keep the system running and able to push package "
+"updates to the repostories."
+msgstr ""
+
+#: data/templates/docs-users.html:17
+msgid "Guidelines for developers"
+msgstr ""
+
+#: data/templates/docs-users.html:22
+msgid ""
+"Testers are like users but have the right to vote on packages, which is used "
+"to figure out the quality of the package."
+msgstr ""
+
+#: data/templates/docs-users.html:23
+msgid ""
+"Everyone can become a tester after he or she has proven to know the IPFire "
+"system very well."
+msgstr ""
+
+#: data/templates/docs-users.html:24
+msgid ""
+"On these people depends a very huge amount of the quality of the "
+"distribution that is made out of the feedback they give."
+msgstr ""
+
+#: data/templates/docs-users.html:27
+msgid "Guidelines for testers"
+msgstr ""
+
+#: data/templates/docs-users.html:32
+msgid "Everybody can join the Pakfire Build Service by registering an account."
+msgstr ""
+
+#: data/templates/docs-users.html:33
+msgid ""
+"After a successful activation you are able to leave comments on packages and "
+"give feedback to the developers about its status."
+msgstr ""
+
+#: data/templates/docs-users.html:38 data/templates/base.html:22
+msgid "Register"
+msgstr ""
+
+#: data/templates/register-activation-success.html:4
+msgid "Activation successful"
+msgstr ""
+
+#: data/templates/register-activation-success.html:6
+#, python-format
+msgid "Your account has been activated, %s."
+msgstr ""
+
+#: data/templates/register-activation-success.html:7
+msgid "Have fun!"
+msgstr ""
+
+#: data/templates/builder-new.html:6
+msgid "Create a new builder"
+msgstr ""
+
+#: data/templates/builder-new.html:12 data/templates/builder-edit.html:12
+#: data/templates/register.html:10
+msgid "Name"
+msgstr ""
+
+#: data/templates/builder-new.html:17
+msgid "Must be the canonical hostname of the machine."
+msgstr ""
+
+#: data/templates/build-filter.html:13
+msgid "All"
+msgstr ""
+
+#: data/templates/build-filter.html:14
+msgid "Running"
+msgstr ""
+
+#: data/templates/build-filter.html:15
+msgid "Pending"
+msgstr ""
+
+#: data/templates/build-filter.html:16
+msgid "Finished"
+msgstr ""
+
+#: data/templates/build-filter.html:17
+msgid "Failed"
+msgstr ""
+
+#: data/templates/build-filter.html:18
+msgid "Permanently failed"
+msgstr ""
+
+#: data/templates/build-filter.html:19
+msgid "Dispatching"
+msgstr ""
+
+#: data/templates/build-filter.html:20
+msgid "Uploading"
+msgstr ""
+
+#: data/templates/build-filter.html:24
+msgid "Only show builds with given state."
+msgstr ""
+
+#: data/templates/build-filter.html:28
+msgid "Build host"
+msgstr ""
+
+#: data/templates/build-filter.html:31
+msgid "Any"
+msgstr ""
+
+#: data/templates/build-filter.html:38
+msgid "Display only builds by selected host."
+msgstr ""
+
+#: data/templates/build-filter.html:53
+msgid "Show all builds"
+msgstr ""
+
+#: data/templates/docs-index.html:6
+msgid "Documents"
+msgstr ""
+
+#: data/templates/docs-index.html:8
+msgid ""
+"This is a collection of documents that should be read by everybody who is "
+"using this system."
+msgstr ""
+
+#: data/templates/docs-index.html:9
+msgid "To make this easy for you, the documents are grouped into two parts."
+msgstr ""
+
+#: data/templates/docs-index.html:12
+msgid "Documents for testers"
+msgstr ""
+
+#: data/templates/docs-index.html:17
+msgid "Documents for developers"
+msgstr ""
+
+#: data/templates/docs-index.html:19 data/templates/docs-base.html:11
+msgid "Builds"
+msgstr ""
+
+#: data/templates/docs-index.html:24
+msgid "Technical documentation is available on the wiki:"
+msgstr ""
+
+#: data/templates/docs-index.html:25
+msgid "Technical documentation"
+msgstr ""
+
+#: data/templates/builder-edit.html:3 data/templates/builder-edit.html:6
+#, python-format
+msgid "Edit builder %s"
+msgstr ""
+
+#: data/templates/builder-edit.html:17
+msgid "The hostname cannot be changed."
+msgstr ""
+
+#: data/templates/builder-edit.html:21
+msgid "Enabled"
+msgstr ""
+
+#: data/templates/builder-edit.html:26
+msgid "The builder must be enabled in order to process build jobs."
+msgstr ""
+
+#: data/templates/builder-edit.html:32
+msgid "Build job settings"
+msgstr ""
+
+#: data/templates/builder-edit.html:34
+msgid "These settings do only take effect if the builder is enabled."
+msgstr ""
+
+#: data/templates/builder-edit.html:38
+msgid "Authorized to build source packages"
+msgstr ""
+
+#: data/templates/builder-edit.html:43
+msgid "Only a few build servers are allowed to build source packages."
+msgstr ""
+
+#: data/templates/builder-edit.html:47
+msgid "Authorized to build binary packages"
+msgstr ""
+
+#: data/templates/builder-edit.html:54
+msgid "Authorized to build test packages"
+msgstr ""
+
+#: data/templates/builder-delete.html:3
+#, python-format
+msgid "Delete builder %s"
+msgstr ""
+
+#: data/templates/builder-delete.html:6 data/templates/builder-detail.html:3
+#: data/templates/builder-detail.html:6 data/templates/builder-pass.html:4
+msgid "Builder"
+msgstr ""
+
+#: data/templates/builder-delete.html:9
+#, python-format
+msgid "You are going to delete the build host <strong>%s</strong>."
+msgstr ""
+
+#: data/templates/user-comments.html:4
+msgid "Latest user comments"
+msgstr ""
+
+#: data/templates/user-comments.html:12
+msgid "Show all users"
+msgstr ""
+
+#: data/templates/user-profile.html:5 data/templates/user-profile.html:26
+#: data/templates/user-profile-edit.html:74
+msgid "User"
+msgstr ""
+
+#: data/templates/user-profile.html:9 data/templates/login.html:22
+#: data/templates/user-profile-edit.html:10
+msgid "Username"
+msgstr ""
+
+#: data/templates/user-profile.html:13 data/templates/register.html:19
+#: data/templates/user-profile-edit.html:15
+msgid "Email"
+msgstr ""
+
+#: data/templates/user-profile.html:22
+#: data/templates/user-profile-edit.html:81
+msgid "Admin"
+msgstr ""
+
+#: data/templates/user-profile.html:24
+#: data/templates/user-profile-edit.html:77
+msgid "Tester"
+msgstr ""
+
+#: data/templates/user-profile.html:32
+msgid "Registered"
+msgstr ""
+
+#: data/templates/user-profile.html:42
+#, python-format
+msgid "Comments written by %s"
+msgstr ""
+
+#: data/templates/user-profile.html:51
+msgid "Account settings"
+msgstr ""
+
+#: data/templates/user-profile.html:54
+msgid "Delete account"
+msgstr ""
+
+#: data/templates/repository-detail.html:5
+msgid "Repository"
+msgstr ""
+
+#: data/templates/repository-detail.html:6
+msgid "from"
+msgstr ""
+
+#: data/templates/repository-detail.html:12
+#, python-format
+msgid "This repository contains %s packages and is available for %s."
+msgstr ""
+
+#: data/templates/repository-detail.html:15
+msgid "Waiting packages"
+msgstr ""
+
+#: data/templates/repository-detail.html:17
+msgid "These packages are waiting to be published."
+msgstr ""
+
+#: data/templates/repository-detail.html:21
+msgid "Pushed packages"
+msgstr ""
+
+#: data/templates/register-success.html:4
+msgid "Registration successful"
+msgstr ""
+
+#: data/templates/register-success.html:6
+#, python-format
+msgid "Your new account has been created, %s."
+msgstr ""
+
+#: data/templates/register-success.html:7
+msgid ""
+"To complete the activation, follow the instructions that were sent to you in "
+"an activation email."
+msgstr ""
+
+#: data/templates/login.html:4 data/templates/base.html:21
+msgid "Login"
+msgstr ""
+
+#: data/templates/login.html:8
+msgid "Username and/or password was wrong. Login failed."
+msgstr ""
+
+#: data/templates/login.html:13
+msgid "Please type your username and your password to the form to log in."
+msgstr ""
+
+#: data/templates/login.html:14
+msgid "If you have no account, yet you can create a new one."
+msgstr ""
+
+#: data/templates/login.html:15
+msgid "Register a new account."
+msgstr ""
+
+#: data/templates/login.html:26 data/templates/register.html:41
+#: data/templates/user-profile-edit.html:33
+msgid "Password"
+msgstr ""
+
+#: data/templates/register.html:4
+msgid "Register new account"
+msgstr ""
+
+#: data/templates/register.html:15
+msgid "Must be a unique name you login with."
+msgstr ""
+
+#: data/templates/register.html:24
+msgid "Type your email address."
+msgstr ""
+
+#: data/templates/register.html:28 data/templates/user-profile-edit.html:24
+msgid "Real name (optional)"
+msgstr ""
+
+#: data/templates/register.html:33
+msgid "Type you firstname and your lastname here."
+msgstr ""
+
+#: data/templates/register.html:38
+msgid "Account security"
+msgstr ""
+
+#: data/templates/register.html:46 data/templates/user-profile-edit.html:38
+msgid ""
+"The password is used to secure the login and must be at least 8 characters."
+msgstr ""
+
+#: data/templates/register.html:50 data/templates/user-profile-edit.html:42
+msgid "Confirm"
+msgstr ""
+
+#: data/templates/login-successful.html:4
+msgid "Login successful"
+msgstr ""
+
+#: data/templates/login-successful.html:6
+#, python-format
+msgid "Welcome, %s."
+msgstr ""
+
+#: data/templates/login-successful.html:10
+msgid "Your login to the Pakfire Build Server was successful."
+msgstr ""
+
+#: data/templates/user-profile-edit-fail.html:4
+msgid "Edit failed"
+msgstr ""
+
+#: data/templates/user-profile-edit-fail.html:7
+msgid "The user profile cannot be saved."
+msgstr ""
+
+#: data/templates/modules/files-table.html:8
+msgid "Info"
+msgstr ""
+
+#: data/templates/modules/files-table.html:9
+msgid "Download"
+msgstr ""
+
+#: data/templates/modules/comments-table.html:8
+#, python-format
+msgid "on %s"
+msgstr ""
+
+#: data/templates/modules/comments-table.html:11
+#, python-format
+msgid "by %s"
+msgstr ""
+
+#: data/templates/modules/comments-table.html:18
+msgid "No comments so far."
+msgstr ""
+
+#: data/templates/modules/log-table.html:23
+msgid "No log entries, yet."
+msgstr ""
+
+#: data/templates/source-list.html:3
+msgid "Sources repositories"
+msgstr ""
+
+#: data/templates/source-list.html:6
+msgid "Source repositories"
+msgstr ""
+
+#: data/templates/source-list.html:17
+msgid "Add source repository"
+msgstr ""
+
+#: data/templates/source-list.html:18
+msgid "Blah 123"
+msgstr ""
+
+#: data/templates/distro-list.html:3 data/templates/distro-list.html:6
+#: data/templates/base.html:37
+msgid "Distributions"
+msgstr ""
+
+#: data/templates/distro-list.html:19
+msgid "Add distribution"
+msgstr ""
+
+#: data/templates/user-profile-edit.html:4
+#, python-format
+msgid "Edit user %s"
+msgstr ""
+
+#: data/templates/user-profile-edit.html:12
+msgid "Cannot be changed."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:20
+msgid ""
+"If the email address is changed, your account will be disabled until you "
+"reconfirm the new email address."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:29
+msgid "Your real name is used to identify you by others."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:47
+msgid "Leave the password fields empty to keep the current password."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:51
+msgid "Preferred language"
+msgstr ""
+
+#: data/templates/user-profile-edit.html:54
+msgid "Auto-detect"
+msgstr ""
+
+#: data/templates/user-profile-edit.html:62
+msgid "Auto-detect will use the language transmitted by your browser."
+msgstr ""
+
+#: data/templates/user-profile-edit.html:68
+msgid "Admin actions"
+msgstr ""
+
+#: data/templates/user-profile-edit.html:86
+msgid "Define the permissions of the user."
+msgstr ""
+
+#: data/templates/base.html:4
+msgid "No title given"
+msgstr ""
+
+#: data/templates/base.html:19
+msgid "Logout"
+msgstr ""
+
+#: data/templates/base.html:26
+#, python-format
+msgid "A service by the %s."
+msgstr ""
+
+#: data/templates/base.html:31
+msgid "Index"
+msgstr ""
+
+#: data/templates/base.html:34
+msgid "Packages"
+msgstr ""
+
+#: data/templates/base.html:84
+msgid "About Pakfire"
+msgstr ""
+
+#: data/templates/base.html:86
+msgid ""
+"Pakfire is the buildsystem that is used to build the IPFire Linux firewall "
+"distribution."
+msgstr ""
+
+#: data/templates/base.html:87
+msgid "It also installs and updates packages on the IPFire systems."
+msgstr ""
+
+#: data/templates/base.html:91
+msgid "Documentation"
+msgstr ""
+
+#: data/templates/base.html:94
+msgid "Documentation index"
+msgstr ""
+
+#: data/templates/base.html:100
+msgid "All rights reserved."
+msgstr ""
+
+#: data/templates/docs-build.html:8
+msgid ""
+"Every build that is done by the Pakfire Build Service has to go through "
+"several states:"
+msgstr ""
+
+#: data/templates/docs-build.html:11
+msgid ""
+"After checking out the source from the source repository a source package is "
+"created and submitted to the build server."
+msgstr ""
+
+#: data/templates/docs-build.html:12
+msgid ""
+"Starting from inserting a source file to the build service, there are binary "
+"build jobs created for every supported architecture."
+msgstr ""
+
+#: data/templates/docs-build.html:15
+msgid ""
+"These get assigned to a build host which has to compile or assemble the "
+"package and return it back to the build server."
+msgstr ""
+
+#: data/templates/docs-build.html:16
+msgid "In the table below, there are all states that a build job goes through:"
+msgstr ""
+
+#: data/templates/docs-build.html:19
+msgid "Build is running"
+msgstr ""
+
+#: data/templates/docs-build.html:20
+msgid "Build has failed"
+msgstr ""
+
+#: data/templates/docs-build.html:21
+msgid "Build is waiting to be processed"
+msgstr ""
+
+#: data/templates/docs-build.html:22
+msgid "There was a dependency error when the package was built"
+msgstr ""
+
+#: data/templates/docs-build.html:23
+msgid "Build is waiting for source to go to pending state"
+msgstr ""
+
+#: data/templates/docs-build.html:24
+msgid "Files of this build are transferred to the build server"
+msgstr ""
+
+#: data/templates/docs-build.html:25
+msgid "Files are being uploaded to the service"
+msgstr ""
+
+#: data/templates/docs-build.html:26
+msgid "Build has an unknown state"
+msgstr ""
+
+#: data/templates/builder-detail.html:10
+msgid "Status"
+msgstr ""
+
+#: data/templates/builder-detail.html:16
+msgid "Load average"
+msgstr ""
+
+#: data/templates/builder-detail.html:24
+msgid "Supported architectures"
+msgstr ""
+
+#: data/templates/builder-detail.html:26 data/templates/builder-detail.html:59
+msgid "Unknown"
+msgstr ""
+
+#: data/templates/builder-detail.html:32
+msgid "Configuration"
+msgstr ""
+
+#: data/templates/builder-detail.html:35
+msgid "Builds source packages"
+msgstr ""
+
+#: data/templates/builder-detail.html:37 data/templates/builder-detail.html:43
+#: data/templates/builder-detail.html:49
+msgid "Yes"
+msgstr ""
+
+#: data/templates/builder-detail.html:37 data/templates/builder-detail.html:43
+#: data/templates/builder-detail.html:49
+msgid "No"
+msgstr ""
+
+#: data/templates/builder-detail.html:41
+msgid "Builds binary packages"
+msgstr ""
+
+#: data/templates/builder-detail.html:47
+msgid "Runs tests"
+msgstr ""
+
+#: data/templates/builder-detail.html:55
+msgid "Host information"
+msgstr ""
+
+#: data/templates/builder-detail.html:58
+msgid "CPU model"
+msgstr ""
+
+#: data/templates/builder-detail.html:62
+msgid "Memory"
+msgstr ""
+
+#: data/templates/builder-detail.html:69
+msgid "Currently running builds on this host"
+msgstr ""
+
+#: data/templates/builder-detail.html:78
+msgid "Show all build jobs"
+msgstr ""
+
+#: data/templates/builder-detail.html:82
+msgid "Edit builder"
+msgstr ""
+
+#: data/templates/builder-detail.html:86
+msgid "Renew passphrase"
+msgstr ""
+
+#: data/templates/builder-detail.html:90
+msgid "Delete builder"
+msgstr ""
+
+#: data/templates/builder-pass.html:8
+#, python-format
+msgid "The new host <strong>%s</strong> has been successfully created."
+msgstr ""
+
+#: data/templates/builder-pass.html:10
+#, python-format
+msgid "The passphrase for <strong>%s</strong> has been regenerated."
+msgstr ""
+
+#: data/templates/builder-pass.html:13
+msgid ""
+"For authorization to the Pakfire Master Server there is a passphrase "
+"required which must be configured to the host."
+msgstr ""
+
+#: data/templates/builder-pass.html:14
+msgid "This passphrase is:"
+msgstr ""
+
+#: data/templates/builder-pass.html:20
+msgid "Next"
+msgstr ""
+
+#: data/templates/build-schedule.html:3 data/templates/build-schedule.html:6
+#, python-format
+msgid "Schedule test build for %s"
+msgstr ""
+
+#: data/templates/build-schedule.html:8
+msgid ""
+"A test build is used to check if a package builds with the current package "
+"set."
+msgstr ""
+
+#: data/templates/build-schedule.html:9
+msgid ""
+"In this way, developers are able to find quality issues fast and without "
+"actively searching for them."
+msgstr ""
+
+#: data/templates/build-schedule.html:12
+msgid ""
+"As this build platform only has a limited amount of performance, test builds "
+"only have a very less priority."
+msgstr ""
+
+#: data/templates/build-schedule.html:13
+msgid "However, you can manually request to run a test."
+msgstr ""
+
+#: data/templates/build-schedule.html:16
+msgid ""
+"The build job will be started when a build slot is available but not before "
+"the given time."
+msgstr ""
+
+#: data/templates/build-schedule.html:23
+msgid "Start time"
+msgstr ""
+
+#: data/templates/build-schedule.html:26
+msgid "As soon as possible"
+msgstr ""
+
+#: data/templates/build-schedule.html:27
+msgid "After 5 minutes"
+msgstr ""
+
+#: data/templates/build-schedule.html:28
+msgid "After 15 minutes"
+msgstr ""
+
+#: data/templates/build-schedule.html:29
+msgid "After one hour"
+msgstr ""
+
+#: data/templates/build-schedule.html:30
+msgid "After one day"
+msgstr ""
+
+#: data/templates/build-schedule.html:34
+msgid "Set the time after which the build job starts."
+msgstr ""
+
+#: data/templates/docs-base.html:5
+msgid "All Documents"
+msgstr ""
+
+#: data/templates/docs-base.html:9
+msgid "Topics"
+msgstr ""
+
+#: web/handlers_auth.py:53
+msgid "No username provided."
+msgstr ""
+
+#: web/handlers_auth.py:55
+msgid "The given username is already taken."
+msgstr ""
+
+#: web/handlers_auth.py:58 web/handlers_users.py:85
+msgid "No email address provided."
+msgstr ""
+
+#: web/handlers_auth.py:60 web/handlers_users.py:87
+msgid "Email address is invalid."
+msgstr ""
+
+#: web/handlers_auth.py:62
+msgid "The given email address is already used for another account."
+msgstr ""
+
+#: web/handlers_auth.py:66
+msgid "No password provided."
+msgstr ""
+
+#: web/handlers_auth.py:68 web/handlers_users.py:91
+msgid "Password has less than 8 characters."
+msgstr ""
+
+#: web/handlers_auth.py:70 web/handlers_users.py:93
+msgid "Passwords do not match."
+msgstr ""
+
+#: web/handlers_users.py:97
+msgid "The choosen locale is invalid."
+msgstr ""
+
+#: backend/users.py:207
+msgid "Account Activation"
+msgstr ""
+
+#: backend/users.py:209
+msgid ""
+"You, or somebody using you email address, has registered an account on the "
+"Pakfire Build Service."
+msgstr ""
+
+#: backend/users.py:211
+msgid "To activate your account, please click on the link below."
+msgstr ""
+
+#: backend/constants.py:22
+msgid "Build job created"
+msgstr ""
+
+#: backend/constants.py:23
+msgid "Build job is now pending"
+msgstr ""
+
+#: backend/constants.py:24
+msgid "Build job is dispatching"
+msgstr ""
+
+#: backend/constants.py:25
+msgid "Build job is running"
+msgstr ""
+
+#: backend/constants.py:26
+msgid "Build job has failed"
+msgstr ""
+
+#: backend/constants.py:27
+msgid "Build job has permanently failed"
+msgstr ""
+
+#: backend/constants.py:28
+msgid "Build job has dependency errors"
+msgstr ""
+
+#: backend/constants.py:29
+msgid "Build job is waiting for the source package"
+msgstr ""
+
+#: backend/constants.py:30
+msgid "Build job is finished"
+msgstr ""
+
+#: backend/constants.py:31
+msgid "Build job has an unknown state"
+msgstr ""
+
+#: backend/constants.py:32
+msgid "Build job is uploading"
+msgstr ""
+
+#: backend/constants.py:37
+#, python-format
+msgid "Build job failed: %(build_name)s"
+msgstr ""
+
+#: backend/constants.py:38
+#, python-format
+msgid ""
+"The build job \"%(build_name)s\" has failed.\n"
+"\n"
+"This could have a couple of reasons and needs to be investigated by you.\n"
+"\n"
+"Here is more information about the incident:\n"
+"\n"
+" Build name: %(build_name)s\n"
+" Build host: %(build_host)s\n"
+"\n"
+"Click on this link to get all details about the build:\n"
+" http://pakfire.ipfire.org/build/%(build_uuid)s\n"
+"\n"
+"Sincerely,\n"
+" The Pakfire Build Service"
+msgstr ""
+
+#: backend/constants.py:55
+#, python-format
+msgid "Build job finished: %(build_name)s"
+msgstr ""
+
+#: backend/constants.py:56
+#, python-format
+msgid ""
+"The build job \"%(build_name)s\" has finished.\n"
+"\n"
+"If you are the maintainer, it is up to you to push it to one or more "
+"repositories.\n"
+"\n"
+"Click on this link to get all details about the build:\n"
+" http://pakfire.ipfire.org/build/%(build_uuid)s\n"
+"\n"
+"Sincerely,\n"
+" The Pakfire Build Service"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "January"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "February"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "March"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:225
+msgid "April"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "May"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "June"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "July"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:226
+msgid "August"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "September"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "October"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "November"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:227
+msgid "December"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Monday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Tuesday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Wednesday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:229
+msgid "Thursday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Friday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Saturday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:230
+msgid "Sunday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:274
+#, python-format
+msgid "1 second ago"
+msgid_plural "%(seconds)d seconds ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:279
+#, python-format
+msgid "1 minute ago"
+msgid_plural "%(minutes)d minutes ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:283
+#, python-format
+msgid "1 hour ago"
+msgid_plural "%(hours)d hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:287
+#, python-format
+msgid "%(time)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:290
+msgid "yesterday"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:291
+#, python-format
+msgid "yesterday at %(time)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:293
+#, python-format
+msgid "%(weekday)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:294
+#, python-format
+msgid "%(weekday)s at %(time)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:296
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:338
+#, python-format
+msgid "%(month_name)s %(day)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:297
+#, python-format
+msgid "%(month_name)s %(day)s at %(time)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:300
+#, python-format
+msgid "%(month_name)s %(day)s, %(year)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:301
+#, python-format
+msgid "%(month_name)s %(day)s, %(year)s at %(time)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:332
+#, python-format
+msgid "%(weekday)s, %(month_name)s %(day)s"
+msgstr ""
+
+#: /usr/lib/python2.7/site-packages/tornado/locale.py:353
+#, python-format
+msgid "%(commas)s and %(last)s"
+msgstr ""
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import os.path
+import tornado.httpserver
+import tornado.locale
+import tornado.options
+import tornado.web
+
+import backend
+
+from handlers import *
+
+BASEDIR = os.path.join(os.path.dirname(__file__), "..", "data")
+
+# Enable logging
+tornado.options.parse_command_line()
+
+class MasterApplication(tornado.web.Application):
+ def __init__(self):
+ settings = dict(
+ debug = True,
+ gzip = False,
+ )
+
+ # Load translations.
+ tornado.locale.load_gettext_translations(
+ os.path.join(BASEDIR, "translations"), "pakfire")
+
+ tornado.web.Application.__init__(self, **settings)
+
+ self.add_handlers(r".*", [
+ # API
+ (r"/api/builder/([A-Za-z0-9\-\.]+)/(\w+)", RPCBuilderHandler),
+ ])
+
+ self.pakfire = backend.Pakfire()
+
+ logging.info("Successfully initialied application")
+
+ def __del__(self):
+ logging.info("Shutting down application")
+
+ @property
+ def ioloop(self):
+ return tornado.ioloop.IOLoop.instance()
+
+ def shutdown(self, *args):
+ logging.debug("Caught shutdown signal")
+ self.ioloop.stop()
+
+ def run(self, port=81):
+ logging.debug("Going to background")
+
+ # All requests should be done after 30 seconds or they will be killed.
+ self.ioloop.set_blocking_log_threshold(30)
+
+ http_server = tornado.httpserver.HTTPServer(self, xheaders=True)
+
+ # If we are not running in debug mode, we can actually run multiple
+ # frontends to get best performance out of our service.
+ if not self.settings["debug"]:
+ http_server.bind(port)
+ http_server.start(num_processes=4)
+ else:
+ http_server.listen(port)
+
+ self.ioloop.start()
+
+ def reload(self):
+ logging.debug("Caught reload signal")
--- /dev/null
+#!/usr/bin/python
+
+import hashlib
+import logging
+import os
+import tornado.web
+import uuid
+import xmlrpclib
+
+from backend.build import BinaryBuild, SourceBuild
+from backend.packages import Package
+
+class BaseHandler(tornado.web.RequestHandler):
+ @property
+ def pakfire(self):
+ return self.application.pakfire
+
+ # XXX should not be needed
+ #@property
+ #def db(self):
+ # return self.application.pakfire.db
+
+
+# From: http://blog.joshmarshall.org/2009/10/its-a-twister-now-with-more-xml/
+#
+# This is just a very simple implementation from the website above, because
+# I badly want to run this software out of the box on any distribution.
+#
+
+def private(func):
+ # Decorator to make a method, well, private.
+ class PrivateMethod(object):
+ def __init__(self):
+ self.private = True
+
+ __call__ = func
+
+ return PrivateMethod()
+
+
+class XMLRPCHandler(BaseHandler):
+ """
+ Subclass this to add methods -- you can treat them
+ just like normal methods, this handles the XML formatting.
+ """
+ def post(self):
+ """
+ Later we'll make this compatible with "dot" calls like:
+ server.namespace.method()
+ If you implement this, make sure you do something proper
+ with the Exceptions, i.e. follow the XMLRPC spec.
+ """
+ try:
+ params, method_name = xmlrpclib.loads(self.request.body)
+ except:
+ # Bad request formatting, bad.
+ raise tornado.web.HTTPError(400)
+
+ if method_name in dir(tornado.web.RequestHandler):
+ # Pre-existing, not an implemented attribute
+ raise AttributeError('%s is not implemented.' % method_name)
+
+ try:
+ method = getattr(self, method_name)
+ except:
+ # Attribute doesn't exist
+ raise AttributeError('%s is not a valid method.' % method_name)
+
+ if not callable(method):
+ # Not callable, so not a method
+ raise Exception('Attribute %s is not a method.' % method_name)
+
+ if method_name.startswith('_') or \
+ ('private' in dir(method) and method.private is True):
+ # No, no. That's private.
+ raise Exception('Private function %s called.' % method_name)
+
+ response = method(*params)
+ response_xml = xmlrpclib.dumps((response,), methodresponse=True,
+ allow_none=True)
+
+ self.set_header("Content-Type", "text/xml")
+ self.write(response_xml)
+
+
+class AuthXMLRPCHandler(XMLRPCHandler):
+ """
+ This handler forces the host to authenticate against the server.
+
+ All methods of this class can be sure that they receive 100% okay data.
+ """
+ def post(self, hostname, passphrase):
+ # Get the builder from the database.
+ self.builder = self.pakfire.builders.get_by_name(hostname)
+ if not self.builder:
+ raise tornado.web.HTTPError(403)
+
+ # Check if the passphrase matches and return 403 Forbidden if
+ # the authentication data is invalid.
+ if not self.builder.validate_passphrase(passphrase):
+ raise tornado.web.HTTPError(403)
+
+ # Parse the actual request.
+ XMLRPCHandler.post(self)
+
+
+class RPCBaseHandler(AuthXMLRPCHandler):
+ @staticmethod
+ def chunkPath(id):
+ return os.path.join("/var/tmp/pakfire-upload-%s" % id)
+
+ def get_upload_cookie(self, filename, size, hash):
+ """
+ Create a new upload object in the database and return a unique ID
+ to the uploader.
+ """
+ upload = self.pakfire.uploads.new(self.builder, filename, size, hash)
+
+ return upload.uuid
+
+ def upload_chunk(self, upload_id, data):
+ upload = self.pakfire.uploads.get_by_uuid(upload_id)
+ if not upload:
+ raise tornado.web.HTTPError(404, "Invalid upload id.")
+
+ if not upload.builder == self.builder:
+ raise tornado.web.HTTPError(403, "Uploading an other hosts file.")
+
+ upload.append(data.data)
+
+ def finish_upload(self, upload_id, build_id):
+ upload = self.pakfire.uploads.get_by_uuid(upload_id)
+ if not upload:
+ raise tornado.web.HTTPError(404, "Invalid upload id.")
+
+ # Get the corresponding build (needed for arch, etc.)
+ build = self.pakfire.builds.get_by_uuid(build_id)
+ if not build:
+ raise tornado.web.HTTPError(400, "Invalid build id.")
+
+ # Validate the uploaded data to its hash.
+ ret = upload.validate()
+
+ # If the hash does not match, we delete the upload.
+ if not ret:
+ upload.remove()
+ return ret
+
+ # Save the file to its designated place.
+ upload.commit(build)
+
+ # Send the validation result to the uploader.
+ return ret
+
+ def chunk_upload(self, id, hash, data):
+ if not id:
+ id = "%s" % uuid.uuid4()
+
+ # Get the filename of the upload.
+ filename = self.chunkPath(id)
+
+ # Extract data.
+ data = data.data
+
+ # Check the data integrity.
+ if not hash == hashlib.sha1(data).hexdigest():
+ raise Exception, "Chunk was corrupted"
+
+ # Write the data to file.
+ f = open(filename, "a")
+ f.write(data)
+ f.close()
+
+ # Return the ID to add more chunks to the data.
+ return id
+
+ def package_add_file(self, pkg_id, file_id, info):
+ pkg = self.pakfire.packages.get_by_id(pkg_id)
+
+ filename = self.chunkPath(file_id)
+ if not os.path.exists(filename):
+ raise Exception, "Chunk file not found"
+
+ return pkg.add_file(filename, info)
+
+ def package_add(self, info):
+ pkg = self.pakfire.packages.get_by_tuple(info.get("name"), info.get("epoch"),
+ info.get("version"), info.get("release"))
+
+ if pkg:
+ logging.debug("Package does already exist: %s" % pkg)
+ return pkg.id
+
+ pkg = Package.new(self.pakfire, info)
+
+ return pkg.id
+
+ def build_add_log(self, build_id, file_id):
+ build = self.pakfire.builds.get_by_uuid(build_id)
+
+ filename = self.chunkPath(file_id)
+ if not os.path.exists(filename):
+ raise Exception, "Chunk file not found"
+
+ build.add_log(filename)
+
+ return True
+
+
+class RPCBuilderHandler(RPCBaseHandler):
+ def update_host_info(self, loadavg, cpu_model, memory, arches):
+ """
+ Receive detailed host information and store it to the database.
+ """
+ self.builder.update_info(loadavg, cpu_model, memory, arches)
+
+ def update_build_state(self, build_id, state, message):
+ build = self.pakfire.builds.get_by_uuid(build_id)
+ if not build:
+ return
+
+ # Save information to database.
+ build.state, build.message = state, message
+
+ return True
+
+ def build_job(self, type=None):
+ if self.builder.disabled:
+ logging.warning("Disabled builder wants to get a job: %s" % \
+ self.builder.hostname)
+ return
+
+ # XXX need to handle type here
+
+ # Check if host has already enough running build jobs.
+ if len(self.builder.active_builds) >= self.builder.max_jobs:
+ return
+
+ # Determine what kind of builds the host should get.
+ build = None
+ if self.builder.build_src:
+ # Source builds are preferred over binary builds if the host does
+ # support this.
+
+ # If there is not already a source job running on this host, we
+ # can grab a new one.
+ if not "source" in [b.type for b in self.builder.active_builds]:
+ build = self.pakfire.builds.get_next(type="source", limit=1)
+
+ if not build and self.builder.build_bin:
+ # If the host does not support source builds or there are no source
+ # builds to do, we try to grab a binary build in a supported arch.
+ build = self.pakfire.builds.get_next(type="binary", limit=1,
+ arches=self.builder.arches)
+
+ # If there is no build that we can do, we can skip the rest.
+ if not build:
+ return
+
+ try:
+ # Set build to be dispatching that it won't be taken by another
+ # host.
+ build.state = "dispatching"
+
+ # Assign the build job to the host that requested this.
+ build.host = self.builder.hostname
+
+ if build.type == "source":
+ # The source build job build job is immediately changed to running
+ # state.
+ return {
+ "type" : "source",
+ "id" : build.uuid,
+ "revision" : build.revision,
+ "source" : build.source.info,
+ }
+
+ elif build.type == "binary":
+ # Get source package.
+ source = build.pkg.sourcefile
+ assert source
+
+ return {
+ "type" : "binary",
+ "arch" : build.arch,
+ "id" : build.uuid,
+ "pkg_id" : build.pkg_id,
+ "source_id" : source.source.id,
+ "name" : source.name,
+ "download" : source.download,
+ "hash1" : source.hash1,
+ }
+ except:
+ # If there has been any error, we reset the build.
+ build.state = "pending"
+
+ def get_repos(self, limit=None):
+ repos = self.pakfire.repos.get_needs_update(limit=limit)
+
+ # XXX disabled for testing
+ #for repo in repos:
+ # repo.needs_update = False
+
+ return [r.info for r in repos]
+
+ def get_repo_packages(self, repo_id):
+ repo = self.pakfire.repos.get_by_id(repo_id)
+
+ if not repo:
+ return
+
+ pkgs = []
+ for pkg in repo.get_packages():
+ pkgs += [f.abspath for f in pkg.packagefiles]
+
+ return pkgs
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import time
+import tornado.ioloop
+import tornado.options
+
+import backend
+from backend.managers import *
+
+tornado.options.parse_command_line()
+
+class Daemon(object):
+ def __init__(self):
+ self._managers = []
+
+ self.ioloop.set_blocking_log_threshold(300)
+
+ self.pakfire = backend.Pakfire()
+
+ @property
+ def ioloop(self):
+ return tornado.ioloop.IOLoop.instance()
+
+ def add(self, manager_cls):
+ logging.info("Registering new manager: %s" % manager_cls.__name__)
+ manager = manager_cls(self.pakfire)
+ self._managers.append(manager)
+
+ def run(self):
+ """
+ Main loop.
+ """
+ for manager in self._managers:
+ manager.pc.start()
+
+ self.ioloop.start()
+
+ def shutdown(self):
+ self.ioloop.stop()
+
+
+
+
+if __name__ == "__main__":
+ d = Daemon()
+ for manager in managers:
+ d.add(manager)
+
+ d.run()
--- /dev/null
+#!/usr/bin/python
+
+from master import MasterApplication
+
+app = MasterApplication()
+
+app.run()
--- /dev/null
+#!/usr/bin/python
+
+import daemon
+import os
+import signal
+import sys
+
+import tornado.options
+
+from web import Application
+
+
+#tornado.options.parse_command_line()
+
+if __name__ == "__main__":
+ app = Application()
+
+ context = daemon.DaemonContext(
+ working_directory=os.getcwd(),
+# stdout=sys.stdout, stderr=sys.stderr, # XXX causes errors...
+ )
+
+ context.signal_map = {
+ signal.SIGHUP : app.reload,
+ signal.SIGTERM : app.shutdown,
+ }
+
+# with context:
+# app.run()
+
+ app.run()
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import os.path
+import tornado.httpserver
+import tornado.locale
+import tornado.options
+import tornado.web
+
+from handlers import *
+from ui_modules import *
+
+BASEDIR = os.path.join(os.path.dirname(__file__), "..", "data")
+
+# Enable logging
+tornado.options.parse_command_line()
+
+class Application(tornado.web.Application):
+ def __init__(self):
+ settings = dict(
+ cookie_secret = "12345",
+ debug = True,
+ gzip = True,
+ login_url = "/login",
+ template_path = os.path.join(BASEDIR, "templates"),
+ ui_modules = {
+ "BuildLog" : BuildLogModule,
+ "BuildOffset" : BuildOffsetModule,
+ "BuildTable" : BuildTableModule,
+ "CommentsTable" : CommentsTableModule,
+ "FilesTable" : FilesTableModule,
+ "LogTable" : LogTableModule,
+ "PackageTable" : PackageTableModule,
+ "PackageTable2" : PackageTable2Module,
+ "PackageFilesTable" : PackageFilesTableModule,
+ "RepositoryTable" : RepositoryTableModule,
+ "RepoActionsTable": RepoActionsTableModule,
+ "SourceTable" : SourceTableModule,
+ "UsersTable" : UsersTableModule,
+ },
+ xsrf_cookies = True,
+ )
+
+ # Load translations.
+ tornado.locale.load_gettext_translations(
+ os.path.join(BASEDIR, "translations"), "pakfire")
+
+ tornado.web.Application.__init__(self, **settings)
+
+ self.settings["static_path"] = static_path = os.path.join(BASEDIR, "static")
+ static_handlers = [
+ (r"/static/(.*)", tornado.web.StaticFileHandler, dict(path = static_path)),
+ (r"/(favicon\.ico)", tornado.web.StaticFileHandler, dict(path = static_path)),
+ (r"/(robots\.txt)", tornado.web.StaticFileHandler, dict(path = static_path)),
+ ]
+
+ self.add_handlers(r".*", [
+ # Entry site that lead the user to index
+ (r"/", IndexHandler),
+
+ # Handle all the users logins/logouts/registers and stuff.
+ (r"/login", LoginHandler),
+ (r"/logout", LogoutHandler),
+ (r"/register", RegisterHandler),
+ (r"/users", UsersHandler),
+ (r"/users/comments", UsersCommentsHandler),
+ (r"/user/delete/(\w+)", UserDeleteHandler),
+ (r"/user/edit/(\w+)", UserEditHandler),
+ (r"/user/(\w+)", UserHandler),
+ (r"/user/(\w+)/activate/(\w+)", ActivationHandler),
+ (r"/profile", UserHandler),
+
+ # Packages
+ (r"/packages", PackageListHandler),
+ (r"/package/([\w\-\+]+)/([\d]+)/([\w\.\-]+)/([\w\.]+)", PackageDetailHandler),
+ (r"/package/([\w\-\+]+)", PackageNameHandler),
+
+ # Files
+ (r"/file/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})", FileDetailHandler),
+
+ # Builds
+ (r"/builds", BuildListHandler),
+ (r"/builds/filter", BuildFilterHandler),
+ (r"/build/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})", BuildDetailHandler),
+ (r"/build/priority/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})", BuildPriorityHandler),
+ (r"/build/schedule/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})", BuildScheduleHandler),
+
+ # Builders
+ (r"/builders", BuilderListHandler),
+ (r"/builder/new", BuilderNewHandler),
+ (r"/builder/delete/([A-Za-z0-9\-\.]+)", BuilderDeleteHandler),
+ (r"/builder/edit/([A-Za-z0-9\-\.]+)", BuilderEditHandler),
+ (r"/builder/renew/([A-Za-z0-9\-\.]+)", BuilderRenewPassphraseHandler),
+ (r"/builder/([A-Za-z0-9\-\.]+)", BuilderDetailHandler),
+
+ # Sources
+ (r"/sources", SourceListHandler),
+ (r"/source/([0-9]+)", SourceDetailHandler),
+
+ # Distributions
+ (r"/distributions", DistributionListHandler),
+ (r"/distribution/delete/([A-Za-z0-9\-\.]+)", DistributionDetailHandler),
+ (r"/distribution/edit/([A-Za-z0-9\-\.]+)", DistributionEditHandler),
+ (r"/distribution/([A-Za-z0-9\-\.]+)", DistributionDetailHandler),
+ (r"/distribution/([A-Za-z0-9\-\.]+)/repository/([A-Za-z0-9\-\.]+)", RepositoryDetailHandler),
+
+ # Documents
+ (r"/documents", DocsIndexHandler),
+ (r"/documents/builds", DocsBuildsHandler),
+ (r"/documents/users", DocsUsersHandler),
+
+ # Search
+ (r"/search", SearchHandler),
+
+ # API
+ (r"/api/action/(\w+)", RepoActionHandler),
+
+ # Log
+ (r"/log", LogHandler),
+ ] + static_handlers)
+
+ self.pakfire = backend.Pakfire()
+
+ logging.info("Successfully initialied application")
+
+ def __del__(self):
+ logging.info("Shutting down application")
+
+ @property
+ def ioloop(self):
+ return tornado.ioloop.IOLoop.instance()
+
+ def shutdown(self, *args):
+ logging.debug("Caught shutdown signal")
+ self.ioloop.stop()
+
+ def run(self, port=80):
+ logging.debug("Going to background")
+
+ # All requests should be done after 30 seconds or they will be killed.
+ self.ioloop.set_blocking_log_threshold(30)
+
+ http_server = tornado.httpserver.HTTPServer(self, xheaders=True)
+
+ # If we are not running in debug mode, we can actually run multiple
+ # frontends to get best performance out of our service.
+ if not self.settings["debug"]:
+ http_server.bind(port)
+ http_server.start(num_processes=4)
+ else:
+ http_server.listen(port)
+
+ self.ioloop.start()
+
+ def reload(self):
+ logging.debug("Caught reload signal")
--- /dev/null
+#!/usr/bin/python
+
+import tornado.web
+
+from handlers_auth import *
+from handlers_base import *
+from handlers_builders import *
+from handlers_search import *
+from handlers_users import *
+
+class IndexHandler(BaseHandler):
+ def get(self):
+ active_builds = self.pakfire.builds.get_active()
+ latest_builds = self.pakfire.builds.get_latest(limit=10)
+ next_builds = self.pakfire.builds.get_next(limit=10)
+
+ # Get counters
+ counter_pending = self.pakfire.builds.count(state="pending")
+
+ average_build_time = self.pakfire.builds.average_build_time()
+
+ self.render("index.html", latest_builds=latest_builds,
+ active_builds=active_builds, next_builds=next_builds,
+ counter_pending=counter_pending, average_build_time=average_build_time)
+
+
+class PackageIDDetailHandler(BaseHandler):
+ def get(self, id):
+ package = self.packages.get_by_id(id)
+ if not package:
+ return tornado.web.HTTPError(404, "Package not found: %s" % id)
+
+ self.render("package-detail.html", package=package)
+
+
+class PackageListHandler(BaseHandler):
+ def get(self):
+ packages = {}
+
+ # Sort all packages in an array like "<first char>" --> [packages, ...]
+ # to print them in a table for each letter of the alphabet.
+ for pkg in self.pakfire.packages.get_all_names():
+ c = pkg[0].lower()
+
+ if not packages.has_key(c):
+ packages[c] = []
+
+ packages[c].append(pkg)
+
+ self.render("package-list.html", packages=packages)
+
+
+class PackageNameHandler(BaseHandler):
+ def get(self, package):
+ packages = self.pakfire.packages.get_by_name(package)
+
+ if not packages:
+ raise tornado.web.HTTPError(404, "Package '%s' was not found")
+
+ # Take info from the most recent package.
+ pkg = packages[0]
+
+ self.render("package-detail-list.html", pkg=pkg, packages=packages)
+
+
+class PackageDetailHandler(BaseHandler):
+ def get(self, name, epoch, version, release):
+ pkg = self.pakfire.packages.get_by_tuple(name, epoch, version, release)
+ pkg.update()
+
+ self.render("package-detail.html", pkg=pkg)
+
+ @tornado.web.authenticated
+ def post(self, name, epoch, version, release):
+ pkg = self.pakfire.packages.get_by_tuple(name, epoch, version, release)
+
+ action = self.get_argument("action", None)
+
+ if action == "comment":
+ vote = self.get_argument("vote", None)
+ if not self.current_user.is_tester() and \
+ not self.current_user.is_admin():
+ vote = None
+
+ pkg.comment(self.current_user.id, self.get_argument("text"),
+ vote or "none")
+
+ self.render("package-detail.html", pkg=pkg)
+
+
+class BuildDetailHandler(BaseHandler):
+ def get(self, uuid):
+ build = self.pakfire.builds.get_by_uuid(uuid)
+
+ if not build:
+ raise tornado.web.HTTPError(404, "Build not found")
+
+ self.render("build-detail.html", build=build)
+
+
+class BuildPriorityHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self, uuid):
+ build = self.pakfire.builds.get_by_uuid(uuid)
+
+ if not build:
+ raise tornado.web.HTTPError(404, "Build not found")
+
+ self.render("build-priority.html", build=build)
+
+ @tornado.web.authenticated
+ def post(self, uuid):
+ build = self.pakfire.builds.get_by_uuid(uuid)
+
+ if not build:
+ raise tornado.web.HTTPError(404, "Build not found")
+
+ # Get the priority from the request data and convert it to an integer.
+ # If that cannot be done, we default to zero.
+ prio = self.get_argument("priority")
+ try:
+ prio = int(prio)
+ except TypeError:
+ prio = 0
+
+ # Check if the value is in a valid range.
+ if not prio in (-2, -1, 0, 1, 2):
+ prio = 0
+
+ # Save priority.
+ build.priority = prio
+
+ self.redirect("/build/%s" % build.uuid)
+
+
+class BuildScheduleHandler(BaseHandler):
+ allowed_types = ("test", "rebuild",)
+
+ @tornado.web.authenticated
+ def get(self, uuid):
+ type = self.get_argument("type")
+ assert type in self.allowed_types
+
+ build = self.pakfire.builds.get_by_uuid(uuid)
+ if not build:
+ raise tornado.web.HTTPError(404, "Build not found")
+
+ self.render("build-schedule-%s.html" % type, type=type, build=build)
+
+ @tornado.web.authenticated
+ def post(self, uuid):
+ type = self.get_argument("type")
+ assert type in self.allowed_types
+
+ build = self.pakfire.builds.get_by_uuid(uuid)
+ if not build:
+ raise tornado.web.HTTPError(404, "Build not found")
+
+ # Get the start offset.
+ offset = self.get_argument("offset", 0)
+ try:
+ offset = int(offset)
+ except TypeError:
+ offset = 0
+
+ # Submit the build.
+ if type == "test":
+ build.schedule_test(offset)
+ elif type == "rebuild":
+ build.schedule_rebuild(offset)
+
+ self.redirect("/build/%s" % build.uuid)
+
+
+class BuildListHandler(BaseHandler):
+ def get(self):
+ builder = self.get_argument("builder", None)
+ state = self.get_argument("state", None)
+
+ builds = self.pakfire.builds.get_latest(state=state, builder=builder,
+ limit=25)
+
+ self.render("build-list.html", builds=builds)
+
+
+class BuildFilterHandler(BaseHandler):
+ def get(self):
+ builders = self.pakfire.builders.get_all()
+
+ self.render("build-filter.html", builders=builders)
+
+
+class DocsIndexHandler(BaseHandler):
+ def get(self):
+ self.render("docs-index.html")
+
+
+class DocsBuildsHandler(BaseHandler):
+ def get(self):
+ self.render("docs-build.html")
+
+
+class DocsUsersHandler(BaseHandler):
+ def get(self):
+ self.render("docs-users.html")
+
+
+class SourceListHandler(BaseHandler):
+ def get(self):
+ sources = self.pakfire.sources.get_all()
+
+ self.render("source-list.html", sources=sources)
+
+
+class SourceDetailHandler(BaseHandler):
+ def get(self, id):
+ source = self.pakfire.sources.get_by_id(id)
+
+ self.render("source-detail.html", source=source)
+
+
+class FileDetailHandler(BaseHandler):
+ def get(self, uuid):
+ pkg, file = self.pakfire.packages.get_with_file_by_uuid(uuid)
+
+ if not file:
+ raise tornado.web.HTTPError(404, "File not found")
+
+ self.render("file-detail.html", pkg=pkg, file=file)
+
+
+class DistributionListHandler(BaseHandler):
+ def get(self):
+ distros = self.pakfire.distros.get_all()
+
+ self.render("distro-list.html", distros=distros)
+
+
+class DistributionDetailHandler(BaseHandler):
+ def get(self, name):
+ distro = self.pakfire.distros.get_by_name(name)
+ if not distro:
+ raise tornado.web.HTTPError(404, "Distro not found")
+
+ self.render("distro-detail.html", distro=distro)
+
+
+class DistributionEditHandler(BaseHandler):
+ def prepare(self):
+ self.arches = self.pakfire.builders.get_all_arches()
+ self.sources = self.pakfire.sources.get_all()
+
+ @tornado.web.authenticated
+ def get(self, name):
+ distro = self.pakfire.distros.get_by_name(name)
+ if not distro:
+ raise tornado.web.HTTPError(404, "Distro not found")
+
+ self.render("distro-edit.html", distro=distro, arches=self.arches,
+ sources=self.sources)
+
+ @tornado.web.authenticated
+ def post(self, name):
+ distro = self.pakfire.distros.get_by_name(name)
+ if not distro:
+ raise tornado.web.HTTPError(404, "Distro not found")
+
+ name = self.get_argument("name", distro.name)
+ vendor = self.get_argument("vendor", distro.vendor)
+ slogan = self.get_argument("slogan", distro.slogan)
+ arches = self.get_argument("arches", distro.arches)
+ sources = self.get_argument("sources", distro.sources)
+
+ distro.set("name", name)
+ distro.set("vendor", vendor)
+ distro.set("slogan", slogan)
+ distro.set("arches", arches)
+ distro.set("sources", sources)
+
+ self.redirect("/distribution/%s" % distro.sname)
+
+
+class LogHandler(BaseHandler):
+ def get(self):
+ self.render("log.html", log=self.pakfire.log)
+
+
+class RepositoryDetailHandler(BaseHandler):
+ def get(self, distro, repo):
+ distro = self.pakfire.distros.get_by_name(distro)
+ if not distro:
+ raise tornado.web.HTTPError(404)
+
+ repo = distro.get_repo(repo)
+ if not repo:
+ raise tornado.web.HTTPError(404)
+
+ self.render("repository-detail.html", distro=distro, repo=repo)
+
+
+class RepoActionHandler(BaseHandler):
+ @tornado.web.authenticated
+ def post(self, type):
+ assert type in ("run", "remove")
+
+ action_id = self.get_argument("id")
+
+ action = self.pakfire.repos.get_action_by_id(action_id)
+ if not action:
+ raise tornado.web.HTTPError(400)
+
+ if type == "run":
+ action.run(self.current_user)
+
+ elif type == "remove":
+ action.delete(self.current_user)
--- /dev/null
+#!/usr/bin/python
+
+import tornado.web
+
+from handlers_base import *
+
+class LoginHandler(BaseHandler):
+ def get(self):
+ self.render("login.html", failed=False)
+
+ def post(self):
+ name = self.get_argument("name", None)
+ passphrase = self.get_argument("pass", None)
+
+ user = self.pakfire.users.auth(name, passphrase)
+
+ if user:
+ # Set a cookie and update the current user.
+ self.set_secure_cookie("user", user.name)
+ self._current_user = user
+
+ # If there is "next" given, we redirect the user accordingly.
+ # Otherwise we show a nice welcome message and tell the user, that
+ # the login was successful.
+ next = self.get_argument("next", None)
+
+ if next:
+ self.redirect(next)
+ else:
+ self.render("login-successful.html", user=user)
+
+ else:
+ # If the login failed we return an error message.
+ self.render("login.html", failed=True)
+
+
+class RegisterHandler(BaseHandler):
+ def get(self):
+ self.render("register.html")
+
+ def post(self):
+ _ = self.locale.translate
+ msgs = []
+
+ # Read all information from the request.
+ name = self.get_argument("name", None)
+ email = self.get_argument("email", None)
+ realname = self.get_argument("realname", None)
+ pass1 = self.get_argument("pass1", None)
+ pass2 = self.get_argument("pass2", None)
+
+ if not name:
+ msgs.append(_("No username provided."))
+ elif self.pakfire.users.name_is_used(name):
+ msgs.append(_("The given username is already taken."))
+
+ if not email:
+ msgs.append(_("No email address provided."))
+ elif not "@" in email:
+ msgs.append(_("Email address is invalid."))
+ elif self.pakfire.users.email_is_used(email):
+ msgs.append(_("The given email address is already used for another account."))
+
+ # Check if the passphrase is okay.
+ if not pass1:
+ msgs.append(_("No password provided."))
+ elif not len(pass1) >= 8:
+ msgs.append(_("Password has less than 8 characters."))
+ elif not pass1 == pass2:
+ msgs.append(_("Passwords do not match."))
+
+ if msgs:
+ self.render("register-fail.html", messages=msgs)
+ return
+
+ # All provided data seems okay.
+ # Register the new user to the database.
+ user = self.pakfire.users.register(name, pass1, email, realname,
+ self.locale.code)
+
+ self.render("register-success.html", user=user)
+
+
+class ActivationHandler(BaseHandler):
+ def get(self, _user, code):
+ user = self.pakfire.users.get_by_name(_user)
+ if not user:
+ raise tornado.web.HTTPError(404)
+
+ # Check if the activation code matches and then activate the account.
+ if user.activation_code == code:
+ user.activate()
+
+ # Automatically login the user.
+ self.set_secure_cookie("user", user.name)
+ self._current_user = user
+
+ self.render("register-activation-success.html", user=user)
+ return
+
+ # Otherwise, show an error message.
+ self.render("register-activation-fail.html")
+
+
+class LogoutHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ # Remove the cookie, that identifies the user.
+ self.clear_cookie("user")
+ del self._current_user
+
+ # Show a message to the user.
+ self.render("logout.html")
--- /dev/null
+#!/usr/bin/python
+
+import httplib
+import time
+import tornado.locale
+import tornado.web
+
+import backend
+import backend.misc
+
+class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ user = self.get_secure_cookie("user")
+ if not user:
+ return
+
+ return self.pakfire.users.get_by_name(user)
+
+ def get_user_locale(self):
+ DEFAULT_LOCALE = tornado.locale.get("en_US")
+ ALLOWED_LOCALES = \
+ [tornado.locale.get(l) for l in tornado.locale.get_supported_locales(None)]
+
+ # One can append "?locale=de" to mostly and URI on the site and
+ # another output that guessed.
+ locale = self.get_argument("locale", None)
+ if locale:
+ locale = tornado.locale.get(locale)
+ for locale in ALLOWED_LOCALES:
+ return locale
+
+ # Get the locale from the user settings.
+ if self.current_user and self.current_user.locale:
+ locale = tornado.locale.get(self.current_user.locale)
+ if locale in ALLOWED_LOCALES:
+ return locale
+
+ # If no locale was provided we guess what the browser sends us
+ locale = self.get_browser_locale()
+ if locale in ALLOWED_LOCALES:
+ return locale
+
+ # If no one of the cases above worked we use our default locale
+ return DEFAULT_LOCALE
+
+ @property
+ def render_args(self):
+ return {
+ "hostname" : self.request.host,
+ "friendly_size" : backend.misc.friendly_size,
+ "lang" : self.locale.code[:2],
+ "year" : time.strftime("%Y"),
+ }
+
+ def render(self, *args, **kwargs):
+ kwargs.update(self.render_args)
+ tornado.web.RequestHandler.render(self, *args, **kwargs)
+
+ def render_string(self, *args, **kwargs):
+ kwargs.update(self.render_args)
+ return tornado.web.RequestHandler.render_string(self, *args, **kwargs)
+
+ def get_error_html(self, status_code, **kwargs):
+ if status_code in (404, 500):
+ render_args = ({
+ "code" : status_code,
+ "exception" : kwargs.get("exception", None),
+ "message" : httplib.responses[status_code],
+ })
+ return self.render_string("error-%s.html" % status_code, **render_args)
+ else:
+ return tornado.web.RequestHandler.get_error_html(self, status_code, **kwargs)
+
+ @property
+ def pakfire(self):
+ return self.application.pakfire
+
--- /dev/null
+#!/usr/bin/python
+
+import backend
+
+from handlers_base import *
+
+class BuilderListHandler(BaseHandler):
+ def get(self):
+ builders = self.pakfire.builders.get_all()
+
+ self.render("builder-list.html", builders=builders)
+
+
+class BuilderDetailHandler(BaseHandler):
+ def get(self, hostname):
+ builder = self.pakfire.builders.get_by_name(hostname)
+
+ self.render("builder-detail.html", builder=builder)
+
+
+class BuilderNewHandler(BaseHandler):
+ def get(self):
+ self.render("builder-new.html")
+
+ def post(self):
+ name = self.get_argument("name")
+
+ # Create a new builder.
+ builder = backend.builders.Builder.new(self.pakfire, name)
+
+ self.render("builder-pass.html", action="new", builder=builder)
+
+
+class BuilderEditHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self, hostname):
+ builder = self.pakfire.builders.get_by_name(hostname)
+ if not builder:
+ raise tornado.web.HTTPError(404, "Builder not found")
+
+ self.render("builder-edit.html", builder=builder)
+
+ @tornado.web.authenticated
+ def post(self, hostname):
+ builder = self.pakfire.builders.get_by_name(hostname)
+ if not builder:
+ raise tornado.web.HTTPError(404, "Builder not found")
+
+ builder.enabled = self.get_argument("enabled", False)
+ builder.build_src = self.get_argument("build_src", False)
+ builder.build_bin = self.get_argument("build_bin", False)
+ builder.build_test = self.get_argument("build_test", False)
+
+ # Save max_jobs.
+ max_jobs = self.get_argument("max_jobs", builder.max_jobs)
+ try:
+ max_jobs = int(max_jobs)
+ except TypeError:
+ max_jobs = 1
+
+ if not max_jobs in (1, 2, 3, 4, 5, 6, 7, 8,):
+ max_jobs = 1
+ builder.max_jobs = max_jobs
+
+ self.redirect("/builder/%s" % builder.hostname)
+
+
+class BuilderRenewPassphraseHandler(BaseHandler):
+ def get(self, name):
+ builder = self.pakfire.builders.get_by_name(name)
+
+ builder.regenerate_passphrase()
+
+ self.render("builder-pass.html", action="update", builder=builder)
+
+
+class BuilderDeleteHandler(BaseHandler):
+ def get(self, name):
+ builder = self.pakfire.builders.get_by_name(name)
+
+ confirmed = self.get_argument("confirmed", None)
+ if confirmed:
+ builder.delete()
+ self.redirect("/builders")
+ return
+
+ self.render("builder-delete.html", builder=builder)
--- /dev/null
+#!/usr/bin/python
+
+from handlers_base import *
+
+class SearchHandler(BaseHandler):
+ def get(self):
+ query = self.get_argument("q", "")
+ if not query:
+ self.render("search-form.html")
+ return
+
+ pkgs = self.pakfire.packages.search(query)
+
+ self.render("search-results.html", query=query, pkgs=pkgs)
--- /dev/null
+#!/usr/bin/python
+
+import tornado.locale
+import tornado.web
+
+from handlers_base import *
+
+class UserHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self, name=None):
+ user = self.current_user
+
+ if name:
+ user = self.pakfire.users.get_by_name(name)
+ if not user:
+ raise tornado.web.HTTPError(404, "User does not exist: %s" % name)
+
+ self.render("user-profile.html", user=user)
+
+
+class UserDeleteHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self, name):
+ user = self.pakfire.users.get_by_name(name)
+ if not user:
+ raise tornado.web.HTTPError(404)
+
+ if not self.current_user == user and not self.current_user.is_admin():
+ raise tornado.web.HTTPError(403)
+
+ confirmed = self.get_argument("confirmed", None)
+ if confirmed:
+ user.delete()
+
+ if self.current_user == user:
+ self.redirect("/logout")
+ else:
+ self.redirect("/users")
+
+ self.render("user-delete.html", user=user)
+
+
+class UserEditHandler(BaseHandler):
+ def prepare(self):
+ # Make list of all supported locales.
+ self.supported_locales = \
+ [tornado.locale.get(l) for l in tornado.locale.get_supported_locales(None)]
+
+ @tornado.web.authenticated
+ def get(self, name):
+ user = self.pakfire.users.get_by_name(name)
+ if not user:
+ raise tornado.web.HTTPError(404)
+
+ if not self.current_user == user and not self.current_user.is_admin():
+ raise tornado.web.HTTPError(403)
+
+ self.render("user-profile-edit.html", user=user,
+ supported_locales=self.supported_locales)
+
+ @tornado.web.authenticated
+ def post(self, name):
+ _ = self.locale.translate
+
+ user = self.pakfire.users.get_by_name(name)
+ if not user:
+ raise tornado.web.HTTPError(404)
+
+ email = self.get_argument("email", user.email)
+ realname = self.get_argument("realname", user.realname)
+ pass1 = self.get_argument("pass1", None)
+ pass2 = self.get_argument("pass2", None)
+ locale = self.get_argument("locale", "")
+
+ # Only an admin can alter the state of a user.
+ if self.current_user.is_admin():
+ state = self.get_argument("state", user.state)
+ else:
+ state = user.state
+
+ # Collect error messages
+ msgs = []
+
+ if not email:
+ msgs.append(_("No email address provided."))
+ elif not "@" in email:
+ msgs.append(_("Email address is invalid."))
+
+ # Check if the passphrase is okay.
+ if pass1 and not len(pass1) >= 8:
+ msgs.append(_("Password has less than 8 characters."))
+ elif not pass1 == pass2:
+ msgs.append(_("Passwords do not match."))
+
+ # Check if locale is valid.
+ if locale and not locale in [l.code for l in self.supported_locales]:
+ msg.append(_("The choosen locale is invalid."))
+
+ if msgs:
+ self.render("user-profile-edit-fail.html", messages=msgs)
+ return
+
+ # Everything is okay, we can save the new settings.
+ user.locale = locale
+ user.email = email
+ user.realname = realname
+ if pass1:
+ user.passphrase = pass1
+ user.state = state
+
+ if not user.activated:
+ self.render("user-profile-need-activation.html", user=user)
+ return
+
+ self.redirect("/user/%s" % user.name)
+
+
+class UsersHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ admins, testers, users = [], [], []
+
+ for user in self.pakfire.users.get_all():
+ if user.is_admin():
+ admins.append(user)
+ elif user.is_tester():
+ testers.append(user)
+ else:
+ users.append(user)
+
+ self.render("user-list.html", admins=admins, testers=testers,
+ users=users)
+
+
+class UsersCommentsHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ comments = self.pakfire.packages.get_comments(limit=20)
+
+ self.render("user-comments.html", comments=comments)
+
--- /dev/null
+
+import tornado.web
+
+from backend.constants import *
+
+class UIModule(tornado.web.UIModule):
+ @property
+ def pakfire(self):
+ return self.handler.application.pakfire
+
+
+class PackageTableModule(UIModule):
+ def render(self, letter, packages):
+ return self.render_string("modules/package-table.html",
+ letter=letter, packages=packages)
+
+
+class PackageTable2Module(UIModule):
+ def render(self, packages):
+ return self.render_string("modules/package-table-detail.html",
+ packages=packages)
+
+
+class FilesTableModule(UIModule):
+ def render(self, files):
+ return self.render_string("modules/files-table.html", files=files)
+
+
+class PackageFilesTableModule(UIModule):
+ def render(self, files):
+ return self.render_string("modules/package-files-table.html", files=files)
+
+
+class BuildTableModule(UIModule):
+ def render(self, builds):
+ return self.render_string("modules/build-table.html", builds=builds)
+
+
+class RepositoryTableModule(UIModule):
+ def render(self, distro, repos):
+ return self.render_string("modules/repository-table.html",
+ distro=distro, repos=repos)
+
+
+class SourceTableModule(UIModule):
+ def render(self, distro, sources):
+ return self.render_string("modules/source-table.html",
+ distro=distro, sources=sources)
+
+
+class CommentsTableModule(UIModule):
+ def render(self, comments, show_package=False, show_user=True):
+ pkgs, users = {}, {}
+ for comment in comments:
+ if show_package:
+ try:
+ pkg = pkgs[comment.pkg_id]
+ except KeyError:
+ pkg = pkgs[comment.pkg_id] = \
+ self.pakfire.packages.get_by_id(comment.pkg_id)
+
+ comment["pkg"] = pkg
+
+ if show_user:
+ try:
+ user = users[comment.user_id]
+ except KeyError:
+ user = users[comment.user_id] = \
+ self.pakfire.users.get_by_id(comment.user_id)
+
+ comment["user"] = user
+
+ return self.render_string("modules/comments-table.html",
+ comments=comments, show_package=show_package, show_user=show_user)
+
+
+class BuildLogModule(UIModule):
+ # XXX deprecated
+ def render(self, messages):
+ _ = self.locale.translate
+
+ for message in messages:
+ try:
+ msg = LOG2MSG[message.message]
+ message["message"] = _(msg)
+ except KeyError:
+ pass
+
+ return self.render_string("modules/build-log.html", messages=messages)
+
+
+class LogTableModule(UIModule):
+ def render(self, messages, links=["pkg",]):
+ for message in messages:
+ try:
+ message["message"] = LOG2MSG[message.message]
+ except KeyError:
+ pass
+
+ if message.build_id:
+ message["build"] = self.pakfire.builds.get_by_id(message.build_id)
+
+ elif message.pkg_id:
+ message["pkg"] = self.pakfire.packages.get_by_id(message.pkg_id)
+
+ return self.render_string("modules/log-table.html",
+ messages=messages, links=links)
+
+
+class UsersTableModule(UIModule):
+ def render(self, users):
+ return self.render_string("modules/user-table.html", users=users)
+
+
+class BuildOffsetModule(UIModule):
+ def render(self):
+ return self.render_string("modules/build-offset.html")
+
+
+class RepoActionsTableModule(UIModule):
+ def render(self, repo):
+ actions = repo.get_actions()
+
+ return self.render_string("modules/repo-actions-table.html",
+ repo=repo, actions=actions)