import React, { useEffect, useContext, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Button, Col, Descriptions, Form, Input, Popconfirm, Row, Table, Tag, Select, Space, Spin, Switch, } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import * as lib from "@clusterio/lib"; import { useRoles } from "../model/roles"; import useTableQueryState from "../util/useTableQueryState"; import { useAccount } from "../model/account"; import { useInstances } from "../model/instance"; import ControlContext from "./ControlContext"; import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import SectionHeader from "./SectionHeader"; import ResourceNotes from "./ResourceNotes"; import useRowNavigation from "../util/useRowNavigation"; import Link from "./Link"; import notify, { notifyErrorHandler } from "../util/notify"; import { formatDuration } from "../util/time_format"; import { formatFirstSeen, formatLastSeen, sortFirstSeen, sortLastSeen, useUser } from "../model/user"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; // The user page has a lot of optional elements, that does not make it // praticularly complicated. // eslint-disable-next-line complexity export default function UserViewPage() { let params = useParams(); let userName = params.name as string; let navigate = useNavigate(); const rowNav = useRowNavigation(); let account = useAccount(); let control = useContext(ControlContext); let [instances] = useInstances(); const [user, synced] = useUser(userName); let [roles] = useRoles(); let [form] = Form.useForm(); const statsTable = useTableQueryState<[number, lib.PlayerStats]>({ namespace: "stats", defaultSortKey: "instance", }); let [rolesDirty, setRolesDirty] = useState(false); let [banReasonDirty, setBanReasonDirty] = useState(false); let [applyingRoles, setApplyingRoles] = useState(false); let [rolesError, setRolesError] = useState(); useEffect(() => { if ( account.hasPermission("core.user.set_banned") && !banReasonDirty && user && user.banReason !== form.getFieldValue("banReason") ) { form.setFieldsValue({ "banReason": user.banReason }); } }, [account, form, user, banReasonDirty]); let nav = [{ name: "Users", path: "/users" }, { name: userName }]; if (!user) { if (!synced) { return ; } return

User with name {userName} was not found on the controller.

; } // TODO: Update displayed roles when user roles change and have not been edited locally let roleSelector = (rolesError ? Promise.reject(rolesError) : Promise.resolve()) }, ]} > ; function instanceName(id: number) { return instances.get(id)?.name ?? String(id); } return {userName} {user.isAdmin && Admin} {user.isWhitelisted && Whitelisted} {user.isBanned && Banned} } extra={ {account.hasPermission("core.user.revoke_token") && ( account.name === userName || account.hasPermission("core.user.revoke_other_token") ) && } {account.hasPermission("core.user.delete") && Delete user account and all data associated with it? {(user.isBanned && <>
This will remove the user ban!)} } placement="bottomRight" okText="Delete" okButtonProps={{ danger: true }} onConfirm={() => { control.send( new lib.UserDeleteRequest(userName) ).then(() => { navigate("/users"); }, err => { if (err instanceof lib.SessionLost && userName === account.name) { // Got kicked out after deleting our own account navigate("/users"); return; } throw err; }).catch(notifyErrorHandler("Error deleting user")); }} >
}
} />
{account.hasPermission("core.user.update_roles") ? roleSelector : [...user.roleIds!].map(id => { roles?.get(id)?.name ?? id }) } {account.hasPermission("core.user.update_roles") && } {account.hasPermission("core.user.set_admin") && { control.send( new lib.UserSetAdminRequest(userName, false, !user.isAdmin) ).catch(notifyErrorHandler("Error toggling user admin status")); }} /> } {account.hasPermission("core.user.set_whitelisted") && { control.send( new lib.UserSetWhitelistedRequest(userName, false, !user.isWhitelisted) ).catch(notifyErrorHandler("Error toggling user whitelisted status")); }} /> } {account.hasPermission("core.user.set_banned") ? { setBanReasonDirty(e.target.value !== user.banReason); }} /> {user.isBanned ? : } : user.isBanned && {user.banReason} }
{formatDuration(user.playerStats?.onlineTimeMs ?? 0)} {user.playerStats?.joinCount ?? 0} {formatFirstSeen(user) || " "} {formatLastSeen(user) || " "} {instanceName(id)} , sorter: (a, b) => strcmp(instanceName(a[0]), instanceName(b[0])), sortOrder: statsTable.sortOrder("instance"), }, { title: "Online time", key: "onlineTime", render: (_, [, stats]) => formatDuration(stats.onlineTimeMs || 0), sorter: (a, b) => (a[1].onlineTimeMs || 0) - (b[1].onlineTimeMs || 0), sortOrder: statsTable.sortOrder("onlineTime"), }, { title: "Join count", key: "joinCoint", render: (_, [, stats]) => stats.joinCount || 0, sorter: (a, b) => (a[1].joinCount || 0) - (b[1].joinCount || 0), sortOrder: statsTable.sortOrder("joinCoint"), responsive: ["sm"], }, { title: "First seen", key: "firstSeen", render: (_, [id]) => formatFirstSeen(user, id), sorter: (a, b) => sortFirstSeen(user, user, a[0], b[0]), sortOrder: statsTable.sortOrder("firstSeen"), }, { title: "Last seen", key: "lastSeen", render: (_, [id]) => formatLastSeen(user, id), sorter: (a, b) => sortLastSeen(user, user, a[0], b[0]), sortOrder: statsTable.sortOrder("lastSeen"), }, ]} dataSource={[...(user.instanceStats || []).entries()]} pagination={statsTable.pagination} onChange={statsTable.onChange} rowKey={([id]) => id} onRow={([id]) => rowNav(`/instances/${id}/view`)} /> ; }