167 lines
5.6 KiB
Plaintext
167 lines
5.6 KiB
Plaintext
<%- include("partials/shared_header", { title: "View User - OAI Reverse Proxy Admin" }) %>
|
|
<h1>View User</h1>
|
|
|
|
<table class="striped">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">Key</th>
|
|
<th scope="col" colspan="2">Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<th scope="row">Token</th>
|
|
<td colspan="2"><%- user.token %></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Nickname</th>
|
|
<td><%- user.nickname ?? "none" %></td>
|
|
<td class="actions">
|
|
<a title="Edit" id="edit-nickname" href="#" data-field="nickname" data-token="<%= user.token %>">✏️</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Type</th>
|
|
<td><%- user.type %></td>
|
|
<td class="actions">
|
|
<a title="Edit" id="edit-type" href="#" data-field="type" data-token="<%= user.token %>">✏️</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Prompts</th>
|
|
<td colspan="2"><%- user.promptCount %></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Created At</th>
|
|
<td colspan="2"><%- user.createdAt %></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Last Used At</th>
|
|
<td colspan="2"><%- user.lastUsedAt || "never" %></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Disabled At</th>
|
|
<td><%- user.disabledAt %></td>
|
|
<td class="actions">
|
|
<% if (user.disabledAt) { %>
|
|
<a title="Unban" href="#" class="unban" data-token="<%= user.token %>">🔄️</a>
|
|
<% } else { %>
|
|
<a title="Ban" href="#" class="ban" data-token="<%= user.token %>">🚫</a>
|
|
<% } %>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Disabled Reason</th>
|
|
<td><%- user.disabledReason %></td>
|
|
<% if (user.disabledAt) { %>
|
|
<td class="actions">
|
|
<a title="Edit" id="edit-disabledReason" href="#" data-field="disabledReason" data-token="<%= user.token %>"
|
|
>✏️</a
|
|
>
|
|
</td>
|
|
<% } %>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">IP Address Limit</th>
|
|
<td><%- (user.maxIps ?? maxIps) || "Unlimited" %></td>
|
|
<td class="actions">
|
|
<a title="Edit" id="edit-maxIps" href="#" data-field="maxIps" data-token="<%= user.token %>">✏️</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">IPs</th>
|
|
<td colspan="2"><%- include("partials/shared_user_ip_list", { user, shouldRedact: false }) %></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">
|
|
Admin Note <span title="Unlike nickname, this is not visible to or editable by the user">🔒</span>
|
|
</th>
|
|
<td><%- user.adminNote ?? "none" %></td>
|
|
<td class="actions">
|
|
<a title="Edit" id="edit-adminNote" href="#" data-field="adminNote" data-token="<%= user.token %>">✏️</a>
|
|
</td>
|
|
</tr>
|
|
<% if (user.type === "temporary") { %>
|
|
<tr>
|
|
<th scope="row">Expires At</th>
|
|
<td colspan="2"><%- user.expiresAt %></td>
|
|
</tr>
|
|
<% } %>
|
|
<% if (user.meta) { %>
|
|
<tr>
|
|
<th scope="row">Meta</th>
|
|
<td colspan="2"><%- JSON.stringify(user.meta) %></td>
|
|
</tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
|
|
<form style="display: none" id="current-values">
|
|
<input type="hidden" name="token" value="<%- user.token %>" />
|
|
<% ["nickname", "type", "disabledAt", "disabledReason", "maxIps", "adminNote"].forEach(function (key) { %>
|
|
<input type="hidden" name="<%- key %>" value="<%- user[key] %>" />
|
|
<% }); %>
|
|
<!-- tokenRefresh_ keys are dynamically generated -->
|
|
<% Object.entries(quota).forEach(([family]) => { %>
|
|
<input type="hidden" name="tokenRefresh_<%- family %>" value="<%- user.tokenRefresh[family] || quota[family] %>" />
|
|
<% }); %>
|
|
</form>
|
|
|
|
<h3>Quota Information</h3>
|
|
<% if (quotasEnabled) { %>
|
|
<form action="/admin/manage/refresh-user-quota" method="POST">
|
|
<input type="hidden" name="token" value="<%- user.token %>" />
|
|
<input type="hidden" name="_csrf" value="<%- csrfToken %>" />
|
|
<button type="submit" class="btn btn-primary">Refresh Quotas for User</button>
|
|
</form>
|
|
<% } %>
|
|
<%- include("partials/shared_quota-info", { quota, user, showRefreshEdit: true }) %>
|
|
|
|
<p><a href="/admin/manage/list-users">Back to User List</a></p>
|
|
|
|
<script>
|
|
document.querySelectorAll("td.actions a[data-field]").forEach(function (a) {
|
|
a.addEventListener("click", function (e) {
|
|
e.preventDefault();
|
|
const token = a.dataset.token;
|
|
const field = a.dataset.field;
|
|
const existingValue = document.querySelector(`#current-values input[name=${field}]`).value;
|
|
|
|
let value = prompt(`Enter new value for '${field}':`, existingValue);
|
|
if (value !== null) {
|
|
if (value === "") {
|
|
value = null;
|
|
}
|
|
|
|
const payload = { _csrf: document.querySelector("meta[name=csrf-token]").getAttribute("content") };
|
|
if (field.startsWith("tokenRefresh_")) {
|
|
const family = field.slice("tokenRefresh_".length);
|
|
payload.tokenRefresh = { [family]: Number(value) };
|
|
} else {
|
|
payload[field] = value;
|
|
}
|
|
|
|
fetch(`/admin/manage/edit-user/${token}`, {
|
|
method: "POST",
|
|
credentials: "same-origin",
|
|
body: JSON.stringify(payload),
|
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
})
|
|
.then((res) => Promise.all([res.ok, res.json()]))
|
|
.then(([ok, json]) => {
|
|
const url = new URL(window.location.href);
|
|
const params = new URLSearchParams();
|
|
if (!ok) {
|
|
alert(`Failed to edit user: ${json.message}`);
|
|
}
|
|
url.search = params.toString();
|
|
window.location.assign(url);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<%- include("partials/admin-ban-xhr-script") %>
|
|
<%- include("partials/admin-footer") %>
|