diff --git a/packages/web_ui/src/components/InstanceConsole.jsx b/packages/web_ui/src/components/InstanceConsole.jsx
index 30e8f76f..aa2d50ca 100644
--- a/packages/web_ui/src/components/InstanceConsole.jsx
+++ b/packages/web_ui/src/components/InstanceConsole.jsx
@@ -3,6 +3,7 @@ import { Typography } from "antd";
import { libLink } from "@clusterio/lib";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
const { Paragraph } = Typography;
@@ -49,6 +50,7 @@ function formatLog(info, key) {
}
export default function InstanceConsole(props) {
+ let account = useAccount();
let control = useContext(ControlContext);
let anchor = useRef();
let [pastLines, setPastLines] = useState([{"Loading past entries..."} ]);
@@ -59,19 +61,23 @@ export default function InstanceConsole(props) {
let parent = anchor.current.parentElement;
parent.scrollTop = parent.scrollHeight - parent.clientHeight;
- libLink.messages.queryLog.send(control, {
- all: false,
- master: false,
- slave_ids: [],
- instance_ids: [props.id],
- max_level: null,
- limit: 400,
- order: "desc",
- }).then(result => {
- setPastLines(result.log.map((info, index) => formatLog(info, index)).reverse());
- }).catch(err => {
- setPastLines([{`Error loading log: ${err.message}`} ]);
- });
+ if (account.hasPermission("core.log.query")) {
+ libLink.messages.queryLog.send(control, {
+ all: false,
+ master: false,
+ slave_ids: [],
+ instance_ids: [props.id],
+ max_level: null,
+ limit: 400,
+ order: "desc",
+ }).then(result => {
+ setPastLines(result.log.map((info, index) => formatLog(info, index)).reverse());
+ }).catch(err => {
+ setPastLines([{`Error loading log: ${err.message}`} ]);
+ });
+ } else {
+ setPastLines([]);
+ }
function logHandler(info) {
setLines(currentLines => currentLines.concat(
diff --git a/packages/web_ui/src/components/InstanceViewPage.jsx b/packages/web_ui/src/components/InstanceViewPage.jsx
index d38a300f..e9bf72c9 100644
--- a/packages/web_ui/src/components/InstanceViewPage.jsx
+++ b/packages/web_ui/src/components/InstanceViewPage.jsx
@@ -5,6 +5,7 @@ import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
import { libLink } from "@clusterio/lib";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
import PageLayout from "./PageLayout";
import InstanceConfigTree from "./InstanceConfigTree";
@@ -27,6 +28,7 @@ export default function InstanceViewPage(props) {
let history = useHistory();
+ let account = useAccount();
let control = useContext(ControlContext);
let [instance] = useInstance(instanceId);
let [slave] = useSlave(Number(instance["assigned_slave"]));
@@ -46,9 +48,12 @@ export default function InstanceViewPage(props) {
}
let instanceButtons =
-
-
-
+ }
+ {account.hasPermission("core.instance.load_scenario") && }
+ {account.hasPermission("core.instance.export_data") && {
@@ -63,8 +68,8 @@ export default function InstanceViewPage(props) {
}}
>
Export data
-
- }
+ {account.hasPermission("core.instance.delete") &&
-
+ }
;
let assigned = instance["assigned_slave"] !== null;
@@ -99,7 +104,7 @@ export default function InstanceViewPage(props) {
? Unassigned
: slave["name"] || instance["assigned_slave"]
}
-
+ />}
{instance["status"]}
-
-
Console
-
-
+ {
+ account.hasAllPermission("core.instance.save.list", "core.instance.save.list_subscribe")
+ &&
+ }
+ {
+ account.hasAnyPermission("core.log.follow", "core.instance.send_rcon")
+ && Console
+ }
+ {account.hasPermission("core.log.follow") && }
+ {
+ account.hasPermission("core.instance.send_rcon")
+ &&
+ }
-
+ {account.hasPermission("core.instance.get_config") && }
;
}
diff --git a/packages/web_ui/src/components/InstancesPage.jsx b/packages/web_ui/src/components/InstancesPage.jsx
index be54b6c7..e7020a05 100644
--- a/packages/web_ui/src/components/InstancesPage.jsx
+++ b/packages/web_ui/src/components/InstancesPage.jsx
@@ -4,6 +4,7 @@ import { Button, Form, Input, Modal, PageHeader, Table } from "antd";
import { libConfig, libLink } from "@clusterio/lib";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
import PageLayout from "./PageLayout";
import { useInstanceList } from "../model/instance";
@@ -59,6 +60,7 @@ function CreateInstanceButton(props) {
export default function InstancesPage() {
let history = useHistory();
+ let account = useAccount();
let [slaveList] = useSlaveList();
let [instanceList] = useInstanceList();
@@ -66,7 +68,7 @@ export default function InstancesPage() {
+ extra={account.hasPermission("core.instance.create") && }
/>
Master
-
+ {account.hasPermission("core.master.get_config") && }
;
};
diff --git a/packages/web_ui/src/components/RoleViewPage.jsx b/packages/web_ui/src/components/RoleViewPage.jsx
index 900dfb07..ddd7a9f0 100644
--- a/packages/web_ui/src/components/RoleViewPage.jsx
+++ b/packages/web_ui/src/components/RoleViewPage.jsx
@@ -1,10 +1,11 @@
import React, { useEffect, useContext, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
-import { Button, Checkbox, Col, Form, Input, Popconfirm, Row, Space, Spin } from "antd";
+import { Button, Checkbox, Form, Input, PageHeader, Popconfirm, Spin } from "antd";
import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
import { libLink, libUsers } from "@clusterio/lib";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
import PageLayout from "./PageLayout";
import { notifyErrorHandler } from "../util/notify";
@@ -42,6 +43,7 @@ export default function RoleViewPage() {
let history = useHistory();
+ let account = useAccount();
let control = useContext(ControlContext);
let [role, updateRole] = useRole(roleId);
let [edited, setEdited] = useState(false);
@@ -50,14 +52,20 @@ export default function RoleViewPage() {
let nav = [{ name: "Roles", path: "/roles" }, { name: role["name"] || roleId }];
if (role.loading) {
return
- {roleId}
+
;
}
if (role.missing) {
return
- Role not found
+
Role with id {roleId} was not found on the master server.
;
}
@@ -72,6 +80,7 @@ export default function RoleViewPage() {
},
};
+ let canUpdate = account.hasPermission("core.role.update");
return
-
+
-
+
Permissions
{[...libUsers.permissions.values()].map(({name, title, description}) => (
@@ -141,7 +145,7 @@ export default function RoleViewPage() {
labelAlign="left"
colon={false}
>
-
+
))}
diff --git a/packages/web_ui/src/components/RolesPage.jsx b/packages/web_ui/src/components/RolesPage.jsx
index e4a0aca2..388a48f1 100644
--- a/packages/web_ui/src/components/RolesPage.jsx
+++ b/packages/web_ui/src/components/RolesPage.jsx
@@ -1,50 +1,100 @@
-import React, { useContext } from "react";
+import React, { useContext, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
+import { Button, Form, Input, Modal, PageHeader, Table } from "antd";
import { libLink } from "@clusterio/lib";
-import DataTable from "./data-table";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
+import { notifyErrorHandler } from "../util/notify";
import PageLayout from "./PageLayout";
-export default function UsersPage() {
+function CreateRoleButton() {
+ let control = useContext(ControlContext);
+ let history = useHistory();
+ let [visible, setVisible] = useState(false);
+ let [form] = Form.useForm();
+
+ async function createRole() {
+ let values = form.getFieldsValue();
+ if (!values.roleName) {
+ form.setFields([{ name: "roleName", errors: ["Name is required"] }]);
+ return;
+ }
+
+ let result = await libLink.messages.createRole.send(control, {
+ name: values.roleName,
+ description: values.description || "",
+ permissions: [],
+ });
+ setVisible(false);
+ history.push(`/roles/${result.id}/view`);
+ }
+
+ return <>
+ { setVisible(true); }}
+ >Create
+ { createRole().catch(notifyErrorHandler("Error creating role")); }}
+ onCancel={() => { setVisible(false); }}
+ destroyOnClose
+ >
+
+
+
+
+
+
+
+
+ >;
+}
+
+export default function RolesPage() {
+ let account = useAccount();
let control = useContext(ControlContext);
let history = useHistory();
- async function listRoles() {
- let result = await libLink.messages.listRoles.send(control);
- return result["list"].map(item => ({
- key: item["id"],
- "Name": item["name"],
- "Description": item["description"],
- }));
- }
+ let [roles, setRoles] = useState([]);
+
+ useEffect(() => {
+ libLink.messages.listRoles.send(control).then(result => {
+ setRoles(result["list"]);
+ }).catch(notifyErrorHandler("Error fetching role list"));
+ }, []);
return
- Roles
- {
- await libLink.messages.createRole.send(control, {
- name: args.name,
- description: args.description || "",
- permissions: [],
- });
+ }
+ />
+ ({
- onClick: event => {
- history.push(`/roles/${record.key}/view`);
- },
- }),
- }}
+ {
+ title: "Description",
+ dataIndex: "description",
+ },
+ ]}
+ dataSource={roles}
+ pagination={false}
+ rowKey={role => role["id"]}
+ onRow={(role, rowIndex) => ({
+ onClick: event => {
+ history.push(`/roles/${role["id"]}/view`);
+ },
+ })}
/>
;
}
diff --git a/packages/web_ui/src/components/SavesList.jsx b/packages/web_ui/src/components/SavesList.jsx
index 8b61f063..72c0554a 100644
--- a/packages/web_ui/src/components/SavesList.jsx
+++ b/packages/web_ui/src/components/SavesList.jsx
@@ -5,6 +5,7 @@ import LeftOutlined from "@ant-design/icons/LeftOutlined";
import { libLink } from "@clusterio/lib";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
import CreateSaveModal from "./CreateSaveModal";
import SectionHeader from "./SectionHeader";
@@ -24,6 +25,7 @@ function formatBytes(bytes) {
}
export default function SavesList(props) {
+ let account = useAccount();
let control = useContext(ControlContext);
let saves = useSaves(props.instance.id);
let [starting, setStarting] = useState(false);
@@ -65,7 +67,7 @@ export default function SavesList(props) {
columnWidth: 33,
expandRowByClick: true,
expandedRowRender: save =>
- {
@@ -78,8 +80,8 @@ export default function SavesList(props) {
() => { setStarting(false); }
);
}}
- >Load save
- Load save }
+ {account.hasPermission("core.instance.save.download") && {
libLink.messages.downloadSave.send(
@@ -92,8 +94,8 @@ export default function SavesList(props) {
notifyErrorHandler("Error downloading save")
);
}}
- >Download
- Download}
+ {account.hasPermission("core.instance.save.delete") &&
Delete
-
+ }
,
}}
/>;
@@ -158,15 +160,19 @@ export default function SavesList(props) {
};
return
-
-
+
+ {account.hasPermission("core.instance.save.upload") &&
Upload save
-
-
- />
-
- {saveTable}
-
+ }
+ {account.hasPermission("core.instance.save.create") && }
+ } />
+ {
+ account.hasPermission("core.instance.save.upload")
+ ?
+ {saveTable}
+
+ : saveTable
+ }
{uploadingFiles.length ?
{uploadingFiles.map(file =>
{file.name}
diff --git a/packages/web_ui/src/components/SiteLayout.jsx b/packages/web_ui/src/components/SiteLayout.jsx
index 843d3e6b..5da64ebc 100644
--- a/packages/web_ui/src/components/SiteLayout.jsx
+++ b/packages/web_ui/src/components/SiteLayout.jsx
@@ -69,9 +69,12 @@ export default function SiteLayout(props) {
style={{ height: "100%", borderRight: 0 }}
onClick={({ key }) => history.push(key)}
>
- {combinedPages.map(({sidebarName, path}) => (
- sidebarName ? {sidebarName} : null)
- )}
+ {combinedPages.map(({sidebarName, permission, path}) => {
+ if (!sidebarName || permission && !account.hasPermission(permission)) {
+ return null;
+ }
+ return {sidebarName} ;
+ })}
diff --git a/packages/web_ui/src/components/SlavesPage.jsx b/packages/web_ui/src/components/SlavesPage.jsx
index 751480e7..15a83f3b 100644
--- a/packages/web_ui/src/components/SlavesPage.jsx
+++ b/packages/web_ui/src/components/SlavesPage.jsx
@@ -3,6 +3,7 @@ import { Button, Form, Input, Modal, PageHeader, Table } from "antd";
import { libLink } from "@clusterio/lib";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
import PageLayout from "./PageLayout";
import { useSlaveList } from "../model/slave";
@@ -71,14 +72,14 @@ function GenerateSlaveTokenButton(props) {
export default function SlavesPage() {
- let control = useContext(ControlContext);
+ let account = useAccount();
let [slaveList] = useSlaveList();
return
+ extra={account.hasPermission("core.slave.generate_token") && }
/>
- {userName}
+
;
}
if (user.missing) {
return
- User not found
+
User with name {userName} was not found on the master server.
;
}
+ let roleSelector = (rolesError ? Promise.reject(rolesError) : Promise.resolve()) },
+ ]}
+ >
+ setRolesDirty(true)}
+ loading={!roles}
+ mode="multiple"
+ showArrow={true}
+ filterOption={(inputValue, option) => {
+ let role = roles.get(option.value);
+ return role && role["name"].toLowerCase().includes(inputValue.toLowerCase());
+ }}
+ >
+ {roles && [...roles.values()].map(r => {r["name"]} )}
+
+ ;
+
return
-
-
- {userName}
-
-
-
Delete user account and all data associated with it?
{(user["is_banned"] && <> This will remove the user ban!>)}
@@ -97,40 +129,22 @@ export default function UserViewPage() {
-
-
+ }
+ />
- (rolesError ? Promise.reject(rolesError) : Promise.resolve()) },
- ]}
- >
- setRolesDirty(true)}
- loading={!roles}
- mode="multiple"
- showArrow={true}
- filterOption={(inputValue, option) => {
- let role = roles.get(option.value);
- return role && role["name"].toLowerCase().includes(inputValue.toLowerCase());
- }}
- >
- {roles && [...roles.values()].map(r => {r["name"]} )}
-
-
+ {account.hasPermission("core.user.update_roles")
+ ? roleSelector
+ : [...user["roles"]].map(id => {
+ roles ? (roles.get(id) || { name: id })["name"] : id
+ } )
+ }
-
+ {account.hasPermission("core.user.update_roles") &&
Apply
-
+ }
{
libLink.messages.setUserAdmin.send(control, {
name: userName,
@@ -172,6 +187,7 @@ export default function UserViewPage() {
{
libLink.messages.setUserWhitelisted.send(control, {
name: userName,
@@ -186,7 +202,7 @@ export default function UserViewPage() {
{user["is_banned"] ? "Yes" : "No"}
-
+ content={
@@ -220,7 +236,7 @@ export default function UserViewPage() {
>Ban
-
+ }
>
{user["is_banned"] ? "Pardon User" : "Ban User"}
-
+ }
diff --git a/packages/web_ui/src/components/UsersPage.jsx b/packages/web_ui/src/components/UsersPage.jsx
index e70911a5..c8f98376 100644
--- a/packages/web_ui/src/components/UsersPage.jsx
+++ b/packages/web_ui/src/components/UsersPage.jsx
@@ -1,68 +1,118 @@
import React, { useEffect, useContext, useState } from "react";
import { useHistory } from "react-router-dom";
-import { Spin, Tag } from "antd";
+import { Button, Form, Input, Modal, PageHeader, Table, Tag } from "antd";
import { libLink } from "@clusterio/lib";
-import DataTable from "./data-table";
+import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
+import { notifyErrorHandler } from "../util/notify";
import PageLayout from "./PageLayout";
+function CreateUserButton() {
+ let control = useContext(ControlContext);
+ let history = useHistory();
+ let [visible, setVisible] = useState(false);
+ let [form] = Form.useForm();
+
+ async function createUser() {
+ let values = form.getFieldsValue();
+ if (!values.userName) {
+ form.setFields([{ name: "userName", errors: ["Name is required"] }]);
+ return;
+ }
+
+ await libLink.messages.createUser.send(control, { name: values.userName });
+ setVisible(false);
+ history.push(`/users/${values.userName}/view`);
+ }
+
+ return <>
+ { setVisible(true); }}
+ >Create
+ { createUser().catch(notifyErrorHandler("Error creating user")); }}
+ onCancel={() => { setVisible(false); }}
+ destroyOnClose
+ >
+
+
+
+
+
+ >;
+}
+
export default function UsersPage() {
+ let account = useAccount();
let control = useContext(ControlContext);
let history = useHistory();
- let [roles, setRoles] = useState(null);
+ let [users, setUsers] = useState([]);
+ let [roles, setRoles] = useState(new Map());
useEffect(() => {
+ libLink.messages.listUsers.send(control).then(result => {
+ setUsers(result["list"]);
+ }).catch(notifyErrorHandler("Error fetching user list"));
libLink.messages.listRoles.send(control).then(result => {
setRoles(new Map(result["list"].map(role => [role["id"], role])));
}).catch(() => {
- setRoles(new Map());
+ // ignore
});
}, []);
- async function listUsers() {
- let result = await libLink.messages.listUsers.send(control);
- return result["list"].map(item => ({
- key: item["name"],
- "Name": item["name"],
- "Roles": item["roles"].map(id => {(roles.get(id) || { name: id })["name"]} ),
- "Admin": item["is_admin"] && "Yes",
- "Whitelisted": item["is_whitelisted"] && "Yes",
- "Banned": item["is_banned"] && "Yes",
- }));
- }
-
- // DataTable does not make it possible to update the rendering function
- if (roles === null) {
- return
- Users
-
- ;
- }
-
return
- Users
- }
+ />
+ {
- await libLink.messages.createUser.send(control, { name: args.name });
+ dataIndex: "name",
},
- }}
- TableProps={{
- onRow: (record, rowIndex) => ({
- onClick: event => {
- history.push(`/users/${record.key}/view`);
- },
- }),
- }}
+ {
+ title: "Roles",
+ key: "roles",
+ render: user => user.roles.map(id => {(roles.get(id) || { name: id })["name"]} ),
+ },
+ {
+ title: "Admin",
+ key: "admin",
+ render: user => user["is_admin"] && "yes",
+ responsive: ["lg"],
+ },
+ {
+ title: "Whitelisted",
+ key: "whitelisted",
+ render: user => user["is_whitelisted"] && "yes",
+ responsive: ["lg"],
+ },
+ {
+ title: "Banned",
+ key: "banned",
+ render: user => user["is_banned"] && "yes",
+ responsive: ["lg"],
+ },
+ ]}
+ dataSource={users}
+ pagination={false}
+ rowKey={user => user["name"]}
+ onRow={(user, rowIndex) => ({
+ onClick: event => {
+ history.push(`/users/${user["name"]}/view`);
+ },
+ })}
/>
;
}
diff --git a/packages/web_ui/src/components/data-table.jsx b/packages/web_ui/src/components/data-table.jsx
deleted file mode 100644
index 49c46467..00000000
--- a/packages/web_ui/src/components/data-table.jsx
+++ /dev/null
@@ -1,281 +0,0 @@
-import React, { Component } from "react";
-import { Collapse, Row, Col, Modal, Table, Form, Input, Button, Popover } from "antd";
-import PlusOutlined from "@ant-design/icons/PlusOutlined";
-import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
-
-import notify from "../util/notify";
-
-const { Panel } = Collapse;
-
-/**
- * Display data in table. Allows for editing data by clicking rows in the table.
- * Basic usage:
- *
- * DataFunction - String, corresponds to export from util/wslink. Same as ws message name.
- * columns - Array of objects, optional, same as . Note the lowercase c
- * Editable - Boolean - Allow editing
- * AddRecord - Boolean - Allow adding new records
- * CustomDataFilterFunction - function(tableData) - Filter tabledata and return results
- * RecordIdField - String -
- * By default we assume the column "Id" is a unique identifier for a record. If
- * that isn't the case, supply the column name here
- */
-class DataTable extends Component {
- constructor(props) {
- super(props);
- if (!this.props.DataFunction) {
- throw new Error("DataFunction not defined");
- }
- this.state = {
- pagination: {
- pageSize: 100,
- current: 1,
- },
- filters: this.props.Filters,
- sorter: {},
- tableData: [],
- showEditModal: false,
- record: {},
- };
- }
-
- componentDidMount() {
- this.getData();
- }
-
- async getData(pagination, filters, sorter) {
- // pagination = {
- // pageSize: 10, // items per page
- // current: 1, // page number
- // total: 123, // Total amount of items
- // }
- // filters = {
- // ReportId: 123
- // }
- // sorter = {}
- pagination = pagination || this.state.pagination;
- filters = filters || this.state.filters;
- sorter = sorter || this.state.sorter;
-
- // eslint-disable-next-line new-cap
- let data = await this.props.DataFunction();
- data = data || {};
- this.setState({
- // eslint-disable-next-line new-cap
- tableData: this.props.CustomDataFilterFunction ? this.props.CustomDataFilterFunction(data) : data,
- pagination: { ...pagination, ...data.pagination },
- sorter,
- filters,
- });
- }
-
- showEditModal(record, index) {
- if (!this.state.showEditModal) {
- this.setState({
- record: record,
- showEditModal: true,
- });
- }
- }
-
- hideEditModal() {
- this.setState({
- showEditModal: false,
- inserting: false,
- record: {},
- });
- }
-
- async onEditFinish(values) {
- if (this.state.inserting) { // Create new record
- await this.props.AddRecord.insert(values);
- } else { // Edit existing record, capable of doing sparse updates
- await this.props.Editable.insert(values);
- }
- this.setState({
- showEditModal: false,
- inserting: false,
- record: {},
- });
- // Force refresh of data
- this.getData();
- }
-
- async deleteRecord(record) {
- delete record.key;
- // Delete "null" values as they seem to cause deletes to fail silently when
- // the value is null and field type is float
- for (let key in record) {
- if (record[key] === null) { delete record[key]; }
- }
- let status = {};
- // TODO: Delete data here
-
- if (status.ok) { notify("success", status.msg, "success"); }
- if (!status.ok) { notify("error", status.msg, "error"); }
-
- // Force refresh of data
- this.getData();
- }
-
- render() {
- let tableProps = {
- ...this.props.TableProps || {},
- onRow: (record, rowIndex) => {
- // To allow for extending functionality, we take a TableProps argument
- // from props and pass it through to the Table component. Since we
- // are also using onRow internaly here, we handle the externally
- // provided events ourselves by passing along the event object. This
- // way the end user isn't affected by our event handlers.
- let externalOnRow = {
- ...(
- this.props.TableProps
- && this.props.TableProps.onRow
- && this.props.TableProps.onRow(record, rowIndex)
- ),
- };
- return {
- onClick: event => {
- if (event.target.type !== "button") {
- if (this.props.Editable) { this.showEditModal(record, rowIndex); }
- if (externalOnRow.onClick) { externalOnRow.onClick(event); }
- }
- },
- onDoubleClick: event => {
- if (externalOnRow.onDoubleClick) { externalOnRow.onDoubleClick(event); }
- },
- onContextMenu: event => {
- if (externalOnRow.onContextMenu) { externalOnRow.onContextMenu(event); }
- },
- onMouseEnter: event => {
- if (externalOnRow.onMouseEnter) { externalOnRow.onMouseEnter(event); }
- },
- onMouseLeave: event => {
- if (externalOnRow.onMouseLeave) { externalOnRow.onMouseLeave(event); }
- },
- };
- },
- };
- let columns = this.props.columns || this.inferColumns(this.state.tableData);
- if (this.props.DeleteRecord && !columns.find(x => x.dataIndex === "notInUse")) {
- columns.push({
- // Add delete button on the right
- dataIndex: "notInUse",
- title: "",
- render: (text, record) => {
- this.deleteRecord(record);
- }}
- >
- Slett permanent
-
- }
- trigger="click"
- >
-
-
-
- ,
- });
- }
-
- return <>
-
-
-
- ({
- ...x, // Add a 4th parameter to the stock render function with all of our table data
- render: x.render
- ? (...originalParams) => x.render(...originalParams, this.state.tableData)
- : undefined,
- }))}
- dataSource={this.state.tableData}
- pagination={this.state.pagination.total > 10 ? this.state.pagination : false}
- onChange={this.getData.bind(this)}
- />
- {this.props.AddRecord && this.setState({
- showEditModal: true,
- inserting: true,
- })}>
- Add
- }
-
- {this.state.inserting ? "Add" : "Edit"}
- }
- >
- {this.state.showEditModal &&
- {col.renderEdit && col.renderEdit(
- this.state.record[col.dataIndex],
- this.state.record,
- undefined /* Non spec compliant, is supposed to be index of record */,
- this.state.tableData
- ) || }
- )}
-
-
- {this.state.inserting ? "Save" : "Save"}
-
-
- }
-
-
- >;
- }
-
- inferColumns(data) {
- // Look at the data and find columns and how to display them. Can be
- // overridden by props.Columns if more customization is needed
- let columns = [];
- data.forEach(row => {
- Object.keys(row).filter(x => x !== "key").forEach(key => {
- let existing = columns.find(col => col.key === key);
- if (!existing) {
- columns.push({
- key: key,
- title: key,
- dataIndex: key,
- });
- }
- });
- });
- return columns;
- }
-}
-
-export default DataTable;
diff --git a/packages/web_ui/src/pages.jsx b/packages/web_ui/src/pages.jsx
index 6ffe389b..b643b0e5 100644
--- a/packages/web_ui/src/pages.jsx
+++ b/packages/web_ui/src/pages.jsx
@@ -13,16 +13,60 @@ import PluginViewPage from "./components/PluginViewPage";
export const pages = [
- { path: "/master", sidebarName: "Master", content: },
- { path: "/slaves", sidebarName: "Slaves", content: },
- { path: "/instances", sidebarName: "Instances", content: },
- { path: "/instances/:id/view", sidebarPath: "/instances", content: },
- { path: "/users", sidebarName: "Users", content: },
- { path: "/users/:name/view", sidebarPath: "/users", content: },
- { path: "/roles", sidebarName: "Roles", content: },
- { path: "/roles/:id/view", sidebarPath: "/roles", content: },
- { path: "/plugins", sidebarName: "Plugins", content: },
- { path: "/plugins/:name/view", sidebarPath: "/plugins", content: },
+ {
+ path: "/master",
+ sidebarName: "Master",
+ content: ,
+ },
+ {
+ path: "/slaves",
+ sidebarName: "Slaves",
+ permission: "core.slave.list",
+ content: ,
+ },
+ {
+ path: "/instances",
+ sidebarName: "Instances",
+ permission: "core.instance.list",
+ content: ,
+ },
+ {
+ path: "/instances/:id/view",
+ sidebarPath: "/instances",
+ content: ,
+ },
+ {
+ path: "/users",
+ sidebarName: "Users",
+ permission: "core.user.list",
+ content: ,
+ },
+ {
+ path: "/users/:name/view",
+ sidebarPath: "/users",
+ content: ,
+ },
+ {
+ path: "/roles",
+ sidebarName: "Roles",
+ permission: "core.user.list",
+ content: ,
+ },
+ {
+ path: "/roles/:id/view",
+ sidebarPath: "/roles",
+ content: ,
+ },
+ {
+ path: "/plugins",
+ sidebarName: "Plugins",
+ content: ,
+ },
+ {
+ path: "/plugins/:name/view",
+ sidebarPath: "/plugins",
+ content: ,
+ },
];
export const sidebarPages = [
diff --git a/plugins/inventory_sync/web/index.jsx b/plugins/inventory_sync/web/index.jsx
index 8261705f..a7fdc6ee 100644
--- a/plugins/inventory_sync/web/index.jsx
+++ b/plugins/inventory_sync/web/index.jsx
@@ -31,7 +31,12 @@ function InventoryPage() {
export class WebPlugin extends libPlugin.BaseWebPlugin {
async init() {
this.pages = [
- { path: "/inventory", sidebarName: "Inventory sync", content: },
+ {
+ path: "/inventory",
+ sidebarName: "Inventory sync",
+ permission: "inventory_sync.inventory.view",
+ content: ,
+ },
];
}
diff --git a/plugins/subspace_storage/web/index.jsx b/plugins/subspace_storage/web/index.jsx
index c6123713..a143dc5c 100644
--- a/plugins/subspace_storage/web/index.jsx
+++ b/plugins/subspace_storage/web/index.jsx
@@ -117,7 +117,12 @@ function StoragePage() {
export class WebPlugin extends libPlugin.BaseWebPlugin {
async init() {
this.pages = [
- { path: "/storage", sidebarName: "Storage", content: },
+ {
+ path: "/storage",
+ sidebarName: "Storage",
+ permission: "subspace_storage.storage.view",
+ content: ,
+ },
];
this.storage = new Map();