mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-07-26 18:36:23 +09:00
Final Cleanup
This commit is contained in:
@@ -397,7 +397,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
|||||||
return manual;
|
return manual;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = this.controller.users.get(playerName);
|
const user = this.controller.users.getByName(playerName);
|
||||||
const userRoles = user?.roleIds ?? new Set<number>();
|
const userRoles = user?.roleIds ?? new Set<number>();
|
||||||
|
|
||||||
// 2) Role mappings
|
// 2) Role mappings
|
||||||
|
|||||||
+7
-2
@@ -131,6 +131,11 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
title: "ExpGaming - Permission Groups",
|
title: "ExpGaming - Permission Groups",
|
||||||
description: "Clusterio plugin providing syncing of permission groups",
|
description: "Clusterio plugin providing syncing of permission groups",
|
||||||
|
|
||||||
|
features: [
|
||||||
|
"SavePatching",
|
||||||
|
"ScriptCommands",
|
||||||
|
],
|
||||||
|
|
||||||
messages: [
|
messages: [
|
||||||
messages.GroupUpdatedEvent,
|
messages.GroupUpdatedEvent,
|
||||||
messages.ManualAssignmentUpdatedEvent,
|
messages.ManualAssignmentUpdatedEvent,
|
||||||
@@ -172,7 +177,7 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
|
|
||||||
webEntrypoint: "./web",
|
webEntrypoint: "./web",
|
||||||
routes: [
|
routes: [
|
||||||
"/exp_groups",
|
"/permission_groups",
|
||||||
"/exp_groups/:id/view",
|
"/permission_groups/:id/view",
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
+23
-10
@@ -5,7 +5,7 @@ import * as messages from "./messages";
|
|||||||
export type IpcGroupUpdated = {
|
export type IpcGroupUpdated = {
|
||||||
group_name: string,
|
group_name: string,
|
||||||
group_id: number | undefined,
|
group_id: number | undefined,
|
||||||
permissions: { is_blacklist: boolean, permissions: string[] },
|
permissions: { is_blacklist: boolean, permissions: string[] | undefined },
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IpcGroupDeleted = {
|
export type IpcGroupDeleted = {
|
||||||
@@ -21,6 +21,8 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
// Once only, don't send permissions for these groups
|
// Once only, don't send permissions for these groups
|
||||||
// This is used for groups created on this instance that only need the controller generated id
|
// This is used for groups created on this instance that only need the controller generated id
|
||||||
skipSendingPermissions = new Set<string>();
|
skipSendingPermissions = new Set<string>();
|
||||||
|
// This is used for groups updated / deleted on this instance to stop cycles
|
||||||
|
skipSendingUpdate = new Set<number>();
|
||||||
// Track known online players so that we only apply assignment updates for them
|
// Track known online players so that we only apply assignment updates for them
|
||||||
onlinePlayers = new Set<string>();
|
onlinePlayers = new Set<string>();
|
||||||
|
|
||||||
@@ -80,8 +82,8 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
async handleGroupUpdatedIPC(event: IpcGroupUpdated) {
|
async handleGroupUpdatedIPC(event: IpcGroupUpdated) {
|
||||||
const permissions = new messages.GroupPermissions(
|
const permissions = new messages.GroupPermissions(
|
||||||
event.permissions.is_blacklist,
|
event.permissions.is_blacklist,
|
||||||
event.permissions.permissions,
|
event.permissions.permissions ?? [],
|
||||||
);
|
)
|
||||||
|
|
||||||
if (event.group_id === undefined) {
|
if (event.group_id === undefined) {
|
||||||
this.skipSendingPermissions.add(event.group_name);
|
this.skipSendingPermissions.add(event.group_name);
|
||||||
@@ -89,6 +91,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
new messages.GroupCreateRequest(event.group_name, permissions),
|
new messages.GroupCreateRequest(event.group_name, permissions),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
this.skipSendingUpdate.add(event.group_id);
|
||||||
await this.instance.sendTo("controller", new messages.GroupUpdateRequest(
|
await this.instance.sendTo("controller", new messages.GroupUpdateRequest(
|
||||||
new messages.GroupRecord(event.group_id, event.group_name, permissions),
|
new messages.GroupRecord(event.group_id, event.group_name, permissions),
|
||||||
));
|
));
|
||||||
@@ -99,6 +102,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
if (event.group_id === undefined) {
|
if (event.group_id === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.skipSendingUpdate.add(event.group_id);
|
||||||
await this.instance.sendTo("controller", new messages.GroupDeleteRequest(event.group_id));
|
await this.instance.sendTo("controller", new messages.GroupDeleteRequest(event.group_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,14 +129,19 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async luaSendInitialGroups(groups: messages.GroupRecord[]) {
|
async luaSendInitialGroups(groups: messages.GroupRecord[]) {
|
||||||
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.instance.sendRcon(`/sc exp_groups.initialise_groups(helpers.json_to_table${JSON.stringify(groups)})`)
|
await this.luaSend("initialise_groups", groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
async luaSendGroupUpdate(group: messages.GroupRecord) {
|
async luaSendGroupUpdate(group: messages.GroupRecord) {
|
||||||
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.skipSendingUpdate.has(group.id)) {
|
||||||
|
this.skipSendingUpdate.delete(group.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,17 +151,21 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
delete (json as any).permissions;
|
delete (json as any).permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.instance.sendRcon(`/sc exp_groups.receive_group_update(helpers.json_to_table${JSON.stringify(json)})`)
|
await this.luaSend("receive_group_update", json);
|
||||||
}
|
}
|
||||||
|
|
||||||
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
|
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
|
||||||
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.instance.sendRcon(`/sc exp_groups.initialise_groups(helpers.receive_assignment_update${JSON.stringify(assignment)})`)
|
await this.luaSend("receive_assignment_update", assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
async luaSetEmitEvents(emitEvents: boolean) {
|
async luaSetEmitEvents(emitEvents: boolean) {
|
||||||
await this.instance.sendRcon(`/sc exp_groups.set_emit_events(${emitEvents})`)
|
await this.luaSend("set_emit_events", emitEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
async luaSend(receiver: string, json: any) {
|
||||||
|
await this.instance.sendRcon(`/c exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ local ExpGroups = {}
|
|||||||
|
|
||||||
--- @class ExpPermissionGroups.GroupPermissions
|
--- @class ExpPermissionGroups.GroupPermissions
|
||||||
--- @field is_blacklist boolean
|
--- @field is_blacklist boolean
|
||||||
--- @field permissions string[]
|
--- @field permissions string[]?
|
||||||
|
|
||||||
--- @class ExpPermissionGroups.GroupRecord
|
--- @class ExpPermissionGroups.GroupRecord
|
||||||
--- @field id number
|
--- @field id number
|
||||||
@@ -21,8 +21,8 @@ local ExpGroups = {}
|
|||||||
|
|
||||||
--- @class ExpPermissionGroups.AssignmentRecord
|
--- @class ExpPermissionGroups.AssignmentRecord
|
||||||
--- @field name string
|
--- @field name string
|
||||||
--- @field groupId number
|
--- @field group_id number
|
||||||
--- @field isDeleted boolean
|
--- @field is_deleted boolean
|
||||||
|
|
||||||
--- @class ExpPermissionGroups.ScriptData
|
--- @class ExpPermissionGroups.ScriptData
|
||||||
--- @field factorio_to_clusterio_id table<number, number?>
|
--- @field factorio_to_clusterio_id table<number, number?>
|
||||||
@@ -73,7 +73,7 @@ end
|
|||||||
local function decode_group_permissions(group, permissions)
|
local function decode_group_permissions(group, permissions)
|
||||||
-- Construct a hash map for faster lookup
|
-- Construct a hash map for faster lookup
|
||||||
local action_map = {}
|
local action_map = {}
|
||||||
for _, input_action_name in pairs(permissions.permissions) do
|
for _, input_action_name in pairs(assert(permissions.permissions)) do
|
||||||
action_map[input_action_name] = true
|
action_map[input_action_name] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -112,11 +112,15 @@ local function encode_group_permissions(group)
|
|||||||
|
|
||||||
-- Return the whitelist if it is smaller
|
-- Return the whitelist if it is smaller
|
||||||
if blacklist_index > whitelist_index then
|
if blacklist_index > whitelist_index then
|
||||||
return { is_blacklist = false, permissions = whitelist }
|
return whitelist_index > 0
|
||||||
|
and { is_blacklist = false, permissions = whitelist }
|
||||||
|
or { is_blacklist = false }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Otherwise return the blacklist as it is smaller
|
-- Otherwise return the blacklist as it is smaller
|
||||||
return { is_blacklist = true, permissions = blacklist }
|
return blacklist_index > 0
|
||||||
|
and { is_blacklist = true, permissions = blacklist }
|
||||||
|
or { is_blacklist = true }
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -167,9 +171,9 @@ end
|
|||||||
--- Update an assignment by moving the player to their new group
|
--- Update an assignment by moving the player to their new group
|
||||||
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||||
local function update_assignment(assignment_record)
|
local function update_assignment(assignment_record)
|
||||||
assert(not assignment_record.isDeleted)
|
assert(not assignment_record.is_deleted)
|
||||||
|
|
||||||
local group = script_data.clusterio_id_to_group[assignment_record.groupId]
|
local group = script_data.clusterio_id_to_group[assignment_record.group_id]
|
||||||
local player = assert(game.get_player(assignment_record.name))
|
local player = assert(game.get_player(assignment_record.name))
|
||||||
if group then
|
if group then
|
||||||
group.add_player(player)
|
group.add_player(player)
|
||||||
@@ -179,7 +183,7 @@ end
|
|||||||
--- Clear an assignment by moving the player to the default group
|
--- Clear an assignment by moving the player to the default group
|
||||||
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||||
local function delete_assignment(assignment_record)
|
local function delete_assignment(assignment_record)
|
||||||
assert(assignment_record.isDeleted)
|
assert(assignment_record.is_deleted)
|
||||||
|
|
||||||
local default_group = get_default_group()
|
local default_group = get_default_group()
|
||||||
local player = assert(game.get_player(assignment_record.name))
|
local player = assert(game.get_player(assignment_record.name))
|
||||||
@@ -258,7 +262,7 @@ function ExpGroups.receive_assignment_update(assignment_record)
|
|||||||
local _emit_events = script_data.emit_updates
|
local _emit_events = script_data.emit_updates
|
||||||
script_data.emit_updates = false
|
script_data.emit_updates = false
|
||||||
|
|
||||||
if assignment_record.isDeleted then
|
if assignment_record.is_deleted then
|
||||||
delete_assignment(assignment_record)
|
delete_assignment(assignment_record)
|
||||||
else
|
else
|
||||||
update_assignment(assignment_record)
|
update_assignment(assignment_record)
|
||||||
@@ -378,7 +382,7 @@ local function on_permission_group_added(event)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
emit_group_update(event.group)
|
mark_group_dirty(event.group)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Handle deletion of permission groups
|
--- Handle deletion of permission groups
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"control.lua"
|
"control.lua"
|
||||||
],
|
],
|
||||||
"require": [
|
"require": [
|
||||||
|
"globals.lua"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clusterio": "*"
|
"clusterio": "*"
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
import { Modal, Form, Select } from "antd";
|
import { Modal, Form, Select } from "antd";
|
||||||
|
|
||||||
import { ControlContext, useUsers } from "@clusterio/web_ui";
|
import { ControlContext, useUsers } from "@clusterio/web_ui";
|
||||||
import * as messages from "../../messages";
|
import * as messages from "../../messages";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
export default function AssignmentForm({ open, setOpen, initial }: any) {
|
export default function AssignmentForm({ open, setOpen, initial }: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
initial?: messages.AssignmentRecord,
|
||||||
|
}) {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const plugin = control.plugins.get("exp_groups") as any;
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
|
||||||
const [groups] = plugin.useGroups();
|
const [groups] = plugin.useGroups();
|
||||||
const [users] = useUsers();
|
const [users] = useUsers();
|
||||||
@@ -16,7 +21,10 @@ export default function AssignmentForm({ open, setOpen, initial }: any) {
|
|||||||
function submit(values: any) {
|
function submit(values: any) {
|
||||||
if (initial) {
|
if (initial) {
|
||||||
control.send(new messages.AssignmentUpdateRequest(
|
control.send(new messages.AssignmentUpdateRequest(
|
||||||
new messages.AssignmentRecord(values.name, values.groupId)
|
new messages.AssignmentRecord(
|
||||||
|
values.name,
|
||||||
|
values.groupId,
|
||||||
|
)
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
control.send(new messages.AssignmentCreateRequest(
|
control.send(new messages.AssignmentCreateRequest(
|
||||||
@@ -28,13 +36,20 @@ export default function AssignmentForm({ open, setOpen, initial }: any) {
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
form.resetFields();
|
||||||
|
form.setFieldsValue(initial);
|
||||||
|
}
|
||||||
|
}, [open, initial]);
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
title={initial ? "Edit Assignment" : "Create Assignment"}
|
title={initial ? "Edit Assignment" : "Create Assignment"}
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
>
|
>
|
||||||
<Form form={form} initialValues={initial} onFinish={submit}>
|
<Form form={form} onFinish={submit}>
|
||||||
<Form.Item name="name" label="Player" rules={[{ required: true }]}>
|
<Form.Item name="name" label="Player" rules={[{ required: true }]}>
|
||||||
<Select disabled={Boolean(initial)} showSearch>
|
<Select disabled={Boolean(initial)} showSearch>
|
||||||
{[...users.values()].map(u => (
|
{[...users.values()].map(u => (
|
||||||
|
|||||||
@@ -1,40 +1,86 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState, useRef } from "react";
|
||||||
import { Table, Button, Popconfirm, Space } from "antd";
|
import { Table, Button, Space, Input, InputRef } from "antd";
|
||||||
import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
|
import { EditOutlined, SearchOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
import { ControlContext, useAccount } from "@clusterio/web_ui";
|
import { ControlContext, useAccount } from "@clusterio/web_ui";
|
||||||
import * as messages from "../../messages";
|
import { AssignmentDeleteRequest, AssignmentRecord } from "../../messages";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
import AssignmentForm from "./AssignmentForm";
|
import AssignmentForm from "./AssignmentForm";
|
||||||
|
import DeletedConfirm from "./DeleteConfirm";
|
||||||
|
|
||||||
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
|
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
|
||||||
|
|
||||||
export default function AssignmentsTable() {
|
export default function AssignmentsTable() {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const plugin = control.plugins.get("exp_groups") as any;
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
|
||||||
const [assignments, synced] = plugin.useAssignments();
|
const searchInput = useRef<InputRef>(null);
|
||||||
const [groups] = plugin.useGroups();
|
|
||||||
|
|
||||||
const [editing, setEditing] = useState<any>(null);
|
const [assignments, assignmentsSynced] = plugin.useAssignments();
|
||||||
|
const [groups, groupsSynced] = plugin.useGroups();
|
||||||
|
|
||||||
|
const [editing, setEditing] = useState<AssignmentRecord | undefined>();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const assignmentsArray = [...assignments.values()];
|
||||||
|
|
||||||
|
const groupFilters = [...groups.values()]
|
||||||
|
.filter(group => assignmentsArray.some(assignment => assignment.groupId === group.id))
|
||||||
|
.map(group => ({ text: group.name, value: group.id }))
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Player",
|
title: "Player",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
sorter: (a: any, b: any) => strcmp(a.name, b.name),
|
sorter: (a: AssignmentRecord, b: AssignmentRecord) => strcmp(a.name, b.name),
|
||||||
|
filterIcon: (filtered: boolean) => (
|
||||||
|
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
|
||||||
|
),
|
||||||
|
onFilter: (value, record: AssignmentRecord) => (
|
||||||
|
record.name.toLowerCase().includes(String(value).toLowerCase())
|
||||||
|
),
|
||||||
|
filterDropdownProps: {
|
||||||
|
onOpenChange: (open: boolean) => open && setTimeout(() => searchInput.current?.select(), 100),
|
||||||
|
},
|
||||||
|
filterDropdown: ({ selectedKeys, setSelectedKeys, confirm, clearFilters }) => (
|
||||||
|
<div style={{ padding: 4 }} onKeyDown={(e) => e.stopPropagation()}>
|
||||||
|
<Input.Search
|
||||||
|
allowClear
|
||||||
|
ref={searchInput}
|
||||||
|
placeholder={"Search username"}
|
||||||
|
value={selectedKeys[0]}
|
||||||
|
onChange={(e) => setSelectedKeys([e.target.value])}
|
||||||
|
onSearch={() => confirm({ closeDropdown: false })}
|
||||||
|
onClear={() => {
|
||||||
|
clearFilters?.({ closeDropdown: false });
|
||||||
|
confirm({ closeDropdown: true });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Group",
|
title: "Group",
|
||||||
render: (_: any, a: any) => groups.get(a.groupId)?.name ?? a.groupId,
|
width: "40%",
|
||||||
|
filters: groupFilters,
|
||||||
|
onFilter: (value, record: AssignmentRecord) => (
|
||||||
|
record.groupId === value
|
||||||
|
),
|
||||||
|
render: (_: any, a: any) => (
|
||||||
|
groups.get(a.groupId)?.name ?? a.groupId
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
...(account.hasAnyPermission(
|
||||||
|
"exp_groups.assignment.update",
|
||||||
|
"exp_groups.assignment.delete",
|
||||||
|
) ? [{
|
||||||
title: "Actions",
|
title: "Actions",
|
||||||
render: (_: any, record: any) => (
|
width: "10%",
|
||||||
|
render: (_: any, record: AssignmentRecord) => (
|
||||||
<Space>
|
<Space>
|
||||||
{account.hasPermission("exp_groups.assignment.update") &&
|
{account.hasPermission("exp_groups.assignment.update") &&
|
||||||
<Button icon={<EditOutlined />} onClick={() => {
|
<Button icon={<EditOutlined />} onClick={() => {
|
||||||
@@ -42,22 +88,22 @@ export default function AssignmentsTable() {
|
|||||||
setOpen(true);
|
setOpen(true);
|
||||||
}} />
|
}} />
|
||||||
}
|
}
|
||||||
{account.hasPermission("exp_groups.assignment.delete") &&
|
{account.hasPermission("exp_groups.assignment.delete") && <DeletedConfirm
|
||||||
<Popconfirm
|
onConfirm={() => control.send(new AssignmentDeleteRequest(record.name))}
|
||||||
title="Delete?"
|
/>}
|
||||||
onConfirm={() => control.send(new messages.AssignmentDeleteRequest(record.name))}
|
|
||||||
>
|
|
||||||
<Button danger icon={<DeleteOutlined />} />
|
|
||||||
</Popconfirm>
|
|
||||||
}
|
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
}] : []),
|
||||||
]}
|
]}
|
||||||
dataSource={[...assignments.values()]}
|
dataSource={assignmentsArray}
|
||||||
loading={!synced}
|
loading={!assignmentsSynced || !groupsSynced}
|
||||||
pagination={false}
|
|
||||||
rowKey={(a) => a.name}
|
rowKey={(a) => a.name}
|
||||||
|
pagination={{
|
||||||
|
defaultPageSize: 50,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ["10", "20", "50", "100", "200"],
|
||||||
|
showTotal: (total: number) => `${total} Assignments`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AssignmentForm open={open} setOpen={setOpen} initial={editing} />
|
<AssignmentForm open={open} setOpen={setOpen} initial={editing} />
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button, Popconfirm } from "antd";
|
||||||
|
import { DeleteOutlined } from "@ant-design/icons";
|
||||||
|
import { TooltipPlacement } from "antd/es/tooltip";
|
||||||
|
|
||||||
|
export default function DeletedConfirm(props: { onConfirm: () => void, placement?: TooltipPlacement }) {
|
||||||
|
return <Popconfirm
|
||||||
|
title="Delete?"
|
||||||
|
okText="Delete"
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
onConfirm={props.onConfirm}
|
||||||
|
placement={props.placement}
|
||||||
|
>
|
||||||
|
<Button danger icon={<DeleteOutlined />} />
|
||||||
|
</Popconfirm>
|
||||||
|
}
|
||||||
@@ -1,38 +1,42 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
import { Modal, Form, Input, Switch } from "antd";
|
import { Modal, Form, Input, Switch } from "antd";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { ControlContext } from "@clusterio/web_ui";
|
import { ControlContext } from "@clusterio/web_ui";
|
||||||
import * as messages from "../../messages";
|
import * as messages from "../../messages";
|
||||||
|
|
||||||
export default function GroupForm({ open, setOpen, initial }: any) {
|
export default function GroupForm({ open, setOpen }: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
}) {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const [form] = Form.useForm();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
async function submit(values: any) {
|
const [form] = Form.useForm();
|
||||||
if (initial) {
|
|
||||||
control.send(new messages.GroupUpdateRequest(
|
|
||||||
new messages.GroupRecord(
|
|
||||||
initial.id, values.name,
|
|
||||||
new messages.GroupPermissions(Boolean(values.isBlacklist), [])
|
|
||||||
)
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
const group = await control.send(new messages.GroupCreateRequest(
|
|
||||||
values.name, new messages.GroupPermissions(Boolean(values.isBlacklist), [])
|
|
||||||
));
|
|
||||||
navigate(`/exp_groups/${group.id}/view`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function submit(values: any) {
|
||||||
|
const group = await control.send(new messages.GroupCreateRequest(
|
||||||
|
values.name, new messages.GroupPermissions(Boolean(values.isBlacklist), [])
|
||||||
|
));
|
||||||
|
navigate(`/exp_groups/${group.id}/view`);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Modal title="Group" open={open} onCancel={() => setOpen(false)} onOk={() => form.submit()}>
|
useEffect(() => {
|
||||||
<Form form={form} initialValues={initial} onFinish={submit}>
|
if (open) form.resetFields();
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return <Modal
|
||||||
|
title="Create Group"
|
||||||
|
open={open}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onOk={() => form.submit()}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={submit}>
|
||||||
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="isBlacklist" label="Grant all by default" valuePropName="checked">
|
<Form.Item name="isBlacklist" label="Grant all by default" valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Button, Checkbox, Input, Space, Spin, Alert } from "antd";
|
import { Button, Checkbox, Input, Space, Spin, Alert } from "antd";
|
||||||
|
|
||||||
import { ControlContext, useAccount, useDefaultModPack } from "@clusterio/web_ui";
|
import { ControlContext, useAccount, useDefaultModPack, PageLayout, PageHeader, notifyErrorHandler } from "@clusterio/web_ui";
|
||||||
import { PageLayout, PageHeader, notifyErrorHandler } from "@clusterio/web_ui";
|
import DeletedConfirm from "./DeleteConfirm";
|
||||||
|
|
||||||
import * as messages from "../../messages";
|
import * as messages from "../../messages";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
const DOMAIN_RULES: Record<string, string[]> = {
|
const DOMAIN_MAPPING = {
|
||||||
"Admin": ["admin", "cheat", "permission", "infinity", "editor", "spawn"],
|
"Admin": ["admin", "cheat", "permission", "infinity", "editor", "spawn"],
|
||||||
"Building & Crafting": ["mining", "build", "craft", "deconstruct", "rotate", "entity"],
|
"Building & Crafting": ["mining", "build", "craft", "deconstruct", "rotate", "entity"],
|
||||||
"Inventory": ["inventory", "stack", "cursor", "slot", "quick_bar", "item", "equipment"],
|
"Inventory": ["inventory", "stack", "cursor", "slot", "quick_bar", "item", "equipment"],
|
||||||
@@ -24,7 +25,7 @@ const DOMAIN_RULES: Record<string, string[]> = {
|
|||||||
function getDomain(name: string) {
|
function getDomain(name: string) {
|
||||||
const n = name.toLowerCase();
|
const n = name.toLowerCase();
|
||||||
|
|
||||||
for (const [domain, keywords] of Object.entries(DOMAIN_RULES)) {
|
for (const [domain, keywords] of Object.entries(DOMAIN_MAPPING)) {
|
||||||
if (keywords.some(k => n.includes(k))) return domain;
|
if (keywords.some(k => n.includes(k))) return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ function getDomain(name: string) {
|
|||||||
|
|
||||||
export default function GroupViewPage() {
|
export default function GroupViewPage() {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const plugin = control.plugins.get("exp_groups") as any;
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
const navigate = useNavigate();
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -52,6 +54,7 @@ export default function GroupViewPage() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
const canUpdate = Boolean(account.hasPermission("exp_groups.group.update"));
|
const canUpdate = Boolean(account.hasPermission("exp_groups.group.update"));
|
||||||
|
const canDelete = Boolean(account.hasPermission("exp_groups.group.delete"));
|
||||||
|
|
||||||
// fetch defines
|
// fetch defines
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -61,14 +64,10 @@ export default function GroupViewPage() {
|
|||||||
const assetName = defaultModPack.exportManifest.assets.defines;
|
const assetName = defaultModPack.exportManifest.assets.defines;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
const response = await fetch(`${staticRoot}static/${assetName}`);
|
||||||
const response = await fetch(`${staticRoot}static/${assetName}`);
|
const json = await response.json();
|
||||||
const json = await response.json();
|
setDefinesJson(json.input_action ?? {});
|
||||||
setDefinesJson(json.input_action ?? {});
|
})().catch(notifyErrorHandler("Failed to load permissions"));
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to load defines", err);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [defaultModPack]);
|
}, [defaultModPack]);
|
||||||
|
|
||||||
// initialise permissions
|
// initialise permissions
|
||||||
@@ -94,17 +93,13 @@ export default function GroupViewPage() {
|
|||||||
}, [group]);
|
}, [group]);
|
||||||
|
|
||||||
// derived data
|
// derived data
|
||||||
const allPermissions = useMemo(
|
const allPermissions = useMemo(() => (
|
||||||
() => Object.keys(definesJson ?? {}),
|
Object.keys(definesJson ?? {})
|
||||||
[definesJson]
|
), [definesJson]);
|
||||||
);
|
|
||||||
|
|
||||||
const filtered = useMemo(
|
const filtered = useMemo(() => allPermissions.filter(p =>
|
||||||
() => allPermissions.filter(p =>
|
p.toLowerCase().includes(search.toLowerCase())
|
||||||
p.toLowerCase().includes(search.toLowerCase())
|
), [allPermissions, search]);
|
||||||
),
|
|
||||||
[allPermissions, search]
|
|
||||||
);
|
|
||||||
|
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
const map = new Map<string, string[]>();
|
const map = new Map<string, string[]>();
|
||||||
@@ -169,8 +164,16 @@ export default function GroupViewPage() {
|
|||||||
</PageLayout>;
|
</PageLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PageLayout nav={[{ name: "exp_groups", path: "/exp_groups" }, { name: group.name }]}>
|
return <PageLayout nav={[{ name: "Permission Groups", path: "/permission_groups" }, { name: group.name }]}>
|
||||||
<PageHeader title={group.name} />
|
<PageHeader
|
||||||
|
title={group.name}
|
||||||
|
extra={<Space wrap>
|
||||||
|
{canDelete && <DeletedConfirm placement="bottomRight" onConfirm={async () => {
|
||||||
|
control.send(new messages.GroupDeleteRequest(group.id));
|
||||||
|
navigate(`/permission_groups`);
|
||||||
|
}}/>}
|
||||||
|
</Space>}
|
||||||
|
/>
|
||||||
|
|
||||||
{edited && (
|
{edited && (
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -204,7 +207,8 @@ export default function GroupViewPage() {
|
|||||||
<Alert
|
<Alert
|
||||||
type="warning"
|
type="warning"
|
||||||
message="Missing export"
|
message="Missing export"
|
||||||
description="An export of the default mod pack is required to see all available permissions."
|
description="An export of the default mod pack is required to modify permissions."
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
showIcon
|
showIcon
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import React, { useContext, useState } from "react";
|
|||||||
import { Table } from "antd";
|
import { Table } from "antd";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { ControlContext, useAccount } from "@clusterio/web_ui";
|
import { ControlContext } from "@clusterio/web_ui";
|
||||||
|
import { GroupRecord } from "../../messages";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
import GroupForm from "./GroupForm";
|
import GroupForm from "./GroupForm";
|
||||||
|
|
||||||
@@ -10,7 +12,7 @@ const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base"
|
|||||||
|
|
||||||
export default function GroupsTable() {
|
export default function GroupsTable() {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const plugin = control.plugins.get("exp_groups") as any;
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [groups, synced] = plugin.useGroups();
|
const [groups, synced] = plugin.useGroups();
|
||||||
@@ -23,23 +25,22 @@ export default function GroupsTable() {
|
|||||||
{
|
{
|
||||||
title: "Name",
|
title: "Name",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
sorter: (a: any, b: any) => strcmp(a.name, b.name),
|
sorter: (a: any, b: GroupRecord) => strcmp(a.name, b.name),
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Type",
|
|
||||||
render: (_: any, g: any) => g.permissions.isBlacklist ? "Blacklist" : "Whitelist",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Permissions",
|
title: "Permissions",
|
||||||
render: (_: any, g: any) => g.permissions.permissions.length,
|
width: "50%",
|
||||||
|
render: (_: any, g: GroupRecord) => (
|
||||||
|
`${g.permissions.permissions.length} ${g.permissions.isBlacklist ? "Disallowed" : "Allowed"}`
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
dataSource={[...groups.values()]}
|
dataSource={[...groups.values()]}
|
||||||
loading={!synced}
|
loading={!synced}
|
||||||
pagination={false}
|
|
||||||
rowKey={(g) => g.id}
|
rowKey={(g) => g.id}
|
||||||
|
pagination={false}
|
||||||
onRow={(g) => ({
|
onRow={(g) => ({
|
||||||
onClick: () => navigate(`/exp_groups/${g.id}/view`),
|
onClick: () => navigate(`/permission_groups/${g.id}/view`),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
import { Modal, Form, Select, InputNumber, Switch } from "antd";
|
import { Modal, Form, Select, InputNumber, Switch, Alert } from "antd";
|
||||||
|
|
||||||
import { ControlContext, useRoles } from "@clusterio/web_ui";
|
import { ControlContext, useRoles } from "@clusterio/web_ui";
|
||||||
import * as messages from "../../messages";
|
import { RoleMappingRecord, RoleMappingCreateRequest, RoleMappingUpdateRequest } from "../../messages";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
export default function RoleMappingForm({ open, setOpen, initial }: any) {
|
export default function RoleMappingForm({ open, setOpen, initial }: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
initial?: RoleMappingRecord,
|
||||||
|
}) {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const plugin = control.plugins.get("exp_groups") as any;
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
|
||||||
const [groups] = plugin.useGroups();
|
const [groups] = plugin.useGroups();
|
||||||
const [roles] = useRoles();
|
const [roles] = useRoles();
|
||||||
@@ -14,18 +19,18 @@ export default function RoleMappingForm({ open, setOpen, initial }: any) {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
function submit(values: any) {
|
function submit(values: any) {
|
||||||
const record = new messages.RoleMappingRecord(
|
|
||||||
initial?.id,
|
|
||||||
new Set(values.roleIds),
|
|
||||||
values.groupId,
|
|
||||||
values.priority,
|
|
||||||
values.enabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (initial) {
|
if (initial) {
|
||||||
control.send(new messages.RoleMappingUpdateRequest(record));
|
control.send(new RoleMappingUpdateRequest(
|
||||||
|
new RoleMappingRecord(
|
||||||
|
initial.id,
|
||||||
|
new Set(values.roleIds),
|
||||||
|
values.groupId,
|
||||||
|
values.priority,
|
||||||
|
values.enabled,
|
||||||
|
)
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
control.send(new messages.RoleMappingCreateRequest(
|
control.send(new RoleMappingCreateRequest(
|
||||||
values.roleIds,
|
values.roleIds,
|
||||||
values.groupId,
|
values.groupId,
|
||||||
values.priority,
|
values.priority,
|
||||||
@@ -36,30 +41,39 @@ export default function RoleMappingForm({ open, setOpen, initial }: any) {
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
form.resetFields();
|
||||||
|
form.setFieldsValue({
|
||||||
|
...initial,
|
||||||
|
roleIds: initial ? [...initial.roleIds] : [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [open, initial]);
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
title={initial ? "Edit Role Mapping" : "Create Role Mapping"}
|
title={initial ? "Edit Role Mapping" : "Create Role Mapping"}
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
>
|
>
|
||||||
<Form
|
<Alert
|
||||||
form={form}
|
type="info"
|
||||||
initialValues={{
|
showIcon
|
||||||
...initial,
|
message="How role mappings work"
|
||||||
roleIds: initial ? [...initial.roleIds] : [],
|
style={{ marginBottom: 16 }}
|
||||||
}}
|
description={
|
||||||
onFinish={submit}
|
<ul style={{ paddingLeft: 16, margin: 0 }}>
|
||||||
>
|
<li>Manual assignments always take priority.</li>
|
||||||
<Form.Item name="roleIds" label="Roles" rules={[{ required: true }]}>
|
<li>Mappings are checked in priority order (highest first).</li>
|
||||||
<Select mode="multiple">
|
<li>A mapping applies only if the user has <b>all listed roles</b>.</li>
|
||||||
{[...roles.values()].map(r => (
|
<li>The first matching mapping assigns the group.</li>
|
||||||
<Select.Option key={r.id} value={r.id}>
|
<li>If none match, the player is given all permissions.</li>
|
||||||
{r.name}
|
</ul>
|
||||||
</Select.Option>
|
}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
|
<Form form={form} onFinish={submit}>
|
||||||
<Form.Item name="groupId" label="Group" rules={[{ required: true }]}>
|
<Form.Item name="groupId" label="Group" rules={[{ required: true }]}>
|
||||||
<Select>
|
<Select>
|
||||||
{[...groups.values()].map(g => (
|
{[...groups.values()].map(g => (
|
||||||
@@ -70,7 +84,17 @@ export default function RoleMappingForm({ open, setOpen, initial }: any) {
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="priority" label="Priority" initialValue={0}>
|
<Form.Item name="roleIds" label="Roles" rules={[{ required: true }]}>
|
||||||
|
<Select mode="multiple">
|
||||||
|
{[...roles.values()].map(r => (
|
||||||
|
<Select.Option key={r.id} value={r.id}>
|
||||||
|
{r.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="priority" label="Priority" initialValue={100}>
|
||||||
<InputNumber style={{ width: "100%" }} />
|
<InputNumber style={{ width: "100%" }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
@@ -1,72 +1,105 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { Table, Button, Popconfirm, Space, Tag } from "antd";
|
import { Table, Button, Space, Tag } from "antd";
|
||||||
import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
|
import { EditOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
import { ControlContext, useAccount, useRoles } from "@clusterio/web_ui";
|
import { ControlContext, useAccount, useRoles } from "@clusterio/web_ui";
|
||||||
import * as messages from "../../messages";
|
import { RoleMappingRecord, RoleMappingDeleteRequest } from "../../messages";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
import RoleMappingForm from "./RoleMappingForm";
|
import RoleMappingForm from "./RoleMappingForm";
|
||||||
|
import DeletedConfirm from "./DeleteConfirm";
|
||||||
|
|
||||||
export default function RoleMappingsTable() {
|
export default function RoleMappingsTable() {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const plugin = control.plugins.get("exp_groups") as any;
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
|
||||||
const [roleMappings, synced] = plugin.useRoleMappings();
|
const [roleMappings, roleMappingsSynced] = plugin.useRoleMappings();
|
||||||
const [groups] = plugin.useGroups();
|
const [groups, groupsSynced] = plugin.useGroups();
|
||||||
const [roles] = useRoles();
|
const [roles, rolesSynced] = useRoles();
|
||||||
|
|
||||||
const [editing, setEditing] = useState<any>(null);
|
const [editing, setEditing] = useState<RoleMappingRecord | undefined>();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const roleMappingArray = [...roleMappings.values()];
|
||||||
|
|
||||||
|
const roleFilters = [...roles.values()]
|
||||||
|
.filter(role => roleMappingArray.some(mapping => mapping.roleIds.has(role.id)))
|
||||||
|
.map(role => ({ text: role.name, value: role.id }));
|
||||||
|
|
||||||
|
const groupFilters = [...groups.values()]
|
||||||
|
.filter(group => roleMappingArray.some(mapping => mapping.groupId === group.id))
|
||||||
|
.map(group => ({ text: group.name, value: group.id }))
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Priority",
|
title: "Priority",
|
||||||
dataIndex: "priority",
|
dataIndex: "priority",
|
||||||
|
width: "10%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Roles",
|
title: "Roles",
|
||||||
render: (_: any, m: any) => [...m.roleIds].map((id: number) => {
|
filters: roleFilters,
|
||||||
const role = roles.get(id);
|
onFilter: (value, record: RoleMappingRecord) => (
|
||||||
return <Tag key={id}>{role?.name ?? id}</Tag>;
|
record.roleIds.has(value as number)
|
||||||
}),
|
),
|
||||||
|
render: (_: any, record: RoleMappingRecord) => (
|
||||||
|
[...record.roleIds].map(id => <Tag key={id}>{roles.get(id)?.name ?? id}</Tag>)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Group",
|
title: "Group",
|
||||||
render: (_: any, m: any) => groups.get(m.groupId)?.name ?? m.groupId,
|
width: "30%",
|
||||||
|
filters: groupFilters,
|
||||||
|
onFilter: (value, record: RoleMappingRecord) => (
|
||||||
|
record.groupId === value
|
||||||
|
),
|
||||||
|
render: (_: any, record: RoleMappingRecord) => (
|
||||||
|
groups.get(record.groupId)?.name ?? record.groupId
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Enabled",
|
title: "Enabled",
|
||||||
render: (_: any, m: any) => m.enabled ? "Yes" : "No",
|
width: "10%",
|
||||||
},
|
filters: [
|
||||||
{
|
{ text: "Enabled", value: true },
|
||||||
title: "Actions",
|
{ text: "Disabled", value: false },
|
||||||
render: (_: any, record: any) => (
|
],
|
||||||
<Space>
|
onFilter: (value, record: RoleMappingRecord) => (
|
||||||
{account.hasPermission("exp_groups.role_mapping.update") &&
|
record.enabled === value
|
||||||
<Button icon={<EditOutlined />} onClick={() => {
|
),
|
||||||
setEditing(record);
|
render: (_: any, record: RoleMappingRecord) => (
|
||||||
setOpen(true);
|
record.enabled ? "Yes" : "No"
|
||||||
}} />
|
|
||||||
}
|
|
||||||
{account.hasPermission("exp_groups.role_mapping.delete") &&
|
|
||||||
<Popconfirm
|
|
||||||
title="Delete?"
|
|
||||||
onConfirm={() => control.send(new messages.RoleMappingDeleteRequest(record.id))}
|
|
||||||
>
|
|
||||||
<Button danger icon={<DeleteOutlined />} />
|
|
||||||
</Popconfirm>
|
|
||||||
}
|
|
||||||
</Space>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
...(account.hasAnyPermission(
|
||||||
|
"exp_groups.role_mapping.update",
|
||||||
|
"exp_groups.role_mapping.delete",
|
||||||
|
) ? [{
|
||||||
|
title: "Actions",
|
||||||
|
width: "10%",
|
||||||
|
render: (_: any, record: any) => (
|
||||||
|
<Space>
|
||||||
|
{account.hasPermission("exp_groups.role_mapping.update") && <Button
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setEditing(record);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
{account.hasPermission("exp_groups.role_mapping.delete") && <DeletedConfirm
|
||||||
|
onConfirm={() => control.send(new RoleMappingDeleteRequest(record.id))}
|
||||||
|
/>}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
}] : []),
|
||||||
]}
|
]}
|
||||||
dataSource={[...roleMappings.values()].sort((a, b) => b.priority - a.priority)}
|
dataSource={roleMappingArray.sort((a, b) => b.priority - a.priority)}
|
||||||
loading={!synced}
|
loading={!roleMappingsSynced || !groupsSynced || !rolesSynced}
|
||||||
pagination={false}
|
|
||||||
rowKey={(m) => m.id}
|
rowKey={(m) => m.id}
|
||||||
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RoleMappingForm open={open} setOpen={setOpen} initial={editing} />
|
<RoleMappingForm open={open} setOpen={setOpen} initial={editing} />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function ExpGroupsPage() {
|
|||||||
const [assignmentOpen, setAssignmentOpen] = useState(false);
|
const [assignmentOpen, setAssignmentOpen] = useState(false);
|
||||||
const [roleMappingOpen, setRoleMappingOpen] = useState(false);
|
const [roleMappingOpen, setRoleMappingOpen] = useState(false);
|
||||||
|
|
||||||
return <PageLayout nav={[{ name: "exp_groups" }]}>
|
return <PageLayout nav={[{ name: "Permission Groups" }]}>
|
||||||
<PageHeader title="Permission Groups" />
|
<PageHeader title="Permission Groups" />
|
||||||
|
|
||||||
{account.hasPermission("exp_groups.group.list") && <>
|
{account.hasPermission("exp_groups.group.list") && <>
|
||||||
@@ -73,7 +73,7 @@ export class WebPlugin extends BaseWebPlugin {
|
|||||||
async init() {
|
async init() {
|
||||||
this.pages = [
|
this.pages = [
|
||||||
{
|
{
|
||||||
path: "/exp_groups",
|
path: "/permission_groups",
|
||||||
sidebarName: "Permission Groups",
|
sidebarName: "Permission Groups",
|
||||||
permission: (account => account.hasAnyPermission(
|
permission: (account => account.hasAnyPermission(
|
||||||
"exp_groups.group.list",
|
"exp_groups.group.list",
|
||||||
@@ -83,8 +83,8 @@ export class WebPlugin extends BaseWebPlugin {
|
|||||||
content: <ExpGroupsPage />,
|
content: <ExpGroupsPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/exp_groups/:id/view",
|
path: "/permission_groups/:id/view",
|
||||||
sidebarPath: "/exp_groups",
|
sidebarPath: "/permission_groups",
|
||||||
permission: "exp_groups.group.get",
|
permission: "exp_groups.group.get",
|
||||||
content: <GroupViewPage />,
|
content: <GroupViewPage />,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user