website/web/templates/admin/user_actions.html
Noah Petherbridge 066765d2dc Chat Moderation Rules + Shy Accounts on Chat
* Add chat moderation rules to the website, so admins can apply selective rules
  to problematic users. Available rules are:
  * redcam: user's camera is always NSFW.
  * nobroadcast: user can not broadcast their camera.
  * novideo: user can not broadcast OR watch any video.
  * noimage: user can not share OR see any shared image on chat.
* The page to manage a user's active rules is available on their admin card of
  their profile page. When the user has rules active, a yellow counter is shown
  by the link to manage their rules.
  * Only chat moderator admins have access to the page or can see the yellow
    counter to know whether rules are active.
* "Shy Accounts" are now permitted on the chat room! With some moderation rules
  automatically applied to them: novideo,noimage.
* Update the Shy Account FAQ and messaging on the chat landing page.
* Update the auto-kick from chat behavior regarding shy accounts:
  * They are kicked from chat only when an update to their profile settings will
    transition then FROM a non-shy into a shy account.
  * For example: when saving their profile settings (going private) or when
    editing or deleting a photo (if they will have no more public photos left)
2024-09-19 19:30:02 -07:00

411 lines
24 KiB
HTML

{{define "title"}}Admin Action: {{.User.Username}}{{end}}
{{define "content"}}
<div class="container">
<section class="hero is-link is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">
Admin Action
</h1>
<h2 class="subtitle">On user {{.User.Username}}</h2>
</div>
</div>
</section>
{{$Root := .}}
<div class="block p-4">
<div class="columns is-centered">
<div class="column is-half">
<div class="card" style="width: 100%; max-width: 640px">
<header class="card-header has-background-link">
<p class="card-header-title has-text-light">
{{if eq .Intent "impersonate"}}
<i class="mr-2 fa fa-ghost"></i>
Impersonate User
{{else if eq .Intent "chat.rules"}}
<i class="mr-2 fa fa-gavel"></i>
Chat Moderation Rules
{{else if eq .Intent "essays"}}
<i class="mr-2 fa fa-pencil"></i>
Edit Profile Text
{{else if eq .Intent "ban"}}
<i class="mr-2 fa fa-ban"></i>
Ban User
{{else if eq .Intent "promote"}}
<i class="mr-2 fa fa-peace"></i>
Promote User
{{else if eq .Intent "password"}}
<i class="mr-2 fa fa-lock"></i>
Reset Password
{{else if eq .Intent "delete"}}
<i class="mr-2 fa fa-trash"></i>
Delete User
{{else if eq .Intent "insights"}}
<i class="mr-2 fa fa-search"></i>
Admin Insights
{{end}}
</p>
</header>
<div class="card-content">
<div class="media block">
<div class="media-left">
{{template "avatar-64x64" .User}}
</div>
<div class="media-content">
<p class="title is-4">{{.User.NameOrUsername}}</p>
<p class="subtitle is-6">
<span class="icon"><i class="fa fa-user"></i></span>
<a href="/u/{{.User.Username}}" target="_blank">{{.User.Username}}</a>
</p>
</div>
</div>
<form action="/admin/user-action" method="POST">
{{InputCSRF}}
<input type="hidden" name="intent" value="{{.Intent}}">
<input type="hidden" name="user_id" value="{{.User.ID}}">
{{if eq .Intent "insights"}}
<div class="block content">
<h2>Admin Insights</h2>
<p>
This page gives a peek into the database to glean some insights about a user.
So far, this means taking a look at their block lists: how many people do they
block (and who), and more importantly, how many people are blocking them. It
may be useful information to guage a problematic user, if they are angering or
creeping a lot of people out and ending up on a lot of block lists.
</p>
<h3>Social Change Log</h3>
<p class="is-size-7">
These links go into the admin Change Log viewer to show history of the user's
interactions with parts of the website. Also look for "Change Log" buttons scattered
around various pages (profile, gallery, certification photo view) for history
about the user's data tables.
</p>
<ul>
<li>
<a href="/admin/changelog?table_name=comments&table_id={{.User.ID}}">
Comments written
</a>
</li>
<li>
<a href="/admin/changelog?table_name=threads&table_id={{.User.ID}}">
Forum threads started
</a>
</li>
</ul>
<h3>Block Lists</h3>
<!-- Surface if admin users are blocked -->
{{if .AdminBlockCount}}
<h5 class="has-text-danger">
<i class="fa fa-peace"></i>
Blocked Admins <span class="tag is-danger">{{.AdminBlockCount}}</span>
</h5>
<p>
This user blocks <strong>{{.AdminBlockCount}}</strong> out of {{.AdminBlockTotal}} admin{{Pluralize64 .AdminBlockTotal}} of this website.
</p>
<p>
If this number is unusually high, it can indicate this user may be proactively blocking all the admins in order to be
sneaky or evade moderation.
</p>
{{end}}
<div class="columns">
<div class="column">
<h5 class="has-text-warning">Forward List <span class="tag is-warning">{{len .BlocklistInsights.Blocks}}</span></h5>
<p>
(Users who {{.User.Username}} is blocking)
</p>
<p>
<a href="/admin/changelog?table_name=blocks&about_user_id={{.User.ID}}" class="button is-small">
<span class="icon"><i class="fa fa-clipboard-list"></i></span>
<span>Change Log</span>
</a>
</p>
<ul>
{{range .BlocklistInsights.Blocks}}
<li>
<a href="/u/{{.Username}}">{{.Username}}</a>
{{if .IsAdmin}}
<sup class="has-text-warning fa fa-peace" title="Admin user"></sup>
{{end}}
<small class="has-text-grey" title="{{.Date}}">{{.Date.Format "2006-01-02"}}</small>
</li>
{{end}}
</ul>
</div>
<div class="column">
<h5 class="has-text-warning">Reverse List <span class="tag is-danger">{{len .BlocklistInsights.BlockedBy}}</span></h5>
<p>
(Users who block {{.User.Username}})
</p>
<p>
<a href="/admin/changelog?table_name=blocks&table_id={{.User.ID}}" class="button is-small">
<span class="icon"><i class="fa fa-clipboard-list"></i></span>
<span>Change Log</span>
</a>
</p>
<ul>
{{range .BlocklistInsights.BlockedBy}}
<li>
<a href="/u/{{.Username}}">{{.Username}}</a>
{{if .IsAdmin}}
<sup class="has-text-warning fa fa-peace" title="Admin user"></sup>
{{end}}
<small class="has-text-grey" title="{{.Date}}">{{.Date.Format "2006-01-02"}}</small>
</li>
{{end}}
</ul>
</div>
</div>
</div>
{{else if eq .Intent "chat.rules"}}
<div class="block content">
<p>
You may use this page to add or remove <strong>chat moderation rules</strong> for this user account.
</p>
<p>
Moderation rules are useful to apply restrictions to certain problematic users who habitually break
the site rules. For example: somebody who insists on keeping their camera "blue" (non-explicit) while
always jerking off and resisting the admin request that their camera should be marked "red" can have that
choice taken away from them, and have their camera be forced red at all times when they are broadcasting.
</p>
<p>
<strong>Note:</strong> <a href="/faq#shy-faqs">"Shy Accounts"</a> automatically have the <strong>No webcam privileges</strong>
and <strong>No image sharing privileges</strong> rules applied when they log onto the chat room.
</p>
</div>
{{range .ChatModerationRules}}
<div class="field">
<label class="checkbox">
<input type="checkbox"
name="rules"
value="{{.Value}}"
{{if $Root.User.ProfileFieldIn "chat_moderation_rules" .Value}}checked{{end}}>
{{.Label}}
</label>
<p class="help">
{{.Help}}
</p>
</div>
{{end}}
<div class="field has-text-centered">
<button type="submit" class="button is-success">
Save Changes
</button>
</div>
{{else if eq .Intent "essays"}}
<div class="block content">
<p>
You may use this page to edit the essay texts (e.g. About Me) section of a user's profile page.
The main use cases may be to remove Onlyfans spammy links or that sort of thing, so that you
don't need to fully impersonate their account to do so.
</p>
</div>
<div class="field">
<label class="label" for="about_me">About Me</label>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="about_me" id="about_me">{{.User.GetProfileField "about_me"}}</textarea>
</div>
<div class="field">
<label class="label" for="interests">My Interests</label>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="interests" id="interests">{{.User.GetProfileField "interests"}}</textarea>
</div>
<div class="field">
<label class="label" for="music_movies">Music/Bands/Movies</label>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="music_movies" id="music_movies">{{.User.GetProfileField "music_movies"}}</textarea>
</div>
<div class="field has-text-centered">
<button type="submit" class="button is-success">
Save Changes
</button>
</div>
{{else if eq .Intent "impersonate"}}
<div class="block content">
<h3>With great power...</h3>
<p>
By <strong>impersonating</strong> this user, you will be considered as "logged in"
to their account and have access to their messages, profile, photos and settings.
</p>
<p>
Please respect user privacy and only impersonate an account as needed to diagnose
a customer support issue or similar.
</p>
<p>
<strong class="has-text-danger">
This event is logged and will be noticed.
</strong>
Write an explanation below why you are impersonating this user. It will
be e-mailed to the admin mailing list and trigger an admin notification
and be logged as a <a href="/admin/feedback?intent=report">Report</a> to
the admin dashboard. Reports can be acknowledged, but not deleted.
</p>
<p>
Good reasons may include:
<ul>
<li>
I need to diagnose a bug report given by one of our users
(briefly describe what the bug is; e.g. user saw a database error
at the top of a page).
</li>
<li>
A user has reported a Direct Message conversation and I need to
take a look at the context. (There is no other way to read user DMs)
</li>
</ul>
</p>
</div>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="reason"
placeholder="Reason"
required></textarea>
<div class="field has-text-centered">
<button type="submit" class="button is-success">
Log in as {{.User.Username}}
</button>
</div>
{{else if eq .Intent "ban"}}
<div class="block content">
<p>
This user is currently:
{{if eq .User.Status "active"}}
<strong class="has-text-success">Active (not banned)</strong>
{{else if eq .User.Status "disabled"}}
<strong class="has-text-warning">Disabled</strong>
{{else if eq .User.Status "banned"}}
<strong class="has-text-danger">Banned</strong>
{{end}}
</p>
<p>
Select a new status for them below:
</p>
</div>
<div class="field has-text-centered">
<button type="submit" name="status" value="active" class="button is-success">
Active
</button>
<button type="submit" name="status" value="banned" class="button is-danger">
Banned
</button>
</div>
{{else if eq .Intent "promote"}}
<div class="block content">
<p>
This user is currently:
{{if .User.IsAdmin}}
<strong class="has-text-danger">Admin</strong>
{{else}}
<strong class="has-text-success">NOT Admin</strong>
{{end}}
</p>
<p>
Select a new status for them below:
</p>
</div>
<div class="field has-text-centered">
<button type="submit" name="action" value="promote" class="button is-success">
Make Admin
</button>
<button type="submit" name="action" value="demote" class="button is-danger">
Remove Admin
</button>
</div>
{{else if eq .Intent "password"}}
<p class="block">
This page allows you to reset a user's password on their behalf. For example, if
they have forgotten their password and aren't receiving the e-mail reset link and
they reached out for manual assistance.
</p>
<div class="field">
<label class="label" for="password">New password:</label>
<input type="text" class="input" name="password" id="password" placeholder="Password">
<a href="#" id="random-password" class="has-text-warning is-size-7">
<i class="fa fa-refresh mr-2"></i> Random password
</a>
</div>
<div class="field has-text-centered">
<button type="submit" name="action" value="password" class="button is-danger">
Update Password
</button>
<a href="/u/{{.User.Username}}" class="button is-success">Cancel</a>
</div>
<script>
window.addEventListener("DOMContentLoaded", (event) => {
// Password randomization.
const $password = document.querySelector("#password"),
$randomize = document.querySelector("#random-password");
alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789";
$randomize.addEventListener("click", (e) => {
e.preventDefault();
let password = "";
for (let i = 0; i < 16; i++) {
password += alphabet[parseInt(Math.random() * alphabet.length)];
}
$password.value = password;
});
});
</script>
{{else if eq .Intent "delete"}}
<div class="block content">
<p>
Click the button below to <strong>deep delete</strong> this user account.
</p>
</div>
<div class="field has-text-centered">
<button type="submit" class="button is-danger">
Delete User Account
</button>
</div>
{{end}}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{{end}}