XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
import { useState, type FormEvent } from 'react';
import { Ban, Gavel, Plus, ShieldAlert } from 'lucide-react';
import { useApiResource, useServer } from '../context/server-context';
import { useI18n } from '../lib/i18n';
import { Badge, Button, FeatureUnavailable, Field, Input, PageHeader, Panel, ResourceState, Select, TableShell, Textarea, Toast, formatDate } from '../components/ui';

export function ModerationPage() {
  const { t, locale } = useI18n();
  const { api, features } = useServer();
  const enabled = features?.moderationEnabled === true;
  const resource = useApiResource(() => api.moderationCases(), [], ['moderation'], enabled);
  const [player, setPlayer] = useState('');
  const [type, setType] = useState('WARN');
  const [reason, setReason] = useState('');
  const [message, setMessage] = useState('');
  const [duration, setDuration] = useState('');
  const [previewing, setPreviewing] = useState(false);
  const [busy, setBusy] = useState(false);
  const [revoking, setRevoking] = useState<{ caseId: string; actionId: string }>();
  const [revokeReason, setRevokeReason] = useState('');
  const [toast, setToast] = useState<{ message: string; tone: 'good' | 'danger' }>();

  const submit = async (event: FormEvent) => {
    event.preventDefault();
    if (!player.trim() || !reason.trim()) return;
    if (!previewing) { setPreviewing(true); return; }
    setBusy(true);
    try {
      const moderationCase = await api.createModerationCase({ playerId: player.trim(), action: type,
        reason: reason.trim(), message: message.trim() || reason.trim(), duration: duration.trim() || undefined });
      setToast({ message: `${t('moderation.case')} · ${moderationCase.id}`, tone: 'good' });
      setPlayer(''); setReason(''); setMessage(''); setDuration(''); setPreviewing(false); resource.reload();
    } catch (error) {
      setToast({ message: error instanceof Error ? error.message : String(error), tone: 'danger' });
    } finally { setBusy(false); }
  };

  const revoke = async () => {
    if (!revoking || !revokeReason.trim()) return;
    setBusy(true);
    try {
      await api.revokeModerationAction(revoking.caseId, revoking.actionId, revokeReason.trim());
      setToast({ message: t('moderation.revoked'), tone: 'good' }); setRevoking(undefined); setRevokeReason(''); resource.reload();
    } catch (error) { setToast({ message: error instanceof Error ? error.message : String(error), tone: 'danger' }); }
    finally { setBusy(false); }
  };

  return <div className="page"><PageHeader title={t('moderation.title')} description={t('moderation.description')} />
    {!enabled && <FeatureUnavailable>{t('moderation.featureOff')}</FeatureUnavailable>}
    <div className="moderation-layout">
      <Panel title={<><Plus size={18} />{t('moderation.newCase')}</>}>
        <form className="form-stack" onSubmit={submit}>
          <Field label={t('moderation.player')}><Input value={player} onChange={(event) => { setPlayer(event.target.value); setPreviewing(false); }} required /></Field>
          <Field label={t('moderation.type')}><Select value={type} onChange={(event) => { setType(event.target.value); setPreviewing(false); }}><option>WARN</option><option>MUTE</option><option>KICK</option><option>TEMPBAN</option><option>BAN</option></Select></Field>
          <Field label={t('moderation.duration')}><Input value={duration} onChange={(event) => { setDuration(event.target.value); setPreviewing(false); }} placeholder="2h" /></Field>
          <Field label={t('moderation.reason')}><Textarea value={reason} onChange={(event) => { setReason(event.target.value); setPreviewing(false); }} required /></Field>
          <Field label={t('moderation.message')} hint={t('moderation.messageHint')}><Textarea value={message}
            onChange={(event) => { setMessage(event.target.value); setPreviewing(false); }} /></Field>
          {previewing && <div className="confirm-box confirm-box--danger"><ShieldAlert /><div><strong>{type} · {player}</strong>
            <p>{reason}{duration ? ` · ${duration}` : ''}</p><small>{message.trim() || reason}</small></div></div>}
          <Button type="submit" variant={previewing ? 'danger' : 'primary'} disabled={busy || !enabled || !player.trim() || !reason.trim()}><Gavel size={16} />{previewing ? t('common.confirm') : t('moderation.create')}</Button>
        </form>
      </Panel>
      <Panel title={<><Ban size={18} />{t('moderation.title')}</>}>
        {revoking && <div className="confirm-box confirm-box--danger"><ShieldAlert /><div className="form-stack"><strong>{t('moderation.revoke')}</strong><Input value={revokeReason} onChange={(event) => setRevokeReason(event.target.value)} placeholder={t('common.reason')} /><div className="button-row"><Button variant="danger" disabled={busy || !revokeReason.trim()} onClick={() => void revoke()}>{t('common.confirm')}</Button><Button variant="ghost" onClick={() => setRevoking(undefined)}>{t('common.cancel')}</Button></div></div></div>}
        <ResourceState loading={resource.loading} error={resource.error} empty={resource.data?.items.length === 0} onRetry={resource.reload}><TableShell><thead><tr><th>{t('moderation.case')}</th><th>{t('moderation.player')}</th><th>{t('moderation.type')}</th><th>{t('moderation.state')}</th><th>{t('moderation.expiry')}</th><th /></tr></thead><tbody>{resource.data?.items.map((item) => <tr key={item.id}><td><code>{item.id}</code><small className="table-subline">{formatDate(item.createdAt, locale)}</small></td><td><strong>{item.playerName ?? item.playerUuid}</strong><small className="table-subline">{item.message ?? item.reason}</small><small className="table-subline">{item.reason}</small></td><td><Badge tone="warn">{item.type}</Badge></td><td><Badge tone={item.state === 'ACTIVE' ? 'danger' : 'neutral'}>{item.state}</Badge></td><td>{formatDate(item.expiresAt, locale)}</td><td>{item.state === 'ACTIVE' && item.history?.at(-1)?.id && <Button variant="ghost" onClick={() => setRevoking({ caseId: item.id, actionId: item.history!.at(-1)!.id })}>{t('moderation.revoke')}</Button>}</td></tr>)}</tbody></TableShell></ResourceState>
      </Panel>
    </div>{toast && <Toast {...toast} onClose={() => setToast(undefined)} />}</div>;
}