mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 07:37:25 +01:00
Add UI for managing blockers
This commit is contained in:
Submodule habitica-images updated: 992d838120...aa72332019
51
website/client/src/components/blocker/blocker_form.vue
Normal file
51
website/client/src/components/blocker/blocker_form.vue
Normal 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>
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row standard-page col-12 d-flex justify-content-center">
|
<div class="row standard-page col-12 d-flex justify-content-center">
|
||||||
<div class="blocker-content">
|
<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">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -10,45 +13,60 @@
|
|||||||
<th>Area</th>
|
<th>Area</th>
|
||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
<th>Reason</th>
|
<th>Reason</th>
|
||||||
<th class="action-column"></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr v-if="showCreateForm">
|
||||||
|
<BlockerForm
|
||||||
|
:is-new="true"
|
||||||
|
:blocker="newBlocker"
|
||||||
|
@save="createBlocker"
|
||||||
|
@cancel="showCreateForm = false"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-for="blocker in blockers"
|
v-for="blocker in blockers"
|
||||||
:key="blocker._id">
|
:key="blocker._id">
|
||||||
<td>{{ blocker.created }}</td>
|
<BlockerForm
|
||||||
<td>{{ blocker.type }}</td>
|
v-if="blocker._id === editedBlockerId"
|
||||||
<td>{{ blocker.area }}</td>
|
:blocker="blocker"
|
||||||
<td>{{ blocker.value }}</td>
|
@save="saveBlocker(blocker)"
|
||||||
<td>{{ blocker.reason }}</td>
|
@cancel="editedBlockerId = null"
|
||||||
<td>
|
/>
|
||||||
<button
|
<template v-else>
|
||||||
class="btn btn-primary mr-2"
|
<td>{{ blocker.createdAt }}</td>
|
||||||
@click="editBlocker(blocker._id)"
|
<td>{{ getTypeName(blocker.type) }}</td>
|
||||||
>
|
<td>{{ getAreaName(blocker.area) }}</td>
|
||||||
<span
|
<td>{{ blocker.value }}</td>
|
||||||
v-once
|
<td>{{ blocker.reason }}</td>
|
||||||
class="svg-icon icon-16"
|
<td>
|
||||||
v-html="icons.editIcon"
|
<button
|
||||||
></span>
|
class="btn btn-primary mr-2"
|
||||||
</button>
|
@click="editBlocker(blocker._id)"
|
||||||
<button
|
>
|
||||||
class="btn btn-danger"
|
<span
|
||||||
@click="removeBlocker(blocker._id)"
|
v-once
|
||||||
>
|
class="svg-icon icon-16"
|
||||||
<span
|
v-html="icons.editIcon"
|
||||||
v-once
|
></span>
|
||||||
class="svg-icon icon-16"
|
</button>
|
||||||
v-html="icons.deleteIcon"
|
<button
|
||||||
></span>
|
class="btn btn-danger"
|
||||||
</button>
|
@click="deleteBlocker(blocker._id)"
|
||||||
</td>
|
>
|
||||||
|
<span
|
||||||
|
v-once
|
||||||
|
class="svg-icon icon-16"
|
||||||
|
v-html="icons.deleteIcon"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -72,11 +90,23 @@ import { mapState } from '@/libs/store';
|
|||||||
|
|
||||||
import editIcon from '@/assets/svg/edit.svg';
|
import editIcon from '@/assets/svg/edit.svg';
|
||||||
import deleteIcon from '@/assets/svg/delete.svg';
|
import deleteIcon from '@/assets/svg/delete.svg';
|
||||||
|
import BlockerForm from './blocker_form.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
BlockerForm,
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
showCreateForm: false,
|
||||||
|
newBlocker: {
|
||||||
|
type: '',
|
||||||
|
area: '',
|
||||||
|
value: '',
|
||||||
|
reason: '',
|
||||||
|
},
|
||||||
blockers: [],
|
blockers: [],
|
||||||
|
editedBlockerId: null,
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
editIcon,
|
editIcon,
|
||||||
deleteIcon,
|
deleteIcon,
|
||||||
@@ -95,7 +125,47 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async loadBlockers () {
|
async loadBlockers () {
|
||||||
this.blockers = await this.$store.dispatch('blockers:getBlockers');
|
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;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ import axios from 'axios';
|
|||||||
|
|
||||||
export async function getBlockers () {
|
export async function getBlockers () {
|
||||||
const response = await axios.get('/api/v4/admin/blockers');
|
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;
|
return response.data.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
@@ -123,7 +125,7 @@ api.getBlockers = {
|
|||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const blockers = await Blocker
|
const blockers = await Blocker
|
||||||
.find()
|
.find({ disabled: false })
|
||||||
.lean()
|
.lean()
|
||||||
.exec();
|
.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;
|
export default api;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import validator from 'validator';
|
|
||||||
import baseModel from '../libs/baseModel';
|
import baseModel from '../libs/baseModel';
|
||||||
|
|
||||||
export const blockTypes = [
|
export const blockTypes = [
|
||||||
@@ -17,12 +15,6 @@ export const blockArea = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const schema = new mongoose.Schema({
|
export const schema = new mongoose.Schema({
|
||||||
id: {
|
|
||||||
$type: String,
|
|
||||||
default: uuid,
|
|
||||||
validate: [v => validator.isUUID(v), 'Invalid uuid for tag.'],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
disabled: {
|
disabled: {
|
||||||
$type: Boolean, default: false, // If true, the block is disabled
|
$type: Boolean, default: false, // If true, the block is disabled
|
||||||
},
|
},
|
||||||
@@ -44,14 +36,11 @@ export const schema = new mongoose.Schema({
|
|||||||
}, {
|
}, {
|
||||||
strict: true,
|
strict: true,
|
||||||
minimize: false, // So empty objects are returned
|
minimize: false, // So empty objects are returned
|
||||||
_id: false, // use id instead of _id
|
|
||||||
typeKey: '$type', // So that we can use fields named `type`
|
typeKey: '$type', // So that we can use fields named `type`
|
||||||
});
|
});
|
||||||
|
|
||||||
schema.plugin(baseModel, {
|
schema.plugin(baseModel, {
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
noSet: ['_id'],
|
|
||||||
_id: false, // use id instead of _id
|
|
||||||
});
|
});
|
||||||
|
|
||||||
schema.statics.watchBlockers = function watchBlockers (query, options) {
|
schema.statics.watchBlockers = function watchBlockers (query, options) {
|
||||||
|
|||||||
Reference in New Issue
Block a user