405 lines
12 KiB
JavaScript
405 lines
12 KiB
JavaScript
import { makeRecoveredPage } from "../../runtime/makeRecoveredPage.js";
|
|
import UserManagePageView from "./UserManagePage.jsx";
|
|
|
|
export default makeRecoveredPage({
|
|
title: "User Manage",
|
|
routePath: "/user/manage",
|
|
moduleId: "u8t",
|
|
featureKey: "user.manage",
|
|
component: UserManagePageView,
|
|
translationNamespaces: ["user", "common", "order", "traffic"],
|
|
summary:
|
|
"The user management page is one of the heaviest admin pages in the bundle: it combines a server-side data table, advanced filter sheet, batch actions, email sending, user generation, traffic history dialog, inline order assignment, reset-secret/reset-traffic flows, and a full edit side sheet.",
|
|
api: [
|
|
{
|
|
name: "user.fetch",
|
|
method: "POST",
|
|
path: "{secure}/user/fetch",
|
|
queryKey: ["userList", "{pagination}", "{filters}", "{sorting}"],
|
|
purpose: "Loads paginated user table data from server-side filters and sorting.",
|
|
requestShape: {
|
|
pageSize: "number",
|
|
current: "1-based page number",
|
|
filter: [{ id: "column id", value: "raw value or operator:value string" }],
|
|
sort: [{ id: "column id", desc: "boolean" }],
|
|
},
|
|
responseShape: {
|
|
data: [
|
|
{
|
|
id: "number",
|
|
email: "string",
|
|
is_admin: "boolean",
|
|
is_staff: "boolean",
|
|
banned: "boolean",
|
|
balance: "number",
|
|
commission_balance: "number",
|
|
online_count: "number",
|
|
device_limit: "number | null",
|
|
transfer_enable: "number",
|
|
total_used: "number",
|
|
u: "number",
|
|
d: "number",
|
|
expired_at: "unix timestamp | null",
|
|
next_reset_at: "unix timestamp | null",
|
|
created_at: "unix timestamp",
|
|
subscribe_url: "string",
|
|
uuid: "string",
|
|
token: "string",
|
|
remarks: "string | null",
|
|
plan: {
|
|
id: "number",
|
|
name: "string",
|
|
},
|
|
group: {
|
|
id: "number",
|
|
name: "string",
|
|
},
|
|
invite_user: {
|
|
id: "number",
|
|
email: "string",
|
|
},
|
|
invite_user_id: "number | null",
|
|
commission_type: "number",
|
|
commission_rate: "number | null",
|
|
},
|
|
],
|
|
total: "number",
|
|
},
|
|
},
|
|
{
|
|
name: "user.update",
|
|
method: "POST",
|
|
path: "{secure}/user/update",
|
|
purpose: "Persists edited user fields from the edit side sheet.",
|
|
notes: [
|
|
"Only dirty fields are submitted beyond the required id.",
|
|
"Upload/download fields are edited in GB in the form and converted back to bytes before submit.",
|
|
],
|
|
},
|
|
{
|
|
name: "user.resetSecret",
|
|
method: "POST",
|
|
path: "{secure}/user/resetSecret",
|
|
purpose: "Resets a user's secret/token from the actions menu.",
|
|
},
|
|
{
|
|
name: "user.generate",
|
|
method: "POST",
|
|
path: "{secure}/user/generate",
|
|
purpose: "Bulk generates users from the toolbar dialog, optionally downloading CSV output.",
|
|
},
|
|
{
|
|
name: "user.getStatUser",
|
|
method: "POST",
|
|
path: "{secure}/stat/getStatUser",
|
|
purpose: "Loads per-user traffic history rows for the traffic records dialog.",
|
|
},
|
|
{
|
|
name: "user.destroy",
|
|
method: "POST",
|
|
path: "{secure}/user/destroy",
|
|
purpose: "Deletes a user after confirmation from the row action menu.",
|
|
},
|
|
{
|
|
name: "user.sendMail",
|
|
method: "POST",
|
|
path: "{secure}/user/sendMail",
|
|
purpose: "Sends email to selected users, filtered users, or all users from the send-mail dialog.",
|
|
},
|
|
{
|
|
name: "user.dumpCsv",
|
|
method: "POST",
|
|
path: "{secure}/user/dumpCSV",
|
|
purpose: "Exports current selection/filter scope as CSV from the toolbar menu.",
|
|
},
|
|
{
|
|
name: "user.ban",
|
|
method: "POST",
|
|
path: "{secure}/user/ban",
|
|
purpose: "Batch-bans users by selected rows, current filtered scope, or all users.",
|
|
payloadExamples: [
|
|
{ scope: "selected", user_ids: [1, 2, 3] },
|
|
{ scope: "filtered", filter: [{ id: "email", value: "foo" }], sort: "id", sort_type: "DESC" },
|
|
{ scope: "all" },
|
|
],
|
|
},
|
|
{
|
|
name: "trafficReset.resetUser",
|
|
method: "POST",
|
|
path: "{secure}/traffic-reset/reset-user",
|
|
purpose: "Resets user traffic through the row action dialog.",
|
|
},
|
|
{
|
|
name: "order.assign",
|
|
method: "POST",
|
|
path: "{secure}/order/assign",
|
|
purpose: "Embedded assign-order flow reused from the order page.",
|
|
},
|
|
{
|
|
name: "plan.fetch",
|
|
method: "GET",
|
|
path: "{secure}/plan/fetch",
|
|
purpose: "Provides subscription plan options for filters, editing, generation, and assign-order.",
|
|
},
|
|
{
|
|
name: "server.group.fetch",
|
|
method: "GET",
|
|
path: "{secure}/server/group/fetch",
|
|
purpose: "Provides permission group options for toolbar and edit form.",
|
|
},
|
|
],
|
|
stateModel: {
|
|
list: {
|
|
rowSelection: {},
|
|
columnVisibility: {
|
|
is_admin: false,
|
|
is_staff: false,
|
|
},
|
|
columnFilters: [],
|
|
sorting: [],
|
|
pagination: {
|
|
pageIndex: 0,
|
|
pageSize: 20,
|
|
},
|
|
initialHiddenColumns: [
|
|
"commission_balance",
|
|
"created_at",
|
|
"is_admin",
|
|
"is_staff",
|
|
"permission_group",
|
|
"plan_id",
|
|
],
|
|
initialColumnPinning: {
|
|
right: ["actions"],
|
|
},
|
|
},
|
|
routeBootstrap: {
|
|
searchParamsToFilters: [{ key: "email", parser: "string" }],
|
|
},
|
|
toolbarDialogs: {
|
|
generateUsers: false,
|
|
sendMail: false,
|
|
batchBanConfirm: false,
|
|
advancedFilterSheet: false,
|
|
},
|
|
editSheet: {
|
|
open: false,
|
|
editingUser: null,
|
|
planOptions: [],
|
|
},
|
|
},
|
|
toolbarModel: {
|
|
primaryActions: [
|
|
"Generate users dialog",
|
|
"Email search filter on the email column",
|
|
"Advanced filter sheet",
|
|
"Column visibility menu",
|
|
"Bulk action dropdown",
|
|
],
|
|
advancedFilters: [
|
|
"email",
|
|
"id",
|
|
"plan_id",
|
|
"transfer_enable",
|
|
"total_used",
|
|
"online_count",
|
|
"expired_at",
|
|
"uuid",
|
|
"token",
|
|
"banned",
|
|
"remarks",
|
|
"invite_user.email",
|
|
"invite_user_id",
|
|
"is_admin",
|
|
"is_staff",
|
|
],
|
|
bulkMenu: [
|
|
"Send mail",
|
|
"Export CSV",
|
|
"Batch ban",
|
|
],
|
|
scopeResolution:
|
|
"Selection scope is selected rows when any row is checked, otherwise filtered scope when filters exist, otherwise all users.",
|
|
},
|
|
tableModel: {
|
|
columns: [
|
|
{
|
|
key: "select",
|
|
titleKey: "columns.select_row",
|
|
renderer: "Checkbox column with select-all checkbox in header.",
|
|
},
|
|
{
|
|
key: "is_admin",
|
|
titleKey: "columns.is_admin",
|
|
hiddenByDefault: true,
|
|
filterBehavior: "Facet filter based on boolean values.",
|
|
},
|
|
{
|
|
key: "is_staff",
|
|
titleKey: "columns.is_staff",
|
|
hiddenByDefault: true,
|
|
filterBehavior: "Facet filter based on boolean values.",
|
|
},
|
|
{
|
|
key: "id",
|
|
titleKey: "columns.id",
|
|
renderer: "Outline badge",
|
|
sortable: true,
|
|
},
|
|
{
|
|
key: "email",
|
|
titleKey: "columns.email",
|
|
renderer:
|
|
"Composite identity cell rendered by a dedicated user summary component, including email, UUID/token-related context, and online/offline indicators.",
|
|
},
|
|
{
|
|
key: "online_count",
|
|
titleKey: "columns.online_count",
|
|
renderer:
|
|
"Online devices badge showing current online_count against device_limit, with tooltip for unlimited vs limited semantics.",
|
|
sortable: true,
|
|
},
|
|
{
|
|
key: "banned",
|
|
titleKey: "columns.status",
|
|
renderer: "Status badge for banned vs normal.",
|
|
sortable: true,
|
|
},
|
|
{
|
|
key: "plan_id",
|
|
titleKey: "columns.subscription",
|
|
renderer: "Nested plan.name text",
|
|
},
|
|
{
|
|
key: "group_id",
|
|
titleKey: "columns.group",
|
|
renderer: "Nested group.name badge",
|
|
},
|
|
{
|
|
key: "total_used",
|
|
titleKey: "columns.used_traffic",
|
|
renderer:
|
|
"Usage progress bar with percentage and tooltip containing total traffic.",
|
|
},
|
|
{
|
|
key: "transfer_enable",
|
|
titleKey: "columns.total_traffic",
|
|
renderer: "Formatted total traffic text",
|
|
},
|
|
{
|
|
key: "expired_at",
|
|
titleKey: "columns.expire_time",
|
|
renderer:
|
|
"Status badge for permanent/remaining/expired with tooltip exposing full time, day delta, and next_reset_at when present.",
|
|
},
|
|
{
|
|
key: "balance",
|
|
titleKey: "columns.balance",
|
|
renderer: "Currency value",
|
|
},
|
|
{
|
|
key: "commission_balance",
|
|
titleKey: "columns.commission",
|
|
renderer: "Currency value",
|
|
},
|
|
{
|
|
key: "created_at",
|
|
titleKey: "columns.register_time",
|
|
renderer: "Registration time text",
|
|
},
|
|
{
|
|
key: "actions",
|
|
titleKey: "columns.actions",
|
|
renderer:
|
|
"Dropdown menu containing edit, assign order, copy subscribe URL, reset secret, orders link, invites filter shortcut, traffic records dialog, reset traffic dialog, and delete confirmation.",
|
|
},
|
|
],
|
|
mobileLayout: {
|
|
primaryField: "email",
|
|
gridFields: ["online_count", "transfer_enable", "used_traffic", "expired_at"],
|
|
},
|
|
},
|
|
dialogs: {
|
|
generateUsers: {
|
|
formFields: [
|
|
"email_prefix",
|
|
"email_suffix",
|
|
"password",
|
|
"expired_at",
|
|
"plan_id",
|
|
"generate_count",
|
|
"download_csv",
|
|
],
|
|
behavior: [
|
|
"If generate_count is empty, email_prefix becomes required.",
|
|
"If download_csv is enabled and generate_count is used, the response is handled as a Blob download.",
|
|
],
|
|
},
|
|
sendMail: {
|
|
scopes: ["selected", "filtered", "all"],
|
|
fields: ["subject", "content"],
|
|
templateSupport:
|
|
"Supports placeholder variables such as {{app.name}}, {{user.email}}, {{user.plan_name}}, etc.",
|
|
specialAction: "Apply system notice template button fills subject and content from built-in defaults.",
|
|
},
|
|
editUser: {
|
|
fields: [
|
|
"email",
|
|
"invite_user_email",
|
|
"password",
|
|
"balance",
|
|
"commission_balance",
|
|
"u",
|
|
"d",
|
|
"transfer_enable",
|
|
"expired_at",
|
|
"plan_id",
|
|
"banned",
|
|
"commission_type",
|
|
"commission_rate",
|
|
"discount",
|
|
"speed_limit",
|
|
"device_limit",
|
|
"is_admin",
|
|
"is_staff",
|
|
"remarks",
|
|
],
|
|
conversions: [
|
|
"u and d are displayed in GB with three decimals and converted back to bytes on submit.",
|
|
"transfer_enable is edited in GB and converted back to bytes on change.",
|
|
],
|
|
expireTimePresets: [
|
|
"permanent",
|
|
"1 month",
|
|
"3 months",
|
|
"specific datetime",
|
|
],
|
|
},
|
|
trafficRecords: {
|
|
columns: ["record_at", "u", "d", "server_rate", "total"],
|
|
purpose: "Shows paginated traffic usage history for a single user.",
|
|
},
|
|
},
|
|
interactions: [
|
|
"If the route opens with ?email=... the page seeds that value into the email column filter automatically.",
|
|
"Export CSV and batch ban both resolve their target scope from selected rows first, then filtered data, then all users.",
|
|
"Invites action does not navigate away; it rewrites current columnFilters to invite_user_id=eq:<current user id>.",
|
|
"Orders action deep-links into /finance/order?user_id=eq:<current user id>.",
|
|
"Copy URL action copies subscribe_url directly from the current row.",
|
|
"Reset secret, reset traffic, and delete all refetch the list after success.",
|
|
],
|
|
notes: [
|
|
"The page uses a provider to share edit-sheet state and refreshData across the table and side sheet.",
|
|
"Advanced filters are composed into table column filters, using operator prefixes such as eq:, gt:, lt:, and raw contains values.",
|
|
"Column visibility is user-controlled through a generic shared table view-options dropdown.",
|
|
],
|
|
sourceRefs: [
|
|
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:27389",
|
|
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:300842",
|
|
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:301426",
|
|
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:303321",
|
|
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:303835",
|
|
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:304646",
|
|
],
|
|
});
|