ioroutes: Implement better spam detection
http: Added new POST /update/:name support other: Bunch of refactoring
This commit is contained in:
parent
24bcea69a2
commit
a68a73438c
6 changed files with 118 additions and 54 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -104,6 +104,5 @@ dist
|
|||
.tern-port
|
||||
|
||||
db.json
|
||||
app/*
|
||||
manage/*
|
||||
hello-world
|
||||
public/main.js
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import defaults from '../defaults.mjs'
|
||||
import { formatLog } from './loghelper.mjs'
|
||||
import { getStatus } from '../util.mjs'
|
||||
|
||||
const stopSpam = {}
|
||||
import { getApp, stopSpam } from '../util.mjs'
|
||||
|
||||
/*
|
||||
* Event: 'core.restart'
|
||||
|
@ -27,12 +25,8 @@ export async function getlastlogs(ctx, data, cb) {
|
|||
if (data.name === 'service-core') {
|
||||
return cb(ctx.core.log.ringbuffer.records.map(formatLog))
|
||||
}
|
||||
let app = ctx.core.applicationMap.get(data.name)
|
||||
if (!app) {
|
||||
ctx.log.warn('Invalid getlastlogs command for app ' + data.name)
|
||||
ctx.log.event.warn('Invalid getlastlogs command for app ' + data.name)
|
||||
return
|
||||
}
|
||||
let app = getApp(ctx, data.name, 'getlastlogs')
|
||||
if (!app) return
|
||||
|
||||
cb(app.ctx.log.ringbuffer.records.map(formatLog))
|
||||
}
|
||||
|
@ -61,26 +55,16 @@ export async function unlistenlogs(ctx, data) {
|
|||
* Update specific software
|
||||
*/
|
||||
export async function update(ctx, data, cb) {
|
||||
let app = ctx.core.applicationMap.get(data.name)
|
||||
if (!app) {
|
||||
ctx.log.warn('Invalid update command for app ' + data.name)
|
||||
ctx.log.event.warn('Invalid update command for app ' + data.name)
|
||||
return
|
||||
}
|
||||
let app = getApp(ctx, data.name, 'update')
|
||||
if (!app) return
|
||||
|
||||
let d = new Date()
|
||||
if (stopSpam[app.name] && d - stopSpam[app.name] < 1000 * 60 * 5) {
|
||||
ctx.log.warn('Update called too fast for app ' + data.name)
|
||||
ctx.log.event.warn('Update called too fast for app ' + data.name)
|
||||
return
|
||||
}
|
||||
stopSpam[app.name] = d
|
||||
if (stopSpam(ctx, 'update', data.name)) return
|
||||
|
||||
ctx.log.info('Checking for updates on app ' + data.name)
|
||||
app.update().then(function(res) {
|
||||
ctx.log.info(res, 'Update completed on app ' + data.name)
|
||||
}, function(err) {
|
||||
ctx.log.err(err, 'Error checking for updates on app ' + data.name)
|
||||
ctx.log.error(err, 'Error checking for updates on app ' + data.name)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -90,27 +74,16 @@ export async function update(ctx, data, cb) {
|
|||
* Start specific software
|
||||
*/
|
||||
export async function start(ctx, data, cb) {
|
||||
let app = ctx.core.applicationMap.get(data.name)
|
||||
|
||||
if (!app || (!app.config.scAllowStop && app.running)) {
|
||||
ctx.log.warn('Invalid start command for app ' + data.name)
|
||||
ctx.log.event.warn('Invalid start command for app ' + data.name)
|
||||
return
|
||||
}
|
||||
|
||||
let d = new Date()
|
||||
if (app.running && stopSpam[app.name] && d - stopSpam[app.name] < 1000 * 60 * 5) {
|
||||
ctx.log.warn('Update called too fast for app ' + data.name)
|
||||
ctx.log.event.warn('Update called too fast for app ' + data.name)
|
||||
return
|
||||
}
|
||||
stopSpam[app.name] = d
|
||||
let app = getApp(ctx, data.name, 'start')
|
||||
if (!app) return
|
||||
|
||||
if (stopSpam(ctx, 'start', data.name)) return
|
||||
|
||||
ctx.log.info('Checking for updates on app ' + data.name)
|
||||
ctx.core.runApplication(app).then(function(res) {
|
||||
ctx.log.info('Successfully started ' + data.name + ' running ' + app.running)
|
||||
}, function(err) {
|
||||
ctx.log.err(err, 'Error starting app ' + data.name)
|
||||
ctx.log.error(err, 'Error starting app ' + data.name)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -125,12 +98,9 @@ export async function listentoapp(ctx, data) {
|
|||
return
|
||||
}
|
||||
|
||||
let app = ctx.core.applicationMap.get(data.name)
|
||||
let app = getApp(ctx, data.name, 'listentoapp')
|
||||
if (!app) return
|
||||
|
||||
if (!app) {
|
||||
ctx.log.warn(`listento called on non-existing app ${data.name}`)
|
||||
return
|
||||
}
|
||||
ctx.socket.join('app.' + data.name)
|
||||
let version = ctx.db.get(ctx.db.data.core[app.name].versions, ctx.db.data.core[app.name].latestInstalled)
|
||||
ctx.socket.emit('app.updatelog', {
|
||||
|
@ -149,13 +119,9 @@ export async function unlistentoapp(ctx, data) {
|
|||
ctx.log.warn(`unlistento called with missing name`)
|
||||
return
|
||||
}
|
||||
|
||||
let app = ctx.core.applicationMap.get(data.name)
|
||||
|
||||
if (!app) {
|
||||
ctx.log.warn(`unlistento called on non-existing app ${data.name}`)
|
||||
return
|
||||
}
|
||||
|
||||
let app = getApp(ctx, data.name, 'unlistentoapp')
|
||||
if (!app) return
|
||||
|
||||
ctx.socket.leave('app.' + data.name)
|
||||
}
|
||||
|
|
50
api/core/routes.mjs
Normal file
50
api/core/routes.mjs
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { HttpError } from 'flaska'
|
||||
import { getApp, stopSpam } from '../util.mjs'
|
||||
|
||||
/*
|
||||
* POST: /update/:name
|
||||
*
|
||||
*/
|
||||
export async function updateApp(ctx) {
|
||||
let app = getApp(ctx, ctx.params.name, 'POST.update')
|
||||
if (!app) {
|
||||
ctx.status = 404
|
||||
return ctx.body = {
|
||||
status: 404,
|
||||
message: `Application ${ctx.params.name} was not found`
|
||||
}
|
||||
}
|
||||
|
||||
let t = stopSpam(ctx, 'update', ctx.params.name)
|
||||
if (t) {
|
||||
let d2 = new Date()
|
||||
let timeLeft =
|
||||
Math.round((new Date(t).setMinutes(t.getMinutes() + 1) - d2) / 1000)
|
||||
ctx.headers['Retry-After'] = timeLeft
|
||||
|
||||
ctx.status = 503
|
||||
return ctx.body = {
|
||||
status: 503,
|
||||
message: `Update request too fast for ${ctx.params.name}. Try again in ${timeLeft} seconds.`
|
||||
}
|
||||
}
|
||||
|
||||
return app.update()
|
||||
.then(function(res) {
|
||||
if (res) {
|
||||
ctx.body = res
|
||||
} else {
|
||||
ctx.body = {
|
||||
message: 'No update was found',
|
||||
}
|
||||
}
|
||||
}, function(err) {
|
||||
ctx.log.error(err)
|
||||
|
||||
ctx.status = 500
|
||||
return ctx.body = {
|
||||
status: 500,
|
||||
message: `Application ${ctx.params.name} failed to update: ${err.message}`,
|
||||
}
|
||||
})
|
||||
}
|
|
@ -3,6 +3,7 @@ import fs from 'fs/promises'
|
|||
import socket from 'socket.io-serveronly'
|
||||
import { Flaska, FileResponse } from 'flaska'
|
||||
import coremonitor from './core/coremonitor.mjs'
|
||||
import * as routes from './core/routes.mjs'
|
||||
|
||||
import onConnection from './routerio.mjs'
|
||||
|
||||
|
@ -15,6 +16,7 @@ export function run(http, port, orgCtx) {
|
|||
}, http)
|
||||
|
||||
flaska.before(function(ctx) {
|
||||
ctx.core = orgCtx.core
|
||||
ctx.state.started = new Date().getTime()
|
||||
})
|
||||
|
||||
|
@ -47,6 +49,15 @@ export function run(http, port, orgCtx) {
|
|||
})
|
||||
|
||||
flaska.on404(function(ctx) {
|
||||
if (ctx.method !== 'GET' && ctx.method !== 'HEAD') {
|
||||
ctx.status = 404
|
||||
ctx.body = {
|
||||
status: 404,
|
||||
message: 'Not Found',
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let file = path.resolve(path.join(staticRoot, ctx.url === '/' ? 'index.html' : ctx.url))
|
||||
if (!file.startsWith(staticRoot)) {
|
||||
ctx.status = 404
|
||||
|
@ -64,6 +75,8 @@ export function run(http, port, orgCtx) {
|
|||
})
|
||||
})
|
||||
|
||||
flaska.post('/update/:name', routes.updateApp)
|
||||
|
||||
return flaska.listenAsync(port).then(function() {
|
||||
orgCtx.log.info('Server is listening on port ' + port)
|
||||
|
||||
|
|
36
api/util.mjs
36
api/util.mjs
|
@ -28,6 +28,42 @@ export function getConfig(ctx) {
|
|||
return defaults(ctx.db.config, merge)
|
||||
}
|
||||
|
||||
export function getApp(ctx, name, action) {
|
||||
let app = ctx.core.applicationMap.get(name)
|
||||
|
||||
if (!app) {
|
||||
ctx.log.warn(`Invalid action ${action} on non-existing app ${name}`)
|
||||
ctx.log.event.warn(`Invalid action ${action} on non-existing app ${name}`)
|
||||
return null
|
||||
}
|
||||
if (action === 'start' && !app.config.scAllowStop && app.running) {
|
||||
ctx.log.warn(`Invalid action ${action} on existing app ${name}`)
|
||||
ctx.log.event.warn(`Invalid action ${action} on existing app ${name}`)
|
||||
return null
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
const lastAction = {
|
||||
|
||||
}
|
||||
|
||||
export function stopSpam(ctx, action, name) {
|
||||
let key = name + '.' + action
|
||||
var d = new Date()
|
||||
if (!lastAction[key]) {
|
||||
lastAction[key] = d
|
||||
return false
|
||||
}
|
||||
if (d - lastAction[key] < 1000 * 60 * 1) {
|
||||
ctx.log.warn(`${action} called too fast on ${name}`)
|
||||
ctx.log.event.warn(`${action} called too fast on ${name}`)
|
||||
return lastAction[key]
|
||||
}
|
||||
lastAction[key] = d
|
||||
return false
|
||||
}
|
||||
|
||||
export function getStatus(ctx) {
|
||||
let status = {}
|
||||
for (let app of ctx.core.applications) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sc-manager",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
Loading…
Reference in a new issue