diff --git a/MANIFEST.in b/MANIFEST.in index 08ebc95b..9ff618ea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include Makefile include requirements.txt include version.txt +recursive-include swh/web/ui/static * diff --git a/PKG-INFO b/PKG-INFO index 52c8c76c..f321fdcb 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,10 +1,10 @@ Metadata-Version: 1.0 Name: swh.web.ui -Version: 0.0.1 +Version: 0.0.2 Summary: Software Heritage Web UI Home-page: https://forge.softwareheritage.org/diffusion/DWUI/ Author: Software Heritage developers Author-email: swh-devel@inria.fr License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN diff --git a/bin/swh-web-ui b/bin/swh-web-ui index 6d3621ff..52af25d1 100755 --- a/bin/swh-web-ui +++ b/bin/swh-web-ui @@ -1,61 +1,41 @@ #!/usr/bin/env python3 # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import argparse import logging import os -from swh.core import config -from swh.web.ui import controller -from swh.storage import Storage +from swh.web.ui import controller, main # Default configuration file DEFAULT_CONF_FILE = '~/.config/swh/web-ui.ini' - -# default configuration -DEFAULT_CONF = { - 'storage_args': ('list[str]', ['http://localhost:5000/']), - 'storage_class': ('str', 'remote_storage'), - 'log_dir': ('string', '/tmp/swh/loader-git/log'), - 'debug': ('bool' , None), - 'host': ('string', '127.0.0.1'), - 'port': ('int' , 6543), -} - +# Default configuration in swh.web.ui.main def parse_args(): """Parse the configuration for the cli. """ cli = argparse.ArgumentParser(description="SWH's web ui.") cli.add_argument('--verbose', '-v', action='store_true', help='Verbosity level in log file.') cli.add_argument('--config', '-c', help='configuration file path') args = cli.parse_args() return args if __name__ == '__main__': args = parse_args() - conf = config.read(args.config or DEFAULT_CONF_FILE, DEFAULT_CONF) - config.prepare_folders(conf, 'log_dir') + conf = main.read_config(args.config or DEFAULT_CONF_FILE) - if conf['storage_class'] == 'remote_storage': - from swh.storage.api.client import RemoteStorage as Storage - else: - from swh.storage import Storage - - conf.update({ - 'storage': Storage(*conf['storage_args']) - }) logging.basicConfig(filename=os.path.join(conf['log_dir'], 'web-ui.log'), level=logging.DEBUG if args.verbose else logging.INFO) + controller.run(conf) diff --git a/debian/changelog b/debian/changelog index 7613cd7a..18d42009 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,14 +1,14 @@ -swh-web (0.0.1-1~swh1~bpo9+1) stretch-swh; urgency=medium +swh-web (0.0.2-1~swh1) unstable-swh; urgency=medium - * Rebuild for stretch-backports. + * Prepare swh.web.ui v0.0.2 deployment - -- Antoine R. Dumont (@ardumont) Thu, 01 Oct 2015 10:01:29 +0200 + -- Nicolas Dandrimont Tue, 13 Oct 2015 16:25:46 +0200 swh-web (0.0.1-1~swh1) unstable-swh; urgency=medium * Initial release * v0.0.1 * Hash lookup to check existence in swh's backend * Hash lookup to detail a content -- Antoine R. Dumont (@ardumont) Thu, 01 Oct 2015 10:01:29 +0200 diff --git a/PKG-INFO b/swh.web.ui.egg-info/PKG-INFO similarity index 94% copy from PKG-INFO copy to swh.web.ui.egg-info/PKG-INFO index 52c8c76c..f321fdcb 100644 --- a/PKG-INFO +++ b/swh.web.ui.egg-info/PKG-INFO @@ -1,10 +1,10 @@ Metadata-Version: 1.0 Name: swh.web.ui -Version: 0.0.1 +Version: 0.0.2 Summary: Software Heritage Web UI Home-page: https://forge.softwareheritage.org/diffusion/DWUI/ Author: Software Heritage developers Author-email: swh-devel@inria.fr License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN diff --git a/swh.web.ui.egg-info/SOURCES.txt b/swh.web.ui.egg-info/SOURCES.txt new file mode 100644 index 00000000..f293792a --- /dev/null +++ b/swh.web.ui.egg-info/SOURCES.txt @@ -0,0 +1,36 @@ +.gitignore +AUTHORS +LICENSE +MANIFEST.in +Makefile +Makefile.local +README +README-uri-scheme.md +requirements.txt +setup.py +version.txt +bin/swh-web-ui +debian/changelog +debian/compat +debian/control +debian/copyright +debian/rules +debian/source/format +resources/test/web-ui.ini +swh.web.ui.egg-info/PKG-INFO +swh.web.ui.egg-info/SOURCES.txt +swh.web.ui.egg-info/dependency_links.txt +swh.web.ui.egg-info/requires.txt +swh.web.ui.egg-info/top_level.txt +swh/web/ui/controller.py +swh/web/ui/main.py +swh/web/ui/query.py +swh/web/ui/service.py +swh/web/ui/static/style.css +swh/web/ui/templates/content.html +swh/web/ui/templates/directory.html +swh/web/ui/templates/layout.html +swh/web/ui/templates/revision.html +swh/web/ui/templates/search.html +swh/web/ui/tests/test_controller.py +swh/web/ui/tests/test_query.py \ No newline at end of file diff --git a/swh.web.ui.egg-info/dependency_links.txt b/swh.web.ui.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/swh.web.ui.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/swh.web.ui.egg-info/requires.txt b/swh.web.ui.egg-info/requires.txt new file mode 100644 index 00000000..5c2eb354 --- /dev/null +++ b/swh.web.ui.egg-info/requires.txt @@ -0,0 +1,3 @@ +Flask +swh.core +swh.storage diff --git a/swh.web.ui.egg-info/top_level.txt b/swh.web.ui.egg-info/top_level.txt new file mode 100644 index 00000000..0cb0f8f5 --- /dev/null +++ b/swh.web.ui.egg-info/top_level.txt @@ -0,0 +1 @@ +swh diff --git a/swh/web/ui/controller.py b/swh/web/ui/controller.py index c1c9d3ef..ba4d2d41 100755 --- a/swh/web/ui/controller.py +++ b/swh/web/ui/controller.py @@ -1,318 +1,320 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import logging from flask import redirect, render_template, url_for, flash, request from flask import make_response from swh.core.hashutil import ALGORITHMS from swh.web.ui.main import app from swh.web.ui import service, query hash_filter_keys = ALGORITHMS @app.route('/') def main(): """Main application view. At the moment, redirect to the content search view. """ return redirect(url_for('info')) @app.route('/info') def info(): """A simple api to define what the server is all about. """ logging.info('Dev SWH UI') return 'Dev SWH UI' @app.route('/search') def search(): """Search for hashes in swh-storage. """ q = request.args.get('q', '') if q: flash("Search hash '%s' posted!" % q) message = service.lookup_hash(q) else: message = '' return render_template('search.html', q=q, message=message) @app.route('/browse/revision/') def revision(sha1_git): """Show commit information. Args: sha1_git: the revision's sha1 Returns: Revision information """ return render_template('revision.html', sha1_git=sha1_git) @app.route('/browse/directory/') def directory(sha1_git): """Show directory information. Args: sha1_git: the directory's sha1 Returns: Directory information """ return render_template('directory.html', sha1_git=sha1_git) @app.route('/browse/directory//') def directory_at_path(sha1_git, p): """Show directory information for the sha1_git at path. Args: sha1_git: the directory's sha1 path: file or directory pointed to Returns: Directory information at sha1_git + path """ return render_template('directory.html', sha1_git=sha1_git, path=p) @app.route('/browse/content/:') def content(hash, sha): """Show content information. Args: hash: hash according to HASH_ALGO, where HASH_ALGO is one of: sha1, sha1_git, sha256. This means that several different URLs (at least one per HASH_ALGO) will point to the same content sha: the sha with 'hash' format Returns: The content's information at sha1_git """ # Checks user input if hash not in hash_filter_keys: return make_response( 'Bad request, sha must be one of sha1, sha1_git, sha256', 400) h = query.categorize_hash(sha) if h == {}: return make_response( 'Bad request, %s is not of type %s' % (sha, hash), 400) if hash == 'sha256' and not h.get(hash): return make_response( 'Bad request, %s is not of type sha256' % (sha,), 400) if hash != 'sha256' and not h.get('sha1') and not h.get('sha1_git'): return make_response( 'Bad request, %s is not of type sha1 or sha1_git' % (sha,), 400) message = service.lookup_hash_origin(h) return render_template('content.html', hash=hash, sha=sha, message=message) @app.route('/browse/release/') def release(sha1_git): """Show release's information. Args: sha1_git: sha1_git for this particular release Returns: Release's information """ return 'Release information at %s' % sha1_git @app.route('/browse/person/') def person(id): """Show Person's information at id. Args: id: person's unique identifier Returns: Person's information """ return 'Person information at %s' % id @app.route('/browse/origin/') def origin(id): """Show origin's information at id. Args: id: origin's unique identifier Returns: Origin's information """ return 'Origin information at %s' % id @app.route('/browse/project/') def project(id): """Show project's information at id. Args: id: project's unique identifier Returns: Project's information """ return 'Project information at %s' % id @app.route('/browse/organization/') def organization(id): """Show organization's information at id. Args: id: organization's unique identifier Returns: Organization's information """ return 'Organization information at %s' % id @app.route('/browse/directory//' '+|/' '|/') def directory_at_origin(timestamp, origin_type, origin_url, branch, path): """Show directory information at timestamp, origin-type, origin-url, branch and path. Those parameters are separated by the `|` terminator. Args: timestamp: the timestamp to look for. can be latest or some iso8601 date format. (TODO: decide the time matching policy.) origin_type: origin's type origin_url: origin's url (can contain `/`) branch: branch name which can contain `/` path: path to directory or file Returns: Directory information at the given parameters. """ return 'Directory at (%s, %s, %s, %s, %s)' % (timestamp, origin_type, origin_url, branch, path) @app.route('/browse/revision//' '+|/') def revision_at_origin_and_branch(timestamp, origin_type, origin_url, branch): """Show revision information at timestamp, origin, and branch. Those parameters are separated by the `|` terminator. Args: timestamp: the timestamp to look for. can be latest or some iso8601 date format. (TODO: decide the time matching policy.) origin_type: origin's type origin_url: origin's url (can contain `/`) branch: branch name which can contain / Returns: Revision information at the given parameters. """ return 'Revision at (ts=%s, type=%s, url=%s, branch=%s)' % (timestamp, origin_type, origin_url, branch) @app.route('/browse/revision//' '+|') def revision_at_origin(timestamp, origin_type, origin_url): """Show revision information at timestamp, origin, and branch. Those parameters are separated by the `|` terminator. Args: timestamp: the timestamp to look for. can be latest or iso8601 date format. (TODO: decide the time matching policy.) origin_type: origin's type origin_url: origin's url (can contain `/`) Returns: Revision information at the given parameters. """ return 'Revision at (timestamp=%s, type=%s, url=%s)' % (timestamp, origin_type, origin_url) def run(conf): """Run the api's server. Args: conf is a dictionary of keywords: - 'db_url' the db url's access (through psycopg2 format) - 'content_storage_dir' revisions/directories/contents storage on disk - 'host' to override the default 127.0.0.1 to open or not the server to the world - 'port' to override the default of 5000 (from the underlying layer: flask) - 'debug' activate the verbose logs + - 'secret_key' the flask secret key Returns: Never Raises: ? """ print("""SWH Web UI run host: %s port: %s debug: %s""" % (conf['host'], conf.get('port', None), conf['debug'])) + app.secret_key = conf['secret_key'] app.config.update({'conf': conf}) app.run(host=conf['host'], port=conf.get('port', None), debug=conf['debug']) diff --git a/swh/web/ui/main.py b/swh/web/ui/main.py index 27d07eb5..9d932cbe 100644 --- a/swh/web/ui/main.py +++ b/swh/web/ui/main.py @@ -1,22 +1,65 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +import logging from flask import Flask +from swh.core import config -SECRET_KEY = 'development key' +DEFAULT_CONFIG = { + 'storage_args': ('list[str]', ['http://localhost:5000/']), + 'storage_class': ('str', 'remote_storage'), + 'log_dir': ('string', '/tmp/swh/log'), + 'debug': ('bool', None), + 'host': ('string', '127.0.0.1'), + 'port': ('int', 6543), + 'secret_key': ('string', 'development key'), +} # api's definition app = Flask(__name__) -app.config.from_object(__name__) + + +def read_config(config_file): + """Read the configuration file `config_file`, update the app with + parameters (secret_key, conf) and return the parsed configuration as a + dict""" + + conf = config.read(config_file, DEFAULT_CONFIG) + config.prepare_folders(conf, 'log_dir') + + if conf['storage_class'] == 'remote_storage': + from swh.storage.api.client import RemoteStorage as Storage + else: + from swh.storage import Storage + + conf['storage'] = Storage(*conf['storage_args']) + + return conf + + +def run_from_webserver(environ, start_response): + """Run the WSGI app from the webserver, loading the configuration.""" + + config_path = '/etc/softwareheritage/webapp/webapp.ini' + + conf = read_config(config_path) + + app.secret_key = conf['secret_key'] + app.config['conf'] = conf + + handler = logging.StreamHandler() + app.logger.addHandler(handler) + + return app(environ, start_response) def storage(): """Return the current application's storage. """ return app.config['conf']['storage'] diff --git a/swh/web/ui/tests/test_query.py b/swh/web/ui/tests/test_query.py index 6a2010cb..d579144d 100644 --- a/swh/web/ui/tests/test_query.py +++ b/swh/web/ui/tests/test_query.py @@ -1,35 +1,36 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import unittest from nose.tools import istest from swh.web.ui import query from swh.core import hashutil + class QueryTestCase(unittest.TestCase): @istest def categorize_hash(self): input_sha1 = 'f1d2d2f924e986ac86fdf7b36c94bcdf32beec15' res = query.categorize_hash(input_sha1) self.assertEquals(res, {'sha1': hashutil.hex_to_hash(input_sha1)}) def categorize_hash_2(self): input_sha256 = \ '084c799cd551dd1d8d5c5f9a5d593b2e931f5e36122ee5c793c1d08a19839cc0' res = query.categorize_hash(input_sha256) self.assertEquals(res, {'sha256': hashutil.hex_to_hash(input_sha256)}) def categorize_hash_3(self): input_bad_length = '1234567890987654' res = query.categorize_hash(input_bad_length) self.assertEquals(res, {}) diff --git a/version.txt b/version.txt new file mode 100644 index 00000000..0fa0f3f3 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +v0.0.2-0-g636f979 \ No newline at end of file