diff --git a/inginious/frontend/common/static/js/task.js b/inginious/frontend/common/static/js/task.js
index a246b47e..a0ad5e48 100644
--- a/inginious/frontend/common/static/js/task.js
+++ b/inginious/frontend/common/static/js/task.js
@@ -1006,7 +1006,10 @@ function convertInginiousLanguageToCodemirror(inginiousLanguage) {
"c11": "c",
"python2": "python",
"python3": "python",
- "ruby": "ruby"
+ "ruby": "ruby",
+ "GNU C++": "cpp",
+ "GNU C++11" : "cpp",
+ "GNU C++14" : "cpp"
};
return languages[inginiousLanguage];
@@ -1133,7 +1136,13 @@ var lintServerUrl = "http://localhost:4567/";
function lintCode(language, problemId, callback){
if(language == "plain")
language = getLanguageForProblemId(problemId);
- var editor = getEditorForProblemId(problemId);
+
+ language = convertInginiousLanguageToCodemirror(language)
+
+
+ var editor = getEditorForProblemId(problemId);
+
+
var code = editor.getValue();
var apiUrl = lintServerUrl + language;
callback = callback || getCallbackForLanguage(language, editor);
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/__init__.py b/inginious/frontend/webapp/plugins/rubric_scoring/__init__.py
new file mode 100644
index 00000000..20ee5f45
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/__init__.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
+# more information about the licensing of this file.
+
+""" A demo plugin that adds a page """
+from . import pages
+
+
+def init(plugin_manager, _, _2, _3):
+ """ Init the plugin """
+
+ plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring', pages.CourseTaskListPage)
+ plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring/task/([a-z0-9A-Z\-_]+)', pages.TaskListSubmissionPage)
+ plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring/task/([a-z0-9A-Z\-_]+)/submission/([a-z0-9A-Z\-_]+)',
+ pages.SubmissionRubricPage)
+
+
+
+ plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring_temp', pages.CourseTaskListPageTemp)
+ plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring_temp/task/([a-z0-9A-Z\-_]+)',
+ pages.TaskListSubmissionPageTemp)
+ plugin_manager.add_page(
+ r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring_temp/task/([a-z0-9A-Z\-_]+)/submission/([a-z0-9A-Z\-_]+)',
+ pages.SubmissionRubricPageTemp)
+
+
+
+ plugin_manager.add_hook('course_admin_menu', pages.rubric_course_admin_menu_hook)
+ plugin_manager.add_hook('course_admin_menu', pages.rubric_course_admin_menu_hook_temp)
+
+
+
+
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/pages.py b/inginious/frontend/webapp/plugins/rubric_scoring/pages.py
new file mode 100644
index 00000000..cf87af0c
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/pages.py
@@ -0,0 +1,471 @@
+import web
+import posixpath
+import urllib
+import os
+import collections
+import inginious.frontend.webapp.pages.api._api_page as api
+from .rubric_wdo import RubricWdo
+from bson.objectid import ObjectId
+from pymongo import MongoClient
+import gridfs
+import bson
+
+from inginious.frontend.webapp.pages.api._api_page import APIAuthenticatedPage
+from inginious.frontend.webapp.pages.utils import INGIniousAuthPage, INGIniousPage
+from inginious.frontend.webapp.pages.course_admin.utils import INGIniousAdminPage
+from inginious.common.filesystems.local import LocalFSProvider
+from inginious.common.course_factory import CourseNotFoundException, CourseUnreadableException, InvalidNameException
+
+from collections import OrderedDict
+
+
+_BASE_RENDERER_PATH = 'frontend/webapp/plugins/rubric_scoring'
+_BASE_RENDERER_PATH_TEMP = 'frontend/webapp/plugins/rubric_scoring_temp'
+
+_BASE_STATIC_FOLDER = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static')
+
+def rubric_course_admin_menu_hook_temp(course):
+ return "rubric_scoring_temp", ' Rubric Scoring Temp'
+
+def rubric_course_admin_menu_hook(course):
+ return "rubric_scoring", ' Rubric Scoring'
+
+
+#listado de task
+class CourseTaskListPage(INGIniousAdminPage):
+ """ List informations about all tasks """
+
+ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
+ """ GET request """
+ course, _ = self.get_course_and_check_rights(courseid)
+ return self.page(course)
+
+ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
+ """ POST request """
+ course, _ = self.get_course_and_check_rights(courseid)
+ data = web.input(task=[])
+
+ if "task" in data:
+ # Change tasks order
+ for index, taskid in enumerate(data["task"]):
+ try:
+ task = self.task_factory.get_task_descriptor_content(courseid, taskid)
+ task["order"] = index
+ self.task_factory.update_task_descriptor_content(courseid, taskid, task)
+ except:
+ pass
+
+ return self.page(course)
+
+ def submission_url_generator(self, taskid):
+ """ Generates a submission url """
+ return "?format=taskid%2Fusername&tasks=" + taskid
+
+ def page(self, course):
+ """ Get all data and display the page """
+ url = 'rubric_scoring'
+ data = list(self.database.user_tasks.aggregate(
+ [
+ {
+ "$match":
+ {
+ "courseid": course.get_id(),
+ "username": {"$in": self.user_manager.get_course_registered_users(course, False)}
+ }
+ },
+ {
+ "$group":
+ {
+ "_id": "$taskid",
+ "viewed": {"$sum": 1},
+ "attempted": {"$sum": {"$cond": [{"$ne": ["$tried", 0]}, 1, 0]}},
+ "attempts": {"$sum": "$tried"},
+ "succeeded": {"$sum": {"$cond": ["$succeeded", 1, 0]}}
+ }
+ }
+ ]))
+
+ # Load tasks and verify exceptions
+ files = self.task_factory.get_readable_tasks(course)
+ output = {}
+ errors = []
+ for task in files:
+ try:
+ output[task] = course.get_task(task)
+ except Exception as inst:
+ errors.append({"taskid": task, "error": str(inst)})
+ tasks = OrderedDict(sorted(list(output.items()), key=lambda t: (t[1].get_order(), t[1].get_id())))
+
+ # Now load additional informations
+ result = OrderedDict()
+ for taskid in tasks:
+ result[taskid] = {"name": tasks[taskid].get_name(), "viewed": 0, "attempted": 0, "attempts": 0, "succeeded": 0,
+ "url": self.submission_url_generator(taskid)}
+
+ for entry in data:
+ if entry["_id"] in result:
+ result[entry["_id"]]["viewed"] = entry["viewed"]
+ result[entry["_id"]]["attempted"] = entry["attempted"]
+ result[entry["_id"]]["attempts"] = entry["attempts"]
+ result[entry["_id"]]["succeeded"] = entry["succeeded"]
+
+ return self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_list(course, result, errors, url)
+
+
+#Listado de submissions
+class TaskListSubmissionPage(INGIniousAdminPage):
+ def GET_AUTH(self, course_id, task_id):
+
+ course, task = self.get_course_and_check_rights(course_id, task_id)
+
+
+ self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js")
+ self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js")
+ self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js")
+
+ return self.page(course, task_id, task)
+
+
+ def page(self, course, task_id, task ):
+ """ Get all data and display the page """
+ #print(self.database.collection_names())
+
+ url = 'rubric_scoring'
+
+ result = list(self.database.submissions.aggregate(
+
+ [
+ {
+ "$match":
+ {
+ "courseid": course.get_id(),
+ "taskid": task_id,
+ "username": {"$in": self.user_manager.get_course_registered_users(course, False)},
+
+ }
+ },
+ {
+ "$project": {
+
+ "taskid": 1,
+ "result": 1,
+ "submitted_on": 1,
+ "username": 1,
+ "custom": 1
+
+ }
+ }
+
+ ]))
+
+ #print("result", result)
+ data = OrderedDict()
+
+ for entry in result:
+ data[entry["_id"]] = {"taskid": entry["taskid"], "result": entry["result"], "_id":entry["_id"],
+ "username":entry["username"], "date": entry["submitted_on"]}
+
+ if "rubric_score" not in entry["custom"]:
+ data[entry["_id"]]["rubric_score"] = "not grade"
+
+ else:
+ data[entry["_id"]]["rubric_score"] = entry["custom"]["rubric_score"]
+
+
+ #print ("data -> ",data)
+ return (
+ self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_admin_rubric(
+ course,data, task, url)
+ )
+
+
+
+class SubmissionRubricPage(INGIniousAdminPage):
+
+ def POST_AUTH(self, course_id, task_id, submission_id):
+ """ POST request """
+ course, task = self.get_course_and_check_rights(course_id, task_id)
+ data = web.input()
+
+ self.database.submissions.update(
+ {"_id": ObjectId(submission_id)},
+ {"$set": {"custom.rubric_score": data["grade"]}
+ })
+
+ return self.page(course, task, submission_id)
+
+
+
+ def GET_AUTH(self, course_id, task_id, submission_id):
+ #print("-->", course_id, task_id, submission_id)
+ course, task = self.get_course_and_check_rights(course_id, task_id)
+
+ self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js")
+ self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js")
+ self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js")
+ self.template_helper.add_css("static/css/rubric_scoring.css")
+
+ return self.page(course, task, submission_id)
+
+ def page(self, course, task, submission_id):
+ #print (submission_id, type(submission_id))
+ #print ("////", course)
+
+
+ # TODO: verificar que exista exactamente un elemento. TOMAR MEDIDAS PREVENTIVAS EN CASO CONTRARIO
+ problem_id = task.get_problems()[0].get_id()
+
+
+
+ submission = self.submission_manager.get_submission( submission_id, user_check=False)
+ #print (submission)
+ submission_input = self.submission_manager.get_input_from_submission(submission)
+ print ("submission_code", submission_input)
+ print ("------------")
+
+ language = submission_input['input'][problem_id + '/language']
+ data = {
+ "url": 'rubric_scoring',
+
+ "memory": "not memory",
+ "test_passed": "no test casses",
+ "verdict": "verdict"
+
+ }
+
+ rubric_wdo = RubricWdo("/home/lina/Documents/Rubrica.xlsx")
+
+ return (
+ self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).submission_rubric(
+ course, task, submission_input, problem_id, rubric_wdo, data, language)
+ )
+
+
+#listado de task
+class CourseTaskListPageTemp(INGIniousAdminPage):
+ """ List informations about all tasks """
+
+ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
+ """ GET request """
+ course, _ = self.get_course_and_check_rights(courseid)
+ return self.page(course)
+
+ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
+ """ POST request """
+ course, _ = self.get_course_and_check_rights(courseid)
+ data = web.input(task=[])
+
+ if "task" in data:
+ # Change tasks order
+ for index, taskid in enumerate(data["task"]):
+ try:
+ task = self.task_factory.get_task_descriptor_content(courseid, taskid)
+ task["order"] = index
+ self.task_factory.update_task_descriptor_content(courseid, taskid, task)
+ except:
+ pass
+
+ return self.page(course)
+
+ def submission_url_generator(self, taskid):
+ """ Generates a submission url """
+ return "?format=taskid%2Fusername&tasks=" + taskid
+
+ def page(self, course):
+ """ Get all data and display the page """
+ url = 'rubric_scoring_temp'
+
+ data = list(self.database.user_tasks.aggregate(
+ [
+ {
+ "$match":
+ {
+ "courseid": course.get_id(),
+ "username": {"$in": self.user_manager.get_course_registered_users(course, False)}
+ }
+ },
+ {
+ "$group":
+ {
+ "_id": "$taskid",
+ "viewed": {"$sum": 1},
+ "attempted": {"$sum": {"$cond": [{"$ne": ["$tried", 0]}, 1, 0]}},
+ "attempts": {"$sum": "$tried"},
+ "succeeded": {"$sum": {"$cond": ["$succeeded", 1, 0]}}
+ }
+ }
+ ]))
+
+ # Load tasks and verify exceptions
+ files = self.task_factory.get_readable_tasks(course)
+ output = {}
+ errors = []
+ for task in files:
+ try:
+ output[task] = course.get_task(task)
+ except Exception as inst:
+ errors.append({"taskid": task, "error": str(inst)})
+ tasks = OrderedDict(sorted(list(output.items()), key=lambda t: (t[1].get_order(), t[1].get_id())))
+
+ # Now load additional informations
+ result = OrderedDict()
+ for taskid in tasks:
+ result[taskid] = {"name": tasks[taskid].get_name(), "viewed": 0, "attempted": 0, "attempts": 0, "succeeded": 0,
+ "url": self.submission_url_generator(taskid)}
+
+ for entry in data:
+ if entry["_id"] in result:
+ result[entry["_id"]]["viewed"] = entry["viewed"]
+ result[entry["_id"]]["attempted"] = entry["attempted"]
+ result[entry["_id"]]["attempts"] = entry["attempts"]
+ result[entry["_id"]]["succeeded"] = entry["succeeded"]
+
+ return self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_list(course, result, errors, url)
+
+
+#Listado de submissions
+class TaskListSubmissionPageTemp(INGIniousAdminPage):
+ def GET_AUTH(self, course_id, task_id):
+
+ course, task = self.get_course_and_check_rights(course_id, task_id)
+
+
+ self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js")
+ self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js")
+ self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js")
+
+ return self.page(course, task_id, task)
+
+
+ def page(self, course, task_id, task ):
+ """ Get all data and display the page """
+ #print(self.database.collection_names())
+ print ("here", course.get_id(), task_id)
+ url = 'rubric_scoring_temp'
+
+ result = list(self.database.submissions_rubric.aggregate(
+
+ [
+ {
+ "$match":
+ {
+ "courseid": course.get_id(),
+ "taskid": task_id,
+ "username": {"$in": self.user_manager.get_course_registered_users(course, False)},
+
+ }
+ },
+ {
+ "$project": {
+
+ "taskid": 1,
+ "veredict": 1,
+ "submitted_on": 1,
+ "username": 1,
+ "custom": 1
+
+ }
+ }
+
+ ]))
+
+ print("result", result)
+ data = OrderedDict()
+
+ for entry in result:
+ data[entry["_id"]] = {"taskid": entry["taskid"], "result": entry["veredict"], "_id":entry["_id"],
+ "username":entry["username"], "date": entry["submitted_on"]}
+
+ if "rubric_score" not in entry["custom"]:
+ data[entry["_id"]]["rubric_score"] = "not grade"
+
+ else:
+ data[entry["_id"]]["rubric_score"] = entry["custom"]["rubric_score"]
+
+
+ #print ("data -> ",data)
+ return (
+ self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_admin_rubric(
+ course,data, task, url)
+ )
+
+
+
+class SubmissionRubricPageTemp(INGIniousAdminPage):
+
+ client = MongoClient()
+ database = client['INGInious']
+ fs = gridfs.GridFS(database)
+
+ def POST_AUTH(self, course_id, task_id, submission_id):
+ """ POST request """
+ course, task = self.get_course_and_check_rights(course_id, task_id)
+ data = web.input()
+ print ("data->",data)
+ self.database.submissions_rubric.update(
+ {"_id": ObjectId(submission_id)},
+ {"$set": {"custom.rubric_score": data["grade"]}
+ })
+
+ return self.page(course, task, submission_id)
+
+ def GET_AUTH(self, course_id, task_id, submission_id):
+ #print("-->", course_id, task_id, submission_id)
+ course, task = self.get_course_and_check_rights(course_id, task_id)
+
+ self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js")
+ self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js")
+ self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js")
+ self.template_helper.add_css("static/css/rubric_scoring.css")
+
+ return self.page(course, task, submission_id)
+
+ def get_submission(self, submissionid):
+ """ Get a submission from the database """
+ sub = self.database.submissions_rubric.find_one({'_id': ObjectId(submissionid)})
+
+ return sub
+
+
+
+ def page(self, course, task, submission_id):
+ #print (submission_id, type(submission_id))
+ #print ("////", course)
+
+
+ # TODO: verificar que exista exactamente un elemento. TOMAR MEDIDAS PREVENTIVAS EN CASO CONTRARIO
+ problem_id = task.get_problems()[0].get_id()
+
+
+
+ submission = self.get_submission( submission_id)
+
+ #print ("submission ->>>>", submission)
+
+ #print (submission)
+ submission_input = self.submission_manager.get_input_from_submission(submission)
+ print ("submission_code", submission_input)
+ print ("------------")
+
+ language = submission_input['language']
+
+
+ data = {
+ "url" : 'rubric_scoring_temp',
+
+ "memory": submission_input['memory'],
+ "test_passed": submission_input['test_passed'],
+ "verdict" : submission_input['veredict']
+
+ }
+
+ rubric_wdo = RubricWdo("/home/lina/Documents/Rubrica.xlsx")
+
+
+
+
+ return (
+ self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).submission_rubric(
+ course, task, submission_input, problem_id, rubric_wdo, data, language)
+ )
+
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/rubric_wdo.py b/inginious/frontend/webapp/plugins/rubric_scoring/rubric_wdo.py
new file mode 100644
index 00000000..bc5453b9
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/rubric_wdo.py
@@ -0,0 +1,27 @@
+import pandas as pd
+
+class RubricWdo():
+
+
+ def __init__(self, source):
+ if (source != None and len(source) > 0):
+ self.data = self.read_data(source)
+ else:
+ self.data = self.load_data()
+
+ #read data from source
+ def read_data(self, source):
+ return pd.read_excel(source, index_col=0, header=0)
+
+ def load_data(self):
+ self.grades = ["grade 1", "grade 2", "grade 3"]
+ self.categories = ["cat 1", "cat 2", "cat 3"]
+ data_matrix = [["1","2","3"],["4","5","6"],["7","8","9"]]
+ data = pd.DataFrame(data_matrix, columns=self.grades, index=self.categories)
+
+ return data
+
+
+
+
+
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/static/css/rubric_scoring.css b/inginious/frontend/webapp/plugins/rubric_scoring/static/css/rubric_scoring.css
new file mode 100644
index 00000000..4ae89aa1
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/static/css/rubric_scoring.css
@@ -0,0 +1,7 @@
+.table th.table_rubric
+{
+ padding: 0;
+ border: none;
+
+ background-color: red;
+}
\ No newline at end of file
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/submission_rubric.html b/inginious/frontend/webapp/plugins/rubric_scoring/submission_rubric.html
new file mode 100644
index 00000000..0796fb18
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/submission_rubric.html
@@ -0,0 +1,258 @@
+$def with (course, task_id, submission_code, problem_id, rubrica, data, language)
+
+$var title: $:course.get_name() - Rubric Scoring
+
+$def NavbarF():
+
+ $course.get_name()
+ $task_id.get_id()
+ Rubric Scoring (current)
+
+
+$var Navbar: $:NavbarF()
+
+
+Rubric scoring for Task: $task_id.get_id()
+
+
+
+
+
+
+
+
Source code of the submission
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $for header in rubrica.data.columns.values:
+ $header
+
+
+
+
+ $for index,row in rubrica.data.iterrows():
+
+ $index
+ $for zeld in rubrica.data.columns.values:
+ $row[zeld]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/task_admin_rubric.html b/inginious/frontend/webapp/plugins/rubric_scoring/task_admin_rubric.html
new file mode 100644
index 00000000..27579e4b
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/task_admin_rubric.html
@@ -0,0 +1,45 @@
+$def with (course, data, task, url)
+
+$var title: $:course.get_name() - Rubric Scoring
+
+$def NavbarF():
+
+ $course.get_name()
+ Rubric Scoring (current)
+
+
+$var Navbar: $:NavbarF()
+
+Rubric scoring for Task: $task.get_id()
+
+
+
+
+
+ # submission id
+ # verdict
+ # task
+ # username
+ # date
+ # rubric scoring
+
+
+
+ $for taskid in data:
+
+
+ $data[taskid]["_id"]
+
+
+ $data[taskid]["result"]
+ $data[taskid]["taskid"]
+ $data[taskid]["username"][0]
+ $data[taskid]["date"]
+ $data[taskid]["rubric_score"]
+
+
+
+
+
+
+
diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/task_list.html b/inginious/frontend/webapp/plugins/rubric_scoring/task_list.html
new file mode 100644
index 00000000..e27bf622
--- /dev/null
+++ b/inginious/frontend/webapp/plugins/rubric_scoring/task_list.html
@@ -0,0 +1,105 @@
+$def with (course,data,errors, url)
+
+$#
+$# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
+$# more information about the licensing of this file.
+$#
+
+$var title: $:course.get_name()
+
+$var Column: $:template_helper.call('course_admin_menu',course=course,current='tasks')
+$ is_admin = user_manager.has_admin_rights_on_course(course)
+
+$def NavbarF():
+
+ $course.get_name()
+
+
+ Tasks (current)
+
+$var Navbar: $:NavbarF()
+
+
+Tasks
+
+
+ Change task order
+ Update task order
+
+
+
+
+
+
+
+ task name
+ # student viewed
+ # student attempted
+ # student succeeded
+ # attempts
+
+
+
+
+
+
+
+ $for taskid in data:
+
+
+ $data[taskid]["name"]
+
+
+ $data[taskid]["viewed"]
+ $data[taskid]["attempted"]
+ $data[taskid]["succeeded"]
+ $data[taskid]["attempts"]
+
+
+
+
+
+
+
+
+
+$if len(errors) != 0:
+ Errors while loading tasks
+
+
+ task id
+ error
+
+ $for task in errors:
+
+ $task['taskid']
+ $task['error']
+
+
+
+
\ No newline at end of file
diff --git a/inginious/frontend/webapp/static/css/INGInious.less b/inginious/frontend/webapp/static/css/INGInious.less
index 89f1841c..5155c2ed 100644
--- a/inginious/frontend/webapp/static/css/INGInious.less
+++ b/inginious/frontend/webapp/static/css/INGInious.less
@@ -207,7 +207,7 @@ html, body
min-height: 100%;
height: auto !important;
height: 100%;
- margin: 0 auto -81px;
+ margin: 0 auto 0;
}
#footer, .footer_push