mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-21 17:04:00 +00:00
Move the seed into exp_scenario and seed the permission groups (#457)
The seed roles were owned by exp_roles, but they describe the scenario, so they now live in exp_scenario/seed.ts next to the permissions they grant. Each seed role names the permission group its holders belong to, and the five groups the legacy config defined (Admin, Trusted, Standard, Guest, Restricted) are seeded through exp_groups along with one role mapping per role. Mapping priorities follow how exp_roles ranks a player's highest role, so Jail lands in Restricted regardless of other roles. SeedRolesRequest becomes exp_scenario's SeedRequest behind a new exp_scenario.seed permission, and the button moves to the scenario's web plugin, which gets a web entrypoint for it. exp_roles no longer depends on exp_scenario, and exp_scenario gains controller tests around the seed. With groups seeded from the controller the legacy expcore.permission_groups module and its config are removed, along with the Group rcon static.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { BaseControllerPlugin, InstanceRecord } from "@clusterio/controller";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages";
|
||||
import { SeedRole, seedRoles, flattenSeedPermissions } from "./seed";
|
||||
import * as path from "node:path";
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
@@ -35,7 +34,6 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
|
||||
this.controller.handle(messages.RoleListRequest, this.handleRoleListRequest.bind(this));
|
||||
this.controller.handle(messages.RoleMetaUpdateRequest, this.handleRoleMetaUpdateRequest.bind(this));
|
||||
this.controller.handle(messages.SeedRolesRequest, this.handleSeedRolesRequest.bind(this));
|
||||
|
||||
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
|
||||
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
|
||||
@@ -45,68 +43,6 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
await this.roleMeta.save();
|
||||
}
|
||||
|
||||
/*
|
||||
Seeding
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create the roles the scenario shipped with. Roles which already exist
|
||||
* by name are reused and only gain the seed permissions.
|
||||
*/
|
||||
async handleSeedRolesRequest() {
|
||||
for (const [index, seedRole] of seedRoles.entries()) {
|
||||
const role = this.seedRole(seedRole);
|
||||
if (!role) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.roleMeta.set(new messages.RoleMetaRecord(
|
||||
role.id,
|
||||
index + 1,
|
||||
seedRole.priority ?? 0,
|
||||
seedRole.shortHand,
|
||||
"",
|
||||
seedRole.color,
|
||||
seedRole.autoAssignHours === undefined ? null : seedRole.autoAssignHours * 3600000,
|
||||
seedRole.blockAutoAssign ?? false,
|
||||
));
|
||||
}
|
||||
|
||||
this.logger.info(`Seeded ${seedRoles.length} roles`);
|
||||
}
|
||||
|
||||
/** Find or create the clusterio role for a seed role, returns undefined if it has no role to use. */
|
||||
seedRole(seedRole: SeedRole) {
|
||||
const roles = this.controller.roles;
|
||||
if (seedRole.isAdmin) {
|
||||
return roles.get(lib.Role.DefaultAdminRoleId);
|
||||
}
|
||||
if (seedRole.isDefault) {
|
||||
const defaultRoleId = this.controller.config.get("controller.default_role_id");
|
||||
return defaultRoleId !== null ? roles.get(defaultRoleId) : undefined;
|
||||
}
|
||||
|
||||
const permissions = flattenSeedPermissions(seedRole);
|
||||
for (const permission of permissions) {
|
||||
if (!lib.permissions.has(permission)) {
|
||||
this.logger.warn(`Seed role ${seedRole.name} grants unknown permission ${permission}`);
|
||||
}
|
||||
}
|
||||
|
||||
let role = [...roles.valuesMutable()].find(other => other.name === seedRole.name);
|
||||
if (role) {
|
||||
for (const permission of permissions) {
|
||||
role.permissions.add(permission);
|
||||
}
|
||||
} else {
|
||||
const id = Math.max(5, ...[...roles.keys()].map(other => other + 1));
|
||||
role = new lib.Role(id, seedRole.name, "", permissions);
|
||||
this.logger.info(`Created role ${seedRole.name}`);
|
||||
}
|
||||
roles.set(role);
|
||||
return role;
|
||||
}
|
||||
|
||||
/*
|
||||
Role properties
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,6 @@ export const plugin: lib.PluginDeclaration = {
|
||||
|
||||
messages.RoleListRequest,
|
||||
messages.RoleMetaUpdateRequest,
|
||||
messages.SeedRolesRequest,
|
||||
|
||||
messages.AssignmentListRequest,
|
||||
messages.AssignmentUpdateRequest,
|
||||
|
||||
@@ -365,18 +365,6 @@ export class RoleMetaUpdateRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create the roles the scenario shipped with, see seed.ts. */
|
||||
export class SeedRolesRequest {
|
||||
declare ["constructor"]: typeof SeedRolesRequest;
|
||||
static plugin = "exp_roles" as const;
|
||||
static type = "request" as const;
|
||||
static src = "control" as const;
|
||||
static dst = "controller" as const;
|
||||
static permission = "core.role.create" as const;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
/*
|
||||
Assignment requests
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"@clusterio/host": "workspace:^",
|
||||
"@clusterio/lib": "workspace:^",
|
||||
"@clusterio/web_ui": "workspace:^",
|
||||
"@expcluster/scenario": "workspace:^",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"antd": "catalog:",
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
import { RoleColor } from "./messages";
|
||||
|
||||
/**
|
||||
* A role created by the seed, as the scenario defined it before roles moved to
|
||||
* the controller. `parent` stands in for the inheritance
|
||||
* the old config had and is flattened by the seed. The default and admin roles
|
||||
* already exist, so those entries only provide the in game properties.
|
||||
*/
|
||||
export interface SeedRole {
|
||||
name: string;
|
||||
shortHand: string;
|
||||
color: RoleColor | null;
|
||||
priority?: number;
|
||||
autoAssignHours?: number;
|
||||
blockAutoAssign?: boolean;
|
||||
/** Uses the controller's default role rather than creating one. */
|
||||
isDefault?: boolean;
|
||||
/** Uses the controller's admin role rather than creating one. */
|
||||
isAdmin?: boolean;
|
||||
/** Name of the role whose permissions are also granted, applied recursively. */
|
||||
parent?: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export const seedRoles: SeedRole[] = [
|
||||
{
|
||||
name: "System",
|
||||
shortHand: "SYS",
|
||||
color: null,
|
||||
isAdmin: true,
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
name: "Senior Administrator",
|
||||
shortHand: "SAdmin",
|
||||
color: new RoleColor(233, 63, 233),
|
||||
parent: "Administrator",
|
||||
permissions: [
|
||||
"exp_scenario.command._rcon",
|
||||
"exp_scenario.command.debug",
|
||||
"exp_scenario.command.set_cheat_mode",
|
||||
"exp_scenario.command.research_all",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Administrator",
|
||||
shortHand: "Admin",
|
||||
color: new RoleColor(233, 63, 233),
|
||||
parent: "Moderator",
|
||||
permissions: [
|
||||
"exp_scenario.gui.warp_list.bypass_proximity",
|
||||
"exp_scenario.gui.warp_list.bypass_cooldown",
|
||||
"exp_scenario.command.connect_all",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Moderator",
|
||||
shortHand: "Mod",
|
||||
color: new RoleColor(0, 170, 0),
|
||||
parent: "Trainee",
|
||||
permissions: [
|
||||
"exp_scenario.player.instant_respawn",
|
||||
"exp_scenario.command.assign_role",
|
||||
"exp_scenario.command.unassign_role",
|
||||
"exp_scenario.command.repair",
|
||||
"exp_scenario.command.kill.always",
|
||||
"exp_scenario.command.tag_clear.always",
|
||||
"exp_scenario.command.spawn.always",
|
||||
"exp_scenario.command.clear_reports",
|
||||
"exp_scenario.command.clear_inventory",
|
||||
"exp_scenario.command.kill_enemies",
|
||||
"exp_scenario.command.remove_enemies",
|
||||
"exp_scenario.command.home",
|
||||
"exp_scenario.command.set_home",
|
||||
"exp_scenario.command.get_home",
|
||||
"exp_scenario.command.return",
|
||||
"exp_scenario.command.connect_player",
|
||||
"exp_scenario.command.set_bot_queue",
|
||||
"exp_scenario.command.set_game_speed",
|
||||
"exp_scenario.command.set_friendly_fire",
|
||||
"exp_scenario.command.set_always_day",
|
||||
"exp_scenario.command.set_pollution_enabled",
|
||||
"exp_scenario.command.clear_pollution",
|
||||
"exp_scenario.gui.rocket_info.toggle_active",
|
||||
"exp_scenario.gui.rocket_info.remote_launch",
|
||||
"exp_scenario.gui.bonus",
|
||||
"exp_scenario.decon.fast_trees",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Trainee",
|
||||
shortHand: "TrMod",
|
||||
color: new RoleColor(0, 170, 0),
|
||||
parent: "Veteran",
|
||||
permissions: [
|
||||
"exp_scenario.player.admin",
|
||||
"exp_scenario.player.spectator",
|
||||
"exp_scenario.bypass.reports",
|
||||
"exp_scenario.command.admin_chat",
|
||||
"exp_scenario.command.goto",
|
||||
"exp_scenario.command.teleport",
|
||||
"exp_scenario.command.bring",
|
||||
"exp_scenario.command.get_reports",
|
||||
"exp_scenario.command.protect_entity",
|
||||
"exp_scenario.command.protect_area",
|
||||
"exp_scenario.command.protect_tag",
|
||||
"exp_scenario.command.jail",
|
||||
"exp_scenario.command.unjail",
|
||||
"exp_scenario.gui.player_list.kick",
|
||||
"exp_scenario.gui.player_list.ban",
|
||||
"exp_scenario.command.spectate",
|
||||
"exp_scenario.command.follow",
|
||||
"exp_scenario.command.search",
|
||||
"exp_scenario.command.search_online",
|
||||
"exp_scenario.command.search_amount",
|
||||
"exp_scenario.command.search_recent",
|
||||
"exp_scenario.command.clear_blueprints_surface",
|
||||
"exp_scenario.gui.playerdata",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Board Member",
|
||||
shortHand: "Board",
|
||||
color: new RoleColor(247, 246, 54),
|
||||
parent: "Sponsor",
|
||||
permissions: [
|
||||
"exp_scenario.command.goto",
|
||||
"exp_scenario.command.repair",
|
||||
"exp_scenario.command.spectate",
|
||||
"exp_scenario.command.follow",
|
||||
"exp_scenario.gui.playerdata",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Senior Backer",
|
||||
shortHand: "Backer",
|
||||
color: new RoleColor(238, 172, 44),
|
||||
parent: "Sponsor",
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
name: "Sponsor",
|
||||
shortHand: "Spon",
|
||||
color: new RoleColor(238, 172, 44),
|
||||
parent: "Supporter",
|
||||
permissions: [
|
||||
"exp_scenario.bypass.reports",
|
||||
"exp_scenario.player.instant_respawn",
|
||||
"exp_scenario.gui.rocket_info.toggle_active",
|
||||
"exp_scenario.gui.rocket_info.remote_launch",
|
||||
"exp_scenario.gui.bonus",
|
||||
"exp_scenario.command.home",
|
||||
"exp_scenario.command.set_home",
|
||||
"exp_scenario.command.get_home",
|
||||
"exp_scenario.command.return",
|
||||
"exp_scenario.decon.fast_trees",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Supporter",
|
||||
shortHand: "Sup",
|
||||
color: new RoleColor(230, 99, 34),
|
||||
parent: "Veteran",
|
||||
permissions: [
|
||||
"exp_scenario.player.spectator",
|
||||
"exp_scenario.command.tag_color",
|
||||
"exp_scenario.command.jail",
|
||||
"exp_scenario.command.unjail",
|
||||
"exp_scenario.command.set_join_message",
|
||||
"exp_scenario.command.remove_join_message",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Partner",
|
||||
shortHand: "Part",
|
||||
color: new RoleColor(140, 120, 200),
|
||||
parent: "Veteran",
|
||||
permissions: [
|
||||
"exp_scenario.player.spectator",
|
||||
"exp_scenario.command.jail",
|
||||
"exp_scenario.command.unjail",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Veteran",
|
||||
shortHand: "Vet",
|
||||
color: new RoleColor(140, 120, 200),
|
||||
parent: "Member",
|
||||
autoAssignHours: 10,
|
||||
permissions: [
|
||||
"exp_scenario.chat.commands",
|
||||
"exp_scenario.command.clear_ground_items",
|
||||
"exp_scenario.command.clear_blueprints",
|
||||
"exp_scenario.command.set_trains_to_automatic",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Member",
|
||||
shortHand: "Mem",
|
||||
color: new RoleColor(24, 172, 188),
|
||||
parent: "Regular",
|
||||
permissions: [
|
||||
"exp_scenario.bypass.deconstruction_log",
|
||||
"exp_scenario.gui.task_list.add",
|
||||
"exp_scenario.gui.task_list.edit",
|
||||
"exp_scenario.gui.warp_list.add",
|
||||
"exp_scenario.gui.warp_list.edit",
|
||||
"exp_scenario.gui.surveillance",
|
||||
"exp_scenario.gui.vlayer_edit",
|
||||
"exp_scenario.gui.tool",
|
||||
"exp_scenario.command.save_quickbar",
|
||||
"exp_scenario.command.vlayer_info",
|
||||
"exp_scenario.command.lawnmower",
|
||||
"exp_scenario.command.waterfill",
|
||||
"exp_scenario.command.artillery",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Regular",
|
||||
shortHand: "Reg",
|
||||
color: new RoleColor(79, 155, 163),
|
||||
autoAssignHours: 3,
|
||||
permissions: [
|
||||
"exp_scenario.command.kill",
|
||||
"exp_scenario.command.rainbow",
|
||||
"exp_scenario.command.spawn",
|
||||
"exp_scenario.command.me",
|
||||
"exp_scenario.decon.standard",
|
||||
"exp_scenario.bypass.entity_protection",
|
||||
"exp_scenario.bypass.nuke_protection",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Jail",
|
||||
shortHand: "Jail",
|
||||
color: new RoleColor(50, 50, 50),
|
||||
priority: 1,
|
||||
blockAutoAssign: true,
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
name: "Guest",
|
||||
shortHand: "",
|
||||
color: new RoleColor(185, 187, 160),
|
||||
isDefault: true,
|
||||
permissions: [],
|
||||
},
|
||||
];
|
||||
|
||||
/** The permissions a seed role grants, including those of its parents. */
|
||||
export function flattenSeedPermissions(role: SeedRole, roles = seedRoles) {
|
||||
const permissions = new Set<string>();
|
||||
const seen = new Set<string>();
|
||||
let current: SeedRole | undefined = role;
|
||||
while (current && !seen.has(current.name)) {
|
||||
seen.add(current.name);
|
||||
for (const permission of current.permissions) {
|
||||
permissions.add(permission);
|
||||
}
|
||||
current = roles.find(other => other.name === current!.parent);
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
@@ -4,17 +4,12 @@ const lib = require("@clusterio/lib");
|
||||
const { Controller } = require("@clusterio/controller");
|
||||
const { ControllerPlugin } = require("../dist/node/controller");
|
||||
const messages = require("../dist/node/messages");
|
||||
const { seedRoles } = require("../dist/node/seed");
|
||||
|
||||
// Importing this defines the exp_scenario permissions the seed grants
|
||||
require("@expcluster/scenario/dist/node/permissions");
|
||||
|
||||
// The controller validates message classes against the link registry
|
||||
lib.Link.register(messages.RoleUpdatedEvent);
|
||||
lib.Link.register(messages.AssignmentUpdatedEvent);
|
||||
lib.Link.register(messages.RoleListRequest);
|
||||
lib.Link.register(messages.RoleMetaUpdateRequest);
|
||||
lib.Link.register(messages.SeedRolesRequest);
|
||||
lib.Link.register(messages.AssignmentListRequest);
|
||||
lib.Link.register(messages.AssignmentUpdateRequest);
|
||||
|
||||
@@ -229,22 +224,5 @@ t.test("class ControllerPlugin", t2 => {
|
||||
t3.strictSame(none, null, "nothing is replayed when up to date");
|
||||
});
|
||||
|
||||
t2.test(".handleSeedRolesRequest() creates the roles and reuses them by name", async t3 => {
|
||||
const { plugin, controller } = await startPlugin(t3, {
|
||||
roles: [role(0, "Cluster Admin", ["core.admin"]), role(1, "Player")],
|
||||
});
|
||||
|
||||
await plugin.handleSeedRolesRequest();
|
||||
t3.strictSame(controller.roles.size, seedRoles.length, "every seed role exists");
|
||||
|
||||
const moderator = [...controller.roles.values()].find(other => other.name === "Moderator");
|
||||
t3.ok(moderator.permissions.has("exp_scenario.command.jail"), "parent permissions are flattened in");
|
||||
t3.ok(plugin.roleMeta.get(moderator.id), "the role properties are created");
|
||||
t3.strictSame(plugin.roleMeta.get(moderator.id).shortHand, "Mod", "the properties match the seed");
|
||||
|
||||
await plugin.handleSeedRolesRequest();
|
||||
t3.strictSame(controller.roles.size, seedRoles.length, "seeding again reuses the roles");
|
||||
});
|
||||
|
||||
t2.end();
|
||||
});
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
"use strict";
|
||||
const t = require("tap");
|
||||
const lib = require("@clusterio/lib");
|
||||
const { seedRoles, flattenSeedPermissions } = require("../dist/node/seed");
|
||||
|
||||
// Importing this defines the exp_scenario permissions the seed grants
|
||||
require("@expcluster/scenario/dist/node/permissions");
|
||||
|
||||
t.test("seedRoles[] grant only defined permissions", t2 => {
|
||||
for (const role of seedRoles) {
|
||||
for (const permission of role.permissions) {
|
||||
t2.ok(lib.permissions.has(permission), `${role.name} grants defined permission ${permission}`);
|
||||
}
|
||||
}
|
||||
t2.end();
|
||||
});
|
||||
|
||||
t.test("seedRoles[] are unique with one default and one admin", t2 => {
|
||||
const names = seedRoles.map(role => role.name);
|
||||
t2.strictSame(names.length, new Set(names).size, "names are unique");
|
||||
t2.strictSame(seedRoles.filter(role => role.isDefault).length, 1, "one default role");
|
||||
t2.strictSame(seedRoles.filter(role => role.isAdmin).length, 1, "one admin role");
|
||||
t2.end();
|
||||
});
|
||||
|
||||
t.test("flattenSeedPermissions() inherits permissions from parent roles", t2 => {
|
||||
const byName = new Map(seedRoles.map(role => [role.name, role]));
|
||||
for (const role of seedRoles) {
|
||||
if (role.parent === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parent = byName.get(role.parent);
|
||||
t2.ok(parent, `${role.name} has parent ${role.parent}`);
|
||||
|
||||
const flattened = flattenSeedPermissions(role);
|
||||
for (const permission of flattenSeedPermissions(parent)) {
|
||||
t2.ok(flattened.has(permission), `${role.name} inherits ${permission}`);
|
||||
}
|
||||
}
|
||||
t2.end();
|
||||
});
|
||||
|
||||
t.test("flattenSeedPermissions() does not inherit permissions when parent is undefined", t2 => {
|
||||
for (const role of seedRoles) {
|
||||
if (role.parent !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const flattened = flattenSeedPermissions(role);
|
||||
for (const permission of role.permissions) {
|
||||
t2.ok(flattened.has(permission), `${role.name} contains ${permission}`);
|
||||
}
|
||||
|
||||
t2.equal(
|
||||
flattened.size,
|
||||
role.permissions.length,
|
||||
`${role.name} has no inherited permissions`,
|
||||
);
|
||||
}
|
||||
t2.end();
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Button, Popconfirm } from "antd";
|
||||
|
||||
import { ControlContext, SectionHeader, useAccount, notifyErrorHandler } from "@clusterio/web_ui";
|
||||
|
||||
import { SeedRolesRequest } from "../../messages";
|
||||
|
||||
/** Button on the roles page which creates the roles the scenario shipped with. */
|
||||
export default function SeedRoles() {
|
||||
const control = useContext(ControlContext);
|
||||
const account = useAccount();
|
||||
const [seeding, setSeeding] = useState(false);
|
||||
|
||||
if (!account.hasPermission("core.role.create")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SectionHeader
|
||||
title="ExpGaming Roles"
|
||||
extra={<Popconfirm
|
||||
title="Create the ExpGaming roles?"
|
||||
description="Roles which already exist by name are kept and only gain permissions."
|
||||
onConfirm={() => {
|
||||
setSeeding(true);
|
||||
control.send(new SeedRolesRequest())
|
||||
.catch(notifyErrorHandler("Error seeding roles"))
|
||||
.finally(() => setSeeding(false));
|
||||
}}
|
||||
>
|
||||
<Button loading={seeding}>Seed roles</Button>
|
||||
</Popconfirm>}
|
||||
/>;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import * as lib from "@clusterio/lib";
|
||||
import * as messages from "../messages";
|
||||
|
||||
import RoleProperties from "./components/RoleProperties";
|
||||
import SeedRoles from "./components/SeedRoles";
|
||||
|
||||
export class WebPlugin extends BaseWebPlugin {
|
||||
roles = new lib.MapSubscriber(messages.RoleUpdatedEvent, this.control);
|
||||
@@ -15,7 +14,6 @@ export class WebPlugin extends BaseWebPlugin {
|
||||
// does not carry in its type
|
||||
this.componentExtra = {
|
||||
RoleViewPage: RoleProperties as React.ComponentType,
|
||||
RolesPage: SeedRoles,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user