Fork project
This commit is contained in:
24
templates/config/base.router.config.html
Normal file
24
templates/config/base.router.config.html
Normal file
@ -0,0 +1,24 @@
|
||||
<div class="tab-pane fade" id="router" role="tabpanel">
|
||||
{% set value = get_config('whale:router_type') %}
|
||||
{% set cur_type = get_config("whale:router_type", "frp") %}
|
||||
<div class="form-group">
|
||||
<label for="router-type">
|
||||
Router type
|
||||
<small class="form-text text-muted">
|
||||
Select which router backend to use
|
||||
</small>
|
||||
</label>
|
||||
<select id="router-type" class="form-control custom-select" onchange="window.updateConfigs">
|
||||
{% for type in ["frp", "trp"] %}
|
||||
<option value="{{ type }}" {{ "selected" if value == type }}>{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% set template = "config/" + cur_type + ".router.config.html" %}
|
||||
{% include template %}
|
||||
<div class="submit-row float-right">
|
||||
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
25
templates/config/challenges.config.html
Normal file
25
templates/config/challenges.config.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="tab-pane fade" id="challenges" role="tabpanel">
|
||||
{% for config, val in {
|
||||
"Subdomain Template": ("template_http_subdomain", "Controls how the subdomain of a container is generated"),
|
||||
"Flag Template": ("template_chall_flag", "Controls how a flag is generated"),
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">
|
||||
{{ val[1] }}
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}"
|
||||
{% if value != None %}value="{{ value }}"{% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="submit-row float-right">
|
||||
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
122
templates/config/docker.config.html
Normal file
122
templates/config/docker.config.html
Normal file
@ -0,0 +1,122 @@
|
||||
<div class="tab-pane fade show active" id="docker" role="tabpanel" aria-autocomplete="none">
|
||||
<h5>Common</h5>
|
||||
<small class="form-text text-muted">
|
||||
Common configurations for both standalone and grouped containers
|
||||
</small><br>
|
||||
{% for config, val in {
|
||||
"API URL": ("docker_api_url", "Docker API to connect to"),
|
||||
"Credentials": ("docker_credentials", "docker.io username and password, separated by ':'. useful for private images"),
|
||||
"Swarm Nodes": ("docker_swarm_nodes", "Will pick up one from it, You should set your node with label name=windows-* or name=linux-*. Separated by commas."),
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">{{ val[1] }}</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}"
|
||||
{% if value != None %}value="{{ value }}"{% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% set use_ssl = get_config('whale:docker_use_ssl') %}
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="docker-use-ssl" name="whale:docker_use_ssl"
|
||||
{% if use_ssl == True %}checked{% endif %}>
|
||||
<label for="docker-use-ssl">Use SSL</label>
|
||||
</div>
|
||||
<div class="container" id="docker-ssl-config">
|
||||
<div class="form-group">
|
||||
<label for="docker-ssl-ca-cert">
|
||||
SSL CA Certificate
|
||||
<small class="form-text text-muted">
|
||||
the location of the CA certificate file used in ssl connection
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="docker-ssl-ca-cert" name="whale:docker_ssl_ca_cert"
|
||||
value="{{ get_config('whale:docker_ssl_ca_cert') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="docker-ssl-client-cert">
|
||||
SSL Client Certificate
|
||||
<small class="form-text text-muted">
|
||||
the location of the client certificate file used in ssl connection
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="docker-ssl-client-cert" name="whale:docker_ssl_client_cert"
|
||||
value="{{ get_config('whale:docker_ssl_client_cert') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="docker-ssl-client-key">
|
||||
SSL Client Key
|
||||
<small class="form-text text-muted">
|
||||
the location of the client key file used in ssl connection
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="docker-ssl-client-key" name="whale:docker_ssl_client_key"
|
||||
value="{{ get_config('whale:docker_ssl_client_key') }}">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
let config = document.getElementById('docker-ssl-config');
|
||||
let option = document.getElementById('docker-use-ssl');
|
||||
config.hidden = !option.checked;
|
||||
option.onclick = () => (config.hidden = !option.checked) || true;
|
||||
}) ()
|
||||
</script>
|
||||
<hr>
|
||||
<h5>Standalone Containers</h5>
|
||||
<small class="form-text text-muted">
|
||||
Typical challenges. Under most circumstances you only need to set these.
|
||||
</small><br>
|
||||
{% for config, val in {
|
||||
"Auto Connect Network": ("docker_auto_connect_network", "The network connected for single-containers. It's usually the same network as the frpc is in."),
|
||||
"Dns Setting": ("docker_dns", "Decide which dns will be used in container network."),
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">
|
||||
{{ val[1] }}
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}"
|
||||
{% if value != None %}value="{{ value }}"{% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<hr>
|
||||
<h5>Grouped Containers</h5>
|
||||
<small class="form-text text-muted">
|
||||
Designed for multi-container challenges
|
||||
</small><br>
|
||||
{% for config, val in {
|
||||
"Auto Connect Containers": ("docker_auto_connect_containers","Decide which container will be connected to multi-container-network automatically. Separated by commas."),
|
||||
"Multi-Container Network Subnet": ("docker_subnet", "Subnet which will be used by auto created networks for multi-container challenges."),
|
||||
"Multi-Container Network Subnet New Prefix": ("docker_subnet_new_prefix", "Prefix for auto created network.")
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">
|
||||
{{ val[1] }}
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}"
|
||||
{% if value != None %}value="{{ value }}"{% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="submit-row float-right">
|
||||
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
50
templates/config/frp.router.config.html
Normal file
50
templates/config/frp.router.config.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% for config, val in {
|
||||
"API URL": ("frp_api_url", "Frp API to connect to"),
|
||||
"Http Domain Suffix": ("frp_http_domain_suffix", "Will be appended to the hash of a container"),
|
||||
"External Http Port": ("frp_http_port", "Keep in sync with frps:vhost_http_port"),
|
||||
"Direct IP Address":("frp_direct_ip_address","For direct redirect"),
|
||||
"Direct Minimum Port": ("frp_direct_port_minimum", "For direct redirect (pwn challenges)"),
|
||||
"Direct Maximum Port": ("frp_direct_port_maximum", "For direct redirect (pwn challenges)"),
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">
|
||||
{{ val[1] }}
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}" {% if
|
||||
value !=None %}value="{{ value }}" {% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% set frpc_template = get_config("whale:frp_config_template", "") %}
|
||||
<div class="form-group">
|
||||
<label for="frp-config-template">
|
||||
Frpc config template
|
||||
<small class="form-text text-muted">
|
||||
Frp config template, only need common section!
|
||||
</small>
|
||||
</label>
|
||||
<textarea class="form-control input-filled-valid" id="frp-config-template" rows="7"
|
||||
name="whale:frp_config_template">{{ frpc_template }}</textarea>
|
||||
</div>
|
||||
{% if frpc_template %}
|
||||
<div class="form-group">
|
||||
<label for="frps-config-template">
|
||||
Frps config template [generated]
|
||||
<small class="form-text text-muted">
|
||||
This configuration is generated with your settings above.
|
||||
</small>
|
||||
</label>
|
||||
<textarea class="form-control input-filled-valid grey-text" id="frps-config-template" rows="6" disabled>
|
||||
[common]
|
||||
{% for i in frpc_template.split('\n') %}
|
||||
{%- if 'token' in i -%}{{ i }}{%- endif -%}
|
||||
{%- if 'server_port' in i -%}{{ i.replace('server_port', 'bind_port') }}{%- endif -%}
|
||||
{% endfor %}
|
||||
vhost_http_port = {{ get_config('whale:frp_http_port') }}
|
||||
subdomain_host = {{ get_config('whale:frp_http_domain_suffix', '127.0.0.1.xip.io').lstrip('.') }}
|
||||
</textarea>
|
||||
</div>
|
||||
{% endif %}
|
||||
26
templates/config/limits.config.html
Normal file
26
templates/config/limits.config.html
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="tab-pane fade" id="limits" role="tabpanel">
|
||||
{% for config, val in {
|
||||
"Max Container Count": ("docker_max_container_count", "The maximum number of countainers allowed on the server"),
|
||||
"Max Renewal Times": ("docker_max_renew_count", "The maximum times a user is allowed to renew a container"),
|
||||
"Docker Container Timeout": ("docker_timeout", "A container times out after [timeout] seconds."),
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">
|
||||
{{ val[1] }}
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control"
|
||||
id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}"
|
||||
{% if value != None %}value="{{ value }}"{% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="submit-row float-right">
|
||||
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
17
templates/config/trp.router.config.html
Normal file
17
templates/config/trp.router.config.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% for config, val in {
|
||||
"API URL": ("trp_api_url", "trp API to connect to"),
|
||||
"Domain Suffix": ("trp_domain_suffix", "Will be used to generated the access link of a challenge"),
|
||||
"Listening Port": ("trp_listening_port", "Will be used to generated the access link of a challenge"),
|
||||
}.items() %}
|
||||
{% set value = get_config('whale:' + val[0]) %}
|
||||
<div class="form-group">
|
||||
<label for="{{ val[0].replace('_', '-') }}">
|
||||
{{ config }}
|
||||
<small class="form-text text-muted">
|
||||
{{ val[1] }}
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="{{ val[0].replace('_', '-') }}" name="{{ 'whale:' + val[0] }}"
|
||||
{% if value != None %}value="{{ value }}" {% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
57
templates/containers/card.containers.html
Normal file
57
templates/containers/card.containers.html
Normal file
@ -0,0 +1,57 @@
|
||||
<style>
|
||||
.info-card.card {
|
||||
height: 11rem;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-text:hover {
|
||||
white-space: pre-line;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="row">
|
||||
{% for container in containers %}
|
||||
<div class="col-sm-6 pb-3">
|
||||
<div class="info-card card">
|
||||
<div class="card-body">
|
||||
<h5 class="d-inline-block card-title">
|
||||
<a style="width: 5rem;"
|
||||
href="{{ url_for('admin.challenges_detail', challenge_id=container.challenge.id) }}"
|
||||
>{{ container.challenge.name | truncate(15) }}
|
||||
</a>
|
||||
</h5>
|
||||
<h6 class="d-inline-block card-subtitle float-right">
|
||||
<a style="width: 5rem;"
|
||||
class="btn btn-outline-secondary rounded"
|
||||
href="{{ url_for('admin.users_detail', user_id=container.user.id) }}"
|
||||
>{{ container.user.name | truncate(5) }}
|
||||
</a>
|
||||
</h6>
|
||||
<p class="card-text">{{ container.user_access }}</p>
|
||||
<p class="card-text">{{ container.flag }}</p>
|
||||
Time Started: {{ container.start_time }}
|
||||
<a class="delete-container float-right" container-id="{{ container.id }}"
|
||||
data-toggle="tooltip" data-placement="top"
|
||||
user-id="{{ container.user.id }}"
|
||||
style="margin-right: 0.5rem;"
|
||||
title="Destroy Container #{{ container.id }}">
|
||||
<i class="fas fa-stop-circle"></i>
|
||||
</a>
|
||||
<a class="renew-container float-right" container-id="{{ container.id }}"
|
||||
data-toggle="tooltip" data-placement="top"
|
||||
user-id="{{ container.user.id }}"
|
||||
style="margin-right: 0.5rem;"
|
||||
title="Renew Container #{{ container.id }}">
|
||||
<i class="fas fa-clock"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
78
templates/containers/list.containers.html
Normal file
78
templates/containers/list.containers.html
Normal file
@ -0,0 +1,78 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-right" data-checkbox>
|
||||
<div class="form-check text-center">
|
||||
<input type="checkbox" class="form-check-input" data-checkbox-all>
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort-col text-center"><b>ID</b></td>
|
||||
<th class="text-center"><b>User</b></td>
|
||||
<th class="sort-col text-center"><b>Challenge</b></td>
|
||||
<th class="text-center"><b>Access Method</b></td>
|
||||
<th class="text-center"><b>Flag</b></td>
|
||||
<th class="sort-col text-center"><b>Startup Time</b></td>
|
||||
<th class="sort-col text-center"><b>Renewal Times</b></td>
|
||||
<th class="text-center"><b>Delete</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for container in containers %}
|
||||
<tr>
|
||||
<td class="border-right" data-checkbox>
|
||||
<div class="form-check text-center">
|
||||
<input type="checkbox" class="form-check-input" data-user-id="{{ container.user.id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ container.id }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ url_for('admin.users_detail', user_id=container.user.id) }}">
|
||||
{{ container.user.name | truncate(12) }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ url_for('admin.challenges_detail', challenge_id=container.challenge.id) }}">
|
||||
{{ container.challenge.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ container.challenge.redirect_type }}
|
||||
<button class="btn btn-link p-0 click-copy" data-copy="{{ container.user_access }}">
|
||||
<i class="fas fa-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-link p-0 click-copy" data-copy="{{ container.flag }}">
|
||||
<i class="fas fa-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span data-time="{{ container.start_time | isoformat }}"></span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ container.renew_count }}
|
||||
<button class="btn btn-link p-0 renew-container"
|
||||
container-id="{{ container.id }}" data-toggle="tooltip"
|
||||
user-id="{{ container.user.id }}" data-placement="top"
|
||||
title="Renew Container #{{ container.id }}">
|
||||
<i class="fas fa-sync"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-link p-0 delete-container"
|
||||
container-id="{{ container.id }}" data-toggle="tooltip"
|
||||
user-id="{{ container.user.id }}" data-placement="top"
|
||||
title="Destroy Container #{{ container.id }}">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
25
templates/whale_base.html
Normal file
25
templates/whale_base.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<h1>CTFd Whale</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-pills flex-column">
|
||||
{% block menu %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="tab-content">
|
||||
{% block panel %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
38
templates/whale_config.html
Normal file
38
templates/whale_config.html
Normal file
@ -0,0 +1,38 @@
|
||||
{% extends "whale_base.html" %}
|
||||
|
||||
{% block menu %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="pill" href="#docker">Docker</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="pill" href="#router">Router</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="pill" href="#limits">Limits</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="pill" href="#challenges">Challenges</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/plugins/ctfd-whale/admin/containers">🔗 Instances</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% include "components/errors.html" %}
|
||||
<div role="tabpanel" class="tab-pane config-section active" id="settings">
|
||||
<form method="POST" accept-charset="utf-8" action="/admin/plugins/ctfd-whale"
|
||||
class="form-horizontal">
|
||||
<div class="tab-content">
|
||||
{% include "config/docker.config.html" %}
|
||||
{% include "config/base.router.config.html" %}
|
||||
{% include "config/limits.config.html" %}
|
||||
{% include "config/challenges.config.html" %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script defer src="{{ url_for('plugins.ctfd-whale.assets', path='config.js') }}"></script>
|
||||
{% endblock %}
|
||||
69
templates/whale_containers.html
Normal file
69
templates/whale_containers.html
Normal file
@ -0,0 +1,69 @@
|
||||
{% extends "whale_base.html" %}
|
||||
|
||||
{% block menu %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/plugins/ctfd-whale/admin/settings">🔗 Settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">Instances</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-link">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-toggle="tooltip" title="Renew Containers" id="containers-renew-button">
|
||||
<i class="btn-fa fas fa-sync"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
data-toggle="tooltip" title="Stop Containers" id="containers-delete-button">
|
||||
<i class="btn-fa fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-link">
|
||||
<ul class="pagination">
|
||||
<li class="page-item{{ ' disabled' if curr_page <= 1 else '' }}">
|
||||
<a class="page-link" aria-label="Previous"
|
||||
href="/plugins/ctfd-whale/admin/containers?page={{ curr_page - 1 }}"
|
||||
>
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
{% set range_l = [[curr_page - 1, 1]|max, [pages - 3, 1]|max]|min %}
|
||||
{% set range_r = [[curr_page + 2, pages]|min, [4, pages]|min]|max %}
|
||||
{% for page in range(range_l, range_r + 1) %}
|
||||
<li class="page-item{{ ' active' if curr_page == page }}">
|
||||
<a class="page-link"
|
||||
href="/plugins/ctfd-whale/admin/containers?page={{ page }}"
|
||||
>{{ page }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="page-item{{ ' disabled' if curr_page >= pages else '' }}">
|
||||
<a class="page-link" aria-label="Next"
|
||||
href="/plugins/ctfd-whale/admin/containers?page={{ curr_page + 1 }}"
|
||||
>
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-link">
|
||||
{% if session['view_mode'] == 'card' %}
|
||||
<a href="?mode=list">Switch to list mode</a>
|
||||
{% else %}
|
||||
<a href="?mode=card">Switch to card mode</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% include "containers/" + session["view_mode"] + ".containers.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script defer src="{{ url_for('plugins.ctfd-whale.assets', path='containers.js') }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user