diff --git a/CTFd/admin.py b/CTFd/admin.py index 3f655117..aef2e981 100644 --- a/CTFd/admin.py +++ b/CTFd/admin.py @@ -196,7 +196,7 @@ def delete_page(pageroute): def admin_chals(): if request.method == 'POST': chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all() - + json_data = {'game':[]} for x in chals: json_data['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5]}) @@ -228,7 +228,7 @@ def admin_keys(chalid): flag_dict = {'flag':flag, 'type':int(val)} flags.append(flag_dict) json_data = json.dumps(flags) - + chal.flags = json_data db.session.commit() @@ -293,7 +293,7 @@ def admin_files(chalid): if len(filename) <= 0: continue - + md5hash = hashlib.md5(os.urandom(64)).hexdigest() if not os.path.exists(os.path.join(os.path.normpath(app.static_folder), 'uploads', md5hash)): @@ -433,7 +433,7 @@ def admin_graph(graph_type): return jsonify(json_data) elif graph_type == "solves": solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() - json_data = {} + json_data = {} for chal, count in solves: json_data[chal.chal.name] = count return jsonify(json_data) @@ -489,7 +489,7 @@ def delete_solve(teamid, chalid): @admins_only def admin_stats(): db.session.commit() - + teams_registered = db.session.query(db.func.count(Teams.id)).first()[0] wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0] solve_count = db.session.query(db.func.count(Solves.id)).first()[0] diff --git a/CTFd/static/admin/css/style.css b/CTFd/static/admin/css/style.css index b9c1863a..b4793303 100644 --- a/CTFd/static/admin/css/style.css +++ b/CTFd/static/admin/css/style.css @@ -73,7 +73,7 @@ table{ } #keys-pie-graph{ - width: 400px; + width: 600px; max-height: 330px; float: left; } @@ -86,16 +86,14 @@ table{ .chal-button{ width: 150px; + color: white; + border: None; } -.chal-button > p{ - font-family: monospace; +.chal-button > p { margin-bottom: 0px; height: 20px; font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } .chal-button > span{ diff --git a/CTFd/static/admin/js/chalboard.js b/CTFd/static/admin/js/chalboard.js index 50397c3a..d8fc2b4a 100644 --- a/CTFd/static/admin/js/chalboard.js +++ b/CTFd/static/admin/js/chalboard.js @@ -21,7 +21,7 @@ String.prototype.hashCode = function() { return hash; }; -function loadchal(id) { +function loadchal(id, update) { // $('#chal *').show() // $('#chal > h1').hide() obj = $.grep(challenges['game'], function (e) { @@ -37,8 +37,8 @@ function loadchal(id) { //$('#update-challenge .chal-delete').attr({ // 'href': '/admin/chal/close/' + (id + 1) //}) - $('#update-challenge').foundation('reveal', 'open'); - + if (typeof update === 'undefined') + $('#update-challenge').modal(); } function submitkey(chal, key) { @@ -57,12 +57,13 @@ function loadkeys(chal){ keys = keys['keys']; $('#current-keys').empty(); for(x=0; x'); - elem.append($("").val(keys[x].key)); - elem.append('Static'); - elem.append('Regex'); - elem.append('Remove'); - + var elem = $('
'); + + elem.append($("
").append($("").val(keys[x].key))); + elem.append('
Static
'); + elem.append('
Regex
'); + elem.append('Remove'); + $('#current-keys').append(elem); $('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true); } @@ -80,7 +81,8 @@ function updatekeys(){ vals.push($(this).val()); }) $.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()}) - loadchal(chal) + loadchal(chal, true) + $('#update-keys').modal('hide'); } function loadtags(chal){ @@ -91,7 +93,7 @@ function loadtags(chal){ tags = $.parseJSON(JSON.stringify(data)) tags = tags['tags'] for (var i = 0; i < tags.length; i++) { - tag = ""+tags[i].tag+"×" + tag = ""+tags[i].tag+"×" $('#current-tags').append(tag) }; $('.delete-tag').click(function(e){ @@ -120,7 +122,7 @@ function updatetags(){ } function loadfiles(chal){ - $('#update-files > form').attr('action', '/admin/files/'+chal) + $('#update-files form').attr('action', '/admin/files/'+chal) $.get('/admin/files/' + chal, function(data){ $('#files-chal').val(chal) files = $.parseJSON(JSON.stringify(data)); @@ -129,7 +131,7 @@ function loadfiles(chal){ for(x=0; x'+''+filename+'Delete
') + $('#current-files').append('') } }); } @@ -137,7 +139,7 @@ function loadfiles(chal){ function deletefile(chal, file, elem){ $.post('/admin/files/' + chal,{ 'nonce': $('#nonce').val(), - 'method': 'delete', + 'method': 'delete', 'file': file }, function (data){ if (data == "1") { @@ -159,13 +161,13 @@ function loadchals(){ for (var i = challenges['game'].length - 1; i >= 0; i--) { if ($.inArray(challenges['game'][i].category, categories) == -1) { categories.push(challenges['game'][i].category) - $('#challenges').append($('

' + challenges['game'][i].category + '

')) + $('#challenges').append($('

' + challenges['game'][i].category + '

')) } }; for (var i = 0; i <= challenges['game'].length - 1; i++) { var chal = challenges['game'][i] - var chal_button = $(''.format(chal.id, chal.name, chal.value)) + var chal_button = $(''.format(chal.id, chal.name, chal.value)) $('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button); }; @@ -179,7 +181,7 @@ function loadchals(){ $('.create-challenge').click(function (e) { $('#new-chal-category').val($($(this).siblings()[0]).text().trim()) $('#new-chal-title').text($($(this).siblings()[0]).text().trim()) - $('#new-challenge').foundation('reveal', 'open'); + $('#new-challenge').modal(); }); }); @@ -190,25 +192,28 @@ $('#submit-key').click(function (e) { }); $('#submit-keys').click(function (e) { + e.preventDefault(); updatekeys() }); $('#submit-tags').click(function (e) { + e.preventDefault(); updatetags() }); -$('#delete-chal > form').submit(function(e){ +$('#delete-chal form').submit(function(e){ e.preventDefault(); $.post('/admin/chal/delete', $(this).serialize(), function(data){ console.log(data) if (data){ loadchals(); - $('#delete-chal').foundation('reveal', 'close'); } else { alert('There was an error'); } }) + $("#delete-chal").modal("hide"); + $("#update-challenge").modal("hide"); }); $(".tag-insert").keyup(function (e) { @@ -216,7 +221,7 @@ $(".tag-insert").keyup(function (e) { tag = $('.tag-insert').val() tag = tag.replace(/'/g, ''); if (tag.length > 0){ - tag = ""+tag+"×" + tag = ""+tag+"×" $('#chal-tags').append(tag) } $('.tag-insert').val("") @@ -226,34 +231,33 @@ $(".tag-insert").keyup(function (e) { // Markdown Preview -$('#desc-edit').on('toggled', function (event, tab) { - if (tab[0].id == 'desc-preview'){ - $(tab[0]).html(marked($('#desc-editor').val(), {'gfm':true, 'breaks':true})) +$('#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('toggled', function (event, tab) { - if (tab[0].id == 'new-desc-preview'){ - $(tab[0]).html(marked($('#new-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').foundation('reveal', 'open'); + $('#create-challenge').modal(); }); $('#create-key').click(function(e){ var amt = $('#current-keys input[type=text]').length - // $('#current-keys').append(""); - // $('#current-keys').append('Static'.format(amt)); - // $('#current-keys').append('Regex'.format(amt)); - - var elem = $('
'); - elem.append(""); - elem.append('Static'.format(amt)); - elem.append('Regex'.format(amt)); - elem.append('Remove'); + + var elem = $('
'); + + elem.append($("
").append($(""))); + elem.append('
Static
'); + elem.append('
Regex
'); + elem.append('Remove'); + $('#current-keys').append(elem); }); diff --git a/CTFd/static/admin/js/multi-modal.js b/CTFd/static/admin/js/multi-modal.js new file mode 100644 index 00000000..37dd3b2b --- /dev/null +++ b/CTFd/static/admin/js/multi-modal.js @@ -0,0 +1,63 @@ +(function($, window) { + 'use strict'; + + var MultiModal = function(element) { + this.$element = $(element); + this.modalCount = 0; + }; + + MultiModal.BASE_ZINDEX = 1040; + + MultiModal.prototype.show = function(target) { + var that = this; + var $target = $(target); + var modalIndex = that.modalCount++; + + $target.css('z-index', MultiModal.BASE_ZINDEX + (modalIndex * 20) + 10); + + window.setTimeout(function() { + if(modalIndex > 0) + $('.modal-backdrop').not(':first').addClass('hidden'); + + that.adjustBackdrop(); + }); + }; + + MultiModal.prototype.hidden = function(target) { + this.modalCount--; + + if(this.modalCount) { + this.adjustBackdrop(); + $('body').addClass('modal-open'); + } + }; + + MultiModal.prototype.adjustBackdrop = function() { + var modalIndex = this.modalCount - 1; + $('.modal-backdrop:first').css('z-index', MultiModal.BASE_ZINDEX + (modalIndex * 20)); + }; + + function Plugin(method, target) { + return this.each(function() { + var $this = $(this); + var data = $this.data('multi-modal-plugin'); + + if(!data) + $this.data('multi-modal-plugin', (data = new MultiModal(this))); + + if(method) + data[method](target); + }); + } + + $.fn.multiModal = Plugin; + $.fn.multiModal.Constructor = MultiModal; + + $(document).on('show.bs.modal', function(e) { + $(document).multiModal('show', e.target); + }); + + $(document).on('hidden.bs.modal', function(e) { + $(document).multiModal('hidden', e.target); + }); +}(jQuery, window)); diff --git a/CTFd/static/css/input.css b/CTFd/static/css/input.css new file mode 100644 index 00000000..9d01cd28 --- /dev/null +++ b/CTFd/static/css/input.css @@ -0,0 +1,132 @@ +.submit-row { + padding-left: 15px; + padding-right: 30px; +} + +.input { + width: 100%; + position: relative; + z-index: 1; + display: inline-block; + margin: 1em 0em; + vertical-align: top; +} + +.input-field { + position: relative; + display: block; + float: right; + padding: 0.8em; + width: 60%; + border: none; + border-radius: 0; + background: #f0f0f0; + color: #aaa; + font-weight: 400; + font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif; + -webkit-appearance: none; /* for box shadows to show on iOS */ +} + +.input-field:focus { + outline: none; +} + +.input-label { + display: inline-block; + float: right; + padding: 0 1em; + width: 40%; + color: #6a7989; + font-weight: bold; + font-size: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.label-content { + position: relative; + display: block; + padding: 1.6em 0; + width: 100%; +} + +.graphic { + position: absolute; + top: 0; + left: 0; + fill: none; +} + +.icon { + color: #ddd; + font-size: 150%; +} + +.input-field { + width: 100%; + background-color: #eee; + border: 2px solid transparent; + -webkit-transition: background-color 0.3s, border-color 0.3s; + transition: background-color 0.3s, border-color 0.3s; +} + +.input-label { + width: 100%; + text-align: left; + position: absolute; + bottom: 100%; + pointer-events: none; + overflow: hidden; + padding: 0 1.25em; + -webkit-transform: translate3d(0, 3em, 0); + transform: translate3d(0, 3em, 0); + -webkit-transition: -webkit-transform 0.3s; + transition: transform 0.3s; +} + +.label-content { + color: #8B8C8B; + padding: 0.25em 0; + -webkit-transition: -webkit-transform 0.3s; + transition: transform 0.3s; +} + +.label-content::after { + content: attr(data-content); + position: absolute; + font-weight: 800; + bottom: 100%; + left: 0; + height: 100%; + width: 100%; + color: #a3d39c; + padding: 0.25em 0; + letter-spacing: 1px; + font-size: 0.85em; +} + +.input-field:focus + .input-field, +.input--filled .input-field { + background-color: transparent; + border-color: #a3d39c; +} + +.input--hide { + display: none; +} + +#submit { + position: relative; + right: -15px; +} + +.done-row { + margin: 0px; +} + diff --git a/CTFd/static/css/main-style.css b/CTFd/static/css/main-style.css new file mode 100644 index 00000000..74471adf --- /dev/null +++ b/CTFd/static/css/main-style.css @@ -0,0 +1,236 @@ + +html, body, .container { + height: 100% !important; + font-family: 'Lato', sans-serif; +} + +h1, h2 { + font-family: 'Raleway', sans-serif; + font-weight: 500; + letter-spacing: 2px; +} + +td { + padding: 15px !important; +} + +a { + outline: 0; +} + +.no-margin-right { + margin-right: 0px !important; +} + +.no-margin-left { + margin-left: -3px !important; +} + +.navbar > .container { + padding-top: 30px; +} + +.navbar .navbar-brand { + font-size: 24px; + letter-spacing: -0.04rem; + line-height: 15px; + color: #FFF; +} + +.navbar-inverse .navbar-nav > li > a { + color: #FFF !important; +} + +.navbar li > a { + opacity: 0.5; + transition: opacity 0.08s ease-in 0s; + cursor: pointer; +} + +.navbar li > a:hover { + opacity: 0.8; +} + +.align-text-to-button { + padding-top: 16px; +} + +.padded-container { + margin-top: 50px; + padding-left: 50px; + padding-right: 50px; +} + +.main-container { + margin-top: 35px; + margin-bottom: 25px; +} + +#login-container { + padding-left: 70px; + padding-right: 70px; +} + +#chal > form{ + width: 400px; + margin: 0 auto; +} + +.reveal-modal{ + text-align: center; +} + +.chal-desc{ + text-align: left; +} + +table{ + width: 100%; +} + +#challenges button{ + margin: 8px; +} + +.row > h1{ + text-align: center; +} + +#challenges{ + line-height: 66px; +} + +#score-graph{ + max-height: 400px; +} + +#keys-pie-graph{ + width: 50%; + max-height: 330px; + float: left; +} + +#categories-pie-graph{ + width: 50%; + float: left; + max-height: 330px; +} + +.logo{ + margin: 0 auto; + width: 500px; + padding: 50px; + display: block; +} + +@media only screen and (min-width: 40.063em){ + .top-bar .dropdown{ + display: block; + padding: 0 15px 5px; + width: 200% !important; + } +} + +.btn { + letter-spacing: 1px; + text-decoration: none; + background: none; + -moz-user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 0; + cursor: pointer; + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + white-space: nowrap; + font-size:14px; + line-height:20px; + font-weight:700; + text-transform:uppercase; + border: 3px solid; + padding:8px 20px; +} + +.btn-outlined { + border-radius: 0; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; +} + +.file-wrapper { + background-color: #5B7290; +} + +.file-wrapper:hover { + background-color: #747474; +} + +.theme-background { + background-color: #545454 !important; + +} + +.solved-challenge { + background-color: #8EDC9D !important; +} + +.panel-theme { + border-color: #545454; +} + +.panel-theme > .panel-heading { + border-color: #545454; + background-color: #545454; + opacity: 1; + color: #FFF; + text-align: center; +} + +.btn-outlined.btn-theme { + background: none; + color: #545454; + border-color: #545454; +} + +.btn-outlined.btn-theme:hover, +.btn-outlined.btn-theme:active { + color: #FFF; + background: #545454; + border-color: #545454; +} + +.navbar-inverse { + background: none repeat scroll 0% 0% #545454; + border: medium none; + margin-bottom: 0px; +} + +.jumbotron { + background-color: #545454; + color: #FFF; + padding: 0px 0px 25px; +} + +.jumbotron.home { + text-align: center; + margin-bottom: 0px; +} + +.home h1 { + font-size: 48px !important; +} + +.navbar { + border-radius: 0px; +} + +.modal-content { + border-radius: 0px; +} + +.alert { + border-radius: 0px; +} + diff --git a/CTFd/static/css/style.css b/CTFd/static/css/style.css deleted file mode 100644 index 4610c42e..00000000 --- a/CTFd/static/css/style.css +++ /dev/null @@ -1,129 +0,0 @@ -#chal > form{ - width: 400px; - margin: 0 auto; -} - -.reveal-modal{ - text-align: center; -} - -.chal-desc{ - text-align: left; -} - -table{ - width: 100%; -} - -/*Not sure why foundation needs these two...*/ -.top-bar input{ - height: auto; - padding-top: 0.35rem; - padding-bottom: 0.35rem; - font-size: 0.75rem; -} - -.top-bar .button{ - padding-top: 0.45rem; - padding-bottom: 0.35rem; - margin-bottom: 0; - font-size: 0.75rem; -} - -.dropdown{ - background-color: #333 !important; - padding: 5px; -} - -.dropdown button{ - padding-top: 0.45rem; - padding-bottom: 0.35rem; - margin-bottom: 0; - font-size: 0.75rem; -} - -#challenges button{ - margin: 8px; -} - -.row > h1{ - text-align: center; -} - -#challenges{ - line-height: 66px; -} - -#score-graph{ - max-height: 400px; - clear:both; -} - -.dropdown{ - padding: 20px; -} - -.dropdown input{ - margin: 5px auto; - width: 95%; -} - -.dropdown button{ - margin: 10px auto; - width: 100%; -} - -#keys-pie-graph{ - width: 50%; - max-height: 330px; - float: left; -} - -#categories-pie-graph{ - width: 50%; - float: left; - max-height: 330px; -} - -.logo{ - margin: 0 auto; - width: 500px; - padding: 50px; - display: block; -} - -.chal-button{ - width: 150px; -} - -.chal-button > p{ - font-family: monospace; - margin-bottom: 0px; - height: 20px; - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.chal-button > span{ - font-size: 14px; -} - -@media only screen and (min-width: 40.063em){ - .top-bar .dropdown{ - display: block; - padding: 0 15px 5px; - width: 200% !important; - } -} - -.top-bar input{ - padding: 10px; -} - -.scroll-wrap{ - height: 400px; - overflow-y: auto; - overflow-x: hidden; -} diff --git a/CTFd/static/js/chalboard.js b/CTFd/static/js/chalboard.js index 0d999474..89fea9da 100644 --- a/CTFd/static/js/chalboard.js +++ b/CTFd/static/js/chalboard.js @@ -1,3 +1,4 @@ + //http://stackoverflow.com/a/2648463 - wizardry! String.prototype.format = String.prototype.f = function() { var s = this, @@ -30,58 +31,43 @@ var challenges; function loadchal(id) { obj = $.grep(challenges['game'], function (e) { return e.id == id; - })[0] - window.location.hash = obj.name - $('#chal-window .chal-name').text(obj.name) - $('#chal-window .chal-desc').html(marked(obj.description, {'gfm':true, 'breaks':true})) + })[0]; + updateChalWindow(obj); +} + +function loadchalbyname(chalname) { + obj = $.grep(challenges['game'], function (e) { + return e.name == chalname; + })[0]; + + updateChalWindow(obj); +} + +function updateChalWindow(obj) { + window.location.hash = obj.name + $('#chal-window').find('.chal-name').text(obj.name) + $('#chal-window').find('.chal-desc').html(marked(obj.description, {'gfm':true, 'breaks':true})) + $('#chal-window').find('.chal-files').empty(); for (var i = 0; i < obj.files.length; i++) { filename = obj.files[i].split('/') filename = filename[filename.length - 1] - $('#chal-window .chal-desc').append(""+filename+"
") + $('#chal-window').find('.chal-files').append("") }; - $('#chal-window .chal-value').text(obj.value) - $('#chal-window .chal-category').text(obj.category) - $('#chal-window #chal-id').val(obj.id) - $('#chal-window .chal-solves').text(obj.solves + " solves") + $('#chal-window').find('.chal-value').text(obj.value) + $('#chal-window').find('.chal-category').text(obj.category) + $('#chal-window').find('#chal-id').val(obj.id) + var solves = obj.solves == 1 ? " Solve" : " Solves"; + $('#chal-window').find('.chal-solves').text(obj.solves + solves) $('#answer').val("") $('pre code').each(function(i, block) { hljs.highlightBlock(block); }); - $('#chal-window').foundation('reveal', 'open'); } -function loadchalbyname(chalname) { - obj = $.grep(challenges['game'], function (e) { - return e.name == chalname; - })[0] - window.location.hash = obj.name - $('#chal-window .chal-name').text(obj.name) - $('#chal-window .chal-desc').html(marked(obj.description, {'gfm':true, 'breaks':true})) - - for (var i = 0; i < obj.files.length; i++) { - filename = obj.files[i].split('/') - filename = filename[filename.length - 1] - $('#chal-window .chal-desc').append(""+filename+"
") - }; - - $('#chal-window .chal-value').text(obj.value) - $('#chal-window .chal-category').text(obj.category) - $('#chal-window #chal-id').val(obj.id) - $('#chal-window .chal-solves').text(obj.solves + " solves") - $('#answer').val("") - - $('pre code').each(function(i, block) { - hljs.highlightBlock(block); - }); - - $('#chal-window').foundation('reveal', 'open'); -} - - -$("#answer").keyup(function(event){ +$("#answer-input").keyup(function(event){ if(event.keyCode == 13){ $("#submit-key").click(); } @@ -89,8 +75,10 @@ $("#answer").keyup(function(event){ function submitkey(chal, key, nonce) { + $('#submit-key').addClass("disabled-button"); + $('#submit-key').prop('disabled', true); $.post("/chal/" + chal, { - key: key, + key: key, nonce: nonce }, function (data) { if (data == -1){ @@ -98,37 +86,37 @@ function submitkey(chal, key, nonce) { return } else if (data == 0){ // Incorrect key - $('#submit-key').text('Incorrect, sorry') - $('#submit-key').css('background-color', 'red') - $('#submit-key').prop('disabled', true) + $("#incorrect-key").slideDown(); + $("#answer-input").addClass("wrong"); + $("#answer-input").removeClass("correct"); + setTimeout(function() { + $("#answer-input").removeClass("wrong"); + }, 3000); } else if (data == 1){ // Challenge Solved - $('#submit-key').text('Correct!') - $('#submit-key').css('background-color', 'green') - $('#submit-key').prop('disabled', true) - $('#chal-window .chal-solves').text( (parseInt($('#chal-window .chal-solves').text().split(" ")[0]) + 1 + " solves") ) + $("#correct-key").slideDown(); + $('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 + " Solves") ) + $("#answer-input").val(""); + $("#answer-input").removeClass("wrong"); + $("#answer-input").addClass("correct"); } else if (data == 2){ // Challenge already solved - $('#submit-key').text('You already solved this') - $('#submit-key').prop('disabled', true) + $("#already-solved").slideDown(); + $("#answer-input").addClass("correct"); } else if (data == 3){ // Keys per minute too high - $('#submit-key').text("You're submitting keys too fast. Slow down.") - $('#submit-key').css('background-color', '#e18728') - $('#submit-key').prop('disabled', true) + $("#too-fast").slideDown(); + $("#answer-input").addClass("wrong"); + setTimeout(function() { + $("#answer-input").removeClass("wrong"); + }, 3000); } - else if (data == 4){ // too many incorrect solves - $('#submit-key').text('Too many attempts.') - $('#submit-key').css('background-color', 'red') - $('#submit-key').prop('disabled', true) - } - marktoomanyattempts() marksolves() updatesolves() setTimeout(function(){ - $('#submit-key').text('Submit') - $('#submit-key').prop('disabled', false) - $('#submit-key').css('background-color', '#007095') + $('.alert').slideUp(); + $('#submit-key').removeClass("disabled-button"); + $('#submit-key').prop('disabled', false); }, 3000); }) } @@ -137,26 +125,13 @@ function marksolves() { $.get('/solves', function (data) { solves = $.parseJSON(JSON.stringify(data)); for (var i = solves['solves'].length - 1; i >= 0; i--) { - id = solves['solves'][i].chalid - $('#challenges button[value="' + id + '"]').addClass('secondary') - $('#challenges button[value="' + id + '"]').css('opacity', '0.3') - }; - if (window.location.hash.length > 0){ - loadchalbyname(window.location.hash.substring(1)) - } - }); -} - -function marktoomanyattempts() { - $.get('/maxattempts', function (data) { - maxattempts = $.parseJSON(JSON.stringify(data)); - for (var i = maxattempts['maxattempts'].length - 1; i >= 0; i--) { - id = maxattempts['maxattempts'][i].chalid - $('#challenges button[value="' + id + '"]').addClass('secondary') - $('#challenges button[value="' + id + '"]').css('background-color', '#FF9999') + id = solves['solves'][i].chalid; + $('button[value="' + id + '"]').removeClass('theme-background'); + $('button[value="' + id + '"]').addClass('solved-challenge'); }; if (window.location.hash.length > 0){ loadchalbyname(window.location.hash.substring(1)) + $("#chal-window").modal("show"); } }); } @@ -166,7 +141,7 @@ function updatesolves(){ solves = $.parseJSON(JSON.stringify(data)); chals = Object.keys(solves); - for (var i = 0; i < chals.length; i++) { + for (var i = 0; i < chals.length; i++) { obj = $.grep(challenges['game'], function (e) { return e.name == chals[i]; })[0] @@ -196,39 +171,64 @@ function loadchals() { categories = []; challenges = $.parseJSON(JSON.stringify(data)); + $('#challenges-board').html(""); for (var i = challenges['game'].length - 1; i >= 0; i--) { challenges['game'][i].solves = 0 if ($.inArray(challenges['game'][i].category, categories) == -1) { - categories.push(challenges['game'][i].category) - $('#challenges').append($('

' + challenges['game'][i].category + '

')) + var category = challenges['game'][i].category; + categories.push(category); + + var categoryid = category.replace(/ /g,"-").hashCode(); + var categoryrow = $('
'.format(categoryid)); + categoryrow.find(".category-header").append($("

"+ category +"

")); + + $('#challenges-board').append(categoryrow); } }; for (var i = 0; i <= challenges['game'].length - 1; i++) { - var chal = challenges['game'][i] - var chal_button = $(''.format(chal.id, chal.name, chal.value)) - $('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button); - }; - updatesolves() - marktoomanyattempts() - marksolves() + chalinfo = challenges['game'][i]; + challenge = chalinfo.category.replace(/ /g,"-").hashCode(); + var chalid = chalinfo.name.replace(/ /g,"-").hashCode(); + var catid = chalinfo.category.replace(/ /g,"-").hashCode(); + var chalwrap = $("
".format(chalid)); + var chalbutton = $("
".format(chalinfo.id)); + var chalheader = $("
{0}
".format(chalinfo.name)); + var chalscore = $("{0}".format(chalinfo.value)); + chalbutton.append(chalheader); + chalbutton.append(chalscore); + chalwrap.append(chalbutton); - $('#challenges button').click(function (e) { + $("#"+ catid +"-row").find(".category-challenges > .row").append(chalwrap); + }; + + updatesolves(); + marksolves(); + + $('.challenge-button').click(function (e) { loadchal(this.value); }); - }); } $('#submit-key').click(function (e) { - submitkey($('#chal-id').val(), $('#answer').val(), $('#nonce').val()) + submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val()) }); $('.chal-solves').click(function (e) { getsolves($('#chal-id').val()) }); +$('#chal-window').on('hide.bs.modal', function (event) { + $("#answer-input").removeClass("wrong"); + $("#answer-input").removeClass("correct"); + $("#incorrect-key").slideUp(); + $("#correct-key").slideUp(); + $("#already-solved").slideUp(); + $("#too-fast").slideUp(); +}); + // $.distint(array) // Unique elements in array $.extend({ @@ -240,6 +240,7 @@ $.extend({ return result; } }); + function colorhash (x) { color = "" for (var i = 20; i <= 60; i+=20){ @@ -250,35 +251,17 @@ function colorhash (x) { return "#" + color.substring(0, 6) } -$(document).on('close', '[data-reveal]', function () { - window.location.hash = "" -}); - -// function solves_graph() { -// $.get('/graphs/solves', function(data){ -// solves = $.parseJSON(JSON.stringify(data)); -// chals = [] -// counts = [] -// colors = [] -// i = 1 -// $.each(solves, function(key, value){ -// chals.push(key) -// counts.push(value) -// colors.push(colorhash(i++)) -// }); - -// }); -// } - function update(){ - $('#challenges').empty() loadchals() - solves_graph() } $(function() { - loadchals() - // solves_graph() + loadchals(); }); +$('.nav-tabs a').click(function (e) { + e.preventDefault() + $(this).tab('show') +}) + setInterval(update, 300000); diff --git a/CTFd/static/js/input.js b/CTFd/static/js/input.js new file mode 100644 index 00000000..e73123d9 --- /dev/null +++ b/CTFd/static/js/input.js @@ -0,0 +1,45 @@ +(function() { + // trim polyfill : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim + if (!String.prototype.trim) { + (function() { + // Make sure we trim BOM and NBSP + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + String.prototype.trim = function() { + return this.replace(rtrim, ''); + }; + })(); + } + [].slice.call( document.querySelectorAll( 'input.input-field' ) ).forEach( function( inputEl ) { + // in case the input is already filled.. + if( inputEl.value.trim() !== '' ) { + classie.add( inputEl.parentNode, 'input--filled' ); + classie.add( inputEl.nextElementSibling, 'input--hide' ); + $label = $(inputEl).siblings(".input-label"); + $label.removeClass("input--hide"); + $label.css({ + "transform": "translate3d(0, 0, 0)" + }); + } + } ); +})(); + +$(".input-field").bind({ + focus: function() { + $(this).parent().addClass('input--filled' ); + $label = $(this).siblings(".input-label"); + //$label.addClass('input--hide' ); + $label.css({ + "transform": "translate3d(0, 10%, 0)" + }); + }, + blur: function() { + if ($(this).val() === '') { + $(this).parent().removeClass('input--filled' ); + $label = $(this).siblings(".input-label"); + $label.removeClass('input--hide' ); + $label.css({ + "transform": "translate3d(0, 150%, 0)" + }); + } + } +}); diff --git a/CTFd/static/js/scoreboard.js b/CTFd/static/js/scoreboard.js index db7246d5..16592e69 100644 --- a/CTFd/static/js/scoreboard.js +++ b/CTFd/static/js/scoreboard.js @@ -79,7 +79,7 @@ function scoregraph () { team_scores.unshift(teams[i]) // team_scores.push( team_scores[team_scores.length-1] ) - + xs_data[teams[i]] = "x"+i column_data.push(times) column_data.push(team_scores) @@ -99,11 +99,11 @@ function scoregraph () { x : { tick: { count: 10, - format: function (x) { + format: function (x) { return moment(x*1000).local().format('LLL'); } }, - + }, y:{ label: { diff --git a/CTFd/static/js/team.js b/CTFd/static/js/team.js index f633e674..f9fea665 100644 --- a/CTFd/static/js/team.js +++ b/CTFd/static/js/team.js @@ -1,6 +1,6 @@ function teamid (){ loc = window.location.pathname - return parseInt(loc.substring(loc.lastIndexOf('/')+1, loc.length)); + return loc.substring(loc.lastIndexOf('/')+1, loc.length); } function colorhash (x) { @@ -29,8 +29,6 @@ function scoregraph () { solves = $.parseJSON(JSON.stringify(data)); solves = solves['solves'] - console.log(solves) - if (solves.length == 0) return @@ -46,8 +44,6 @@ function scoregraph () { scores.unshift('data1') // scores.push( scores[scores.length-1] ) - console.log(scores) - var chart = c3.generate({ bindto: "#score-graph", data: { @@ -70,11 +66,11 @@ function scoregraph () { axis : { x : { tick: { - format: function (x) { + format: function (x) { return moment(x).local().format('M/D h:mm:ss'); } }, - + }, y:{ label: { diff --git a/CTFd/static/uploads/index.html b/CTFd/static/uploads/index.html deleted file mode 100644 index e69de29b..00000000 diff --git a/CTFd/templates/admin/base.html b/CTFd/templates/admin/base.html index 7caabaa3..2a82c5b3 100644 --- a/CTFd/templates/admin/base.html +++ b/CTFd/templates/admin/base.html @@ -2,56 +2,57 @@ - + Admin Panel - - - + + + + + + + {% block stylesheets %} {% endblock %} - - - {% block content %} {% endblock %} +
+ {% block content %} {% endblock %} +
+
- - - + + {% block scripts %} {% endblock %} diff --git a/CTFd/templates/admin/chals.html b/CTFd/templates/admin/chals.html index 395a27a9..a0367552 100644 --- a/CTFd/templates/admin/chals.html +++ b/CTFd/templates/admin/chals.html @@ -1,141 +1,283 @@ {% extends "admin/base.html" %} +{% block stylesheets %} + +{% endblock %} + {% block content %} + + -
-

New Challenge

-
-
-
- - -
-
-
+