summaryrefslogtreecommitdiffstats
path: root/templates
diff options
context:
space:
mode:
Diffstat (limited to 'templates')
-rw-r--r--templates/accounts/manage.html70
-rw-r--r--templates/backups/list.html43
-rw-r--r--templates/base.html37
-rw-r--r--templates/change_password.html24
-rw-r--r--templates/dashboard.html45
-rw-r--r--templates/instances/create.html115
-rw-r--r--templates/instances/edit.html62
-rw-r--r--templates/instances/list.html50
-rw-r--r--templates/login.html25
-rw-r--r--templates/players/manage.html69
10 files changed, 540 insertions, 0 deletions
diff --git a/templates/accounts/manage.html b/templates/accounts/manage.html
new file mode 100644
index 0000000..e7c9b2f
--- /dev/null
+++ b/templates/accounts/manage.html
@@ -0,0 +1,70 @@
1{% extends "base.html" %}
2{% block content %}
3<h1>Account Management</h1>
4
5{% if let Some(msg) = message %}
6<div class="flash flash-ok">{{ msg }}</div>
7{% endif %}
8
9<div class="card">
10 <h3>Change My Password</h3>
11 <form action="/accounts/change-own-password" method="post" class="form-row">
12 <input type="password" name="current_password" placeholder="Current password" required>
13 <input type="password" name="new_password" placeholder="New password" required minlength="4">
14 <button type="submit" class="btn-ok">Update</button>
15 </form>
16</div>
17
18<div class="card">
19 <h3>Create Account</h3>
20 <form action="/accounts/create" method="post" class="form-row">
21 <input type="text" name="username" placeholder="Username" required>
22 <input type="password" name="password" placeholder="Password" required>
23 <label>
24 <input type="checkbox" name="is_superuser"> Superuser
25 </label>
26 <button type="submit" class="btn-ok">Create</button>
27 </form>
28</div>
29
30<div class="card">
31 <h3>Existing Accounts</h3>
32 <table class="table">
33 <thead>
34 <tr>
35 <th>Username</th>
36 <th>Role</th>
37 <th>Actions</th>
38 </tr>
39 </thead>
40 <tbody>
41 {% for acct in accounts %}
42 <tr>
43 <td>{{ acct.username }}</td>
44 <td>
45 {% if acct.is_superuser %}
46 <span class="tag tag-admin">Superuser</span>
47 {% else %}
48 <span class="tag">User</span>
49 {% endif %}
50 </td>
51 <td>
52 <div class="actions" style="gap:0.25rem">
53 <form action="/accounts/set-password" method="post" style="display:inline">
54 <input type="hidden" name="id" value="{{ acct.id }}">
55 <input type="password" name="new_password" placeholder="New password" required minlength="4" style="width:140px;padding:0.25rem 0.5rem;font-size:0.8rem">
56 <button type="submit" class="btn-sm">Set</button>
57 </form>
58 <form action="/accounts/delete" method="post" style="display:inline"
59 onsubmit="return confirm('Delete account {{ acct.username }}?')">
60 <input type="hidden" name="id" value="{{ acct.id }}">
61 <button type="submit" class="btn-sm btn-danger">Delete</button>
62 </form>
63 </div>
64 </td>
65 </tr>
66 {% endfor %}
67 </tbody>
68 </table>
69</div>
70{% endblock %}
diff --git a/templates/backups/list.html b/templates/backups/list.html
new file mode 100644
index 0000000..d7508eb
--- /dev/null
+++ b/templates/backups/list.html
@@ -0,0 +1,43 @@
1{% extends "base.html" %}
2{% block content %}
3<div class="page-header">
4 <h1>Backups: {{ instance_name }}</h1>
5 <div class="actions">
6 <form action="/instances/{{ instance_id }}/backups/create" method="post" style="display:inline">
7 <button type="submit" class="btn-ok">Create Backup</button>
8 </form>
9 <a href="/instances/{{ instance_id }}/world/export" class="btn">Export World</a>
10 <a href="/instances" class="btn">Back to Instances</a>
11 </div>
12</div>
13
14{% if backups.is_empty() %}
15 <div class="card card-muted">
16 <p>No backups yet. Create one to safeguard your world.</p>
17 </div>
18{% else %}
19 <table class="table">
20 <thead>
21 <tr>
22 <th>Filename</th>
23 <th>Created</th>
24 <th>Actions</th>
25 </tr>
26 </thead>
27 <tbody>
28 {% for backup in backups %}
29 <tr>
30 <td>{{ backup.filename }}</td>
31 <td>{{ backup.created_at }}</td>
32 <td>
33 <a href="/backups/{{ backup.id }}/download" class="btn-sm">Download</a>
34 <form action="/backups/{{ backup.id }}/restore" method="post" style="display:inline">
35 <button type="submit" class="btn-sm btn-ok">Restore</button>
36 </form>
37 </td>
38 </tr>
39 {% endfor %}
40 </tbody>
41 </table>
42{% endif %}
43{% endblock %}
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..3a92cd0
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,37 @@
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Minecraft UI</title>
7 <script src="/static/htmx.min.js"></script>
8 <link rel="stylesheet" href="/static/style.css">
9</head>
10<body hx-boost="true">
11 <nav>
12 <span class="brand">Minecraft UI</span>
13 <div class="nav-links">
14 <a href="/">Dashboard</a>
15 <a href="/instances">Instances</a>
16 <a href="/players">Players</a>
17 {% if is_superuser %}
18 <a href="/accounts">Accounts</a>
19 {% endif %}
20 <span class="nav-user">{{ username }}</span>
21 <form action="/logout" method="post" style="display:inline">
22 <button type="submit" class="btn-link">Logout</button>
23 </form>
24 </div>
25 </nav>
26
27 {% if password_change_required %}
28 <div class="flash flash-warn">
29 You must change your password before continuing.
30 </div>
31 {% endif %}
32
33 <main>
34 {% block content %}{% endblock %}
35 </main>
36</body>
37</html>
diff --git a/templates/change_password.html b/templates/change_password.html
new file mode 100644
index 0000000..6dfc11c
--- /dev/null
+++ b/templates/change_password.html
@@ -0,0 +1,24 @@
1{% extends "base.html" %}
2{% block content %}
3<h1>Change Password</h1>
4
5{% if !error.is_empty() %}
6<div class="flash flash-err">{{ error }}</div>
7{% endif %}
8
9{% if password_change_required %}
10<div class="flash flash-warn">You must change your password before continuing.</div>
11{% endif %}
12
13<form action="/change-password" method="post" class="form-col">
14 <label>
15 Current Password
16 <input type="password" name="current_password" required autofocus>
17 </label>
18 <label>
19 New Password
20 <input type="password" name="new_password" required minlength="4">
21 </label>
22 <button type="submit" class="btn-ok">Change Password</button>
23</form>
24{% endblock %}
diff --git a/templates/dashboard.html b/templates/dashboard.html
new file mode 100644
index 0000000..571a43e
--- /dev/null
+++ b/templates/dashboard.html
@@ -0,0 +1,45 @@
1{% extends "base.html" %}
2{% block content %}
3<h1>Dashboard</h1>
4
5{% if let Some(inst) = active_instance %}
6 <div class="card {% if container_state == "running" %}card-ok{% else if container_state == "error" || container_state == "not_found" %}card-err{% endif %}">
7 <h2>Active: {{ inst.name }}</h2>
8 <div class="instance-meta">
9 <span>Version: {{ inst.version }}</span>
10 <span>Type: {{ inst.server_type }}</span>
11 <span>Max Players: {{ inst.max_players }}</span>
12 </div>
13
14 <div class="status-row">
15 <div class="status-badge status-{{ container_state }}">
16 {% if container_state == "running" %}
17 Running
18 {% else if container_state == "pulling" %}
19 Pulling image…
20 {% else if container_state == "starting" %}
21 Starting…
22 {% else if container_state == "stopped" %}
23 Stopped
24 {% else if container_state == "error" %}
25 SOMETHING BAD IS HAPPENING, CHECK DOCKER LOGS
26 {% else %}
27 {{ container_state }}
28 {% endif %}
29 </div>
30 </div>
31
32 <div class="actions">
33 <a href="/players" class="btn">Player Management</a>
34 <form action="/instances/deactivate" method="post" style="display:inline">
35 <button type="submit" class="btn-danger">Stop Server</button>
36 </form>
37 </div>
38 </div>
39{% else %}
40 <div class="card card-muted">
41 <h2>No active instance</h2>
42 <p>Choose a server instance to start from the <a href="/instances">Instances</a> page.</p>
43 </div>
44{% endif %}
45{% endblock %}
diff --git a/templates/instances/create.html b/templates/instances/create.html
new file mode 100644
index 0000000..45f70f4
--- /dev/null
+++ b/templates/instances/create.html
@@ -0,0 +1,115 @@
1{% extends "base.html" %}
2{% block content %}
3<h1>Create Instance</h1>
4
5{% if let Some(msg) = error %}
6<div class="flash flash-err">{{ msg }}</div>
7{% endif %}
8
9<form action="/instances/new" method="post" class="form-col form-wide">
10 <fieldset>
11 <legend>Basic Info</legend>
12 <label>
13 Name
14 <input type="text" name="name" required placeholder="My Server">
15 </label>
16 <label>
17 Version
18 <select name="version" required>
19 {% for v in versions %}
20 <option value="{{ v }}">{{ v }}</option>
21 {% endfor %}
22 </select>
23 </label>
24 <label>
25 Type
26 <select name="server_type" required>
27 <option value="VANILLA">Vanilla</option>
28 <option value="FORGE">Forge</option>
29 <option value="FABRIC">Fabric</option>
30 </select>
31 </label>
32 <label>
33 Memory (MB)
34 <input type="number" name="memory_mb" value="4096" min="512" max="{{ max_memory_mb }}" required>
35 <small>512 MB – {{ max_memory_mb }} MB</small>
36 </label>
37 </fieldset>
38
39 <fieldset>
40 <legend>World</legend>
41 <label>
42 <input type="radio" name="world_source" value="generate" checked onchange="toggleWorldSource()">
43 Generate new world
44 </label>
45 <div id="gen-options">
46 <label>
47 Seed (optional)
48 <input type="text" name="seed" placeholder="Leave empty for random">
49 </label>
50 <label>
51 Level Type
52 <select name="level_type">
53 <option value="DEFAULT">Default</option>
54 <option value="FLAT">Flat</option>
55 <option value="LARGEBIOMES">Large Biomes</option>
56 <option value="AMPLIFIED">Amplified</option>
57 </select>
58 </label>
59 </div>
60 <label>
61 <input type="radio" name="world_source" value="upload" onchange="toggleWorldSource()">
62 Upload world (ZIP)
63 </label>
64 <div id="upload-options" style="display:none">
65 <label>
66 World ZIP
67 <input type="file" name="world_zip" accept=".zip">
68 </label>
69 </div>
70 </fieldset>
71
72 <fieldset>
73 <legend>Server Properties</legend>
74 <label>
75 Difficulty
76 <select name="difficulty">
77 <option value="0">Peaceful</option>
78 <option value="1">Easy</option>
79 <option value="2">Normal</option>
80 <option value="3" selected>Hard</option>
81 </select>
82 </label>
83 <label>
84 Gamemode
85 <select name="gamemode">
86 <option value="0" selected>Survival</option>
87 <option value="1">Creative</option>
88 <option value="2">Adventure</option>
89 <option value="3">Spectator</option>
90 </select>
91 </label>
92 <label>
93 Max Players
94 <input type="number" name="max_players" value="20" min="1" max="100">
95 </label>
96 <label>
97 <input type="checkbox" name="pvp" checked> PvP enabled
98 </label>
99 <label>
100 MOTD
101 <input type="text" name="motd" value="A Minecraft Server" maxlength="59">
102 </label>
103 </fieldset>
104
105 <button type="submit" class="btn-ok">Create Instance</button>
106</form>
107
108<script>
109function toggleWorldSource() {
110 var genRadio = document.querySelector('input[value="generate"]');
111 document.getElementById('gen-options').style.display = genRadio.checked ? '' : 'none';
112 document.getElementById('upload-options').style.display = genRadio.checked ? 'none' : '';
113}
114</script>
115{% endblock %}
diff --git a/templates/instances/edit.html b/templates/instances/edit.html
new file mode 100644
index 0000000..356c7cd
--- /dev/null
+++ b/templates/instances/edit.html
@@ -0,0 +1,62 @@
1{% extends "base.html" %}
2{% block content %}
3<h1>Edit: {{ instance.name }}</h1>
4
5<div class="card card-muted">
6 <div class="meta">
7 <span><strong>Version:</strong> {{ instance.version }}</span>
8 <span><strong>Type:</strong> {{ instance.server_type }}</span>
9 <span><strong>World:</strong> {{ instance.world_source }}</span>
10 {% if let Some(seed) = instance.world_seed %}
11 <span><strong>Seed:</strong> {{ seed }}</span>
12 {% endif %}
13 {% if instance.has_modpack %}
14 <span><strong>Modpack:</strong> Installed</span>
15 {% endif %}
16 </div>
17</div>
18
19<form action="/instances/{{ instance.id }}/edit" method="post" class="form-col">
20 <fieldset>
21 <legend>Server Properties</legend>
22 <label>
23 Memory (MB)
24 <input type="number" name="memory_mb" value="{{ instance.memory_mb }}" min="512" max="{{ max_memory_mb }}" required>
25 </label>
26 <label>
27 Difficulty
28 <select name="difficulty">
29 <option value="0" {% if instance.difficulty == 0 %}selected{% endif %}>Peaceful</option>
30 <option value="1" {% if instance.difficulty == 1 %}selected{% endif %}>Easy</option>
31 <option value="2" {% if instance.difficulty == 2 %}selected{% endif %}>Normal</option>
32 <option value="3" {% if instance.difficulty == 3 %}selected{% endif %}>Hard</option>
33 </select>
34 </label>
35 <label>
36 Gamemode
37 <select name="gamemode">
38 <option value="0" {% if instance.gamemode == 0 %}selected{% endif %}>Survival</option>
39 <option value="1" {% if instance.gamemode == 1 %}selected{% endif %}>Creative</option>
40 <option value="2" {% if instance.gamemode == 2 %}selected{% endif %}>Adventure</option>
41 <option value="3" {% if instance.gamemode == 3 %}selected{% endif %}>Spectator</option>
42 </select>
43 </label>
44 <label>
45 Max Players
46 <input type="number" name="max_players" value="{{ instance.max_players }}" min="1" max="100">
47 </label>
48 <label>
49 <input type="checkbox" name="pvp" {% if instance.pvp %}checked{% endif %}> PvP enabled
50 </label>
51 <label>
52 MOTD
53 <input type="text" name="motd" value="{{ instance.motd }}" maxlength="59">
54 </label>
55 </fieldset>
56
57 <div class="actions">
58 <button type="submit" class="btn-ok">Save Changes</button>
59 <a href="/instances" class="btn">Cancel</a>
60 </div>
61</form>
62{% endblock %}
diff --git a/templates/instances/list.html b/templates/instances/list.html
new file mode 100644
index 0000000..b8b2337
--- /dev/null
+++ b/templates/instances/list.html
@@ -0,0 +1,50 @@
1{% extends "base.html" %}
2{% block content %}
3<div class="page-header">
4 <h1>Server Instances</h1>
5 <a href="/instances/new" class="btn">Create Instance</a>
6</div>
7
8{% if instances.is_empty() %}
9 <div class="card card-muted">
10 <p>No instances yet. Create one to get started.</p>
11 </div>
12{% else %}
13 <div class="instance-grid">
14 {% for inst in instances %}
15 <div class="card {% if inst.is_active %}card-active{% endif %}">
16 {% if inst.is_active %}
17 <div class="active-badge">ACTIVE</div>
18 {% endif %}
19 <h3>{{ inst.name }}</h3>
20 <div class="meta">
21 <span>{{ inst.version }}</span>
22 <span class="tag">{{ inst.server_type }}</span>
23 {% if inst.has_modpack %}
24 <span class="tag">Modded</span>
25 {% endif %}
26 </div>
27 <div class="actions">
28 {% if !inst.is_active %}
29 <form action="/instances/{{ inst.id }}/activate" method="post" style="display:inline">
30 <button type="submit" class="btn-ok">Activate</button>
31 </form>
32 {% else %}
33 <form action="/instances/deactivate" method="post" style="display:inline">
34 <button type="submit" class="btn-danger">Deactivate</button>
35 </form>
36 {% endif %}
37 <a href="/instances/{{ inst.id }}/edit" class="btn">Edit</a>
38 <a href="/instances/{{ inst.id }}/backups" class="btn">Backups</a>
39 {% if !inst.is_active %}
40 <form action="/instances/{{ inst.id }}/delete" method="post" style="display:inline"
41 onsubmit="return confirm('Delete instance {{ inst.name }}? This cannot be undone.')">
42 <button type="submit" class="btn-danger">Delete</button>
43 </form>
44 {% endif %}
45 </div>
46 </div>
47 {% endfor %}
48 </div>
49{% endif %}
50{% endblock %}
diff --git a/templates/login.html b/templates/login.html
new file mode 100644
index 0000000..36e725e
--- /dev/null
+++ b/templates/login.html
@@ -0,0 +1,25 @@
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Login — Minecraft UI</title>
7 <link rel="stylesheet" href="/static/style.css">
8</head>
9<body>
10 <main class="login-page">
11 <h1>Minecraft UI</h1>
12 <form action="/login" method="post" class="login-form">
13 <label>
14 Username
15 <input type="text" name="username" required autofocus>
16 </label>
17 <label>
18 Password
19 <input type="password" name="password" required>
20 </label>
21 <button type="submit">Login</button>
22 </form>
23 </main>
24</body>
25</html>
diff --git a/templates/players/manage.html b/templates/players/manage.html
new file mode 100644
index 0000000..5c31e4c
--- /dev/null
+++ b/templates/players/manage.html
@@ -0,0 +1,69 @@
1{% extends "base.html" %}
2{% block content %}
3<h1>Player Management</h1>
4
5{% if let Some(name) = active_instance_name %}
6 <p>Managing: <strong>{{ name }}</strong></p>
7{% else %}
8 <div class="flash flash-warn">No active instance. Start a server to manage players.</div>
9{% endif %}
10
11<div class="tab-row">
12 <div class="card">
13 <h3>Whitelist</h3>
14 <form action="/players/whitelist/add" method="post" class="form-row">
15 <input type="text" name="username" placeholder="Player name" required>
16 <button type="submit" class="btn-ok">Add</button>
17 </form>
18 <ul class="player-list">
19 {% for player in whitelist %}
20 <li>
21 {{ player }}
22 <form action="/players/whitelist/remove" method="post" style="display:inline">
23 <input type="hidden" name="username" value="{{ player }}">
24 <button type="submit" class="btn-sm btn-danger">Remove</button>
25 </form>
26 </li>
27 {% endfor %}
28 </ul>
29 </div>
30
31 <div class="card">
32 <h3>Operators</h3>
33 <form action="/players/op/add" method="post" class="form-row">
34 <input type="text" name="username" placeholder="Player name" required>
35 <button type="submit" class="btn-ok">Add</button>
36 </form>
37 <ul class="player-list">
38 {% for player in ops %}
39 <li>
40 {{ player }}
41 <form action="/players/op/remove" method="post" style="display:inline">
42 <input type="hidden" name="username" value="{{ player }}">
43 <button type="submit" class="btn-sm btn-danger">Remove</button>
44 </form>
45 </li>
46 {% endfor %}
47 </ul>
48 </div>
49
50 <div class="card">
51 <h3>Banned</h3>
52 <form action="/players/ban/add" method="post" class="form-row">
53 <input type="text" name="username" placeholder="Player name" required>
54 <button type="submit" class="btn-ok">Ban</button>
55 </form>
56 <ul class="player-list">
57 {% for player in bans %}
58 <li>
59 {{ player }}
60 <form action="/players/ban/remove" method="post" style="display:inline">
61 <input type="hidden" name="username" value="{{ player }}">
62 <button type="submit" class="btn-sm btn-ok">Unban</button>
63 </form>
64 </li>
65 {% endfor %}
66 </ul>
67 </div>
68</div>
69{% endblock %}