Skip to content

Commit

Permalink
Merge pull request #34 from epochtalk/admin-panel
Browse files Browse the repository at this point in the history
Admin panel
  • Loading branch information
akinsey committed Aug 8, 2022
2 parents fad8c95 + f5ff6ba commit 7836a69
Show file tree
Hide file tree
Showing 23 changed files with 5,078 additions and 1,285 deletions.
33 changes: 33 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const postsApi = {
lock: postId => $http(`/api/posts/${postId}/lock`, { method: 'POST'}),
unlock: postId => $http(`/api/posts/${postId}/unlock`, { method: 'POST'}),
byThread: params => $http('/api/posts', { params }),
find: id => $http(`/api/posts/${id}`),
byUser: params => $http(`/api/posts/user/${params.username}`, { params }),
startedByUser: params => $http(`/api/posts/user/${params.username}/started`, { params }),
slugToPostId: slug => $http(`/api/posts/${slug}/id`),
Expand Down Expand Up @@ -253,6 +254,14 @@ export const banApi = {
export const adminApi = {
configurations: () => $http('/api/configurations'),
updateConfigurations: data => $http('/api/configurations', { method: 'POST', data }),
invitations: {
all: query => $http('/api/invites', { params: query }),
resend: data => $http('/api/invites/resend', { method: 'POST', data }),
remove: data => $http('/api/invites/remove', { method: 'POST', data })
},
logs: {
page: params => $http('/api/admin/modlog', { params })
},
roles: {
all: () => $http('/api/admin/roles/all'),
users: (id, query) => $http(`/api/admin/roles/${id}/users`, { params: query }),
Expand All @@ -263,6 +272,13 @@ export const adminApi = {
update: data => $http(`/api/admin/roles/update`, { method: 'PUT', data }),
delete: id => $http(`/api/admin/roles/remove/${id}`, { method: 'DELETE' })
},
bans: {
pageBannedAddresses: params => $http('/api/ban/addresses', { params }),
addBannedAddresses: data => $http('/api/ban/addresses', { method: 'POST', data}),
editBannedAddress: data => $http('/api/ban/addresses', { method: 'PUT', data}),
deleteBannedAddress: query => $http('/api/ban/addresses', { method: 'DELETE', params: query }),
pageByBannedBoards: params => $http('/api/users/banned', { params })
},
trust: {
getDefaultTrustList: () => $http('/api/admin/trustlist'),
editDefaultTrustList: data => $http('/api/admin/trustlist', { method: 'POST', data }),
Expand All @@ -288,6 +304,23 @@ export const adminApi = {
addRule: data => $http('/api/automoderation/rules', { method: 'POST', data }),
updateRule: data => $http(`/api/automoderation/rules/${data.id}`, { method: 'PUT', data }),
deleteRule: data => $http(`/api/automoderation/rules/${data.id}`, { method: 'DELETE' })
},
reports: {
pageMessageReports: params => $http('/api/reports/messages', { params }),
updateMessageReport: data => $http('/api/reports/messages', { method: 'PUT', data}),
pageUserReports: params => $http('/api/reports/users', { params }),
updateUserReport: data => $http('/api/reports/users', { method: 'PUT', data}),
pagePostReports: params => $http('/api/reports/posts', { params }),
updatePostReport: data => $http('/api/reports/posts', { method: 'PUT', data}),
pageMessageNotes: (id, params) => $http(`/api/reports/messagenotes/${id}`, { params }),
pageUserNotes: (id, params) => $http(`/api/reports/usernotes/${id}`, { params }),
pagePostNotes: (id, params) => $http(`/api/reports/postnotes/${id}`, { params }),
updateMessageNote: data => $http('/api/reports/messagenotes', { method: 'PUT', data}),
updateUserNote: data => $http('/api/reports/usernotes', { method: 'PUT', data}),
updatePostNote: data => $http('/api/reports/postnotes', { method: 'PUT', data}),
createMessageNote: data => $http('/api/reports/messagenotes', { method: 'POST', data}),
createUserNote: data => $http('/api/reports/usernotes', { method: 'POST', data}),
createPostNote: data => $http('/api/reports/postnotes', { method: 'POST', data})
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/components/layout/AdminNavigation.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<ul class="menu-left">
<li v-if="permUtils.hasPermission('adminAccess.settings')">
<router-link :to="{ path: '/admin/settings' }" class="menu-btn" :class="{ 'menu-btn-selected': checkActive('/admin/settings') }">
<router-link :to="{ path: '/admin/settings' }" class="menu-btn" :class="{ 'menu-btn-selected': checkActive('/admin/settings') || checkActivePath('/admin') }">
<i class="fa fa-cogs"></i><span class="hide-mobile">&nbsp;&nbsp;&nbsp;Settings</span>
</router-link>
</li>
Expand Down Expand Up @@ -32,10 +32,11 @@ export default {
const $auth = inject(AuthStore)
const checkActive = basePath => $route.path.indexOf(basePath) === 0
const checkActivePath = basePath => $route.path === basePath
const permUtils = $auth.permissionUtils
return { checkActive, permUtils }
return { checkActive, checkActivePath, permUtils }
}
}
</script>
13 changes: 9 additions & 4 deletions src/components/layout/AdminSubNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<dl class="tabs">
<span v-for="link in nav" :key="link.routeName">
<dd class="no-select" :class="{'active': link.active}" v-if="link.permission">
<router-link :to="{ name: link.routeName }" v-html="link.title" />
<router-link :to="{ name: link.routeName, query: link.query }" v-html="link.title" />
</dd>
</span>
</dl>
Expand Down Expand Up @@ -41,11 +41,12 @@ export default {
const checkSaveEnabled = () => {
let routeName = $route.name
let noSaveRoutes = ['UserManagement']
let noSaveRoutes = ['UserManagement', 'RoleManagement', 'BannedAddressManagement', 'InvitationManagement', 'LogModeration', 'UserModeration', 'PostModeration', 'MessageModeration', 'BoardBanModeration', 'UserModeration.ProfilePreview.UserPosts', 'UserModeration.ProfilePreview']
return noSaveRoutes.indexOf(routeName) < 0
}
const checkActive = n => n === $route.name
const checkActive = n => n === $route.name || $route.name.indexOf(n) === 0
const permUtils = $auth.permissionUtils
const nav = {
Expand Down Expand Up @@ -111,18 +112,21 @@ export default {
{
title: 'Users',
routeName: 'UserModeration',
query: { filter: 'Pending' },
permission: permUtils.hasPermission('modAccess.users'),
active: computed(() => checkActive('UserModeration'))
active: computed(() => checkActive('UserModeration') || checkActive('ProfilePreview'))
},
{
title: 'Posts',
routeName: 'PostModeration',
query: { filter: 'Pending' },
permission: permUtils.hasPermission('modAccess.posts'),
active: computed(() => checkActive('PostModeration'))
},
{
title: 'Messages',
routeName: 'MessageModeration',
query: { filter: 'Pending' },
permission: permUtils.hasPermission('modAccess.messages'),
active: computed(() => checkActive('MessageModeration'))
},
Expand Down Expand Up @@ -162,6 +166,7 @@ export default {
display: flex;
background: $base-background-color;
border-bottom: 1px solid $breadcrumbs-border-color;
.active { pointer-events: none; }
.tabs {
flex: 1 0 50%;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ export default {
watch(() => props.post, p => {
if (p) {
if (p.body.length) v.editMode = true
if (p.position !== 1) p.title = 'RE: ' + props.thread.title
if (p.position !== 1) p.title = props.thread ? 'RE: ' + props.thread.title : p.title
nextTick(() => v.posting.post = p)
}
})
Expand Down
21 changes: 9 additions & 12 deletions src/components/layout/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
</svg>
</div>
<ul>
<li @click="showMobileMenu = false" v-if="permissionUtils.hasPermission('adminAccess')">
<router-link :to="{ name: 'GeneralSettings' }">
<i class="fa fa-cogs" aria-hidden="true"></i>Admin Panel
<li @click="showMobileMenu = false" v-if="permissionUtils.hasPermission('adminAccess') || permissionUtils.hasPermission('modAccess')">
<router-link :to="{ path: '/admin' }">
<i class="fa fa-cogs" aria-hidden="true"></i>
<span v-if="Object.keys(permissionUtils.hasPermission('adminAccess')).length">Admin Panel</span>
<span v-if="Object.keys(permissionUtils.hasPermission('modAccess')).length && !Object.keys(permissionUtils.hasPermission('adminAccess')).length">Mod Panel</span>
</router-link>
</li>
<li @click="showMobileMenu = false" v-if="permissionUtils.hasPermission('modAccess') && !permissionUtils.hasPermission('adminAccess')">
<a href="#"><i class="fa fa-cogs" aria-hidden="true"></i>Mod Panel</a>
</li>
<li @click="showMobileMenu = false" v-if="isPatroller()">
<router-link :to="{ name: 'Patrol' }"><i class="fa fa-binoculars" aria-hidden="true"></i>Patrol</router-link>
</li>
Expand Down Expand Up @@ -187,14 +186,12 @@
</router-link>
</div>
<ul id="user-dropdown">
<li v-if="permissionUtils.hasPermission('adminAccess')">
<router-link :to="{ name: 'GeneralSettings' }">
Admin Panel
<li v-if="permissionUtils.hasPermission('adminAccess') || permissionUtils.hasPermission('modAccess')">
<router-link :to="{ path: '/admin' }">
<span v-if="Object.keys(permissionUtils.hasPermission('adminAccess')).length">Admin Panel</span>
<span v-if="Object.keys(permissionUtils.hasPermission('modAccess')).length && !Object.keys(permissionUtils.hasPermission('adminAccess')).length">Mod Panel</span>
</router-link>
</li>
<li v-if="permissionUtils.hasPermission('modAccess') && !permissionUtils.hasPermission('adminAccess')">
<a href="#">Mod Panel</a>
</li>
<li v-if="isPatroller()">
<router-link :to="{ name: 'Patrol' }">Patrol</router-link>
</li>
Expand Down
6 changes: 5 additions & 1 deletion src/components/layout/Modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export default {
// Show for first time
if (props.show && !open.value) {
open.value = true
nextTick(() => { if (props.focusInput) props.focusInput.focus() })
nextTick(() => {
if (props.focusInput?.focusSearch) props.focusInput.focusSearch()
else if (props.focusInput?.length) props.focusInput[0].focus()
else if (props.focusInput) props.focusInput.focus()
})
}
// Hide for first time, emit event to parent
else if (props.show && open.value) { emit('close') }
Expand Down
168 changes: 168 additions & 0 deletions src/components/modals/admin/management/BannedAddressManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<template>
<modal :name="$options.name" :show="show" @close="close()" :focusInput="focusInput">
<template v-slot:header>
<span v-if="banAddress">Manually Ban Addresses
<span class="info-tooltip pointer" data-balloon="Allows admins to manually ban users from registering from particular hostnames/ip addresses. Weight is used when calculating how malicious a user trying to register is. Decay will allow users to register from that particular hostname/ip after an lengthy amount of time, assuming there were no re-offences causing the same address to be banned again" data-balloon-pos="down" data-balloon-length="large" data-balloon-break><i class="fa fa-info-circle"></i></span>
</span>
<span v-if="editAddress">Edit Address</span>
<span v-if="deleteAddress">Delete Address</span>
</template>

<template v-slot:body>
<form class="css-form">
<div v-if="banAddress">
<table class="striped ban-addresses" width="100%">
<thead>
<tr>
<th>Type</th>
<th>Address</th>
<th width="5%">Decays</th>
<th width="20%">Weight</th>
</tr>
</thead>
<tr v-for="(addr, index) in addressesToBan" :key="addr">
<td>
<select ref="focusInput" class="type" v-model="addr.typeIp" @change="addr.typeIp ? addr.hostname = undefined : addr.ip = undefined">
<option :value="true" selected="true">IP Address</option>
<option :value="false">Hostname</option>
</select>
</td>
<td v-if="addr.typeIp">
<input v-model="addr.ip" type="text" class="address" placeholder="IP Address to ban" />
</td>
<td v-if="!addr.typeIp">
<input v-model="addr.hostname" type="text" class="address" placeholder="Hostname to ban" />
</td>
<td class="decay">
<input v-model="addr.decay" type="checkbox" :checked="true" />
</td>
<td>
<input v-model="addr.weight" type="number" min="0" class="weight" placeholder="Weight" @keydown="!$event.shiftKey && ($event.which === 9 || $event.which === 13) && addressesToBan.length === (index + 1) && addressesToBan.push({ typeIp:true, weight: 50, decay: true })" />
</td>
</tr>
<tfoot>
<tr>
<td colspan="4"><a class="right" @click="addressesToBan.push({ typeIp:true, weight: 50, decay: true })" href="#"><i class="fa fa-plus"></i>&nbsp;Add another address</a></td>
</tr>
</tfoot>
</table>
</div>
<div v-if="editAddress">
<p>Edit weight and decay properties of banned address: <strong v-html="selectedAddress.hostname || selectedAddress.ip"></strong></p>
<label for="addrWeight"><strong>Weight</strong></label>
<input type="number" id="addrWeight" name="addrWeight" v-model="selectedAddress.weight" ref="focusInput" required />
<label for="addrDecay"><strong>Decays</strong></label>
<input type="radio" name="addrDecay" id="decayYes" :value="true" v-model="selectedAddress.decay" required />
<label for="decayYes">Yes</label>
<input type="radio" name="addrDecay" id="decayNo" :value="false" v-model="selectedAddress.decay" required />
<label for="decayNo">No</label>
<br>
<br>
</div>
<div v-if="deleteAddress">
<p ref="focusInput">
Are you sure you want to delete the address <strong v-html="selectedAddress.hostname || selectedAddress.ip"></strong>?
</p>
<br>
</div>

<div class="col">
<div>
<button class="fill-row" @click.prevent="modify()" :disabled="requestSubmitted || (banAddress && !formValid)" v-html="saveRuleBtnLabel"></button>
</div>
<div>
<button class="fill-row negative" @click.prevent="close()" :disabled="requestSubmitted">Cancel</button>
</div>
</div>
</form>
</template>
</modal>
</template>

<script>
import Modal from '@/components/layout/Modal.vue'
import { reactive, toRefs, watch, inject } from 'vue'
import { cloneDeep, debounce } from 'lodash'
import { adminApi } from '@/api'
import { basicIpRegex, hostnameRegex } from '@/composables/utils/globalRegex'
export default {
name: 'banned-address-manager-modal',
props: ['show', 'banAddress', 'editAddress', 'deleteAddress', 'selected'],
emits: ['close', 'success'],
components: { Modal },
setup(props, { emit }) {
/* Template Methods */
const resetForm = () => {
v.requestSubmitted = false
v.addressesToBan = [{ typeIp:true, weight: 50, decay: true }]
v.saveRuleBtnLabel = props.deleteAddress ? 'Confirm Delete' : 'Save'
}
const modify = () => {
v.requestSubmitted = true
let promise, successMsg, errorMsg
if (props.banAddress) {
promise = adminApi.bans.addBannedAddresses(v.addressesToBan.filter(addr => {
if ((addr.ip || addr.hostname) && addr.weight) return addr
}))
successMsg = 'Successfully banned addresses!'
errorMsg = 'There was an error banning addresses'
}
else if (props.editAddress) {
promise = adminApi.bans.editBannedAddress(v.selectedAddress)
successMsg = `Successfully edited banned addresss ${v.selectedAddress.hostname || v.selectedAddress.ip}!`
errorMsg = `There was an error editing banned address: ${v.selectedAddress.hostname || v.selectedAddress.ip}`
}
else if (props.deleteAddress) {
promise = adminApi.bans.deleteBannedAddress(v.selectedAddress)
successMsg = `Successfully deleted banned addresss ${v.selectedAddress.hostname || v.selectedAddress.ip}!`
errorMsg = `There was an error deleting banned address: ${v.selectedAddress.hostname || v.selectedAddress.ip}`
}
promise.then(() => {
$alertStore.success(successMsg)
emit('success')
close()
})
.catch(() => $alertStore.error(errorMsg))
}
const close = () => {
resetForm()
emit('close')
}
const $alertStore = inject('$alertStore')
/* Template Data */
const v = reactive({
focusInput: null,
formValid: false,
selectedAddress: {},
addressesToBan: [{ typeIp:true, weight: 50, decay: true }],
saveRuleBtnLabel: props.deleteAddress ? 'Confirm Delete' : 'Save',
requestSubmitted: false,
})
watch(() => props.show, () => {
v.saveRuleBtnLabel = props.deleteAddress ? 'Confirm Delete' : 'Save'
v.selectedAddress = cloneDeep(props.selected)
})
watch(() => v.addressesToBan, debounce(async () => {
v.formValid = true
v.addressesToBan.forEach(addr => v.formValid = v.formValid && (addr.typeIp && basicIpRegex.test(addr.ip) || !addr.typeIp && hostnameRegex.test(addr.hostname) && addr.weight))
}), { deep: true })
return { ...toRefs(v), modify, close }
}
}
</script>

<style lang="scss" scoped>
.ban-addresses {
input, select { margin-bottom: 0; }
td.decay { padding-top: 1rem; }
}
</style>
Loading

0 comments on commit 7836a69

Please sign in to comment.