From patchwork Fri Jun 28 09:47:15 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Stevan Radakovic X-Patchwork-Id: 18176 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-qe0-f69.google.com (mail-qe0-f69.google.com [209.85.128.69]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id 7534825E2D for ; Fri, 28 Jun 2013 09:47:19 +0000 (UTC) Received: by mail-qe0-f69.google.com with SMTP id a11sf1311562qen.0 for ; Fri, 28 Jun 2013 02:47:18 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-beenthere:x-forwarded-to:x-forwarded-for:delivered-to :mime-version:x-launchpad-project:x-launchpad-branch :x-launchpad-message-rationale:x-launchpad-branch-revision-number :x-launchpad-notification-type:to:from:subject:message-id:date :reply-to:sender:errors-to:precedence:x-generated-by :x-launchpad-hash:x-gm-message-state:x-original-sender :x-original-authentication-results:mailing-list:list-id :x-google-group-id:list-post:list-help:list-archive:list-unsubscribe :content-type; bh=p2z93JmguNe6TyF0Bcy3T2GJHKnJMtFEBswIJLlS0hA=; b=MOz4Gp3N4zQNSWfoHlCbEIY6PF8LyrODnPDu8pTdwUdSo0Eoz3RUE/mcEchyc+RBCl PjIeXn/M0hqCryk3O/HUUH6O/QEkWTabtFSlrYGVjJvRN2jwg2zK9gBhDOw4wVQyIxNw oByf1AOyw9kxib1BrirSX+Z4v8rc/TnMEGmysfB7+0NUQaDp4W8PGqQDiw7khUURUgqE kdYgVW8SvqCeEdUUH4K6+wGL5DT4q3d7vyuEmqxGatSxYzoZ9k1A1lt4Obpq8I/k+sEI ai/07Oy/4v8xJmVDB9+hLb2NnAKitx4ZGKL7tjmWLe52DyaEEmTOD/i0X2n0qquqgW0r TOEA== X-Received: by 10.236.113.197 with SMTP id a45mr7037487yhh.14.1372412838455; Fri, 28 Jun 2013 02:47:18 -0700 (PDT) X-BeenThere: patchwork-forward@linaro.org Received: by 10.49.13.168 with SMTP id i8ls1242118qec.65.gmail; Fri, 28 Jun 2013 02:47:18 -0700 (PDT) X-Received: by 10.59.0.131 with SMTP id ay3mr1067759ved.78.1372412838166; Fri, 28 Jun 2013 02:47:18 -0700 (PDT) Received: from mail-vb0-f46.google.com (mail-vb0-f46.google.com [209.85.212.46]) by mx.google.com with ESMTPS id v5si1828393vec.15.2013.06.28.02.47.18 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 28 Jun 2013 02:47:18 -0700 (PDT) Received-SPF: neutral (google.com: 209.85.212.46 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) client-ip=209.85.212.46; Received: by mail-vb0-f46.google.com with SMTP id 10so1557695vbe.33 for ; Fri, 28 Jun 2013 02:47:18 -0700 (PDT) X-Received: by 10.220.53.7 with SMTP id k7mr5168429vcg.52.1372412838065; Fri, 28 Jun 2013 02:47:18 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patches@linaro.org Received: by 10.58.165.8 with SMTP id yu8csp182777veb; Fri, 28 Jun 2013 02:47:17 -0700 (PDT) X-Received: by 10.194.109.104 with SMTP id hr8mr9093183wjb.32.1372412836577; Fri, 28 Jun 2013 02:47:16 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id ez1si2435076wib.74.2013.06.28.02.47.15 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Fri, 28 Jun 2013 02:47:16 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1UsVGt-00064J-Sl for ; Fri, 28 Jun 2013 09:47:15 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id CE5DCEB2FA for ; Fri, 28 Jun 2013 09:47:15 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-scheduler X-Launchpad-Branch: ~linaro-validation/lava-scheduler/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 248 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-scheduler/trunk] Rev 248: Add GUI for job submissions. Reviewed by stylesen. Message-Id: <20130628094715.27278.76815.launchpad@ackee.canonical.com> Date: Fri, 28 Jun 2013 09:47:15 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: list X-Generated-By: Launchpad (canonical.com); Revision="16692"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 40907a7342a80ffbfcc5b82442a7a686cbd8f65d X-Gm-Message-State: ALoCoQnL9kJN9tqAKYYMiVskRMtjS5fFE/cj0DknMw0n78OcisB7np1PBWRvt2gwsP8xdmi1taUz X-Original-Sender: noreply@launchpad.net X-Original-Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.212.46 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) smtp.mail=patch+caf_=patchwork-forward=linaro.org@linaro.org Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org List-ID: X-Google-Group-Id: 836684582541 List-Post: , List-Help: , List-Archive: List-Unsubscribe: , Merge authors: Stevan Radaković (stevanr) Related merge proposals: https://code.launchpad.net/~stevanr/lava-scheduler/gui-job-submission/+merge/171571 proposed by: Stevan Radaković (stevanr) review: Approve - Senthil Kumaran S (stylesen) ------------------------------------------------------------ revno: 248 [merge] committer: Stevan Radakovic branch nick: trunk timestamp: Fri 2013-06-28 11:46:21 +0200 message: Add GUI for job submissions. Reviewed by stylesen. added: lava_scheduler_app/static/lava_scheduler_app/css/jquery-linedtextarea.css lava_scheduler_app/static/lava_scheduler_app/js/job-submit.js lava_scheduler_app/static/lava_scheduler_app/js/jquery-linedtextarea.js lava_scheduler_app/templates/lava_scheduler_app/job_submit.html modified: lava_scheduler_app/extension.py lava_scheduler_app/models.py lava_scheduler_app/static/lava_scheduler_app/css/scheduler.css lava_scheduler_app/urls.py lava_scheduler_app/views.py --- lp:lava-scheduler https://code.launchpad.net/~linaro-validation/lava-scheduler/trunk You are subscribed to branch lp:lava-scheduler. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-scheduler/trunk/+edit-subscription === modified file 'lava_scheduler_app/extension.py' --- lava_scheduler_app/extension.py 2012-06-16 03:04:57 +0000 +++ lava_scheduler_app/extension.py 2013-06-24 13:26:57 +0000 @@ -49,6 +49,7 @@ Menu("Status", reverse("lava.scheduler")), Menu("Jobs", reverse("lava.scheduler.job.list")), Menu("Reports", reverse("lava.scheduler.reports")), + Menu("Submit Job", reverse("lava.scheduler.job.submit")), ] return menu === modified file 'lava_scheduler_app/models.py' --- lava_scheduler_app/models.py 2013-02-10 21:26:06 +0000 +++ lava_scheduler_app/models.py 2013-06-26 12:57:41 +0000 @@ -41,7 +41,7 @@ ob = simplejson.loads(data) validate_job_data(ob) except ValueError, e: - raise ValidationError(str(e)) + raise ValidationError(e) class DeviceType(models.Model): === added file 'lava_scheduler_app/static/lava_scheduler_app/css/jquery-linedtextarea.css' --- lava_scheduler_app/static/lava_scheduler_app/css/jquery-linedtextarea.css 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/static/lava_scheduler_app/css/jquery-linedtextarea.css 2013-06-26 12:57:41 +0000 @@ -0,0 +1,74 @@ +/** + * jQuery Lined Textarea Plugin + * http://alan.blog-city.com/jquerylinedtextarea.htm + * + * Copyright (c) 2010 Alan Williamson + * + * Contribution done by Ryan Zielke (neoalchemy@gmail.com) + * + * Released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + * Usage: + * Displays a line number count column to the left of the textarea + * + * Class up your textarea with a given class, or target it directly + * with JQuery Selectors + * + * $(".lined").linedtextarea({ + * selectedLine: 10, + * selectedClass: 'lineselect' + * }); + * + */ + +textarea { resize:both; } + +.linedwrap { + border: 1px solid #c0c0c0; + padding: 3px; + display: inline-block; +} + +.linedtextarea { + padding: 0px; + margin: 0px; +} + +.linedtextarea textarea, .linedwrap .codelines .lineno { + font-size: 9pt; + font-family: monospace; + line-height: normal !important; +} + +.linedtextarea textarea { + padding-right:0.3em; + padding-top:0.3em; + border: 0; +} + +.linedwrap .lines { + margin-top: 0px; + width: 50px; + float: left; + overflow: hidden; + border-right: 1px solid #c0c0c0; + margin-right: 10px; +} + +.linedwrap .codelines { + padding-top: 3px; +} + +.linedwrap .codelines .lineno { + color:#AAAAAA; + padding-right: 0.5em; + padding-top: 0.0em; + text-align: right; + white-space: nowrap; +} + +.linedwrap .codelines .lineselect { + color: white; + background-color: red; +} \ No newline at end of file === modified file 'lava_scheduler_app/static/lava_scheduler_app/css/scheduler.css' --- lava_scheduler_app/static/lava_scheduler_app/css/scheduler.css 2012-03-15 09:27:48 +0000 +++ lava_scheduler_app/static/lava_scheduler_app/css/scheduler.css 2013-06-28 09:45:22 +0000 @@ -44,4 +44,34 @@ .logbuttons .ui-button-text { padding: 0.1em 0.4em; -} \ No newline at end of file +} + +#json-input { + width: 900px; + height: 400px; + margin: 0px; + resize: both; +} + +#submit-container { + margin-top: 10px; +} + +#json-valid-container { + border: 1px solid #cccccc; + display: none; + margin-top: 10px; + width: 500px; + padding: 10px; +} + +#job-success { + font-size: 16px; + font-weight: bold; +} + +#job-error { + font-size: 14px; + font-weight: bold; + color: red; +} === added file 'lava_scheduler_app/static/lava_scheduler_app/js/job-submit.js' --- lava_scheduler_app/static/lava_scheduler_app/js/job-submit.js 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/static/lava_scheduler_app/js/job-submit.js 2013-06-27 11:51:21 +0000 @@ -0,0 +1,107 @@ +$(window).ready( + function () { + $("#json-input").linedtextarea(); + + $("#json-input").bind('paste', function() { + // Need a timeout since paste event does not give the content + // of the clipboard. + setTimeout(function(){ + validate_input($("#json-input").val()); + },100); + }); + + $("#json-input").blur(function() { + validate_input($("#json-input").val()); + }); + + $("#submit").attr("disabled", "disabled"); + }); + +validate_input = function(json_input) { + + if ($("#json-input").val().split("\n").length == 1) { + load_url(); + } else { + validate_job_data(json_input); + } +} + +load_url = function() { + // Loads JSON content if URL is provided in the json text area. + if ($("#json-input").val().split("\n").length == 1) { + $.ajax({ + type: "POST", + url: remote_json_url, + data: { + "url": $("#json-input").val().trim(), + "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val() + }, + success: function(data) { + try { + $.parseJSON(data); + $("#json-input").val(data); + validate_job_data(data); + } catch (e) { + $("#json-valid-container").html("Invalid JSON: " + data); + valid_json_css(false); + $("#submit").attr("disabled", "disabled"); + } + }}); + } +} + +validate_job_data = function(data) { + $.post(window.location.pathname, + {"json-input": data, + "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val()}, + function(data) { + if (data == "success") { + $("#json-valid-container").html("Valid JSON."); + valid_json_css(true); + $("#submit").removeAttr("disabled"); + unselect_error_line(); + } else { + $("#json-valid-container").html( + data.replace("[u'", "").replace("']", ""). + replace('[u"', "").replace('"]', "")); + valid_json_css(false); + $("#submit").attr("disabled", "disabled"); + select_error_line(data); + } + }, "json"); +} + + +valid_json_css = function(success) { + // Updates the css of the json validation container with appropriate msg. + if (success) { + $("#json-valid-container").css("backgound-color", "#50ef53"); + $("#json-valid-container").css("color", "#139a16"); + $("#json-valid-container").css("border-color", "#139a16"); + $("#json-valid-container").show(); + } else { + $("#json-valid-container").css("backgound-color", "#ff8383"); + $("#json-valid-container").css("color", "#da110a"); + $("#json-valid-container").css("border-color", "#da110a"); + $("#json-valid-container").show(); + } +} + +unselect_error_line = function() { + // Unselect any potential previously selected lines. + $(".lineno").removeClass("lineselect"); +} + +select_error_line = function(error) { + // Selects the appropriate line in text area based on the parsed error msg. + line_string = error.split(": ")[1]; + line_number = parseInt(line_string.split(" ")[1]); + + $(".lineno").removeClass("lineselect"); + $("#lineno"+line_number).addClass("lineselect"); + + // Scroll the textarea to the highlighted line. + $("#json-input").scrollTop( + line_number * (parseInt($("#lineno1").css( + "height")) - 1) - ($("#json-input").height() / 2)); +} === added file 'lava_scheduler_app/static/lava_scheduler_app/js/jquery-linedtextarea.js' --- lava_scheduler_app/static/lava_scheduler_app/js/jquery-linedtextarea.js 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/static/lava_scheduler_app/js/jquery-linedtextarea.js 2013-06-26 11:39:05 +0000 @@ -0,0 +1,119 @@ +/** + * jQuery Lined Textarea Plugin + * http://alan.blog-city.com/jquerylinedtextarea.htm + * + * Copyright (c) 2010 Alan Williamson + * + * Contributions done by Ryan Zielke (NeoAlchemy@gmail.com) + * + * Version: + * $Id: jquery-linedtextarea.js 464 2010-01-08 10:36:33Z alan $ + * + * Released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + * Usage: + * Displays a line number count column to the left of the textarea + * + * Class up your textarea with a given class, or target it directly + * with JQuery Selectors + * + * $(".lined").linedtextarea({ + * selectedLine: 10, + * selectedClass: 'lineselect' + * }); + */ + +(function($) { + $.fn.linedtextarea = function(options) { + // Get the Options + var opts = $.extend({}, $.fn.linedtextarea.defaults, options); + + /* + * Helper function to make sure the line numbers are always + * kept up to the current system + */ + var fillOutLines = function(codeLines, h, lineNo){ + while ( (codeLines.height() - h ) <= 0 ){ + if ( lineNo == opts.selectedLine ) + codeLines.append("
" + lineNo + "
"); + else + codeLines.append("
" + lineNo + "
"); + + lineNo++; + } + return lineNo; + }; + + /* + * Iterate through each of the elements are to be applied to + */ + return this.each(function() { + var lineNo = 1; + var textarea = $(this); + + /* Turn off the wrapping of as we don't want to screw up the line numbers */ + textarea.attr("wrap", "off"); + textarea.css({resize:'both'}); + var originalTextAreaWidth = textarea.outerWidth(); + + /* Wrap the text area in the elements we need */ + var linedTextAreaDiv = textarea.wrap("
"); + var linedWrapDiv = linedTextAreaDiv.parent(); + + linedWrapDiv.prepend("
"); + + var linesDiv = linedWrapDiv.find(".lines"); + + /* Draw the number bar; filling it out where necessary */ + linesDiv.append("
"); + var codeLinesDiv = linesDiv.find(".codelines"); + lineNo = fillOutLines( codeLinesDiv, linesDiv.height(), 1 ); + + /* Move the textarea to the selected line */ + if ( opts.selectedLine != -1 && !isNaN(opts.selectedLine) ){ + var fontSize = parseInt( textarea.height() / (lineNo-2) ); + var position = parseInt( fontSize * opts.selectedLine ) - (textarea.height()/2); + textarea[0].scrollTop = position; + } + + /* Set the width */ + var sidebarWidth = linesDiv.outerWidth(true); + var paddingHorizontal = parseInt( linedWrapDiv.css("border-left-width") ) + parseInt( linedWrapDiv.css("border-right-width") ) + parseInt( linedWrapDiv.css("padding-left") ) + parseInt( linedWrapDiv.css("padding-right") ); + var linedWrapDivNewWidth = originalTextAreaWidth - paddingHorizontal; + var textareaNewWidth = originalTextAreaWidth - sidebarWidth - paddingHorizontal; + + textarea.width(textareaNewWidth); + textarea.css({maxWidth: textareaNewWidth - 6}); //TODO make this calculated + + /* React to the scroll event */ + textarea.scroll( function(tn){ + var domTextArea = $(this)[0]; + var scrollTop = domTextArea.scrollTop; + var clientHeight = domTextArea.clientHeight; + codeLinesDiv.css({'margin-top': (-1*scrollTop) + "px"}); + lineNo = fillOutLines(codeLinesDiv, scrollTop + clientHeight, lineNo); + }); + + /* Should the textarea get resized outside of our control */ + textarea.resize( function(tn){ + var domTextArea = $(this)[0]; + linesDiv.height(domTextArea.clientHeight + 6); + }); + + window.setInterval( function(tn) { + linesDiv.height(textarea.height()); + var scrollTop = textarea[0].scrollTop; + var clientHeight = textarea[0].clientHeight; + codeLinesDiv.css({'margin-top': (-1*scrollTop) + "px"}); + lineNo = fillOutLines(codeLinesDiv, scrollTop + clientHeight, lineNo); + },10); + }); + }; + + // default options + $.fn.linedtextarea.defaults = { + selectedLine: -1, + selectedClass: 'lineselect' + }; +})(jQuery); === added file 'lava_scheduler_app/templates/lava_scheduler_app/job_submit.html' --- lava_scheduler_app/templates/lava_scheduler_app/job_submit.html 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/templates/lava_scheduler_app/job_submit.html 2013-06-28 09:43:44 +0000 @@ -0,0 +1,67 @@ +{% extends "lava_scheduler_app/_content.html" %} + +{% block extrahead %} +{{ block.super }} + + + +{% url lava.scheduler.get_remote_json as remote_json_url %} + + +{% endblock %} + + +{% block content %} +

Submit Job

+ +{% if is_authorized %} + +{% if job_id %} + +{% url lava.scheduler.job.detail job_id as detail_url %} +{% url lava.scheduler.job.list as list_url %} + +
Job submission successfull! +
+
+Job with ID {{ job_id }} has been created. +
+To view the full job list click here. +
+ +{% else %} + +{% if error %} +
+ Job submission error: {{ error }} +
+{% endif %} + +

Paste your job definition JSON here. Alternatively, you can paste a URL to your job definition file:

+ +
+ {% csrf_token %} + +
+
+
+ +
+
+ +{% endif %} + +{% else %} +

+Error: +

+

Permission denied. You not have the required permissions to submit new jobs. +
+Please contact the administrators.

+{% endif %} + +{% endblock %} === modified file 'lava_scheduler_app/urls.py' --- lava_scheduler_app/urls.py 2013-01-15 17:44:36 +0000 +++ lava_scheduler_app/urls.py 2013-06-27 11:01:30 +0000 @@ -24,6 +24,9 @@ url(r'^alljobs$', 'job_list', name='lava.scheduler.job.list'), + url(r'^jobsubmit$', + 'job_submit', + name='lava.scheduler.job.submit'), url(r'^alljobs_json$', 'alljobs_json', name='lava.scheduler.job.list_json'), @@ -105,4 +108,7 @@ url(r'^job/(?P[0-9]+)/full_log_incremental$', 'job_full_log_incremental', name='lava.scheduler.job.full_log_incremental'), + url(r'^get-remote-json', + 'get_remote_json', + name='lava.scheduler.get_remote_json'), ) === modified file 'lava_scheduler_app/views.py' --- lava_scheduler_app/views.py 2013-05-02 09:00:04 +0000 +++ lava_scheduler_app/views.py 2013-06-27 11:28:22 +0000 @@ -4,6 +4,7 @@ import simplejson import StringIO import datetime +import urllib2 from dateutil.relativedelta import relativedelta from django import forms @@ -51,7 +52,9 @@ DeviceType, DeviceStateTransition, JobFailureTag, + JSONDataError, TestJob, + validate_job_json, ) @@ -555,6 +558,51 @@ RequestContext(request)) +@BreadCrumb("Submit Job", parent=index) +def job_submit(request): + + is_authorized = False + if request.user and request.user.has_perm( + 'lava_scheduler_app.add_testjob'): + is_authorized = True + + response_data = { + 'is_authorized': is_authorized, + 'bread_crumb_trail': BreadCrumbTrail.leading_to(job_submit), + } + + if request.method == "POST" and is_authorized: + if request.is_ajax(): + try: + validate_job_json(request.POST.get("json-input")) + return HttpResponse(simplejson.dumps("success")) + except Exception as e: + return HttpResponse(simplejson.dumps(str(e)), + mimetype="application/json") + + else: + try: + job = TestJob.from_json_and_user( + request.POST.get("json-input"), request.user) + + response_data["job_id"] = job.id + return render_to_response( + "lava_scheduler_app/job_submit.html", + response_data, RequestContext(request)) + + except Exception as e: + response_data["error"] = str(e) + response_data["json_input"] = request.POST.get("json-input") + return render_to_response( + "lava_scheduler_app/job_submit.html", + response_data, RequestContext(request)) + + else: + return render_to_response( + "lava_scheduler_app/job_submit.html", + response_data, RequestContext(request)) + + @BreadCrumb("Job #{pk}", parent=index, needs=['pk']) def job_detail(request, pk): job = get_restricted_job(request.user, pk) @@ -779,6 +827,23 @@ return HttpResponse(json_text, content_type=content_type) +@post_only +def get_remote_json(request): + """Fetches remote json file.""" + url = request.POST.get("url") + + try: + data = urllib2.urlopen(url).read() + # Validate that the data at the location is really JSON. + # This is security based check so noone can misuse this url. + simplejson.loads(data) + except Exception as e: + return HttpResponse(simplejson.dumps(str(e)), + mimetype="application/json") + + return HttpResponse(data) + + class RecentJobsTable(JobTable): def get_queryset(self, device):