Files
setra/static/index.html
2025-10-30 13:18:44 +01:00

364 lines
9.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="author" content="Daniel Svitan">
<title>Setra</title>
<style>
body {
display: flex;
align-items: center;
flex-direction: column;
}
.container {
width: 60vw;
margin-top: 2rem;
}
h1 {
margin: 0;
}
p {
width: min-content;
}
.create-form {
display: flex;
flex-direction: column;
border: 1px black solid;
padding: 0.5rem;
border-radius: 0.5rem;
}
.create-form label {
font-size: 1.15rem;
}
.create-form input {
width: min-content;
border-radius: 0.5rem;
border: 1px gray solid;
padding: 0.4rem 0.4rem 0.15rem 0.4rem;
font-size: 1.05rem;
}
.create-form button[type=submit] {
width: min-content;
margin-top: 0.5rem;
border: none;
font-size: 1.05rem;
color: #eeeeee;
background: #0078e7;
padding: 0.5rem 0.75rem 0.25rem 0.75rem;
border-radius: 0.5rem;
cursor: pointer;
}
.dialog {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
backdrop-filter: blur(1rem);
}
.dialog form {
border: 1px solid black;
padding: 0.5rem;
border-radius: 0.5rem;
display: flex;
flex-direction: column;
}
.dialog form label {
font-size: 1.15rem;
}
.dialog form input {
width: min-content;
border-radius: 0.5rem;
border: 1px gray solid;
padding: 0.4rem 0.4rem 0.15rem 0.4rem;
font-size: 1.05rem;
}
.dialog form button[type=submit] {
width: min-content;
margin-top: 0.5rem;
border: none;
font-size: 1.05rem;
color: #eeeeee;
background: #0078e7;
padding: 0.5rem 0.75rem 0.25rem 0.75rem;
border-radius: 0.5rem;
align-self: end;
cursor: pointer;
}
#tracker-table {
width: 100%;
margin-top: 2rem;
font-size: 1.10rem;
}
#tracker-table th:nth-child(1) {
width: 20rem;
}
#tracker-table td:nth-child(3) {
width: 15rem;
}
#tracker-table td:nth-child(4) {
width: 10rem;
}
#tracker-table td, #tracker-table th, #hit-table td, #hit-table th {
margin: 0;
padding: 0.2rem 0;
border-bottom: 1px solid black;
}
#hit-title {
font-size: 1.15rem;
width: 100%;
margin-top: 2rem;
margin-bottom: 0;
}
#hit-table {
width: 100%;
overflow: auto;
font-size: 1.10rem;
}
#hit-table th:nth-child(1) {
width: 20rem;
}
#hit-table th:nth-child(2) {
width: 10rem;
}
#hit-table th:nth-child(4) {
width: 8rem;
}
#hit-table th:nth-child(5) {
width: 15rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Setra</h1>
<form onsubmit="createTracker(event)" class="create-form">
<label for="name-input">Name:</label>
<input id="name-input" type="text">
<button type="submit">Create</button>
</form>
<table id="tracker-table">
<tr>
<th>ID</th>
<th>Name</th>
<th>Created at</th>
<th></th>
</tr>
</table>
<p id="hit-title">No tracker selected</p>
<table id="hit-table">
<tr>
<th>ID</th>
<th>IP</th>
<th>Agent</th>
<th>Language</th>
<th>Created at</th>
<th></th>
</tr>
</table>
<div class="dialog" id="dialog">
<form onsubmit="saveAPIKey(event)">
<label for="api-key-input">API key:</label>
<input id="api-key-input" type="password">
<button type="submit">Save</button>
</form>
</div>
</div>
<script>
function saveAPIKey(event) {
event.preventDefault();
const input = document.getElementById("api-key-input");
localStorage.setItem("api-key", input.value);
document.getElementById("dialog").style.display = "none";
loadTrackers();
}
function init() {
if (localStorage.getItem("api-key")) {
document.getElementById("dialog").style.display = "none";
loadTrackers();
}
}
function loadTrackers() {
const apiKey = localStorage.getItem("api-key");
if (!apiKey) {
return;
}
fetch(`${window.location.origin}/tracker`, {
method: "GET",
headers: {
Authorization: apiKey,
}
}).then((res) => {
res.json().then((trackers) => {
document.getElementById("tracker-table").innerHTML = `
<tr>
<th>ID</th>
<th>Name</th>
<th>Created at</th>
<th></th>
</tr>`;
trackers.forEach((tracker) => {
document.getElementById("tracker-table").innerHTML += `
<tr>
<td>${tracker.id}</td>
<td>${tracker.name}</td>
<td>${tracker.created_at}</td>
<td>
<button onclick="loadTracker('${tracker.id}')">
Show
</button>
<button onclick="copyLink('${tracker.id}')">
Copy
</button>
<button onclick="deleteTracker('${tracker.id}')">
Delete
</button>
</td>
</tr>`;
});
}).catch((err) => alert(`Couldn't unwrap json: ${err}`))
}).catch((err) => alert(`Couldn't fetch trackers: ${err}`));
}
function loadTracker(id) {
const apiKey = localStorage.getItem("api-key");
if (!apiKey) {
return;
}
fetch(`${window.location.origin}/tracker/${id}/hits`, {
method: "GET",
headers: {
Authorization: apiKey,
}
}).then((res) => {
res.json().then((hits) => {
document.getElementById("hit-title").innerHTML = `${hits.length} hit(s) for tracker ${id}`;
document.getElementById("hit-table").innerHTML = `
<tr>
<th>ID</th>
<th>IP</th>
<th>Agent</th>
<th>Language</th>
<th>Created at</th>
<th></th>
</tr>`;
hits.forEach((hit) => {
document.getElementById("hit-table").innerHTML += `
<tr>
<td>${hit.id}</td>
<td>${hit.ip}</td>
<td>${hit.agent}</td>
<td>${hit.language}</td>
<td>${hit.created_at}</td>
<td>
<button onclick="deleteHit('${hit.id}', '${id}')">
Delete
</button>
</td>
</tr>`;
});
}).catch((err) => alert(`Couldn't unwrap json: ${err}`));
}).catch((err) => alert(`Couldn't fetch hits: ${err}`));
}
function createTracker(event) {
event.preventDefault();
const apiKey = localStorage.getItem("api-key");
if (!apiKey) {
return;
}
const name = document.getElementById("name-input").value;
fetch(`${window.location.origin}/tracker`, {
method: "POST",
body: JSON.stringify({name: name}),
headers: {
Authorization: apiKey,
"Content-Type": "application/json",
}
}).then(loadTrackers).catch((err) => alert(`Couldn't create tracker: ${err}`));
}
function deleteTracker(id) {
const apiKey = localStorage.getItem("api-key");
if (!apiKey) {
return;
}
if (!confirm("Are you sure you want to delete this tracker?")) return;
fetch(`${window.location.origin}/tracker/${id}`, {
method: "DELETE",
headers: {
Authorization: apiKey,
}
}).then(loadTrackers).catch((err) => alert(`Couldn't fetch tracker: ${err}`));
}
function deleteHit(id, trackerId) {
const apiKey = localStorage.getItem("api-key");
if (!apiKey) {
return;
}
if (!confirm("Are you sure you want to delete this hit?")) return;
fetch(`${window.location.origin}/hit/${id}`, {
method: "DELETE",
headers: {
Authorization: apiKey,
}
}).then(() => loadTracker(trackerId)).catch((err) => alert(`Couldn't delete hit: ${err}`));
}
function copyLink(id) {
navigator.clipboard.writeText(`${window.location.origin}/image/${id}`).catch((err) => alert(`Couldn't copy: ${err}`));
}
init();
</script>
</body>
</html>