summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/api/configloader.js230
-rw-r--r--server/api/events.js86
-rw-r--r--server/api/roles.js18
-rw-r--r--server/migrations/20190226151600-create-event.js31
-rw-r--r--server/migrations/20190226152330-create-client_x_event.js33
-rw-r--r--server/migrations/20190226152400-create-group_x_event.js33
-rw-r--r--server/models/client.js2
-rw-r--r--server/models/event.js26
-rw-r--r--server/models/group.js3
-rw-r--r--webapp/src/components/EventModule.vue83
-rw-r--r--webapp/src/components/EventModuleDelete.vue66
-rw-r--r--webapp/src/components/EventModuleEdit.vue638
-rw-r--r--webapp/src/components/EventModuleEventList.vue85
-rw-r--r--webapp/src/components/PermissionModule.vue4
-rw-r--r--webapp/src/components/PermissionModuleEdit.vue1
-rw-r--r--webapp/src/components/PermissionModuleRoleList.vue2
-rw-r--r--webapp/src/config/dashboard.js4
-rw-r--r--webapp/src/config/i18n.js6
-rw-r--r--webapp/src/config/store.js4
-rw-r--r--webapp/src/store/events.js35
20 files changed, 1300 insertions, 90 deletions
diff --git a/server/api/configloader.js b/server/api/configloader.js
index 2422693..0ede64f 100644
--- a/server/api/configloader.js
+++ b/server/api/configloader.js
@@ -1,70 +1,125 @@
/* global __appdir */
-const path = require('path')
+var path = require('path')
var db = require(path.join(__appdir, 'lib', 'sequelize'))
var express = require('express')
-var noAuthRouter = express.Router()
+const { decorateApp } = require('@awaitjs/express')
+var noAuthRouter = decorateApp(express.Router())
// if client in db -> load script (default if none is found), else load registration script
-noAuthRouter.get('/:uuid', (req, res) => {
+noAuthRouter.getAsync('/:uuid', async (req, res) => {
const uuid = req.params.uuid
res.setHeader('content-type', 'text/plain')
- db.client.findOne({ where: { uuid: uuid }, include: ['groups'] }).then(client => {
- if (client !== null) {
- // Check for registration hooks.
- if (client.registrationState !== null) {
- // client is in registration state, load scripts
- db.registrationhook.findOne({ where: { id: client.registrationState } }).then(hook => {
- if (hook.type === 'IPXE') res.send(hook.script)
- else if (hook.type === 'BASH') res.sendFile(path.join(__appdir, 'ipxe', 'minilinux.ipxe'))
- })
+ var client = await db.client.findOne({ where: { uuid: uuid }, include: ['groups', 'events'] })
+ if (client !== null) {
+ // 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 } })
+ if (hook.type === 'IPXE') res.send(hook.script)
+ else if (hook.type === 'BASH') res.sendFile(path.join(__appdir, 'ipxe', 'minilinux.ipxe'))
+ return
+ }
+
+ var events = []
+ var blacklist = []
+ var importantEvents = []
+ var groupIds = []
+ var result
+
+ for (let i = 0; i < client.events.length; i++) {
+ if (client.events[i].important) importantEvents.push(client.events[i])
+ if (client.events[i].client_x_event.blacklist) blacklist.push(client.events[i].id)
+ else events.push(client.events[i])
+ }
+
+ importantEvents = importantEvents.filter(e => !blacklist.includes(e.id))
+ importantEvents = importantEvents.map(e => e.config)
+ importantEvents = importantEvents.filter(c => c !== null)
+ importantEvents = importantEvents.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos })
+ if (importantEvents.length === 1) {
+ result = await createIpxeScript(importantEvents[0])
+ res.send(result)
+ return
+ }
+ if (importantEvents.length > 1) {
+ result = await createDynamicMenu(importantEvents)
+ res.send(result)
+ return
+ }
+
+ events = events.filter(e => !blacklist.includes(e.id))
+ events = events.map(e => e.config)
+ events = events.filter(c => c !== null)
+ events = events.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos })
+
+ groupIds = client.groups.map(x => x.id)
+
+ var response = await fetchParentConfigs(groupIds, blacklist)
+ if (response.ids.length > 0) {
+ // Check if there is an important event
+ if (response.type === 'important') {
+ if (response.ids.length === 1) {
+ result = await createIpxeScript(response.ids[0])
+ res.send(result)
+ return
+ }
+ if (response.ids.length > 1) {
+ result = await createDynamicMenu(response.ids)
+ res.send(result)
+ return
+ }
+ }
+ // No important event, use client event if existent
+ if (events.length === 1) {
+ result = await createIpxeScript(events[0])
+ res.send(result)
return
}
-
- // client is in db
- if (client.configId !== null) {
- // client has a config
- createIpxeScript(client.configId).then(response => {
- res.send(response)
- })
+ if (events.length > 1) {
+ result = await createDynamicMenu(events)
+ res.send(result)
return
}
- // client has no config, check parent groups
- var parentIds = []
- var configIds = []
- for (var i = 0; i < client.groups.length; i++) {
- // gather parent ids
- parentIds.push(client.groups[i].id)
+ // No client event, use events of lowest parents
+ if (response.type === 'event') {
+ if (response.ids.length === 1) {
+ result = await createIpxeScript(response.ids[0])
+ res.send(result)
+ return
+ }
+ if (response.ids.length > 1) {
+ result = await createDynamicMenu(response.ids)
+ res.send(result)
+ return
+ }
+ }
+ // No events, use client config
+ if (client.configId !== null) {
+ result = await createIpxeScript(client.configId)
+ res.send(result)
+ return
}
- if (parentIds.length !== 0) {
- // client has a parent to look for
- checkGroupsForConfigs(parentIds).then(response => {
- configIds = response
- if (configIds.length === 1) {
- // only one parent has a config, load it
- createIpxeScript(configIds[0]).then(response => {
- res.send(response)
- })
- return
- } else if (configIds.length > 1) {
- // multiple parents have a config, make dynamic menu
- createDynamicMenu(configIds).then(response => {
- res.send(response)
- })
- return
- }
- // no parent has a config, load default
- res.sendFile(path.join(__appdir, 'ipxe', 'default.ipxe'))
- })
- } else {
- // no config given, load default
- res.sendFile(path.join(__appdir, 'ipxe', 'default.ipxe'))
+ // No client config, use configs of lowest parents
+ if (response.type === 'config') {
+ if (response.ids.length === 1) {
+ result = await createIpxeScript(response.ids[0])
+ res.send(result)
+ return
+ }
+ if (response.ids.length > 1) {
+ result = await createDynamicMenu(response.ids)
+ res.send(result)
+ return
+ }
}
- } else {
- // client not known, start registration
- res.sendFile(path.join(__appdir, 'ipxe', 'registration.ipxe'))
}
- })
+ // No config found, use default config
+ res.sendFile(path.join(__appdir, 'ipxe', 'default.ipxe'))
+ } else {
+ // client not known, start registration
+ res.sendFile(path.join(__appdir, 'ipxe', 'registration.ipxe'))
+ }
})
// load config by given id
@@ -77,32 +132,54 @@ noAuthRouter.get('/getconfig/:configId', (req, res) => {
})
})
-// recursive iteration through the layers of parents until a config is found or no parents left
-function checkGroupsForConfigs (groupIds) {
+async function fetchParentConfigs(groupIds, blacklist) {
+ if (groupIds.length === 0) return {'ids': [], 'type': ''}
+
+ var importantEvents = []
+ var events = []
+ var configs = []
var parentIds = []
- var configIds = []
- if (groupIds.length === 0) {
- return configIds
- }
- return db.group.findAll({ where: { id: groupIds }, include: ['parents'] }).then(groups => {
- groups.forEach(group => {
- group.parents.forEach(parent => {
- if (!parentIds.includes(parent.id)) {
- parentIds.push(parent.id)
- }
- })
- if (group.configId !== null && !configIds.includes(group.configId)) {
- configIds.push(group.configId)
- }
- })
- if (configIds.length !== 0 || parentIds.length === 0) {
- return configIds
- } else {
- return checkGroupsForConfigs(parentIds).then(response => {
- return response
- })
+ var newBlacklist = blacklist.slice(0)
+
+ var groups = await db.group.findAll({ where: { id: groupIds }, include: ['parents', 'events'] })
+ for (let i = 0; i < groups.length; i++) {
+ configs.push(groups[i].configId)
+ // groups[i].map(g => g.parents.map(p => p.id))
+ for (let j = 0; j < groups[i].parents.length; j++) {
+ parentIds.push(groups[i].parents[j].id)
}
- })
+ for (let j = 0; j < groups[i].events.length; j++) {
+ if (groups[i].events[j].important) importantEvents.push(groups[i].events[j])
+ if (groups[i].events[j].group_x_event.blacklist) newBlacklist.push(groups[i].events[j].id)
+ else events.push(groups[i].events[j])
+ }
+ }
+
+ importantEvents = importantEvents.filter(e => !newBlacklist.includes(e.id))
+ importantEvents = importantEvents.map(e => e.config)
+ importantEvents = importantEvents.filter(c => c !== null)
+ importantEvents = importantEvents.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos })
+ if (importantEvents.length > 0) return {'ids': importantEvents, 'type': 'important'}
+
+ var response = await fetchParentConfigs(parentIds, newBlacklist)
+
+ if (response.type === 'important') return response
+
+ events = events.filter(e => !newBlacklist.includes(e.id))
+ events = events.map(e => e.config)
+ events = events.filter(c => c !== null)
+ events = events.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos })
+ if (events.length > 0) return {'ids': events, 'type': 'event'}
+
+ if (response.type === 'event') return response
+
+ configs = configs.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos })
+ configs = configs.filter(c => c !== null)
+ if (configs.length > 0) return {'ids': configs, 'type': 'config'}
+
+ if (response.type === 'config') return response
+
+ return {'ids': [], 'type': ''}
}
// create the config script from database
@@ -143,6 +220,7 @@ function createIpxeScript (id) {
}
// create dynamic menu to load the different given configs for a client
+// TODO: Hardcoded Chain Port?!
function createDynamicMenu (ids) {
return db.config.findAll({ where: { id: ids } }).then(configs => {
var script = ''
diff --git a/server/api/events.js b/server/api/events.js
new file mode 100644
index 0000000..96bf15e
--- /dev/null
+++ b/server/api/events.js
@@ -0,0 +1,86 @@
+/* global __appdir */
+var path = require('path')
+var db = require(path.join(__appdir, 'lib', 'sequelize'))
+var groupUtil = require(path.join(__appdir, 'lib', 'grouputil'))
+var express = require('express')
+const { decorateApp } = require('@awaitjs/express')
+var router = decorateApp(express.Router())
+
+// ############################################################################
+// ########################### GET requests #################################
+
+/*
+ * @return: Returns event of given id.
+ */
+router.getAsync('/:id', async (req, res) => {
+ var event = await db.event.findOne({ where: { id: req.params.id }, include: ['clients', 'groups'] })
+ if (event) res.send(event)
+ else res.status(404).end()
+})
+
+/*
+ * @return: Returns a list of all events in the database.
+ */
+router.getAsync('', async (req, res) => {
+ var events = await db.event.findAll({ include: ['clients', 'groups'] })
+ if (events) res.status(200).send(events)
+ else res.status(404).end()
+})
+
+// ############################################################################
+// ########################## POST requests #################################
+
+router.postAsync('/blacklist', async (req, res) => {
+ console.log('body: ' + req.body.groups)
+ if (req.body.groups) {
+ var blacklist = await groupUtil.getAllChildren(req.body.groups)
+ res.send(blacklist)
+ }
+ else res.status(404).end()
+})
+
+// Create, Update or Delete POST
+router.postAsync(['', '/:id'], async (req, res) => {
+ if (req.query.delete !== undefined && req.query.delete !== 'false') {
+ await db.event.destroy({ where: { id: req.params.id || req.body.ids } }).then(count => { res.send({ count }) })
+ } else {
+ var promises = []
+ var promisesBlacklist = []
+ var eventDb
+ if (req.body.config.length !== 1) req.body.config = null
+ if (req.body.times.length === 0) req.body.times = null
+ if (req.params.id > 0) {
+ // Update existing role
+ eventDb = await db.event.findOne({ where: { id: req.params.id } })
+ console.log('Blacklist: ' + req.body.blacklistGroups)
+ console.log('Groups: ' + req.body.groups)
+ if (eventDb !== null) {
+ promises.push(eventDb.update({ name: req.body.name, description: req.body.description, config: req.body.config || null, times: req.body.times, important: req.body.important }))
+ promises.push(eventDb.setGroups(req.body.groups, { through: { blacklist: 0 } }))
+ promises.push(eventDb.setClients(req.body.clients, { through: { blacklist: 0 } }))
+ await Promise.all(promises)
+ promisesBlacklist.push(eventDb.addGroups(req.body.blacklistGroups, { through: { blacklist: 1 } }))
+ promisesBlacklist.push(eventDb.addClients(req.body.blacklistClients, { through: { blacklist: 1 } }))
+ await Promise.all(promisesBlacklist)
+ res.send({ id: req.params.id })
+ } else {
+ res.status(404).end()
+ }
+ } else {
+ // Create new role
+ eventDb = await db.event.create({ name: req.body.name, description: req.body.description, config: req.body.config || null, times: req.body.times, important: req.body.important })
+ promises.push(eventDb.setGroups(req.body.groups, { through: { blacklist: 0 } }))
+ promises.push(eventDb.setClients(req.body.clients, { through: { blacklist: 0 } }))
+ await Promise.all(promises)
+ promisesBlacklist.push(eventDb.addGroups(req.body.blacklistGroups, { through: { blacklist: 1 } }))
+ promisesBlacklist.push(eventDb.addClients(req.body.blacklistClients, { through: { blacklist: 1 } }))
+ await Promise.all(promisesBlacklist)
+ res.send({ id: req.body.id })
+ }
+ }
+})
+
+// ############################################################################
+// ############################################################################
+
+module.exports.router = router
diff --git a/server/api/roles.js b/server/api/roles.js
index d90d1bd..4d75bfb 100644
--- a/server/api/roles.js
+++ b/server/api/roles.js
@@ -59,13 +59,17 @@ router.postAsync(['', '/:id'], async (req, res) => {
res.send({ id: req.body.id })
} else {
// Update existing role
- roleDb = await db.role.findOne({ where: { id: req.body.id } })
- promises.push(roleDb.update({ name: req.body.name, descr: req.body.descr }))
- promises.push(roleDb.setPermissions(req.body.permissions))
- promises.push(roleDb.setGroups(req.body.groups, { through: { blacklist: 0 } }))
- promises.push(roleDb.addGroups(req.body.blacklist, { through: { blacklist: 1 } }))
- await Promise.all(promises)
- res.send({ id: req.body.id })
+ roleDb = await db.role.findOne({ where: { id: req.params.id } })
+ if (roleDb !== null) {
+ promises.push(roleDb.update({ name: req.body.name, descr: req.body.descr }))
+ promises.push(roleDb.setPermissions(req.body.permissions))
+ promises.push(roleDb.setGroups(req.body.groups, { through: { blacklist: 0 } }))
+ promises.push(roleDb.addGroups(req.body.blacklist, { through: { blacklist: 1 } }))
+ await Promise.all(promises)
+ res.send({ id: req.params.id })
+ } else {
+ res.status(404).end()
+ }
}
}
})
diff --git a/server/migrations/20190226151600-create-event.js b/server/migrations/20190226151600-create-event.js
new file mode 100644
index 0000000..59cab86
--- /dev/null
+++ b/server/migrations/20190226151600-create-event.js
@@ -0,0 +1,31 @@
+'use strict'
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.createTable('events', {
+ id: {
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true,
+ type: Sequelize.INTEGER
+ },
+ name: {
+ type: Sequelize.STRING
+ },
+ description: {
+ type: Sequelize.STRING(2048)
+ },
+ config: {
+ type: Sequelize.INTEGER
+ },
+ times: {
+ type: Sequelize.STRING(4096)
+ },
+ important: {
+ type: Sequelize.BOOLEAN
+ }
+ })
+ },
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.dropTable('permissions')
+ }
+}
diff --git a/server/migrations/20190226152330-create-client_x_event.js b/server/migrations/20190226152330-create-client_x_event.js
new file mode 100644
index 0000000..1d6952e
--- /dev/null
+++ b/server/migrations/20190226152330-create-client_x_event.js
@@ -0,0 +1,33 @@
+'use strict'
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.createTable('client_x_event', {
+ clientId: {
+ primaryKey: true,
+ allowNull: false,
+ type: Sequelize.INTEGER,
+ onDelete: 'cascade',
+ references: {
+ model: 'clients',
+ key: 'id'
+ }
+ },
+ eventId: {
+ primaryKey: true,
+ allowNull: false,
+ type: Sequelize.INTEGER,
+ onDelete: 'cascade',
+ references: {
+ model: 'events',
+ key: 'id'
+ }
+ },
+ blacklist: {
+ type: Sequelize.BOOLEAN
+ }
+ })
+ },
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.dropTable('client_x_event')
+ }
+}
diff --git a/server/migrations/20190226152400-create-group_x_event.js b/server/migrations/20190226152400-create-group_x_event.js
new file mode 100644
index 0000000..8c438de
--- /dev/null
+++ b/server/migrations/20190226152400-create-group_x_event.js
@@ -0,0 +1,33 @@
+'use strict'
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.createTable('group_x_event', {
+ groupId: {
+ primaryKey: true,
+ allowNull: false,
+ type: Sequelize.INTEGER,
+ onDelete: 'cascade',
+ references: {
+ model: 'groups',
+ key: 'id'
+ }
+ },
+ eventId: {
+ primaryKey: true,
+ allowNull: false,
+ type: Sequelize.INTEGER,
+ onDelete: 'cascade',
+ references: {
+ model: 'events',
+ key: 'id'
+ }
+ },
+ blacklist: {
+ type: Sequelize.BOOLEAN
+ }
+ })
+ },
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.dropTable('group_x_event')
+ }
+}
diff --git a/server/models/client.js b/server/models/client.js
index 6483573..502f668 100644
--- a/server/models/client.js
+++ b/server/models/client.js
@@ -20,6 +20,8 @@ module.exports = (sequelize, DataTypes) => {
var GroupXClient = sequelize.define('group_x_client', {}, { timestamps: false, freezeTableName: true })
client.belongsToMany(models.group, { as: 'groups', through: GroupXClient, foreignKey: 'clientId', otherKey: 'groupId' })
client.belongsTo(models.config)
+ var ClientXEvent = sequelize.define('client_x_event', { blacklist: DataTypes.BOOLEAN }, { timestamps: false, freezeTableName: true })
+ client.belongsToMany(models.event, { as: 'events', through: ClientXEvent, foreignKey: 'clientId', otherKey: 'eventId' })
}
return client
}
diff --git a/server/models/event.js b/server/models/event.js
new file mode 100644
index 0000000..11a35e7
--- /dev/null
+++ b/server/models/event.js
@@ -0,0 +1,26 @@
+'use strict'
+module.exports = (sequelize, DataTypes) => {
+ var event = sequelize.define('event', {
+ id: {
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true,
+ type: DataTypes.INTEGER
+ },
+ name: DataTypes.STRING,
+ description: DataTypes.STRING(2048),
+ config: DataTypes.INTEGER,
+ times: DataTypes.STRING(4096),
+ important: DataTypes.BOOLEAN
+ }, {
+ timestamps: false
+ })
+ event.associate = function (models) {
+ var ClientXEvent = sequelize.define('client_x_event', { blacklist: DataTypes.BOOLEAN }, { timestamps: false, freezeTableName: true })
+ event.belongsToMany(models.client, { as: 'clients', through: ClientXEvent, foreignKey: 'eventId', otherKey: 'clientId' })
+
+ var GroupXEvent = sequelize.define('group_x_event', { blacklist: DataTypes.BOOLEAN }, { timestamps: false, freezeTableName: true })
+ event.belongsToMany(models.group, { as: 'groups', through: GroupXEvent, foreignKey: 'eventId', otherKey: 'groupId' })
+ }
+ return event
+}
diff --git a/server/models/group.js b/server/models/group.js
index 30fc78d..6ff9034 100644
--- a/server/models/group.js
+++ b/server/models/group.js
@@ -15,11 +15,14 @@ module.exports = (sequelize, DataTypes) => {
group.associate = function (models) {
var GroupXGroup = sequelize.define('group_x_group', {}, { timestamps: false, freezeTableName: true })
var GroupXClient = sequelize.define('group_x_client', {}, { timestamps: false, freezeTableName: true })
+ var GroupXEvent = sequelize.define('group_x_event', { blacklist: DataTypes.BOOLEAN }, { timestamps: false, freezeTableName: true })
group.belongsToMany(group, { as: 'parents', through: GroupXGroup, foreignKey: 'childId', otherKey: 'parentId' })
group.belongsToMany(group, { as: 'subgroups', through: GroupXGroup, foreignKey: 'parentId', otherKey: 'childId' })
group.belongsToMany(models.client, { as: 'clients', through: GroupXClient, foreignKey: 'groupId', otherKey: 'clientId' })
+ group.belongsToMany(models.event, { as: 'events', through: GroupXEvent, foreignKey: 'groupId', otherKey: 'eventId' })
group.belongsTo(models.config)
group.hasMany(models.iprange, { as: 'ipranges' })
+
}
return group
}
diff --git a/webapp/src/components/EventModule.vue b/webapp/src/components/EventModule.vue
new file mode 100644
index 0000000..68f9537
--- /dev/null
+++ b/webapp/src/components/EventModule.vue
@@ -0,0 +1,83 @@
+<i18n>
+{
+ "en": {
+ "events": "Events",
+ "eventCalendar": "Event Calendar",
+ "eventList": "Event List"
+ },
+ "de": {
+ "events": "Veranstaltungen",
+ "eventCalendar": "Veranstaltungskalendar",
+ "eventList": "Veranstaltungsliste"
+ }
+}
+</i18n>
+
+<template>
+ <v-container fill-height>
+ <v-layout>
+ <v-flex xl10 offset-xl1 lg12>
+ <v-card class="tabbar-card">
+ <v-tabs v-model="tabs" grow hide-slider :dark="tabsDark" :color="tabsColor" :slider-color="tabsSliderColor">
+ <v-tab><v-icon class="tabbar-tabicon">calendar_view_day</v-icon>{{ $t('eventList') }}</v-tab>
+ </v-tabs>
+ </v-card>
+ <v-tabs-items v-model="tabs" style="padding-bottom: 20px">
+ <v-tab-item>
+ <v-subheader>{{ $t('eventList') }}</v-subheader>
+ <event-module-event-list/>
+ </v-tab-item>
+ </v-tabs-items>
+ </v-flex>
+ </v-layout>
+ <v-dialog
+ :value="dialog.show"
+ @input="setDialog({ show: $event })"
+ :max-width="dialog.type === 'delete' ? '500px' : '1200px'"
+ scrollable
+ persistent
+ :fullscreen="$vuetify.breakpoint.smAndDown"
+ >
+ <event-module-delete v-if="dialog.type === 'delete'" />
+ <event-module-edit v-else-if="dialog.type === 'edit'"/>
+ </v-dialog>
+ </v-container>
+</template>
+
+<script>
+import EventModuleEventList from '@/components/EventModuleEventList'
+import EventModuleEdit from '@/components/EventModuleEdit'
+import EventModuleDelete from '@/components/EventModuleDelete'
+import { mapState, mapGetters, mapMutations } from 'vuex'
+
+export default {
+ name: 'EventModule',
+ components: {
+ EventModuleEventList,
+ EventModuleEdit,
+ EventModuleDelete
+ },
+ data () {
+ return {
+ tabs: 0
+ }
+ },
+ computed: {
+ ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor']),
+ ...mapState('events', ['dialog'])
+ },
+ watch: {
+ },
+ methods: {
+ ...mapMutations('events', ['setDialog'])
+ },
+ created () {
+ this.$store.dispatch('events/loadLists')
+ this.$store.dispatch('groups/loadLists')
+ }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/webapp/src/components/EventModuleDelete.vue b/webapp/src/components/EventModuleDelete.vue
new file mode 100644
index 0000000..f22fc41
--- /dev/null
+++ b/webapp/src/components/EventModuleDelete.vue
@@ -0,0 +1,66 @@
+<i18n>
+{
+ "en": {
+ "delete-are-you-sure": "Are you sure you want to delete this event? | Are you sure you want to delete these events?"
+ },
+ "de": {
+ "delete-are-you-sure": "Sind sie sicher, dass sie diese Veranstaltung Löschen wollen? | Sind sie sicher, dass sie diese Veranstaltungen löschen wollen?"
+ }
+}
+</i18n>
+
+<template>
+ <v-card>
+ <v-card-title class="elevation-3">
+ <div class="headline">{{ $t('title') }}</div>
+ </v-card-title>
+ <v-card-text>
+ {{ $tc('delete-are-you-sure', dialog.info.length) }}
+ <template v-for="item in dialog.info">
+ <div class="grey--text" :key="item.id">{{ item.name }} ({{ item.description }})</div>
+ </template>
+ </v-card-text>
+ <v-divider></v-divider>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn flat="flat" @click="cancelDelete">{{ $t('cancel') }}</v-btn>
+ <v-btn color="error" @click="deleteEvents">{{ $t('delete') }}</v-btn>
+ </v-card-actions>
+ </v-card>
+</template>
+
+<script>
+import { mapState, mapMutations } from 'vuex'
+
+export default {
+ name: 'EventModuleDelete',
+ data () {
+ return {
+ }
+ },
+ computed: {
+ ...mapState('events', ['dialog'])
+ },
+ methods: {
+ ...mapMutations('events', ['setDialog', 'loadEvents']),
+ cancelDelete () {
+ this.setDialog({ show: false })
+ },
+ async deleteEvents () {
+ var deleteIds = []
+ for (let i = 0; i < this.dialog.info.length; i++) {
+ deleteIds.push(this.dialog.info[i].id)
+ }
+ await this.$http.post('/api/events?delete', { ids: deleteIds })
+ this.setDialog({ show: false })
+ this.$snackbar({ color: 'success', text: this.$t('eventDeleteSuccess') })
+ this.$store.dispatch('events/loadEvents')
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+</style>
diff --git a/webapp/src/components/EventModuleEdit.vue b/webapp/src/components/EventModuleEdit.vue
new file mode 100644
index 0000000..2b6dbc3
--- /dev/null
+++ b/webapp/src/components/EventModuleEdit.vue
@@ -0,0 +1,638 @@
+<i18n>
+{
+ "en": {
+ "april": "April",
+ "august": "August",
+ "blacklist": "Blacklist",
+ "blacklistClients": "Blacklist Clients",
+ "blacklistGroups": "Blacklist Groups",
+ "clients": "Clients",
+ "config": "Configuration",
+ "day": "Day",
+ "days": "Days",
+ "december": "December",
+ "description": "Description",
+ "endDate": "End Date",
+ "endTime": "End Time",
+ "event": "Event",
+ "eventName": "Event Name",
+ "eventNameEmptyError": "Event Name can't be empty!",
+ "february": "February",
+ "friday": "Friday",
+ "groups": "Groups",
+ "groupsAndClients": "Groups / Clients",
+ "id": "ID",
+ "importantEvent": "Important Event",
+ "interval": "Interval",
+ "intervalMustBeNumberError": "Interval must be a valid number!",
+ "intervalTypes": "Interval Types",
+ "ip": "IP",
+ "january": "January",
+ "july": "July",
+ "june": "June",
+ "march": "March",
+ "may": "May",
+ "monday": "Monday",
+ "month": "Month",
+ "months": "Months",
+ "name": "Name",
+ "november": "November",
+ "october": "October",
+ "repetitiveModeSwitch": "repetitive Event",
+ "saturday": "Saturday",
+ "september": "September",
+ "startDate": "Start Date",
+ "startTime": "Start Time",
+ "sunday": "Sunday",
+ "thursday": "Thursday",
+ "tuesday": "Tuesday",
+ "uuid": "UUID",
+ "wednesday": "Wednesday",
+ "week": "Week"
+ },
+ "de": {
+ "april": "April",
+ "august": "August",
+ "blacklist": "Blacklist",
+ "blacklistClients": "Blacklist Clienten",
+ "blacklistGroups": "Blacklist Gruppen",
+ "clients": "Clients",
+ "config": "Konfiguration",
+ "day": "Tag",
+ "days": "Tage",
+ "december": "Dezember",
+ "description": "Beschreibung",
+ "endDate": "End Datum",
+ "endTime": "End Zeit",
+ "event": "Veranstaltung",
+ "eventName": "Veranstaltungsname",
+ "eventNameEmptyError": "Der Veranstaltungsname kann nicht leer sein!",
+ "february": "Februar",
+ "friday": "Freitag",
+ "groups": "Gruppen",
+ "groupsAndClients": "Gruppen / Clients",
+ "id": "ID",
+ "importantEvent": "Wichtige Veranstaltung",
+ "interval": "Intervall",
+ "intervalMustBeNumberError": "Das Intervall muss eine gültige Zahl sein!",
+ "intervalTypes": "Intervalltypen",
+ "ip": "IP",
+ "january": "Januar",
+ "july": "Juli",
+ "june": "Juni",
+ "march": "März",
+ "may": "Mai",
+ "monday": "Montag",
+ "month": "Monat",
+ "months": "Monate",
+ "name": "Name",
+ "november": "November",
+ "october": "Oktober",
+ "repetitiveModeSwitch": "Wiederholende Veranstaltung",
+ "saturday": "Samstag",
+ "september": "September",
+ "startDate": "Start Datum",
+ "startTime": "Start Zeit",
+ "sunday": "Sonntag",
+ "thursday": "Donnerstag",
+ "tuesday": "Dienstag",
+ "uuid": "UUID",
+ "wednesday": "Mittwoch",
+ "week": "Woche"
+ }
+}
+</i18n>
+
+<template>
+ <v-card>
+ <v-card-title style="padding: 0px">
+ <v-stepper v-model="step" horizontal style="width: 100%; background: transparent;" class="elevation-3">
+ <v-stepper-header>
+ <v-stepper-step
+ :complete="stepCompleted >= 1"
+ step="1"
+ :editable="stepCompleted >= 0"
+ edit-icon="check"
+ >
+ {{ $t('event') }}
+ </v-stepper-step>
+ <v-divider></v-divider>
+ <v-stepper-step
+ :complete="stepCompleted >= 2"
+ step="2"
+ :editable="stepCompleted >= 1"
+ edit-icon="check"
+ >
+ {{ $t('groupsAndClients') }}
+ </v-stepper-step>
+ <v-divider></v-divider>
+ <v-stepper-step
+ :complete="stepCompleted >= 3"
+ step="3"
+ :editable="stepCompleted >= 2"
+ edit-icon="check"
+ >
+ {{ $t('blacklist') }}
+ </v-stepper-step>
+ </v-stepper-header>
+ </v-stepper>
+ </v-card-title>
+ <v-card-text class="table-container">
+ <v-form v-model="valid" ref="form" @submit.prevent="submit" lazy-validation>
+ <v-stepper v-model="step" horizontal style="width: 100%; background: transparent" class="elevation-0">
+ <v-stepper-items>
+ <v-stepper-content step="1">
+ <v-layout row wrap>
+ <v-flex xs12 md5>
+ <v-text-field
+ v-model="name"
+ :label="$t('eventName')"
+ prepend-icon="label"
+ color="primary"
+ :rules="[() => !!name || $t('eventNameEmptyError')]"
+ ref="name"
+ ></v-text-field>
+ </v-flex>
+ <v-flex xs12 md5 offset-md1>
+ <select-box
+ prepend-icon="device_hub"
+ :label="$t('config')"
+ v-model="config"
+ :items="configList"
+ single-select
+ :max-columns="1"
+ ></select-box>
+ </v-flex>
+ <v-flex xs12 md5>
+ <v-textarea
+ v-model="description"
+ :label="$t('description')"
+ prepend-icon="description"
+ color="primary"
+ rows="1"
+ auto-grow
+ ></v-textarea>
+ </v-flex>
+ <v-flex xs12 md5 offset-md1>
+ <v-checkbox
+ v-model="important"
+ color="primary"
+ :label="$t('importantEvent')"
+ ></v-checkbox>
+ </v-flex>
+ </v-layout>
+ <v-layout row wrap>
+ <v-flex xs12 md5>
+ <v-menu
+ v-model="startDateMenu"
+ transition="scale-transition"
+ :close-on-content-click="false"
+ lazy
+ offset-y
+ full-width
+ min-width="290px"
+ >
+ <template v-slot:activator="{ on }">
+ <v-text-field
+ v-model="startDate"
+ :label="$t('startDate')"
+ prepend-icon="event"
+ readonly
+ v-on="on"
+ ></v-text-field>
+ </template>
+ <v-date-picker color="primary" header-color="primary" v-model="startDate" @input="startDatePick()"></v-date-picker>
+ </v-menu>
+ </v-flex>
+ <v-flex xs12 md5 offset-md1>
+ <v-menu
+ v-model="endDateMenu"
+ transition="scale-transition"
+ :close-on-content-click="false"
+ lazy
+ offset-y
+ full-width
+ min-width="290px"
+ >
+ <template v-slot:activator="{ on }">
+ <v-text-field
+ v-model="endDate"
+ :label="$t('endDate')"
+ prepend-icon="event"
+ readonly
+ v-on="on"
+ ></v-text-field>
+ </template>
+ <v-date-picker color="primary" header-color="primary" :min="startDate" v-model="endDate" @input="endDatePick()"></v-date-picker>
+ </v-menu>
+ </v-flex>
+ <v-flex xs12 md5>
+ <v-menu
+ v-model="startTimeMenu"
+ transition="scale-transition"
+ :close-on-content-click="false"
+ lazy
+ offset-y
+ full-width
+ max-width="290px"
+ min-width="290px"
+ >
+ <template v-slot:activator="{ on }">
+ <v-text-field
+ v-model="startTime"
+ :label="$t('startTime')"
+ prepend-icon="access_time"
+ readonly
+ v-on="on"
+ ></v-text-field>
+ </template>
+ <v-time-picker
+ v-model="startTime"
+ full-width
+ format="24hr"
+ color="primary"
+ header-color="primary"
+ @click:minute="startTimeMenu = false"
+ ></v-time-picker>
+ </v-menu>
+ </v-flex>
+ <v-flex xs12 md5 offset-md1>
+ <v-menu
+ v-model="endTimeMenu"
+ transition="scale-transition"
+ :close-on-content-click="false"
+ lazy
+ offset-y
+ full-width
+ max-width="290px"
+ min-width="290px"
+ >
+ <template v-slot:activator="{ on }">
+ <v-text-field
+ v-model="endTime"
+ :label="$t('endTime')"
+ prepend-icon="access_time"
+ readonly
+ v-on="on"
+ ></v-text-field>
+ </template>
+ <v-time-picker
+ v-model="endTime"
+ :min="startDate === endDate ? startTime : '00:00'"
+ full-width
+ format="24hr"
+ color="primary"
+ header-color="primary"
+ @click:minute="endTimeMenu = false"
+ ></v-time-picker>
+ </v-menu>
+ </v-flex>
+ </v-layout>
+ <v-layout row wrap>
+ <v-flex>
+ <v-switch color="primary" v-model="repetitiveEvent" :label="$t('repetitiveModeSwitch')"/>
+ </v-flex>
+ </v-layout>
+ <v-layout row wrap v-show="repetitiveEvent">
+ <v-flex xs12 md12>
+ <p style="margin-bottom: 0px">{{ $t('days') }}</p>
+ <v-btn small class="no-margin" :color="dayMap[0] ? 'primary' : ''" @click="setDayMap(0)">{{ $t('monday') }}</v-btn>
+ <v-btn small class="no-margin" :color="dayMap[1] ? 'primary' : ''" @click="setDayMap(1)">{{ $t('tuesday') }}</v-btn>
+ <v-btn small class="no-margin" :color="dayMap[2] ? 'primary' : ''" @click="setDayMap(2)">{{ $t('wednesday') }}</v-btn>
+ <v-btn small class="no-margin" :color="dayMap[3] ? 'primary' : ''" @click="setDayMap(3)">{{ $t('thursday') }}</v-btn>
+ <v-btn small class="no-margin" :color="dayMap[4] ? 'primary' : ''" @click="setDayMap(4)">{{ $t('friday') }}</v-btn>
+ <v-btn small class="no-margin" :color="dayMap[5] ? 'primary' : ''" @click="setDayMap(5)">{{ $t('saturday') }}</v-btn>
+ <v-btn small class="no-margin" :color="dayMap[6] ? 'primary' : ''" @click="setDayMap(6)">{{ $t('sunday') }}</v-btn>
+ </v-flex>
+ <v-flex xs12 md12 style="margin-top: 20px; margin-bottom: 20px">
+ <p style="margin-bottom: 0px">{{ $t('months') }}</p>
+ <v-btn small class="no-margin" :color="monthMap[0] ? 'primary' : ''" @click="setMonthMap(0)">{{ $t('january') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[1] ? 'primary' : ''" @click="setMonthMap(1)">{{ $t('february') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[2] ? 'primary' : ''" @click="setMonthMap(2)">{{ $t('march') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[3] ? 'primary' : ''" @click="setMonthMap(3)">{{ $t('april') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[4] ? 'primary' : ''" @click="setMonthMap(4)">{{ $t('may') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[5] ? 'primary' : ''" @click="setMonthMap(5)">{{ $t('june') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[6] ? 'primary' : ''" @click="setMonthMap(6)">{{ $t('july') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[7] ? 'primary' : ''" @click="setMonthMap(7)">{{ $t('august') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[8] ? 'primary' : ''" @click="setMonthMap(8)">{{ $t('september') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[9] ? 'primary' : ''" @click="setMonthMap(9)">{{ $t('october') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[10] ? 'primary' : ''" @click="setMonthMap(10)">{{ $t('november') }}</v-btn>
+ <v-btn small class="no-margin" :color="monthMap[11] ? 'primary' : ''" @click="setMonthMap(11)">{{ $t('december') }}</v-btn>
+ </v-flex>
+ <v-flex xs12 md5>
+ <v-text-field
+ v-model="interval"
+ :label="$t('interval')"
+ prepend-icon="repeat"
+ color="primary"
+ type="number"
+ min="1"
+ :rules="[() => !!interval || $t('intervalMustBeNumberError')]"
+ ></v-text-field>
+ </v-flex>
+ <v-flex xs12 md5 offset-md1>
+ <v-select :items="intervalTypes" :label="$t('intervalTypes')" v-model="intervalType" prepend-icon="repeat" color="primary" offset-y></v-select>
+ </v-flex>
+ </v-layout>
+ </v-stepper-content>
+
+ <v-stepper-content step="2">
+ <v-tabs v-model="tabs" grow slider-color="primary">
+ <v-tab><v-icon class="tabbar-tabicon">category</v-icon>{{ groups.length + ' ' + $t('groups') }}</v-tab>
+ <v-tab><v-icon class="tabbar-tabicon">computer</v-icon>{{ clients.length + ' ' + $t('clients') }}</v-tab>
+ </v-tabs>
+ <v-tabs-items v-model="tabs">
+ <v-tab-item>
+ <data-table v-model="groups" :headers="groupHeaders" :items="groupList"/>
+ </v-tab-item>
+ <v-tab-item>
+ <data-table v-model="clients" :headers="clientHeaders" :items="clientList"/>
+ </v-tab-item>
+ </v-tabs-items>
+ </v-stepper-content>
+
+ <v-stepper-content step="3">
+ <v-tabs v-model="tabs" grow slider-color="primary">
+ <v-tab><v-icon class="tabbar-tabicon">category</v-icon>{{ blacklistGroups.length + ' ' + $t('blacklistGroups') }}</v-tab>
+ <v-tab><v-icon class="tabbar-tabicon">computer</v-icon>{{ blacklistClients.length + ' ' + $t('blacklistClients') }}</v-tab>
+ </v-tabs>
+ <v-tabs-items v-model="tabs">
+ <v-tab-item>
+ <data-table v-model="blacklistGroups" :headers="groupHeaders" :items="blackgroupList"/>
+ </v-tab-item>
+ <v-tab-item>
+ <data-table v-model="blacklistClients" :headers="clientHeaders" :items="blackclientList"/>
+ </v-tab-item>
+ </v-tabs-items>
+ </v-stepper-content>
+
+ </v-stepper-items>
+ </v-stepper>
+ </v-form>
+ </v-card-text>
+ <v-divider></v-divider>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn flat @click.native="$store.commit('events/setDialog', { show : false } )">{{ $t('cancel') }}</v-btn>
+ <v-btn color="primary" v-show="step == 1" @click.native="completeStepOne()">{{ $t('continue') }}</v-btn>
+ <v-btn :color="dialog.info.id ? 'primary' : 'success'" v-show="step == 2" @click="submit" type="submit">{{ dialog.info.id ? $t('save') : $t('create') }}</v-btn>
+ <v-btn color="primary" v-show="step == 2" @click.native="completeStepTwo()">{{ $t('continue') }}</v-btn>
+ <v-btn :color="dialog.info.id ? 'primary' : 'success'" v-show="step == 3" @click="submit" type="submit">{{ dialog.info.id ? $t('save') : $t('create') }}</v-btn>
+ </v-card-actions>
+ </v-card>
+</template>
+
+<script>
+import SelectBox from '@/components/SelectBox'
+import DataTable from '@/components/DataTable'
+import { mapState } from 'vuex'
+
+export default {
+ name: 'EventModuleEdit',
+ components: {
+ SelectBox,
+ DataTable
+ },
+ data () {
+ return {
+ tabs: 0,
+ startDate: new Date().toISOString().substr(0, 10),
+ endDate: new Date().toISOString().substr(0, 10),
+ startTime: '00:00',
+ endTime: '23:59',
+ startDateMenu: false,
+ endDateMenu: false,
+ startTimeMenu: false,
+ endTimeMenu: false,
+ repetitiveEvent: false,
+ dayMap: [],
+ monthMap: [],
+ interval: 1,
+ intervalType: 'day',
+ valid: true,
+ step: 1,
+ stepCompleted: 0,
+ name: '',
+ description: '',
+ important: false,
+ config: [],
+ times: {},
+ groups: [],
+ blacklistGroups: [],
+ blackgroupList: [],
+ clients: [],
+ blacklistClients: [],
+ blackclientList: [],
+ clientHeaders: [
+ { text: this.$t('name'), key: 'name' },
+ { text: this.$t('ip'), key: 'ip' },
+ { text: this.$t('uuid'), key: 'uuid' }
+ ],
+ groupHeaders: [
+ { text: this.$t('id'), key: 'id' },
+ { text: this.$t('name'), key: 'name' },
+ { text: this.$t('description'), key: 'description' }
+ ],
+ intervalTypes: []
+ }
+ },
+ computed: {
+ ...mapState('events', ['dialog', 'configList']),
+ ...mapState('groups', ['groupList', 'clientList'])
+ },
+ watch: {
+ dialog: {
+ immediate: true,
+ deep: true,
+ handler (value) {
+ if (value.type === 'edit' && value.show) {
+ if (this.$refs.form) this.$refs.form.resetValidation()
+ this.step = 1
+ this.intervalTypes = []
+ this.intervalTypes.push(this.$t('day'))
+ this.intervalTypes.push(this.$t('week'))
+ this.intervalTypes.push(this.$t('month'))
+ this.stepCompleted = value.info.id ? 4 : 0
+ this.name = value.info.name || ''
+ this.description = value.info.description || ''
+ this.important = value.info.important || false
+
+ this.config = []
+ if (value.info.config) {
+ for (let i = 0; i < this.configList.length; i++) {
+ if (this.configList[i].id === value.info.config) {
+ this.config.push(this.configList[i])
+ }
+ }
+ }
+
+ this.times = value.info.times ? JSON.parse(value.info.times) : {}
+ this.repetitiveEvent = this.times.repetitive || false
+ if (this.repetitiveEvent) {
+ // edit of repetitive event
+ this.startDate = new Date(this.times.startDate * 1000)
+ this.startDate = this.formatDate(this.startDate, { time: false })
+ this.endDate = new Date(this.times.endDate * 1000)
+ this.endDate = this.formatDate(this.endDate, { time: false })
+ this.startTime = this.times.startTime
+ this.endTime = this.times.endTime
+ } else if (value.info.times) {
+ // edit of non repetitive event
+ var start = new Date(this.times.start * 1000)
+ var end = new Date(this.times.end * 1000)
+ this.startDate = this.formatDate(start, { time: false })
+ this.endDate = this.formatDate(end, { time: false })
+ this.startTime = this.formatDate(start, { date: false, seconds: false })
+ this.endTime = this.formatDate(end, { date: false, seconds: false })
+ } else {
+ // create new event
+ this.startDate = this.formatDate(new Date(), { time: false })
+ this.endDate = this.formatDate(new Date(), { time: false })
+ this.startTime = '00:00'
+ this.endTime = '23:59'
+ }
+ this.interval = this.times.interval || 1
+ this.intervalType = this.times.intervalType || 'day'
+ this.dayMap = this.times.dayMap || [1, 1, 1, 1, 1, 1, 1]
+ this.monthMap = this.times.monthMap || [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+
+ if (value.info.groups) {
+ this.groups = []
+ this.clients = []
+ var groupsPush = []
+ var clientsPush = []
+ for (let i = 0; i < value.info.groups.length; i++) {
+ if (value.info.groups[i].group_x_event.blacklist) groupsPush.push(value.info.groups[i])
+ else this.groups.push(value.info.groups[i])
+ }
+ for (let i = 0; i < value.info.clients.length; i++) {
+ if (value.info.clients[i].client_x_event.blacklist) clientsPush.push(value.info.clients[i])
+ else this.clients.push(value.info.clients[i])
+ }
+ this.loadChilds().then(() => {
+ this.blacklistGroups = groupsPush
+ this.blacklistClients = clientsPush
+ })
+ } else {
+ this.groups = []
+ this.clients = value.info.clients ? value.info.clients : []
+ }
+ }
+ }
+ }
+ },
+ methods: {
+ formatDate (date, options = {}) {
+ var result = ''
+ const pad = x => x < 10 ? '0' + x : x
+ if (options.date !== false) {
+ result += date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
+ }
+ if (options.time !== false) {
+ if (result !== '') result += ' '
+ result += pad(date.getHours()) + ':' + pad(date.getMinutes())
+ if (options.seconds !== false) result += ':' + pad(date.getSeconds())
+ }
+ return result
+ },
+ completeStepOne () {
+ if (this.$refs.form.validate()) {
+ this.step = 2
+ this.stepCompleted = Math.max(1, this.stepCompleted)
+ }
+ },
+ completeStepTwo () {
+ if (this.groups.length > 0) {
+ this.loadChilds()
+ }
+ this.step = 3
+ this.stepCompleted = Math.max(2, this.stepCompleted)
+ },
+ async submit (event) {
+ if (this.$refs.form.validate()) {
+ // Build times object
+ if (this.repetitiveEvent) {
+ var startD = new Date(this.startDate)
+ var endD = new Date(this.endDate)
+ this.times = {
+ 'repetitive': true,
+ 'startDate': (startD.getTime() / 1000),
+ 'endDate': (endD.getTime() / 1000),
+ 'startTime': this.startTime,
+ 'endTime': this.endTime,
+ 'dayMap': this.dayMap,
+ 'monthMap': this.monthMap,
+ 'interval': this.interval,
+ 'intervalType': this.intervalType
+ }
+ } else {
+ var start = new Date(this.startDate)
+ var startSplit = this.startTime.split(':')
+ start.setHours(startSplit[0])
+ start.setMinutes(startSplit[1])
+ var end = new Date(this.endDate)
+ var endSplit = this.endTime.split(':')
+ end.setHours(endSplit[0])
+ end.setMinutes(endSplit[1])
+ this.times = {
+ 'repetitive': false,
+ 'start': (start.getTime() / 1000),
+ 'end': (end.getTime() / 1000)
+ }
+ }
+ // Submit
+ await this.$http.post('/api/events/' + this.dialog.info.id, {
+ name: this.name,
+ description: this.description,
+ groups: this.groups.map(x => x.id),
+ blacklistGroups: this.blacklistGroups.map(x => x.id),
+ clients: this.clients.map(x => x.id),
+ blacklistClients: this.blacklistClients.map(x => x.id),
+ config: this.config.map(x => x.id),
+ important: this.important,
+ times: JSON.stringify(this.times)
+ })
+ this.$store.dispatch('events/loadEvents')
+ this.$store.commit('events/setDialog', { show: false })
+ this.$snackbar({ color: 'success', text: this.$t('eventSavedSuccess') })
+ } else {
+ this.$snackbar({ color: 'error', text: this.$t('eventNameEmptyError') })
+ }
+ },
+ startDatePick () {
+ this.startDateMenu = false
+ if (this.startDate > this.endDate) {
+ this.endDate = this.startDate
+ }
+ },
+ endDatePick () {
+ this.endDateMenu = false
+ if (this.endTime < this.startTime) {
+ this.endTime = this.startTime
+ }
+ },
+ setDayMap (i) {
+ this.$set(this.dayMap, i, this.dayMap[i] ? 0 : 1)
+ },
+ setMonthMap (i) {
+ this.$set(this.monthMap, i, this.monthMap[i] ? 0 : 1)
+ },
+ async loadChilds () {
+ var response = await this.$http.post('/api/events/blacklist', { groups: this.groups })
+ this.blackgroupList = response.data.subgroups
+ this.blackclientList = response.data.clients
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.info-input {
+ margin: 20px;
+}
+.no-margin {
+ margin-right: 0px;
+ margin-left: 0px;
+}
+</style>
diff --git a/webapp/src/components/EventModuleEventList.vue b/webapp/src/components/EventModuleEventList.vue
new file mode 100644
index 0000000..fe2ac1a
--- /dev/null
+++ b/webapp/src/components/EventModuleEventList.vue
@@ -0,0 +1,85 @@
+<i18n>
+{
+ "en": {
+ "create-event": "Create Event",
+ "delete-event": "Delete {0} Event | Delete {0} Events",
+ "name": "Name",
+ "description": "Description",
+ "config": "Configuration",
+ "times": "Times"
+ },
+ "de": {
+ "create-event": "Veranstaltung erstellen",
+ "delete-event": "Lösche {0} Veranstaltung | Lösche {0} Veranstaltungen",
+ "name": "Name",
+ "description": "Beschreibung",
+ "config": "Konfiguration",
+ "times": "Zeiten"
+ }
+}
+</i18n>
+
+<template>
+ <div>
+ <v-card>
+ <data-table v-model="selectedEvents" :headers="headers" :items="events">
+ <div slot="action" slot-scope="row" style="text-align: right">
+ <v-btn flat icon color="primary" @click.stop="editEvent(row.item)"><v-icon>edit</v-icon></v-btn>
+ </div>
+ </data-table>
+ </v-card>
+ <div class="text-xs-right">
+ <v-btn color="error" flat @click="deleteEvent" :disabled="selectedEvents.length === 0">
+ <v-icon left>remove_circle_outline</v-icon>{{ $tc('delete-event', selectedEvents.length, [selectedEvents.length]) }}
+ </v-btn>
+ <v-btn color="success" flat @click="createEvent">
+ <v-icon left>add_circle_outline</v-icon>{{ $t('create-event') }}
+ </v-btn>
+ </div>
+ </div>
+</template>
+
+<script>
+import { mapState, mapMutations } from 'vuex'
+import DataTable from '@/components/DataTable'
+
+export default {
+ name: 'EventModuleEventList',
+ components: {
+ DataTable
+ },
+ data () {
+ return {
+ selectedEvents: []
+ }
+ },
+ computed: {
+ headers () {
+ return [
+ { text: this.$t('name'), key: 'name' },
+ { text: this.$t('description'), key: 'description' },
+ { text: this.$t('config'), key: 'config' },
+ { sortable: false, key: 'action', width: '60px' }
+ ]
+ },
+ ...mapState('events', ['events'])
+ },
+ methods: {
+ ...mapMutations('events', ['setDialog']),
+ createEvent () {
+ this.setDialog({ show: true, type: 'edit', info: {} })
+ },
+ deleteEvent () {
+ this.setDialog({ show: true, type: 'delete', info: this.selectedEvents })
+ },
+ editEvent (event) {
+ this.setDialog({ show: true, type: 'edit', info: event })
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+</style>
diff --git a/webapp/src/components/PermissionModule.vue b/webapp/src/components/PermissionModule.vue
index 213ca4f..109007c 100644
--- a/webapp/src/components/PermissionModule.vue
+++ b/webapp/src/components/PermissionModule.vue
@@ -3,14 +3,14 @@
"en": {
"roles": "Roles",
"users": "Users",
- "delete-are-you-sure": "Are you sure you want to delete this role? | Are you sure you want to delete those roles?",
+ "delete-are-you-sure": "Are you sure you want to delete this role? | Are you sure you want to delete these roles?",
"delete-permission": "Delete {0} role | Delete {0} roles",
"delete-role": "Delete {0} Role | Delete {0} Roles"
},
"de": {
"roles": "Rollen",
"users": "Nutzer",
- "delete-are-you-sure": "Sind sie sicher, dass sie diese Rolle Löschen wollen? | Sind sie sicher, dass sie diese Rollen Löschen wollen?",
+ "delete-are-you-sure": "Sind sie sicher, dass sie diese Rolle Löschen wollen? | Sind sie sicher, dass sie diese Rollen löschen wollen?",
"delete-permission": "Delete {0} role | Delete {0} roles",
"delete-role": "Lösche {0} Rolle | Lösche {0} Rollen"
}
diff --git a/webapp/src/components/PermissionModuleEdit.vue b/webapp/src/components/PermissionModuleEdit.vue
index c026f54..f88eb4c 100644
--- a/webapp/src/components/PermissionModuleEdit.vue
+++ b/webapp/src/components/PermissionModuleEdit.vue
@@ -274,6 +274,7 @@ export default {
}).then(response => {
this.$snackbar({ color: 'success', text: this.$t('roleSavedSuccess') })
this.$store.dispatch('permissions/loadRoleData')
+ this.$store.dispatch('permissions/loadUserData')
this.$store.commit('permissions/setEdit', false)
}).catch(error => {
console.log(error)
diff --git a/webapp/src/components/PermissionModuleRoleList.vue b/webapp/src/components/PermissionModuleRoleList.vue
index 7b744b6..67fbb9f 100644
--- a/webapp/src/components/PermissionModuleRoleList.vue
+++ b/webapp/src/components/PermissionModuleRoleList.vue
@@ -52,7 +52,7 @@ export default {
{ text: this.$t('id'), key: 'id' },
{ text: this.$t('name'), key: 'name' },
{ text: this.$t('description'), key: 'descr' },
- { sortable: false, key: 'action' }
+ { sortable: false, key: 'action', width: '60px' }
],
canEdit: false
}
diff --git a/webapp/src/config/dashboard.js b/webapp/src/config/dashboard.js
index 26de0fc..7a5244e 100644
--- a/webapp/src/config/dashboard.js
+++ b/webapp/src/config/dashboard.js
@@ -6,6 +6,7 @@ import PermissionModule from '@/components/PermissionModule'
import IpxeBuilderModule from '@/components/IpxeBuilderModule'
import UserModule from '@/components/UserModule'
import LogModule from '@/components/LogModule'
+import EventModule from '@/components/EventModule'
export default [
{ path: 'groups', component: GroupModule, icon: 'category' },
@@ -15,5 +16,6 @@ export default [
{ path: 'permissions', component: PermissionModule, icon: 'lock_open' },
{ path: 'ipxe', component: IpxeBuilderModule, icon: 'merge_type' },
{ path: 'users', component: UserModule, icon: 'contacts' },
- { path: 'log', component: LogModule, icon: 'error_outline' }
+ { path: 'log', component: LogModule, icon: 'error_outline' },
+ { path: 'events', component: EventModule, icon: 'event' }
]
diff --git a/webapp/src/config/i18n.js b/webapp/src/config/i18n.js
index c9cf137..25e6f3a 100644
--- a/webapp/src/config/i18n.js
+++ b/webapp/src/config/i18n.js
@@ -30,7 +30,8 @@ export default {
'PermissionModule': 'Permission Manager',
'IpxeBuilderModule': 'iPXE Builder',
'UserModule': 'User Management',
- 'LogModule': 'System Log'
+ 'LogModule': 'System Log',
+ 'EventModule': 'Event Manager'
}
},
'de': {
@@ -64,7 +65,8 @@ export default {
'PermissionModule': 'Rechteverwaltung',
'IpxeBuilderModule': 'iPXE Builder',
'UserModule': 'Benutzerverwaltung',
- 'LogModule': 'System Protokoll'
+ 'LogModule': 'System Protokoll',
+ 'EventModule': 'Veranstaltungsverwaltung'
}
}
}
diff --git a/webapp/src/config/store.js b/webapp/src/config/store.js
index ac65696..f8bb4f0 100644
--- a/webapp/src/config/store.js
+++ b/webapp/src/config/store.js
@@ -5,6 +5,7 @@ import registration from '@/store/registration'
import backends from '@/store/backends'
import permissions from '@/store/permissions'
import users from '@/store/users'
+import events from '@/store/events'
export default {
notifications,
@@ -13,5 +14,6 @@ export default {
registration,
backends,
permissions,
- users
+ users,
+ events
}
diff --git a/webapp/src/store/events.js b/webapp/src/store/events.js
new file mode 100644
index 0000000..27cb3e7
--- /dev/null
+++ b/webapp/src/store/events.js
@@ -0,0 +1,35 @@
+import axios from 'axios'
+
+export default {
+ namespaced: true,
+ state: {
+ events: [],
+ configList: [],
+ dialog: { show: false, type: null, info: {} }
+ },
+ mutations: {
+ setEvents (state, value) { state.events = value },
+ setConfigList (state, value) { state.configList = value },
+ setDialog (state, { show, type, info }) {
+ if (show !== undefined) state.dialog.show = show
+ if (type !== undefined) state.dialog.type = type
+ if (info !== undefined) state.dialog.info = info
+ }
+ },
+ actions: {
+ loadEvents (context) {
+ axios.get('/api/events').then(response => {
+ context.commit('setEvents', response.data)
+ })
+ },
+ loadConfigs (context) {
+ axios.get('/api/configurator/configs').then(result => {
+ context.commit('setConfigList', result.data)
+ })
+ },
+ loadLists (context) {
+ context.dispatch('loadEvents')
+ context.dispatch('loadConfigs')
+ }
+ }
+}