diff --git a/src/api/admin.js b/src/api/admin.js index ac0d086..3ce8c39 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -52,14 +52,128 @@ exports.setup = (mstream) => { } }); - mstream.get("/api/v1/admin/db-params", async (req, res) => { + mstream.get("/api/v1/admin/db/params", async (req, res) => { try { res.json(config.program.scanOptions); } catch (err) { console.log(err) return res.status(500).json({ error: 'Failed to get scan options' }); } - }); + }); + + mstream.post("/api/v1/admin/db/params/scan-interval", async (req, res) => { + try { + const schema = Joi.object({ + scanInterval: Joi.number().integer().min(0).required() + }); + await schema.validateAsync(req.body); + }catch (err) { + return res.status(500).json({ error: 'Validation Error' }); + } + + try { + await admin.editScanInterval(req.body.scanInterval); + res.json({}); + } catch (err) { + console.log(err); + return res.status(500).json({ error: 'Failed' }); + } + }); + + mstream.post("/api/v1/admin/db/params/save-interval", async (req, res) => { + try { + const schema = Joi.object({ + saveInterval: Joi.number().integer().min(0).required() + }); + await schema.validateAsync(req.body); + }catch (err) { + return res.status(500).json({ error: 'Validation Error' }); + } + + try { + await admin.editSaveInterval(req.body.saveInterval); + res.json({}); + } catch (err) { + console.log(err); + return res.status(500).json({ error: 'Failed' }); + } + }); + + mstream.post("/api/v1/admin/db/params/skip-img", async (req, res) => { + try { + const schema = Joi.object({ + skipImg: Joi.boolean().required() + }); + await schema.validateAsync(req.body); + }catch (err) { + return res.status(500).json({ error: 'Validation Error' }); + } + + try { + await admin.editSkipImg(req.body.skipImg); + res.json({}); + } catch (err) { + console.log(err); + return res.status(500).json({ error: 'Failed' }); + } + }); + + mstream.post("/api/v1/admin/db/params/pause", async (req, res) => { + try { + const schema = Joi.object({ + pause: Joi.number().integer().min(0).required() + }); + await schema.validateAsync(req.body); + }catch (err) { + return res.status(500).json({ error: 'Validation Error' }); + } + + try { + await admin.editPause(req.body.pause); + res.json({}); + } catch (err) { + console.log(err); + return res.status(500).json({ error: 'Failed' }); + } + }); + + mstream.post("/api/v1/admin/db/params/boot-scan-delay", async (req, res) => { + try { + const schema = Joi.object({ + bootScanDelay: Joi.number().integer().min(0).required() + }); + await schema.validateAsync(req.body); + }catch (err) { + return res.status(500).json({ error: 'Validation Error' }); + } + + try { + await admin.editBootScanDelay(req.body.bootScanDelay); + res.json({}); + } catch (err) { + console.log(err); + return res.status(500).json({ error: 'Failed' }); + } + }); + + mstream.post("/api/v1/admin/db/params/max-concurrent-scans", async (req, res) => { + try { + const schema = Joi.object({ + maxConcurrentTasks: Joi.number().integer().min(0).required() + }); + await schema.validateAsync(req.body); + }catch (err) { + return res.status(500).json({ error: 'Validation Error' }); + } + + try { + await admin.editMaxConcurrentTasks(req.body.maxConcurrentTasks); + res.json({}); + } catch (err) { + console.log(err); + return res.status(500).json({ error: 'Failed' }); + } + }); mstream.get("/api/v1/admin/users", async (req, res) => { try { diff --git a/src/db/task-queue.js b/src/db/task-queue.js index cb18a60..90e5b3e 100644 --- a/src/db/task-queue.js +++ b/src/db/task-queue.js @@ -102,4 +102,11 @@ exports.runAfterBoot = () => { scanIntervalTimer = setInterval(() => scanAll(), config.program.scanOptions.scanInterval * 60 * 60 * 1000); } }, config.program.scanOptions.scanDelay * 1000); +} + +exports.resetScanInterval = () => { + if (scanIntervalTimer) { clearInterval(scanIntervalTimer); } + if (config.program.scanOptions.scanInterval > 0) { + scanIntervalTimer = setInterval(() => scanAll(), config.program.scanOptions.scanInterval * 60 * 60 * 1000); + } } \ No newline at end of file diff --git a/src/state/config.js b/src/state/config.js index ccd84fd..e09d068 100644 --- a/src/state/config.js +++ b/src/state/config.js @@ -12,9 +12,9 @@ const storageJoi = Joi.object({ const scanOptions = Joi.object({ skipImg: Joi.boolean().default(false), - scanInterval: Joi.number().default(24), + scanInterval: Joi.number().min(0).default(24), saveInterval: Joi.number().default(250), - pause: Joi.number().default(0), + pause: Joi.number().min(0).default(0), bootScanDelay: Joi.number().default(3), maxConcurrentTasks: Joi.number().integer().min(1).default(1) }); diff --git a/src/util/admin.js b/src/util/admin.js index 1c7050a..dfe166a 100644 --- a/src/util/admin.js +++ b/src/util/admin.js @@ -3,6 +3,7 @@ const express = require('express'); const auth = require('./auth'); const config = require('../state/config'); const mStreamServer = require('../../mstream'); +const dbQueue = require('../db/task-queue'); exports.loadFile = async (file) => { return JSON.parse(await fs.readFile(file, 'utf-8')); @@ -196,3 +197,60 @@ exports.editSecret = async (val) => { config.program.secret = val; } + +exports.editScanInterval = async (val) => { + const loadConfig = await this.loadFile(config.configFile); + if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; } + loadConfig.scanOptions.scanInterval = val; + await this.saveFile(loadConfig, config.configFile); + + config.program.scanOptions.scanInterval = val; + + // update timer + dbQueue.resetScanInterval(); +} + +exports.editSaveInterval = async (val) => { + const loadConfig = await this.loadFile(config.configFile); + if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; } + loadConfig.scanOptions.saveInterval = val; + await this.saveFile(loadConfig, config.configFile); + + config.program.scanOptions.saveInterval = val; +} + +exports.editSkipImg = async (val) => { + const loadConfig = await this.loadFile(config.configFile); + if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; } + loadConfig.scanOptions.skipImg = val; + await this.saveFile(loadConfig, config.configFile); + + config.program.scanOptions.skipImg = val; +} + +exports.editPause = async (val) => { + const loadConfig = await this.loadFile(config.configFile); + if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; } + loadConfig.scanOptions.pause = val; + await this.saveFile(loadConfig, config.configFile); + + config.program.scanOptions.pause = val; +} + +exports.editBootScanDelay = async (val) => { + const loadConfig = await this.loadFile(config.configFile); + if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; } + loadConfig.scanOptions.bootScanDelay = val; + await this.saveFile(loadConfig, config.configFile); + + config.program.scanOptions.bootScanDelay = val; +} + +exports.editMaxConcurrentTasks = async (val) => { + const loadConfig = await this.loadFile(config.configFile); + if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; } + loadConfig.scanOptions.maxConcurrentTasks = val; + await this.saveFile(loadConfig, config.configFile); + + config.program.scanOptions.maxConcurrentTasks = val; +} diff --git a/webapp/admin/index.css b/webapp/admin/index.css index da63822..995040f 100644 --- a/webapp/admin/index.css +++ b/webapp/admin/index.css @@ -19,4 +19,8 @@ .pad-checkbox { padding-bottom: 20px; +} + +a { + cursor: pointer; } \ No newline at end of file diff --git a/webapp/admin/index.js b/webapp/admin/index.js index e4f4464..4d853ff 100644 --- a/webapp/admin/index.js +++ b/webapp/admin/index.js @@ -45,7 +45,7 @@ const ADMINDATA = (() => { module.getDbParams = async () => { const res = await API.axios({ method: 'GET', - url: `${API.url()}/api/v1/admin/db-params` + url: `${API.url()}/api/v1/admin/db/params` }); Object.keys(res.data).forEach(key=>{ @@ -611,28 +611,44 @@ const dbView = Vue.component('db-view', {
- DB Settings + DB Scan Settings - + + + + + - + - + - + - +
Scan Interval: {{dbParams.scanInterval}} hours[info][edit] + [edit] +
Save Interval: {{dbParams.saveInterval}} files + [edit] +
Boot Scan Delay: {{dbParams.bootScanDelay}} seconds[info][edit] + [edit] +
Pause Between Files: {{dbParams.pause}} milliseconds[info][edit] + [edit] +
Skip Image Metadata: {{dbParams.skipImg}}[info][edit] + [edit] +
Max Concurrent Scans: {{dbParams.maxConcurrentTasks}}[info][edit] + [edit] +
@@ -641,7 +657,56 @@ const dbView = Vue.component('db-view', {
- ` + `, + methods: { + toggleSkipImg: function() { + iziToast.question({ + timeout: 20000, + close: false, + overlayClose: true, + overlay: true, + displayMode: 'once', + id: 'question', + zindex: 99999, + layout: 2, + maxWidth: 600, + title: `${this.dbParams.skipImg === true ? 'Disable' : 'Enable'} Image Skip?`, + position: 'center', + buttons: [ + [``, (instance, toast) => { + instance.hide({ transitionOut: 'fadeOut' }, toast, 'button'); + API.axios({ + method: 'POST', + url: `${API.url()}/api/v1/admin/db/params/skip-img`, + data: { skipImg: !this.dbParams.skipImg } + }).then(() => { + // update fronted data + Vue.set(ADMINDATA.dbParams, 'skipImg', !this.dbParams.skipImg); + + iziToast.success({ + title: 'Updated Successfully', + position: 'topCenter', + timeout: 3500 + }); + }).catch(() => { + iziToast.error({ + title: 'Failed', + position: 'topCenter', + timeout: 3500 + }); + }); + }, true], + ['', (instance, toast) => { + instance.hide({ transitionOut: 'fadeOut' }, toast, 'button'); + }], + ] + }); + }, + openModal: function(modalView) { + modVM.currentViewModal = modalView; + M.Modal.getInstance(document.getElementById('admin-modal')).open(); + } + } }); const rpnView = Vue.component('rpn-view', { @@ -1068,6 +1133,316 @@ const editPortModal = Vue.component('edit-port-modal', { } }); +const editMaxScanModal = Vue.component('edit-max-scans-modal', { + data() { + return { + params: ADMINDATA.dbParams, + submitPending: false, + editValue: ADMINDATA.dbParams.maxConcurrentTasks + }; + }, + template: ` +
+ + +
`, + mounted: function () { + M.updateTextFields(); + }, + methods: { + updateParam: async function() { + try { + this.submitPending = true; + + await API.axios({ + method: 'POST', + url: `${API.url()}/api/v1/admin/db/params/max-concurrent-scans`, + data: { maxConcurrentTasks: this.editValue } + }); + + // update fronted data + Vue.set(ADMINDATA.dbParams, 'maxConcurrentTasks', this.editValue); + + // close & reset the modal + M.Modal.getInstance(document.getElementById('admin-modal')).close(); + + iziToast.success({ + title: 'Updated Successfully', + position: 'topCenter', + timeout: 3500 + }); + } catch(err) { + iziToast.error({ + title: 'Update Failed', + position: 'topCenter', + timeout: 3500 + }); + }finally { + this.submitPending = false; + } + } + } +}); + +const editPauseModal = Vue.component('edit-pause-modal', { + data() { + return { + params: ADMINDATA.dbParams, + submitPending: false, + editValue: ADMINDATA.dbParams.pause + }; + }, + template: ` +
+ + +
`, + mounted: function () { + M.updateTextFields(); + }, + methods: { + updateParam: async function() { + try { + this.submitPending = true; + + await API.axios({ + method: 'POST', + url: `${API.url()}/api/v1/admin/db/params/pause`, + data: { pause: this.editValue } + }); + + // update fronted data + Vue.set(ADMINDATA.dbParams, 'pause', this.editValue); + + // close & reset the modal + M.Modal.getInstance(document.getElementById('admin-modal')).close(); + + iziToast.success({ + title: 'Updated Successfully', + position: 'topCenter', + timeout: 3500 + }); + } catch(err) { + iziToast.error({ + title: 'Update Failed', + position: 'topCenter', + timeout: 3500 + }); + }finally { + this.submitPending = false; + } + } + } +}); + +const editBootScanView = Vue.component('edit-boot-scan-delay-modal', { + data() { + return { + params: ADMINDATA.dbParams, + submitPending: false, + editValue: ADMINDATA.dbParams.bootScanDelay + }; + }, + template: ` +
+ + +
`, + mounted: function () { + M.updateTextFields(); + }, + methods: { + updateParam: async function() { + try { + this.submitPending = true; + + await API.axios({ + method: 'POST', + url: `${API.url()}/api/v1/admin/db/params/boot-scan-delay`, + data: { bootScanDelay: this.editValue } + }); + + // update fronted data + Vue.set(ADMINDATA.dbParams, 'bootScanDelay', this.editValue); + + // close & reset the modal + M.Modal.getInstance(document.getElementById('admin-modal')).close(); + + iziToast.success({ + title: 'Updated Successfully', + position: 'topCenter', + timeout: 3500 + }); + } catch(err) { + iziToast.error({ + title: 'Update Failed', + position: 'topCenter', + timeout: 3500 + }); + }finally { + this.submitPending = false; + } + } + } +}); + +const editSaveIntervalView = Vue.component('edit-save-interval-modal', { + data() { + return { + params: ADMINDATA.dbParams, + submitPending: false, + editValue: ADMINDATA.dbParams.saveInterval + }; + }, + template: ` +
+ + +
`, + mounted: function () { + M.updateTextFields(); + }, + methods: { + updateParam: async function() { + try { + this.submitPending = true; + + await API.axios({ + method: 'POST', + url: `${API.url()}/api/v1/admin/db/params/save-interval`, + data: { saveInterval: this.editValue } + }); + + // update fronted data + Vue.set(ADMINDATA.dbParams, 'saveInterval', this.editValue); + + // close & reset the modal + M.Modal.getInstance(document.getElementById('admin-modal')).close(); + + iziToast.success({ + title: 'Updated Successfully', + position: 'topCenter', + timeout: 3500 + }); + } catch(err) { + iziToast.error({ + title: 'Update Failed', + position: 'topCenter', + timeout: 3500 + }); + }finally { + this.submitPending = false; + } + } + } +}); + +const editScanIntervalView = Vue.component('edit-scan-interval-modal', { + data() { + return { + params: ADMINDATA.dbParams, + submitPending: false, + editValue: ADMINDATA.dbParams.scanInterval + }; + }, + template: ` +
+ + +
`, + mounted: function () { + M.updateTextFields(); + }, + methods: { + updateParam: async function() { + try { + this.submitPending = true; + + await API.axios({ + method: 'POST', + url: `${API.url()}/api/v1/admin/db/params/scan-interval`, + data: { scanInterval: this.editValue } + }); + + // update fronted data + Vue.set(ADMINDATA.dbParams, 'scanInterval', this.editValue); + + // close & reset the modal + M.Modal.getInstance(document.getElementById('admin-modal')).close(); + + iziToast.success({ + title: 'Updated Successfully', + position: 'topCenter', + timeout: 3500 + }); + } catch(err) { + iziToast.error({ + title: 'Update Failed', + position: 'topCenter', + timeout: 3500 + }); + }finally { + this.submitPending = false; + } + } + } +}); + const nullModal = Vue.component('null-modal', { template: '
NULL MODAL ERROR: How did you get here?
' }); @@ -1080,6 +1455,11 @@ const modVM = new Vue({ 'user-access-modal': userAccessView, 'file-explorer-modal': fileExplorerModal, 'edit-port-modal': editPortModal, + 'edit-scan-interval-modal': editScanIntervalView, + 'edit-save-interval-modal': editSaveIntervalView, + 'edit-boot-scan-delay-modal': editBootScanView, + 'edit-pause-modal': editPauseModal, + 'edit-max-scan-modal': editMaxScanModal, 'null-modal': nullModal }, data: {