mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 14:34:21 +01:00
Closes #15 (Thanks mwinstead3790), various fixes
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
from flask import render_template, request, redirect, abort, jsonify, url_for, session
|
from flask import render_template, request, redirect, abort, jsonify, url_for, session
|
||||||
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail
|
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail, rmdir
|
||||||
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
||||||
from itsdangerous import TimedSerializer, BadTimeSignature
|
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
@@ -23,7 +23,10 @@ def init_admin(app):
|
|||||||
|
|
||||||
admin = Teams.query.filter_by(name=request.form['name'], admin=True).first()
|
admin = Teams.query.filter_by(name=request.form['name'], admin=True).first()
|
||||||
if admin and bcrypt_sha256.verify(request.form['password'], admin.password):
|
if admin and bcrypt_sha256.verify(request.form['password'], admin.password):
|
||||||
|
try:
|
||||||
session.regenerate() # NO SESSION FIXATION FOR YOU
|
session.regenerate() # NO SESSION FIXATION FOR YOU
|
||||||
|
except:
|
||||||
|
pass # TODO: Some session objects dont implement regenerate :(
|
||||||
session['username'] = admin.name
|
session['username'] = admin.name
|
||||||
session['id'] = admin.id
|
session['id'] = admin.id
|
||||||
session['admin'] = True
|
session['admin'] = True
|
||||||
@@ -445,7 +448,6 @@ def init_admin(app):
|
|||||||
|
|
||||||
@app.route('/admin/chal/new', methods=['POST'])
|
@app.route('/admin/chal/new', methods=['POST'])
|
||||||
def admin_create_chal():
|
def admin_create_chal():
|
||||||
|
|
||||||
files = request.files.getlist('files[]')
|
files = request.files.getlist('files[]')
|
||||||
|
|
||||||
# Create challenge
|
# Create challenge
|
||||||
@@ -466,17 +468,36 @@ def init_admin(app):
|
|||||||
|
|
||||||
md5hash = hashlib.md5(filename).hexdigest()
|
md5hash = hashlib.md5(filename).hexdigest()
|
||||||
|
|
||||||
if not os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], md5hash)):
|
if not os.path.exists(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash)):
|
||||||
os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], md5hash))
|
os.makedirs(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash))
|
||||||
|
|
||||||
f.save(os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
f.save(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename))
|
||||||
db_f = Files(chal.id, os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
db_f = Files(chal.id, os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename))
|
||||||
db.session.add(db_f)
|
db.session.add(db_f)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return redirect('/admin/chals')
|
return redirect('/admin/chals')
|
||||||
|
|
||||||
|
@app.route('/admin/chal/delete', methods=['POST'])
|
||||||
|
def admin_delete_chal():
|
||||||
|
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||||
|
if challenge:
|
||||||
|
WrongKeys.query.filter_by(chal=challenge.id).delete()
|
||||||
|
Solves.query.filter_by(chalid=challenge.id).delete()
|
||||||
|
Keys.query.filter_by(chal=challenge.id).delete()
|
||||||
|
files = Files.query.filter_by(chal=challenge.id).all()
|
||||||
|
print files
|
||||||
|
Files.query.filter_by(chal=challenge.id).delete()
|
||||||
|
for file in files:
|
||||||
|
folder = os.path.dirname(file.location)
|
||||||
|
rmdir(folder)
|
||||||
|
Tags.query.filter_by(chal=challenge.id).delete()
|
||||||
|
Challenges.query.filter_by(id=challenge.id).delete()
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return '1'
|
||||||
|
|
||||||
@app.route('/admin/chal/update', methods=['POST'])
|
@app.route('/admin/chal/update', methods=['POST'])
|
||||||
def admin_update_chal():
|
def admin_update_chal():
|
||||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||||
|
|||||||
@@ -105,7 +105,10 @@ Did you initiate a password reset?
|
|||||||
# team = Teams.query.filter_by(name=request.form['name'], password=sha512(request.form['password'])).first()
|
# team = Teams.query.filter_by(name=request.form['name'], password=sha512(request.form['password'])).first()
|
||||||
team = Teams.query.filter_by(name=request.form['name']).first()
|
team = Teams.query.filter_by(name=request.form['name']).first()
|
||||||
if team and bcrypt_sha256.verify(request.form['password'], team.password):
|
if team and bcrypt_sha256.verify(request.form['password'], team.password):
|
||||||
# session.regenerate() # NO SESSION FIXATION FOR YOU
|
try:
|
||||||
|
session.regenerate() # NO SESSION FIXATION FOR YOU
|
||||||
|
except:
|
||||||
|
pass # TODO: Some session objects don't implement regenerate :(
|
||||||
session['username'] = team.name
|
session['username'] = team.name
|
||||||
session['id'] = team.id
|
session['id'] = team.id
|
||||||
session['admin'] = team.admin
|
session['admin'] = team.admin
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SESSION_TYPE = "filesystem"
|
|||||||
SESSION_FILE_DIR = "/tmp/flask_session"
|
SESSION_FILE_DIR = "/tmp/flask_session"
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
HOST = ".ctfd.io"
|
HOST = ".ctfd.io"
|
||||||
UPLOAD_FOLDER = 'static/uploads'
|
UPLOAD_FOLDER = os.path.normpath('static/uploads')
|
||||||
|
|
||||||
##### EMAIL #####
|
##### EMAIL #####
|
||||||
CTF_NAME = ''
|
CTF_NAME = ''
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import time
|
|||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import digitalocean
|
import digitalocean
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
def init_utils(app):
|
def init_utils(app):
|
||||||
app.jinja_env.filters['unix_time'] = unix_time
|
app.jinja_env.filters['unix_time'] = unix_time
|
||||||
@@ -26,9 +28,11 @@ def pages():
|
|||||||
pages = Pages.query.filter(Pages.route!="index").all()
|
pages = Pages.query.filter(Pages.route!="index").all()
|
||||||
return pages
|
return pages
|
||||||
|
|
||||||
|
|
||||||
def authed():
|
def authed():
|
||||||
return bool(session.get('id', False))
|
return bool(session.get('id', False))
|
||||||
|
|
||||||
|
|
||||||
def is_setup():
|
def is_setup():
|
||||||
setup = Config.query.filter_by(key='setup').first()
|
setup = Config.query.filter_by(key='setup').first()
|
||||||
if setup:
|
if setup:
|
||||||
@@ -36,6 +40,7 @@ def is_setup():
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_admin():
|
def is_admin():
|
||||||
if authed():
|
if authed():
|
||||||
return session['admin']
|
return session['admin']
|
||||||
@@ -50,6 +55,7 @@ def can_register():
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def admins_only(f):
|
def admins_only(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
@@ -89,6 +95,7 @@ def ctftime():
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def can_view_challenges():
|
def can_view_challenges():
|
||||||
config = Config.query.filter_by(key="view_challenges_unregistered").first()
|
config = Config.query.filter_by(key="view_challenges_unregistered").first()
|
||||||
if config:
|
if config:
|
||||||
@@ -96,24 +103,30 @@ def can_view_challenges():
|
|||||||
else:
|
else:
|
||||||
return authed()
|
return authed()
|
||||||
|
|
||||||
|
|
||||||
def unix_time(dt):
|
def unix_time(dt):
|
||||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||||
delta = dt - epoch
|
delta = dt - epoch
|
||||||
return int(delta.total_seconds())
|
return int(delta.total_seconds())
|
||||||
|
|
||||||
|
|
||||||
def unix_time_millis(dt):
|
def unix_time_millis(dt):
|
||||||
return unix_time(dt) * 1000
|
return unix_time(dt) * 1000
|
||||||
|
|
||||||
|
|
||||||
def long2ip(ip_int):
|
def long2ip(ip_int):
|
||||||
return inet_ntoa(pack('!I', ip_int))
|
return inet_ntoa(pack('!I', ip_int))
|
||||||
|
|
||||||
|
|
||||||
def ip2long(ip):
|
def ip2long(ip):
|
||||||
return unpack('!I', inet_aton(ip))[0]
|
return unpack('!I', inet_aton(ip))[0]
|
||||||
|
|
||||||
|
|
||||||
def get_kpm(teamid): # keys per minute
|
def get_kpm(teamid): # keys per minute
|
||||||
one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1)
|
one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1)
|
||||||
return len(db.session.query(WrongKeys).filter(WrongKeys.team == teamid, WrongKeys.date >= one_min_ago).all())
|
return len(db.session.query(WrongKeys).filter(WrongKeys.team == teamid, WrongKeys.date >= one_min_ago).all())
|
||||||
|
|
||||||
|
|
||||||
def get_config(key):
|
def get_config(key):
|
||||||
config = Config.query.filter_by(key=key).first()
|
config = Config.query.filter_by(key=key).first()
|
||||||
if config:
|
if config:
|
||||||
@@ -121,6 +134,7 @@ def get_config(key):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def set_config(key, value):
|
def set_config(key, value):
|
||||||
config = Config.query.filter_by(key=key).first()
|
config = Config.query.filter_by(key=key).first()
|
||||||
if config:
|
if config:
|
||||||
@@ -137,6 +151,7 @@ def mailserver():
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def sendmail(addr, text):
|
def sendmail(addr, text):
|
||||||
try:
|
try:
|
||||||
msg = Message("Message from {0}".format(app.config['CTF_NAME']), sender = app.config['ADMINS'][0], recipients = [addr])
|
msg = Message("Message from {0}".format(app.config['CTF_NAME']), sender = app.config['ADMINS'][0], recipients = [addr])
|
||||||
@@ -146,14 +161,20 @@ def sendmail(addr, text):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def rmdir(dir):
|
||||||
|
shutil.rmtree(dir, ignore_errors=True)
|
||||||
|
|
||||||
def is_safe_url(target):
|
def is_safe_url(target):
|
||||||
ref_url = urlparse(request.host_url)
|
ref_url = urlparse(request.host_url)
|
||||||
test_url = urlparse(urljoin(request.host_url, target))
|
test_url = urlparse(urljoin(request.host_url, target))
|
||||||
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
|
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
|
||||||
|
|
||||||
|
|
||||||
def sha512(string):
|
def sha512(string):
|
||||||
return hashlib.sha512(string).hexdigest()
|
return hashlib.sha512(string).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def get_digitalocean():
|
def get_digitalocean():
|
||||||
token = get_config('do_api_key')
|
token = get_config('do_api_key')
|
||||||
if token:
|
if token:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Flask-SQLAlchemy==2.0
|
|||||||
Flask-Session==0.1.1
|
Flask-Session==0.1.1
|
||||||
SQLAlchemy==0.9.8
|
SQLAlchemy==0.9.8
|
||||||
passlib==1.6.2
|
passlib==1.6.2
|
||||||
py-bcrypt==0.4
|
bcrypt
|
||||||
six==1.8.0
|
six==1.8.0
|
||||||
itsdangerous
|
itsdangerous
|
||||||
python-digitalocean
|
python-digitalocean
|
||||||
@@ -4,14 +4,14 @@ function loadchal(id) {
|
|||||||
obj = $.grep(challenges['game'], function (e) {
|
obj = $.grep(challenges['game'], function (e) {
|
||||||
return e.id == id;
|
return e.id == id;
|
||||||
})[0]
|
})[0]
|
||||||
$('#update-challenge .chal-name').val(obj.name)
|
$('.chal-name').val(obj.name);
|
||||||
$('#update-challenge .chal-desc').html(obj.description)
|
$('.chal-desc').html(obj.description);
|
||||||
$('#update-challenge .chal-value').val(obj.value)
|
$('.chal-value').val(obj.value);
|
||||||
$('#update-challenge .chal-category').val(obj.category)
|
$('.chal-category').val(obj.category);
|
||||||
$('#update-challenge .chal-id').val(obj.id)
|
$('.chal-id').val(obj.id);
|
||||||
$('#update-challenge .chal-delete').attr({
|
//$('#update-challenge .chal-delete').attr({
|
||||||
'href': '/admin/chal/close/' + (id + 1)
|
// 'href': '/admin/chal/close/' + (id + 1)
|
||||||
})
|
//})
|
||||||
$('#update-challenge').foundation('reveal', 'open');
|
$('#update-challenge').foundation('reveal', 'open');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -27,14 +27,14 @@ function submitkey(chal, key) {
|
|||||||
|
|
||||||
function loadkeys(chal){
|
function loadkeys(chal){
|
||||||
$.get('/admin/keys/' + chal, function(data){
|
$.get('/admin/keys/' + chal, function(data){
|
||||||
$('#keys-chal').val(chal)
|
$('#keys-chal').val(chal);
|
||||||
keys = $.parseJSON(JSON.stringify(data));
|
keys = $.parseJSON(JSON.stringify(data));
|
||||||
keys = keys['keys']
|
keys = keys['keys'];
|
||||||
$('#current-keys').empty()
|
$('#current-keys').empty();
|
||||||
for(x=0; x<keys.length; x++){
|
for(x=0; x<keys.length; x++){
|
||||||
$('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key))
|
$('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key));
|
||||||
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static')
|
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static');
|
||||||
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex')
|
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex');
|
||||||
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
|
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -76,6 +76,10 @@ function deletetag(tagid){
|
|||||||
$.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
$.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deletechal(chalid){
|
||||||
|
$.post('/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
||||||
|
}
|
||||||
|
|
||||||
function updatetags(){
|
function updatetags(){
|
||||||
tags = [];
|
tags = [];
|
||||||
chal = $('#tags-chal').val()
|
chal = $('#tags-chal').val()
|
||||||
@@ -115,6 +119,7 @@ function deletefile(chal, file, elem){
|
|||||||
|
|
||||||
|
|
||||||
function loadchals(){
|
function loadchals(){
|
||||||
|
$('#challenges').empty();
|
||||||
$.post("/admin/chals", {
|
$.post("/admin/chals", {
|
||||||
'nonce': $('#nonce').val()
|
'nonce': $('#nonce').val()
|
||||||
}, function (data) {
|
}, function (data) {
|
||||||
@@ -169,6 +174,20 @@ $('#submit-tags').click(function (e) {
|
|||||||
updatetags()
|
updatetags()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#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');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
$(".tag-insert").keyup(function (e) {
|
$(".tag-insert").keyup(function (e) {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode == 13) {
|
||||||
tag = $('.tag-insert').val()
|
tag = $('.tag-insert').val()
|
||||||
@@ -184,14 +203,14 @@ $(".tag-insert").keyup(function (e) {
|
|||||||
$('.create-category').click(function (e) {
|
$('.create-category').click(function (e) {
|
||||||
$('#new-category').foundation('reveal', 'open');
|
$('#new-category').foundation('reveal', 'open');
|
||||||
});
|
});
|
||||||
count = 1
|
count = 1;
|
||||||
$('#create-key').click(function (e) {
|
$('#create-key').click(function (e) {
|
||||||
$('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>");
|
$('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>");
|
||||||
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="0">Static');
|
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="0">Static');
|
||||||
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="1">Regex');
|
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="1">Regex');
|
||||||
count++
|
count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function(){
|
$(function(){
|
||||||
loadchals()
|
loadchals();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<h3>New Challenge</h3>
|
<h3>New Challenge</h3>
|
||||||
<input type='text' name='name' placeholder='Name'><br/>
|
<input type='text' name='name' placeholder='Name'><br/>
|
||||||
<textarea name='desc' placeholder='Description'></textarea><br/>
|
<textarea name='desc' placeholder='Description'></textarea><br/>
|
||||||
<input type='text' name='value' placeholder='Value'><br/>
|
<input type='number' name='value' placeholder='Value'><br/>
|
||||||
<input type='text' name='key' placeholder='Key'><br/>
|
<input type='text' name='key' placeholder='Key'><br/>
|
||||||
<input type="radio" name="key_type[0]" value="0">Static
|
<input type="radio" name="key_type[0]" value="0">Static
|
||||||
<input type="radio" name="key_type[0]" value="1">Regex
|
<input type="radio" name="key_type[0]" value="1">Regex
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
<input type='text' name='name' placeholder='Name'><br/>
|
<input type='text' name='name' placeholder='Name'><br/>
|
||||||
<textarea class="textbox" name='desc' placeholder='Description'></textarea><br/>
|
<textarea class="textbox" name='desc' placeholder='Description'></textarea><br/>
|
||||||
<input type='text' name='value' placeholder='Value'><br/>
|
<input type='number' name='value' placeholder='Value'><br/>
|
||||||
<input id="new-chal-category" type="hidden" name="category">
|
<input id="new-chal-category" type="hidden" name="category">
|
||||||
<input type='text' name='key' placeholder='Key'><br/>
|
<input type='text' name='key' placeholder='Key'><br/>
|
||||||
|
|
||||||
@@ -49,6 +49,21 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="delete-chal" class="reveal-modal" data-reveal>
|
||||||
|
<h2 class="text-center">Delete Challenge</h2>
|
||||||
|
<form method="POST" action="/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>
|
||||||
|
<button type="button" class="button alert radius" onclick="$('#delete-chal').foundation('reveal', 'close');">No</button>
|
||||||
|
<button type="submit" id="delete-user" class="button success radius">Yes</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="update-keys" class="reveal-modal" data-reveal>
|
<div id="update-keys" class="reveal-modal" data-reveal>
|
||||||
<form method="POST" action="/admin/keys">
|
<form method="POST" action="/admin/keys">
|
||||||
<h3>Keys</h3>
|
<h3>Keys</h3>
|
||||||
@@ -100,7 +115,7 @@
|
|||||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
<input class="chal-name" type='text' name='name' placeholder='Name'><br/>
|
<input class="chal-name" type='text' name='name' placeholder='Name'><br/>
|
||||||
<textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/>
|
<textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/>
|
||||||
<input class="chal-value" type='text' name='value' placeholder='Value'><br/>
|
<input class="chal-value" type='number' name='value' placeholder='Value'><br/>
|
||||||
<select class="chal-category" name="category">
|
<select class="chal-category" name="category">
|
||||||
<option>-</option>
|
<option>-</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -109,6 +124,7 @@
|
|||||||
<a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a>
|
<a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a>
|
||||||
<a href="#" data-reveal-id="update-files" class="secondary button">Files</a>
|
<a href="#" data-reveal-id="update-files" class="secondary button">Files</a>
|
||||||
<a href="#" data-reveal-id="update-keys" class="secondary button">Keys</a>
|
<a href="#" data-reveal-id="update-keys" class="secondary button">Keys</a>
|
||||||
|
<a href="#" data-reveal-id="delete-chal" class="secondary alert button">Delete</a>
|
||||||
<button type='submit'>Update</button>
|
<button type='submit'>Update</button>
|
||||||
<a class="close-reveal-modal">×</a>
|
<a class="close-reveal-modal">×</a>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user