Fix web interface indents

Use project standard of tabs for indents.
This commit is contained in:
Hornwitser committed 2020-09-28 05:51:15 +02:00
1 parent 94200433cc
commit 8dce1dd0fd
16 files changed
+1267 -1267

No files matched your search

+5 -5
View File
@@ -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>
);
}
+230 -230
View File
@@ -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;
+133 -133
View File
@@ -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);
+6 -6
View File
@@ -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>
}
}
+383 -383
View File
@@ -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>
}
}
+91 -91
View File
@@ -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>
}
}
+36 -36
View File
@@ -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>
}
}
+70 -70
View File
@@ -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>
}
}
+75 -75
View File
@@ -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>
}
}
+33 -33
View File
@@ -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 -6
View File
@@ -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)
}
+40 -40
View File
@@ -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");
}
}
+75 -75
View File
@@ -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]
}
}
}