Big one: Add admin panel
This commit is contained in:
parent
127de127a7
commit
a28f672516
248
index.js
248
index.js
|
@ -38,6 +38,12 @@ const port = process.env.SERVER_PORT || 3000;
|
|||
|
||||
app.use(express.json());
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
const ejs = require("ejs")
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', __dirname + '/views');
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (!process.env.LOGFILE) return next();
|
||||
var requestIp = req.ip;
|
||||
|
@ -59,6 +65,7 @@ const reasonFlagTypes = [
|
|||
|
||||
const reasonFlags = flags.defineFlags(reasonFlagTypes)
|
||||
process.env.REASON_FLAGS = JSON.stringify(reasonFlags)
|
||||
console.log(process.env.REASON_FLAGS)
|
||||
|
||||
// Discord stuff
|
||||
const Discord = require("discord.js");
|
||||
|
@ -94,227 +101,29 @@ client.on("ready", async () => {
|
|||
})();
|
||||
});
|
||||
|
||||
|
||||
// In-memory storage for the ban command (store each message id so we can track flags)
|
||||
const banMessages = {}
|
||||
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
// Switch by type (either slash command or modal)
|
||||
switch (interaction.type) {
|
||||
// Slash Command Handler
|
||||
case Discord.InteractionType.ApplicationCommand:
|
||||
if (!interaction.isCommand()) return;
|
||||
const command = interaction.commandName;
|
||||
const args = interaction.options;
|
||||
switch (command) {
|
||||
// Report Command
|
||||
case "report":
|
||||
robloxId = args.getNumber("roblox_id");
|
||||
reason = args.getString("reason");
|
||||
// TODO: Report Command
|
||||
break;
|
||||
|
||||
// Ban Command
|
||||
case "ban":
|
||||
robloxId = args.getString("roblox_id");
|
||||
discordId = args.getString("discord_id");
|
||||
reason = args.getString("reason");
|
||||
if (robloxId && !robloxId.match(/^\d+$/)) {
|
||||
return interaction.reply({
|
||||
ephemeral: true,
|
||||
content: "Invalid Roblox ID!"
|
||||
})
|
||||
}
|
||||
if (discordId && !discordId.match(/^\d+$/)) {
|
||||
return interaction.reply({
|
||||
ephemeral: true,
|
||||
content: "Invalid Discord ID!"
|
||||
})
|
||||
}
|
||||
|
||||
if (!robloxId && !discordId) {
|
||||
return interaction.reply({
|
||||
ephemeral: true,
|
||||
content: "Specify a Roblox ID and/or Discord ID!"
|
||||
})
|
||||
}
|
||||
|
||||
if (robloxId) {
|
||||
try {
|
||||
robloxUsername = await noblox.getUsernameFromId(robloxId) || "Unknown"
|
||||
} catch (e) {
|
||||
return interaction.reply({
|
||||
ephemeral: true,
|
||||
content: "Invalid Roblox ID!"
|
||||
})
|
||||
}
|
||||
} else {
|
||||
robloxUsername = null
|
||||
}
|
||||
|
||||
if (discordId) {
|
||||
discordUsername = (await client.users.fetch(discordId))?.username || "Unknown"
|
||||
} else {
|
||||
discordUsername = null
|
||||
}
|
||||
|
||||
embed = {
|
||||
title: "Ban User",
|
||||
color: 0xff0000,
|
||||
fields: [
|
||||
robloxId ? { name: "Roblox", value: `${robloxUsername} (${robloxId})` } : null,
|
||||
discordId ? { name: "Discord ID", value: `${discordUsername} (${discordId})` } : null,
|
||||
{ name: "Reason", value: reason},
|
||||
{ name: "Moderator", value: interaction.user.tag }
|
||||
].filter(field => field !== null)
|
||||
}
|
||||
flagButtons = await reasonFlagTypes.map(flag => {
|
||||
return new Discord.ButtonBuilder()
|
||||
.setCustomId(flag)
|
||||
.setStyle(Discord.ButtonStyle.Danger)
|
||||
.setLabel(flag)
|
||||
})
|
||||
|
||||
submitButton = new Discord.ButtonBuilder()
|
||||
.setCustomId("ban")
|
||||
.setStyle(Discord.ButtonStyle.Primary)
|
||||
.setLabel("Ban")
|
||||
.setEmoji("🔨")
|
||||
interaction.reply({
|
||||
ephemeral: true,
|
||||
embeds: [embed],
|
||||
components: [
|
||||
new Discord.ActionRowBuilder().addComponents(flagButtons),
|
||||
new Discord.ActionRowBuilder().addComponents(submitButton)
|
||||
]
|
||||
})
|
||||
|
||||
rep = await interaction.fetchReply()
|
||||
banMessages[rep.id] = {
|
||||
flags: 0,
|
||||
robloxId,
|
||||
discordId,
|
||||
robloxUsername,
|
||||
discordUsername,
|
||||
moderator: interaction.user.id,
|
||||
reason,
|
||||
interaction: interaction
|
||||
}
|
||||
break;
|
||||
case "unban":
|
||||
robloxId = args.getNumber("roblox_id");
|
||||
discordId = args.getString("discord_id");
|
||||
// In the db, find any instance of either robloxId or discordId and set if the expiry is null or in the future, set it to now
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.query('UPDATE bans SET expiresTimestamp = UTC_TIMESTAMP() WHERE robloxId = ? OR discordId = ? AND (expiresTimestamp IS NULL OR expiresTimestamp > UTC_TIMESTAMP())', [robloxId || uuid(), discordId || uuid()]);
|
||||
interaction.reply({
|
||||
embeds: [
|
||||
{
|
||||
title: "User Unbanned",
|
||||
color: 0x00ff00,
|
||||
fields: [
|
||||
robloxId ? { name: "Roblox", value: robloxId } : null,
|
||||
discordId ? { name: "Discord ID", value: discordId } : null,
|
||||
{ name: "Moderator", value: interaction.user.tag }
|
||||
].filter(field => field !== null)
|
||||
}
|
||||
]
|
||||
})
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
interaction.reply({
|
||||
embeds: [
|
||||
{
|
||||
title: "Error",
|
||||
color: 0xff0000,
|
||||
description: "An error occurred while unbanning the user."
|
||||
}
|
||||
]
|
||||
})
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
break;
|
||||
};
|
||||
break;
|
||||
|
||||
// Modal Handler
|
||||
case Discord.InteractionType.MessageComponent:
|
||||
if (!interaction.isButton()) return;
|
||||
const flag = interaction.customId;
|
||||
const message = banMessages[interaction.message.id];
|
||||
if (!message) return interaction.reply({
|
||||
ephemeral: true,
|
||||
content: "Invalid message!"
|
||||
})
|
||||
|
||||
if (flag == "ban") {
|
||||
// Ban the user by adding a ban record to the database
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.query('INSERT INTO bans (robloxId, discordId, robloxUsername, discordUsername, reasonShort, moderator, reasonsFlag) VALUES (?, ?, ?, ?, ?, ? ,?)', [message.robloxId, message.discordId, message.robloxUsername, message.discordUsername, message.reason, message.moderator, message.flags]);
|
||||
message.interaction.editReply({
|
||||
embeds: [
|
||||
{
|
||||
title: "User Banned",
|
||||
color: 0xff0000,
|
||||
fields: [
|
||||
message.robloxId ? { name: "Roblox", value: `${message.robloxUsername} (${message.robloxId})` } : null,
|
||||
message.discordId ? { name: "Discord ID", value: `${message.discordUsername} (${message.discordId})` }: null,
|
||||
{ name: "Moderator", value: interaction.user.tag }
|
||||
].filter(field => field !== null)
|
||||
}
|
||||
],
|
||||
components: []
|
||||
})
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
message.interaction.editReply({
|
||||
embeds: [
|
||||
{
|
||||
title: "Error",
|
||||
color: 0xff0000,
|
||||
description: "An error occurred while banning the user."
|
||||
}
|
||||
],
|
||||
components: []
|
||||
})
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
} else {
|
||||
message.flags ^= reasonFlags[flag]
|
||||
interaction.deferUpdate();
|
||||
flagButtons = await reasonFlagTypes.map(flag => {
|
||||
return new Discord.ButtonBuilder()
|
||||
.setCustomId(flag)
|
||||
.setStyle(flags.hasFlag(message.flags, reasonFlags[flag]) ? Discord.ButtonStyle.Success : Discord.ButtonStyle.Danger)
|
||||
.setLabel(flag)
|
||||
})
|
||||
|
||||
submitButton = new Discord.ButtonBuilder()
|
||||
.setCustomId("ban")
|
||||
.setStyle(Discord.ButtonStyle.Primary)
|
||||
.setLabel("Ban")
|
||||
.setEmoji("🔨")
|
||||
message.interaction.editReply({
|
||||
components: [
|
||||
new Discord.ActionRowBuilder().addComponents(flagButtons),
|
||||
new Discord.ActionRowBuilder().addComponents(submitButton)
|
||||
]
|
||||
})
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Startup
|
||||
log.info("Starting up...")
|
||||
|
||||
|
||||
require("./migrations")(pool).then(() => {
|
||||
const bcrypt = require("bcrypt")
|
||||
const crypto = require("crypto")
|
||||
pool.getConnection().then((conn) => {
|
||||
require("./migrations")(pool).then(() => {
|
||||
conn.query("SELECT * FROM users WHERE id = 1").then((row) => {
|
||||
if (row.length == 0 || process.env.RESET_ADMIN == "true") {
|
||||
// delete all users (The big scary one lol)
|
||||
conn.query("DELETE FROM users").then(() => {
|
||||
// Generate 32 char random string
|
||||
const passwd = process.env.DEV_PWD || crypto.randomBytes(32).toString('hex');
|
||||
bcrypt.hash(passwd, 10).then((hash) => {
|
||||
conn.query("INSERT INTO users (id, username, passwordHash) VALUES (1, 'admin', ?)",
|
||||
[hash]).then(() => {
|
||||
console.log(`Created admin user with password: ${passwd}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}).finally(() => {
|
||||
conn.release();
|
||||
// Load all route modules from the 'routes' folder
|
||||
const routesPath = path.join(__dirname, 'routes');
|
||||
fs.readdirSync(routesPath).forEach((file) => {
|
||||
|
@ -328,4 +137,5 @@ require("./migrations")(pool).then(() => {
|
|||
log.info(`Listening on ${port}`)
|
||||
})
|
||||
client.login(process.env.DISCORD_TOKEN);
|
||||
});
|
||||
});
|
|
@ -59,7 +59,7 @@ function runMigrations(pool) {
|
|||
resolve();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error running migrations:', err);
|
||||
console.errorr('Error running migrations:', err);
|
||||
reject(err);
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
5
migrations/004_init_users_table.sql
Normal file
5
migrations/004_init_users_table.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
passwordHash VARCHAR(255) NOT NULL
|
||||
);
|
2
migrations/005_add_totp_token_users.sql
Normal file
2
migrations/005_add_totp_token_users.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE users
|
||||
ADD COLUMN totp_token VARCHAR(255);
|
632
package-lock.json
generated
632
package-lock.json
generated
|
@ -9,13 +9,16 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"colors": "^1.4.0",
|
||||
"discord.js": "^14.16.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"mariadb": "^3.4.0",
|
||||
"noblox.js": "^6.0.2",
|
||||
"totp-generator": "^1.0.0",
|
||||
"uuid": "^11.0.3"
|
||||
}
|
||||
},
|
||||
|
@ -170,6 +173,26 @@
|
|||
"integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/signalr": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz",
|
||||
|
@ -312,6 +335,12 @@
|
|||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
|
@ -337,6 +366,41 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base/node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
|
@ -353,6 +417,15 @@
|
|||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
|
@ -368,6 +441,26 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
|
@ -445,6 +538,20 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
|
||||
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"node-addon-api": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
|
@ -597,6 +704,15 @@
|
|||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
@ -615,6 +731,15 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/colors": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
|
@ -642,6 +767,12 @@
|
|||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
|
@ -759,6 +890,12 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
|
@ -787,6 +924,15 @@
|
|||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.37.100",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.100.tgz",
|
||||
|
@ -929,6 +1075,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
|
@ -1071,6 +1223,40 @@
|
|||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
|
||||
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.7",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
|
@ -1195,6 +1381,36 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
|
@ -1204,6 +1420,27 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
|
@ -1232,6 +1469,27 @@
|
|||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
|
@ -1315,6 +1573,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
|
@ -1388,6 +1652,42 @@
|
|||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
@ -1400,6 +1700,17 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
|
@ -1415,6 +1726,15 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
|
@ -1500,6 +1820,15 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jssha": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz",
|
||||
"integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
|
@ -1524,6 +1853,30 @@
|
|||
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mariadb": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.0.tgz",
|
||||
|
@ -1624,6 +1977,52 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
|
@ -1657,6 +2056,12 @@
|
|||
"node": ">=18.18"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
|
@ -1677,6 +2082,34 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
|
@ -1698,6 +2131,15 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||
|
@ -1722,6 +2164,24 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||
|
@ -1780,6 +2240,15 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||
|
@ -1898,6 +2367,15 @@
|
|||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
|
@ -1922,12 +2400,42 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
@ -1954,6 +2462,18 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
|
@ -2008,6 +2528,12 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
|
@ -2055,6 +2581,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sshpk": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
|
||||
|
@ -2098,6 +2630,41 @@
|
|||
"bluebird": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -2110,6 +2677,23 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
|
@ -2119,6 +2703,15 @@
|
|||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/totp-generator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/totp-generator/-/totp-generator-1.0.0.tgz",
|
||||
"integrity": "sha512-Iu/1Lk60/MH8FE+5cDWPiGbwKK1hxzSq+KT9oSqhZ1BEczGIKGcN50bP0WMLiIZKRg7t29iWLxw6f81TICQdoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jssha": "^3.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
|
@ -2171,6 +2764,18 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
|
||||
|
@ -2223,6 +2828,12 @@
|
|||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
@ -2317,6 +2928,21 @@
|
|||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
|
@ -2337,6 +2963,12 @@
|
|||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,16 @@
|
|||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"colors": "^1.4.0",
|
||||
"discord.js": "^14.16.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"mariadb": "^3.4.0",
|
||||
"noblox.js": "^6.0.2",
|
||||
"totp-generator": "^1.0.0",
|
||||
"uuid": "^11.0.3"
|
||||
}
|
||||
}
|
||||
|
|
6
public/assets/css/bootstrap.min.css
vendored
Normal file
6
public/assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
66
public/assets/js/admin/create.js
Normal file
66
public/assets/js/admin/create.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
const alertBox = (type, message) => {
|
||||
const alert = document.getElementById('message');
|
||||
alert.classList.add(type);
|
||||
alert.innerText = message;
|
||||
alert.style.display = 'block';
|
||||
};
|
||||
|
||||
var reasonFlags = {};
|
||||
|
||||
fetch('/api/v1/info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
reasonFlags = data.reasonFlags;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
|
||||
document.getElementById('banForm').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Combine flag values
|
||||
const checkboxes = document.querySelectorAll('.flag-checkbox');
|
||||
let combinedFlags = 0;
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox.checked) {
|
||||
combinedFlags |= parseInt(checkbox.value);
|
||||
}
|
||||
});
|
||||
document.getElementById('reasonsFlag').value = combinedFlags;
|
||||
const data = Object.fromEntries(new FormData(event.target));
|
||||
|
||||
if (data.expires) {
|
||||
const expiresDate = new Date(data.expiresTimestamp);
|
||||
data.expiresTimestamp = Math.floor(expiresDate.getTime());
|
||||
}
|
||||
|
||||
if ((!data.robloxId && !data.discordId)) {
|
||||
alertBox('alert-danger', 'Please enter a Roblox ID or Discord ID.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(event.target.action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
console.log('Success:', result);
|
||||
// Handle success (e.g., show a success message, redirect, etc.)
|
||||
window.location.href = '/admin';
|
||||
} else {
|
||||
console.error('Error:', result.message);
|
||||
alertBox('alert-danger', 'An error has occurred: ' + result.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alertBox('alert-danger', 'An error has occurred. Please contact support.\n' + error);
|
||||
// Handle error (e.g., show an error message)
|
||||
});
|
||||
});
|
43
public/assets/js/admin/dashboard.js
Normal file
43
public/assets/js/admin/dashboard.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
var reasonFlags = {};
|
||||
fetch('/api/v1/info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
reasonFlags = data.reasonFlags;
|
||||
console.log('Reason Flags:', reasonFlags);
|
||||
fetch('/admin/api/bans')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const tableBody = document.querySelector('#bansTableBody');
|
||||
data.forEach(ban => {
|
||||
const row = document.createElement('tr');
|
||||
const banTimestamp = new Date(ban.banTimestamp).toLocaleString();
|
||||
const expiresTimestamp = ban.expiresTimestamp ? new Date(ban.expiresTimestamp).toLocaleString() : 'Never';
|
||||
const reasonsFlagNames = getSetFlags(ban.reasonsFlag, reasonFlags).join(', ');
|
||||
console.log(ban.reasonsFlag)
|
||||
console.log(reasonFlags)
|
||||
console.log(getSetFlags(ban.reasonsFlag, reasonFlags))
|
||||
row.innerHTML = `
|
||||
<td>${ban.id}</td>
|
||||
<td>${ban.robloxId || 'N/A'}</td>
|
||||
<td>${ban.discordId || 'N/A'}</td>
|
||||
<td>${ban.robloxUsername || 'N/A'}</td>
|
||||
<td>${ban.discordUsername || 'N/A'}</td>
|
||||
<td>${ban.reasonShort || 'N/A'}</td>
|
||||
<td>${ban.reasonLong || 'N/A'}</td>
|
||||
<td>${reasonsFlagNames}</td>
|
||||
<td>${ban.moderator || 'N/A'}</td>
|
||||
<td>${banTimestamp}</td>
|
||||
<td>${expiresTimestamp}</td>
|
||||
<td style="color: ${ban.expiresTimestamp && new Date(ban.expiresTimestamp) < new Date() ? 'green' : ''};">
|
||||
${expiresTimestamp}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/edit/${ban.id}" class="btn btn-primary btn-sm">Edit</a>
|
||||
</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error fetching ban data:', error));
|
||||
})
|
||||
.catch(error => console.error('Error fetching reason flags:', error));
|
72
public/assets/js/admin/edit.js
Normal file
72
public/assets/js/admin/edit.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
const alertBox = (type, message) => {
|
||||
const alert = document.getElementById('message');
|
||||
alert.classList.add(type);
|
||||
alert.innerText = message;
|
||||
alert.style.display = 'block';
|
||||
};
|
||||
|
||||
// Set expiresTimestamp to the time in const ban.expiresTimestamp (convert from js date number to local time string that conforms with html form values)
|
||||
const expiresTimestamp = document.getElementById('expiresTimestamp').value;
|
||||
const expiresDate = new Date(ban.expiresTimestamp)
|
||||
document.getElementById('expiresTimestamp').value = new Date(expiresDate.getTime() - (expiresDate.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
|
||||
|
||||
|
||||
var reasonFlags = {};
|
||||
|
||||
fetch('/api/v1/info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
reasonFlags = data.reasonFlags;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
|
||||
document.getElementById('banForm').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Combine flag values
|
||||
const checkboxes = document.querySelectorAll('.flag-checkbox');
|
||||
let combinedFlags = 0;
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox.checked) {
|
||||
combinedFlags |= parseInt(checkbox.value);
|
||||
}
|
||||
});
|
||||
document.getElementById('reasonsFlag').value = combinedFlags;
|
||||
const data = Object.fromEntries(new FormData(event.target));
|
||||
|
||||
if (data.expires) {
|
||||
const expiresDate = new Date(data.expiresTimestamp);
|
||||
data.expiresTimestamp = Math.floor(expiresDate.getTime());
|
||||
}
|
||||
|
||||
if ((!data.robloxId && !data.discordId)) {
|
||||
alertBox('alert-danger', 'Please enter a Roblox ID or Discord ID.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(event.target.action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
console.log('Success:', result);
|
||||
// Handle success (e.g., show a success message, redirect, etc.)
|
||||
window.location.href = '/admin';
|
||||
} else {
|
||||
console.error('Error:', result.message);
|
||||
alertBox('alert-danger', 'An error has occurred: ' + result.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alertBox('alert-danger', 'An error has occurred. Please contact support.\n' + error);
|
||||
// Handle error (e.g., show an error message)
|
||||
});
|
||||
});
|
7
public/assets/js/bootstrap.bundle.min.js
vendored
Normal file
7
public/assets/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
83
public/assets/js/flags.js
Normal file
83
public/assets/js/flags.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Adds a flag to the current flags.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {number} flagToAdd - The flag to add.
|
||||
* @returns {number} - The updated flags with the new flag added.
|
||||
*/
|
||||
function addFlag(flags, flagToAdd) {
|
||||
return flags | flagToAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a flag from the current flags.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {number} flagToRemove - The flag to remove.
|
||||
* @returns {number} - The updated flags with the flag removed.
|
||||
*/
|
||||
function removeFlag(flags, flagToRemove) {
|
||||
return flags & ~flagToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a flag is set in the current flags.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {number} flagToCheck - The flag to check.
|
||||
* @returns {boolean} - True if the flag is set, otherwise false.
|
||||
*/
|
||||
function hasFlag(flags, flagToCheck) {
|
||||
return (flags & flagToCheck) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a flag in the current flags.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {number} flagToToggle - The flag to toggle.
|
||||
* @returns {number} - The updated flags with the flag toggled.
|
||||
*/
|
||||
function toggleFlag(flags, flagToToggle) {
|
||||
return flags ^ flagToToggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all specified flags are set.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {number} flagsToCheck - The flags to check (can be multiple combined).
|
||||
* @returns {boolean} - True if all specified flags are set, otherwise false.
|
||||
*/
|
||||
function hasAllFlags(flags, flagsToCheck) {
|
||||
return (flags & flagsToCheck) === flagsToCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the specified flags are set.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {number} flagsToCheck - The flags to check (can be multiple combined).
|
||||
* @returns {boolean} - True if any of the specified flags are set, otherwise false.
|
||||
*/
|
||||
function hasAnyFlag(flags, flagsToCheck) {
|
||||
return (flags & flagsToCheck) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a flag mapping from an array of names.
|
||||
* @param {string[]} flagNames - An array of names to define as flags.
|
||||
* @returns {Object} - An object where keys are the flag names and values are powers of 2.
|
||||
*/
|
||||
function defineFlags(flagNames) {
|
||||
return flagNames.reduce((flags, name, index) => {
|
||||
flags[name] = 1 << index; // Assign a power of 2 to each flag
|
||||
return flags;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the names of the flags that are set.
|
||||
* @param {number} flags - The current set of flags.
|
||||
* @param {Object} flagDefinitions - An object where keys are flag names and values are powers of 2.
|
||||
* @returns {string[]} - An array of flag names that are set.
|
||||
*/
|
||||
function getSetFlags(flags, flagDefinitions) {
|
||||
return Object.keys(flagDefinitions).filter(flagName =>
|
||||
(flags & flagDefinitions[flagName]) !== 0
|
||||
);
|
||||
}
|
46
public/assets/js/login.js
Normal file
46
public/assets/js/login.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
const alertBox = (type, message) => {
|
||||
const alert = document.getElementById('message');
|
||||
alert.classList.add(type);
|
||||
alert.innerText = message;
|
||||
alert.style.display = 'block';
|
||||
};
|
||||
|
||||
document.getElementById('loginForm').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const totp = document.getElementById('totp').value;
|
||||
|
||||
const body = { username, password };
|
||||
if (document.getElementById('totpField').style.display !== 'none') {
|
||||
body.totp = totp;
|
||||
}
|
||||
|
||||
fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
if (data.success) {
|
||||
// Handle successful login
|
||||
window.location.href = data.redirect || window.location.href.split('/').slice(0, -1).join('/');
|
||||
} else if (data.totpRequired) {
|
||||
// Prompt for TOTP
|
||||
document.getElementById('totpField').style.display = 'block';
|
||||
alertBox('alert-danger', data.message);
|
||||
} else {
|
||||
// Show error modal
|
||||
alertBox('alert-danger', data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alertBox('alert-danger', 'An error has occurred. Please try again later.');
|
||||
});
|
||||
});
|
177
routes/admin.js
Normal file
177
routes/admin.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const mariadb = require('mariadb');
|
||||
const reasonFlags = JSON.parse(process.env.REASON_FLAGS)
|
||||
const expressSession = require('express-session');
|
||||
const bcrypt = require("bcrypt")
|
||||
const crypto = require("crypto")
|
||||
const flags = require('../flags');
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const { env } = require('process');
|
||||
const session = require('express-session');
|
||||
const totp = require('totp-generator').TOTP;
|
||||
|
||||
// Create a MariaDB connection pool
|
||||
const pool = mariadb.createPool({
|
||||
host: process.env.DB_HOST, // Replace with your database host
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER, // Replace with your database username
|
||||
password: process.env.DB_PASS, // Replace with your database password
|
||||
database: process.env.DB_DATABASE, // Replace with your database name
|
||||
connectionLimit: 5 // Adjust connection limit as needed
|
||||
});
|
||||
|
||||
router.use(express.json());
|
||||
router.use(express.urlencoded({ extended: true }));
|
||||
|
||||
|
||||
|
||||
router.use(expressSession({
|
||||
store: expressSession.MemoryStore(),
|
||||
secret: process.env.SESSION_SECRET || 'default_secret',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
}
|
||||
}));
|
||||
|
||||
const authenticate = (req, res, next) => {
|
||||
if (!req.session.admin) {
|
||||
res.redirect('/admin/login');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
// MAIN PAGES
|
||||
|
||||
router.get('/', authenticate, (req, res) => {
|
||||
res.render('admin/dashboard', { env: process.env, session: req.session });
|
||||
});
|
||||
|
||||
router.get('/edit/:id', authenticate, async (req, res) => {
|
||||
const conn = await pool.getConnection();
|
||||
const id = req.params.id;
|
||||
const row = await conn.query('SELECT * FROM bans WHERE id = ?', [id]);
|
||||
conn.end();
|
||||
if (!row[0]) {
|
||||
res.redirect('/admin');
|
||||
return;
|
||||
}
|
||||
res.render('admin/edit', { env: process.env, session: req.session, ban: row[0], reasonFlags, setFlags: flags.getSetFlags(row[0].reasonsFlag, reasonFlags) });
|
||||
});
|
||||
|
||||
// Ban Creation
|
||||
|
||||
router.get('/create', authenticate, (req, res) => {
|
||||
res.render('admin/create', { env: process.env, session: req.session, reasonFlags });
|
||||
});
|
||||
|
||||
router.post('/create', authenticate, async (req, res) => {
|
||||
const conn = await pool.getConnection();
|
||||
const data = req.body;
|
||||
|
||||
if (!data.robloxId && !data.discordId) {
|
||||
res.json({ success: false, message: 'Please enter a Roblox ID or Discord ID.' });
|
||||
return;
|
||||
}
|
||||
const reasonShort = data.reasonShort || 'No reason provided';
|
||||
const reasonLong = data.reasonLong || 'No reason provided';
|
||||
const reasonsFlag = data.reasonsFlag || 0;
|
||||
const moderator = req.session.user.username || 'Unknown';
|
||||
const expiresTimestamp = data.expiresTimestamp || null;
|
||||
const robloxId = data.robloxId || null;
|
||||
const discordId = data.discordId || null;
|
||||
|
||||
await conn.query('INSERT INTO bans (reasonShort, reasonLong, reasonsFlag, moderator, expiresTimestamp, robloxId, discordId) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[reasonShort, reasonLong, reasonsFlag, moderator, expiresTimestamp, robloxId, discordId]);
|
||||
conn.end();
|
||||
|
||||
res.json({ success: true, message: 'User banned successfully', redirect: '/admin' });
|
||||
});
|
||||
|
||||
// Ban Editing
|
||||
router.post('/edit/:id', authenticate, async (req, res) => {
|
||||
const conn = await pool.getConnection();
|
||||
const id = req.params.id;
|
||||
const data = req.body;
|
||||
|
||||
if (!data.robloxId && !data.discordId) {
|
||||
res.json({ success: false, message: 'Please enter a Roblox ID or Discord ID.' });
|
||||
return;
|
||||
}
|
||||
const reasonShort = data.reasonShort || 'No reason provided';
|
||||
const reasonLong = data.reasonLong || 'No reason provided';
|
||||
const reasonsFlag = data.reasonsFlag || 0;
|
||||
const moderator = req.session.user.username || 'Unknown';
|
||||
const expiresTimestamp = data.expiresTimestamp || null;
|
||||
const robloxId = data.robloxId || null;
|
||||
const discordId = data.discordId || null;
|
||||
|
||||
await conn.query('UPDATE bans SET reasonShort = ?, reasonLong = ?, reasonsFlag = ?, moderator = ?, expiresTimestamp = ?, robloxId = ?, discordId = ? WHERE id = ?',
|
||||
[reasonShort, reasonLong, reasonsFlag, moderator, expiresTimestamp, robloxId, discordId, id]);
|
||||
conn.end();
|
||||
|
||||
res.json({ success: true, message: 'User updated successfully', redirect: '/admin' });
|
||||
});
|
||||
|
||||
// API STUFF //
|
||||
|
||||
router.get("/api/bans", authenticate, async (req, res) => {
|
||||
const conn = await pool.getConnection();
|
||||
const rows = await conn.query('SELECT * FROM bans');
|
||||
conn.end();
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
// AUTH STUFF //
|
||||
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.admin) {
|
||||
res.redirect('/admin');
|
||||
return;
|
||||
}
|
||||
res.render('admin/login', { env: process.env });
|
||||
});
|
||||
|
||||
router.post('/login', async (req, res) => {
|
||||
const conn = await pool.getConnection();
|
||||
const username = req.body.username;
|
||||
const password = req.body.password;
|
||||
|
||||
const row = await conn.query('SELECT * FROM users WHERE username = ?', [username]);
|
||||
conn.end();
|
||||
if (row[0]) {
|
||||
const user = row[0];
|
||||
const match = await bcrypt.compare(password, user.passwordHash);
|
||||
if (match) {
|
||||
if (user.totp_token) {
|
||||
if (!req.body.totp) {
|
||||
return res.json({ success: false, totpRequired: true, message: 'Please enter your 2FA code!' });
|
||||
}
|
||||
const generatedToken = totp.generate(user.totp_token).otp;
|
||||
if (req.body.totp !== generatedToken) {
|
||||
return res.json({ success: false, totpRequired: true, message: 'Invalid TOTP token' });
|
||||
}
|
||||
}
|
||||
|
||||
req.session.admin = true;
|
||||
req.session.user = user;
|
||||
delete req.session.user.passwordHash; // Security measure
|
||||
return res.json({ success: true, message: 'Login successful', redirect: '/admin' });
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: false, message: 'Invalid username or password' });
|
||||
});
|
||||
|
||||
router.all('/logout', (req, res) => {
|
||||
req.session.destroy();
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
67
views/admin/create.ejs
Normal file
67
views/admin/create.ejs
Normal file
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>UBS Admin - New ban</title>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card bg-dark text-light shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-center mb-4">Ban User</h2>
|
||||
<div class="alert" id="message" style="display: none;"></div>
|
||||
<form id="banForm">
|
||||
<div class="mb-3">
|
||||
<label for="robloxId" class="form-label">Roblox ID (Optional)</label>
|
||||
<input type="text" class="form-control" id="robloxId" name="robloxId" pattern="\d*" title="ID Only, must be a number">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="discordId" class="form-label">Discord ID (Optional)</label>
|
||||
<input type="text" class="form-control" id="discordId" name="discordId" pattern="\d*" title="ID Only, must be a number">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="robloxUsername" class="form-label">Roblox Username (Optional)</label>
|
||||
<input type="text" class="form-control" id="robloxUsername" name="robloxUsername">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="discordUsername" class="form-label">Discord Username (Optional)</label>
|
||||
<input type="text" class="form-control" id="discordUsername" name="discordUsername">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="reasonShort" class="form-label">Short Reason</label>
|
||||
<input type="text" class="form-control" id="reasonShort" name="reasonShort" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="reasonLong" class="form-label">Long Reason</label>
|
||||
<textarea class="form-control" id="reasonLong" name="reasonLong" rows="3" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Flags</label>
|
||||
<div>
|
||||
<input type="hidden" id="reasonsFlag" name="reasonsFlag" value="0">
|
||||
<% Object.keys(reasonFlags).forEach(function(key) { %>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input flag-checkbox" type="checkbox" id="<%= key %>" value="<%= reasonFlags[key] %>">
|
||||
<label class="form-check-label" for="<%= key %>"><%= key %></label>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="expiresTimestamp" class="form-label">Expires (Optional)</label>
|
||||
<input type="datetime-local" class="form-control" id="expiresTimestamp" name="expiresTimestamp">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/admin/create.js"></script>
|
||||
</body>
|
||||
</html>
|
56
views/admin/dashboard.ejs
Normal file
56
views/admin/dashboard.ejs
Normal file
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>UBS Admin Dashboard</title>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">UBS Admin</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Welcome, <%= session.user.username %></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/logout">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">Bans</h2>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Roblox ID</th>
|
||||
<th scope="col">Discord ID</th>
|
||||
<th scope="col">Roblox Username</th>
|
||||
<th scope="col">Discord Username</th>
|
||||
<th scope="col">Reason (Short)</th>
|
||||
<th scope="col">Reason (Long)</th>
|
||||
<th scope="col">Reasons Flag</th>
|
||||
<th scope="col">Moderator</th>
|
||||
<th scope="col">Ban Timestamp</th>
|
||||
<th scope="col">Expires Timestamp</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bansTableBody">
|
||||
<!-- Rows will be populated by the script -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/flags.js"></script>
|
||||
<script src="/assets/js/admin/dashboard.js"></script>
|
||||
</body>
|
||||
</html>
|
72
views/admin/edit.ejs
Normal file
72
views/admin/edit.ejs
Normal file
|
@ -0,0 +1,72 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>UBS Admin - Edit ban</title>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card bg-dark text-light shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-center mb-4">Edit Ban</h2>
|
||||
<div class="alert" id="message" style="display: none;"></div>
|
||||
<form id="banForm">
|
||||
<div class="mb-3">
|
||||
<label for="robloxId" class="form-label">Roblox ID (Optional)</label>
|
||||
<input type="text" class="form-control" id="robloxId" name="robloxId" pattern="\d*" title="ID Only, must be a number" value="<%= ban.robloxId %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="discordId" class="form-label">Discord ID (Optional)</label>
|
||||
<input type="text" class="form-control" id="discordId" name="discordId" pattern="\d*" title="ID Only, must be a number" value="<%= ban.discordId %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="robloxUsername" class="form-label">Roblox Username (Optional)</label>
|
||||
<input type="text" class="form-control" id="robloxUsername" name="robloxUsername" value="<%= ban.robloxUsername %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="discordUsername" class="form-label">Discord Username (Optional)</label>
|
||||
<input type="text" class="form-control" id="discordUsername" name="discordUsername" value="<%= ban.discordUsername %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="reasonShort" class="form-label">Short Reason</label>
|
||||
<input type="text" class="form-control" id="reasonShort" name="reasonShort" required value="<%= ban.reasonShort %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="reasonLong" class="form-label">Long Reason</label>
|
||||
<textarea class="form-control" id="reasonLong" name="reasonLong" rows="3" required><%= ban.reasonLong %></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Flags</label>
|
||||
<div>
|
||||
<input type="hidden" id="reasonsFlag" name="reasonsFlag" value="0">
|
||||
<% Object.keys(reasonFlags).forEach(function(key) { %>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input flag-checkbox" type="checkbox" id="<%= key %>" value="<%= reasonFlags[key] %>" <%= setFlags.includes(key) ? 'checked' : '' %>>
|
||||
<label class="form-check-label" for="<%= key %>"><%= key %></label>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="expiresTimestamp" class="form-label">Expires (Optional)</label>
|
||||
<input type="datetime-local" class="form-control" id="expiresTimestamp" name="expiresTimestamp" value="<%= ban.expiresTimestamp %>">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const ban = <%- JSON.stringify(ban) %>;
|
||||
console.log(ban)
|
||||
</script>
|
||||
<script src="/assets/js/flags.js"></script>
|
||||
<script src="/assets/js/admin/edit.js"></script>
|
||||
</body>
|
||||
</html>
|
39
views/admin/login.ejs
Normal file
39
views/admin/login.ejs
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>UBS Admin Login</title>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card bg-dark text-light shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-center mb-4">UBS Admin</h2>
|
||||
<div class="alert" id="message" style="display: none;"></div>
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username:</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password:</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3" id="totpField" style="display: none;">
|
||||
<label for="totp" class="form-label">2FA Key:</label>
|
||||
<input type="text" class="form-control" id="totp" name="totp" pattern="\d{6}" maxlength="6" title="Please enter a 6-digit number">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/login.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue