From c618fad9b41fc2cdc4d5e258f7a9b5ec36588678 Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Thu, 24 Oct 2019 01:30:38 +0000 Subject: [configloader] new confighelper and test api to get a priority list of loaded configs --- server/api/configloader.js | 14 +++ server/lib/confighelper.js | 227 +++++++++++++++++++++++++++++++++++++++++++++ server/lib/eventhelper.js | 22 ++--- 3 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 server/lib/confighelper.js (limited to 'server') diff --git a/server/api/configloader.js b/server/api/configloader.js index 6b69a49..4059e5b 100644 --- a/server/api/configloader.js +++ b/server/api/configloader.js @@ -6,6 +6,20 @@ const { decorateApp } = require('@awaitjs/express') var noAuthRouter = decorateApp(express.Router()) const config = require(path.join(__appdir, 'config', 'config')) const url = config.https.host + ':' + config.https.port +const configHelper = require(path.join(__appdir, 'lib', 'confighelper')) + + +// if client in db -> load script (default if none is found), else load registration script +noAuthRouter.getAsync('/test/:uuid', async (req, res) => { + const list = req.query.list !== undefined && req.query.list !== 'false' + const config = await configHelper.getConfig(req.params.uuid, list) + if (!list) { + res.set('Content-Type', 'text/plain') + res.send(config.script) + } else { + res.send(config) + } +}) // if client in db -> load script (default if none is found), else load registration script noAuthRouter.getAsync('/:uuid', async (req, res) => { diff --git a/server/lib/confighelper.js b/server/lib/confighelper.js new file mode 100644 index 0000000..2771596 --- /dev/null +++ b/server/lib/confighelper.js @@ -0,0 +1,227 @@ +/* global __appdir */ +const path = require('path') +const fs = require('fs') +const db = require(path.join(__appdir, 'lib', 'sequelize')) +const eventHelper = require(path.join(__appdir, 'lib', 'eventhelper')) +const config = require(path.join(__appdir, 'config', 'config')) +const url = config.https.host + ':' + config.https.port + + +async function getConfig (uuid, list) { + const client = await db.client.findOne({ where: { uuid: uuid }, include: ['groups', 'events'] }) + let configPath = [] + + // client not known, start registration + if (client === null) { + let config = { id: 'REGISTRATION', name: 'Registration Script' } + if (!list) { + config.script = fs.readFileSync(path.join(__appdir, 'ipxe', 'registration.ipxe'), 'utf8') + return config + } + configPath.push(config) + return configPath + } + + // Client is in db, check for registration hooks. + if (client.registrationState !== null) { + // client is in registration state, load scripts + var hook = await db.registrationhook.findOne({ where: { id: client.registrationState } }) + let config = { id: 'HOOK', name: 'hook.name', hookId: hook.id, hookType: hook.type } + if (!list) { + if (hook.type === 'IPXE') { + config.script = hook.script + } else if (hook.type === 'BASH') { + config.script = fs.readFileSync(path.join(__appdir, 'ipxe', 'minilinux.ipxe'), 'utf8') + } + return config + } + configPath.push(config) + } + + // Check for event or direct configs + let importantList = [] + let eventList = [] + let configList = [] + await _checkParentConfigs(client.id, importantList, eventList, configList, !list) + + if (!list) { + if (importantList.length) return importantList[0] + else if (eventList.length) return eventList[0] + else if (configList.length) return configList[0] + } else { + configPath = configPath.concat(importantList, eventList, configList) + } + + // No config found, use default config + let config = await _prepareConfig({ id: 'DEFAULT', source: { type: 'DEFAULT' } }) + if (!list) return config + + configPath.push(config) + return configPath +} + + +async function _checkParentConfigs (ids, importantList, eventList, configList, breakOnImportant, blackList = []) { + let events = [] + let configs = [] + + if (Array.isArray(ids)) { + events = await db.event.findAll({ where: { '$groups.id$': ids }, include: ['groups', 'config'] }) + let groups = await db.group.findAll({ where: { id: ids }, include: ['config'] }) + groups.forEach(group => { + if (group.configId) configs.push({ id: group.configId, source: { type: 'GROUP', id: group.id, name: group.name } }) + }) + } else { + events = await db.event.findAll({ where: { '$clients.id$': ids }, include: ['clients', 'config'] }) + let client = await db.client.findOne({ where: { id: ids }, include: ['config'] }) + if (client && client.configId) configs = [{ id: client.configId, source: { type: 'CLIENT', id: client.id, name: client.name } }] + } + + let importants = [] + let nonImportants = [] + + events.forEach(event => { + let eventTimes = JSON.parse(event.times) + if (!eventHelper.isActive(eventTimes)) return + + if ((event.groups && event.groups.some(group => group.group_x_event.blacklist)) + || (event.clients && event.clients.length && event.clients[0].client_x_event.blacklist)) { + return blackList.push(event.id) + } + + if (blackList.includes(event.id) || !event.configId) return + + let config = { + id: event.configId, + source: { + type: event.important ? 'IMPORTANT_EVENT' : 'EVENT', + id: event.id, + name: event.name + } + } + + if (event.groups) config.source.groups = event.groups.map(x => ({ id: x.id, name: x.name })) + + if (event.important) importants.push(config) + else nonImportants.push(config) + }) + + if (importants.length > 1) importantList.push(await _createDynamicMenu(importants)) + else if (importants.length === 1) importantList.push(await _prepareConfig(importants[0])) + + if (breakOnImportant && importantList.length) return + + if (nonImportants.length > 1) eventList.push(await _createDynamicMenu(nonImportants)) + else if (nonImportants.length === 1) eventList.push(await _prepareConfig(nonImportants[0])) + + if (configs.length > 1) configList.push(await _createDynamicMenu(configs)) + else if (configs.length === 1) configList.push(await _prepareConfig(configs[0])) + + let parents = [] + if (Array.isArray(ids)) { + parents = await db.group.findAll({ where: { '$subgroups.id$': ids }, include: ['subgroups'] }) + } else { + parents = await db.group.findAll({ where: { '$clients.id$': ids }, include: ['clients'] }) + } + if (parents.length) await _checkParentConfigs(parents.map(x => x.id), importantList, eventList, configList, breakOnImportant, blackList) +} + + +// create the config script from database +async function _prepareConfig (configInfo, noScript) { + let id = configInfo.id + + // If no id is given, find the default config + const where = {} + if (id === 'DEFAULT') where.isDefault = true + else where.id = id + + const config = await db.config.findOne({ where, include: ['entries'], order: [[db.config.associations.entries, db.config.associations.entries.through, 'sortValue', 'ASC']] }) + if (!config) return configInfo + + const result = { id: config.id, name: config.name, source: configInfo.source } + + if (noScript) return result + if (config.script !== null && config.script !== '') { + result.script = config.script + return result + } + var script = '' + var menuscript = '' + script += '#!ipxe\r\n\r\n' + script += ':start\r\n' + script += 'menu ' + config.name + '\r\n' + config.entries.forEach(entry => { + script += 'item' + if (entry.config_x_entry.keyBind !== null) { + script += ' --key ' + entry.config_x_entry.keyBind + } + script += ' menuentry' + entry.id + ' ' + if (entry.config_x_entry.customName !== null && entry.config_x_entry.customName !== '') { + script += entry.config_x_entry.customName + } else { + script += entry.name + } + script += '\r\n' + menuscript += ':' + 'menuentry' + entry.id + '\r\n' + menuscript += entry.script + '\r\n\r\n' + }) + script += 'choose ' + if (config.defaultEntry !== null && config.timeout !== null) { + script += '--default menuentry' + config.defaultEntry + ' --timeout ' + config.timeout + ' ' + } + script += `target && goto \${target}\r\n\r\n` + script += menuscript + + result.script = script + return result +} + + +// create dynamic menu to load the different given configs for a client +async function _createDynamicMenu (configInfos, noScript) { + const ids = [] + const idSourceMap = {} + + configInfos.forEach(configInfo => { + ids.push(configInfo.id) + idSourceMap[configInfo.id] = configInfo.source + }) + + const configs = await db.config.findAll({ where: { id: ids } }) + const result = { + merged: true, + id: JSON.stringify(configs.map(x => x.id)), + configs: configs.map(x => ({ id: x.id, name: x.name, source: idSourceMap[x.id] })) + } + if (noScript) return result + + var script = '' + var menuscript = '' + var defaultentry = '' + script += '#!ipxe\r\n\r\n' + script += ':start\r\n' + script += 'menu ' + 'Choose one configuration to boot' + '\r\n' + configs.forEach(config => { + script += 'item ' + script += ' menuentry' + config.id + ' ' + script += config.name + script += '\r\n' + + // Last script processed is default script + defaultentry = 'menuentry' + config.id + + menuscript += ':' + 'menuentry' + config.id + '\r\n' + menuscript += 'chain ' + 'https://' + url + '/api/configloader/getconfig/' + config.id + '\r\n\r\n' + }) + script += 'choose --default ' + defaultentry + ' ' + script += '--timeout 15 ' + script += `target && goto \${target}\r\n\r\n` + script += menuscript + + result.script = script + return result +} + + +module.exports = { getConfig } \ No newline at end of file diff --git a/server/lib/eventhelper.js b/server/lib/eventhelper.js index 46213e6..e5d54f3 100644 --- a/server/lib/eventhelper.js +++ b/server/lib/eventhelper.js @@ -20,15 +20,14 @@ class Schedule { const splitStartTime = splitStart[1].split(':') const rule = { dtstart: new Date(event.start + 'Z'), - until: new Date(event.start + 'Z'), + until: new Date(event.end + 'Z'), interval: event.interval, - byhour: splitStartTime[0], - byminute: splitStartTime[1] + byhour: [splitStartTime[0]], + byminute: [splitStartTime[1]], + byweekday: event.dayMap.reduce((arr, v, i) => { if (v) arr.push(rruleWeekdays[i]); return arr }, []), + bymonth: event.monthMap.reduce((arr, v, i) => { if (v) arr.push(i + 1); return arr }, []) } - rule.byweekday = event.dayMap.reduce((arr, v, i) => { if (v) arr.push(rruleWeekdays[i]); return arr }, []) - rule.bymonth = event.monthMap.reduce((arr, v, i) => { if (v) arr.push(i + 1); return arr }, []) - if (event.intervalType === 'month') rule.freq = RRule.MONTHLY else if (event.intervalType === 'week') rule.freq = RRule.WEEKLY else rule.freq = RRule.DAILY @@ -56,13 +55,10 @@ class Schedule { } function isActive (event) { - if (event.repetitive) { - const schedule = new Schedule(event) - return schedule.isActive(new Date()) - } else { - const now = formatDate(new Date()) - return event.start <= now && now <= event.end - } + const now = formatDate(new Date()) + if (now < event.start || event.end < now) return false + if (event.repetitive) return (new Schedule(event)).isValid(now) + else return true } function formatDate (date) { -- cgit v1.2.3-55-g7522