summaryrefslogtreecommitdiffstats
path: root/web/views
diff options
context:
space:
mode:
authorLeonardo Bishop <me@leonardobishop.net>2026-01-07 23:39:53 +0000
committerLeonardo Bishop <me@leonardobishop.net>2026-01-07 23:39:53 +0000
commit03cd6bdfbd473dba3f3dc50a1b15e389aac5bc70 (patch)
tree5fea2b1840e298aaab953add749fb9226bd4a710 /web/views
Initial commit
Diffstat (limited to 'web/views')
-rw-r--r--web/views/auth.html51
-rw-r--r--web/views/f_auth_error.html3
-rw-r--r--web/views/f_deploy.html32
-rw-r--r--web/views/f_instance.html28
-rw-r--r--web/views/f_instance_result.html4
-rw-r--r--web/views/index.html98
6 files changed, 216 insertions, 0 deletions
diff --git a/web/views/auth.html b/web/views/auth.html
new file mode 100644
index 0000000..995815a
--- /dev/null
+++ b/web/views/auth.html
@@ -0,0 +1,51 @@
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Challenge Instancer</title>
+
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js" integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/htmx-ext-sse@2.2.4" integrity="sha384-A986SAtodyH8eg8x8irJnYUk7i9inVQqYigD6qZ9evobksGNIXfeFvDwLSHcp31N" crossorigin="anonymous"></script>
+</head>
+<body class="bg-light">
+
+<div class="container py-5">
+ <div class="row justify-content-center">
+ <div class="col-12 col-md-6 col-lg-4">
+ <div class="card mx-auto">
+ <div class="card-body">
+ <h4 class="card-title mb-3">Enter a team ID</h4>
+
+ <div id="auth-response"></div>
+
+ <form hx-post="/auth" hx-target="#auth-response" hx-swap="innerHTML">
+ <div class="input-group">
+ <span class="input-group-text">ID</span>
+ <input id="team" name="team" class="form-control" type="number" required>
+ </input>
+ <button id="deploy-btn" type="submit" class="btn btn-primary">
+ Continue
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+
+ <div class="card mt-4 mx-auto">
+ <div class="card-body">
+ <p class="card-text">
+ <b>Attacking this platform is out of scope of the CTF and is forbidden.</b>
+ If there are any issues, please speak to an organiser.
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/web/views/f_auth_error.html b/web/views/f_auth_error.html
new file mode 100644
index 0000000..4ebdac3
--- /dev/null
+++ b/web/views/f_auth_error.html
@@ -0,0 +1,3 @@
+<div class="alert alert-danger" role="alert">
+ {{.Message}}
+</div>
diff --git a/web/views/f_deploy.html b/web/views/f_deploy.html
new file mode 100644
index 0000000..52edd5f
--- /dev/null
+++ b/web/views/f_deploy.html
@@ -0,0 +1,32 @@
+<div
+ id="deploy-{{.DeployKey}}"
+ class="card mt-4"
+ hx-ext="sse"
+ hx-on::sse-close="document.getElementById('progress-{{.DeployKey}}').remove(); document.getElementById('close-deploy-{{.DeployKey}}').classList.remove('d-none');"
+ sse-connect="/deploy/stream?deploy={{.DeployKey}}"
+ sse-close="done">
+
+ <div class="card-body">
+
+ <div class="d-flex justify-content-between align-items-start">
+ <h5 class="card-title mb-3">Deploying {{.Challenge}}</h5>
+ <button id="close-deploy-{{.DeployKey}}" type="button" class="btn-close d-none" aria-label="Close" hx-on:click="document.getElementById('deploy-{{.DeployKey}}').remove()"></button>
+ </div>
+
+ <div class="d-flex gap-3 flex-column">
+ <div id="progress-{{.DeployKey}}" class="d-flex align-items-center gap-2">
+ <div class="spinner-grow spinner-grow-sm" role="status">
+ </div>
+ <div id="status-log" sse-swap="progress">
+ Deployment requested
+ </div>
+ </div>
+
+ <div id="deployment-result-{{.DeployKey}}" class="d-none"></div>
+
+ <div>Instance <code>{{.DeployKey}}</code></div>
+ </div>
+ <span sse-swap="success" hx-target="#deployment-result-{{.DeployKey}}" hx-swap="outerHTML"></span>
+ <span sse-swap="error" hx-target="#deployment-result-{{.DeployKey}}" hx-swap="outerHTML"></span>
+ </div>
+</div>
diff --git a/web/views/f_instance.html b/web/views/f_instance.html
new file mode 100644
index 0000000..1518ee2
--- /dev/null
+++ b/web/views/f_instance.html
@@ -0,0 +1,28 @@
+{{range .Instances}}
+<tr>
+<td>
+ <div class="d-flex flex-column gap-2 justify-content-between">
+ <b>{{.Address}}</b>
+
+ <span>Expires in <code>{{.ExpiresIn}}</code></span>
+
+ <small>Instance <code>{{.DeployKey}}</code> of <i>{{.ChallengeName}}</i></small>
+ </div>
+</td>
+<td>
+ <button
+ hx-post="/instances/{{.DeployKey}}/stop"
+ hx-target="#instance-action-result"
+ hx-swap="beforeend"
+ class="btn btn-danger">
+ Stop
+ </button>
+</td>
+</tr>
+{{else}}
+<tr>
+<td colspan="2">
+<i class="text-center text-muted">Your team does not have any instances</i>
+</td>
+</tr>
+{{end}}
diff --git a/web/views/f_instance_result.html b/web/views/f_instance_result.html
new file mode 100644
index 0000000..85bd4eb
--- /dev/null
+++ b/web/views/f_instance_result.html
@@ -0,0 +1,4 @@
+<div class="alert alert-{{.State}} alert-dismissible fade show" role="alert">
+ {{.Message}}
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+</div>
diff --git a/web/views/index.html b/web/views/index.html
new file mode 100644
index 0000000..97edc88
--- /dev/null
+++ b/web/views/index.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Challenge Instancer</title>
+
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js" integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/htmx-ext-sse@2.2.4" integrity="sha384-A986SAtodyH8eg8x8irJnYUk7i9inVQqYigD6qZ9evobksGNIXfeFvDwLSHcp31N" crossorigin="anonymous"></script>
+</head>
+<body class="bg-light">
+
+<div class="container py-5">
+ <div class="row justify-content-center">
+ <div class="col-9">
+ <div class="card">
+ <div class="card-body">
+ <h4 class="card-title mb-3">Deploy challenge</h4>
+
+ <form hx-post="/deploy" hx-target="#deployment-area" hx-swap="afterbegin">
+ <div class="input-group">
+ <span class="input-group-text">Challenge</span>
+ <select id="challenge" name="challenge" class="form-select" required>
+ <option value="" disabled selected>Choose a challenge</option>
+ {{range .Challenges}}
+ <option value="{{.}}">
+ {{.}}
+ </option>
+ {{end}}
+ </select>
+ <button id="deploy-btn" type="submit" class="btn btn-primary">
+ Deploy
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+
+ <div
+ id="instances"
+ class="card mt-4">
+ <div class="card-body">
+ <div class="d-flex justify-content-between align-items-start">
+ <h5 class="card-title mb-3">Instances</h5>
+ <span class="badge bg-primary">Refreshing</span>
+ </div>
+
+ <div id="instance-action-result"></div>
+
+ <table class="table align-middle">
+ <thead>
+ <tr>
+ <th scope="col">Instance</th>
+ <th scope="col">Controls</th>
+ </tr>
+ </thead>
+ <tbody id="instances-tbody" hx-get="/instances" hx-trigger="load, every 5s, poll-instances-now from:body">
+
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div id="deployment-area"></div>
+
+ </div>
+ <div class="col">
+ <div class="card">
+ <div class="card-body">
+ <h5 class="card-title mb-3">What is this?</h5>
+ <p class="card-text">
+ This platform allows you to spawn an instance of a challenge for your team.
+ Each instance is shared across your team and can be stopped at any time.
+ </p>
+ <p>
+ Instances will automatically stop after a while; if more time is needed then
+ you can stop the instance and deploy a new one.
+ </p>
+ <p class="card-text">
+ <b>Attacking this platform is out of scope of the CTF and is forbidden.</b>
+ If there are any issues, please speak to an organiser.
+ </p>
+ </div>
+ </div>
+ <div class="mt-2 px-3">
+ <small class="text-muted">
+ Logged in as <b>{{.Team}}</b>.
+ <a href="/logout">Not you</a>?
+ </small>
+ </div>
+ </div>
+ </div>
+</div>
+
+</body>
+</html>