mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Implement webpack build for web interface
Set up webpack for the master server's web interface and fix the issues from the imported interface source files. The .jsx files in package/master/web are bundled together into package/master/static and served by the master server via express.
This commit is contained in:
1 parent
2382f4bbdc
commit
701ff87bcd
25 files changed
+219
-191
No files matched your search
@@ -41,7 +41,6 @@ Factorio*
|
||||
package-lock.json
|
||||
mod_cache.json
|
||||
static/export
|
||||
packages/master/static/external
|
||||
saves
|
||||
/temp/
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Webpack output
|
||||
static
|
||||
@@ -1553,7 +1553,9 @@ async function startServer() {
|
||||
})
|
||||
.demandCommand(1, "You need to specify a command to run");
|
||||
})
|
||||
.command("run", "Run master server")
|
||||
.command("run", "Run master server", yargs => {
|
||||
yargs.option("dev", { hidden: true, type: "boolean", nargs: 0 });
|
||||
})
|
||||
.demandCommand(1, "You need to specify a command to run")
|
||||
.strict()
|
||||
.argv
|
||||
@@ -1634,6 +1636,16 @@ async function startServer() {
|
||||
|
||||
// If we get here the command was run
|
||||
|
||||
if (args.dev) {
|
||||
console.log("Development mode enabled");
|
||||
const webpack = require("webpack");
|
||||
const middleware = require("webpack-dev-middleware");
|
||||
const webpackConfig = require("./webpack.config");
|
||||
|
||||
const compiler = webpack(webpackConfig({}));
|
||||
app.use(middleware(compiler, {}));
|
||||
}
|
||||
|
||||
let secondSigint = false;
|
||||
process.on("SIGINT", () => {
|
||||
if (secondSigint) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"version": "2.0.0-alpha0",
|
||||
"main": "master.js",
|
||||
"scripts": {
|
||||
"build": "webpack --env.production",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -32,5 +33,24 @@
|
||||
"set-blocking": "^2.0.0",
|
||||
"ws": "^7.2.3",
|
||||
"yargs": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.2.2",
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"antd": "^4.6.5",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^4.3.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"null-loader": "^4.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"style-loader": "^1.2.1",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-middleware": "^3.7.2"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,9 @@
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Collapse, Row, Col, Modal, Table,
|
||||
Form,
|
||||
Input,
|
||||
Button,
|
||||
Radio,
|
||||
Select,
|
||||
Cascader,
|
||||
DatePicker,
|
||||
InputNumber,
|
||||
TreeSelect,
|
||||
Switch,
|
||||
Popover
|
||||
} from "antd";
|
||||
import { Collapse, Row, Col, Modal, Table, Form, Input, Button, Popover } from "antd";
|
||||
import PlusOutlined from "@ant-design/icons/PlusOutlined"
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
|
||||
|
||||
import notify from "../util/notify";
|
||||
import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
@@ -45,10 +34,12 @@ class DataTable extends Component {
|
||||
record: {},
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData()
|
||||
}
|
||||
getData = async (pagination, filters, sorter) => {
|
||||
|
||||
async getData(pagination, filters, sorter) {
|
||||
/*
|
||||
pagination = {
|
||||
pageSize: 10, // items per page
|
||||
@@ -84,21 +75,22 @@ class DataTable extends Component {
|
||||
filters,
|
||||
})
|
||||
}
|
||||
showEditModal = (record, index) => {
|
||||
|
||||
showEditModal(record, index) {
|
||||
if (!this.state.showEditModal)
|
||||
this.setState({
|
||||
record: record,
|
||||
showEditModal: true,
|
||||
})
|
||||
}
|
||||
hideEditModal = () => {
|
||||
hideEditModal() {
|
||||
this.setState({
|
||||
showEditModal: false,
|
||||
inserting: false,
|
||||
record: {}
|
||||
})
|
||||
}
|
||||
onEditFinish = async values => {
|
||||
async onEditFinish(values) {
|
||||
console.log("Finished editing", values)
|
||||
|
||||
if (this.state.inserting) { // Create new record
|
||||
@@ -116,7 +108,7 @@ class DataTable extends Component {
|
||||
// Force refresh of data
|
||||
this.getData()
|
||||
}
|
||||
deleteRecord = async record => {
|
||||
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) {
|
||||
@@ -148,7 +140,7 @@ class DataTable extends Component {
|
||||
object. This way the end user isn't affected by our event handlers.
|
||||
*/
|
||||
let externalOnRow = {
|
||||
...this.props.TableProps?.onRow?.(record, rowIndex),
|
||||
...(this.props.TableProps && this.props.TableProps.onRow && this.props.TableProps.onRow(record, rowIndex)),
|
||||
}
|
||||
return {
|
||||
onClick: event => {
|
||||
@@ -219,7 +211,7 @@ class DataTable extends Component {
|
||||
})}
|
||||
dataSource={this.state.tableData}
|
||||
pagination={this.state.pagination.total > 10 ? this.state.pagination : false}
|
||||
onChange={this.getData}
|
||||
onChange={this.getData.bind(this)}
|
||||
/>
|
||||
{this.props.AddRecord && <Button type="primary" onClick={() => this.setState({
|
||||
showEditModal: true,
|
||||
@@ -244,18 +236,18 @@ class DataTable extends Component {
|
||||
span: 12,
|
||||
}}
|
||||
layout="horizontal"
|
||||
onFinish={this.onEditFinish}
|
||||
onFinish={this.onEditFinish.bind(this)}
|
||||
initialValues={this.state.record}
|
||||
>
|
||||
{
|
||||
|
||||
(
|
||||
(this.state.inserting && this.props.AddRecord?.fields) // Use externally supplied form input fields
|
||||
|| this.props.columns?.filter(x => x.dataIndex !== "notInUse") // Generate input fields from external columns
|
||||
(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?.(this.state.record[col.dataIndex], this.state.record, undefined /* Non spec compliant, is supposed to be index of record */, this.state.tableData) || <Input
|
||||
{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>
|
||||
@@ -271,7 +263,7 @@ class DataTable extends Component {
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
inferColumns = data => {
|
||||
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 => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '~antd/dist/antd.dark.css';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Clusterio</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import Layout from './layout'
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import moment from "moment";
|
||||
import locale from "moment/locale/nb";
|
||||
|
||||
moment.updateLocale("nb", locale);
|
||||
// ReactDOM.render(
|
||||
// <React.StrictMode>
|
||||
// <Layout />
|
||||
// </React.StrictMode>,
|
||||
// document.getElementById('root')
|
||||
// );
|
||||
|
||||
ReactDOM.render(
|
||||
<BrowserRouter>
|
||||
<Layout />
|
||||
</BrowserRouter>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { BrowserRouter } from "react-router-dom"
|
||||
|
||||
import "./index.css"
|
||||
import Layout from "./layout"
|
||||
|
||||
ReactDOM.render(
|
||||
<BrowserRouter>
|
||||
<Layout />
|
||||
</BrowserRouter>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -1,23 +1,9 @@
|
||||
import {
|
||||
Layout,
|
||||
Menu,
|
||||
Avatar,
|
||||
} from "antd";
|
||||
import 'antd/dist/antd.dark.css';
|
||||
import {
|
||||
UnorderedListOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined,
|
||||
GoldOutlined,
|
||||
FileTextOutlined,
|
||||
ScheduleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Layout, Menu } from "antd";
|
||||
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { Dashboard } from "../pages/dashboard";
|
||||
import { InstancesTable } from "../pages/instances/instancesTable";
|
||||
import { SlavesTable } from "../pages/slaves/slavesTable"
|
||||
@@ -40,8 +26,6 @@ class SiteLayout extends Component {
|
||||
|
||||
// this.history = useHistory();
|
||||
|
||||
this.onCollapse = this.onCollapse.bind(this);
|
||||
|
||||
this.state = {
|
||||
collapsed: false,
|
||||
setcollapsed: false,
|
||||
@@ -76,19 +60,11 @@ class SiteLayout extends Component {
|
||||
return (
|
||||
<Router>
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<Helmet>
|
||||
<meta charSet="utf-8" />
|
||||
<title>Clusterio 2.0</title>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Titillium+Web:200,400&display=swap"
|
||||
rel="stylesheet"
|
||||
></link>
|
||||
</Helmet>
|
||||
{document.location.search !== "?hideExtras" ? (
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={this.state.collapsed}
|
||||
onCollapse={this.onCollapse}
|
||||
onCollapse={this.onCollapse.bind(this)}
|
||||
width={250}
|
||||
breakpoint="lg"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table, Button, List, Popover, Select, Divider, Input, Form, Col, Row, Checkbox, Space } from "antd"
|
||||
import { Card, Button, Popover, Select, Divider, Input, Form, Col, Row, Checkbox, Space } from "antd"
|
||||
import {
|
||||
listSlaves,
|
||||
listInstances,
|
||||
@@ -15,7 +15,9 @@ import {
|
||||
} from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
import notify from "../../util/notify"
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons"
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined"
|
||||
import EditOutlined from "@ant-design/icons/EditOutlined"
|
||||
import PlusOutlined from "@ant-design/icons/PlusOutlined"
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
@@ -27,8 +29,8 @@ export class InstanceView extends Component {
|
||||
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()
|
||||
}
|
||||
messagesEndRef = React.createRef()
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
@@ -50,7 +52,7 @@ export class InstanceView extends Component {
|
||||
// this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
getData = async () => {
|
||||
async getData() {
|
||||
let id = this.props.match.params.id
|
||||
|
||||
let instances = await listInstances()
|
||||
@@ -67,7 +69,7 @@ export class InstanceView extends Component {
|
||||
config
|
||||
})
|
||||
}
|
||||
start = async () => {
|
||||
async start() {
|
||||
this.setState({
|
||||
starting: true,
|
||||
})
|
||||
@@ -88,7 +90,7 @@ export class InstanceView extends Component {
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
stop = async () => {
|
||||
async stop() {
|
||||
this.setState({
|
||||
stopping: true,
|
||||
})
|
||||
@@ -108,7 +110,7 @@ export class InstanceView extends Component {
|
||||
})
|
||||
this.getData()
|
||||
}
|
||||
createSave = async () => {
|
||||
async createSave() {
|
||||
this.setState({
|
||||
creatingSave: true,
|
||||
})
|
||||
@@ -127,7 +129,7 @@ export class InstanceView extends Component {
|
||||
creatingSave: false,
|
||||
})
|
||||
}
|
||||
onConfigEditFinish = async values => {
|
||||
async onConfigEditFinish(values) {
|
||||
console.log("Completed config edit with", values)
|
||||
for (let k in values) {
|
||||
if (values[k] !== this.getInstanceConfigAsKvObject(this.state.config)[k]) {
|
||||
@@ -150,28 +152,29 @@ export class InstanceView extends Component {
|
||||
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: {slaves?.find(x => x.id === instance.assigned_slave)?.name}</p>
|
||||
<p>Slave: {slave && slave.name}</p>
|
||||
{instance.status !== "running" && <Button
|
||||
loading={this.state.starting}
|
||||
type="primary"
|
||||
onClick={this.start}
|
||||
onClick={this.start.bind(this)}
|
||||
>
|
||||
Start server
|
||||
</Button>}
|
||||
{instance.status !== "stopped" && <Button
|
||||
loading={this.state.stopping}
|
||||
type="primary"
|
||||
onClick={this.stop}
|
||||
onClick={this.stop.bind(this)}
|
||||
>
|
||||
Stop server
|
||||
</Button>}
|
||||
{instance.status !== "running" && <Button
|
||||
loading={this.state.creatingSave}
|
||||
type="primary"
|
||||
onClick={this.createSave}
|
||||
onClick={this.createSave.bind(this)}
|
||||
>
|
||||
Create save
|
||||
</Button>}
|
||||
@@ -283,7 +286,7 @@ export class InstanceView extends Component {
|
||||
<Form
|
||||
{...layout}
|
||||
initialValues={this.getInstanceConfigAsKvObject(config)}
|
||||
onFinish={this.onConfigEditFinish}
|
||||
onFinish={this.onConfigEditFinish.bind(this)}
|
||||
>
|
||||
{this.state.editingConfig ?
|
||||
<Form.Item>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table } from "antd"
|
||||
import { Card } from "antd"
|
||||
import { listSlaves, listInstances, createInstance } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
const config = require("../../lib/config")
|
||||
const config = require("@clusterio/lib/config")
|
||||
|
||||
export class InstancesTable extends Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table } from "antd"
|
||||
import { Card } from "antd"
|
||||
import { listSlaves, listInstances, listPermissions } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table, Button, List, Popover, Select, Divider, Input, Form, Col, Row, Checkbox, Space, Transfer } from "antd"
|
||||
|
||||
import { Card, Button, Popover, Select, Transfer } from "antd"
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined"
|
||||
|
||||
import {
|
||||
listSlaves,
|
||||
listInstances,
|
||||
@@ -21,7 +24,6 @@ import {
|
||||
} from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
import notify from "../../util/notify"
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons"
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
@@ -31,15 +33,15 @@ export class RoleView extends Component {
|
||||
this.state = {
|
||||
role: {}
|
||||
}
|
||||
this.messagesEndRef = React.createRef()
|
||||
}
|
||||
messagesEndRef = React.createRef()
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
await this.getData()
|
||||
}
|
||||
getData = async () => {
|
||||
async getData() {
|
||||
let id = this.props.match.params.id
|
||||
|
||||
let roles = await listRoles()
|
||||
@@ -50,7 +52,7 @@ export class RoleView extends Component {
|
||||
permissions,
|
||||
})
|
||||
}
|
||||
handleChange = async targetKeys => {
|
||||
async handleChange(targetKeys) {
|
||||
this.setState({
|
||||
role: {
|
||||
...this.state.role,
|
||||
@@ -62,7 +64,7 @@ export class RoleView extends Component {
|
||||
permissions: targetKeys,
|
||||
})
|
||||
console.log("Updated user roles", response)
|
||||
};
|
||||
}
|
||||
render() {
|
||||
console.log(this.state)
|
||||
let { role, permissions } = this.state
|
||||
@@ -103,7 +105,7 @@ export class RoleView extends Component {
|
||||
showSearch
|
||||
filterOption={(inputValue, option) => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1}
|
||||
targetKeys={role.permissions}
|
||||
onChange={this.handleChange}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
render={item => item.name}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table } from "antd"
|
||||
import { Card } from "antd"
|
||||
import { listSlaves, listInstances, listRoles, createRole } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Button, Select, Input, Form } from "antd"
|
||||
import { Card, Button, Input, Form } from "antd"
|
||||
|
||||
const layout = {
|
||||
labelCol: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table } from "antd"
|
||||
import { Card } from "antd";
|
||||
import { listSlaves, listInstances } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table, Button, List, Popover, Select, Divider, Input, Form, Col, Row, Checkbox, Space, Transfer } from "antd"
|
||||
import { Card, Button, Popover, Select, Transfer } from "antd";
|
||||
import {
|
||||
listSlaves,
|
||||
listInstances,
|
||||
startInstance,
|
||||
stopInstance,
|
||||
setInstanceOutputSubscriptions,
|
||||
deleteInstance,
|
||||
assignInstance,
|
||||
createSave,
|
||||
getInstanceConfig,
|
||||
setInstanceConfigField,
|
||||
sendRcon,
|
||||
deleteUser,
|
||||
listUsers,
|
||||
listRoles,
|
||||
@@ -19,7 +8,7 @@ import {
|
||||
} from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
import notify from "../../util/notify"
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons"
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined"
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
@@ -29,15 +18,15 @@ export class UserView extends Component {
|
||||
this.state = {
|
||||
user: {}
|
||||
}
|
||||
this.messagesEndRef = React.createRef();
|
||||
}
|
||||
messagesEndRef = React.createRef()
|
||||
navigate(url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
async componentDidMount() {
|
||||
await this.getData()
|
||||
}
|
||||
getData = async () => {
|
||||
async getData() {
|
||||
let name = this.props.match.params.id
|
||||
|
||||
let users = await listUsers()
|
||||
@@ -48,7 +37,7 @@ export class UserView extends Component {
|
||||
roles,
|
||||
})
|
||||
}
|
||||
handleChange = async targetKeys => {
|
||||
async handleChange(targetKeys) {
|
||||
this.setState({
|
||||
user: {
|
||||
...this.state.user,
|
||||
@@ -73,7 +62,7 @@ export class UserView extends Component {
|
||||
showSearch
|
||||
filterOption={(inputValue, option) => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1}
|
||||
targetKeys={user.roles}
|
||||
onChange={this.handleChange}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
render={item => item.name}
|
||||
/>
|
||||
<Popover
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react"
|
||||
import { Card, Table } from "antd"
|
||||
import { Card } from "antd"
|
||||
import { listSlaves, listInstances, listUsers, createUser } from "../../util/wslink"
|
||||
import DataTable from "../../components/data-table"
|
||||
|
||||
|
||||
File renamed without changes.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
File renamed without changes.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
File renamed without changes.
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -1,49 +1,41 @@
|
||||
const link = require("./../lib/link");
|
||||
const plugin = require("./../lib/plugin");
|
||||
const errors = require("./../lib/errors");
|
||||
const chalk = require("chalk");
|
||||
const link = require("@clusterio/lib/link");
|
||||
const plugin = require("@clusterio/lib/plugin");
|
||||
const errors = require("@clusterio/lib/errors");
|
||||
|
||||
/**
|
||||
* Format a parsed Factorio output message with colors
|
||||
* Format a parsed Factorio output message
|
||||
*
|
||||
* Formats a parsed Factorio output from lib/factorio into a readable
|
||||
* colorized output using terminal escape codes that can be printed.
|
||||
* Formats a parsed Factorio output from lib/factorio back into the
|
||||
* text string it was parsed from.
|
||||
*
|
||||
* @param {Object} output - Factorio server output.
|
||||
* @returns {string} terminal colorized message.
|
||||
* @returns {string} origial output text.
|
||||
* @private
|
||||
*/
|
||||
function formatOutputColored(output) {
|
||||
function formatOutput(output) {
|
||||
let time = "";
|
||||
if (output.format === "seconds") {
|
||||
time = chalk.yellow(output.time.padStart(8)) + " ";
|
||||
time = `${output.time.padStart(8)} `;
|
||||
} else if (output.format === "date") {
|
||||
time = chalk.yellow(output.time) + " ";
|
||||
time = `${output.time} `;
|
||||
}
|
||||
|
||||
let info = "";
|
||||
if (output.type === "log") {
|
||||
let level = output.level;
|
||||
if (level === "Info") {
|
||||
level = chalk.bold.blueBright(level);
|
||||
} else if (output.level === "Warning") {
|
||||
level = chalk.bold.yellowBright(level);
|
||||
} else if (output.level === "Error") {
|
||||
level = chalk.bold.redBright(level);
|
||||
}
|
||||
|
||||
info = level + " " + chalk.gray(output.file) + ": ";
|
||||
info = `${output.level} ${output.file}: `;
|
||||
|
||||
} else if (output.type === "action") {
|
||||
info = "[" + chalk.yellow(output.action) + "] ";
|
||||
info = `[${output.action}] `;
|
||||
}
|
||||
|
||||
return time + info + output.message;
|
||||
return `${time}${info}${output.message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connector for control connection to master server
|
||||
* @private
|
||||
*/
|
||||
class ControlConnector extends link.WebSocketClientConnector {
|
||||
export class ControlConnector extends link.WebSocketClientConnector {
|
||||
constructor(url, reconnectDelay, token) {
|
||||
super(url, reconnectDelay);
|
||||
this._token = token;
|
||||
@@ -53,7 +45,7 @@ class ControlConnector extends link.WebSocketClientConnector {
|
||||
console.log("SOCKET | registering control");
|
||||
this.sendHandshake("register_control", {
|
||||
token: this._token,
|
||||
agent: "clusterctl",
|
||||
agent: "web",
|
||||
version: "2.0.0-alpha",
|
||||
});
|
||||
}
|
||||
@@ -65,16 +57,15 @@ class ControlConnector extends link.WebSocketClientConnector {
|
||||
* Connects to the master server over WebSocket and sends commands to it.
|
||||
* @static
|
||||
*/
|
||||
class Control extends link.Link {
|
||||
|
||||
export class Control extends link.Link {
|
||||
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>}
|
||||
*/
|
||||
/**
|
||||
* 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);
|
||||
@@ -83,7 +74,7 @@ class Control extends link.Link {
|
||||
|
||||
async instanceOutputEventHandler(message) {
|
||||
let { instance_id, output } = message.data;
|
||||
console.log(formatOutputColored(output));
|
||||
console.log(formatOutput(output));
|
||||
window.instanceOutputEventHandler && window.instanceOutputEventHandler({instance_id, output})
|
||||
}
|
||||
|
||||
@@ -105,8 +96,3 @@ class Control extends link.Link {
|
||||
await this.connector.close(1001, "Control Quit");
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
ControlConnector,
|
||||
Control
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Control, ControlConnector } from "./../util/websocket"
|
||||
import * as errors from "./../lib/errors"
|
||||
import * as link from "./../lib/link"
|
||||
import * as errors from "@clusterio/lib/errors"
|
||||
import * as link from "@clusterio/lib/link"
|
||||
import config from "@clusterio/lib/config"
|
||||
|
||||
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
|
||||
@@ -16,31 +18,7 @@ let control, controlConnector
|
||||
await controlConnector.connect();
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
if (err instanceof errors.AuthenticationFailed) {
|
||||
throw new errors.StartupError(err.message);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
let response = await link.messages.listSlaves.send(control);
|
||||
|
||||
// console.log(response.list);
|
||||
console.log(link.messages)
|
||||
let data = {}
|
||||
// for (let key in {
|
||||
// ...link.messages,
|
||||
// }) {
|
||||
// if (link.messages[key].constructor.name === "Request") {
|
||||
// let resp, error
|
||||
// try{
|
||||
// resp = await link.messages[key].send(control)
|
||||
// } catch(e){
|
||||
// error = e
|
||||
// }
|
||||
// data[key] = resp
|
||||
// data[key].error = error
|
||||
// }
|
||||
// }
|
||||
// console.log(data)
|
||||
})()
|
||||
|
||||
let listSlaves = createListFunction("listSlaves")
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
|
||||
module.exports = (env = {}) => ({
|
||||
entry: "./web/index.jsx",
|
||||
mode: env.production ? "production" : "development",
|
||||
devtool: env.production ? "source-map" : "eval-source-map",
|
||||
devServer: {
|
||||
contentBase: "./static",
|
||||
},
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.APP_ENV": JSON.stringify("browser"),
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "web/index.html",
|
||||
}),
|
||||
|
||||
// Make sure ant-design icons use the ES variant
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@ant-design\/icons\/[A-Z]/,
|
||||
function(resource) {
|
||||
resource.request = resource.request.replace(/@ant-design\/icons/, "$&/es/icons");
|
||||
}
|
||||
),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /node_modules.fs-extra/,
|
||||
use: "null-loader",
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: !env.production,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.jsx$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-react"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".json", ".jsx"],
|
||||
},
|
||||
node: {
|
||||
"crypto": "empty",
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user