Add UI for managing blockers

This commit is contained in:
Phillip Thelen
2025-05-28 23:26:18 +02:00
parent 3d16387a61
commit ef2b7eb928
6 changed files with 224 additions and 45 deletions

View File

@@ -0,0 +1,51 @@
<template>
<div style="display: contents">
<td></td>
<td><select class="form-control" v-model="blocker.type">
<option value="ipaddress">IP-Address</option>
<option value="email">E-Mail</option>
</select>
</td>
<td><select class="form-control" v-model="blocker.area">
<option value="full">Full</option>
<option value="payments">Payments</option>
</select></td>
<td><input v-model="blocker.value"></td>
<td><input v-model="blocker.reason"></td>
<td>
<button class="btn btn-primary mr-2" @click="$emit('save', blocker)">
<span>Save</span>
</button>
<button class="btn btn-danger" @click="$emit('cancel')">
<span>Cancel</span>
</button>
</td>
</div>
</template>
<script>
export default {
name: 'BlockerForm',
props: {
isNew: {
type: Boolean,
default: false,
},
blocker: {
type: Object,
default: () => ({
type: '',
area: '',
value: '',
reason: '',
}),
},
},
data () {
return {};
},
methods: {
// Add methods if needed
},
};
</script>

View File

@@ -1,7 +1,10 @@
<template>
<div class="row standard-page col-12 d-flex justify-content-center">
<div class="blocker-content">
<h1>Blockers</h1>
<h1>Blockers
<button
class="btn btn-primary float-right"
@click="showCreateForm = true">Create</button></h1>
<table class="table">
<thead>
<tr>
@@ -10,16 +13,31 @@
<th>Area</th>
<th>Value</th>
<th>Reason</th>
<th class="action-column"></th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-if="showCreateForm">
<BlockerForm
:is-new="true"
:blocker="newBlocker"
@save="createBlocker"
@cancel="showCreateForm = false"
/>
</tr>
<tr
v-for="blocker in blockers"
:key="blocker._id">
<td>{{ blocker.created }}</td>
<td>{{ blocker.type }}</td>
<td>{{ blocker.area }}</td>
<BlockerForm
v-if="blocker._id === editedBlockerId"
:blocker="blocker"
@save="saveBlocker(blocker)"
@cancel="editedBlockerId = null"
/>
<template v-else>
<td>{{ blocker.createdAt }}</td>
<td>{{ getTypeName(blocker.type) }}</td>
<td>{{ getAreaName(blocker.area) }}</td>
<td>{{ blocker.value }}</td>
<td>{{ blocker.reason }}</td>
<td>
@@ -35,7 +53,7 @@
</button>
<button
class="btn btn-danger"
@click="removeBlocker(blocker._id)"
@click="deleteBlocker(blocker._id)"
>
<span
v-once
@@ -44,11 +62,11 @@
></span>
</button>
</td>
</template>
</tr>
</tbody>
</table>
</div>
</div>
</template>
@@ -72,11 +90,23 @@ import { mapState } from '@/libs/store';
import editIcon from '@/assets/svg/edit.svg';
import deleteIcon from '@/assets/svg/delete.svg';
import BlockerForm from './blocker_form.vue';
export default {
components: {
BlockerForm,
},
data () {
return {
showCreateForm: false,
newBlocker: {
type: '',
area: '',
value: '',
reason: '',
},
blockers: [],
editedBlockerId: null,
icons: Object.freeze({
editIcon,
deleteIcon,
@@ -95,7 +125,47 @@ export default {
methods: {
async loadBlockers () {
this.blockers = await this.$store.dispatch('blockers:getBlockers');
console.log(this.blockers);
},
editBlocker (id) {
this.editedBlockerId = id;
},
async saveBlocker (blocker) {
await this.$store.dispatch('blockers:updateBlocker', { blocker });
this.editedBlockerId = null;
this.loadBlockers();
},
async deleteBlocker (blockerId) {
if (!window.confirm('Are you sure you want to delete this blocker?')) {
return;
}
await this.$store.dispatch('blockers:deleteBlocker', { blockerId });
this.loadBlockers();
},
async createBlocker (blocker) {
await this.$store.dispatch('blockers:createBlocker', { blocker });
this.showCreateForm = false;
this.loadBlockers();
},
getTypeName (type) {
switch (type) {
case 'ipaddress':
return 'IP Address';
case 'email':
return 'E-Mail';
default:
return type;
}
},
getAreaName (area) {
switch (area) {
case 'full':
return 'Full';
case 'payments':
return 'Payments';
default:
return area;
}
},
},
};

View File

@@ -2,6 +2,18 @@ import axios from 'axios';
export async function getBlockers () {
const response = await axios.get('/api/v4/admin/blockers');
console.log(response);
return response.data.data;
}
export async function createBlocker (store, payload) {
const response = await axios.post('/api/v4/admin/blockers', payload.blocker);
return response.data.data;
}
export async function updateBlocker (store, payload) {
const response = await axios.put(`/api/v4/admin/blockers/${payload.blocker._id}`, payload.blocker);
return response.data.data;
}
export async function deleteBlocker (store, payload) {
const response = await axios.delete(`/api/v4/admin/blockers/${payload.blockerId}`);
return response.data.data;
}

View File

@@ -1,4 +1,6 @@
import validator from 'validator';
import _ from 'lodash';
import { v4 as uuid } from 'uuid';
import { authWithHeaders } from '../../middlewares/auth';
import { ensurePermission } from '../../middlewares/ensureAccessRight';
import { model as User } from '../../models/user';
@@ -123,7 +125,7 @@ api.getBlockers = {
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
async handler (req, res) {
const blockers = await Blocker
.find()
.find({ disabled: false })
.lean()
.exec();
@@ -131,4 +133,59 @@ api.getBlockers = {
},
};
api.createBlocker = {
method: 'POST',
url: '/admin/blockers',
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
async handler (req, res) {
const id = uuid();
const blocker = await Blocker({
_id: id,
...Blocker.sanitize(req.body),
}).save();
res.respond(200, blocker);
},
};
api.updateBlocker = {
method: 'PUT',
url: '/admin/blockers/:blockerId',
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
async handler (req, res) {
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const blocker = await Blocker.findById(req.params.blockerId).exec();
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
_.merge(blocker, Blocker.sanitize(req.body));
const savedBlocker = await blocker.save();
res.respond(200, savedBlocker);
},
};
api.deleteBlocker = {
method: 'DELETE',
url: '/admin/blockers/:blockerId',
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
async handler (req, res) {
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const blocker = await Blocker.findById(req.params.blockerId).exec();
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
blocker.disabled = true;
const savedBlocker = await blocker.save();
res.respond(200, savedBlocker);
},
};
export default api;

View File

@@ -2,8 +2,6 @@
import mongoose from 'mongoose';
import EventEmitter from 'events';
import { v4 as uuid } from 'uuid';
import validator from 'validator';
import baseModel from '../libs/baseModel';
export const blockTypes = [
@@ -17,12 +15,6 @@ export const blockArea = [
];
export const schema = new mongoose.Schema({
id: {
$type: String,
default: uuid,
validate: [v => validator.isUUID(v), 'Invalid uuid for tag.'],
required: true,
},
disabled: {
$type: Boolean, default: false, // If true, the block is disabled
},
@@ -44,14 +36,11 @@ export const schema = new mongoose.Schema({
}, {
strict: true,
minimize: false, // So empty objects are returned
_id: false, // use id instead of _id
typeKey: '$type', // So that we can use fields named `type`
});
schema.plugin(baseModel, {
timestamps: true,
noSet: ['_id'],
_id: false, // use id instead of _id
});
schema.statics.watchBlockers = function watchBlockers (query, options) {