add admintools to user area
This commit is contained in:
parent
bc7613b431
commit
1d081991ba
|
@ -492,6 +492,7 @@ export function receiveMe(
|
||||||
dailyRanking,
|
dailyRanking,
|
||||||
minecraftname,
|
minecraftname,
|
||||||
canvases,
|
canvases,
|
||||||
|
userlvl,
|
||||||
} = me;
|
} = me;
|
||||||
return {
|
return {
|
||||||
type: 'RECEIVE_ME',
|
type: 'RECEIVE_ME',
|
||||||
|
@ -504,6 +505,7 @@ export function receiveMe(
|
||||||
dailyRanking,
|
dailyRanking,
|
||||||
minecraftname,
|
minecraftname,
|
||||||
canvases,
|
canvases,
|
||||||
|
userlvl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,8 @@ export type Action =
|
||||||
ranking: number,
|
ranking: number,
|
||||||
dailyRanking: number,
|
dailyRanking: number,
|
||||||
minecraftname: string,
|
minecraftname: string,
|
||||||
canvases: Object
|
canvases: Object,
|
||||||
|
userlvl: number,
|
||||||
}
|
}
|
||||||
| { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }
|
| { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }
|
||||||
| { type: 'SET_NAME', name: string }
|
| { type: 'SET_NAME', name: string }
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* Html for adminpage
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom/server';
|
|
||||||
|
|
||||||
import Html from './Html';
|
|
||||||
|
|
||||||
const Admin = () => (
|
|
||||||
<form method="post" action="admintools" encType="multipart/form-data">
|
|
||||||
<p>------Image Upload------</p>
|
|
||||||
<p>file:</p>
|
|
||||||
<select name="imageaction">
|
|
||||||
<option value="build">build</option>
|
|
||||||
<option value="protect">protect</option>
|
|
||||||
<option value="wipe">wipe</option>
|
|
||||||
</select>
|
|
||||||
<input type="file" name="image" /><br />
|
|
||||||
<p><span>canvasId (d: default, m: moon):</span>
|
|
||||||
<input type="text" name="canvasident" /></p>
|
|
||||||
<span>x:</span>
|
|
||||||
<input type="number" name="x" min="-40000" max="40000" /><br />
|
|
||||||
<span>y:</span>
|
|
||||||
<input type="number" name="y" min="-40000" max="40000" /><br />
|
|
||||||
<br />
|
|
||||||
<p>---------IP actions---------</p>
|
|
||||||
<select name="action">
|
|
||||||
<option value="ban">ban</option>
|
|
||||||
<option value="unban">unban</option>
|
|
||||||
<option value="whitelist">whitelist</option>
|
|
||||||
<option value="unwhitelist">unwhitelist</option>
|
|
||||||
</select>
|
|
||||||
<br />
|
|
||||||
<textarea rows="10" cols="100" name="ip" /><br />
|
|
||||||
<p>-----------------------</p>
|
|
||||||
<button type="submit" name="upload">Submit</button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
|
|
||||||
const title = 'PixelPlanet.fun AdminTools';
|
|
||||||
const description = 'admin access on pixelplanet';
|
|
||||||
const body = <Admin />;
|
|
||||||
|
|
||||||
const adminHtml = `<!doctype html>${
|
|
||||||
ReactDOM.renderToStaticMarkup(
|
|
||||||
<Html
|
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
body={body}
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
}`;
|
|
||||||
|
|
||||||
export default adminHtml;
|
|
232
src/components/Admintools.jsx
Normal file
232
src/components/Admintools.jsx
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
/*
|
||||||
|
* Html for adminpage
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
|
|
||||||
|
async function submitAction(
|
||||||
|
action,
|
||||||
|
canvas,
|
||||||
|
coords,
|
||||||
|
callback,
|
||||||
|
) {
|
||||||
|
const data = new FormData();
|
||||||
|
const fileSel = document.getElementById('imgfile');
|
||||||
|
const file = (!fileSel.files || !fileSel.files[0])
|
||||||
|
? null : fileSel.files[0];
|
||||||
|
data.append('imageaction', action);
|
||||||
|
data.append('image', file);
|
||||||
|
data.append('canvasid', canvas);
|
||||||
|
data.append('coords', coords);
|
||||||
|
const resp = await fetch('./admintools', {
|
||||||
|
credentials: 'include',
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
callback(await resp.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitIPAction(
|
||||||
|
action,
|
||||||
|
callback,
|
||||||
|
) {
|
||||||
|
const data = new FormData();
|
||||||
|
const iplist = document.getElementById('iparea').value;
|
||||||
|
data.append('ip', iplist);
|
||||||
|
data.append('ipaction', action);
|
||||||
|
const resp = await fetch('./admintools', {
|
||||||
|
credentials: 'include',
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
callback(await resp.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Admintools({
|
||||||
|
canvasId,
|
||||||
|
canvases,
|
||||||
|
}) {
|
||||||
|
const [selectedCanvas, selectCanvas] = useState(canvasId);
|
||||||
|
const [imageAction, selectImageAction] = useState('build');
|
||||||
|
const [iPAction, selectIPAction] = useState('ban');
|
||||||
|
const [resp, setResp] = useState(null);
|
||||||
|
const [coords, selectCoords] = useState('X_Y');
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
let descAction;
|
||||||
|
switch (imageAction) {
|
||||||
|
case 'build':
|
||||||
|
descAction = 'Build image on canvas.';
|
||||||
|
break;
|
||||||
|
case 'protect':
|
||||||
|
descAction = 'Build image and set it to protected.';
|
||||||
|
break;
|
||||||
|
case 'wipe':
|
||||||
|
descAction = 'Build image, but reset cooldown to unset-pixel cd.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
|
||||||
|
{resp && (
|
||||||
|
<div style={{
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '#D4D4D4',
|
||||||
|
borderWidth: 2,
|
||||||
|
padding: 5,
|
||||||
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resp.split('\n').map((line) => (
|
||||||
|
<p className="modaltext">
|
||||||
|
{line}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
<span
|
||||||
|
role="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
className="modallink"
|
||||||
|
onClick={() => setResp(null)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3 className="modaltitle">Image Upload</h3>
|
||||||
|
<p className="modalcotext">Upload images to canvas</p>
|
||||||
|
<p className="modalcotext">Choose Canvas:
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
const sel = e.target;
|
||||||
|
selectCanvas(sel.options[sel.selectedIndex].value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
Object.keys(canvases).map((canvas) => ((canvases[canvas].v)
|
||||||
|
? null
|
||||||
|
: (
|
||||||
|
<option
|
||||||
|
selected={canvas === selectedCanvas}
|
||||||
|
value={canvas}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
canvases[canvas].title
|
||||||
|
}
|
||||||
|
</option>
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p className="modalcotext">
|
||||||
|
File:
|
||||||
|
<input type="file" name="image" id="imgfile" />
|
||||||
|
</p>
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
const sel = e.target;
|
||||||
|
selectImageAction(sel.options[sel.selectedIndex].value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['build', 'protect', 'wipe'].map((opt) => (
|
||||||
|
<option
|
||||||
|
value={opt}
|
||||||
|
selected={imageAction === opt}
|
||||||
|
>
|
||||||
|
{opt}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<p className="modalcotext">{descAction}</p>
|
||||||
|
<p className="modalcotext">
|
||||||
|
Coordinates in X_Y format:
|
||||||
|
<input
|
||||||
|
value={coords}
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '15em',
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
onChange={(evt) => {
|
||||||
|
selectCoords(evt.target.value.trim());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
|
submitAction(
|
||||||
|
imageAction,
|
||||||
|
selectedCanvas,
|
||||||
|
coords,
|
||||||
|
(ret) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
setResp(ret);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(submitting) ? '...' : 'Submit'}
|
||||||
|
</button>
|
||||||
|
<br />
|
||||||
|
<div className="modaldivider" />
|
||||||
|
<h3 className="modaltitle">IP Actions</h3>
|
||||||
|
<p className="modalcotext">Do stuff with IPs (one IP per line)</p>
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
const sel = e.target;
|
||||||
|
selectIPAction(sel.options[sel.selectedIndex].value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['ban', 'unban', 'whitelist', 'unwhitelist'].map((opt) => (
|
||||||
|
<option
|
||||||
|
value={opt}
|
||||||
|
selected={iPAction === opt}
|
||||||
|
>
|
||||||
|
{opt}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<br />
|
||||||
|
<textarea rows="10" cols="100" id="iparea" /><br />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
|
submitIPAction(
|
||||||
|
iPAction,
|
||||||
|
(ret) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
setResp(ret);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(submitting) ? '...' : 'Submit'}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: State) {
|
||||||
|
const { canvasId, canvases } = state.canvas;
|
||||||
|
return { canvasId, canvases };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Admintools);
|
|
@ -31,6 +31,9 @@ class Tabs extends Component {
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
<ol className="tab-list">
|
<ol className="tab-list">
|
||||||
{children.map((child) => {
|
{children.map((child) => {
|
||||||
|
if (!child.props) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
const { label } = child.props;
|
const { label } = child.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -45,7 +48,9 @@ class Tabs extends Component {
|
||||||
</ol>
|
</ol>
|
||||||
<div className="tab-content">
|
<div className="tab-content">
|
||||||
{children.map((child) => {
|
{children.map((child) => {
|
||||||
if (child.props.label !== activeTab) return undefined;
|
if (!child.props || child.props.label !== activeTab) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return child.props.children;
|
return child.props.children;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,8 @@ import Rankings from './Rankings';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ './Converter'));
|
const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ './Converter'));
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ './Admintools'));
|
||||||
|
|
||||||
const logoStyle = {
|
const logoStyle = {
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
|
@ -82,7 +84,14 @@ const LogInArea = ({ register, forgotPassword, me }) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const UserAreaModal = ({
|
const UserAreaModal = ({
|
||||||
name, register, forgotPassword, doMe, logout, setUserName, setUserMailreg,
|
name,
|
||||||
|
register,
|
||||||
|
forgotPassword,
|
||||||
|
doMe,
|
||||||
|
logout,
|
||||||
|
setUserName,
|
||||||
|
setUserMailreg,
|
||||||
|
userlvl,
|
||||||
}) => (
|
}) => (
|
||||||
<p style={{ textAlign: 'center' }}>
|
<p style={{ textAlign: 'center' }}>
|
||||||
{(name === null)
|
{(name === null)
|
||||||
|
@ -110,6 +119,13 @@ const UserAreaModal = ({
|
||||||
<Converter />
|
<Converter />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
{userlvl && (
|
||||||
|
<div label="Admintools">
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Admintools />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)}
|
)}
|
||||||
<p>Also join our Discord:
|
<p>Also join our Discord:
|
||||||
|
@ -149,8 +165,8 @@ function mapDispatchToProps(dispatch) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
function mapStateToProps(state: State) {
|
||||||
const { name } = state.user;
|
const { name, userlvl } = state.user;
|
||||||
return { name };
|
return { name, userlvl };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
|
|
@ -48,6 +48,7 @@ export async function imageABGR2Canvas(
|
||||||
const [ucx, ucy] = getChunkOfPixel(size, x, y);
|
const [ucx, ucy] = getChunkOfPixel(size, x, y);
|
||||||
const [lcx, lcy] = getChunkOfPixel(size, x + width, y + height);
|
const [lcx, lcy] = getChunkOfPixel(size, x + width, y + height);
|
||||||
|
|
||||||
|
let totalPxlCnt = 0;
|
||||||
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
|
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
|
||||||
let chunk;
|
let chunk;
|
||||||
for (let cx = ucx; cx <= lcx; cx += 1) {
|
for (let cx = ucx; cx <= lcx; cx += 1) {
|
||||||
|
@ -83,12 +84,14 @@ export async function imageABGR2Canvas(
|
||||||
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
|
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
|
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
|
||||||
|
totalPxlCnt += pxlCnt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chunk = null;
|
chunk = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info('Image loading done.');
|
logger.info('Image loading done.');
|
||||||
|
return totalPxlCnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,7 @@ class User {
|
||||||
ranking: regUser.ranking,
|
ranking: regUser.ranking,
|
||||||
dailyRanking: regUser.dailyRanking,
|
dailyRanking: regUser.dailyRanking,
|
||||||
mailreg: !!(regUser.password),
|
mailreg: !!(regUser.password),
|
||||||
|
userlvl: this.isAdmin() ? 1 : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ export type UserState = {
|
||||||
isOnMobile: boolean,
|
isOnMobile: boolean,
|
||||||
// small notifications for received cooldown
|
// small notifications for received cooldown
|
||||||
notification: string,
|
notification: string,
|
||||||
|
// 1: Admin, 0: ordinary user
|
||||||
|
userlvl: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: UserState = {
|
const initialState: UserState = {
|
||||||
|
@ -53,6 +55,7 @@ const initialState: UserState = {
|
||||||
minecraftname: null,
|
minecraftname: null,
|
||||||
isOnMobile: false,
|
isOnMobile: false,
|
||||||
notification: null,
|
notification: null,
|
||||||
|
userlvl: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function user(
|
export default function user(
|
||||||
|
@ -171,6 +174,7 @@ export default function user(
|
||||||
ranking,
|
ranking,
|
||||||
dailyRanking,
|
dailyRanking,
|
||||||
minecraftname,
|
minecraftname,
|
||||||
|
userlvl,
|
||||||
} = action;
|
} = action;
|
||||||
const messages = (action.messages) ? action.messages : [];
|
const messages = (action.messages) ? action.messages : [];
|
||||||
return {
|
return {
|
||||||
|
@ -183,6 +187,7 @@ export default function user(
|
||||||
ranking,
|
ranking,
|
||||||
dailyRanking,
|
dailyRanking,
|
||||||
minecraftname,
|
minecraftname,
|
||||||
|
userlvl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* basic admin api
|
* basic admin api
|
||||||
|
* is used by ../components/Admintools
|
||||||
*
|
*
|
||||||
* @flow
|
* @flow
|
||||||
*
|
*
|
||||||
|
@ -15,7 +16,6 @@ import sharp from 'sharp';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
|
|
||||||
import { getIPFromRequest, getIPv6Subnet } from '../utils/ip';
|
import { getIPFromRequest, getIPv6Subnet } from '../utils/ip';
|
||||||
import { getIdFromObject } from '../core/utils';
|
|
||||||
import redis from '../data/redis';
|
import redis from '../data/redis';
|
||||||
import session from '../core/session';
|
import session from '../core/session';
|
||||||
import passport from '../core/passport';
|
import passport from '../core/passport';
|
||||||
|
@ -27,8 +27,6 @@ import { MINUTE } from '../core/constants';
|
||||||
import canvases from './canvases.json';
|
import canvases from './canvases.json';
|
||||||
import { imageABGR2Canvas } from '../core/Image';
|
import { imageABGR2Canvas } from '../core/Image';
|
||||||
|
|
||||||
import adminHtml from '../components/Admin';
|
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const limiter = expressLimiter(router, redis);
|
const limiter = expressLimiter(router, redis);
|
||||||
|
@ -73,7 +71,7 @@ router.use(async (req, res, next) => {
|
||||||
}
|
}
|
||||||
if (!req.user.isAdmin()) {
|
if (!req.user.isAdmin()) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`ADMINTOOLS: ${ip}/${req.user.id} wrongfully tried to access admintools`,
|
`ADMINTOOLS: ${ip} / ${req.user.id} tried to access admintools`,
|
||||||
);
|
);
|
||||||
res.status(403).send('You are not allowed to access this page');
|
res.status(403).send('You are not allowed to access this page');
|
||||||
return;
|
return;
|
||||||
|
@ -91,7 +89,7 @@ router.use(async (req, res, next) => {
|
||||||
* @param ip already sanizized ip
|
* @param ip already sanizized ip
|
||||||
* @return true if successful
|
* @return true if successful
|
||||||
*/
|
*/
|
||||||
async function executeAction(action: string, ips: string): boolean {
|
async function executeIPAction(action: string, ips: string): boolean {
|
||||||
const ipArray = ips.split('\n');
|
const ipArray = ips.split('\n');
|
||||||
let out = '';
|
let out = '';
|
||||||
const splitRegExp = /\s+/;
|
const splitRegExp = /\s+/;
|
||||||
|
@ -104,7 +102,7 @@ async function executeAction(action: string, ips: string): boolean {
|
||||||
ip = ipLine[2];
|
ip = ipLine[2];
|
||||||
}
|
}
|
||||||
if (!ip || ip.length < 8 || ip.indexOf(' ') !== -1) {
|
if (!ip || ip.length < 8 || ip.indexOf(' ') !== -1) {
|
||||||
out += `Couln't parse ${action} ${ip}<br>\n`;
|
out += `Couln't parse ${action} ${ip}\n`;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const ipKey = getIPv6Subnet(ip);
|
const ipKey = getIPv6Subnet(ip);
|
||||||
|
@ -137,84 +135,106 @@ async function executeAction(action: string, ips: string): boolean {
|
||||||
await redis.del(key);
|
await redis.del(key);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
out += `Failed to ${action} ${ip}<br>\n`;
|
out += `Failed to ${action} ${ip}\n`;
|
||||||
}
|
}
|
||||||
out += `Succseefully did ${action} ${ip}<br>\n`;
|
out += `Succseefully did ${action} ${ip}\n`;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute Image based actions (upload, protect, etc.)
|
||||||
|
* @param action what to do with the image
|
||||||
|
* @param file imagefile
|
||||||
|
* @param coords coord sin X_Y format
|
||||||
|
* @param canvasid numerical canvas id
|
||||||
|
* @return [ret, msg] http status code and message
|
||||||
|
*/
|
||||||
|
async function executeImageAction(
|
||||||
|
action: string,
|
||||||
|
file: Object,
|
||||||
|
coords: string,
|
||||||
|
canvasid: string,
|
||||||
|
) {
|
||||||
|
const splitCoords = coords.trim().split('_');
|
||||||
|
if (splitCoords.length !== 2) {
|
||||||
|
return [403, 'Invalid Coordinate Format'];
|
||||||
|
}
|
||||||
|
const [x, y] = splitCoords.map((z) => Math.floor(Number(z)));
|
||||||
|
|
||||||
|
let error = null;
|
||||||
|
if (Number.isNaN(x)) {
|
||||||
|
error = 'x is not a valid number';
|
||||||
|
} else if (Number.isNaN(y)) {
|
||||||
|
error = 'y is not a valid number';
|
||||||
|
} else if (!action) {
|
||||||
|
error = 'No imageaction given';
|
||||||
|
} else if (!canvasid) {
|
||||||
|
error = 'No canvas specified';
|
||||||
|
} else if (!canvases[canvasid]) {
|
||||||
|
error = 'Invalid canvas selected';
|
||||||
|
}
|
||||||
|
if (error !== null) {
|
||||||
|
return [403, error];
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = canvases[canvasid];
|
||||||
|
|
||||||
|
if (canvas.v) {
|
||||||
|
return [403, 'Can not upload Image to 3D canvas'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasMaxXY = canvas.size / 2;
|
||||||
|
const canvasMinXY = -canvasMaxXY;
|
||||||
|
if (x < canvasMinXY || y < canvasMinXY
|
||||||
|
|| x >= canvasMaxXY || y >= canvasMaxXY) {
|
||||||
|
return [403, 'Coordinates are outside of canvas'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const protect = (action === 'protect');
|
||||||
|
const wipe = (action === 'wipe');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, info } = await sharp(file.buffer)
|
||||||
|
.ensureAlpha()
|
||||||
|
.raw()
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
|
const pxlCount = await imageABGR2Canvas(
|
||||||
|
canvasid,
|
||||||
|
x, y,
|
||||||
|
data,
|
||||||
|
info.width, info.height,
|
||||||
|
wipe, protect,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
`Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`,
|
||||||
|
];
|
||||||
|
} catch {
|
||||||
|
return [400, 'Can not read image file'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check for POST parameters,
|
* Check for POST parameters,
|
||||||
*/
|
*/
|
||||||
router.post('/', upload.single('image'), async (req, res, next) => {
|
router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
if (req.file) {
|
if (req.body.imageaction) {
|
||||||
const { imageaction, canvasident } = req.body;
|
const { imageaction, coords, canvasid } = req.body;
|
||||||
|
const [ret, msg] = await executeImageAction(
|
||||||
let error = null;
|
imageaction,
|
||||||
if (Number.isNaN(req.body.x)) {
|
req.file,
|
||||||
error = 'x is not a valid number';
|
coords,
|
||||||
} else if (Number.isNaN(req.body.y)) {
|
canvasid,
|
||||||
error = 'y is not a valid number';
|
);
|
||||||
} else if (!imageaction) {
|
res.status(ret).send(msg);
|
||||||
error = 'No imageaction given';
|
|
||||||
} else if (!canvasident) {
|
|
||||||
error = 'No canvas specified';
|
|
||||||
}
|
|
||||||
if (error !== null) {
|
|
||||||
res.status(403).send(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const x = parseInt(req.body.x, 10);
|
|
||||||
const y = parseInt(req.body.y, 10);
|
|
||||||
|
|
||||||
const canvasId = getIdFromObject(canvases, canvasident);
|
|
||||||
if (canvasId === null) {
|
|
||||||
res.status(403).send('This canvas does not exist');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvas = canvases[canvasId];
|
|
||||||
|
|
||||||
if (canvas.v) {
|
|
||||||
res.status(403).send('Can not upload Image to 3D canvas');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvasMaxXY = canvas.size / 2;
|
|
||||||
const canvasMinXY = -canvasMaxXY;
|
|
||||||
if (x < canvasMinXY || y < canvasMinXY
|
|
||||||
|| x >= canvasMaxXY || y >= canvasMaxXY) {
|
|
||||||
res.status(403).send('Coordinates are outside of canvas');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const protect = (imageaction === 'protect');
|
|
||||||
const wipe = (imageaction === 'wipe');
|
|
||||||
|
|
||||||
await sharp(req.file.buffer)
|
|
||||||
.ensureAlpha()
|
|
||||||
.raw()
|
|
||||||
.toBuffer({ resolveWithObject: true })
|
|
||||||
.then(({ err, data, info }) => {
|
|
||||||
if (err) throw err;
|
|
||||||
return imageABGR2Canvas(
|
|
||||||
canvasId,
|
|
||||||
x, y,
|
|
||||||
data,
|
|
||||||
info.width, info.height,
|
|
||||||
wipe, protect,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).send('Successfully loaded image');
|
|
||||||
return;
|
return;
|
||||||
}
|
} if (req.body.ipaction) {
|
||||||
|
const ret = await executeIPAction(req.body.ipaction, req.body.ip);
|
||||||
if (req.body.ip) {
|
|
||||||
const ret = await executeAction(req.body.action, req.body.ip);
|
|
||||||
res.status(200).send(ret);
|
res.status(200).send(ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -231,8 +251,8 @@ router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
*/
|
*/
|
||||||
router.get('/', async (req: Request, res: Response, next) => {
|
router.get('/', async (req: Request, res: Response, next) => {
|
||||||
try {
|
try {
|
||||||
const { ip, action } = req.query;
|
const { ip, ipaction } = req.query;
|
||||||
if (!action) {
|
if (!ipaction) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -241,9 +261,9 @@ router.get('/', async (req: Request, res: Response, next) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret = await executeAction(action, ip);
|
const ret = await executeIPAction(ipaction, ip);
|
||||||
|
|
||||||
res.json({ action: 'success', messages: ret.split('\n') });
|
res.json({ ipaction: 'success', messages: ret.split('\n') });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
@ -251,10 +271,7 @@ router.get('/', async (req: Request, res: Response, next) => {
|
||||||
|
|
||||||
|
|
||||||
router.use(async (req: Request, res: Response) => {
|
router.use(async (req: Request, res: Response) => {
|
||||||
res.set({
|
res.status(400).send('Invalid request');
|
||||||
'Content-Type': 'text/html',
|
|
||||||
});
|
|
||||||
res.status(200).send(adminHtml);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user