Admin page refresh. (#6)

Added settings and options.
Settings parsing catch.
Fix postSetting API responses.
Adjusted spinner visibility toggle.

Reviewed-on: #6
Co-authored-by: Armored-Dragon <forgejo3829105@armoreddragon.com>
Co-committed-by: Armored-Dragon <forgejo3829105@armoreddragon.com>
pull/2/head
Armored Dragon 2023-11-28 20:52:19 +00:00 committed by Armored Dragon
parent 94424df08f
commit 83da8100dc
11 changed files with 348 additions and 75 deletions

View File

@ -19,6 +19,12 @@ let settings = {
HIDE_LOGIN: false,
BLOG_UPLOADING: false,
CD_RSS: false,
CD_AP: false,
WEBSITE_NAME: "",
PLAUSIBLE_URL: "",
USER_MINIMUM_PASSWORD_LENGTH: 7,
BLOG_MINIMUM_TITLE_LENGTH: 7,
@ -372,9 +378,18 @@ async function _getSettings() {
let found_value = await prisma.setting.findUnique({ where: { id: key } });
if (!found_value) return;
return (settings[key] = JSON.parse(found_value.value));
let value;
// Parse JSON if possible
try {
value = JSON.parse(found_value.value);
} catch {
value = found_value.value;
}
return (settings[key] = value);
});
}
async function getSetting(key, { parse = true }) {
if (!settings[key]) return null;

View File

@ -43,9 +43,7 @@ async function postSetting(request, response) {
if (!user.success) return response.json({ success: false, message: user.message });
if (user.data.role !== "ADMIN") return response.json({ success: false, message: "User is not permitted" });
await core.postSetting(request.body.setting_name, request.body.value);
response.json({ success: true });
response.json(await core.postSetting(request.body.setting_name, request.body.value));
}
async function deleteImage(req, res) {
// TODO: Permissions for deleting image

View File

@ -2,7 +2,8 @@ const external = require("./core/external_api");
const core = require("./core/core");
function getDefaults(req) {
return { logged_in_user: req.session.user, website_name: process.env.WEBSITE_NAME, settings: core.settings };
// TODO: Fix reference to website_name
return { logged_in_user: req.session.user, website_name: core.settings.WEBSITE_NAME || "Yet-Another-Blog", settings: core.settings };
}
async function index(request, response) {

View File

@ -2,6 +2,7 @@ body {
margin: 0;
background-color: #111;
color: white;
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.header {
@ -58,11 +59,18 @@ button:focus {
background-color: #122d57;
}
button.good {
button.good,
a.good {
background-color: #015b01;
}
button.bad {
button.yellow,
a.yellow {
background-color: #4a4a00;
}
button.bad,
a.bad {
background-color: #8e0000;
}
@ -75,9 +83,78 @@ button.bad {
display: flex;
flex-direction: row;
}
.page .horizontal-button-container button {
width: 100px;
.page .horizontal-button-container button,
.page .horizontal-button-container a {
width: 120px;
min-height: 30px;
text-decoration: none;
display: flex;
margin-right: 5px;
}
.page .horizontal-button-container button span,
.page .horizontal-button-container a span {
margin: auto;
}
.page .horizontal-button-container button:last-of-type,
.page .horizontal-button-container a:last-of-type {
margin-right: 0;
}
.page .blog-admin {
margin-bottom: 10px;
}
.page .pagination {
display: flex;
flex-direction: row;
width: 100%;
margin: 0 auto;
}
.page .pagination a {
height: 40px;
width: 150px;
margin-right: 5px;
display: flex;
text-decoration: none;
background-color: #222;
}
.page .pagination a span {
margin: auto;
color: white;
font-size: 20px;
}
.page .pagination a:last-of-type {
margin-right: 0;
margin-left: 5px;
}
.page .pagination a.disabled {
filter: brightness(50%);
}
.page .pagination .page-list {
flex-grow: 1;
display: flex;
flex-direction: row;
margin-bottom: 50px;
}
.page .pagination .page-list a {
width: 40px;
height: 40px;
display: flex;
text-decoration: none;
background-color: #222;
border-radius: 10px;
margin: 0 10px 0 0;
}
.page .pagination .page-list a span {
margin: auto;
color: white;
}
.page .pagination .page-list a:first-of-type {
margin: auto 10px auto auto;
}
.page .pagination .page-list a:last-of-type {
margin: auto auto auto 0;
}
.page .pagination .page-list a.active {
background-color: #993d00;
}
.hidden {
@ -86,7 +163,7 @@ button.bad {
@media screen and (max-width: 1010px) {
.page {
width: 100%;
width: 95%;
}
}
.container {
@ -95,32 +172,73 @@ button.bad {
padding: 10px 20px;
box-sizing: border-box;
}
.container .settings-group .settings-row {
.container .category-navigation {
width: 100%;
height: 30px;
display: flex;
margin-bottom: 15px;
}
.container .category-navigation button {
width: 100%;
margin-right: 5px;
border-radius: 5px;
text-decoration: none;
display: flex;
}
.container .category-navigation button span {
margin: auto;
color: white;
}
.container .category-navigation button:not(.active) {
background-color: #4c515e;
}
.container .category-navigation button:hover,
.container .category-navigation button:focus {
filter: brightness(50%);
}
.container .setting-row {
display: flex;
flex-direction: row;
padding: 4px;
min-height: 30px;
padding: 5px;
box-sizing: border-box;
}
.container .settings-group .settings-row .label {
.container .setting-row .setting-title {
font-size: 18px;
margin: auto auto auto 0;
}
.container .settings-group .settings-row .button-group {
.container .setting-row .setting-toggleable {
min-width: 150px;
display: flex;
flex-direction: row;
margin: auto 0 auto auto;
}
.container .settings-group .settings-row .button-group .spinner {
margin: auto 10px auto auto;
.container .setting-row .setting-toggleable .spinner {
margin: auto 0 auto auto;
animation: spin 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.container .settings-group .settings-row .button-group button {
height: 30px;
padding: 0px 20px;
.container .setting-row .setting-toggleable input {
padding: 0;
margin: auto 0 auto auto;
width: 100px;
box-sizing: border-box;
text-align: center;
border-radius: 10px;
}
.container .settings-group .settings-row:nth-child(even) {
background-color: rgba(0, 0, 0, 0.2);
.container .setting-row .setting-toggleable input[type=text] {
width: 350px;
}
.container .setting-row .setting-toggleable button {
width: 125px;
margin-left: auto;
height: 30px;
}
.container .setting-row:nth-child(even) {
background-color: #1c1c1c;
}
.container .setting-row:nth-child(odd) {
background-color: #191919;
}
@keyframes spin {

View File

@ -6,35 +6,89 @@
padding: 10px 20px;
box-sizing: border-box;
.settings-group {
.settings-row {
.category-navigation {
width: 100%;
height: 30px;
display: flex;
margin-bottom: 15px;
button {
width: 100%;
margin-right: 5px;
border-radius: 5px;
text-decoration: none;
display: flex;
span {
margin: auto;
color: white;
}
}
button:not(.active) {
background-color: #4c515e;
}
button:hover,
button:focus {
filter: brightness(50%);
}
button:active {
// display: none;
}
}
.setting-row {
display: flex;
flex-direction: row;
padding: 4px;
min-height: 30px;
padding: 5px;
box-sizing: border-box;
.label {
.setting-title {
font-size: 18px;
margin: auto auto auto 0;
}
.button-group {
.setting-toggleable {
min-width: 150px;
display: flex;
flex-direction: row;
margin: auto 0 auto auto;
.spinner {
margin: auto 10px auto auto;
margin: auto 0 auto auto;
animation: spin 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
input {
padding: 0;
margin: auto 0 auto auto;
// width: 100%;
width: 100px;
box-sizing: border-box;
text-align: center;
border-radius: 10px;
}
input[type="text"] {
width: 350px;
}
button {
width: 125px;
margin-left: auto;
height: 30px;
padding: 0px 20px;
}
}
}
.settings-row:nth-child(even) {
background-color: #00000033;
.setting-row:nth-child(even) {
background-color: #1c1c1c;
}
.setting-row:nth-child(odd) {
background-color: #191919;
}
}
@keyframes spin {

View File

@ -9,10 +9,11 @@ async function toggleState(setting_name, new_value, element_id) {
const response = await request("/setting", "POST", form);
// Check response for errors
if (response.body.success) {
qs(`#${element_id}`).parentNode.querySelector(".spinner").classList.add("hidden");
// TODO: On failure, notify the user
// Check response for errors
if (response.body.success) {
// Update visual to reflect current setting
// Class
const add_class = new_value ? "good" : "bad";
@ -30,3 +31,42 @@ async function toggleState(setting_name, new_value, element_id) {
qs(`#${element_id}`).setAttribute("onclick", add_function);
}
}
async function updateValue(key_pressed, setting_name, new_value, element_id) {
if (key_pressed !== 13) return;
// Show spinner
qs(`#${element_id}`).parentNode.querySelector(".spinner").classList.remove("hidden");
const form = {
setting_name: setting_name,
value: new_value,
};
const response = await request("/setting", "POST", form);
qs(`#${element_id}`).parentNode.querySelector(".spinner").classList.add("hidden");
// TODO: On failure, notify the user
// Check response for errors
if (response.body.success) {
}
}
function toggleActiveCategory(category_id) {
// Pages ----------------
// Hide all pages
qsa(".category-page").forEach((page) => {
page.classList.add("hidden");
});
// Show requested page
qs(`#${category_id}`).classList.remove("hidden");
// Navigation bar -------
// Unactive all buttons
qsa(".category-navigation button").forEach((btn) => {
btn.classList.remove("active");
});
// Active current page
qs(`#${category_id}-nav-btn`).classList.add("active");
qs(`#${category_id}-nav-btn`).blur(); // Unfocus the button
}

View File

@ -2,6 +2,7 @@
const qs = (selector) => document.querySelector(selector);
const qsa = (selector) => document.querySelectorAll(selector);
// TODO: Try/Catch for failed requests
async function request(url, method, body) {
const response = await fetch(url, {
method: method,

View File

@ -13,35 +13,38 @@
<div class="page">
<div class="container">
<div class="settings-group">
<h1>Accounts</h1>
<div class="settings-row">
<div class="label">Account registration</div>
<div class="button-group">
<div class="spinner hidden">
<div>↺</div>
</div>
<% if (!settings.ACCOUNT_REGISTRATION ) { %>
<button id="registration_toggle" onclick="toggleState('ACCOUNT_REGISTRATION', true, this.id)" class="bad"><div>Disabled</div></button>
<% } else { %>
<button id="registration_toggle" onclick="toggleState('ACCOUNT_REGISTRATION', false, this.id)" class="good"><div>Enabled</div></button>
<%}%>
<div class="category-navigation">
<button id="accounts-nav-btn" class="active" onclick="toggleActiveCategory('accounts')"><span>Accounts</span></button>
<button id="content-delivery-nav-btn" onclick="toggleActiveCategory('content-delivery')"><span>Content Delivery</span></button>
<button id="groups-nav-btn" onclick="toggleActiveCategory('groups')"><span>Groups</span></button>
<button id="yab-master-nav-btn" onclick="toggleActiveCategory('yab-master')"><span>YAB</span></button>
</div>
<div id="accounts" class="category-page">
<!-- Account registration -->
<%- include("partials/admin-setting-toggle.ejs", {setting: {name: 'ACCOUNT_REGISTRATION', name_pretty: 'Account registration', enabled:
settings.ACCOUNT_REGISTRATION}}) %>
<!-- Hide login -->
<%- include("partials/admin-setting-toggle.ejs", {setting: {name: 'HIDE_LOGIN', name_pretty: 'Hide Login', enabled: settings.HIDE_LOGIN}}) %>
<!-- Minimum password length -->
<%- include("partials/admin-setting-number.ejs", {setting: {name:'USER_MINIMUM_PASSWORD_LENGTH', name_pretty: 'Minimum Password Length', value:
settings.USER_MINIMUM_PASSWORD_LENGTH}}) %>
</div>
<div class="settings-row">
<div class="label">Hide Login</div>
<div class="button-group">
<div class="spinner hidden">
<div>↺</div>
</div>
<% if (!settings.HIDE_LOGIN ) { %>
<button id="hidelogin_toggle" onclick="toggleState('HIDE_LOGIN', true, this.id)" class="bad"><div>Disabled</div></button>
<% } else { %>
<button id="hidelogin_toggle" onclick="toggleState('HIDE_LOGIN', false, this.id)" class="good"><div>Enabled</div></button>
<%}%>
</div>
<div id="content-delivery" class="category-page hidden">
<!-- RSS -->
<%- include("partials/admin-setting-toggle.ejs", {setting: {name: 'CD_RSS', name_pretty: 'RSS Feed', enabled: settings.CD_RSS}}) %>
<!-- ActivityPub -->
<%- include("partials/admin-setting-toggle.ejs", {setting: {name: 'CD_AP', name_pretty: 'Activity Pub Feed', enabled: settings.CD_AP}}) %>
<!-- TODO: Add these option automatically to footer on active -->
</div>
<div id="groups" class="category-page hidden">TODO</div>
<div id="yab-master" class="category-page hidden">
<!-- Website title -->
<%- include("partials/admin-setting-text.ejs", {setting: {name: 'WEBSITE_NAME', name_pretty: 'Website Title', value: settings.WEBSITE_NAME}}) %>
<!-- Placeholder thumbnail -->
<!-- Plausible analytics -->
<%- include("partials/admin-setting-text.ejs", {setting: {name: 'PLAUSIBLE_URL', name_pretty: 'Plausible URL', value: settings.PLAUSIBLE_URL}}) %>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
<div class="setting-row">
<div class="setting-title"><%=setting.name_pretty%></div>
<div class="setting-toggleable">
<div class="spinner hidden">
<div>↺</div>
</div>
<input
type="number"
id="<%=setting.name%>"
onkeydown="updateValue(event.keyCode, '<%=setting.name%>', this.value, this.id)"
placeholder="<%=setting.name_pretty%>"
value="<%=setting.value%>"
/>
</div>
</div>

View File

@ -0,0 +1,15 @@
<div class="setting-row">
<div class="setting-title"><%=setting.name_pretty%></div>
<div class="setting-toggleable">
<div class="spinner hidden">
<div>↺</div>
</div>
<input
id="<%=setting.name%>"
type="text"
onkeydown="updateValue(event.keyCode, '<%=setting.name%>', this.value, this.id)"
placeholder="<%=setting.name_pretty%>"
value="<%=setting.value%>"
/>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="setting-row">
<div class="setting-title"><%=setting.name_pretty%></div>
<div class="setting-toggleable">
<div class="spinner hidden">
<div>↺</div>
</div>
<% if (!setting.enabled) { %>
<button id="<%=setting.name%>_toggle" onclick="toggleState('<%=setting.name%>', true, this.id)" class="bad"><div>Disabled</div></button>
<% } else { %>
<button id="<%=setting.name%>_toggle" onclick="toggleState('<%=setting.name%>', false, this.id)" class="good"><div>Enabled</div></button>
<%}%>
</div>
</div>