diff --git a/resources/assets/components/admin/AdminReports.vue b/resources/assets/components/admin/AdminReports.vue index e43f8af62..20fa386a2 100644 --- a/resources/assets/components/admin/AdminReports.vue +++ b/resources/assets/components/admin/AdminReports.vue @@ -147,15 +147,10 @@ @@ -418,6 +413,114 @@ Next + +
+
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
IDUsernameModerationCommentCreated
+ + +

+ {{ truncateText(report.profile.name, 40) }} +

+

+ {{ truncateText(report.profile.username, 40) }} +

+
+

+
+

{{ truncateText(report.note, 140) }}

+
+ + {{ timeAgo(report.created_at) }} + +
+ +
+
+
+ + + + +
+
+
+ +
+ + +
+
@@ -756,6 +859,165 @@ v-on:close="handleCloseRemoteReportModal()" v-on:refresh="refreshRemoteReports()" /> + + @@ -792,13 +1054,34 @@ viewingSpamReportLoading: false, remoteReportsLoaded: false, showRemoteReportModal: undefined, - remoteReportModalModel: {} + remoteReportModalModel: {}, + moderatedProfiles: [], + moderatedProfilesPagination: {}, + moderatedProfilesSearchInput: undefined, + modModalData: undefined, + modModalModel: {}, } }, mounted() { let u = new URLSearchParams(window.location.search); - if(u.has('tab') && u.has('id') && u.get('tab') === 'autospam') { + if(u.has('tab') && u.get('tab') === 'moderated-profiles' && u.has('action') && u.has('id') && u.get('action') === 'view') { + this.tabIndex = 4; + this.fetchModeratedAccounts(); + this.fetchModeratedProfile(u.get('id')); + } else if(u.has('tab') && u.get('tab') === 'autospam' && !u.has('id')) { + this.tabIndex = 2; + this.fetchStats(null, '/i/admin/api/reports/spam/all'); + } else if(u.has('tab') && u.get('tab') === 'closed') { + this.tabIndex = 1; + this.fetchStats('/i/admin/api/reports/all?filter=closed') + } else if(u.has('tab') && u.get('tab') === 'closed') { + this.tabIndex = 3; + this.fetchStats('/i/admin/api/reports/all?filter=remote') + } else if(u.has('tab') && u.get('tab') === 'moderated-profiles') { + this.tabIndex = 4; + this.fetchModeratedAccounts(); + } else if(u.has('tab') && u.has('id') && u.get('tab') === 'autospam') { this.fetchStats(null, '/i/admin/api/reports/spam/all'); this.fetchSpamReport(u.get('id')); } else if(u.has('tab') && u.has('id') && u.get('tab') === 'report') { @@ -819,21 +1102,29 @@ switch(idx) { case 0: this.fetchStats('/i/admin/api/reports/all'); + window.history.pushState(null, null, '/i/admin/reports'); break; case 1: this.fetchStats('/i/admin/api/reports/all?filter=closed') + window.history.pushState(null, null, '/i/admin/reports?tab=closed'); break; case 2: this.fetchStats(null, '/i/admin/api/reports/spam/all'); + window.history.pushState(null, null, '/i/admin/reports?tab=autospam'); break; case 3: this.fetchRemoteReports(); + window.history.pushState(null, null, '/i/admin/reports?tab=remote'); + break; + + case 4: + this.fetchModeratedAccounts(); + window.history.pushState(null, null, '/i/admin/reports?tab=moderated-profiles'); break; } - window.history.pushState(null, null, '/i/admin/reports'); this.tabIndex = idx; }, @@ -891,6 +1182,27 @@ }); }, + fetchModeratedAccounts(apiUrl = '/i/admin/api/reports/moderated-profiles') { + axios.get(apiUrl) + .then(res => { + this.moderatedProfiles = res.data.data; + this.moderatedProfilesPagination = { + prev: res.data.links.prev, + next: res.data.links.next + }; + }) + .finally(() => { + this.loaded = true; + $('[data-toggle="tooltip"]').tooltip() + }) + }, + + paginateModeratedAccounts(dir) { + event.currentTarget.blur(); + let url = dir == 'next' ? this.moderatedProfilesPagination.next : this.moderatedProfilesPagination.prev; + this.fetchModeratedAccounts(url); + }, + fetchReports(url = '/i/admin/api/reports/all') { axios.get(url) .then(res => { @@ -1149,7 +1461,226 @@ this.fetchStats(); window.history.pushState(null, null, '/i/admin/reports'); }) + }, + + truncateText(text, maxLength, appendEllipsis = true) { + if(!text || !text.length) { + return + } + + if (text.length <= maxLength) { + return text; + } + + const truncated = text.slice(0, maxLength).trim(); + return appendEllipsis ? truncated + '...' : truncated; + }, + + getModerationLabels(acct) { + if(acct.is_banned) { + return `Banned` + } + + let labels = []; + + if(acct.is_banned) labels.push('Banned') + if(acct.is_noautolink) labels.push('No Autolink') + if(acct.is_nodms) labels.push('No DMS') + if(acct.is_notrending) labels.push('No Trending') + if(acct.is_nsfw) labels.push('NSFW') + if(acct.is_unlisted) labels.push('Unlisted') + + return labels.map((item, index) => { + const colorClass = item === 'Banned' ? 'danger' : 'primary'; + return `${item}`; + }).join(' '); + }, + + handleModeratedProfileSearch(event) { + event.currentTarget.blur() + let url = `/i/admin/api/reports/moderated-profiles?search=${this.moderatedProfilesSearchInput}` + this.fetchModeratedAccounts(url) + }, + + clearModeratedProfileSearch() { + this.moderatedProfilesSearchInput = undefined; + this.fetchModeratedAccounts(); + }, + + openModeratedProfileModal(report) { + this.modModalData = report; + this.modModalModel = { + is_banned: report.is_banned, + is_noautolink: report.is_noautolink, + is_nodms: report.is_nodms, + is_notrending: report.is_notrending, + is_nsfw: report.is_nsfw, + is_unlisted: report.is_unlisted, + } + $(this.$refs.moderatedProfileModal).modal('show'); + window.history.pushState(null, null, `/i/admin/reports?tab=moderated-profiles&action=view&id=${report.id}`) + }, + + handleModProfileModalUpdate() { + axios.post( + '/i/admin/api/reports/moderated-profiles/update', + {...this.modModalData, ...this.modModalModel} + ).then(res => { + window.history.pushState(null, null, `/i/admin/reports?tab=moderated-profiles`) + window.location.reload(); + }).catch(error => { + let errorMessage = 'An error occurred'; + if (error.response) { + errorMessage = `Error ${error.response.status}: ${error.response.data.error || error.response.data.message || error.response.statusText}`; + } else if (error.request) { + errorMessage = 'No response received from server'; + } else { + errorMessage = error.message; + } + swal('Error', errorMessage, 'error') + }).finally(() => { + $(this.$refs.moderatedProfileModal).modal('hide'); + }) + }, + + handleModProfileModalDelete() { + swal({ + title: 'Confirm Delete', + text: 'Are you sure you want to delete this moderated profile ruleset?', + buttons: { + cancel: "Cancel", + danger: { + text: "Delete", + value: 'delete', + } + } + }).then((val) => { + if(val === 'delete') { + axios.post('/i/admin/api/reports/moderated-profiles/delete', { id: this.modModalData.id}) + .then(res => { + window.history.pushState(null, null, '/i/admin/reports?tab=moderated-profiles'); + window.location.reload(); + }) + } + $(this.$refs.moderatedProfileModal).modal('hide'); + swal.close() + }) + }, + + fetchModeratedProfile(id) { + axios.get(`/i/admin/api/reports/moderated-profiles/show?id=${id}`) + .then(res => { + this.modModalData = res.data.data; + let report = res.data.data; + + this.modModalModel = { + is_banned: report.is_banned, + is_noautolink: report.is_noautolink, + is_nodms: report.is_nodms, + is_notrending: report.is_notrending, + is_nsfw: report.is_nsfw, + is_unlisted: report.is_unlisted, + } + + $(this.$refs.moderatedProfileModal).modal('show'); + }).catch(err => { + window.history.pushState(null, null, '/i/admin/reports?tab=moderated-profiles'); + swal('Error', 'Invalid moderated profile id!', 'error'); + }) + }, + + addModeratedProfile() { + swal({ + text: 'Enter profile URL (ie: https://mastodon.social/@Mastodon)', + content: "input", + button: { + text: "Add", + closeModal: false, + }, + }).then(val => { + if (!val) throw null; + + if(val.startsWith('@')) { + swal('Error', 'Invalid URL, webfinger is not supported yet.', 'error'); + throw null; + } + + if(!val.startsWith('http')) { + swal('Error', 'Invalid URL', 'error'); + throw null; + } + + if(val.indexOf('.') === -1) { + swal('Error', 'Invalid URL', 'error'); + throw null; + } + + let params = { + url: val + } + + return axios.post('/i/admin/api/reports/moderated-profiles/create', params); + }).then(json => { + if(json && json.data && json.data?.id) { + window.location.href = `/i/admin/reports?tab=moderated-profiles&action=view&id=${json.data?.id}` + return; + } + swal.stopLoading(); + swal.close(); + }).catch(err => { + if (err) { + if(err?.response?.data?.error) { + swal("Error", err?.response?.data?.error, "error"); + } else { + swal("Error", "Something went wrong!", "error"); + } + } else { + swal.stopLoading(); + swal.close(); + } + }); + }, + + closeModeratedProfileModal() { + window.history.pushState(null, null, '/i/admin/reports?tab=moderated-profiles'); + }, + + exportModeratedProfiles() { + axios.get('/i/admin/api/reports/moderated-profiles/export', { + responseType: "blob" + }) + .then(res => { + let host = new URL(window.location.href) + let date = new Date(); + let dateStamp = `${date.getMonth()}-${date.getDate()}-${date.getFullYear()}-${Date.now()}`; + let filename = host.host + '-moderated-profiles-' + dateStamp + '.json'; + let el = document.createElement('a'); + el.setAttribute('download', filename) + const href = URL.createObjectURL(res.data); + el.href = href; + el.setAttribute('target', '_blank'); + el.click(); + + swal( + 'Success!', + 'You have successfully exported the moderated profile backup.', + 'success' + ) + }) } } } + +