Add interface to block ip-addresses or clients due to abuse (#15484)
* Read IP blocks from database * begin building general blocking solution * add new frontend files * Add UI for managing blockers * correctly reset local data after creating blocker * Tweak wording * Add UI for managing blockers * restructure admin pages * improve test coverage * Improve blocker UI * add blocker to block emails from registration * lint fix * fix * lint fixes * fix import * add new permission for managing blockers * improve permission check * fix managing permissions from admin * improve navbar display for non fullAccess admin * update block error strings * lint fix * add option to errorHandler to skip logging * validate blocker value during input * improve blocker form display * chore(subproj): reconcile habitica-images * fix(scripts): use same Mongo version for dev/test * fix(whitespace): eof * documentation improvements * remove nconf import * remove old test --------- Co-authored-by: Kalista Payne <kalista@habitica.com> Co-authored-by: Kalista Payne <sabrecat@gmail.com>
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<div class="card mt-2">
|
||||
<div class="card-header">
|
||||
<h3
|
||||
class="mb-0 mt-0"
|
||||
:class="{'open': expand}"
|
||||
@click="expand = !expand"
|
||||
>
|
||||
Items
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
v-if="expand"
|
||||
class="card-body"
|
||||
>
|
||||
<div>
|
||||
The sections below display each item's key (bolded if the player has ever owned it),
|
||||
followed by the item's English name.
|
||||
<ul>
|
||||
<li>
|
||||
Click on an item's key or value to change it
|
||||
(hovering shows an underline to show where you can click).
|
||||
</li>
|
||||
<li>For Mounts and Gear, clicking toggles between the allowed values.</li>
|
||||
<li>For other item types, clicking gives you a form field to enter a new value.</li>
|
||||
<li>Click Save when the correct value is displayed.</li>
|
||||
<li>
|
||||
You must Save for each item individually but you do not need to reload the user
|
||||
between each Save.
|
||||
</li>
|
||||
<li>If you adjust an item and do not click Save for it, the change will be lost.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="itemType in itemTypes"
|
||||
:key="itemType"
|
||||
>
|
||||
<div class="accordion-group">
|
||||
<h4
|
||||
class="expand-toggle"
|
||||
:class="{'open': expandItemType[itemType]}"
|
||||
@click="expandItemType[itemType] = !expandItemType[itemType]"
|
||||
>
|
||||
{{ itemType }}
|
||||
</h4>
|
||||
|
||||
<div v-if="expandItemType[itemType]">
|
||||
<p v-if="itemType === 'pets'">
|
||||
A value of -1 means they owned the Pet but Released it
|
||||
and have not yet rehatched it.
|
||||
</p>
|
||||
<p v-if="itemType === 'mounts'">
|
||||
A value of "null" means they owned the Mount but Released it
|
||||
and have not yet retamed it.
|
||||
</p>
|
||||
<p v-if="itemType === 'special'">
|
||||
When there are 0 of these items, we can't tell if
|
||||
they had been owned and were all used, or have never been owned.
|
||||
</p>
|
||||
<p v-if="itemType === 'gear'">
|
||||
A value of true means they own the item now and can wear it.
|
||||
A value of false means they used to own it but lost it from Death
|
||||
(or an old Rebirth).
|
||||
</p>
|
||||
<ul>
|
||||
<li
|
||||
v-for="item in collatedItemData[itemType]"
|
||||
:key="item.path"
|
||||
>
|
||||
<form @submit.prevent="saveItem(item)">
|
||||
<span
|
||||
class="enableValueChange"
|
||||
@click="enableValueChange(item)"
|
||||
>
|
||||
<span :class="item.value ? 'owned' : 'not-owned'">
|
||||
{{ item | displayValue }}
|
||||
</span>
|
||||
:
|
||||
<span :class="{ ownedItem: !item.neverOwned }">{{ item.key }} : </span>
|
||||
</span>
|
||||
<span v-html="item.name"></span>
|
||||
|
||||
<div
|
||||
v-if="item.modified"
|
||||
class="form-inline"
|
||||
>
|
||||
<input
|
||||
v-if="item.valueIsInteger"
|
||||
v-model="item.value"
|
||||
class="form-control valueField"
|
||||
type="number"
|
||||
>
|
||||
<input
|
||||
v-if="item.modified"
|
||||
type="submit"
|
||||
value="Save"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
ul li {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
.ownedItem {
|
||||
font-weight: bold;
|
||||
}
|
||||
.enableValueChange:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.valueField {
|
||||
min-width: 10ch;
|
||||
}
|
||||
.owned {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.not-owned {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import content from '@/../../common/script/content';
|
||||
import getItemDescription from '../mixins/getItemDescription';
|
||||
import saveHero from '../mixins/saveHero';
|
||||
|
||||
function collateItemData (self) {
|
||||
const collatedItemData = {};
|
||||
self.itemTypes.forEach(itemType => {
|
||||
// itemTypes are pets, food, gear, etc
|
||||
|
||||
// Set up some basic data for this itemType:
|
||||
let basePath = `items.${itemType}`;
|
||||
let ownedItems = self.hero.items[itemType] || {};
|
||||
let allItems = content[itemType];
|
||||
if (itemType === 'gear') {
|
||||
basePath = 'items.gear.owned';
|
||||
ownedItems = self.hero.items.gear.owned || {};
|
||||
allItems = content.gear.flat;
|
||||
} else if (itemType === 'pets' || itemType === 'mounts') {
|
||||
// add the non-Standard pets and mounts
|
||||
const ucItemType = (itemType === 'pets') ? 'Pets' : 'Mounts';
|
||||
self.petMountSubTypes.forEach(subType => {
|
||||
allItems = { ...allItems, ...content[subType + ucItemType] };
|
||||
});
|
||||
}
|
||||
|
||||
const itemData = []; // all items for this itemType
|
||||
|
||||
// Collate data for items that the user owns or used to own:
|
||||
for (const key of Object.keys(ownedItems)) {
|
||||
// Do not sort keys. The order in the items object gives hints about order received.
|
||||
if (itemType !== 'special' || self.specialItems.includes(key)) {
|
||||
const valueIsInteger = !self.nonIntegerTypes.includes(itemType);
|
||||
itemData.push({
|
||||
neverOwned: false,
|
||||
itemType,
|
||||
key,
|
||||
modified: false,
|
||||
name: self.getItemDescription(itemType, key),
|
||||
path: `${basePath}.${key}`,
|
||||
value: ownedItems[key],
|
||||
valueIsInteger,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Collate data for items that the user never owned:
|
||||
for (const key of Object.keys(allItems).sort()) {
|
||||
if (
|
||||
// ignore items the user owns because we captured them above:
|
||||
!(key in ownedItems)
|
||||
|
||||
// ignore gear items that indicate empty equipped slots (e.g., head_base_0):
|
||||
&& !(itemType === 'gear' && content.gear.flat[key].set
|
||||
&& content.gear.flat[key].set === 'base-0')
|
||||
|
||||
// ignore "special" items that aren't Snowballs, Seafoam, etc:
|
||||
&& (itemType !== 'special' || self.specialItems.includes(key))
|
||||
) {
|
||||
const valueIsInteger = !self.nonIntegerTypes.includes(itemType);
|
||||
const value = (valueIsInteger) ? 0 : '';
|
||||
itemData.push({
|
||||
neverOwned: true,
|
||||
itemType,
|
||||
key,
|
||||
modified: false,
|
||||
name: self.getItemDescription(itemType, key),
|
||||
path: `${basePath}.${key}`,
|
||||
value,
|
||||
valueIsInteger,
|
||||
});
|
||||
}
|
||||
}
|
||||
collatedItemData[itemType] = itemData;
|
||||
});
|
||||
return collatedItemData;
|
||||
}
|
||||
|
||||
function resetData (self) {
|
||||
self.collatedItemData = collateItemData(self);
|
||||
self.itemTypes.forEach(itemType => { self.expandItemType[itemType] = false; });
|
||||
}
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
displayValue (item) {
|
||||
if (item.value === '') return 'never owned';
|
||||
if (item.value === 0 && item.neverOwned) return '0 (never owned)';
|
||||
if (item.value === null) return 'null'; // we need visible text
|
||||
return item.value; // true or false or an integer
|
||||
},
|
||||
},
|
||||
mixins: [
|
||||
getItemDescription,
|
||||
saveHero,
|
||||
],
|
||||
props: {
|
||||
resetCounter: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
hero: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
expand: false,
|
||||
expandItemType: {
|
||||
eggs: false,
|
||||
hatchingPotions: false,
|
||||
food: false,
|
||||
pets: false,
|
||||
mounts: false,
|
||||
quests: false,
|
||||
gear: false,
|
||||
special: false,
|
||||
},
|
||||
itemTypes: ['eggs', 'hatchingPotions', 'food', 'pets', 'mounts', 'quests', 'gear', 'special'],
|
||||
nonIntegerTypes: ['mounts', 'gear'],
|
||||
petMountSubTypes: ['premium', 'quest', 'special', 'wacky'], // e.g., 'premiumPets'
|
||||
// items.special includes many things but we are interested in these only:
|
||||
specialItems: ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'],
|
||||
collatedItemData: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
resetCounter () {
|
||||
resetData(this);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
resetData(this);
|
||||
},
|
||||
methods: {
|
||||
async saveItem (item) {
|
||||
// prepare the item's new value and path for being saved
|
||||
const toSave = {
|
||||
_id: this.hero._id,
|
||||
};
|
||||
toSave.itemPath = item.path;
|
||||
if (item.value === null) {
|
||||
toSave.itemVal = 'null';
|
||||
} else if (item.value === false) {
|
||||
toSave.itemVal = 'false';
|
||||
} else {
|
||||
toSave.itemVal = item.value;
|
||||
}
|
||||
|
||||
await this.saveHero({ hero: toSave, msg: item.key });
|
||||
item.neverOwned = false;
|
||||
item.modified = false;
|
||||
},
|
||||
enableValueChange (item) {
|
||||
// allow form field(s) to be shown:
|
||||
item.modified = true;
|
||||
|
||||
// for non-integer items, toggle through the allowed values:
|
||||
if (item.itemType === 'gear' || item.itemType === 'mounts') {
|
||||
// Allowed starting values are true, false, and undefined (never owned)
|
||||
if (item.value && item.value !== '') {
|
||||
item.value = false;
|
||||
} else if (typeof item.value === 'boolean') {
|
||||
item.value = '';
|
||||
} else {
|
||||
item.value = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user