Config page for adding tokens and settings
This commit is contained in:
108
static/config.js
Normal file
108
static/config.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Map config keys to input IDs
|
||||
const CONFIG_FIELDS = {
|
||||
"TORN_API_KEY": "torn-api-key",
|
||||
"FFSCOUTER_KEY": "ffscouter-key",
|
||||
"DISCORD_TOKEN": "discord-token",
|
||||
"ALLOWED_CHANNEL_ID": "allowed-channel-id",
|
||||
"POLL_INTERVAL": "poll-interval",
|
||||
"HIT_CHECK_INTERVAL": "hit-check-interval",
|
||||
"REASSIGN_DELAY": "reassign-delay",
|
||||
"ASSIGNMENT_TIMEOUT": "assignment-timeout",
|
||||
"ASSIGNMENT_REMINDER": "assignment-reminder"
|
||||
};
|
||||
|
||||
let sensitiveFields = [];
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const res = await fetch("/api/config", { cache: "no-store" });
|
||||
if (!res.ok) {
|
||||
console.error("Failed to load config:", res.status);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
sensitiveFields = data.sensitive_fields || [];
|
||||
|
||||
// Populate form fields
|
||||
for (const [key, inputId] of Object.entries(CONFIG_FIELDS)) {
|
||||
const input = document.getElementById(inputId);
|
||||
if (input && data.config[key] !== undefined) {
|
||||
input.value = data.config[key];
|
||||
|
||||
// Add placeholder for masked sensitive fields
|
||||
if (sensitiveFields.includes(key) && String(data.config[key]).startsWith("****")) {
|
||||
input.placeholder = "Current: " + data.config[key];
|
||||
input.value = ""; // Clear the masked value
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error loading config:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveConfigValue(key) {
|
||||
const inputId = CONFIG_FIELDS[key];
|
||||
const input = document.getElementById(inputId);
|
||||
|
||||
if (!input) {
|
||||
console.error("Input not found:", inputId);
|
||||
return;
|
||||
}
|
||||
|
||||
let value = input.value.trim();
|
||||
|
||||
// Don't save if sensitive field is empty (means user didn't change it)
|
||||
if (sensitiveFields.includes(key) && value === "") {
|
||||
alert("No changes to save");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to number if needed
|
||||
if (input.type === "number") {
|
||||
value = parseInt(value);
|
||||
if (isNaN(value)) {
|
||||
alert("Invalid number");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/config", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ key, value })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("Save failed:", res.status);
|
||||
alert("Failed to save config");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
console.log("Config saved:", data);
|
||||
|
||||
// Reload to show masked value
|
||||
await loadConfig();
|
||||
alert("Saved successfully");
|
||||
} catch (err) {
|
||||
console.error("Error saving config:", err);
|
||||
alert("Error saving config");
|
||||
}
|
||||
}
|
||||
|
||||
function wireUp() {
|
||||
// Attach save handlers to all save buttons
|
||||
const saveButtons = document.querySelectorAll(".config-save-btn");
|
||||
saveButtons.forEach(btn => {
|
||||
const key = btn.dataset.key;
|
||||
btn.addEventListener("click", () => saveConfigValue(key));
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
wireUp();
|
||||
await loadConfig();
|
||||
});
|
||||
@@ -617,12 +617,14 @@ async function toggleFriendlyStatus() {
|
||||
if (friendlyStatusIntervalHandle) {
|
||||
clearInterval(friendlyStatusIntervalHandle);
|
||||
friendlyStatusIntervalHandle = null;
|
||||
btn.textContent = "Start Refresh";
|
||||
btn.textContent = "Start";
|
||||
btn.dataset.running = "false";
|
||||
btn.style.backgroundColor = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const id = toInt(document.getElementById("friendly-id").value);
|
||||
const interval = Math.max(1, toInt(document.getElementById("refresh-interval").value) || 10);
|
||||
const interval = Math.max(1, toInt(document.getElementById("friendly-refresh-interval").value) || 10);
|
||||
|
||||
await fetch("/api/start_friendly_status", {
|
||||
method: "POST",
|
||||
@@ -632,7 +634,9 @@ async function toggleFriendlyStatus() {
|
||||
|
||||
friendlyStatusIntervalHandle = setInterval(() => refreshStatus("friendly"), interval * 1000);
|
||||
refreshStatus("friendly");
|
||||
btn.textContent = "Stop Refresh";
|
||||
btn.textContent = "Stop";
|
||||
btn.dataset.running = "true";
|
||||
btn.style.backgroundColor = "#ff6b6b";
|
||||
}
|
||||
|
||||
async function toggleEnemyStatus() {
|
||||
@@ -640,12 +644,14 @@ async function toggleEnemyStatus() {
|
||||
if (enemyStatusIntervalHandle) {
|
||||
clearInterval(enemyStatusIntervalHandle);
|
||||
enemyStatusIntervalHandle = null;
|
||||
btn.textContent = "Start Refresh";
|
||||
btn.textContent = "Start";
|
||||
btn.dataset.running = "false";
|
||||
btn.style.backgroundColor = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const id = toInt(document.getElementById("enemy-id").value);
|
||||
const interval = Math.max(1, toInt(document.getElementById("refresh-interval").value) || 10);
|
||||
const interval = Math.max(1, toInt(document.getElementById("enemy-refresh-interval").value) || 10);
|
||||
|
||||
await fetch("/api/start_enemy_status", {
|
||||
method: "POST",
|
||||
@@ -655,7 +661,9 @@ async function toggleEnemyStatus() {
|
||||
|
||||
enemyStatusIntervalHandle = setInterval(() => refreshStatus("enemy"), interval * 1000);
|
||||
refreshStatus("enemy");
|
||||
btn.textContent = "Stop Refresh";
|
||||
btn.textContent = "Stop";
|
||||
btn.dataset.running = "true";
|
||||
btn.style.backgroundColor = "#ff6b6b";
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
|
||||
@@ -133,9 +133,71 @@ body {
|
||||
}
|
||||
|
||||
.faction-card.small h2 { color: #66ccff; margin: 0; }
|
||||
.faction-card .controls { display:flex; gap: 0.5rem; align-items:center; margin-bottom: 6px; }
|
||||
|
||||
/* Controls row - wraps both populate and status controls */
|
||||
.faction-card .controls-row {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.faction-card .controls {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.faction-card .controls input { padding: 0.5rem; border-radius:6px; border: none; }
|
||||
|
||||
/* Status controls section */
|
||||
.faction-card .status-controls {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
background-color: #3a3a4d;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.faction-card .status-controls label {
|
||||
color: #ffcc66;
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.faction-card .status-controls input {
|
||||
width: 38px;
|
||||
padding: 2px 3px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* Hide spinner buttons in Chrome, Safari, Edge */
|
||||
.faction-card .status-controls input::-webkit-outer-spin-button,
|
||||
.faction-card .status-controls input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Status button */
|
||||
.status-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
min-width: 60px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.status-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.status-btn[data-running="true"] {
|
||||
background-color: #ff6b6b;
|
||||
}
|
||||
|
||||
/* member list in left column */
|
||||
.member-list {
|
||||
max-height: 380px;
|
||||
@@ -237,3 +299,92 @@ button:hover { background-color: #3399ff; }
|
||||
/* scrollbar niceties for drop zones and lists */
|
||||
.member-list::-webkit-scrollbar, .drop-zone::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
.member-list::-webkit-scrollbar-thumb, .drop-zone::-webkit-scrollbar-thumb { background: #66ccff; border-radius: 4px; }
|
||||
|
||||
/* =============================
|
||||
Config Page Styles
|
||||
============================= */
|
||||
|
||||
/* Navigation link */
|
||||
.nav-link {
|
||||
color: #66ccff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 8px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: rgba(102, 204, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Config page layout */
|
||||
.config-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.config-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.config-group:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.config-group label {
|
||||
color: #66ccff;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.config-description {
|
||||
color: #99a7bf;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.config-input {
|
||||
padding: 0.6rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: #1a1a26;
|
||||
color: #f0f0f0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.config-input:focus {
|
||||
outline: none;
|
||||
border-color: #66ccff;
|
||||
}
|
||||
|
||||
.config-save-btn {
|
||||
align-self: flex-start;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.config-save-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user