Fork project
This commit is contained in:
		
							
								
								
									
										27
									
								
								assets/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								assets/config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
const $ = CTFd.lib.$;
 | 
			
		||||
 | 
			
		||||
$(".config-section > form:not(.form-upload)").submit(async function (event) {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    const obj = $(this).serializeJSON();
 | 
			
		||||
    const params = {};
 | 
			
		||||
    for (let x in obj) {
 | 
			
		||||
        if (obj[x] === "true") {
 | 
			
		||||
            params[x] = true;
 | 
			
		||||
        } else if (obj[x] === "false") {
 | 
			
		||||
            params[x] = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            params[x] = obj[x];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    params['whale:refresh'] = btoa(+new Date).slice(-7, -2);
 | 
			
		||||
 | 
			
		||||
    await CTFd.api.patch_config_list({}, params);
 | 
			
		||||
    location.reload();
 | 
			
		||||
});
 | 
			
		||||
$(".config-section > form:not(.form-upload) > div > div > div > #router-type").change(async function () {
 | 
			
		||||
    await CTFd.api.patch_config_list({}, {
 | 
			
		||||
        'whale:router_type': $(this).val(),
 | 
			
		||||
        'whale:refresh': btoa(+new Date).slice(-7, -2),
 | 
			
		||||
    });
 | 
			
		||||
    location.reload();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										120
									
								
								assets/containers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								assets/containers.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
			
		||||
const $ = CTFd.lib.$;
 | 
			
		||||
 | 
			
		||||
function htmlentities(str) {
 | 
			
		||||
    return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function copyToClipboard(event, str) {
 | 
			
		||||
    // Select element
 | 
			
		||||
    const el = document.createElement('textarea');
 | 
			
		||||
    el.value = str;
 | 
			
		||||
    el.setAttribute('readonly', '');
 | 
			
		||||
    el.style.position = 'absolute';
 | 
			
		||||
    el.style.left = '-9999px';
 | 
			
		||||
    document.body.appendChild(el);
 | 
			
		||||
    el.select();
 | 
			
		||||
    document.execCommand('copy');
 | 
			
		||||
    document.body.removeChild(el);
 | 
			
		||||
 | 
			
		||||
    $(event.target).tooltip({
 | 
			
		||||
        title: "Copied!",
 | 
			
		||||
        trigger: "manual"
 | 
			
		||||
    });
 | 
			
		||||
    $(event.target).tooltip("show");
 | 
			
		||||
 | 
			
		||||
    setTimeout(function () {
 | 
			
		||||
        $(event.target).tooltip("hide");
 | 
			
		||||
    }, 1500);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$(".click-copy").click(function (e) {
 | 
			
		||||
    copyToClipboard(e, $(this).data("copy"));
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
async function delete_container(user_id) {
 | 
			
		||||
    let response = await CTFd.fetch("/api/v1/plugins/ctfd-whale/admin/container?user_id=" + user_id, {
 | 
			
		||||
        method: "DELETE",
 | 
			
		||||
        credentials: "same-origin",
 | 
			
		||||
        headers: {
 | 
			
		||||
            Accept: "application/json",
 | 
			
		||||
            "Content-Type": "application/json"
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    response = await response.json();
 | 
			
		||||
    return response.success;
 | 
			
		||||
}
 | 
			
		||||
async function renew_container(user_id) {
 | 
			
		||||
    let response = await CTFd.fetch(
 | 
			
		||||
        "/api/v1/plugins/ctfd-whale/admin/container?user_id=" + user_id, {
 | 
			
		||||
        method: "PATCH",
 | 
			
		||||
        credentials: "same-origin",
 | 
			
		||||
        headers: {
 | 
			
		||||
            Accept: "application/json",
 | 
			
		||||
            "Content-Type": "application/json"
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    response = await response.json();
 | 
			
		||||
    return response.success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$('#containers-renew-button').click(function (e) {
 | 
			
		||||
    let users = $("input[data-user-id]:checked").map(function () {
 | 
			
		||||
        return $(this).data("user-id");
 | 
			
		||||
    });
 | 
			
		||||
    CTFd.ui.ezq.ezQuery({
 | 
			
		||||
        title: "Renew Containers",
 | 
			
		||||
        body: `Are you sure you want to renew the selected ${users.length} container(s)?`,
 | 
			
		||||
        success: async function () {
 | 
			
		||||
            await Promise.all(users.toArray().map((user) => renew_container(user)));
 | 
			
		||||
            location.reload();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('#containers-delete-button').click(function (e) {
 | 
			
		||||
    let users = $("input[data-user-id]:checked").map(function () {
 | 
			
		||||
        return $(this).data("user-id");
 | 
			
		||||
    });
 | 
			
		||||
    CTFd.ui.ezq.ezQuery({
 | 
			
		||||
        title: "Delete Containers",
 | 
			
		||||
        body: `Are you sure you want to delete the selected ${users.length} container(s)?`,
 | 
			
		||||
        success: async function () {
 | 
			
		||||
            await Promise.all(users.toArray().map((user) => delete_container(user)));
 | 
			
		||||
            location.reload();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$(".delete-container").click(function (e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    let container_id = $(this).attr("container-id");
 | 
			
		||||
    let user_id = $(this).attr("user-id");
 | 
			
		||||
 | 
			
		||||
    CTFd.ui.ezq.ezQuery({
 | 
			
		||||
        title: "Destroy Container",
 | 
			
		||||
        body: "<span>Are you sure you want to delete <strong>Container #{0}</strong>?</span>".format(
 | 
			
		||||
            htmlentities(container_id)
 | 
			
		||||
        ),
 | 
			
		||||
        success: async function () {
 | 
			
		||||
            await delete_container(user_id);
 | 
			
		||||
            location.reload();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$(".renew-container").click(function (e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    let container_id = $(this).attr("container-id");
 | 
			
		||||
    let user_id = $(this).attr("user-id");
 | 
			
		||||
 | 
			
		||||
    CTFd.ui.ezq.ezQuery({
 | 
			
		||||
        title: "Renew Container",
 | 
			
		||||
        body: "<span>Are you sure you want to renew <strong>Container #{0}</strong>?</span>".format(
 | 
			
		||||
            htmlentities(container_id)
 | 
			
		||||
        ),
 | 
			
		||||
        success: async function () {
 | 
			
		||||
            await renew_container(user_id);
 | 
			
		||||
            location.reload();
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										100
									
								
								assets/create.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								assets/create.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
			
		||||
{% extends "admin/challenges/create.html" %}
 | 
			
		||||
 | 
			
		||||
{% block header %}
 | 
			
		||||
    <div class="alert alert-secondary" role="alert">
 | 
			
		||||
        Dynamic docker challenge allows players to deploy their per-challenge standalone instances.
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block value %}
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Docker Image<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The docker image used to deploy
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="text" class="form-control" name="docker_image" placeholder="Enter docker image name" required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Frp Redirect Type<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                Decide the redirect type how frp redirect traffic
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <select class="form-control" name="redirect_type">
 | 
			
		||||
            <option value="http" selected>HTTP</option>
 | 
			
		||||
            <option value="direct">Direct</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Frp Redirect Port<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                Decide which port in the instance that frp should redirect traffic for
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="redirect_port" placeholder="Enter the port you want to open"
 | 
			
		||||
               required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Docker Container Memory Limit<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The memory usage limit
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="text" class="form-control" name="memory_limit" placeholder="Enter the memory limit" value="128m"
 | 
			
		||||
               required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Docker Container CPU Limit<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The CPU usage limit
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="cpu_limit" placeholder="Enter the cpu limit" value="0.5"
 | 
			
		||||
               required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Initial Value<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                This is how many points the challenge is worth initially.
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="value" placeholder="Enter value" required>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Decay Limit<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The amount of solves before the challenge reaches its minimum value
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="decay" placeholder="Enter decay limit" required>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Minimum Value<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                This is the lowest that the challenge can be worth
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="minimum" placeholder="Enter minimum value" required>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Score Type<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                Decide it use dynamic score or not
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
 | 
			
		||||
        <select class="form-control" name="dynamic_score">
 | 
			
		||||
            <option value="0" selected>Static Score</option>
 | 
			
		||||
            <option value="1">Dynamic Score</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block type %}
 | 
			
		||||
    <input type="hidden" value="dynamic_docker" name="type" id="chaltype">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										30
									
								
								assets/create.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								assets/create.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// Markdown Preview
 | 
			
		||||
if ($ === undefined) $ = CTFd.lib.$
 | 
			
		||||
$('#desc-edit').on('shown.bs.tab', function(event) {
 | 
			
		||||
    if (event.target.hash == '#desc-preview') {
 | 
			
		||||
        var editor_value = $('#desc-editor').val();
 | 
			
		||||
        $(event.target.hash).html(
 | 
			
		||||
            CTFd._internal.challenge.render(editor_value)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
$('#new-desc-edit').on('shown.bs.tab', function(event) {
 | 
			
		||||
    if (event.target.hash == '#new-desc-preview') {
 | 
			
		||||
        var editor_value = $('#new-desc-editor').val();
 | 
			
		||||
        $(event.target.hash).html(
 | 
			
		||||
            CTFd._internal.challenge.render(editor_value)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
$("#solve-attempts-checkbox").change(function() {
 | 
			
		||||
    if (this.checked) {
 | 
			
		||||
        $('#solve-attempts-input').show();
 | 
			
		||||
    } else {
 | 
			
		||||
        $('#solve-attempts-input').hide();
 | 
			
		||||
        $('#max_attempts').val('');
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$(document).ready(function() {
 | 
			
		||||
    $('[data-toggle="tooltip"]').tooltip();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										94
									
								
								assets/update.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								assets/update.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
{% extends "admin/challenges/update.html" %}
 | 
			
		||||
 | 
			
		||||
{% block value %}
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Current Value<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                This is how many points the challenge is worth right now.
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control chal-value" name="value" value="{{ challenge.value }}" disabled>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Initial Value<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                This is how many points the challenge was worth initially.
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control chal-initial" name="initial" value="{{ challenge.initial }}" required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Decay Limit<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The amount of solves before the challenge reaches its minimum value
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control chal-decay" name="decay" value="{{ challenge.decay }}" required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Minimum Value<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                This is the lowest that the challenge can be worth
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control chal-minimum" name="minimum" value="{{ challenge.minimum }}" required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Docker Image<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The docker image used to deploy
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="text" class="form-control" name="docker_image" placeholder="Enter docker image name"
 | 
			
		||||
               required value="{{ challenge.docker_image }}">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Frp Redirect Type<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                Decide the redirect type how frp redirect traffic
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <select class="form-control" name="redirect_type">
 | 
			
		||||
            <option value="http" {% if challenge.redirect_type == "http" %}selected{% endif %}>HTTP</option>
 | 
			
		||||
            <option value="direct" {% if challenge.redirect_type == "direct" %}selected{% endif %}>Direct</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Frp Redirect Port<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                Decide which port in the instance that frp should redirect traffic for
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="redirect_port" placeholder="Enter the port you want to open"
 | 
			
		||||
               required value="{{ challenge.redirect_port }}">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Docker Container Memory Limit<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The memory usage limit
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="text" class="form-control" name="memory_limit" placeholder="Enter the memory limit"
 | 
			
		||||
               value="{{ challenge.memory_limit }}" required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Docker Container CPU Limit<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                The CPU usage limit
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <input type="number" class="form-control" name="cpu_limit" placeholder="Enter the cpu limit"
 | 
			
		||||
               value="{{ challenge.cpu_limit }}" required>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
        <label for="value">Score Type<br>
 | 
			
		||||
            <small class="form-text text-muted">
 | 
			
		||||
                Decide it use dynamic score or not
 | 
			
		||||
            </small>
 | 
			
		||||
        </label>
 | 
			
		||||
        <select class="form-control" name="dynamic_score">
 | 
			
		||||
            <option value="0" {% if challenge.dynamic_score == 0 %}selected{% endif %}>Static Score</option>
 | 
			
		||||
            <option value="1" {% if challenge.dynamic_score == 1 %}selected{% endif %}>Dynamic Score</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										52
									
								
								assets/update.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								assets/update.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
if ($ === undefined) $ = CTFd.lib.$
 | 
			
		||||
$('#submit-key').click(function(e) {
 | 
			
		||||
    submitkey($('#chalid').val(), $('#answer').val())
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('#submit-keys').click(function(e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    $('#update-keys').modal('hide');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('#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') {
 | 
			
		||||
        var editor_value = $('#desc-editor').val();
 | 
			
		||||
        $(event.target.hash).html(
 | 
			
		||||
            window.challenge.render(editor_value)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
$('#new-desc-edit').on('shown.bs.tab', function(event) {
 | 
			
		||||
    if (event.target.hash == '#new-desc-preview') {
 | 
			
		||||
        var editor_value = $('#new-desc-editor').val();
 | 
			
		||||
        $(event.target.hash).html(
 | 
			
		||||
            window.challenge.render(editor_value)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function loadchal(id, update) {
 | 
			
		||||
    $.get(script_root + '/admin/chal/' + id, function(obj) {
 | 
			
		||||
        $('#desc-write-link').click(); // Switch to Write tab
 | 
			
		||||
        if (typeof update === 'undefined')
 | 
			
		||||
            $('#update-challenge').modal();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openchal(id) {
 | 
			
		||||
    loadchal(id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$(document).ready(function() {
 | 
			
		||||
    $('[data-toggle="tooltip"]').tooltip();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										36
									
								
								assets/view.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								assets/view.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
{% extends "challenge.html" %}
 | 
			
		||||
 | 
			
		||||
{% block description %}
 | 
			
		||||
{{ challenge.html }}
 | 
			
		||||
    <div class="row text-center pb-3">
 | 
			
		||||
        <div id="whale-panel" style="width: 100%;">
 | 
			
		||||
            <div id="whale-panel-stopped" class="card" style="width: 100%;">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Instance Info</h5>
 | 
			
		||||
                    <button class="btn btn-primary card-link" id="whale-button-boot" type="button"
 | 
			
		||||
                        onclick="CTFd._internal.challenge.boot()">Launch an instance</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="whale-panel-started" type="hidden" class="card" style="width: 100%;">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Instance Info</h5>
 | 
			
		||||
                    <h6 class="card-subtitle mb-2 text-muted">
 | 
			
		||||
                        Remaining Time: <span id="whale-challenge-count-down"></span>s
 | 
			
		||||
                    </h6>
 | 
			
		||||
                    <h6 class="card-subtitle mb-2 text-muted">
 | 
			
		||||
                        Lan Domain: <span id="whale-challenge-lan-domain"></span>
 | 
			
		||||
                    </h6>
 | 
			
		||||
                    <p id="whale-challenge-user-access" class="card-text"></p>
 | 
			
		||||
                    <button type="button" class="btn btn-danger card-link" id="whale-button-destroy"
 | 
			
		||||
                            onclick="CTFd._internal.challenge.destroy()">
 | 
			
		||||
                        Destroy this instance
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button type="button" class="btn btn-success card-link" id="whale-button-renew"
 | 
			
		||||
                            onclick="CTFd._internal.challenge.renew()">
 | 
			
		||||
                        Renew this instance
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										239
									
								
								assets/view.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								assets/view.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,239 @@
 | 
			
		||||
CTFd._internal.challenge.data = undefined
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.renderer = null;
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.preRender = function () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.render = null;
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.postRender = function () {
 | 
			
		||||
    loadInfo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (window.$ === undefined) window.$ = CTFd.lib.$;
 | 
			
		||||
 | 
			
		||||
function loadInfo() {
 | 
			
		||||
    var challenge_id = CTFd._internal.challenge.data.id;
 | 
			
		||||
    var url = "/api/v1/plugins/ctfd-whale/container?challenge_id=" + challenge_id;
 | 
			
		||||
 | 
			
		||||
    CTFd.fetch(url, {
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
        credentials: 'same-origin',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Accept': 'application/json',
 | 
			
		||||
            'Content-Type': 'application/json'
 | 
			
		||||
        }
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.status === 429) {
 | 
			
		||||
            // User was ratelimited but process response
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        if (response.status === 403) {
 | 
			
		||||
            // User is not logged in or CTF is paused.
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        return response.json();
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (window.t !== undefined) {
 | 
			
		||||
            clearInterval(window.t);
 | 
			
		||||
            window.t = undefined;
 | 
			
		||||
        }
 | 
			
		||||
        if (response.success) response = response.data;
 | 
			
		||||
        else CTFd._functions.events.eventAlert({
 | 
			
		||||
            title: "Fail",
 | 
			
		||||
            html: response.message,
 | 
			
		||||
            button: "OK"
 | 
			
		||||
        });
 | 
			
		||||
        if (response.remaining_time != undefined) {
 | 
			
		||||
            $('#whale-challenge-user-access').html(response.user_access);
 | 
			
		||||
            $('#whale-challenge-lan-domain').html(response.lan_domain);
 | 
			
		||||
            $('#whale-challenge-count-down').text(response.remaining_time);
 | 
			
		||||
            $('#whale-panel-stopped').hide();
 | 
			
		||||
            $('#whale-panel-started').show();
 | 
			
		||||
 | 
			
		||||
            window.t = setInterval(() => {
 | 
			
		||||
                const c = $('#whale-challenge-count-down').text();
 | 
			
		||||
                if (!c) return;
 | 
			
		||||
                let second = parseInt(c) - 1;
 | 
			
		||||
                if (second <= 0) {
 | 
			
		||||
                    loadInfo();
 | 
			
		||||
                }
 | 
			
		||||
                $('#whale-challenge-count-down').text(second);
 | 
			
		||||
            }, 1000);
 | 
			
		||||
        } else {
 | 
			
		||||
            $('#whale-panel-started').hide();
 | 
			
		||||
            $('#whale-panel-stopped').show();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.destroy = function () {
 | 
			
		||||
    var challenge_id = CTFd._internal.challenge.data.id;
 | 
			
		||||
    var url = "/api/v1/plugins/ctfd-whale/container?challenge_id=" + challenge_id;
 | 
			
		||||
 | 
			
		||||
    $('#whale-button-destroy').text("Waiting...");
 | 
			
		||||
    $('#whale-button-destroy').prop('disabled', true);
 | 
			
		||||
 | 
			
		||||
    var params = {};
 | 
			
		||||
 | 
			
		||||
    CTFd.fetch(url, {
 | 
			
		||||
        method: 'DELETE',
 | 
			
		||||
        credentials: 'same-origin',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Accept': 'application/json',
 | 
			
		||||
            'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify(params)
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.status === 429) {
 | 
			
		||||
            // User was ratelimited but process response
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        if (response.status === 403) {
 | 
			
		||||
            // User is not logged in or CTF is paused.
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        return response.json();
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.success) {
 | 
			
		||||
            loadInfo();
 | 
			
		||||
            CTFd._functions.events.eventAlert({
 | 
			
		||||
                title: "Success",
 | 
			
		||||
                html: "Your instance has been destroyed!",
 | 
			
		||||
                button: "OK"
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            CTFd._functions.events.eventAlert({
 | 
			
		||||
                title: "Fail",
 | 
			
		||||
                html: response.message,
 | 
			
		||||
                button: "OK"
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }).finally(() => {
 | 
			
		||||
        $('#whale-button-destroy').text("Destroy this instance");
 | 
			
		||||
        $('#whale-button-destroy').prop('disabled', false);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.renew = function () {
 | 
			
		||||
    var challenge_id = CTFd._internal.challenge.data.id;
 | 
			
		||||
    var url = "/api/v1/plugins/ctfd-whale/container?challenge_id=" + challenge_id;
 | 
			
		||||
 | 
			
		||||
    $('#whale-button-renew').text("Waiting...");
 | 
			
		||||
    $('#whale-button-renew').prop('disabled', true);
 | 
			
		||||
 | 
			
		||||
    var params = {};
 | 
			
		||||
 | 
			
		||||
    CTFd.fetch(url, {
 | 
			
		||||
        method: 'PATCH',
 | 
			
		||||
        credentials: 'same-origin',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Accept': 'application/json',
 | 
			
		||||
            'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify(params)
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.status === 429) {
 | 
			
		||||
            // User was ratelimited but process response
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        if (response.status === 403) {
 | 
			
		||||
            // User is not logged in or CTF is paused.
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        return response.json();
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.success) {
 | 
			
		||||
            loadInfo();
 | 
			
		||||
            CTFd._functions.events.eventAlert({
 | 
			
		||||
                title: "Success",
 | 
			
		||||
                html: "Your instance has been renewed!",
 | 
			
		||||
                button: "OK"
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            CTFd._functions.events.eventAlert({
 | 
			
		||||
                title: "Fail",
 | 
			
		||||
                html: response.message,
 | 
			
		||||
                button: "OK"
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }).finally(() => {
 | 
			
		||||
        $('#whale-button-renew').text("Renew this instance");
 | 
			
		||||
        $('#whale-button-renew').prop('disabled', false);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.boot = function () {
 | 
			
		||||
    var challenge_id = CTFd._internal.challenge.data.id;
 | 
			
		||||
    var url = "/api/v1/plugins/ctfd-whale/container?challenge_id=" + challenge_id;
 | 
			
		||||
 | 
			
		||||
    $('#whale-button-boot').text("Waiting...");
 | 
			
		||||
    $('#whale-button-boot').prop('disabled', true);
 | 
			
		||||
 | 
			
		||||
    var params = {};
 | 
			
		||||
 | 
			
		||||
    CTFd.fetch(url, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        credentials: 'same-origin',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Accept': 'application/json',
 | 
			
		||||
            'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify(params)
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.status === 429) {
 | 
			
		||||
            // User was ratelimited but process response
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        if (response.status === 403) {
 | 
			
		||||
            // User is not logged in or CTF is paused.
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        return response.json();
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
        if (response.success) {
 | 
			
		||||
            loadInfo();
 | 
			
		||||
            CTFd._functions.events.eventAlert({
 | 
			
		||||
                title: "Success",
 | 
			
		||||
                html: "Your instance has been deployed!",
 | 
			
		||||
                button: "OK"
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            CTFd._functions.events.eventAlert({
 | 
			
		||||
                title: "Fail",
 | 
			
		||||
                html: response.message,
 | 
			
		||||
                button: "OK"
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }).finally(() => {
 | 
			
		||||
        $('#whale-button-boot').text("Launch an instance");
 | 
			
		||||
        $('#whale-button-boot').prop('disabled', false);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CTFd._internal.challenge.submit = function (preview) {
 | 
			
		||||
    var challenge_id = CTFd._internal.challenge.data.id;
 | 
			
		||||
    var submission = $('#challenge-input').val()
 | 
			
		||||
 | 
			
		||||
    var body = {
 | 
			
		||||
        'challenge_id': challenge_id,
 | 
			
		||||
        'submission': submission,
 | 
			
		||||
    }
 | 
			
		||||
    var params = {}
 | 
			
		||||
    if (preview)
 | 
			
		||||
        params['preview'] = true
 | 
			
		||||
 | 
			
		||||
    return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
 | 
			
		||||
        if (response.status === 429) {
 | 
			
		||||
            // User was ratelimited but process response
 | 
			
		||||
            return response
 | 
			
		||||
        }
 | 
			
		||||
        if (response.status === 403) {
 | 
			
		||||
            // User is not logged in or CTF is paused.
 | 
			
		||||
            return response
 | 
			
		||||
        }
 | 
			
		||||
        return response
 | 
			
		||||
    })
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user