Unverified Commit a2ff2a11 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

fix(panel): make the LuckPerms page honest about what the server said (#59)

Live with real LuckPerms 5.5.85: every lp command returns an empty RCON body
(list/plugins answer normally; a standalone RCON client sees the same, and
creategroup/permission-set still persist), so the read projection can never
populate and the page asserted "no parent groups / no explicit nodes" for a
state it could not actually read. The raw reply now rides the same disclosure
the rosters carry, a silent entry-less reply shows an explicit notice instead
of the false empty claims, and the write history's placeholder no longer
dresses up a fabricated "[RCON] ..." line as output.
parent 4d4cdd6e
Loading
Loading
Loading
Loading
+2 −0
Changes for panel/src/i18n/resources/en-US/servers.json: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -159,6 +159,8 @@
  "luckperms_revert_success": "Action reverted successfully!",
  "luckperms_presets": "Presets",
  "luckperms_no_parent_groups": "No parent groups assigned",
  "luckperms_no_reply": "The server returned no parseable data — some LuckPerms versions do not reply over RCON, so the group/permission lists below may be incomplete. Grants and revocations still take effect.",
  "luckperms_no_output": "(the server returned no output)",
  "luckperms_global": "global",
  "luckperms_no_perms": "No explicit permission nodes assigned",
  "luckperms_rcon_output": "RCON Console Output",
+2 −0
Changes for panel/src/i18n/resources/zh-CN/servers.json: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -159,6 +159,8 @@
  "luckperms_revert_success": "已成功撤销该操作!",
  "luckperms_presets": "预设",
  "luckperms_no_parent_groups": "未分配任何父组",
  "luckperms_no_reply": "服务器没有返回可解析的数据——部分 LuckPerms 版本不通过 RCON 回包,下面的用户组/权限列表可能不准确;授予与撤销操作仍然会实际生效。",
  "luckperms_no_output": "(服务器未返回输出)",
  "luckperms_global": "全局",
  "luckperms_no_perms": "尚未分配任何权限节点",
  "luckperms_rcon_output": "RCON 控制台输出",
+1 −0
Changes for panel/src/lib/api.ts: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -245,6 +245,7 @@ export const api = {
      player: string;
      groups: string[];
      permissions: { node: string; value: boolean; world?: string }[];
      output: string;
    }>("GET", `/servers/${name}/access/luckperms/${player}`),

  accessPermission: (
+40 −4
Changes for panel/src/pages/ServerLuckPerms.tsx: 40 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -88,6 +88,18 @@ export function ServerLuckPerms() {
    [name, selectedPlayer]
  );

  // LuckPerms does not reply over RCON (its commands finish asynchronously, after
  // the response is flushed) — verified live on 5.5.85: every `lp` command
  // returns an empty body while `list`/`plugins` answer normally. A silent,
  // entry-less reply therefore means "could not read", NOT "no groups / no
  // permissions": the two projection panels must not claim the latter on their
  // own. The writes still work, so the page keeps offering them.
  const lpSilent =
    !!lpInfo &&
    !(lpInfo.output ?? "").trim() &&
    (lpInfo.groups?.length ?? 0) === 0 &&
    (lpInfo.permissions?.length ?? 0) === 0;

  // Form State
  const [groupNameInput, setGroupNameInput] = useState("");
  const [permNodeInput, setPermNodeInput] = useState("");
@@ -135,7 +147,7 @@ export function ServerLuckPerms() {
        action: "add",
        target: groupName,
        status: "success",
        output: res.output || `[RCON] parent add ${groupName}`,
        output: res.output || t("luckperms_no_output"),
      });
      reloadLp();
    } catch (err) {
@@ -157,7 +169,7 @@ export function ServerLuckPerms() {
        action: "remove",
        target: groupName,
        status: "success",
        output: res.output || `[RCON] parent remove ${groupName}`,
        output: res.output || t("luckperms_no_output"),
      });
      reloadLp();
    } catch (err) {
@@ -194,7 +206,7 @@ export function ServerLuckPerms() {
        value: permValueInput,
        world: world || undefined,
        status: "success",
        output: res.output || `[RCON] permission set ${node}`,
        output: res.output || t("luckperms_no_output"),
      });
      setPermNodeInput("");
      setPermWorldInput("");
@@ -219,7 +231,7 @@ export function ServerLuckPerms() {
        target: node,
        world: world || undefined,
        status: "success",
        output: res.output || `[RCON] permission unset ${node}`,
        output: res.output || t("luckperms_no_output"),
      });
      reloadLp();
    } catch (err) {
@@ -395,6 +407,30 @@ export function ServerLuckPerms() {
                  </span>
                </div>

                {/* Ground truth: the raw RCON reply behind the two projection panels.
                    The parse is LuckPerms-format-specific — on a server without the
                    plugin it yields zero entries, and without this disclosure the
                    panels would present that as "no groups / no permissions" while
                    the real reply ("Unknown or incomplete command…") stays invisible.
                    Same disclosure the whitelist/ban/online rosters carry. */}
                {lpInfo?.output?.trim() && (
                  <details className="group/details">
                    <summary className="cursor-pointer select-none text-[10px] text-muted-foreground/70 hover:text-foreground font-mono transition-colors list-none flex items-center gap-1">
                      <span className="transition-transform group-open/details:rotate-90">▶</span>
                      {t("luckperms_rcon_output")}
                    </summary>
                    <pre className="mt-1.5 p-2 rounded bg-muted/60 border border-border/80 font-mono text-[10px] text-foreground/80 overflow-x-auto whitespace-pre-wrap break-all max-h-24">
                      {lpInfo.output}
                    </pre>
                  </details>
                )}

                {lpSilent && (
                  <div className="rounded-md border border-dashed border-border bg-muted/30 p-3 text-xs text-muted-foreground">
                    {t("luckperms_no_reply")}
                  </div>
                )}

                {/* Operations Feedback */}
                {formFeedback && (
                  <div className={cn(