mirror of https://github.com/JohnHammond/CTFd.git
challenge update modal is now replaceable (#236)
* challenge update modal is now replaceable By defining * [type]-challenge-update.hbs * [type]-challenge-modals.hbs * [type]-challenge-update.js in the /static/admin/js/templates/challenges/[type] folder the challenge update modal will be defined for any challenges of the given type. This allows for essentially full customizability of how you will edit custom challenge types in the admin UI. The reason for having two files, *update.hbs and *modals.hbs, is that *update.hbs defines the body for the main challenge update modal, while *modals.hbs defines any additional modals which will be used within the main modal There is one function which is required in *update.js is `openchal(id)` which will be passed the id of the challenge to be edited and should open the modal as well as load any needed data * fixed multi-modal issues Issues were coming from two sources: * I had placed the modals in an indirect relationship in the DOM tree. They need to be siblings I now see * There was double counting of modals within multi-modal.js. This only started to appear with the dynamically loaded modals. I fixed the script to accurately count modals each timeselenium-screenshot-testing
parent
b027703f80
commit
fd22ef98dc
|
@ -62,3 +62,4 @@ CTFd/static/uploads
|
|||
CTFd/uploads
|
||||
.data/
|
||||
.ctfd_secret_key
|
||||
.*.swp
|
||||
|
|
|
@ -25,7 +25,7 @@ def admin_chal_types():
|
|||
@admins_only
|
||||
def admin_chals():
|
||||
if request.method == 'POST':
|
||||
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category', 'hidden', 'max_attempts').order_by(Challenges.value).all()
|
||||
chals = Challenges.query.add_columns('id', 'type', 'name', 'value', 'description', 'category', 'hidden', 'max_attempts').order_by(Challenges.value).all()
|
||||
|
||||
teams_with_points = db.session.query(Solves.teamid).join(Teams).filter(
|
||||
Teams.banned == False).group_by(Solves.teamid).count()
|
||||
|
@ -39,6 +39,9 @@ def admin_chals():
|
|||
else:
|
||||
percentage = 0.0
|
||||
|
||||
type_class = CHALLENGE_CLASSES.get(x.type)
|
||||
type_name = type_class.name if type_class else None
|
||||
|
||||
json_data['game'].append({
|
||||
'id': x.id,
|
||||
'name': x.name,
|
||||
|
@ -47,6 +50,8 @@ def admin_chals():
|
|||
'category': x.category,
|
||||
'hidden': x.hidden,
|
||||
'max_attempts': x.max_attempts,
|
||||
'type': x.type,
|
||||
'type_name': type_name,
|
||||
'percentage_solved': percentage
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
function load_chal_template(chal_type_name){
|
||||
$.get(script_root + '/static/admin/js/templates/challenges/'+ chal_type_name +'/' + chal_type_name + '-challenge-create.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$("#create-chal-entry-div").html(template({'nonce':nonce, 'script_root':script_root}));
|
||||
$.getScript(script_root + '/static/admin/js/templates/challenges/'+chal_type_name+'/'+chal_type_name+'-challenge-create.js', function(){
|
||||
console.log('loaded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
nonce = "{{ nonce }}";
|
||||
$.get(script_root + '/admin/chal_types', function(data){
|
||||
console.log(data);
|
||||
$("#create-chals-select").empty();
|
||||
var chal_type_amt = Object.keys(data).length;
|
||||
if (chal_type_amt > 1){
|
||||
var option = "<option> -- </option>";
|
||||
$("#create-chals-select").append(option);
|
||||
for (var key in data){
|
||||
var option = "<option value='{0}'>{1}</option>".format(key, data[key]);
|
||||
$("#create-chals-select").append(option);
|
||||
}
|
||||
} else if (chal_type_amt == 1) {
|
||||
var key = Object.keys(data)[0];
|
||||
$("#create-chals-select").parent().parent().parent().empty();
|
||||
load_chal_template(data[key]);
|
||||
}
|
||||
});
|
||||
$('#create-chals-select').change(function(){
|
||||
var chal_type_name = $(this).find("option:selected").text();
|
||||
load_chal_template(chal_type_name);
|
||||
});
|
|
@ -38,255 +38,25 @@ function load_edit_key_modal(key_id, key_type_name) {
|
|||
});
|
||||
}
|
||||
|
||||
function load_hint_modal(method, hintid){
|
||||
$('#hint-modal-hint').val('');
|
||||
$('#hint-modal-cost').val('');
|
||||
if (method == 'create'){
|
||||
$('#hint-modal-submit').attr('action', '/admin/hints');
|
||||
$('#hint-modal-title').text('Create Hint');
|
||||
$("#hint-modal").modal();
|
||||
} else if (method == 'update'){
|
||||
$.get(script_root + '/admin/hints/' + hintid, function(data){
|
||||
$('#hint-modal-submit').attr('action', '/admin/hints/' + hintid);
|
||||
$('#hint-modal-hint').val(data.hint);
|
||||
$('#hint-modal-cost').val(data.cost);
|
||||
$('#hint-modal-title').text('Update Hint');
|
||||
$("#hint-modal-button").text('Update Hint');
|
||||
$("#hint-modal").modal();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadchal(id, update) {
|
||||
// $('#chal *').show()
|
||||
// $('#chal > h1').hide()
|
||||
function load_chal_template(id, success_cb){
|
||||
obj = $.grep(challenges['game'], function (e) {
|
||||
return e.id == id;
|
||||
})[0]
|
||||
$('#desc-write-link').click() // Switch to Write tab
|
||||
$('.chal-title').text(obj.name);
|
||||
$('.chal-name').val(obj.name);
|
||||
$('.chal-desc').val(obj.description);
|
||||
$('.chal-value').val(obj.value);
|
||||
if (parseInt(obj.max_attempts) > 0){
|
||||
$('.chal-attempts').val(obj.max_attempts);
|
||||
$('#limit_max_attempts').prop('checked', true);
|
||||
$('#chal-attempts-group').show();
|
||||
}
|
||||
$('.chal-category').val(obj.category);
|
||||
$('.chal-id').val(obj.id);
|
||||
$('.chal-hidden').prop('checked', false);
|
||||
if (obj.hidden) {
|
||||
$('.chal-hidden').prop('checked', true);
|
||||
}
|
||||
//$('#update-challenge .chal-delete').attr({
|
||||
// 'href': '/admin/chal/close/' + (id + 1)
|
||||
//})
|
||||
if (typeof update === 'undefined')
|
||||
$('#update-challenge').modal();
|
||||
}
|
||||
|
||||
function submitkey(chal, key) {
|
||||
$.post(script_root + "/admin/chal/" + chal, {
|
||||
key: key,
|
||||
nonce: $('#nonce').val()
|
||||
}, function (data) {
|
||||
alert(data)
|
||||
})
|
||||
}
|
||||
|
||||
function create_key(chal, key, key_type) {
|
||||
$.post(script_root + "/admin/keys", {
|
||||
chal: chal,
|
||||
key: key,
|
||||
key_type: key_type,
|
||||
nonce: $('#nonce').val()
|
||||
}, function (data) {
|
||||
if (data == "1"){
|
||||
loadkeys(chal);
|
||||
$("#create-keys").modal('toggle');
|
||||
}
|
||||
$.get(script_root + '/static/admin/js/templates/challenges/'+ obj['type_name'] +'/' + obj['type_name'] + '-challenge-update.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$.get(script_root + '/static/admin/js/templates/challenges/'+ obj['type_name'] +'/' + obj['type_name'] + '-challenge-modals.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$("#update-modals-entry-div").html(template({'nonce':$('#nonce').val(), 'script_root':script_root}));
|
||||
});
|
||||
}
|
||||
|
||||
function loadkeys(chal){
|
||||
$.get(script_root + '/admin/chal/' + chal + '/keys', function(data){
|
||||
$('#keys-chal').val(chal);
|
||||
keys = $.parseJSON(JSON.stringify(data));
|
||||
keys = keys['keys'];
|
||||
$('#current-keys').empty();
|
||||
$.get(script_root + "/static/admin/js/templates/admin-keys-table.hbs", function(data){
|
||||
var template = Handlebars.compile(data);
|
||||
var wrapper = {keys: keys, script_root: script_root};
|
||||
$('#current-keys').append(template(wrapper));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updatekeys(){
|
||||
keys = [];
|
||||
vals = [];
|
||||
chal = $('#keys-chal').val()
|
||||
$('.current-key').each(function(){
|
||||
keys.push($(this).val());
|
||||
})
|
||||
$('#current-keys input[name*="key_type"]:checked').each(function(){
|
||||
vals.push($(this).val());
|
||||
})
|
||||
$.post(script_root + '/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
|
||||
loadchal(chal, true)
|
||||
$('#update-keys').modal('hide');
|
||||
}
|
||||
|
||||
|
||||
function deletekey(key_id){
|
||||
$.post(script_root + '/admin/keys/'+key_id+'/delete', {'nonce': $('#nonce').val()}, function(data){
|
||||
if (data == "1") {
|
||||
$('tr[name={0}]'.format(key_id)).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatekey(){
|
||||
var key_id = $('#key-id').val();
|
||||
var chal = $('#keys-chal').val();
|
||||
var key_data = $('#key-data').val();
|
||||
var key_type = $('#key-type').val();
|
||||
var nonce = $('#nonce').val();
|
||||
$.post(script_root + '/admin/keys/'+key_id, {
|
||||
'chal':chal,
|
||||
'key':key_data,
|
||||
'key_type': key_type,
|
||||
'nonce': nonce
|
||||
}, function(data){
|
||||
if (data == "1") {
|
||||
loadkeys(chal);
|
||||
$('#edit-keys').modal('toggle');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadtags(chal){
|
||||
$('#tags-chal').val(chal)
|
||||
$('#current-tags').empty()
|
||||
$('#chal-tags').empty()
|
||||
$.get(script_root + '/admin/tags/'+chal, function(data){
|
||||
tags = $.parseJSON(JSON.stringify(data))
|
||||
tags = tags['tags']
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
tag = "<span class='label label-primary chal-tag'><span>"+tags[i].tag+"</span><a name='"+tags[i].id+"'' class='delete-tag'>×</a></span>"
|
||||
$('#current-tags').append(tag)
|
||||
};
|
||||
$('.delete-tag').click(function(e){
|
||||
deletetag(e.target.name)
|
||||
$(e.target).parent().remove()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deletetag(tagid){
|
||||
$.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
||||
}
|
||||
|
||||
|
||||
function edithint(hintid){
|
||||
$.get(script_root + '/admin/hints/' + hintid, function(data){
|
||||
console.log(data);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function deletehint(hintid){
|
||||
$.delete(script_root + '/admin/hints/' + hintid, function(data, textStatus, jqXHR){
|
||||
if (jqXHR.status == 204){
|
||||
var chalid = $('.chal-id').val();
|
||||
loadhints(chalid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadhints(chal){
|
||||
$.get(script_root + '/admin/chal/{0}/hints'.format(chal), function(data){
|
||||
var table = $('#hintsboard > tbody');
|
||||
table.empty();
|
||||
for (var i = 0; i < data.hints.length; i++) {
|
||||
var hint = data.hints[i]
|
||||
var hint_row = "<tr>" +
|
||||
"<td class='hint-entry'>{0}</td>".format(hint.hint) +
|
||||
"<td class='hint-cost'>{0}</td>".format(hint.cost) +
|
||||
"<td class='hint-settings'><span>" +
|
||||
"<i role='button' class='fa fa-pencil-square-o' onclick=javascript:load_hint_modal('update',{0})></i>".format(hint.id)+
|
||||
"<i role='button' class='fa fa-times' onclick=javascript:deletehint({0})></i>".format(hint.id)+
|
||||
"</span></td>" +
|
||||
"</tr>";
|
||||
table.append(hint_row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function deletechal(chalid){
|
||||
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
||||
}
|
||||
|
||||
function updatetags(){
|
||||
tags = [];
|
||||
chal = $('#tags-chal').val()
|
||||
$('#chal-tags > span > span').each(function(i, e){
|
||||
tags.push($(e).text())
|
||||
});
|
||||
$.post(script_root + '/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
|
||||
loadchal(chal)
|
||||
}
|
||||
|
||||
function updatefiles(){
|
||||
chal = $('#files-chal').val();
|
||||
var form = $('#update-files form')[0];
|
||||
var formData = new FormData(form);
|
||||
$.ajax({
|
||||
url: script_root + '/admin/files/'+chal,
|
||||
data: formData,
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(data){
|
||||
form.reset();
|
||||
loadfiles(chal);
|
||||
$('#update-files').modal('hide');
|
||||
}
|
||||
url: script_root + '/static/admin/js/templates/challenges/'+obj['type_name']+'/'+obj['type_name']+'-challenge-update.js',
|
||||
dataType: "script",
|
||||
success: success_cb,
|
||||
cache: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadfiles(chal){
|
||||
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
|
||||
$.get(script_root + '/admin/files/' + chal, function(data){
|
||||
$('#files-chal').val(chal)
|
||||
files = $.parseJSON(JSON.stringify(data));
|
||||
files = files['files']
|
||||
$('#current-files').empty()
|
||||
for(x=0; x<files.length; x++){
|
||||
filename = files[x].file.split('/')
|
||||
filename = filename[filename.length - 1]
|
||||
$('#current-files').append('<div class="row" style="margin:5px 0px;">'+'<a style="position:relative;top:10px;" href='+script_root+'/files/'+files[x].file+'>'+filename+'</a><a href="#" class="btn btn-danger" onclick="deletefile('+chal+','+files[x].id+', $(this))" value="'+files[x].id+'" style="float:right;">Delete</a></div>')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deletefile(chal, file, elem){
|
||||
$.post(script_root + '/admin/files/' + chal,{
|
||||
'nonce': $('#nonce').val(),
|
||||
'method': 'delete',
|
||||
'file': file
|
||||
}, function (data){
|
||||
if (data == "1") {
|
||||
elem.parent().remove()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadchals(){
|
||||
$('#challenges').empty();
|
||||
$.post(script_root + "/admin/chals", {
|
||||
|
@ -310,11 +80,10 @@ function loadchals(){
|
|||
};
|
||||
|
||||
$('#challenges button').click(function (e) {
|
||||
loadchal(this.value);
|
||||
loadkeys(this.value);
|
||||
loadhints(this.value);
|
||||
loadtags(this.value);
|
||||
loadfiles(this.value);
|
||||
id = this.value
|
||||
load_chal_template(id, function(){
|
||||
openchal(id);
|
||||
});
|
||||
});
|
||||
|
||||
// $('.create-challenge').click(function (e) {
|
||||
|
@ -326,130 +95,6 @@ function loadchals(){
|
|||
});
|
||||
}
|
||||
|
||||
$('#submit-key').click(function (e) {
|
||||
submitkey($('#chalid').val(), $('#answer').val())
|
||||
});
|
||||
|
||||
$('#submit-keys').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('#update-keys').modal('hide');
|
||||
});
|
||||
|
||||
$('#submit-tags').click(function (e) {
|
||||
e.preventDefault();
|
||||
updatetags()
|
||||
});
|
||||
|
||||
$('#submit-files').click(function (e) {
|
||||
e.preventDefault();
|
||||
updatefiles()
|
||||
});
|
||||
|
||||
$('#delete-chal form').submit(function(e){
|
||||
e.preventDefault();
|
||||
$.post(script_root + '/admin/chal/delete', $(this).serialize(), function(data){
|
||||
console.log(data)
|
||||
if (data){
|
||||
loadchals();
|
||||
}
|
||||
else {
|
||||
alert('There was an error');
|
||||
}
|
||||
})
|
||||
$("#delete-chal").modal("hide");
|
||||
$("#update-challenge").modal("hide");
|
||||
});
|
||||
|
||||
$(".tag-insert").keyup(function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
tag = $('.tag-insert').val()
|
||||
tag = tag.replace(/'/g, '');
|
||||
if (tag.length > 0){
|
||||
tag = "<span class='label label-primary chal-tag'><span>"+tag+"</span><a class='delete-tag' onclick='$(this).parent().remove()'>×</a></span>"
|
||||
$('#chal-tags').append(tag)
|
||||
}
|
||||
$('.tag-insert').val("")
|
||||
}
|
||||
});
|
||||
|
||||
$('#limit_max_attempts').change(function() {
|
||||
if(this.checked) {
|
||||
$('#chal-attempts-group').show();
|
||||
} else {
|
||||
$('#chal-attempts-group').hide();
|
||||
$('#chal-attempts-input').val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Markdown Preview
|
||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#desc-preview'){
|
||||
$(event.target.hash).html(marked($('#desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#new-desc-preview'){
|
||||
$(event.target.hash).html(marked($('#new-desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
|
||||
// Open New Challenge modal when New Challenge button is clicked
|
||||
// $('.create-challenge').click(function (e) {
|
||||
// $('#create-challenge').modal();
|
||||
// });
|
||||
|
||||
|
||||
$('#create-key').click(function(e){
|
||||
$.get(script_root + '/admin/key_types', function(data){
|
||||
$("#create-keys-select").empty();
|
||||
var option = "<option> -- </option>";
|
||||
$("#create-keys-select").append(option);
|
||||
for (var key in data){
|
||||
var option = "<option value='{0}'>{1}</option>".format(key, data[key]);
|
||||
$("#create-keys-select").append(option);
|
||||
}
|
||||
$("#create-keys").modal();
|
||||
});
|
||||
});
|
||||
|
||||
$('#create-keys-select').change(function(){
|
||||
var key_type_name = $(this).find("option:selected").text();
|
||||
|
||||
$.get(script_root + '/static/admin/js/templates/keys/'+key_type_name +'/'+key_type_name+'.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$("#create-keys-entry-div").html(template());
|
||||
$("#create-keys-button-div").show();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('#create-keys-submit').click(function (e) {
|
||||
e.preventDefault();
|
||||
var chalid = $('#create-keys').find('.chal-id').val();
|
||||
var key_data = $('#create-keys').find('input[name=key]').val();
|
||||
var key_type = $('#create-keys-select').val();
|
||||
create_key(chalid, key_data, key_type);
|
||||
});
|
||||
|
||||
|
||||
$('#create-hint').click(function(e){
|
||||
e.preventDefault();
|
||||
load_hint_modal('create');
|
||||
});
|
||||
|
||||
$('#hint-modal-submit').submit(function (e) {
|
||||
e.preventDefault();
|
||||
var params = {}
|
||||
$(this).serializeArray().map(function(x){
|
||||
params[x.name] = x.value;
|
||||
});
|
||||
$.post(script_root + $(this).attr('action'), params, function(data){
|
||||
loadhints(params['chal']);
|
||||
});
|
||||
$("#hint-modal").modal('hide');
|
||||
});
|
||||
|
||||
|
||||
$(function(){
|
||||
loadchals();
|
||||
})
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
var MultiModal = function(element) {
|
||||
this.$element = $(element);
|
||||
this.modalCount = 0;
|
||||
};
|
||||
|
||||
MultiModal.BASE_ZINDEX = 1040;
|
||||
|
@ -11,30 +10,32 @@
|
|||
MultiModal.prototype.show = function(target) {
|
||||
var that = this;
|
||||
var $target = $(target);
|
||||
var modalIndex = that.modalCount++;
|
||||
var modalCount = $('.modal:visible').length;
|
||||
|
||||
$target.css('z-index', MultiModal.BASE_ZINDEX + (modalIndex * 20) + 10);
|
||||
$target.css('z-index', MultiModal.BASE_ZINDEX + (modalCount * 20) + 10);
|
||||
|
||||
window.setTimeout(function() {
|
||||
if(modalIndex > 0)
|
||||
var modalCount = $('.modal:visible').length;
|
||||
if(modalCount > 0)
|
||||
$('.modal-backdrop').not(':first').addClass('hidden');
|
||||
|
||||
that.adjustBackdrop();
|
||||
that.adjustBackdrop(modalCount);
|
||||
});
|
||||
};
|
||||
|
||||
MultiModal.prototype.hidden = function(target) {
|
||||
this.modalCount--;
|
||||
var modalCount = $('.modal:visible').length;
|
||||
|
||||
if(this.modalCount) {
|
||||
this.adjustBackdrop();
|
||||
var $target = $(target);
|
||||
|
||||
if(modalCount) {
|
||||
this.adjustBackdrop(modalCount - 1);
|
||||
$('body').addClass('modal-open');
|
||||
}
|
||||
};
|
||||
|
||||
MultiModal.prototype.adjustBackdrop = function() {
|
||||
var modalIndex = this.modalCount - 1;
|
||||
$('.modal-backdrop:first').css('z-index', MultiModal.BASE_ZINDEX + (modalIndex * 20));
|
||||
MultiModal.prototype.adjustBackdrop = function(modalCount) {
|
||||
$('.modal-backdrop:first').css('z-index', MultiModal.BASE_ZINDEX + ((modalCount)* 20));
|
||||
};
|
||||
|
||||
function Plugin(method, target) {
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
<div id="update-challenge" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 class="chal-title text-center"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/update">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control chal-name" name="name" placeholder="Enter challenge name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="category">Category</label>
|
||||
<input type="text" class="form-control chal-category" name="category" placeholder="Enter challenge category">
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="desc-edit">
|
||||
<li role="presentation" class="active"><a href="#desc-write" id="desc-write-link" aria-controls="home" role="tab" data-toggle="tab">Write</a></li>
|
||||
<li role="presentation"><a href="#desc-preview" aria-controls="home" role="tab" data-toggle="tab">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="desc-write">
|
||||
<div class="form-group">
|
||||
<label for="message-text" class="control-label">Message:</label>
|
||||
<textarea id="desc-editor" class="form-control chal-desc" name="desc" rows="10"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane content" id="desc-preview">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="value">Value</label>
|
||||
<input type="number" class="form-control chal-value" name="value" placeholder="Enter value" required>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input class="chal-attempts-checkbox" id="limit_max_attempts" name="limit_max_attempts" type="checkbox">
|
||||
Limit challenge attempts
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="chal-attempts-group" style="display:none;">
|
||||
<label for="value">Max Attempts</label>
|
||||
<input type="number" class="form-control chal-attempts" id="chal-attempts-input" name="max_attempts" placeholder="Enter value">
|
||||
</div>
|
||||
<input class="chal-id" type='hidden' name='id' placeholder='ID'>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input class="chal-hidden" name="hidden" type="checkbox">
|
||||
Hidden
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<a href="#" data-toggle="modal" data-target="#update-tags" class="btn btn-primary">Tags</a>
|
||||
<a href="#" data-toggle="modal" data-target="#update-files" class="btn btn-primary">Files</a>
|
||||
<a href="#" data-toggle="modal" data-target="#update-hints" class="btn btn-primary">Hints</a>
|
||||
<a href="#" data-toggle="modal" data-target="#update-keys" class="btn btn-primary">Keys</a>
|
||||
<a href="#" data-toggle="modal" data-target="#delete-chal" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<div style="text-align:center">
|
||||
<button class="btn btn-theme btn-outlined update-challenge-submit" type="submit">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-tags" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Tags</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="tag-insert">Value</label>
|
||||
<input max-length="80" type="text" class="form-control tag-insert" name="tag-insert" placeholder="Type tag and press Enter">
|
||||
</div>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="tags-chal" name='chal' type='hidden'>
|
||||
|
||||
<div id="current-tags">
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<div id="chal-tags">
|
||||
</div>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="submit-tags">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-files" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Files</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/files/" enctype="multipart/form-data">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="files-chal" name='chal' type='hidden'>
|
||||
<input name='method' type='hidden' value='upload'>
|
||||
|
||||
<div id="current-files"></div>
|
||||
<input type="hidden" name="method" value="upload">
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="submit-files">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-hints" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Hints</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" style="text-align:center">
|
||||
<a href="#" id="create-hint" class="btn btn-primary" style="margin-bottom:15px;">New Hint</a>
|
||||
</div>
|
||||
<div class='current-hints'>
|
||||
<table id="hintsboard" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center"><b>Hint</b></td>
|
||||
<td class="text-center"><b>Cost</b></td>
|
||||
<td class="text-center"><b>Settings</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-center">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Keys</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/keys" style="text-align:center">
|
||||
<a href="#" id="create-key" class="btn btn-primary" style="margin-bottom:15px;">New Key</a>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="keys-chal" name='chal' type='hidden'>
|
||||
<div id="current-keys" class="row"></div>
|
||||
<div class="row">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button id="submit-keys" class="btn btn-theme btn-outlined">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="delete-chal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Delete Challenge</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/delete">
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||
<input type="hidden" name="id" class="chal-id">
|
||||
<div class="small-6 small-centered text-center columns">
|
||||
<p>Are you sure you want to delete this challenge?</p>
|
||||
<p>Solves, wrong keys, files, tags will all be deleted.</p>
|
||||
<a onclick="$('#delete-chal').modal('hide')" class="btn btn-primary">No</a>
|
||||
<button class="btn btn-danger" id="delete-user" type="submit">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="update-tags" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Tags</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="tag-insert">Value</label>
|
||||
<input max-length="80" type="text" class="form-control tag-insert" name="tag-insert" placeholder="Type tag and press Enter">
|
||||
</div>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="tags-chal" name='chal' type='hidden'>
|
||||
|
||||
<div id="current-tags">
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<div id="chal-tags">
|
||||
</div>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="submit-tags">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-files" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Files</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/files/" enctype="multipart/form-data">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="files-chal" name='chal' type='hidden'>
|
||||
<input name='method' type='hidden' value='upload'>
|
||||
|
||||
<div id="current-files"></div>
|
||||
<input type="hidden" name="method" value="upload">
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
<sub class="help-block">Attach ultiple files using Control+Click or Cmd+Click.</sub>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="submit-files">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-hints" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Hints</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" style="text-align:center">
|
||||
<a href="#" id="create-hint" class="btn btn-primary" style="margin-bottom:15px;">New Hint</a>
|
||||
</div>
|
||||
<div class='current-hints'>
|
||||
<table id="hintsboard" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center"><b>Hint</b></td>
|
||||
<td class="text-center"><b>Cost</b></td>
|
||||
<td class="text-center"><b>Settings</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-center">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Keys</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/keys" style="text-align:center">
|
||||
<a href="#" id="create-key" class="btn btn-primary" style="margin-bottom:15px;">New Key</a>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="keys-chal" name='chal' type='hidden'>
|
||||
<div id="current-keys" class="row"></div>
|
||||
<div class="row">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button id="submit-keys" class="btn btn-theme btn-outlined">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="delete-chal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Delete Challenge</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/delete">
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||
<input type="hidden" name="id" class="chal-id">
|
||||
<div class="small-6 small-centered text-center columns">
|
||||
<p>Are you sure you want to delete this challenge?</p>
|
||||
<p>Solves, wrong keys, files, tags will all be deleted.</p>
|
||||
<a onclick="$('#delete-chal').modal('hide')" class="btn btn-primary">No</a>
|
||||
<button class="btn btn-danger" id="delete-user" type="submit">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-challenge" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 class="chal-title text-center"></h3>
|
||||
</div>
|
||||
<div id="update-chal-entry-div" class="modal-body">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="create-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<input type="hidden" class="chal-id" name="chal-id">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Create Key</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="create-keys-select-div">
|
||||
<label for="create-keys-select" class="control-label">Choose Key Type</label>
|
||||
<select class="form-control" id="create-keys-select">
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<div id="create-keys-entry-div">
|
||||
</div>
|
||||
<br>
|
||||
<div style="text-align:center;display:none;" id="create-keys-button-div">
|
||||
<button id="create-keys-submit" class="btn btn-theme btn-outlined">Create Key</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-keys" class="modal fade" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<div id="hint-modal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 id="hint-modal-title"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="hint-modal-submit" method='POST'>
|
||||
<input type="hidden" class="chal-id" name="chal">
|
||||
<div class="form-group">
|
||||
<textarea id="hint-modal-hint" type="text" class="form-control" name="hint" placeholder="Hint"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="hint-modal-cost" type="number" class="form-control" name="cost" placeholder="Cost">
|
||||
</div>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="hint-modal-button">Add Hint</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,390 @@
|
|||
//http://stackoverflow.com/a/2648463 - wizardry!
|
||||
String.prototype.format = String.prototype.f = function() {
|
||||
var s = this,
|
||||
i = arguments.length;
|
||||
|
||||
while (i--) {
|
||||
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
function load_hint_modal(method, hintid){
|
||||
$('#hint-modal-hint').val('');
|
||||
$('#hint-modal-cost').val('');
|
||||
if (method == 'create'){
|
||||
$('#hint-modal-submit').attr('action', '/admin/hints');
|
||||
$('#hint-modal-title').text('Create Hint');
|
||||
$("#hint-modal").modal();
|
||||
} else if (method == 'update'){
|
||||
$.get(script_root + '/admin/hints/' + hintid, function(data){
|
||||
$('#hint-modal-submit').attr('action', '/admin/hints/' + hintid);
|
||||
$('#hint-modal-hint').val(data.hint);
|
||||
$('#hint-modal-cost').val(data.cost);
|
||||
$('#hint-modal-title').text('Update Hint');
|
||||
$("#hint-modal-button").text('Update Hint');
|
||||
$("#hint-modal").modal();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function submitkey(chal, key) {
|
||||
$.post(script_root + "/admin/chal/" + chal, {
|
||||
key: key,
|
||||
nonce: $('#nonce').val()
|
||||
}, function (data) {
|
||||
alert(data)
|
||||
})
|
||||
}
|
||||
|
||||
function create_key(chal, key, key_type) {
|
||||
$.post(script_root + "/admin/keys", {
|
||||
chal: chal,
|
||||
key: key,
|
||||
key_type: key_type,
|
||||
nonce: $('#nonce').val()
|
||||
}, function (data) {
|
||||
if (data == "1"){
|
||||
loadkeys(chal);
|
||||
$("#create-keys").modal('toggle');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadkeys(chal){
|
||||
$.get(script_root + '/admin/chal/' + chal + '/keys', function(data){
|
||||
$('#keys-chal').val(chal);
|
||||
keys = $.parseJSON(JSON.stringify(data));
|
||||
keys = keys['keys'];
|
||||
$('#current-keys').empty();
|
||||
$.get(script_root + "/static/admin/js/templates/admin-keys-table.hbs", function(data){
|
||||
var template = Handlebars.compile(data);
|
||||
var wrapper = {keys: keys, script_root: script_root};
|
||||
$('#current-keys').append(template(wrapper));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updatekeys(){
|
||||
keys = [];
|
||||
vals = [];
|
||||
chal = $('#keys-chal').val()
|
||||
$('.current-key').each(function(){
|
||||
keys.push($(this).val());
|
||||
})
|
||||
$('#current-keys input[name*="key_type"]:checked').each(function(){
|
||||
vals.push($(this).val());
|
||||
})
|
||||
$.post(script_root + '/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
|
||||
loadchal(chal, true)
|
||||
$('#update-keys').modal('hide');
|
||||
}
|
||||
|
||||
|
||||
function deletekey(key_id){
|
||||
$.post(script_root + '/admin/keys/'+key_id+'/delete', {'nonce': $('#nonce').val()}, function(data){
|
||||
if (data == "1") {
|
||||
$('tr[name={0}]'.format(key_id)).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatekey(){
|
||||
var key_id = $('#key-id').val();
|
||||
var chal = $('#keys-chal').val();
|
||||
var key_data = $('#key-data').val();
|
||||
var key_type = $('#key-type').val();
|
||||
var nonce = $('#nonce').val();
|
||||
$.post(script_root + '/admin/keys/'+key_id, {
|
||||
'chal':chal,
|
||||
'key':key_data,
|
||||
'key_type': key_type,
|
||||
'nonce': nonce
|
||||
}, function(data){
|
||||
if (data == "1") {
|
||||
loadkeys(chal);
|
||||
$('#edit-keys').modal('toggle');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadtags(chal){
|
||||
$('#tags-chal').val(chal)
|
||||
$('#current-tags').empty()
|
||||
$('#chal-tags').empty()
|
||||
$.get(script_root + '/admin/tags/'+chal, function(data){
|
||||
tags = $.parseJSON(JSON.stringify(data))
|
||||
tags = tags['tags']
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
tag = "<span class='label label-primary chal-tag'><span>"+tags[i].tag+"</span><a name='"+tags[i].id+"'' class='delete-tag'>×</a></span>"
|
||||
$('#current-tags').append(tag)
|
||||
};
|
||||
$('.delete-tag').click(function(e){
|
||||
deletetag(e.target.name)
|
||||
$(e.target).parent().remove()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deletetag(tagid){
|
||||
$.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
||||
}
|
||||
|
||||
|
||||
function edithint(hintid){
|
||||
$.get(script_root + '/admin/hints/' + hintid, function(data){
|
||||
console.log(data);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function deletehint(hintid){
|
||||
$.delete(script_root + '/admin/hints/' + hintid, function(data, textStatus, jqXHR){
|
||||
if (jqXHR.status == 204){
|
||||
var chalid = $('.chal-id').val();
|
||||
loadhints(chalid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadhints(chal){
|
||||
$.get(script_root + '/admin/chal/{0}/hints'.format(chal), function(data){
|
||||
var table = $('#hintsboard > tbody');
|
||||
table.empty();
|
||||
for (var i = 0; i < data.hints.length; i++) {
|
||||
var hint = data.hints[i]
|
||||
var hint_row = "<tr>" +
|
||||
"<td class='hint-entry'>{0}</td>".format(hint.hint) +
|
||||
"<td class='hint-cost'>{0}</td>".format(hint.cost) +
|
||||
"<td class='hint-settings'><span>" +
|
||||
"<i role='button' class='fa fa-pencil-square-o' onclick=javascript:load_hint_modal('update',{0})></i>".format(hint.id)+
|
||||
"<i role='button' class='fa fa-times' onclick=javascript:deletehint({0})></i>".format(hint.id)+
|
||||
"</span></td>" +
|
||||
"</tr>";
|
||||
table.append(hint_row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function deletechal(chalid){
|
||||
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
||||
}
|
||||
|
||||
function updatetags(){
|
||||
tags = [];
|
||||
chal = $('#tags-chal').val()
|
||||
$('#chal-tags > span > span').each(function(i, e){
|
||||
tags.push($(e).text())
|
||||
});
|
||||
$.post(script_root + '/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
|
||||
loadchal(chal)
|
||||
}
|
||||
|
||||
function updatefiles(){
|
||||
chal = $('#files-chal').val();
|
||||
var form = $('#update-files form')[0];
|
||||
var formData = new FormData(form);
|
||||
$.ajax({
|
||||
url: script_root + '/admin/files/'+chal,
|
||||
data: formData,
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(data){
|
||||
form.reset();
|
||||
loadfiles(chal);
|
||||
$('#update-files').modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadfiles(chal){
|
||||
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
|
||||
$.get(script_root + '/admin/files/' + chal, function(data){
|
||||
$('#files-chal').val(chal)
|
||||
files = $.parseJSON(JSON.stringify(data));
|
||||
files = files['files']
|
||||
$('#current-files').empty()
|
||||
for(x=0; x<files.length; x++){
|
||||
filename = files[x].file.split('/')
|
||||
filename = filename[filename.length - 1]
|
||||
$('#current-files').append('<div class="row" style="margin:5px 0px;">'+'<a style="position:relative;top:10px;" href='+script_root+'/files/'+files[x].file+'>'+filename+'</a><a href="#" class="btn btn-danger" onclick="deletefile('+chal+','+files[x].id+', $(this))" value="'+files[x].id+'" style="float:right;">Delete</a></div>')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deletefile(chal, file, elem){
|
||||
$.post(script_root + '/admin/files/' + chal,{
|
||||
'nonce': $('#nonce').val(),
|
||||
'method': 'delete',
|
||||
'file': file
|
||||
}, function (data){
|
||||
if (data == "1") {
|
||||
elem.parent().remove()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#submit-key').click(function (e) {
|
||||
submitkey($('#chalid').val(), $('#answer').val())
|
||||
});
|
||||
|
||||
$('#submit-keys').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('#update-keys').modal('hide');
|
||||
});
|
||||
|
||||
$('#submit-tags').click(function (e) {
|
||||
e.preventDefault();
|
||||
updatetags()
|
||||
});
|
||||
|
||||
$('#submit-files').click(function (e) {
|
||||
e.preventDefault();
|
||||
updatefiles()
|
||||
});
|
||||
|
||||
$('#delete-chal form').submit(function(e){
|
||||
e.preventDefault();
|
||||
$.post(script_root + '/admin/chal/delete', $(this).serialize(), function(data){
|
||||
console.log(data)
|
||||
if (data){
|
||||
loadchals();
|
||||
}
|
||||
else {
|
||||
alert('There was an error');
|
||||
}
|
||||
})
|
||||
$("#delete-chal").modal("hide");
|
||||
$("#update-challenge").modal("hide");
|
||||
});
|
||||
|
||||
$(".tag-insert").keyup(function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
tag = $('.tag-insert').val()
|
||||
tag = tag.replace(/'/g, '');
|
||||
if (tag.length > 0){
|
||||
tag = "<span class='label label-primary chal-tag'><span>"+tag+"</span><a class='delete-tag' onclick='$(this).parent().remove()'>×</a></span>"
|
||||
$('#chal-tags').append(tag)
|
||||
}
|
||||
$('.tag-insert').val("")
|
||||
}
|
||||
});
|
||||
|
||||
$('#limit_max_attempts').change(function() {
|
||||
if(this.checked) {
|
||||
$('#chal-attempts-group').show();
|
||||
} else {
|
||||
$('#chal-attempts-group').hide();
|
||||
$('#chal-attempts-input').val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Markdown Preview
|
||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#desc-preview'){
|
||||
$(event.target.hash).html(marked($('#desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#new-desc-preview'){
|
||||
$(event.target.hash).html(marked($('#new-desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
|
||||
// Open New Challenge modal when New Challenge button is clicked
|
||||
// $('.create-challenge').click(function (e) {
|
||||
// $('#create-challenge').modal();
|
||||
// });
|
||||
|
||||
|
||||
$('#create-key').click(function(e){
|
||||
$.get(script_root + '/admin/key_types', function(data){
|
||||
$("#create-keys-select").empty();
|
||||
var option = "<option> -- </option>";
|
||||
$("#create-keys-select").append(option);
|
||||
for (var key in data){
|
||||
var option = "<option value='{0}'>{1}</option>".format(key, data[key]);
|
||||
$("#create-keys-select").append(option);
|
||||
}
|
||||
$("#create-keys").modal();
|
||||
});
|
||||
});
|
||||
|
||||
$('#create-keys-select').change(function(){
|
||||
var key_type_name = $(this).find("option:selected").text();
|
||||
|
||||
$.get(script_root + '/static/admin/js/templates/keys/'+key_type_name +'/'+key_type_name+'.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$("#create-keys-entry-div").html(template());
|
||||
$("#create-keys-button-div").show();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('#create-keys-submit').click(function (e) {
|
||||
e.preventDefault();
|
||||
var chalid = $('#create-keys').find('.chal-id').val();
|
||||
var key_data = $('#create-keys').find('input[name=key]').val();
|
||||
var key_type = $('#create-keys-select').val();
|
||||
create_key(chalid, key_data, key_type);
|
||||
});
|
||||
|
||||
|
||||
$('#create-hint').click(function(e){
|
||||
e.preventDefault();
|
||||
load_hint_modal('create');
|
||||
});
|
||||
|
||||
$('#hint-modal-submit').submit(function (e) {
|
||||
e.preventDefault();
|
||||
var params = {}
|
||||
$(this).serializeArray().map(function(x){
|
||||
params[x.name] = x.value;
|
||||
});
|
||||
$.post(script_root + $(this).attr('action'), params, function(data){
|
||||
loadhints(params['chal']);
|
||||
});
|
||||
$("#hint-modal").modal('hide');
|
||||
});
|
||||
|
||||
function loadchal(id, update) {
|
||||
// $('#chal *').show()
|
||||
// $('#chal > h1').hide()
|
||||
obj = $.grep(challenges['game'], function (e) {
|
||||
return e.id == id;
|
||||
})[0]
|
||||
$('#desc-write-link').click() // Switch to Write tab
|
||||
$('.chal-title').text(obj.name);
|
||||
$('.chal-name').val(obj.name);
|
||||
$('.chal-desc').val(obj.description);
|
||||
$('.chal-value').val(obj.value);
|
||||
if (parseInt(obj.max_attempts) > 0){
|
||||
$('.chal-attempts').val(obj.max_attempts);
|
||||
$('#limit_max_attempts').prop('checked', true);
|
||||
$('#chal-attempts-group').show();
|
||||
}
|
||||
$('.chal-category').val(obj.category);
|
||||
$('.chal-id').val(obj.id);
|
||||
$('.chal-hidden').prop('checked', false);
|
||||
if (obj.hidden) {
|
||||
$('.chal-hidden').prop('checked', true);
|
||||
}
|
||||
//$('#update-challenge .chal-delete').attr({
|
||||
// 'href': '/admin/chal/close/' + (id + 1)
|
||||
//})
|
||||
if (typeof update === 'undefined')
|
||||
$('#update-challenge').modal();
|
||||
}
|
||||
|
||||
function openchal(id){
|
||||
loadchal(id);
|
||||
loadkeys(id);
|
||||
loadhints(id);
|
||||
loadtags(id);
|
||||
loadfiles(id);
|
||||
}
|
||||
|
|
@ -31,338 +31,11 @@
|
|||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>New Challenge</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/new" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" name="name" placeholder="Enter challenge name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="category">Category</label>
|
||||
<input type="text" class="form-control" name="category" placeholder="Enter challenge category">
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="new-desc-edit">
|
||||
<li role="presentation" class="active"><a href="#new-desc-write" aria-controls="home" role="tab" data-toggle="tab">Write</a></li>
|
||||
<li role="presentation"><a href="#new-desc-preview" aria-controls="home" role="tab" data-toggle="tab">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="new-desc-write">
|
||||
<div class="form-group">
|
||||
<label for="message-text" class="control-label">Message:</label>
|
||||
<textarea id="new-desc-editor" class="form-control" name="desc" rows="10"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane content" id="new-desc-preview">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="value">Value</label>
|
||||
<input type="number" class="form-control" name="value" placeholder="Enter value" required>
|
||||
</div>
|
||||
<div class="form-group" style="height:75px">
|
||||
<div class="col-md-9" style="padding-left:0px">
|
||||
<label for="key">Key</label>
|
||||
<input type="text" class="form-control" name="key" placeholder="Enter key">
|
||||
</div>
|
||||
<div class="col-md-3" style="margin-top:30px;padding-right:0px">
|
||||
<div class="radio-inline">
|
||||
<input type="radio" name="key_type[0]" value="0" checked>
|
||||
Static
|
||||
</div>
|
||||
<div class="radio-inline">
|
||||
<input type="radio" name="key_type[0]" value="1">
|
||||
Regex
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input name="hidden" type="checkbox">
|
||||
Hide challenge on creation
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Upload challenge files</label>
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<div style="text-align:center">
|
||||
<button class="btn btn-theme btn-outlined create-challenge-submit" type="submit">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-challenge" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 class="chal-title text-center"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/update">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control chal-name" name="name" placeholder="Enter challenge name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="category">Category</label>
|
||||
<input type="text" class="form-control chal-category" name="category" placeholder="Enter challenge category">
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="desc-edit">
|
||||
<li role="presentation" class="active"><a href="#desc-write" id="desc-write-link" aria-controls="home" role="tab" data-toggle="tab">Write</a></li>
|
||||
<li role="presentation"><a href="#desc-preview" aria-controls="home" role="tab" data-toggle="tab">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="desc-write">
|
||||
<div class="form-group">
|
||||
<label for="message-text" class="control-label">Message:</label>
|
||||
<textarea id="desc-editor" class="form-control chal-desc" name="desc" rows="10"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane content" id="desc-preview">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="value">Value</label>
|
||||
<input type="number" class="form-control chal-value" name="value" placeholder="Enter value" required>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input class="chal-attempts-checkbox" id="limit_max_attempts" name="limit_max_attempts" type="checkbox">
|
||||
Limit challenge attempts
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="chal-attempts-group" style="display:none;">
|
||||
<label for="value">Max Attempts</label>
|
||||
<input type="number" class="form-control chal-attempts" id="chal-attempts-input" name="max_attempts" placeholder="Enter value">
|
||||
</div>
|
||||
<input class="chal-id" type='hidden' name='id' placeholder='ID'>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input class="chal-hidden" name="hidden" type="checkbox">
|
||||
Hidden
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<a href="#" data-toggle="modal" data-target="#update-tags" class="btn btn-primary">Tags</a>
|
||||
<a href="#" data-toggle="modal" data-target="#update-files" class="btn btn-primary">Files</a>
|
||||
<a href="#" data-toggle="modal" data-target="#update-hints" class="btn btn-primary">Hints</a>
|
||||
<a href="#" data-toggle="modal" data-target="#update-keys" class="btn btn-primary">Keys</a>
|
||||
<a href="#" data-toggle="modal" data-target="#delete-chal" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<div style="text-align:center">
|
||||
<button class="btn btn-theme btn-outlined update-challenge-submit" type="submit">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="delete-chal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Delete Challenge</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/delete">
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||
<input type="hidden" name="id" class="chal-id">
|
||||
<div class="small-6 small-centered text-center columns">
|
||||
<p>Are you sure you want to delete this challenge?</p>
|
||||
<p>Solves, wrong keys, files, tags will all be deleted.</p>
|
||||
<a onclick="$('#delete-chal').modal('hide')" class="btn btn-primary">No</a>
|
||||
<button class="btn btn-danger" id="delete-user" type="submit">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="create-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<input type="hidden" class="chal-id" name="chal-id">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Create Key</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="create-keys-select-div">
|
||||
<label for="create-keys-select" class="control-label">Choose Key Type</label>
|
||||
<select class="form-control" id="create-keys-select">
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<div id="create-keys-entry-div">
|
||||
</div>
|
||||
<br>
|
||||
<div style="text-align:center;display:none;" id="create-keys-button-div">
|
||||
<button id="create-keys-submit" class="btn btn-theme btn-outlined">Create Key</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-keys" class="modal fade" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<div id="update-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Keys</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/keys" style="text-align:center">
|
||||
<a href="#" id="create-key" class="btn btn-primary" style="margin-bottom:15px;">New Key</a>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="keys-chal" name='chal' type='hidden'>
|
||||
<div id="current-keys" class="row"></div>
|
||||
<div class="row">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button id="submit-keys" class="btn btn-theme btn-outlined">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-files" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Files</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/files/" enctype="multipart/form-data">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="files-chal" name='chal' type='hidden'>
|
||||
<input name='method' type='hidden' value='upload'>
|
||||
|
||||
<div id="current-files"></div>
|
||||
<input type="hidden" name="method" value="upload">
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="submit-files">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="hint-modal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 id="hint-modal-title"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="hint-modal-submit" method='POST'>
|
||||
<input type="hidden" class="chal-id" name="chal">
|
||||
<div class="form-group">
|
||||
<textarea id="hint-modal-hint" type="text" class="form-control" name="hint" placeholder="Hint"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="hint-modal-cost" type="number" class="form-control" name="cost" placeholder="Cost">
|
||||
</div>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="hint-modal-button">Add Hint</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-hints" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Hints</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" style="text-align:center">
|
||||
<a href="#" id="create-hint" class="btn btn-primary" style="margin-bottom:15px;">New Hint</a>
|
||||
</div>
|
||||
<div class='current-hints'>
|
||||
<table id="hintsboard" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center"><b>Hint</b></td>
|
||||
<td class="text-center"><b>Cost</b></td>
|
||||
<td class="text-center"><b>Settings</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-center">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update-tags" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Tags</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="tag-insert">Value</label>
|
||||
<input max-length="80" type="text" class="form-control tag-insert" name="tag-insert" placeholder="Type tag and press Enter">
|
||||
</div>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input id="tags-chal" name='chal' type='hidden'>
|
||||
|
||||
<div id="current-tags">
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<div id="chal-tags">
|
||||
</div>
|
||||
<div class="row" style="text-align:center;margin-top:20px">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button class="btn btn-theme btn-outlined" id="submit-tags">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="update-modals-entry-div">
|
||||
</div>
|
||||
|
||||
<div style="text-align:center">
|
||||
|
@ -378,6 +51,6 @@
|
|||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/admin/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/multi-modal.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/chalboard.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/multi-modal.js"></script>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue