embed html template and static files
This commit is contained in:
parent
b150fa491c
commit
e5dcc4b884
44 changed files with 316 additions and 255 deletions
47
admin/templates/html/components/credits/addcredit.html
Normal file
47
admin/templates/html/components/credits/addcredit.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
<dialog id="addcredit">
|
||||
<header>
|
||||
<h2>Add Artist Credit</h2>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
{{range $Artist := .Artists}}
|
||||
<li class="new-artist"
|
||||
data-id="{{$Artist.ID}}"
|
||||
hx-get="/admin/release/{{$.ReleaseID}}/newcredit/{{$Artist.ID}}"
|
||||
hx-target="#editcredits ul"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
<img src="{{$Artist.GetAvatar}}" alt="" width="16" loading="lazy" class="artist-avatar">
|
||||
<span class="artist-name">{{$Artist.Name}} <span class="artist-id">({{$Artist.ID}})</span></span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
{{if not .Artists}}
|
||||
<p class="empty">There are no more artists to add.</p>
|
||||
{{end}}
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="cancel" type="button">Cancel</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(() => {
|
||||
const newCreditModal = document.getElementById("addcredit")
|
||||
const editCreditsModal = document.getElementById("editcredits")
|
||||
const cancelBtn = newCreditModal.querySelector("#cancel");
|
||||
|
||||
editCreditsModal.addEventListener("htmx:afterSwap", () => {
|
||||
newCreditModal.close();
|
||||
newCreditModal.remove();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => {
|
||||
newCreditModal.close();
|
||||
newCreditModal.remove();
|
||||
});
|
||||
|
||||
newCreditModal.showModal();
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
114
admin/templates/html/components/credits/editcredits.html
Normal file
114
admin/templates/html/components/credits/editcredits.html
Normal file
|
@ -0,0 +1,114 @@
|
|||
<dialog id="editcredits">
|
||||
<header>
|
||||
<h2>Editing: Credits</h2>
|
||||
<a id="add-credit"
|
||||
class="button new"
|
||||
href="/admin/release/{{.ID}}/addcredit"
|
||||
hx-get="/admin/release/{{.ID}}/addcredit"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
>Add</a>
|
||||
</header>
|
||||
|
||||
<form action="/api/v1/music/{{.ID}}/credits">
|
||||
<ul>
|
||||
{{range .Credits}}
|
||||
<li class="credit" data-artist="{{.Artist.ID}}">
|
||||
<div>
|
||||
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<div class="credit-info">
|
||||
<p class="artist-name">{{.Artist.Name}}</p>
|
||||
<div class="credit-attribute">
|
||||
<label for="role">Role:</label>
|
||||
<input type="text" name="role" value="{{.Role}}">
|
||||
</div>
|
||||
<div class="credit-attribute">
|
||||
<label for="primary">Primary:</label>
|
||||
<input type="checkbox" name="primary" {{if .Primary}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="discard" type="button">Discard</button>
|
||||
<button id="save" type="submit" class="save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { makeMagicList } from "/admin/static/admin.js";
|
||||
|
||||
(() => {
|
||||
const container = document.getElementById("editcredits");
|
||||
const form = document.querySelector("#editcredits form");
|
||||
const creditList = form.querySelector("ul");
|
||||
const addCreditBtn = document.getElementById("add-credit");
|
||||
const discardBtn = form.querySelector("button#discard");
|
||||
|
||||
makeMagicList(creditList, ".credit");
|
||||
|
||||
function rigCredit(el) {
|
||||
const artistID = el.dataset.artist;
|
||||
const deleteBtn = el.querySelector("a.delete");
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
if (!confirm("Are you sure you want to delete " + artistID + "'s credit?")) return;
|
||||
el.remove();
|
||||
});
|
||||
|
||||
el.draggable = true;
|
||||
el.addEventListener("dragstart", () => { el.classList.add("moving") });
|
||||
el.addEventListener("dragend", () => { el.classList.remove("moving") });
|
||||
}
|
||||
|
||||
[...creditList.querySelectorAll(".credit")].map(rigCredit);
|
||||
|
||||
creditList.addEventListener("htmx:afterSwap", () => {
|
||||
rigCredit(creditList.children[creditList.children.length - 1]);
|
||||
});
|
||||
|
||||
container.showModal();
|
||||
|
||||
container.addEventListener("close", () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
const credits = [...creditList.querySelectorAll(".credit")].map(el => {
|
||||
return {
|
||||
"artist": el.dataset.artist,
|
||||
"role": el.querySelector(`input[name="role"]`).value,
|
||||
"primary": el.querySelector(`input[name="primary"]`).checked,
|
||||
};
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
fetch(form.action, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(credits)
|
||||
}).then(res => {
|
||||
if (res.ok) location = location;
|
||||
else {
|
||||
res.text().then(err => {
|
||||
alert(err);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to update credits. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
discardBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
container.close();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
17
admin/templates/html/components/credits/newcredit.html
Normal file
17
admin/templates/html/components/credits/newcredit.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<li class="credit" data-artist="{{.ID}}">
|
||||
<div>
|
||||
<img src="{{.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<div class="credit-info">
|
||||
<p class="artist-name">{{.Name}}</p>
|
||||
<div class="credit-attribute">
|
||||
<label for="role">Role:</label>
|
||||
<input type="text" name="role" value="">
|
||||
</div>
|
||||
<div class="credit-attribute">
|
||||
<label for="primary">Primary:</label>
|
||||
<input type="checkbox" name="primary">
|
||||
</div>
|
||||
</div>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
159
admin/templates/html/components/links/editlinks.html
Normal file
159
admin/templates/html/components/links/editlinks.html
Normal file
|
@ -0,0 +1,159 @@
|
|||
<dialog id="editlinks">
|
||||
<header>
|
||||
<h2>Editing: Links</h2>
|
||||
<button id="add-link" class="button new">Add</button>
|
||||
</header>
|
||||
|
||||
<form action="/api/v1/music/{{.ID}}/links">
|
||||
<table>
|
||||
<tr>
|
||||
<th class="grabber"></th>
|
||||
<th class="link-name">Name</th>
|
||||
<th class="link-url">URL</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{{range .Links}}
|
||||
<tr class="link">
|
||||
<td class="grabber"><img src="/img/list-grabber.svg"/></td>
|
||||
<td class="link-name">
|
||||
<input type="text" name="name" value="{{.Name}}">
|
||||
</td>
|
||||
<td class="link-url">
|
||||
<input type="text" name="url" value="{{.URL}}">
|
||||
</td>
|
||||
<td>
|
||||
<a class="delete">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="discard" type="button">Discard</button>
|
||||
<button id="save" type="submit" class="save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { makeMagicList } from "/admin/static/admin.js";
|
||||
(() => {
|
||||
const container = document.getElementById("editlinks");
|
||||
const form = document.querySelector("#editlinks form");
|
||||
const linkTable = form.querySelector("table tbody");
|
||||
const addLinkBtn = document.getElementById("add-link");
|
||||
const discardBtn = form.querySelector("button#discard");
|
||||
|
||||
makeMagicList(linkTable, "tr.link");
|
||||
|
||||
function rigLinkItem(el) {
|
||||
const nameInput = el.querySelector(`input[name="name"]`)
|
||||
const deleteBtn = el.querySelector("a.delete");
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
if (nameInput.value != "" &&
|
||||
!confirm("Are you sure you want to delete \"" + nameInput.value + "\"?"))
|
||||
return;
|
||||
el.remove();
|
||||
});
|
||||
}
|
||||
|
||||
[...linkTable.querySelectorAll("tr.link")].map(rigLinkItem);
|
||||
|
||||
addLinkBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
const row = document.createElement("tr");
|
||||
row.className = "link";
|
||||
|
||||
const grabberCell = document.createElement("td");
|
||||
grabberCell.className = "grabber";
|
||||
const grabberImg = document.createElement("img");
|
||||
grabberImg.src = "/img/list-grabber.svg";
|
||||
grabberCell.appendChild(grabberImg);
|
||||
row.appendChild(grabberCell);
|
||||
|
||||
const nameCell = document.createElement("td");
|
||||
nameCell.className = "link-name";
|
||||
const nameInput = document.createElement("input");
|
||||
nameInput.type = "text";
|
||||
nameInput.name = "name";
|
||||
nameCell.appendChild(nameInput);
|
||||
row.appendChild(nameCell);
|
||||
|
||||
const urlCell = document.createElement("td");
|
||||
urlCell.className = "link-url";
|
||||
const urlInput = document.createElement("input");
|
||||
urlInput.type = "text";
|
||||
urlInput.name = "url";
|
||||
urlCell.appendChild(urlInput);
|
||||
row.appendChild(urlCell);
|
||||
|
||||
const deleteCell = document.createElement("td");
|
||||
const deleteBtn = document.createElement("a");
|
||||
deleteBtn.className = "delete";
|
||||
deleteBtn.innerText = "Delete";
|
||||
deleteCell.appendChild(deleteBtn);
|
||||
row.appendChild(deleteCell);
|
||||
|
||||
linkTable.appendChild(row);
|
||||
|
||||
row.draggable = true;
|
||||
row.addEventListener("dragstart", () => { row.classList.add("moving") });
|
||||
row.addEventListener("dragend", () => { row.classList.remove("moving") });
|
||||
row.querySelectorAll("input").forEach(el => {
|
||||
el.addEventListener("mousedown", () => { row.draggable = false });
|
||||
el.addEventListener("mouseup", () => { row.draggable = true });
|
||||
el.addEventListener("dragstart", e => { e.stopPropagation() });
|
||||
});
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
if (nameInput.value != "" && !confirm("Are you sure you want to delete \"" + nameInput.value + "\"?")) return;
|
||||
row.remove();
|
||||
});
|
||||
});
|
||||
|
||||
container.showModal();
|
||||
|
||||
container.addEventListener("close", () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
var links = [];
|
||||
[...linkTable.querySelectorAll("tr.link")].map(el => {
|
||||
const name = el.querySelector(`input[name="name"]`).value;
|
||||
const url = el.querySelector(`input[name="url"]`).value;
|
||||
if (name == "" || url == "") return;
|
||||
links.push({
|
||||
"name": name,
|
||||
"url": url,
|
||||
});
|
||||
})
|
||||
|
||||
e.preventDefault();
|
||||
fetch(form.action, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(links)
|
||||
}).then(res => {
|
||||
if (res.ok) location = location;
|
||||
else {
|
||||
res.text().then(err => {
|
||||
alert(err);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to update links. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
discardBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
container.close();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
|
@ -0,0 +1,23 @@
|
|||
{{define "release"}}
|
||||
<div class="release">
|
||||
<div class="release-artwork">
|
||||
<img src="{{.GetArtwork}}" alt="" width="128" loading="lazy">
|
||||
</div>
|
||||
<div class="release-info">
|
||||
<h3 class="release-title">
|
||||
<a href="/admin/release/{{.ID}}">{{.Title}}</a>
|
||||
<small>
|
||||
<span title="{{.PrintReleaseDate}}">{{.ReleaseDate.Year}}</span>
|
||||
{{if not .Visible}}(hidden){{end}}
|
||||
</small>
|
||||
</h3>
|
||||
<p class="release-artists">{{.PrintArtists true true}}</p>
|
||||
<p class="release-type-single">{{.ReleaseType}}
|
||||
(<a href="/admin/release/{{.ID}}#tracks">{{len .Tracks}} track{{if not (eq (len .Tracks) 1)}}s{{end}}</a>)</p>
|
||||
<div class="release-actions">
|
||||
<a href="/admin/release/{{.ID}}">Edit</a>
|
||||
<a href="/music/{{.ID}}" target="_blank">Gateway <img class="icon" src="/img/external-link.svg"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
47
admin/templates/html/components/tracks/addtrack.html
Normal file
47
admin/templates/html/components/tracks/addtrack.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
<dialog id="addtrack">
|
||||
<header>
|
||||
<h2>Add Track</h2>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
{{range $Track := .Tracks}}
|
||||
</li>
|
||||
<li class="new-track"
|
||||
data-id="{{$Track.ID}}"
|
||||
hx-get="/admin/release/{{$.ReleaseID}}/newtrack/{{$Track.ID}}"
|
||||
hx-target="#edittracks ul"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
{{.Title}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
{{if not .Tracks}}
|
||||
<p class="empty">There are no more tracks to add.</p>
|
||||
{{end}}
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="cancel" type="button">Cancel</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(() => {
|
||||
const newTrackModal = document.getElementById("addtrack")
|
||||
const editTracksModal = document.getElementById("edittracks")
|
||||
const cancelBtn = newTrackModal.querySelector("#cancel");
|
||||
|
||||
editTracksModal.addEventListener("htmx:afterSwap", () => {
|
||||
newTrackModal.close();
|
||||
newTrackModal.remove();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => {
|
||||
newTrackModal.close();
|
||||
newTrackModal.remove();
|
||||
});
|
||||
|
||||
newTrackModal.showModal();
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
112
admin/templates/html/components/tracks/edittracks.html
Normal file
112
admin/templates/html/components/tracks/edittracks.html
Normal file
|
@ -0,0 +1,112 @@
|
|||
<dialog id="edittracks">
|
||||
<header>
|
||||
<h2>Editing: Tracks</h2>
|
||||
<a id="add-track"
|
||||
class="button new"
|
||||
href="/admin/release/{{.Release.ID}}/addtrack"
|
||||
hx-get="/admin/release/{{.Release.ID}}/addtrack"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
>Add</a>
|
||||
</header>
|
||||
|
||||
<form action="/api/v1/music/{{.Release.ID}}/tracks">
|
||||
<ul>
|
||||
{{range $i, $track := .Release.Tracks}}
|
||||
<li class="track" data-track="{{$track.ID}}" data-title="{{$track.Title}}" data-number="{{$track.Add $i 1}}" draggable="true">
|
||||
<div>
|
||||
<p class="track-name">
|
||||
<span class="track-number">{{.Add $i 1}}</span>
|
||||
{{$track.Title}}
|
||||
</p>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="discard" type="button">Discard</button>
|
||||
<button id="save" type="submit" class="save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { makeMagicList } from "/admin/static/admin.js";
|
||||
(() => {
|
||||
const container = document.getElementById("edittracks");
|
||||
const form = document.querySelector("#edittracks form");
|
||||
const trackList = form.querySelector("ul");
|
||||
const addTrackBtn = document.getElementById("add-track");
|
||||
const discardBtn = form.querySelector("button#discard");
|
||||
|
||||
makeMagicList(trackList, ".track", refreshTrackNumbers);
|
||||
|
||||
function rigTrackItem(trackItem) {
|
||||
const trackID = trackItem.dataset.track;
|
||||
const trackTitle = trackItem.dataset.title;
|
||||
const deleteBtn = trackItem.querySelector("a.delete");
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
if (!confirm("Are you sure you want to remove " + trackTitle + "?")) return;
|
||||
trackItem.remove();
|
||||
refreshTrackNumbers();
|
||||
});
|
||||
}
|
||||
|
||||
function refreshTrackNumbers() {
|
||||
trackList.querySelectorAll("li").forEach((trackItem, i) => {
|
||||
trackItem.querySelector(".track-number").innerText = i + 1;
|
||||
});
|
||||
}
|
||||
|
||||
trackList.addEventListener("htmx:afterSwap", e => {
|
||||
const trackItem = trackList.children[trackList.children.length - 1];
|
||||
trackList.appendChild(trackItem);
|
||||
trackItem.addEventListener("dragstart", () => { trackItem.classList.add("moving") });
|
||||
trackItem.addEventListener("dragend", () => { trackItem.classList.remove("moving") });
|
||||
rigTrackItem(trackItem);
|
||||
refreshTrackNumbers();
|
||||
});
|
||||
|
||||
trackList.querySelectorAll("li").forEach(trackItem => {
|
||||
rigTrackItem(trackItem);
|
||||
});
|
||||
|
||||
container.showModal();
|
||||
|
||||
container.addEventListener("close", () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
|
||||
let tracks = [...trackList.querySelectorAll(".track")].map(trackItem => trackItem.dataset.track);
|
||||
|
||||
fetch(form.action, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(tracks)
|
||||
}).then(res => {
|
||||
if (res.ok) location = location;
|
||||
else {
|
||||
res.text().then(err => {
|
||||
alert(err);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to update tracks. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
discardBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
container.close();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
9
admin/templates/html/components/tracks/newtrack.html
Normal file
9
admin/templates/html/components/tracks/newtrack.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<li class="track" data-track="{{.ID}}" data-title="{{.Title}}" data-number="0" draggable="true">
|
||||
<div>
|
||||
<p class="track-name">
|
||||
<span class="track-number">0</span>
|
||||
{{.Title}}
|
||||
</p>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
84
admin/templates/html/edit-account.html
Normal file
84
admin/templates/html/edit-account.html
Normal file
|
@ -0,0 +1,84 @@
|
|||
{{define "head"}}
|
||||
<title>Account Settings - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-account.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
{{if .Session.Message.Valid}}
|
||||
<p id="message">{{html .Session.Message.String}}</p>
|
||||
{{end}}
|
||||
{{if .Session.Error.Valid}}
|
||||
<p id="error">{{html .Session.Error.String}}</p>
|
||||
{{end}}
|
||||
<h1>Account Settings ({{.Session.Account.Username}})</h1>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Change Password</h2>
|
||||
</div>
|
||||
<div class="card">
|
||||
<form action="/admin/account/password" method="POST" id="change-password">
|
||||
<label for="current-password">Current Password</label>
|
||||
<input type="password" id="current-password" name="current-password" value="" autocomplete="current-password" required>
|
||||
|
||||
<label for="new-password">New Password</label>
|
||||
<input type="password" id="new-password" name="new-password" value="" autocomplete="new-password" required>
|
||||
|
||||
<label for="confirm-password">Confirm Password</label>
|
||||
<input type="password" id="confirm-password" value="" autocomplete="new-password" required>
|
||||
|
||||
<button type="submit" class="save">Change Password</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>MFA Devices</h2>
|
||||
</div>
|
||||
<div class="card mfa-devices">
|
||||
{{if .TOTPs}}
|
||||
{{range .TOTPs}}
|
||||
<div class="mfa-device">
|
||||
<div>
|
||||
<p class="mfa-device-name">{{.TOTP.Name}}</p>
|
||||
<p class="mfa-device-date">Added: {{.CreatedAtString}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a class="button delete" href="/admin/account/totp-delete/{{.TOTP.Name}}">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p>You have no MFA devices.</p>
|
||||
{{end}}
|
||||
|
||||
<div>
|
||||
<button type="submit" class="save" id="enable-email" disabled>Enable Email TOTP</button>
|
||||
<a class="button new" id="add-totp-device" href="/admin/account/totp-setup">Add TOTP Device</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Danger Zone</h2>
|
||||
</div>
|
||||
<div class="card danger">
|
||||
<p>
|
||||
Clicking the button below will delete your account.
|
||||
This action is <strong>irreversible</strong>.
|
||||
You will need to enter your password and TOTP below.
|
||||
</p>
|
||||
<form action="/admin/account/delete" method="POST">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" value="" autocomplete="current-password" required>
|
||||
|
||||
<label for="totp">TOTP</label>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required>
|
||||
|
||||
<button type="submit" class="delete">Delete Account</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/edit-account.js" defer></script>
|
||||
{{end}}
|
72
admin/templates/html/edit-artist.html
Normal file
72
admin/templates/html/edit-artist.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
{{define "head"}}
|
||||
<title>Editing {{.Artist.Name}} - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="{{.Artist.GetAvatar}}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-artist.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Editing Artist</h1>
|
||||
|
||||
<div id="artist" data-id="{{.Artist.ID}}">
|
||||
<div class="artist-avatar">
|
||||
<img src="{{.Artist.Avatar}}" alt="" width="256" loading="lazy" id="avatar">
|
||||
<input type="file" id="avatar-file" name="Artwork" accept=".png,.jpg,.jpeg" hidden>
|
||||
<button id="remove-avatar">Remove</button>
|
||||
</div>
|
||||
<div class="artist-info">
|
||||
<p class="attribute-header">Name</p>
|
||||
<h2 class="artist-name">
|
||||
<input type="text" id="name" name="artist-name" value="{{.Artist.Name}}">
|
||||
</h2>
|
||||
|
||||
<p class="attribute-header">Website</p>
|
||||
<input type="text" id="website" name="website" value="{{.Artist.Website}}">
|
||||
|
||||
<div class="artist-actions">
|
||||
<button type="submit" class="save" id="save" disabled>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Featured in</h2>
|
||||
</div>
|
||||
<div class="card releases">
|
||||
{{if .Credits}}
|
||||
{{range .Credits}}
|
||||
<div class="credit">
|
||||
<img src="{{.Release.Artwork}}" alt="" width="64" loading="lazy" class="release-artwork">
|
||||
<div class="credit-info">
|
||||
<h3 class="credit-name"><a href="/admin/release/{{.Release.ID}}">{{.Release.Title}}</a></h3>
|
||||
<p class="credit-artists">{{.Release.PrintArtists true true}}</p>
|
||||
<p class="artist-role">
|
||||
Role: {{.Role}}
|
||||
{{if .Primary}}
|
||||
<small>(Primary)</small>
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p>This artist has no credits.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Danger Zone</h2>
|
||||
</div>
|
||||
<div class="card danger">
|
||||
<p>
|
||||
Clicking the button below will delete this artist.
|
||||
This action is <strong>irreversible</strong>.
|
||||
You will be prompted to confirm this decision.
|
||||
</p>
|
||||
<button class="delete" id="delete">Delete Artist</button>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/edit-artist.js" defer></script>
|
||||
{{end}}
|
193
admin/templates/html/edit-release.html
Normal file
193
admin/templates/html/edit-release.html
Normal file
|
@ -0,0 +1,193 @@
|
|||
{{define "head"}}
|
||||
<title>Editing {{.Release.Title}} - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="{{.Release.GetArtwork}}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-release.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
|
||||
<div id="release" data-id="{{.Release.ID}}">
|
||||
<div class="release-artwork">
|
||||
<img src="{{.Release.Artwork}}" alt="" width="256" loading="lazy" id="artwork">
|
||||
<input type="file" id="artwork-file" name="Artwork" accept=".png,.jpg,.jpeg" hidden>
|
||||
<button id="remove-artwork">Remove</button>
|
||||
</div>
|
||||
<div class="release-info">
|
||||
<h1 class="release-title">
|
||||
<input type="text" id="title" name="Title" value="{{.Release.Title}}" autocomplete="on">
|
||||
</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td>
|
||||
{{$t := .Release.ReleaseType}}
|
||||
<select name="Type" id="type">
|
||||
<option value="single" {{if eq $t "single"}}selected{{end}}>
|
||||
Single
|
||||
</option>
|
||||
<option value="album" {{if eq $t "album"}}selected{{end}}>
|
||||
Album
|
||||
</option>
|
||||
<option value="ep" {{if eq $t "ep"}}selected{{end}}>
|
||||
EP
|
||||
</option>
|
||||
<option value="compilation" {{if eq $t "compilation"}}selected{{end}}>
|
||||
Compilation
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
<textarea
|
||||
name="Description"
|
||||
value="{{.Release.Description}}"
|
||||
placeholder="No description provided."
|
||||
rows="3"
|
||||
id="description"
|
||||
>{{.Release.Description}}</textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Release Date</td>
|
||||
<td>
|
||||
<input type="datetime-local" name="release-date" id="release-date" value="{{.Release.TextReleaseDate}}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Buy Name</td>
|
||||
<td>
|
||||
<input type="text" name="buyname" id="buyname" value="{{.Release.Buyname}}" autocomplete="on">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Buy Link</td>
|
||||
<td>
|
||||
<input type="text" name="buylink" id="buylink" value="{{.Release.Buylink}}" autocomplete="on">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Copyright</td>
|
||||
<td>
|
||||
<input type="text" name="copyright" id="copyright" value="{{.Release.Copyright}}" autocomplete="on">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Copyright URL</td>
|
||||
<td>
|
||||
<input type="text" name="copyright-url" id="copyright-url" value="{{.Release.CopyrightURL}}" autocomplete="on">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Visible</td>
|
||||
<td>
|
||||
<select name="Visibility" id="visibility">
|
||||
<option value="true" {{if .Release.Visible}}selected{{end}}>True</option>
|
||||
<option value="false" {{if not .Release.Visible}}selected{{end}}>False</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="release-actions">
|
||||
<a href="/music/{{.Release.ID}}" class="button" target="_blank">Gateway <img class="icon" src="/img/external-link.svg"/></a>
|
||||
<button type="submit" class="save" id="save" disabled>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Credits ({{len .Release.Credits}})</h2>
|
||||
<a class="button edit"
|
||||
href="/admin/release/{{.Release.ID}}/editcredits"
|
||||
hx-get="/admin/release/{{.Release.ID}}/editcredits"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
>Edit</a>
|
||||
</div>
|
||||
<div class="card credits">
|
||||
{{range .Release.Credits}}
|
||||
<div class="credit">
|
||||
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<div class="credit-info">
|
||||
<p class="artist-name"><a href="/admin/artist/{{.Artist.ID}}">{{.Artist.Name}}</a></p>
|
||||
<p class="artist-role">
|
||||
{{.Role}}
|
||||
{{if .Primary}}
|
||||
<small>(Primary)</small>
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .Release.Credits}}
|
||||
<p>There are no credits.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Links ({{len .Release.Links}})</h2>
|
||||
<a class="button edit"
|
||||
href="/admin/release/{{.Release.ID}}/editlinks"
|
||||
hx-get="/admin/release/{{.Release.ID}}/editlinks"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
>Edit</a>
|
||||
</div>
|
||||
<div class="card links">
|
||||
{{range .Release.Links}}
|
||||
<a href="{{.URL}}" target="_blank" class="button" data-name="{{.Name}}">{{.Name}} <img class="icon" src="/img/external-link.svg"/></a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title" id="tracks">
|
||||
<h2>Tracklist ({{len .Release.Tracks}})</h2>
|
||||
<a class="button edit"
|
||||
href="/admin/release/{{.Release.ID}}/edittracks"
|
||||
hx-get="/admin/release/{{.Release.ID}}/edittracks"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
>Edit</a>
|
||||
</div>
|
||||
<div class="card tracks">
|
||||
{{range $i, $track := .Release.Tracks}}
|
||||
<div class="track" data-id="{{$track.ID}}">
|
||||
<h2 class="track-title">
|
||||
<span class="track-number">{{.Add $i 1}}</span>
|
||||
<a href="/admin/track/{{$track.ID}}">{{$track.Title}}</a>
|
||||
</h2>
|
||||
|
||||
<h3>Description</h3>
|
||||
{{if $track.Description}}
|
||||
<p class="track-description">{{$track.GetDescriptionHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-description empty">No description provided.</p>
|
||||
{{end}}
|
||||
|
||||
<h3>Lyrics</h3>
|
||||
{{if $track.Lyrics}}
|
||||
<p class="track-lyrics">{{$track.GetLyricsHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Danger Zone</h2>
|
||||
</div>
|
||||
<div class="card danger">
|
||||
<p>
|
||||
Clicking the button below will delete this release.
|
||||
This action is <strong>irreversible</strong>.
|
||||
You will be prompted to confirm this decision.
|
||||
</p>
|
||||
<button class="delete" id="delete">Delete Release</button>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/edit-release.js" defer></script>
|
||||
{{end}}
|
70
admin/templates/html/edit-track.html
Normal file
70
admin/templates/html/edit-track.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
{{define "head"}}
|
||||
<title>Editing Track - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-track.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Editing Track</h1>
|
||||
|
||||
<div id="track" data-id="{{.Track.ID}}">
|
||||
<div class="track-info">
|
||||
<p class="attribute-header">Title</p>
|
||||
<h2 class="track-title">
|
||||
<input type="text" id="title" name="Title" value="{{.Track.Title}}">
|
||||
</h2>
|
||||
|
||||
<p class="attribute-header">Description</p>
|
||||
<textarea
|
||||
name="Description"
|
||||
value="{{.Track.Description}}"
|
||||
placeholder="No description provided."
|
||||
rows="5"
|
||||
id="description"
|
||||
>{{.Track.Description}}</textarea>
|
||||
|
||||
<p class="attribute-header">Lyrics</p>
|
||||
<textarea
|
||||
name="Lyrics"
|
||||
value="{{.Track.Lyrics}}"
|
||||
placeholder="There are no lyrics."
|
||||
rows="5"
|
||||
id="lyrics"
|
||||
>{{.Track.Lyrics}}</textarea>
|
||||
|
||||
<div class="track-actions">
|
||||
<button type="submit" class="save" id="save" disabled>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Featured in</h2>
|
||||
</div>
|
||||
<div class="card releases">
|
||||
{{if .Releases}}
|
||||
{{range .Releases}}
|
||||
{{block "release" .}}{{end}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p>This track isn't bound to a release.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Danger Zone</h2>
|
||||
</div>
|
||||
<div class="card danger">
|
||||
<p>
|
||||
Clicking the button below will delete this track.
|
||||
This action is <strong>irreversible</strong>.
|
||||
You will be prompted to confirm this decision.
|
||||
</p>
|
||||
<button class="delete" id="delete">Delete Track</button>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/edit-track.js" defer></script>
|
||||
{{end}}
|
72
admin/templates/html/index.html
Normal file
72
admin/templates/html/index.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
{{define "head"}}
|
||||
<title>Admin - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/index.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
|
||||
<div class="card-title">
|
||||
<h1>Releases</h1>
|
||||
<a class="button new" id="create-release">Create New</a>
|
||||
</div>
|
||||
<div class="card releases">
|
||||
{{range .Releases}}
|
||||
{{block "release" .}}{{end}}
|
||||
{{end}}
|
||||
{{if not .Releases}}
|
||||
<p>There are no releases.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h1>Artists</h1>
|
||||
<a class="button new" id="create-artist">Create New</a>
|
||||
</div>
|
||||
<div class="card artists">
|
||||
{{range $Artist := .Artists}}
|
||||
<div class="artist">
|
||||
<img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<a href="/admin/artist/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .Artists}}
|
||||
<p>There are no artists.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h1>Tracks</h1>
|
||||
<a class="button new" id="create-track">Create New</a>
|
||||
</div>
|
||||
<div class="card tracks">
|
||||
<p><em>"Orphaned" tracks that have not yet been bound to a release.</em></p>
|
||||
<br>
|
||||
{{range $Track := .Tracks}}
|
||||
<div class="track">
|
||||
<h2 class="track-title">
|
||||
<a href="/admin/track/{{$Track.ID}}">{{$Track.Title}}</a>
|
||||
</h2>
|
||||
{{if $Track.Description}}
|
||||
<p class="track-description">{{$Track.GetDescriptionHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-description empty">No description provided.</p>
|
||||
{{end}}
|
||||
{{if $Track.Lyrics}}
|
||||
<p class="track-lyrics">{{$Track.GetLyricsHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .Artists}}
|
||||
<p>There are no artists.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/admin.js"></script>
|
||||
<script type="module" src="/admin/static/index.js"></script>
|
||||
{{end}}
|
54
admin/templates/html/layout.html
Normal file
54
admin/templates/html/layout.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
{{block "head" .}}{{end}}
|
||||
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<script type="module" src="/script/vendor/htmx.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<img src="/img/favicon.png" alt="" class="icon">
|
||||
<div class="nav-item">
|
||||
<a href="/">ari melody</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/admin">home</a>
|
||||
</div>
|
||||
{{if .Session.Account}}
|
||||
<div class="nav-item">
|
||||
<a href="/admin/logs">logs</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="flex-fill"></div>
|
||||
|
||||
{{if .Session.Account}}
|
||||
<div class="nav-item">
|
||||
<a href="/admin/account">account ({{.Session.Account.Username}})</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/admin/logout" id="logout">log out</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="nav-item">
|
||||
<a href="/admin/register" id="register">create account</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{{block "content" .}}{{end}}
|
||||
|
||||
{{template "prideflag"}}
|
||||
</body>
|
||||
|
||||
</html>
|
47
admin/templates/html/login-totp.html
Normal file
47
admin/templates/html/login-totp.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
{{define "head"}}
|
||||
<title>Login - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<style>
|
||||
form#login-totp {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form div {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
form button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: calc(100% - 1rem - 2px);
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
{{if .Session.Message.Valid}}
|
||||
<p id="message">{{html .Session.Message.String}}</p>
|
||||
{{end}}
|
||||
{{if .Session.Error.Valid}}
|
||||
<p id="error">{{html .Session.Error.String}}</p>
|
||||
{{end}}
|
||||
|
||||
<form action="/admin/totp" method="POST" id="login-totp">
|
||||
<h1>Two-Factor Authentication</h1>
|
||||
|
||||
<div>
|
||||
<label for="totp">TOTP</label>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required autofocus>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save">Login</button>
|
||||
</form>
|
||||
</main>
|
||||
{{end}}
|
50
admin/templates/html/login.html
Normal file
50
admin/templates/html/login.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
{{define "head"}}
|
||||
<title>Login - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<style>
|
||||
form#login {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form div {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
form button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: calc(100% - 1rem - 2px);
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
{{if .Session.Message.Valid}}
|
||||
<p id="message">{{html .Session.Message.String}}</p>
|
||||
{{end}}
|
||||
{{if .Session.Error.Valid}}
|
||||
<p id="error">{{html .Session.Error.String}}</p>
|
||||
{{end}}
|
||||
|
||||
<form action="/admin/login" method="POST" id="login">
|
||||
<h1>Log In</h1>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" value="" autocomplete="username" required autofocus>
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" value="" autocomplete="current-password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save">Login</button>
|
||||
</form>
|
||||
</main>
|
||||
{{end}}
|
22
admin/templates/html/logout.html
Normal file
22
admin/templates/html/logout.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{{define "head"}}
|
||||
<title>Admin - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
|
||||
<style>
|
||||
p a {
|
||||
color: #2a67c8;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
|
||||
<meta http-equiv="refresh" content="0;url=/admin/login" />
|
||||
<p>
|
||||
Logged out successfully.
|
||||
You should be redirected to <a href="/admin/login">/admin/login</a> shortly.
|
||||
</p>
|
||||
|
||||
</main>
|
||||
{{end}}
|
68
admin/templates/html/logs.html
Normal file
68
admin/templates/html/logs.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
{{define "head"}}
|
||||
<title>Audit Logs - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<link rel="stylesheet" href="/admin/static/logs.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Audit Logs</h1>
|
||||
|
||||
<form action="/admin/logs" method="GET">
|
||||
<div id="search">
|
||||
<input type="text" name="q" value="" placeholder="Filter by message...">
|
||||
<button type="submit" class="save">Search</button>
|
||||
</div>
|
||||
<div id="filters">
|
||||
<div>
|
||||
<p>Level:</p>
|
||||
<label for="level-info">Info</label>
|
||||
<input type="checkbox" name="level-info" id="level-info">
|
||||
<label for="level-warn">Warning</label>
|
||||
<input type="checkbox" name="level-warn" id="level-warn">
|
||||
</div>
|
||||
<div>
|
||||
<p>Type:</p>
|
||||
<label for="type-account">Account</label>
|
||||
<input type="checkbox" name="type-account" id="type-account">
|
||||
<label for="type-music">Music</label>
|
||||
<input type="checkbox" name="type-music" id="type-music">
|
||||
<label for="type-artist">Artist</label>
|
||||
<input type="checkbox" name="type-artist" id="type-artist">
|
||||
<label for="type-blog">Blog</label>
|
||||
<input type="checkbox" name="type-blog" id="type-blog">
|
||||
<label for="type-artwork">Artwork</label>
|
||||
<input type="checkbox" name="type-artwork" id="type-artwork">
|
||||
<label for="type-files">Files</label>
|
||||
<input type="checkbox" name="type-files" id="type-files">
|
||||
<label for="type-misc">Misc</label>
|
||||
<input type="checkbox" name="type-misc" id="type-misc">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<table id="logs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="log-time">Time</th>
|
||||
<th class="log-level">Level</th>
|
||||
<th class="log-type">Type</th>
|
||||
<th class="log-content">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Logs}}
|
||||
<tr class="log {{lower (parseLevel .Level)}}">
|
||||
<td class="log-time">{{prettyTime .CreatedAt}}</td>
|
||||
<td class="log-level">{{parseLevel .Level}}</td>
|
||||
<td class="log-type">{{titleCase .Type}}</td>
|
||||
<td class="log-content">{{.Content}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
{{end}}
|
21
admin/templates/html/prideflag.html
Normal file
21
admin/templates/html/prideflag.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{{define "prideflag"}}
|
||||
<a href="https://github.com/arimelody/prideflag" target="_blank" id="prideflag">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" hx-preserve="true">
|
||||
<path id="red" d="M120,80 L100,100 L120,120 Z" style="fill:#d20605"/>
|
||||
<path id="orange" d="M120,80 V40 L80,80 L100,100 Z" style="fill:#ef9c00"/>
|
||||
<path id="yellow" d="M120,40 V0 L60,60 L80,80 Z" style="fill:#e5fe02"/>
|
||||
<path id="green" d="M120,0 H80 L40,40 L60,60 Z" style="fill:#09be01"/>
|
||||
<path id="blue" d="M80,0 H40 L20,20 L40,40 Z" style="fill:#081a9a"/>
|
||||
<path id="purple" d="M40,0 H0 L20,20 Z" style="fill:#76008a"/>
|
||||
|
||||
<rect id="black" x="60" width="60" height="60" style="fill:#010101"/>
|
||||
<rect id="brown" x="70" width="50" height="50" style="fill:#603814"/>
|
||||
<rect id="lightblue" x="80" width="40" height="40" style="fill:#73d6ed"/>
|
||||
<rect id="pink" x="90" width="30" height="30" style="fill:#ffafc8"/>
|
||||
<rect id="white" x="100" width="20" height="20" style="fill:#fff"/>
|
||||
|
||||
<rect id="intyellow" x="110" width="10" height="10" style="fill:#fed800"/>
|
||||
<circle id="intpurple" cx="120" cy="0" r="5" stroke="#7601ad" stroke-width="2" fill="none"/>
|
||||
</svg>
|
||||
</a>
|
||||
{{end}}
|
61
admin/templates/html/register.html
Normal file
61
admin/templates/html/register.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{{define "head"}}
|
||||
<title>Register - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<style>
|
||||
p a {
|
||||
color: #2a67c8;
|
||||
}
|
||||
|
||||
a.discord {
|
||||
color: #5865F2;
|
||||
}
|
||||
|
||||
form#register {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form div {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
form button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: calc(100% - 1rem - 2px);
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
{{if .Session.Error.Valid}}
|
||||
<p id="error">{{html .Session.Error.String}}</p>
|
||||
{{end}}
|
||||
|
||||
<form action="/admin/register" method="POST" id="register">
|
||||
<h1>Create Account</h1>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" value="" autocomplete="username" required autofocus>
|
||||
|
||||
<label for="email">Email</label>
|
||||
<input type="text" name="email" value="" autocomplete="email" required>
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" value="" autocomplete="new-password" required>
|
||||
|
||||
<label for="invite">Invite Code</label>
|
||||
<input type="text" name="invite" value="" autocomplete="off" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="new">Create Account</button>
|
||||
</form>
|
||||
</main>
|
||||
{{end}}
|
48
admin/templates/html/totp-confirm.html
Normal file
48
admin/templates/html/totp-confirm.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
{{define "head"}}
|
||||
<title>TOTP Confirmation - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<style>
|
||||
.qr-code {
|
||||
border: 1px solid #8888;
|
||||
}
|
||||
code {
|
||||
user-select: all;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
{{if .Session.Error.Valid}}
|
||||
<p id="error">{{html .Session.Error.String}}</p>
|
||||
{{end}}
|
||||
|
||||
<form action="/admin/account/totp-confirm?totp-name={{.NameEscaped}}" method="POST" id="totp-setup">
|
||||
{{if .QRBase64Image}}
|
||||
<img src="data:image/png;base64,{{.QRBase64Image}}" alt="" class="qr-code">
|
||||
|
||||
<p>
|
||||
Scan the QR code above into your authentication app or password manager,
|
||||
then enter your 2FA code below.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If the QR code does not work, you may also enter this secret code:
|
||||
</p>
|
||||
{{else}}
|
||||
<p>
|
||||
Paste the below secret code into your authentication app or password manager,
|
||||
then enter your 2FA code below:
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
<p><code>{{.TOTP.Secret}}</code></p>
|
||||
|
||||
<label for="totp">TOTP:</label>
|
||||
<input type="text" name="totp" value="" autocomplete="one-time-code" required autofocus>
|
||||
|
||||
<button type="submit" class="new">Create</button>
|
||||
</form>
|
||||
</main>
|
||||
{{end}}
|
20
admin/templates/html/totp-setup.html
Normal file
20
admin/templates/html/totp-setup.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{{define "head"}}
|
||||
<title>TOTP Setup - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
{{if .Session.Error.Valid}}
|
||||
<p id="error">{{html .Session.Error.String}}</p>
|
||||
{{end}}
|
||||
|
||||
<form action="/admin/account/totp-setup" method="POST" id="totp-setup">
|
||||
<label for="totp-name">TOTP Device Name:</label>
|
||||
<input type="text" name="totp-name" value="" autocomplete="off" required autofocus>
|
||||
|
||||
<button type="submit" class="new">Create</button>
|
||||
</form>
|
||||
</main>
|
||||
{{end}}
|
Loading…
Add table
Add a link
Reference in a new issue