Added Admin panel to manage Communities, Discovery, and view users, as well as their respective API endpoints

This commit is contained in:
CaramelKat 2021-01-01 00:41:22 -06:00
parent d11b04efad
commit daed45e535
17 changed files with 1927 additions and 8 deletions

9
package-lock.json generated
View File

@ -340,6 +340,15 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
"requires": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6"
}
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",

View File

@ -4,6 +4,7 @@
"dependencies": {
"body-parser": "^1.19.0",
"colors": "^1.4.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"fs-extra": "^9.0.1",
"memory-cache": "^0.2.0",

View File

@ -122,8 +122,6 @@ let methods = {
}
catch(e)
{
console.log(e);
console.error("The token was incorrect");
return null;
}
@ -200,5 +198,18 @@ let methods = {
let pngBuffer = PNG.sync.write(png);
return `data:image/png;base64,${pngBuffer.toString('base64')}`;
},
nintendoPasswordHash: function(password, pid) {
const pidBuffer = Buffer.alloc(4);
pidBuffer.writeUInt32LE(pid);
const unpacked = Buffer.concat([
pidBuffer,
Buffer.from('\x02\x65\x43\x46'),
Buffer.from(password)
]);
const hashed = crypto.createHash('sha256').update(unpacked).digest().toString('hex');
return hashed;
},
};
exports.data = methods;

View File

@ -13,5 +13,8 @@
"useNewUrlParser": true,
"useUnifiedTopology": true
}
}
},
"authorized_PNIDs" : [
0
]
}

View File

@ -48,7 +48,10 @@ async function getTopicByCommunityID(communityID) {
async function getCommunities(numberOfCommunities) {
verifyConnected();
return COMMUNITY.find({}).limit(numberOfCommunities);
if(numberOfCommunities === -1)
return COMMUNITY.find({});
else
return COMMUNITY.find({}).limit(numberOfCommunities);
}
async function getMostPopularCommunities(numberOfCommunities) {
@ -71,7 +74,7 @@ async function getCommunityByTitleID(title_id) {
async function getCommunityByID(community_id) {
verifyConnected();
return COMMUNITY.findOne({
id: community_id
community_id: community_id
});
}
@ -154,6 +157,14 @@ async function getDiscoveryHosts() {
});
}
async function getUsers(numberOfUsers) {
verifyConnected();
if(numberOfUsers === -1)
return USER.find({});
else
return USER.find({}).limit(numberOfUsers);
}
async function getUserByPID(PID) {
verifyConnected();
@ -162,11 +173,17 @@ async function getUserByPID(PID) {
});
}
async function getUserByUsername(user_id) {
verifyConnected();
return USER.findOne({
user_id: new RegExp(`^${user_id}$`, 'i')
});
}
async function getServerConfig() {
verifyConnected();
return ENDPOINT.findOne({
type: "config"
});
return ENDPOINT.findOne();
}
module.exports = {
@ -186,6 +203,9 @@ module.exports = {
getNumberUserPostsByID,
getTotalPostsByUserID,
getPostByID,
getUsers,
getUserByPID,
getUserByUsername,
getUserPostsAfterTimestamp,
getServerConfig
};

View File

@ -7,6 +7,8 @@ const router = express.Router();
const portal = express.Router();
const ctr = express.Router();
const admin = express.Router();
const web_api = express.Router();
// Create subdomains
logger.info('[JUXT-WEB] Creating \'Wii U\' subdomain');
@ -15,6 +17,12 @@ router.use(subdomain('portal.olv', portal));
logger.info('[JUXT-WEB] Creating \'3DS\' subdomain');
router.use(subdomain('ctr.olv', ctr));
logger.info('[JUXT-WEB] Creating \'Admin\' subdomain');
router.use(subdomain('admin.olv', admin));
logger.info('[JUXT-WEB] Creating \'Web API\' subdomain');
router.use(subdomain('web_api.olv', admin));
// Setup routes
portal.use('/titles/show', routes.PORTAL_SHOW);
portal.use('/communities', routes.PORTAL_COMMUNITIES);
@ -27,4 +35,7 @@ ctr.use('/communities', routes.CTR_COMMUNITIES);
ctr.use('/users', routes.CTR_USER);
ctr.use('/', routes.CTR_WEB);
admin.use('/', routes.WEB_ADMIN);
admin.use('/v1/', routes.WEB_API);
module.exports = router;

View File

@ -0,0 +1,275 @@
var express = require('express');
const database = require('../../../../database');
const util = require('../../../../authentication');
const config = require('../../../../config.json');
const { COMMUNITY } = require('../../../../models/communities');
var router = express.Router();
const moment = require('moment');
var multer = require('multer');
const snowflake = require('node-snowflake').Snowflake;
var storage = multer.memoryStorage();
var upload = multer({ storage: storage });
router.get('/communities/all', function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
throw new Error('No service token supplied');
let pid = util.data.processServiceToken(req.cookies.token);
if(pid === null)
throw new Error('Invalid credentials supplied');
let user = await database.getUserByPID(pid);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('Invalid credentials supplied');
res.send(await database.getCommunities(-1))
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
router.get('/communities/:communityID', function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
throw new Error('No service token supplied');
let pid = util.data.processServiceToken(req.cookies.token);
if(pid === null)
throw new Error('Invalid credentials supplied');
let user = await database.getUserByPID(pid);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('Invalid credentials supplied');
res.send(await database.getCommunityByID(req.params.communityID))
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
router.post('/communities/:communityID/update', upload.fields([{name: 'browserIcon', maxCount: 1}, { name: 'CTRbrowserHeader', maxCount: 1}, { name: 'WiiUbrowserHeader', maxCount: 1}]), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
throw new Error('No service token supplied');
let pid = util.data.processServiceToken(req.cookies.token);
if(pid === null)
throw new Error('Invalid credentials supplied');
let user = await database.getUserByPID(pid);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('Invalid credentials supplied');
const community = await database.getCommunityByID(req.params.communityID);
const files = JSON.parse(JSON.stringify(req.files));
if(req.body.name && community.name !== req.body.name)
community.name = req.body.name;
if(req.body.description && community.description !== req.body.description)
community.description = req.body.description;
if(req.body.title_ids[0] !== community.title_ids[0] && req.body.title_ids[1] !== community.title_ids[1] && req.body.title_ids[2] !== community.title_ids[2]
&& req.body.title_ids[0] !== '' && req.body.title_ids[1] !== '' && req.body.title_ids[2] !== '') {
community.title_id = req.body.title_ids;
community.title_ids = req.body.title_ids;
}
if(req.body.icon && community.icon !== req.body.icon)
community.icon = req.body.icon;
if(req.files.browserIcon)
community.browser_icon = `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`;
if(req.files.CTRbrowserHeader)
community.CTR_browser_header = `data:image/png;base64,${req.files.CTRbrowserHeader[0].buffer.toString('base64')}`;
if(req.files.WiiUbrowserHeader)
community.WiiU_browser_header = `data:image/png;base64,${req.files.WiiUbrowserHeader[0].buffer.toString('base64')}`;
if(req.body.is_recommended)
community.is_recommended = req.body.is_recommended;
if(req.body.has_shop_page)
community.has_shop_page = req.body.has_shop_page;
if(req.body.platform_id)
community.platform_id = req.body.platform_id;
community.save();
res.sendStatus(200);
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
router.post('/communities/new', upload.fields([{name: 'browserIcon', maxCount: 1}, { name: 'CTRbrowserHeader', maxCount: 1}, { name: 'WiiUbrowserHeader', maxCount: 1}]), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
throw new Error('No service token supplied');
let pid = util.data.processServiceToken(req.cookies.token);
if(pid === null)
throw new Error('Invalid credentials supplied');
let user = await database.getUserByPID(pid);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('Invalid credentials supplied');
JSON.parse(JSON.stringify(req.files));
const document = {
empathy_count: 0,
id: snowflake.nextId(),
has_shop_page: req.body.has_shop_page,
platform_id: req.body.platform_ID,
icon: req.body.icon,
created_at: moment().format('YYYY-MM-DD HH:MM:SS'),
title_ids: req.body.title_ids,
title_id: req.body.title_ids,
community_id: snowflake.nextId(),
is_recommended: req.body.is_recommended,
name: req.body.name,
browser_icon: `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`,
CTR_browser_header: `data:image/png;base64,${req.files.CTRbrowserHeader[0].buffer.toString('base64')}`,
WiiU_browser_header: `data:image/png;base64,${req.files.WiiUbrowserHeader[0].buffer.toString('base64')}`,
description: req.body.description,
};
const newCommunity = new COMMUNITY(document);
newCommunity.save();
res.sendStatus(200);
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
router.post('/discovery/update', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
throw new Error('No service token supplied');
let pid = util.data.processServiceToken(req.cookies.token);
if(pid === null)
throw new Error('Invalid credentials supplied');
let user = await database.getUserByPID(pid);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('Invalid credentials supplied');
let endpoint = await database.getServerConfig();
endpoint.has_error = req.body.has_error;
endpoint.save();
res.send(endpoint);
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
router.get('/users/all', function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
throw new Error('No service token supplied');
let pid = util.data.processServiceToken(req.cookies.token);
if(pid === null)
throw new Error('Invalid credentials supplied');
let user = await database.getUserByPID(pid);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('Invalid credentials supplied');
res.send(await database.getUsers(-1))
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
module.exports = router;

View File

@ -0,0 +1,421 @@
var express = require('express');
var xml = require('object-to-xml');
const database = require('../../../../database');
const util = require('../../../../authentication');
const config = require('../../../../config.json');
const request = require("request");
const ejs = require('ejs');
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
var router = express.Router();
var cookieParser = require('cookie-parser');
router.use(cookieParser());
router.get('/', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
res.render('admin_home.ejs', {
user: user,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/discovery', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
res.render('admin_discovery.ejs', {
user: user,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/communities', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
res.render('admin_communities.ejs', {
user: user,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/posts', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
res.render('admin_posts.ejs', {
user: user,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/communities/new', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
res.render('admin_new_community.ejs', {
user: user,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/communities/:communityID', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
let community = await database.getCommunityByID(req.params.communityID);
res.render('admin_edit_community.ejs', {
user: user,
community: community,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/users', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.redirect('/login');
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.redirect('/login');
return;
}
let user = await database.getUserByPID(pid);
res.render('admin_users.ejs', {
user: user,
});
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/login', upload.none(), function (req, res) {
database.connect().then(async e => {
//let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]);
if(req.cookies.token === null)
{
res.render('admin_login.ejs', {});
return;
}
let pid = util.data.processServiceToken(req.cookies.token);
//console.log(req.headers["x-nintendo-servicetoken"]);
if(pid === null)
{
res.render('admin_login.ejs', {});
return;
}
let user = await database.getUserByPID(pid);
res.redirect('/');
}).catch(error => {
console.log(error);
res.set("Content-Type", "application/xml");
res.statusCode = 400;
response = {
result: {
has_error: 1,
version: 1,
code: 400,
error_code: 15,
message: "SERVER_ERROR"
}
};
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
});
});
router.get('/token', upload.none(), function (req, res) {
request.get({
url: "http://" + config.account_server_domain + "/v1/api/provider/service_token/@me",
headers: {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"],
'X-Nintendo-Title-ID': req.headers['x-nintendo-title-id'],
'authorization': req.headers['authorization'],
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
res.send(body);
}
else
{
res.statusCode = 400;
let response = {
error_code: 400,
message: 'Invalid account ID or password'
};
res.send(response);
}
});
});
router.post('/login', upload.none(), function (req, res) {
database.connect().then(async e => {
let user_id = req.body.user_id;
let user = await database.getUserByUsername(user_id);
let password = req.body.password;
if(user !== null && password !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
throw new Error('User is not authorized to access the application');
let password_hash = await util.data.nintendoPasswordHash(password, user.pid);
await request.post({
url: "http://" + config.account_server_domain + "/v1/api/oauth20/access_token/generate",
headers: {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"],
'X-Nintendo-Title-ID': '0005001010040100'
},
form: {
user_id: user_id,
password_type: 'hash',
password: password_hash,
grant_type: 'password'
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
res.send(body);
}
else
{
res.statusCode = 400;
let response = {
error_code: 400,
message: 'Invalid account ID or password'
};
res.send(response);
}
});
}
else
throw new Error('Invalid account ID or password');
}).catch(error =>
{
res.statusCode = 400;
let response = {
error_code: 400,
message: error.message
};
res.send(response);
});
});
module.exports = router;

View File

@ -8,4 +8,6 @@ module.exports = {
CTR_WEB: require('./ctr/web'),
CTR_COMMUNITIES: require('./ctr/communities'),
CTR_USER: require('./ctr/userpage'),
WEB_ADMIN: require('./admin/home'),
WEB_API: require('./admin/api'),
};

View File

@ -0,0 +1,225 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Communities</h2>
<button onclick="location.assign('/communities/new')">New</button>
<input type="text" id="mySearch" onkeyup="myFunction()" placeholder="Search..">
<div id="txtHint">Communities should be listed here. If not, that fucking blows</div>
</div>
</div>
<script>
/**
* Fetches table from MongoDB and
* displays in on the main page
*/
function generate_table() {
var xhttp;
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var data = JSON.parse(this.responseText);
let header = '<style>\n' +
' table {\n' +
' font-family: arial, sans-serif;\n' +
' border-collapse: collapse;\n' +
' width: 60%%;\n' +
' }\n' +
'\n' +
' th {\n' +
' cursor: pointer;\n' +
' }\n' +
'\n' +
' th, td {\n' +
' text-align: left;\n' +
' padding: 16px;\n' +
' }\n' +
'\n' +
' tr {\n' +
' border: black;\n' +
' border-width: 2px;\n' +
' border-style: groove;\n' +
' }\n' +
' </style>';
let body;
body = '<table id="adddrop"><tbody id="search-list"><tr>' +
'<th>Icon</th>' +
'<th onClick="sortTable(0)">Name</th>' +
'<th onClick="sortTable(1)">Created At</th>' +
'<th onClick="sortTable(1)">Title ID\'\s</th>' +
'<th onClick="sortTable(2)">Followers</th>' +
'</tr>';
for(let i = 0; i < data.length; i++) {
body +=
'<tr id="' + data[i].community_id + '" onclick="location.assign(\'/communities/\' + this.id)">' +
'<td><img style="width: 80px " src="' + data[i].browser_icon + '"></img></td>' +
'<td><a>' + data[i].name + '</a></td>' +
'<td>' + data[i].created_at + '</td>' +
'<td>' + data[i].title_ids + '</td>' +
'<td>' + data[i].followers + '</td></tr>';
}
body += '</tbody></table>';
document.getElementById("txtHint").innerHTML = header + '</head><body>' + body;
}
};
xhttp.open("GET", "/v1/communities/all", true);
xhttp.send();
}
/**
* Deletes entry from MongoDB
* @param id
*/
function deleteScout(id) {
var xhttp;
var e = document.getElementById("addDrop");
var period = e.options[e.selectedIndex].value;
xhttp = new XMLHttpRequest();
xhttp.open("GET", "/remove?p=" + period + "&id=" + id, true);
xhttp.send();
removeRow((id + "_row"));
}
/**
* Removes row from table
* @param id
*/
function removeRow(id) {
var row = document.getElementById(id);
row.parentNode.removeChild(row);
}
/**
* Sorts table by selected column
* @param n
*/
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("adddrop");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir === "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
generate_table();
</script>
<script>
function myFunction() {
var input, filter, ul, tr, a, i;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
ul = document.getElementById("search-list");
tr = ul.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
a = tr[i].getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Discovery Server Status</h2>
<form action="/v1/discovery/update" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<select name="has_error">
<option value="0" selected>OPEN</option>
<option value="1">SYSTEM_UPDATE_REQUIRED</option>
<option value="2">SETUP_NOT_COMPLETE</option>
<option value="3">SERVICE_MAINTENANCE</option>
<option value="4">SERVICE_CLOSED</option>
<option value="5">PARENTAL_CONTROLS_ENABLED</option>
<option value="6">POSTING_LIMITED_PARENTAL_CONTROLS</option>
<option value="7">PNID_BANNED</option>
<option value="8">SERVER_ERROR</option>
</select>
<input type="submit" value="Submit"><br>
<iframe name="formSubmitFrame"></iframe>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Update <%=community.name%></h2>
<form action="/v1/communities/<%= community.community_id %>/update" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<label for="name">Community Name:</label><br>
<input type="text" id="name" name="name" value="<%=community.name%>"><br>
<label for="description">Description:</label><br>
<input type="text" id="description" name="description" value="<%=community.description%>"><br>
<label for="title_ids">Title ID 1:</label><br>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[0]%>"><br>
<label for="title_ids">Title ID 2:</label><br>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[1]%>"><br>
<label for="title_ids">Title ID 3:</label><br>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[2]%>"><br>
<label for="icon">System Icon (B64 TGA):</label><br>
<input type="text" id="icon" name="icon" value="<%=community.icon%>"><br><br>
Browser Icon (512px x 512px)<br>
<input type="file" id="browserIcon" accept="image/png" name="browserIcon"><br>
<img src="<%=community.browser_icon%>" height="150px"><br>
3DS Browser Banner<br>
<input type="file" id="CTRbrowserHeader" accept="image/png" name="CTRbrowserHeader"><br>
<img src="<%=community.CTR_browser_header%>" height="150px"><br>
Wii U Browser Banner (1498px x 328px)<br>
<input type="file" id="WiiUbrowserHeader" accept="image/png" name="WiiUbrowserHeader"><br>
<img src="<%=community.WiiU_browser_header%>" height="150px"><br>
Is Recommended?
<input type="radio" id="isRecomended" name="is_recommended" value="1" <%if(community.is_recommended === 1) {%> checked <%}%>>
<label for="isRecomended">True</label>
<input type="radio" id="isNotRecomended" name="is_recommended" value="0" <%if(community.is_recommended === 0) {%> checked <%}%>>
<label for="isNotRecomended">False</label><br>
Has Shop Page?
<input type="radio" id="hasShopPage" name="has_shop_page" value="1" <%if(community.has_shop_page === 1) {%> checked <%}%>>
<label for="hasShopPage">True</label>
<input type="radio" id="noShopPage" name="has_shop_page" value="0" <%if(community.has_shop_page === 0) {%> checked <%}%>>
<label for="noShopPage">False</label><br>
Platform
<input type="radio" id="WiiU" name="platform_id" value="0" <%if(community.platform_id === 0) {%> checked <%}%>>
<label for="platform_id">Wii U</label>
<input type="radio" id="3DS" name="platform_id" value="1" <%if(community.platform_id === 1) {%> checked <%}%>>
<label for="platform_id">3DS</label>
<input type="radio" id="Both" name="platform_id" value="2" <%if(community.platform_id === 2) {%> checked <%}%>>
<label for="platform_id">Both</label><br>
<input type="submit" value="Submit">
<iframe name="formSubmitFrame"></iframe>
</form>
</div>
</div>
</body>
</html>

86
src/views/admin_home.ejs Normal file
View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Home</h2>
<p>This is a temporary admin panel for Juxt that will likely be used throughout the beta, or at least until the main Pretendo Network Admin Panel is done lol.</p>
<p>If you can see this and your not a developer. Please tell Jemma how on earth you pulled it off, cause we kinda need this website to be secure :p</p>
</div>
</div>
</body>
</html>

177
src/views/admin_login.ejs Normal file
View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Login</title>
<style>
body {font-family: Arial, Helvetica, sans-serif;}
/* Full-width input fields */
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
user-select: none;
}
/* Set a style for all buttons */
button {
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
cursor: pointer;
width: 100%;
user-select: none;
}
button:hover {
opacity: 0.8;
}
.container {
padding: 16px;
}
/* The Modal (background) */
.modal {
display: block; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
padding-top: 60px;
}
/* Modal Content/Box */
.modal-content {
background-color: #fefefe;
margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
}
/* Add Zoom Animation */
.animate {
-webkit-animation: animatezoom 0.6s;
animation: animatezoom 0.6s;
max-width: 35%;
}
@-webkit-keyframes animatezoom {
from {-webkit-transform: scale(0)}
to {-webkit-transform: scale(1)}
}
@keyframes animatezoom {
from {transform: scale(0)}
to {transform: scale(1)}
}
</style>
</head>
<body>
<div id="id01" class="modal">
<form class="modal-content animate" id="login" method="post" onsubmit="submitForm(); return false">
<div class="container">
<label for="user_id"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="user_id" autocomplete="username" required>
<label for="password"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" autocomplete="current-password" required>
<button>Login</button>
</div>
</form>
</div>
<script>
function submitForm() {
var xhr = new XMLHttpRequest();
xhr.open("POST", '/login');
var user_id = document.getElementsByName('user_id')[0].value;
var password = document.getElementsByName('password')[0].value;
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if(xhr.status === 200)
{
setCookie('auth', xmlToJSON.parseString(xhr.responseText).OAuth20[0].access_token[0].token[0]._text)
console.log(xmlToJSON.parseString(xhr.responseText).OAuth20[0].access_token[0].token[0]._text);
setServiceToken();
}
else
{
alert(JSON.parse(xhr.response).message);
}
}};
var data = "user_id=" + user_id + "&password=" + password;
xhr.send(data);
}
function setServiceToken() {
var xhr = new XMLHttpRequest();
xhr.open("GET", '/token');
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.setRequestHeader("X-Nintendo-Client-ID", "a2efa818a34fa16b8afbc8a74eba3eda");
xhr.setRequestHeader("X-Nintendo-Client-Secret", "c91cdb5658bd4954ade78533a339cf9a");
xhr.setRequestHeader("X-Nintendo-Title-ID", "000500301001610A");
xhr.setRequestHeader("Authorization", "Bearer " + getCookie("auth"));
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if(xhr.status === 200)
{
setCookie('token', xmlToJSON.parseString(xhr.responseText).service_token[0].token[0]._text)
console.log(xmlToJSON.parseString(xhr.responseText).service_token[0].token[0]._text);
location.assign('/');
}
else
{
console.log(xhr.response);
}
}};
xhr.send();
}
function setCookie(cname, cvalue) {
var date = new Date();
date.setTime(date.getTime() + (60 * 60 * 1000));
var expires = "expires="+ date.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + date.toGMTString();
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return null;
}
var xmlToJSON = function () { this.version = "1.3.4"; var e = { mergeCDATA: !0, grokAttr: !0, grokText: !0, normalize: !0, xmlns: !0, namespaceKey: "_ns", textKey: "_text", valueKey: "_value", attrKey: "_attr", cdataKey: "_cdata", attrsAsObject: !0, stripAttrPrefix: !0, stripElemPrefix: !0, childrenAsArray: !0 }, t = new RegExp(/(?!xmlns)^.*:/), r = new RegExp(/^\s+|\s+$/g); return this.grokType = function (e) { return /^\s*$/.test(e) ? null : /^(?:true|false)$/i.test(e) ? "true" === e.toLowerCase() : isFinite(e) ? parseFloat(e) : e }, this.parseString = function (e, t) { return this.parseXML(this.stringToXML(e), t) }, this.parseXML = function (a, n) { for (var s in n) e[s] = n[s]; var l = {}, i = 0, o = ""; if (e.xmlns && a.namespaceURI && (l[e.namespaceKey] = a.namespaceURI), a.attributes && a.attributes.length > 0) { var c = {}; for (i; i < a.attributes.length; i++) { var u = a.attributes.item(i); m = {}; var p = ""; p = e.stripAttrPrefix ? u.name.replace(t, "") : u.name, e.grokAttr ? m[e.valueKey] = this.grokType(u.value.replace(r, "")) : m[e.valueKey] = u.value.replace(r, ""), e.xmlns && u.namespaceURI && (m[e.namespaceKey] = u.namespaceURI), e.attrsAsObject ? c[p] = m : l[e.attrKey + p] = m } e.attrsAsObject && (l[e.attrKey] = c) } if (a.hasChildNodes()) for (var y, d, m, h = 0; h < a.childNodes.length; h++)4 === (y = a.childNodes.item(h)).nodeType ? e.mergeCDATA ? o += y.nodeValue : l.hasOwnProperty(e.cdataKey) ? (l[e.cdataKey].constructor !== Array && (l[e.cdataKey] = [l[e.cdataKey]]), l[e.cdataKey].push(y.nodeValue)) : e.childrenAsArray ? (l[e.cdataKey] = [], l[e.cdataKey].push(y.nodeValue)) : l[e.cdataKey] = y.nodeValue : 3 === y.nodeType ? o += y.nodeValue : 1 === y.nodeType && (0 === i && (l = {}), d = e.stripElemPrefix ? y.nodeName.replace(t, "") : y.nodeName, m = xmlToJSON.parseXML(y), l.hasOwnProperty(d) ? (l[d].constructor !== Array && (l[d] = [l[d]]), l[d].push(m)) : (e.childrenAsArray ? (l[d] = [], l[d].push(m)) : l[d] = m, i++)); else o || (e.childrenAsArray ? (l[e.textKey] = [], l[e.textKey].push(null)) : l[e.textKey] = null); if (o) if (e.grokText) { var x = this.grokType(o.replace(r, "")); null !== x && void 0 !== x && (l[e.textKey] = x) } else e.normalize ? l[e.textKey] = o.replace(r, "").replace(/\s+/g, " ") : l[e.textKey] = o.replace(r, ""); return l }, this.xmlToString = function (e) { try { return e.xml ? e.xml : (new XMLSerializer).serializeToString(e) } catch (e) { return null } }, this.stringToXML = function (e) { try { var t = null; return window.DOMParser ? t = (new DOMParser).parseFromString(e, "text/xml") : (t = new ActiveXObject("Microsoft.XMLDOM"), t.async = !1, t.loadXML(e), t) } catch (e) { return null } }, this }.call({}); "undefined" != typeof module && null !== module && module.exports ? module.exports = xmlToJSON : "function" == typeof define && define.amd && define(function () { return xmlToJSON });
</script>
</body>
</html>

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Create New Community</h2>
<form action="/v1/communities/new" target="formSubmitFrame" enctype="multipart/form-data" method="post">
<label for="name">Community Name:</label><br>
<input type="text" id="name" name="name" required><br>
<label for="description">Description:</label><br>
<input type="text" id="description" name="description" required><br>
<label for="title_ids">Title ID 1:</label><br>
<input type="text" id="title_ids" name="title_ids[]" required><br>
<label for="title_ids">Title ID 2:</label><br>
<input type="text" id="title_ids" name="title_ids[]"><br>
<label for="title_ids">Title ID 3:</label><br>
<input type="text" id="title_ids" name="title_ids[]"><br>
<label for="icon">System Icon (B64 TGA):</label><br>
<input type="text" id="icon" name="icon" required><br>
Browser Icon (512px x 512px)<br>
<input type="file" id="browserIcon" accept="image/png" name="browserIcon" required><br>
3DS Browser Banner<br>
<input type="file" id="CTRbrowserHeader" accept="image/png" name="CTRbrowserHeader" required><br>
Wii U Browser Banner (1498px x 328px)<br>
<input type="file" id="WiiUbrowserHeader" accept="image/png" name="WiiUbrowserHeader" required><br>
Is Recommended?
<input type="radio" id="isRecomended" name="is_recommended" value="1" required>
<label for="isRecomended">True</label>
<input type="radio" id="isNotRecomended" name="is_recommended" value="0" required>
<label for="isNotRecomended">False</label><br>
Has Shop Page?
<input type="radio" id="hasShopPage" name="has_shop_page" value="1" required>
<label for="hasShopPage">True</label>
<input type="radio" id="noShopPage" name="has_shop_page" value="0" required>
<label for="noShopPage">False</label><br>
Platform
<input type="radio" id="WiiU" name="platform_ID" value="0" required>
<label for="platform_ID">Wii U</label>
<input type="radio" id="3DS" name="platform_ID" value="1" required>
<label for="platform_ID">3DS</label>
<input type="radio" id="Both" name="platform_ID" value="2" required>
<label for="platform_ID">Both</label><br>
<input type="submit" value="Submit">
<iframe name="formSubmitFrame"></iframe>
</form>
</div>
</div>
</body>
</html>

84
src/views/admin_posts.ejs Normal file
View File

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Posts</h2>
</div>
</div>
</body>
</html>

224
src/views/admin_users.ejs Normal file
View File

@ -0,0 +1,224 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
/* Create a column layout with Flexbox */
.row {
display: flex;
}
/* Left column (menu) */
.left {
flex: 35%;
padding: 15px 0;
}
.left h2 {
padding-left: 8px;
}
/* Right column (page content) */
.right {
flex: 65%;
padding: 15px;
}
/* Style the search box */
#mySearch {
width: 100%;
font-size: 18px;
padding: 11px;
border: 1px solid #ddd;
}
/* Style the navigation menu inside the left column */
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: black;
display: block
}
#myMenu li a:hover {
background-color: #eee;
}
</style>
</head>
<body>
<h2 style="display: inline-block">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= user.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Communities</h2>
<input type="text" id="mySearch" onkeyup="myFunction()" placeholder="Search..">
<div id="txtHint">Communities should be listed here. If not, that fucking blows</div>
</div>
</div>
<script>
/**
* Fetches table from MongoDB and
* displays in on the main page
*/
function generate_table() {
var xhttp;
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var data = JSON.parse(this.responseText);
let header = '<style>\n' +
' table {\n' +
' font-family: arial, sans-serif;\n' +
' border-collapse: collapse;\n' +
' width: 60%%;\n' +
' }\n' +
'\n' +
' th {\n' +
' cursor: pointer;\n' +
' }\n' +
'\n' +
' th, td {\n' +
' text-align: left;\n' +
' padding: 16px;\n' +
' }\n' +
'\n' +
' tr {\n' +
' border: black;\n' +
' border-width: 2px;\n' +
' border-style: groove;\n' +
' }\n' +
' </style>';
let body;
body = '<table id="adddrop"><tbody id="search-list"><tr>' +
'<th>Icon</th>' +
'<th onClick="sortTable(0)">Name</th>' +
'<th onClick="sortTable(1)">Created At</th>' +
'<th onClick="sortTable(1)">Account Status</th>' +
'<th onClick="sortTable(2)">Followers</th>' +
'</tr>';
for(let i = 0; i < data.length; i++) {
body +=
'<tr id="' + data[i].pid + '" onclick="alert(this.id)">' +
'<td><img style="width: 80px " src="' + data[i].pfp_uri + '"></img></td>' +
'<td><a>' + data[i].user_id + '</a></td>' +
'<td>' + data[i].created_at + '</td>' +
'<td>' + data[i].account_status + '</td>' +
'<td>' + data[i].followers + '</td></tr>';
}
body += '</tbody></table>';
document.getElementById("txtHint").innerHTML = header + '</head><body>' + body;
}
};
xhttp.open("GET", "/v1/users/all", true);
xhttp.send();
}
/**
* Deletes entry from MongoDB
* @param id
*/
function deleteScout(id) {
var xhttp;
var e = document.getElementById("addDrop");
var period = e.options[e.selectedIndex].value;
xhttp = new XMLHttpRequest();
xhttp.open("GET", "/remove?p=" + period + "&id=" + id, true);
xhttp.send();
removeRow((id + "_row"));
}
/**
* Removes row from table
* @param id
*/
function removeRow(id) {
var row = document.getElementById(id);
row.parentNode.removeChild(row);
}
/**
* Sorts table by selected column
* @param n
*/
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("adddrop");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir === "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
generate_table();
</script>
<script>
function myFunction() {
var input, filter, ul, tr, a, i;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
ul = document.getElementById("search-list");
tr = ul.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
a = tr[i].getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
</script>
</body>
</html>