mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Fix web interface indents
Use project standard of tabs for indents.
This commit is contained in:
1 parent
94200433cc
commit
8dce1dd0fd
16 files changed
+1267
-1267
No files matched your search
@@ -5,9 +5,9 @@ import basename from "../basename";
|
||||
import Layout from "../layout";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<BrowserRouter basename={basename}>
|
||||
<Layout />
|
||||
</BrowserRouter>
|
||||
);
|
||||
return (
|
||||
<BrowserRouter basename={basename}>
|
||||
<Layout />
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
@@ -19,28 +19,28 @@ const { Panel } = Collapse;
|
||||
* 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: {},
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
componentDidMount() {
|
||||
this.getData()
|
||||
}
|
||||
|
||||
async getData(pagination, filters, sorter) {
|
||||
/*
|
||||
async getData(pagination, filters, sorter) {
|
||||
/*
|
||||
pagination = {
|
||||
pageSize: 10, // items per page
|
||||
current: 1, // page number
|
||||
@@ -51,235 +51,235 @@ class DataTable extends Component {
|
||||
}
|
||||
sorter = {}
|
||||
*/
|
||||
pagination = pagination || this.state.pagination
|
||||
filters = filters || this.state.filters
|
||||
sorter = sorter || this.state.sorter
|
||||
// console.log(pagination, filters, sorter)
|
||||
pagination = pagination || this.state.pagination
|
||||
filters = filters || this.state.filters
|
||||
sorter = sorter || this.state.sorter
|
||||
// console.log(pagination, filters, sorter)
|
||||
|
||||
let data = await this.props.DataFunction()
|
||||
// await fetchApi(this.state.dataUrl, "post", {
|
||||
// database: this.props.Database,
|
||||
// table: this.props.Table,
|
||||
// dataSource: this.props.DataSource,
|
||||
// pagination: this.props.RecordCount? {...pagination, pageSize: this.props.RecordCount, current: 1}:pagination,
|
||||
// filters,
|
||||
// sorter,
|
||||
// recordIdField: this.props.RecordIdField || "Id", // Sort by this
|
||||
// })
|
||||
console.log(data)
|
||||
data = data || {}
|
||||
this.setState({
|
||||
tableData: this.props.CustomDataFilterFunction ? this.props.CustomDataFilterFunction(data) : data,
|
||||
pagination: { ...pagination, ...data.pagination },
|
||||
sorter,
|
||||
filters,
|
||||
})
|
||||
}
|
||||
let data = await this.props.DataFunction()
|
||||
// await fetchApi(this.state.dataUrl, "post", {
|
||||
// database: this.props.Database,
|
||||
// table: this.props.Table,
|
||||
// dataSource: this.props.DataSource,
|
||||
// pagination: this.props.RecordCount? {...pagination, pageSize: this.props.RecordCount, current: 1}:pagination,
|
||||
// filters,
|
||||
// sorter,
|
||||
// recordIdField: this.props.RecordIdField || "Id", // Sort by this
|
||||
// })
|
||||
console.log(data)
|
||||
data = data || {}
|
||||
this.setState({
|
||||
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) {
|
||||
console.log("Finished editing", values)
|
||||
showEditModal(record, index) {
|
||||
if (!this.state.showEditModal)
|
||||
this.setState({
|
||||
record: record,
|
||||
showEditModal: true,
|
||||
})
|
||||
}
|
||||
hideEditModal() {
|
||||
this.setState({
|
||||
showEditModal: false,
|
||||
inserting: false,
|
||||
record: {}
|
||||
})
|
||||
}
|
||||
async onEditFinish(values) {
|
||||
console.log("Finished editing", values)
|
||||
|
||||
if (this.state.inserting) { // Create new record
|
||||
var status = {}
|
||||
await this.props.AddRecord.insert(values)
|
||||
} else { // Edit existing record, capable of doing sparse updates
|
||||
var status = {}
|
||||
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]
|
||||
}
|
||||
var status = {}
|
||||
// await fetchApi(`/api/${this.props.Server}/tableData/record`, "delete", {
|
||||
// database: this.props.Database,
|
||||
// table: this.props.Table,
|
||||
// dataSource: this.props.DataSource,
|
||||
// record,
|
||||
// }, undefined, true)
|
||||
if (this.state.inserting) { // Create new record
|
||||
var status = {}
|
||||
await this.props.AddRecord.insert(values)
|
||||
} else { // Edit existing record, capable of doing sparse updates
|
||||
var status = {}
|
||||
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]
|
||||
}
|
||||
var status = {}
|
||||
// await fetchApi(`/api/${this.props.Server}/tableData/record`, "delete", {
|
||||
// database: this.props.Database,
|
||||
// table: this.props.Table,
|
||||
// dataSource: this.props.DataSource,
|
||||
// record,
|
||||
// }, undefined, true)
|
||||
|
||||
console.log("Deleted record", status)
|
||||
console.log("Deleted record", status)
|
||||
|
||||
if (status.ok) notify("success", status.msg, "success")
|
||||
if (!status.ok) notify("error", status.msg, "error")
|
||||
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) => {
|
||||
/*
|
||||
// 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") {
|
||||
console.log("onRow", record, rowIndex)
|
||||
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) => <Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={() => {
|
||||
this.deleteRecord(record)
|
||||
}}
|
||||
>
|
||||
let externalOnRow = {
|
||||
...(this.props.TableProps && this.props.TableProps.onRow && this.props.TableProps.onRow(record, rowIndex)),
|
||||
}
|
||||
return {
|
||||
onClick: event => {
|
||||
if (event.target.type !== "button") {
|
||||
console.log("onRow", record, rowIndex)
|
||||
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) => <Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={() => {
|
||||
this.deleteRecord(record)
|
||||
}}
|
||||
>
|
||||
Slett permanent
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
})
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
})
|
||||
|
||||
return <>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<style>
|
||||
{`
|
||||
return <>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<style>
|
||||
{`
|
||||
.editableTable .ant-table-tbody > tr {
|
||||
cursor:pointer
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<Table
|
||||
className={this.props.Editable && "editableTable"}
|
||||
{...tableProps}
|
||||
columns={columns.map(x => {
|
||||
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 && <Button type="primary" onClick={() => this.setState({
|
||||
showEditModal: true,
|
||||
inserting: true
|
||||
})}>
|
||||
<PlusOutlined />Add
|
||||
</Button>}
|
||||
</Col>
|
||||
<Modal
|
||||
visible={this.state.showEditModal}
|
||||
onCancel={this.hideEditModal}
|
||||
footer={null}
|
||||
title={
|
||||
<h2>{this.state.inserting ? "Legg til" : "Rediger"}</h2>
|
||||
}
|
||||
>
|
||||
{this.state.showEditModal && <Form
|
||||
labelCol={{
|
||||
span: 6,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 12,
|
||||
}}
|
||||
layout="horizontal"
|
||||
onFinish={this.onEditFinish.bind(this)}
|
||||
initialValues={this.state.record}
|
||||
>
|
||||
{
|
||||
</style>
|
||||
<Table
|
||||
className={this.props.Editable && "editableTable"}
|
||||
{...tableProps}
|
||||
columns={columns.map(x => {
|
||||
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 && <Button type="primary" onClick={() => this.setState({
|
||||
showEditModal: true,
|
||||
inserting: true
|
||||
})}>
|
||||
<PlusOutlined />Add
|
||||
</Button>}
|
||||
</Col>
|
||||
<Modal
|
||||
visible={this.state.showEditModal}
|
||||
onCancel={this.hideEditModal}
|
||||
footer={null}
|
||||
title={
|
||||
<h2>{this.state.inserting ? "Legg til" : "Rediger"}</h2>
|
||||
}
|
||||
>
|
||||
{this.state.showEditModal && <Form
|
||||
labelCol={{
|
||||
span: 6,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 12,
|
||||
}}
|
||||
layout="horizontal"
|
||||
onFinish={this.onEditFinish.bind(this)}
|
||||
initialValues={this.state.record}
|
||||
>
|
||||
{
|
||||
|
||||
(
|
||||
(this.state.inserting && this.props.AddRecord && this.props.AddRecord.fields) // Use externally supplied form input fields
|
||||
(
|
||||
(this.state.inserting && this.props.AddRecord && this.props.AddRecord.fields) // Use externally supplied form input fields
|
||||
|| this.props.columns.filter && this.props.columns.filter(x => x.dataIndex !== "notInUse") // Generate input fields from external columns
|
||||
|| this.inferColumns(this.state.tableData)).map(col => { // Generate input fields from autogenerated columns (only works with existing data)
|
||||
console.log(this.state.record)
|
||||
return <Form.Item key={col.dataIndex} label={col.title} name={col.dataIndex}>
|
||||
{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) || <Input
|
||||
placeholder={this.state.record[col.dataIndex]}
|
||||
/>}
|
||||
</Form.Item>
|
||||
})
|
||||
}
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{this.state.inserting ? "Legg til" : "Lagre"}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>}
|
||||
</Modal>
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
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
|
||||
}
|
||||
console.log(this.state.record)
|
||||
return <Form.Item key={col.dataIndex} label={col.title} name={col.dataIndex}>
|
||||
{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) || <Input
|
||||
placeholder={this.state.record[col.dataIndex]}
|
||||
/>}
|
||||
</Form.Item>
|
||||
})
|
||||
}
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{this.state.inserting ? "Legg til" : "Lagre"}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>}
|
||||
</Modal>
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
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;
|
||||
@@ -19,148 +19,148 @@ const { SubMenu } = Menu;
|
||||
|
||||
class SiteLayout extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { location } = props;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { location } = props;
|
||||
|
||||
this.state = {
|
||||
collapsed: false,
|
||||
setcollapsed: false,
|
||||
tags: [],
|
||||
setTags: [],
|
||||
location,
|
||||
siteSettings: {},
|
||||
factoryModel: []
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
collapsed: false,
|
||||
setcollapsed: false,
|
||||
tags: [],
|
||||
setTags: [],
|
||||
location,
|
||||
siteSettings: {},
|
||||
factoryModel: []
|
||||
};
|
||||
}
|
||||
|
||||
onCollapse() {
|
||||
this.setState({ collapsed: !this.state.collapsed })
|
||||
}
|
||||
onCollapse() {
|
||||
this.setState({ collapsed: !this.state.collapsed })
|
||||
}
|
||||
|
||||
titleCase(text) {
|
||||
return text.substring(0, 1).toUpperCase() + text.substring(1);
|
||||
}
|
||||
titleCase(text) {
|
||||
return text.substring(0, 1).toUpperCase() + text.substring(1);
|
||||
}
|
||||
|
||||
replaceParametersInUrl({ endpoint, params }) {
|
||||
let path = endpoint;
|
||||
console.log("endpoint", params)
|
||||
Object.keys(params).forEach(key => {
|
||||
path = path.replace(":" + key, params[key])
|
||||
})
|
||||
replaceParametersInUrl({ endpoint, params }) {
|
||||
let path = endpoint;
|
||||
console.log("endpoint", params)
|
||||
Object.keys(params).forEach(key => {
|
||||
path = path.replace(":" + key, params[key])
|
||||
})
|
||||
|
||||
return path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
{document.location.search !== "?hideExtras" ? (
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={this.state.collapsed}
|
||||
onCollapse={this.onCollapse.bind(this)}
|
||||
width={250}
|
||||
breakpoint="lg"
|
||||
>
|
||||
<p></p>
|
||||
<div style={{ width: "100%", textAlign: "center" }}>
|
||||
{" "}
|
||||
{this.state.collapsed ? (
|
||||
<img
|
||||
alt=""
|
||||
src="/smal_logo.png"
|
||||
style={{ height: "48px", margin: 8 }}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
alt=""
|
||||
src="/logo.png"
|
||||
style={{ width: "200px", margin: 8 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p></p>
|
||||
<p></p>
|
||||
render() {
|
||||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
{document.location.search !== "?hideExtras" ? (
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={this.state.collapsed}
|
||||
onCollapse={this.onCollapse.bind(this)}
|
||||
width={250}
|
||||
breakpoint="lg"
|
||||
>
|
||||
<p></p>
|
||||
<div style={{ width: "100%", textAlign: "center" }}>
|
||||
{" "}
|
||||
{this.state.collapsed ? (
|
||||
<img
|
||||
alt=""
|
||||
src="/smal_logo.png"
|
||||
style={{ height: "48px", margin: 8 }}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
alt=""
|
||||
src="/logo.png"
|
||||
style={{ width: "200px", margin: 8 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p></p>
|
||||
<p></p>
|
||||
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[
|
||||
this.state.location.pathname.split("/")[
|
||||
this.state.location.pathname.split("/").length - 1
|
||||
],
|
||||
]}
|
||||
defaultOpenKeys={(() => {
|
||||
let x = this.state.location.pathname.split("/");
|
||||
x.shift();
|
||||
return x;
|
||||
})()}
|
||||
mode="inline"
|
||||
>
|
||||
<Menu.Item key="slaves">
|
||||
<span>Slaves</span>
|
||||
<Link to="/slaves" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="instances">
|
||||
<span>Instances</span>
|
||||
<Link to="/instances" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="users">
|
||||
<span>Users</span>
|
||||
<Link to="/users" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="roles">
|
||||
<span>Roles</span>
|
||||
<Link to="/roles" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="permissions">
|
||||
<span>Permissions</span>
|
||||
<Link to="/permissions" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="settings">
|
||||
<span>Settings</span>
|
||||
<Link to="/settings" />
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Sider>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<Layout style={{
|
||||
// background: "#F4F4F4"
|
||||
}}>
|
||||
<Content
|
||||
style={{
|
||||
margin: "24px 16px",
|
||||
padding: 24,
|
||||
// background: "#fff",
|
||||
minHeight: 280,
|
||||
}}
|
||||
>
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route exact path="/instances" component={InstancesTable} />
|
||||
<Route exact path="/instances/:id/view" component={InstanceView} />
|
||||
<Route exact path="/slaves" component={SlavesTable} />
|
||||
<Route exact path="/slaves/:id/view" component={SlavesTable} />
|
||||
<Route exact path="/users" component={UsersTable} />
|
||||
<Route exact path="/users/:id/view" component={UserView} />
|
||||
<Route exact path="/roles" component={RolesTable} />
|
||||
<Route exact path="/roles/:id/view" component={RoleView} />
|
||||
<Route exact path="/permissions" component={PermissionsTable} />
|
||||
<Route exact path="/permissions/:id/view" component={PermissionsTable} />
|
||||
<Route exact path="/settings" component={Settings} />
|
||||
</Content>
|
||||
<Footer style={{
|
||||
// background: "#F4F4F4",
|
||||
textAlign: "center"
|
||||
}}>
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[
|
||||
this.state.location.pathname.split("/")[
|
||||
this.state.location.pathname.split("/").length - 1
|
||||
],
|
||||
]}
|
||||
defaultOpenKeys={(() => {
|
||||
let x = this.state.location.pathname.split("/");
|
||||
x.shift();
|
||||
return x;
|
||||
})()}
|
||||
mode="inline"
|
||||
>
|
||||
<Menu.Item key="slaves">
|
||||
<span>Slaves</span>
|
||||
<Link to="/slaves" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="instances">
|
||||
<span>Instances</span>
|
||||
<Link to="/instances" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="users">
|
||||
<span>Users</span>
|
||||
<Link to="/users" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="roles">
|
||||
<span>Roles</span>
|
||||
<Link to="/roles" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="permissions">
|
||||
<span>Permissions</span>
|
||||
<Link to="/permissions" />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="settings">
|
||||
<span>Settings</span>
|
||||
<Link to="/settings" />
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Sider>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<Layout style={{
|
||||
// background: "#F4F4F4"
|
||||
}}>
|
||||
<Content
|
||||
style={{
|
||||
margin: "24px 16px",
|
||||
padding: 24,
|
||||
// background: "#fff",
|
||||
minHeight: 280,
|
||||
}}
|
||||
>
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route exact path="/instances" component={InstancesTable} />
|
||||
<Route exact path="/instances/:id/view" component={InstanceView} />
|
||||
<Route exact path="/slaves" component={SlavesTable} />
|
||||
<Route exact path="/slaves/:id/view" component={SlavesTable} />
|
||||
<Route exact path="/users" component={UsersTable} />
|
||||
<Route exact path="/users/:id/view" component={UserView} />
|
||||
<Route exact path="/roles" component={RolesTable} />
|
||||
<Route exact path="/roles/:id/view" component={RoleView} />
|
||||
<Route exact path="/permissions" component={PermissionsTable} />
|
||||
<Route exact path="/permissions/:id/view" component={PermissionsTable} />
|
||||
<Route exact path="/settings" component={Settings} />
|
||||
</Content>
|
||||
<Footer style={{
|
||||
// background: "#F4F4F4",
|
||||
textAlign: "center"
|
||||
}}>
|
||||
Clusterio 2.0 web interface https://github.com/clusterio
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(SiteLayout);
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, {Component} from "react"
|
||||
|
||||
export class Dashboard extends Component {
|
||||
constructor(props){
|
||||
super(props)
|
||||
}
|
||||
render(){
|
||||
return <h2>Dashboard</h2>
|
||||
}
|
||||
constructor(props){
|
||||
super(props)
|
||||
}
|
||||
render(){
|
||||
return <h2>Dashboard</h2>
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Button, Popover, Select, Divider, Input, Form, Col, Row, Checkbox, Space } from "antd"
|
||||
import {
|
||||
listSlaves,
|
||||
listInstances,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
setInstanceOutputSubscriptions,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
sendRcon,
|
||||
listSlaves,
|
||||
listInstances,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
setInstanceOutputSubscriptions,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
sendRcon,
|
||||
} from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
import notify from "../../util/notify"
|
||||
@@ -22,208 +22,208 @@ import PlusOutlined from "@ant-design/icons/PlusOutlined"
|
||||
const { Option } = Select
|
||||
|
||||
export class InstanceView extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
instance: {},
|
||||
slaves: [],
|
||||
logLines: [{ message: "Logviewer. Follows the latest logs if you scroll to the bottom. Does not store logs past the lifetime of this tab" }],
|
||||
}
|
||||
this.messagesEndRef = React.createRef()
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
let id = this.props.match.params.id
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
instance: {},
|
||||
slaves: [],
|
||||
logLines: [{ message: "Logviewer. Follows the latest logs if you scroll to the bottom. Does not store logs past the lifetime of this tab" }],
|
||||
}
|
||||
this.messagesEndRef = React.createRef()
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
let id = this.props.match.params.id
|
||||
|
||||
await this.getData()
|
||||
await this.getData()
|
||||
|
||||
await setInstanceOutputSubscriptions({
|
||||
instance_id: Number(id),
|
||||
})
|
||||
window.instanceOutputEventHandler = ({ instance_id, output }) => {
|
||||
if (instance_id === Number(id)) {
|
||||
this.setState({
|
||||
logLines: [...this.state.logLines, output],
|
||||
})
|
||||
}
|
||||
// Automatically scroll to bottom on new line.
|
||||
// this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
async getData() {
|
||||
let id = this.props.match.params.id
|
||||
await setInstanceOutputSubscriptions({
|
||||
instance_id: Number(id),
|
||||
})
|
||||
window.instanceOutputEventHandler = ({ instance_id, output }) => {
|
||||
if (instance_id === Number(id)) {
|
||||
this.setState({
|
||||
logLines: [...this.state.logLines, output],
|
||||
})
|
||||
}
|
||||
// Automatically scroll to bottom on new line.
|
||||
// this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
async getData() {
|
||||
let id = this.props.match.params.id
|
||||
|
||||
let instances = await listInstances()
|
||||
let slaves = await listSlaves()
|
||||
let instances = await listInstances()
|
||||
let slaves = await listSlaves()
|
||||
|
||||
this.setState({
|
||||
instance: instances.find(x => x.id === Number(id)),
|
||||
slaves,
|
||||
})
|
||||
this.setState({
|
||||
instance: instances.find(x => x.id === Number(id)),
|
||||
slaves,
|
||||
})
|
||||
|
||||
let config = await getInstanceConfig({ instance_id: Number(id) })
|
||||
console.log(config)
|
||||
this.setState({
|
||||
config
|
||||
})
|
||||
}
|
||||
async start() {
|
||||
this.setState({
|
||||
starting: true,
|
||||
})
|
||||
try {
|
||||
await startInstance({
|
||||
instance_id: this.state.instance.id,
|
||||
save: null,
|
||||
})
|
||||
} catch (e) {
|
||||
notify("Error starting instance", "error", e.message)
|
||||
return this.setState({
|
||||
starting: false,
|
||||
})
|
||||
}
|
||||
notify("Instance started")
|
||||
this.setState({
|
||||
starting: false,
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
async stop() {
|
||||
this.setState({
|
||||
stopping: true,
|
||||
})
|
||||
try {
|
||||
await stopInstance({
|
||||
instance_id: this.state.instance.id,
|
||||
})
|
||||
} catch (e) {
|
||||
notify("Error stopping instance", "error", e.message)
|
||||
return this.setState({
|
||||
stopping: false,
|
||||
})
|
||||
}
|
||||
notify("Instance stopped")
|
||||
this.setState({
|
||||
stopping: false,
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
async createSave() {
|
||||
this.setState({
|
||||
creatingSave: true,
|
||||
})
|
||||
try {
|
||||
await createSave({
|
||||
instance_id: this.state.instance.id,
|
||||
})
|
||||
} catch (e) {
|
||||
notify("Error creating save", "error", e.message)
|
||||
return this.setState({
|
||||
creatingSave: false,
|
||||
})
|
||||
}
|
||||
notify("Save created", "success")
|
||||
this.setState({
|
||||
creatingSave: false,
|
||||
})
|
||||
}
|
||||
async onConfigEditFinish(values) {
|
||||
console.log("Completed config edit with", values)
|
||||
for (let k in values) {
|
||||
if (values[k] !== this.getInstanceConfigAsKvObject(this.state.config)[k]) {
|
||||
let value = values[k]
|
||||
if (typeof value === "boolean") value = value.toString()
|
||||
console.log("Setting", k, value)
|
||||
await setInstanceConfigField({
|
||||
instance_id: this.state.instance.id,
|
||||
field: k,
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
notify("Set configuration fields", "success")
|
||||
this.setState({
|
||||
editingConfig: false
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { instance, slaves } = this.state
|
||||
let slave = slaves && slaves.find(x => x.id === instance.assigned_slave);
|
||||
return <Card>
|
||||
<h2>{instance.name}</h2>
|
||||
<p>ID: {instance.id}</p>
|
||||
<p>Slave: {slave && slave.name}</p>
|
||||
{instance.status !== "running" && <Button
|
||||
loading={this.state.starting}
|
||||
type="primary"
|
||||
onClick={this.start.bind(this)}
|
||||
>
|
||||
let config = await getInstanceConfig({ instance_id: Number(id) })
|
||||
console.log(config)
|
||||
this.setState({
|
||||
config
|
||||
})
|
||||
}
|
||||
async start() {
|
||||
this.setState({
|
||||
starting: true,
|
||||
})
|
||||
try {
|
||||
await startInstance({
|
||||
instance_id: this.state.instance.id,
|
||||
save: null,
|
||||
})
|
||||
} catch (e) {
|
||||
notify("Error starting instance", "error", e.message)
|
||||
return this.setState({
|
||||
starting: false,
|
||||
})
|
||||
}
|
||||
notify("Instance started")
|
||||
this.setState({
|
||||
starting: false,
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
async stop() {
|
||||
this.setState({
|
||||
stopping: true,
|
||||
})
|
||||
try {
|
||||
await stopInstance({
|
||||
instance_id: this.state.instance.id,
|
||||
})
|
||||
} catch (e) {
|
||||
notify("Error stopping instance", "error", e.message)
|
||||
return this.setState({
|
||||
stopping: false,
|
||||
})
|
||||
}
|
||||
notify("Instance stopped")
|
||||
this.setState({
|
||||
stopping: false,
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
async createSave() {
|
||||
this.setState({
|
||||
creatingSave: true,
|
||||
})
|
||||
try {
|
||||
await createSave({
|
||||
instance_id: this.state.instance.id,
|
||||
})
|
||||
} catch (e) {
|
||||
notify("Error creating save", "error", e.message)
|
||||
return this.setState({
|
||||
creatingSave: false,
|
||||
})
|
||||
}
|
||||
notify("Save created", "success")
|
||||
this.setState({
|
||||
creatingSave: false,
|
||||
})
|
||||
}
|
||||
async onConfigEditFinish(values) {
|
||||
console.log("Completed config edit with", values)
|
||||
for (let k in values) {
|
||||
if (values[k] !== this.getInstanceConfigAsKvObject(this.state.config)[k]) {
|
||||
let value = values[k]
|
||||
if (typeof value === "boolean") value = value.toString()
|
||||
console.log("Setting", k, value)
|
||||
await setInstanceConfigField({
|
||||
instance_id: this.state.instance.id,
|
||||
field: k,
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
notify("Set configuration fields", "success")
|
||||
this.setState({
|
||||
editingConfig: false
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { instance, slaves } = this.state
|
||||
let slave = slaves && slaves.find(x => x.id === instance.assigned_slave);
|
||||
return <Card>
|
||||
<h2>{instance.name}</h2>
|
||||
<p>ID: {instance.id}</p>
|
||||
<p>Slave: {slave && slave.name}</p>
|
||||
{instance.status !== "running" && <Button
|
||||
loading={this.state.starting}
|
||||
type="primary"
|
||||
onClick={this.start.bind(this)}
|
||||
>
|
||||
Start server
|
||||
</Button>}
|
||||
{instance.status !== "stopped" && <Button
|
||||
loading={this.state.stopping}
|
||||
type="primary"
|
||||
onClick={this.stop.bind(this)}
|
||||
>
|
||||
</Button>}
|
||||
{instance.status !== "stopped" && <Button
|
||||
loading={this.state.stopping}
|
||||
type="primary"
|
||||
onClick={this.stop.bind(this)}
|
||||
>
|
||||
Stop server
|
||||
</Button>}
|
||||
{instance.status !== "running" && <Button
|
||||
loading={this.state.creatingSave}
|
||||
type="primary"
|
||||
onClick={this.createSave.bind(this)}
|
||||
>
|
||||
</Button>}
|
||||
{instance.status !== "running" && <Button
|
||||
loading={this.state.creatingSave}
|
||||
type="primary"
|
||||
onClick={this.createSave.bind(this)}
|
||||
>
|
||||
Create save
|
||||
</Button>}
|
||||
<Popover
|
||||
content={
|
||||
<Select style={{
|
||||
width: "200px"
|
||||
}}
|
||||
onChange={async newSlaveId => {
|
||||
let result = await assignInstance({ instance_id: instance.id, slave_id: newSlaveId })
|
||||
notify("Assigned to slave", "success")
|
||||
this.getData()
|
||||
}}
|
||||
>
|
||||
{this.state.slaves.map(slave => <Option defaultValue={instance.assigned_slave} value={slave.id} key={slave.id}>
|
||||
{slave.name}
|
||||
</Option>)}
|
||||
</Select>
|
||||
}
|
||||
trigger="click"
|
||||
type={instance.status === "unassigned" ? "primary" : "default"}
|
||||
>
|
||||
<Button>
|
||||
</Button>}
|
||||
<Popover
|
||||
content={
|
||||
<Select style={{
|
||||
width: "200px"
|
||||
}}
|
||||
onChange={async newSlaveId => {
|
||||
let result = await assignInstance({ instance_id: instance.id, slave_id: newSlaveId })
|
||||
notify("Assigned to slave", "success")
|
||||
this.getData()
|
||||
}}
|
||||
>
|
||||
{this.state.slaves.map(slave => <Option defaultValue={instance.assigned_slave} value={slave.id} key={slave.id}>
|
||||
{slave.name}
|
||||
</Option>)}
|
||||
</Select>
|
||||
}
|
||||
trigger="click"
|
||||
type={instance.status === "unassigned" ? "primary" : "default"}
|
||||
>
|
||||
<Button>
|
||||
Assign to slave
|
||||
</Button>
|
||||
</Popover>
|
||||
<Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={async () => {
|
||||
await deleteInstance({ instance_id: instance.id });
|
||||
notify("Deleted instance " + instance.id, "success")
|
||||
this.navigate("/instances")
|
||||
}}
|
||||
>
|
||||
</Button>
|
||||
</Popover>
|
||||
<Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={async () => {
|
||||
await deleteInstance({ instance_id: instance.id });
|
||||
notify("Deleted instance " + instance.id, "success")
|
||||
this.navigate("/instances")
|
||||
}}
|
||||
>
|
||||
Delete this instance permanently
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
<style>
|
||||
{/* Scroll sticks to bottom of terminal window after manually scrolling to the bottom */}
|
||||
{`#scroller * {
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
<style>
|
||||
{/* Scroll sticks to bottom of terminal window after manually scrolling to the bottom */}
|
||||
{`#scroller * {
|
||||
/* don't allow the children of the scrollable element to be selected as an anchor node */
|
||||
overflow-anchor: none;
|
||||
}
|
||||
@@ -234,116 +234,116 @@ export class InstanceView extends Component {
|
||||
/* anchor nodes are required to have non-zero area */
|
||||
height: 1px;
|
||||
}`}
|
||||
</style>
|
||||
<div id="scroller" style={{
|
||||
height: "300px",
|
||||
overflowY: "scroll",
|
||||
}}>
|
||||
{this.state.logLines.map(item => <p>{item.time} {item.level} {item.file}: {item.message}</p>)}
|
||||
<div id="anchor" ref={this.messagesEndRef}></div>
|
||||
</div>
|
||||
{/* rcon input field */}
|
||||
<Form
|
||||
onFinish={async values => {
|
||||
await sendRcon({ instance_id: instance.id, command: values.command })
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="RCON: "
|
||||
name="command"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{this.state.config && this.renderConfig(this.state.config)}
|
||||
</Card>
|
||||
}
|
||||
getInstanceConfigAsKvObject(config) {
|
||||
let object = {}
|
||||
for (let group of config.serialized_config.groups) {
|
||||
for (let fieldName in group.fields) {
|
||||
object[group.name + "." + fieldName] = group.fields[fieldName]
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
renderConfig(config) {
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 6,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 14,
|
||||
},
|
||||
};
|
||||
const tailLayout = {
|
||||
wrapperCol: {
|
||||
offset: 6,
|
||||
span: 14,
|
||||
},
|
||||
};
|
||||
return <Card>
|
||||
<Form
|
||||
{...layout}
|
||||
initialValues={this.getInstanceConfigAsKvObject(config)}
|
||||
onFinish={this.onConfigEditFinish.bind(this)}
|
||||
>
|
||||
{this.state.editingConfig ?
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
</style>
|
||||
<div id="scroller" style={{
|
||||
height: "300px",
|
||||
overflowY: "scroll",
|
||||
}}>
|
||||
{this.state.logLines.map(item => <p>{item.time} {item.level} {item.file}: {item.message}</p>)}
|
||||
<div id="anchor" ref={this.messagesEndRef}></div>
|
||||
</div>
|
||||
{/* rcon input field */}
|
||||
<Form
|
||||
onFinish={async values => {
|
||||
await sendRcon({ instance_id: instance.id, command: values.command })
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="RCON: "
|
||||
name="command"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{this.state.config && this.renderConfig(this.state.config)}
|
||||
</Card>
|
||||
}
|
||||
getInstanceConfigAsKvObject(config) {
|
||||
let object = {}
|
||||
for (let group of config.serialized_config.groups) {
|
||||
for (let fieldName in group.fields) {
|
||||
object[group.name + "." + fieldName] = group.fields[fieldName]
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
renderConfig(config) {
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 6,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 14,
|
||||
},
|
||||
};
|
||||
const tailLayout = {
|
||||
wrapperCol: {
|
||||
offset: 6,
|
||||
span: 14,
|
||||
},
|
||||
};
|
||||
return <Card>
|
||||
<Form
|
||||
{...layout}
|
||||
initialValues={this.getInstanceConfigAsKvObject(config)}
|
||||
onFinish={this.onConfigEditFinish.bind(this)}
|
||||
>
|
||||
{this.state.editingConfig ?
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Save config changes
|
||||
</Button>
|
||||
</Form.Item>
|
||||
:
|
||||
<Button onClick={() => this.setState({ editingConfig: true })}>
|
||||
<EditOutlined /> Edit config
|
||||
</Button>
|
||||
}
|
||||
<Row>
|
||||
{config && config.serialized_config.groups.map(group => <>
|
||||
<Col span={24} lg={12}>
|
||||
<Divider
|
||||
// orientation="left"
|
||||
>{group.name}</Divider>
|
||||
{/* {Object.keys(group.fields).map(field => <>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
:
|
||||
<Button onClick={() => this.setState({ editingConfig: true })}>
|
||||
<EditOutlined /> Edit config
|
||||
</Button>
|
||||
}
|
||||
<Row>
|
||||
{config && config.serialized_config.groups.map(group => <>
|
||||
<Col span={24} lg={12}>
|
||||
<Divider
|
||||
// orientation="left"
|
||||
>{group.name}</Divider>
|
||||
{/* {Object.keys(group.fields).map(field => <>
|
||||
<span>{field}: </span>
|
||||
<span>{JSON.stringify(group.fields[field])}</span>
|
||||
<br />
|
||||
</>)} */}
|
||||
{console.log(group)}
|
||||
{Object.keys(group.fields).map(field => <ConfigFormItem field={field} group={group} editingConfig={this.state.editingConfig} />)}
|
||||
</Col>
|
||||
</>)}
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
}
|
||||
{console.log(group)}
|
||||
{Object.keys(group.fields).map(field => <ConfigFormItem field={field} group={group} editingConfig={this.state.editingConfig} />)}
|
||||
</Col>
|
||||
</>)}
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
|
||||
function renderDisplayField(value) {
|
||||
if (typeof value === "object") {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return <Checkbox />
|
||||
}
|
||||
return value.toString()
|
||||
if (typeof value === "object") {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return <Checkbox />
|
||||
}
|
||||
return value.toString()
|
||||
}
|
||||
function renderFormField(value) {
|
||||
if (typeof value === "object") {
|
||||
return <ObjectInput />
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return <Checkbox />
|
||||
}
|
||||
return <Input />
|
||||
if (typeof value === "object") {
|
||||
return <ObjectInput />
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return <Checkbox />
|
||||
}
|
||||
return <Input />
|
||||
}
|
||||
|
||||
class ObjectInput extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
console.log(this.props)
|
||||
/*
|
||||
constructor(props) {
|
||||
super(props)
|
||||
console.log(this.props)
|
||||
/*
|
||||
value: {
|
||||
auto_pause: false,
|
||||
tags: ["clusterio"]
|
||||
@@ -351,89 +351,89 @@ class ObjectInput extends Component {
|
||||
id: "factorio.settings",
|
||||
onChange: f()
|
||||
*/
|
||||
this.state = {}
|
||||
}
|
||||
onChange(e, key) {
|
||||
if (e.target.type === "checkbox") {
|
||||
// if (e.target.checked !== undefined) {
|
||||
// Its a checkbox, set value to true/false
|
||||
let value = { ...this.props.value }
|
||||
value[key] = e.target.checked || false
|
||||
this.props.onChange(value)
|
||||
} else if (e.target.type === "text") {
|
||||
// } else if (e.target.value !== undefined) {
|
||||
// its probably a text input
|
||||
let value = { ...this.props.value }
|
||||
value[key] = e.target.value || ""
|
||||
this.props.onChange(value)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let { value } = this.props
|
||||
let ret = []
|
||||
for (let key in value) {
|
||||
if (typeof value[key] === "boolean") {
|
||||
ret.push(<Checkbox
|
||||
key={key}
|
||||
checked={value[key]}
|
||||
onChange={e => this.onChange(e, key)}
|
||||
>
|
||||
{key}
|
||||
</Checkbox>
|
||||
)
|
||||
} else if (typeof value[key] === "string") {
|
||||
ret.push(<div
|
||||
key={key}
|
||||
><span>{key}</span><Input
|
||||
value={value[key]}
|
||||
onChange={e => this.onChange(e, key)}
|
||||
/></div>
|
||||
)
|
||||
}
|
||||
}
|
||||
ret.push(<Popover
|
||||
key="addMore"
|
||||
content={<>
|
||||
<Input onChange={e => this.setState({ newInput: e.target.value })} />
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.onChange({ target: { value: "", type: "text" } }, this.state.newInput)
|
||||
this.setState({
|
||||
newInput: undefined,
|
||||
addPopoverVisible: false,
|
||||
})
|
||||
}}
|
||||
><PlusOutlined />Add property</Button>
|
||||
</>}
|
||||
trigger="click"
|
||||
visible={this.state.addPopoverVisible}
|
||||
handleVisibleChange={visible => this.setState({ addPopoverVisible: visible })}
|
||||
>
|
||||
<Button
|
||||
onClick={e => this.setState({ addPopoverVisible: true })}
|
||||
>
|
||||
<PlusOutlined />Add property
|
||||
</Button>
|
||||
</Popover>)
|
||||
return <Space direction="vertical">{ret}</Space>
|
||||
}
|
||||
this.state = {}
|
||||
}
|
||||
onChange(e, key) {
|
||||
if (e.target.type === "checkbox") {
|
||||
// if (e.target.checked !== undefined) {
|
||||
// Its a checkbox, set value to true/false
|
||||
let value = { ...this.props.value }
|
||||
value[key] = e.target.checked || false
|
||||
this.props.onChange(value)
|
||||
} else if (e.target.type === "text") {
|
||||
// } else if (e.target.value !== undefined) {
|
||||
// its probably a text input
|
||||
let value = { ...this.props.value }
|
||||
value[key] = e.target.value || ""
|
||||
this.props.onChange(value)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let { value } = this.props
|
||||
let ret = []
|
||||
for (let key in value) {
|
||||
if (typeof value[key] === "boolean") {
|
||||
ret.push(<Checkbox
|
||||
key={key}
|
||||
checked={value[key]}
|
||||
onChange={e => this.onChange(e, key)}
|
||||
>
|
||||
{key}
|
||||
</Checkbox>
|
||||
)
|
||||
} else if (typeof value[key] === "string") {
|
||||
ret.push(<div
|
||||
key={key}
|
||||
><span>{key}</span><Input
|
||||
value={value[key]}
|
||||
onChange={e => this.onChange(e, key)}
|
||||
/></div>
|
||||
)
|
||||
}
|
||||
}
|
||||
ret.push(<Popover
|
||||
key="addMore"
|
||||
content={<>
|
||||
<Input onChange={e => this.setState({ newInput: e.target.value })} />
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.onChange({ target: { value: "", type: "text" } }, this.state.newInput)
|
||||
this.setState({
|
||||
newInput: undefined,
|
||||
addPopoverVisible: false,
|
||||
})
|
||||
}}
|
||||
><PlusOutlined />Add property</Button>
|
||||
</>}
|
||||
trigger="click"
|
||||
visible={this.state.addPopoverVisible}
|
||||
handleVisibleChange={visible => this.setState({ addPopoverVisible: visible })}
|
||||
>
|
||||
<Button
|
||||
onClick={e => this.setState({ addPopoverVisible: true })}
|
||||
>
|
||||
<PlusOutlined />Add property
|
||||
</Button>
|
||||
</Popover>)
|
||||
return <Space direction="vertical">{ret}</Space>
|
||||
}
|
||||
}
|
||||
class ConfigFormItem extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
render() {
|
||||
let { field, group, editingConfig } = this.props
|
||||
return <Form.Item
|
||||
label={field}
|
||||
name={group.name + "." + field}
|
||||
valuePropName={typeof group.fields[field] === "boolean" ? "checked" : "value" /* Boolean fields should be rendered as checkboxes */}
|
||||
>
|
||||
{editingConfig ?
|
||||
renderFormField(group.fields[field])
|
||||
:
|
||||
renderDisplayField(group.fields[field])
|
||||
}
|
||||
</Form.Item>
|
||||
}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
render() {
|
||||
let { field, group, editingConfig } = this.props
|
||||
return <Form.Item
|
||||
label={field}
|
||||
name={group.name + "." + field}
|
||||
valuePropName={typeof group.fields[field] === "boolean" ? "checked" : "value" /* Boolean fields should be rendered as checkboxes */}
|
||||
>
|
||||
{editingConfig ?
|
||||
renderFormField(group.fields[field])
|
||||
:
|
||||
renderDisplayField(group.fields[field])
|
||||
}
|
||||
</Form.Item>
|
||||
}
|
||||
}
|
||||
@@ -5,42 +5,42 @@ import DataTable from "../../components/data-table"
|
||||
const config = require("@clusterio/lib/config")
|
||||
|
||||
export class InstancesTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Instances</h2>
|
||||
<DataTable
|
||||
AddRecord={{
|
||||
fields: [{
|
||||
dataIndex: "name",
|
||||
title: "Name"
|
||||
}],
|
||||
insert: async args => {
|
||||
let instanceConfig = new config.InstanceConfig();
|
||||
await instanceConfig.init();
|
||||
console.log("Base config", instanceConfig)
|
||||
instanceConfig.set("instance.name", args.name);
|
||||
let serialized_config = instanceConfig.serialize();
|
||||
let response = await createInstance(serialized_config)
|
||||
console.log("Created instance", response)
|
||||
}
|
||||
}}
|
||||
DataFunction={listInstances}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/instances/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Instances</h2>
|
||||
<DataTable
|
||||
AddRecord={{
|
||||
fields: [{
|
||||
dataIndex: "name",
|
||||
title: "Name"
|
||||
}],
|
||||
insert: async args => {
|
||||
let instanceConfig = new config.InstanceConfig();
|
||||
await instanceConfig.init();
|
||||
console.log("Base config", instanceConfig)
|
||||
instanceConfig.set("instance.name", args.name);
|
||||
let serialized_config = instanceConfig.serialize();
|
||||
let response = await createInstance(serialized_config)
|
||||
console.log("Created instance", response)
|
||||
}
|
||||
}}
|
||||
DataFunction={listInstances}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/instances/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,27 @@ import { listSlaves, listInstances, listPermissions } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
export class PermissionsTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Permissions</h2>
|
||||
<DataTable
|
||||
DataFunction={listPermissions}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/permissions/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Permissions</h2>
|
||||
<DataTable
|
||||
DataFunction={listPermissions}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/permissions/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -4,23 +4,23 @@ import { Card, Button, Popover, Select, Transfer } from "antd"
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined"
|
||||
|
||||
import {
|
||||
listSlaves,
|
||||
listInstances,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
setInstanceOutputSubscriptions,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
sendRcon,
|
||||
deleteUser,
|
||||
listUsers,
|
||||
listRoles,
|
||||
setRoles,
|
||||
listPermissions,
|
||||
updateRole,
|
||||
listSlaves,
|
||||
listInstances,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
setInstanceOutputSubscriptions,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
sendRcon,
|
||||
deleteUser,
|
||||
listUsers,
|
||||
listRoles,
|
||||
setRoles,
|
||||
listPermissions,
|
||||
updateRole,
|
||||
} from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
import notify from "../../util/notify"
|
||||
@@ -28,86 +28,86 @@ import notify from "../../util/notify"
|
||||
const { Option } = Select
|
||||
|
||||
export class RoleView extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
role: {}
|
||||
}
|
||||
this.messagesEndRef = React.createRef()
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
await this.getData()
|
||||
}
|
||||
async getData() {
|
||||
let id = this.props.match.params.id
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
role: {}
|
||||
}
|
||||
this.messagesEndRef = React.createRef()
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
await this.getData()
|
||||
}
|
||||
async getData() {
|
||||
let id = this.props.match.params.id
|
||||
|
||||
let roles = await listRoles()
|
||||
let permissions = await listPermissions()
|
||||
let roles = await listRoles()
|
||||
let permissions = await listPermissions()
|
||||
|
||||
this.setState({
|
||||
role: roles.find(x => x.id === Number(id)),
|
||||
permissions,
|
||||
})
|
||||
}
|
||||
async handleChange(targetKeys) {
|
||||
this.setState({
|
||||
role: {
|
||||
...this.state.role,
|
||||
permissions: targetKeys
|
||||
}
|
||||
});
|
||||
let response = await updateRole({
|
||||
...this.state.role,
|
||||
permissions: targetKeys,
|
||||
})
|
||||
console.log("Updated user roles", response)
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { role, permissions } = this.state
|
||||
return <Card>
|
||||
<Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={async () => {
|
||||
await deleteUser({ role: role.name })
|
||||
notify("Deleted role " + role.name, "success")
|
||||
this.navigate("/roles")
|
||||
}}
|
||||
>
|
||||
this.setState({
|
||||
role: roles.find(x => x.id === Number(id)),
|
||||
permissions,
|
||||
})
|
||||
}
|
||||
async handleChange(targetKeys) {
|
||||
this.setState({
|
||||
role: {
|
||||
...this.state.role,
|
||||
permissions: targetKeys
|
||||
}
|
||||
});
|
||||
let response = await updateRole({
|
||||
...this.state.role,
|
||||
permissions: targetKeys,
|
||||
})
|
||||
console.log("Updated user roles", response)
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { role, permissions } = this.state
|
||||
return <Card>
|
||||
<Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={async () => {
|
||||
await deleteUser({ role: role.name })
|
||||
notify("Deleted role " + role.name, "success")
|
||||
this.navigate("/roles")
|
||||
}}
|
||||
>
|
||||
Delete this role permanently
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
<h2>{role.name}</h2>
|
||||
<h3>Assign roles</h3>
|
||||
<style>
|
||||
{`
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
<h2>{role.name}</h2>
|
||||
<h3>Assign roles</h3>
|
||||
<style>
|
||||
{`
|
||||
.ant-transfer-list{
|
||||
width: calc( 50% - 34px );
|
||||
height: 500px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<Transfer
|
||||
titles={["Available", "Assigned"]}
|
||||
dataSource={permissions}
|
||||
showSearch
|
||||
filterOption={(inputValue, option) => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1}
|
||||
targetKeys={role.permissions}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
render={item => item.name}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
</style>
|
||||
<Transfer
|
||||
titles={["Available", "Assigned"]}
|
||||
dataSource={permissions}
|
||||
showSearch
|
||||
filterOption={(inputValue, option) => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1}
|
||||
targetKeys={role.permissions}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
render={item => item.name}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -4,40 +4,40 @@ import { listSlaves, listInstances, listRoles, createRole } from "../../util/wsl
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
export class RolesTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Roles</h2>
|
||||
<DataTable
|
||||
AddRecord={{
|
||||
fields: [{
|
||||
dataIndex: "name",
|
||||
title: "Name"
|
||||
},{
|
||||
dataIndex: "description",
|
||||
title: "Description"
|
||||
}],
|
||||
insert: async args => {
|
||||
let response = await createRole({name: args.name, description: args.description})
|
||||
console.log("Created role",response)
|
||||
}
|
||||
}}
|
||||
DataFunction={listRoles}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/roles/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Roles</h2>
|
||||
<DataTable
|
||||
AddRecord={{
|
||||
fields: [{
|
||||
dataIndex: "name",
|
||||
title: "Name"
|
||||
},{
|
||||
dataIndex: "description",
|
||||
title: "Description"
|
||||
}],
|
||||
insert: async args => {
|
||||
let response = await createRole({name: args.name, description: args.description})
|
||||
console.log("Created role",response)
|
||||
}
|
||||
}}
|
||||
DataFunction={listRoles}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/roles/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -2,80 +2,80 @@ import React, { Component } from "react"
|
||||
import { Card, Button, Input, Form } from "antd"
|
||||
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 4,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 16,
|
||||
},
|
||||
labelCol: {
|
||||
span: 4,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 16,
|
||||
},
|
||||
};
|
||||
const tailLayout = {
|
||||
wrapperCol: {
|
||||
offset: 4,
|
||||
span: 16,
|
||||
},
|
||||
wrapperCol: {
|
||||
offset: 4,
|
||||
span: 16,
|
||||
},
|
||||
};
|
||||
|
||||
export class Settings extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
role: {}
|
||||
}
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
return <Card>
|
||||
<h2>Web interface settings</h2>
|
||||
<Form
|
||||
{...layout}
|
||||
name="basic"
|
||||
initialValues={{
|
||||
master_url: localStorage.getItem("master_url"),
|
||||
master_token: localStorage.getItem("master_token"),
|
||||
}}
|
||||
onFinish={values => {
|
||||
console.log("Saving settings",values)
|
||||
for(let key of Object.keys(values)){
|
||||
localStorage.setItem(key, values[key].replace("https://", "wss://").replace("http://", "ws://"))
|
||||
}
|
||||
// Force refresh to apply changes
|
||||
document.location = document.location
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="Master URL"
|
||||
name="master_url"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please input master url, ex ws://localhost:8080 or wss://localhost:8443/ . For SSL connections, ensure you have a valid certificate.',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="User auth token"
|
||||
name="master_token"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please input your user auth token from config-control.json',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item {...tailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
role: {}
|
||||
}
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
return <Card>
|
||||
<h2>Web interface settings</h2>
|
||||
<Form
|
||||
{...layout}
|
||||
name="basic"
|
||||
initialValues={{
|
||||
master_url: localStorage.getItem("master_url"),
|
||||
master_token: localStorage.getItem("master_token"),
|
||||
}}
|
||||
onFinish={values => {
|
||||
console.log("Saving settings",values)
|
||||
for(let key of Object.keys(values)){
|
||||
localStorage.setItem(key, values[key].replace("https://", "wss://").replace("http://", "ws://"))
|
||||
}
|
||||
// Force refresh to apply changes
|
||||
document.location = document.location
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="Master URL"
|
||||
name="master_url"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please input master url, ex ws://localhost:8080 or wss://localhost:8443/ . For SSL connections, ensure you have a valid certificate.',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="User auth token"
|
||||
name="master_token"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please input your user auth token from config-control.json',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item {...tailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Save
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,27 @@ import { listSlaves, listInstances } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
export class SlavesTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Slaves</h2>
|
||||
<DataTable
|
||||
DataFunction={listSlaves}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/slaves/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Slaves</h2>
|
||||
<DataTable
|
||||
DataFunction={listSlaves}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/slaves/${record.id}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Button, Popover, Select, Transfer } from "antd";
|
||||
import {
|
||||
deleteUser,
|
||||
listUsers,
|
||||
listRoles,
|
||||
setRoles,
|
||||
deleteUser,
|
||||
listUsers,
|
||||
listRoles,
|
||||
setRoles,
|
||||
} from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
import notify from "../../util/notify"
|
||||
@@ -13,78 +13,78 @@ import DeleteOutlined from "@ant-design/icons/DeleteOutlined"
|
||||
const { Option } = Select
|
||||
|
||||
export class UserView extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
user: {}
|
||||
}
|
||||
this.messagesEndRef = React.createRef();
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
await this.getData()
|
||||
}
|
||||
async getData() {
|
||||
let name = this.props.match.params.id
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
user: {}
|
||||
}
|
||||
this.messagesEndRef = React.createRef();
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
await this.getData()
|
||||
}
|
||||
async getData() {
|
||||
let name = this.props.match.params.id
|
||||
|
||||
let users = await listUsers()
|
||||
let roles = await listRoles()
|
||||
let users = await listUsers()
|
||||
let roles = await listRoles()
|
||||
|
||||
this.setState({
|
||||
user: users.find(x => x.name === name),
|
||||
roles,
|
||||
})
|
||||
}
|
||||
async handleChange(targetKeys) {
|
||||
this.setState({
|
||||
user: {
|
||||
...this.state.user,
|
||||
roles: targetKeys
|
||||
}
|
||||
});
|
||||
let response = await setRoles({
|
||||
name: this.state.user.name,
|
||||
roles: targetKeys,
|
||||
})
|
||||
console.log("Updated user roles",response)
|
||||
};
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { user, roles } = this.state
|
||||
return <Card>
|
||||
<h2>{user.name}</h2>
|
||||
<h3>Assign roles</h3>
|
||||
<Transfer
|
||||
titles={["Available","Assigned"]}
|
||||
dataSource={roles}
|
||||
showSearch
|
||||
filterOption={(inputValue, option) => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1}
|
||||
targetKeys={user.roles}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
render={item => item.name}
|
||||
/>
|
||||
<Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={async () => {
|
||||
await deleteUser({ name: user.name })
|
||||
notify("Deleted user " + user.name, "success")
|
||||
this.navigate("/users")
|
||||
}}
|
||||
>
|
||||
this.setState({
|
||||
user: users.find(x => x.name === name),
|
||||
roles,
|
||||
})
|
||||
}
|
||||
async handleChange(targetKeys) {
|
||||
this.setState({
|
||||
user: {
|
||||
...this.state.user,
|
||||
roles: targetKeys
|
||||
}
|
||||
});
|
||||
let response = await setRoles({
|
||||
name: this.state.user.name,
|
||||
roles: targetKeys,
|
||||
})
|
||||
console.log("Updated user roles",response)
|
||||
};
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { user, roles } = this.state
|
||||
return <Card>
|
||||
<h2>{user.name}</h2>
|
||||
<h3>Assign roles</h3>
|
||||
<Transfer
|
||||
titles={["Available","Assigned"]}
|
||||
dataSource={roles}
|
||||
showSearch
|
||||
filterOption={(inputValue, option) => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1}
|
||||
targetKeys={user.roles}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
render={item => item.name}
|
||||
/>
|
||||
<Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={async () => {
|
||||
await deleteUser({ name: user.name })
|
||||
notify("Deleted user " + user.name, "success")
|
||||
this.navigate("/users")
|
||||
}}
|
||||
>
|
||||
Delete this user from the cluster
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
</Card>
|
||||
}
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -4,37 +4,37 @@ import { listSlaves, listInstances, listUsers, createUser } from "../../util/wsl
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
export class UsersTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Slaves</h2>
|
||||
<DataTable
|
||||
AddRecord={{
|
||||
fields: [{
|
||||
dataIndex: "name",
|
||||
title: "Name"
|
||||
}],
|
||||
insert: async args => {
|
||||
let response = await createUser({name: args.name})
|
||||
console.log("Created user",response)
|
||||
}
|
||||
}}
|
||||
DataFunction={listUsers}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/users/${record.name}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
render() {
|
||||
return <Card>
|
||||
<h2>Slaves</h2>
|
||||
<DataTable
|
||||
AddRecord={{
|
||||
fields: [{
|
||||
dataIndex: "name",
|
||||
title: "Name"
|
||||
}],
|
||||
insert: async args => {
|
||||
let response = await createUser({name: args.name})
|
||||
console.log("Created user",response)
|
||||
}
|
||||
}}
|
||||
DataFunction={listUsers}
|
||||
TableProps={{
|
||||
onRow: (record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.navigate(`/users/${record.name}/view`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import { notification } from "antd";
|
||||
* @param {("info"|"error"|"success"|"warning"|"warn")} type
|
||||
*/
|
||||
export default function notify(message, type = "info", description) {
|
||||
notification[type]({
|
||||
message: typeof message === "string" ? message : "ERROR: See console",
|
||||
description,
|
||||
placement: "bottomRight",
|
||||
});
|
||||
if (typeof message !== "string") console.error(message)
|
||||
notification[type]({
|
||||
message: typeof message === "string" ? message : "ERROR: See console",
|
||||
description,
|
||||
placement: "bottomRight",
|
||||
});
|
||||
if (typeof message !== "string") console.error(message)
|
||||
}
|
||||
@@ -36,19 +36,19 @@ function formatOutput(output) {
|
||||
* @private
|
||||
*/
|
||||
export class ControlConnector extends link.WebSocketClientConnector {
|
||||
constructor(url, reconnectDelay, token) {
|
||||
super(url, reconnectDelay);
|
||||
this._token = token;
|
||||
}
|
||||
constructor(url, reconnectDelay, token) {
|
||||
super(url, reconnectDelay);
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
register() {
|
||||
console.log("SOCKET | registering control");
|
||||
this.sendHandshake("register_control", {
|
||||
token: this._token,
|
||||
agent: "web",
|
||||
version: "2.0.0-alpha",
|
||||
});
|
||||
}
|
||||
register() {
|
||||
console.log("SOCKET | registering control");
|
||||
this.sendHandshake("register_control", {
|
||||
token: this._token,
|
||||
agent: "web",
|
||||
version: "2.0.0-alpha",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,41 +58,41 @@ export class ControlConnector extends link.WebSocketClientConnector {
|
||||
* @static
|
||||
*/
|
||||
export class Control extends link.Link {
|
||||
constructor(connector, controlPlugins) {
|
||||
super("control", "master", connector);
|
||||
link.attachAllMessages(this);
|
||||
constructor(connector, controlPlugins) {
|
||||
super("control", "master", connector);
|
||||
link.attachAllMessages(this);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Mapping of plugin names to their instance for loaded plugins.
|
||||
* @type {Map<string, module:lib/plugin.BaseControlPlugin>}
|
||||
*/
|
||||
this.plugins = controlPlugins;
|
||||
for (let controlPlugin of controlPlugins.values()) {
|
||||
plugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
|
||||
}
|
||||
}
|
||||
this.plugins = controlPlugins;
|
||||
for (let controlPlugin of controlPlugins.values()) {
|
||||
plugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
async instanceOutputEventHandler(message) {
|
||||
let { instance_id, output } = message.data;
|
||||
console.log(formatOutput(output));
|
||||
window.instanceOutputEventHandler && window.instanceOutputEventHandler({instance_id, output})
|
||||
}
|
||||
async instanceOutputEventHandler(message) {
|
||||
let { instance_id, output } = message.data;
|
||||
console.log(formatOutput(output));
|
||||
window.instanceOutputEventHandler && window.instanceOutputEventHandler({instance_id, output})
|
||||
}
|
||||
|
||||
async debugWsMessageEventHandler(message) {
|
||||
console.log("WS", message.data.direction, message.data.content);
|
||||
}
|
||||
async debugWsMessageEventHandler(message) {
|
||||
console.log("WS", message.data.direction, message.data.content);
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
this.connector.setTimeout(30);
|
||||
async shutdown() {
|
||||
this.connector.setTimeout(30);
|
||||
|
||||
try {
|
||||
await link.messages.prepareDisconnect.send(this);
|
||||
} catch (err) {
|
||||
if (!(err instanceof errors.SessionLost)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await link.messages.prepareDisconnect.send(this);
|
||||
} catch (err) {
|
||||
if (!(err instanceof errors.SessionLost)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
await this.connector.close(1001, "Control Quit");
|
||||
}
|
||||
await this.connector.close(1001, "Control Quit");
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,18 @@ let control, controlConnector
|
||||
|
||||
// Create WS connection to master. Used by all components through the interface exported here.
|
||||
(async () => {
|
||||
config.finalizeConfigs();
|
||||
controlConnector = new ControlConnector(
|
||||
localStorage.getItem("master_url"),
|
||||
10, // Reconnect delay
|
||||
localStorage.getItem("master_token"),
|
||||
);
|
||||
control = new Control(controlConnector, []);
|
||||
try {
|
||||
await controlConnector.connect();
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
config.finalizeConfigs();
|
||||
controlConnector = new ControlConnector(
|
||||
localStorage.getItem("master_url"),
|
||||
10, // Reconnect delay
|
||||
localStorage.getItem("master_token"),
|
||||
);
|
||||
control = new Control(controlConnector, []);
|
||||
try {
|
||||
await controlConnector.connect();
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})()
|
||||
|
||||
let listSlaves = createListFunction("listSlaves")
|
||||
@@ -28,107 +28,107 @@ let listRoles = createListFunction("listRoles")
|
||||
let listPermissions = createListFunction("listPermissions", "name")
|
||||
|
||||
let startInstance = async function ({ instance_id, save = null }) {
|
||||
return await link.messages.startInstance.send(control, { instance_id, save })
|
||||
return await link.messages.startInstance.send(control, { instance_id, save })
|
||||
}
|
||||
let stopInstance = async function ({ instance_id }) {
|
||||
return await link.messages.stopInstance.send(control, { instance_id })
|
||||
return await link.messages.stopInstance.send(control, { instance_id })
|
||||
}
|
||||
let createInstance = async function (serialized_config) {
|
||||
return await link.messages.createInstance.send(control, { serialized_config });
|
||||
return await link.messages.createInstance.send(control, { serialized_config });
|
||||
}
|
||||
let deleteInstance = async function ({ instance_id }) {
|
||||
return await link.messages.deleteInstance.send(control, { instance_id });
|
||||
return await link.messages.deleteInstance.send(control, { instance_id });
|
||||
}
|
||||
let assignInstance = async function ({ instance_id, slave_id }) {
|
||||
return await link.messages.assignInstanceCommand.send(control, {
|
||||
instance_id,
|
||||
slave_id,
|
||||
});
|
||||
return await link.messages.assignInstanceCommand.send(control, {
|
||||
instance_id,
|
||||
slave_id,
|
||||
});
|
||||
}
|
||||
let createSave = async function ({ instance_id }) {
|
||||
return await link.messages.createSave.send(control, { instance_id });
|
||||
return await link.messages.createSave.send(control, { instance_id });
|
||||
}
|
||||
let getInstanceConfig = async function ({ instance_id }) {
|
||||
return await link.messages.getInstanceConfig.send(control, { instance_id });
|
||||
return await link.messages.getInstanceConfig.send(control, { instance_id });
|
||||
}
|
||||
let setInstanceConfigField = async function ({ instance_id, field, value }) {
|
||||
return await link.messages.setInstanceConfigField.send(control, {
|
||||
instance_id,
|
||||
field,
|
||||
value: typeof value === "object" ? JSON.stringify(value) : value,
|
||||
});
|
||||
return await link.messages.setInstanceConfigField.send(control, {
|
||||
instance_id,
|
||||
field,
|
||||
value: typeof value === "object" ? JSON.stringify(value) : value,
|
||||
});
|
||||
}
|
||||
let setInstanceConfigProp = async function ({ instance_id, field, prop, value }) {
|
||||
return await link.messages.setInstanceConfigProp.send(control, {
|
||||
instance_id,
|
||||
field,
|
||||
prop,
|
||||
value, // JSON Object
|
||||
});
|
||||
return await link.messages.setInstanceConfigProp.send(control, {
|
||||
instance_id,
|
||||
field,
|
||||
prop,
|
||||
value, // JSON Object
|
||||
});
|
||||
}
|
||||
let sendRcon = async function ({ instance_id, command }) {
|
||||
return await link.messages.sendRcon.send(control, { instance_id, command })
|
||||
return await link.messages.sendRcon.send(control, { instance_id, command })
|
||||
}
|
||||
let setInstanceOutputSubscriptions = async function ({ instance_id }) {
|
||||
return await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instance_id] })
|
||||
return await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instance_id] })
|
||||
}
|
||||
|
||||
let createUser = async function ({ name }) {
|
||||
return await link.messages.createUser.send(control, { name })
|
||||
return await link.messages.createUser.send(control, { name })
|
||||
}
|
||||
let deleteUser = async function ({ name }) {
|
||||
return await link.messages.deleteUser.send(control, { name })
|
||||
return await link.messages.deleteUser.send(control, { name })
|
||||
}
|
||||
let setRoles = async function ({ name, roles }) {
|
||||
return await link.messages.updateUserRoles.send(control, {
|
||||
name,
|
||||
roles,
|
||||
})
|
||||
return await link.messages.updateUserRoles.send(control, {
|
||||
name,
|
||||
roles,
|
||||
})
|
||||
}
|
||||
|
||||
let createRole = async function ({ name, description, permissions = [] }) {
|
||||
return await link.messages.createRole.send(control, { name, description, permissions })
|
||||
return await link.messages.createRole.send(control, { name, description, permissions })
|
||||
}
|
||||
let updateRole = async function ({ id, name, description, permissions }) {
|
||||
return await link.messages.updateRole.send(control, {
|
||||
id, name, description, permissions
|
||||
})
|
||||
return await link.messages.updateRole.send(control, {
|
||||
id, name, description, permissions
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
listSlaves,
|
||||
listInstances,
|
||||
listUsers,
|
||||
createUser,
|
||||
deleteUser,
|
||||
listRoles,
|
||||
setRoles,
|
||||
createRole,
|
||||
updateRole,
|
||||
listPermissions,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
createInstance,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
setInstanceConfigProp,
|
||||
sendRcon,
|
||||
setInstanceOutputSubscriptions,
|
||||
listSlaves,
|
||||
listInstances,
|
||||
listUsers,
|
||||
createUser,
|
||||
deleteUser,
|
||||
listRoles,
|
||||
setRoles,
|
||||
createRole,
|
||||
updateRole,
|
||||
listPermissions,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
createInstance,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
setInstanceConfigProp,
|
||||
sendRcon,
|
||||
setInstanceOutputSubscriptions,
|
||||
}
|
||||
|
||||
function createListFunction(name, key) {
|
||||
return async () => {
|
||||
return (await link.messages[name].send(control)).list.map(addKey(key || "id"));
|
||||
}
|
||||
return async () => {
|
||||
return (await link.messages[name].send(control)).list.map(addKey(key || "id"));
|
||||
}
|
||||
}
|
||||
function addKey(key) {
|
||||
return function (el) {
|
||||
return {
|
||||
...el,
|
||||
key: el[key]
|
||||
}
|
||||
}
|
||||
return function (el) {
|
||||
return {
|
||||
...el,
|
||||
key: el[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user