mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Implement plugin loading in web interface
Add loading of plugin info to the web interface via webpack Module Federation.
This commit is contained in:
1 parent
542d8a798f
commit
6dbf29153e
25 files changed
+360
-102
No files matched your search
@@ -1 +1,2 @@
|
||||
packages/master/static
|
||||
plugins/*/static
|
||||
@@ -47,6 +47,7 @@ saves
|
||||
|
||||
# Web build
|
||||
/packages/master/static
|
||||
/plugins/*/static
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
+2
-1
@@ -33,6 +33,7 @@
|
||||
"lerna": "^3.22.1",
|
||||
"mocha": "^6.2.0",
|
||||
"nyc": "^14.1.1",
|
||||
"phin": "^3.4.1"
|
||||
"phin": "^3.4.1",
|
||||
"webpack": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -201,6 +201,16 @@ function validateSlaveToken(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
app.get("/api/plugins", (req, res) => {
|
||||
let plugins = [];
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
let name = pluginInfo.name;
|
||||
let enabled = masterPlugins.has(name) && masterConfig.group(name).get("enabled");
|
||||
plugins.push({ name, enabled });
|
||||
}
|
||||
res.send(plugins);
|
||||
});
|
||||
|
||||
// Handle an uploaded export package.
|
||||
async function uploadExport(req, res, next) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
@@ -1744,6 +1754,11 @@ async function startServer() {
|
||||
for (let route of pluginInfo.routes || []) {
|
||||
app.get(route, serveWeb(route));
|
||||
}
|
||||
|
||||
let pluginPackagePath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json"));
|
||||
let staticPath = path.join(path.dirname(pluginPackagePath), "static");
|
||||
console.log(`/plugin/${pluginInfo.name}`, staticPath);
|
||||
app.use(`/plugin/${pluginInfo.name}`, express.static(staticPath));
|
||||
}
|
||||
|
||||
// Load plugins
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"version": "2.0.0-alpha.0",
|
||||
"main": "master.js",
|
||||
"scripts": {
|
||||
"build": "webpack --env.production",
|
||||
"build": "webpack-cli --env.production",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -42,15 +42,17 @@
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^4.3.0",
|
||||
"events": "^3.2.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"null-loader": "^4.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"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"
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-merge": "^5.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Calculate basedir of application based on current location and
|
||||
// the provided web_root from master server.
|
||||
const location = new URL(window.web_root, document.location);
|
||||
const location = new URL(window.webRoot, document.location);
|
||||
const basename = location.pathname !== "/" ? location.pathname : "";
|
||||
|
||||
export default basename;
|
||||
@@ -0,0 +1,77 @@
|
||||
import "./index.css";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import libConfig from "@clusterio/lib/config";
|
||||
|
||||
import App from "./components/App";
|
||||
import { Control, ControlConnector } from "./util/websocket";
|
||||
|
||||
async function loadScript(url) {
|
||||
let script = document.createElement("script");
|
||||
script.src = url;
|
||||
script.type = "text/javascript";
|
||||
script.async = true;
|
||||
|
||||
let result = new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
});
|
||||
|
||||
document.head.appendChild(script);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function loadPluginInfos() {
|
||||
let response = await fetch(`${webRoot}api/plugins`);
|
||||
let pluginList;
|
||||
if (response.ok) {
|
||||
pluginList = await response.json();
|
||||
|
||||
} else {
|
||||
console.log("Failed to get plugin data, running without plugins");
|
||||
pluginList = [];
|
||||
}
|
||||
|
||||
let pluginInfos = [];
|
||||
await __webpack_init_sharing__("default");
|
||||
for (let plugin of pluginList) {
|
||||
try {
|
||||
await loadScript(`${webRoot}plugin/${plugin.name}/remoteEntry.js`);
|
||||
let container = window[`plugin_${plugin.name}`];
|
||||
await container.init(__webpack_share_scopes__.default);
|
||||
let info = (await container.get("./info"))();
|
||||
info.container = container;
|
||||
pluginInfos.push(info);
|
||||
|
||||
} catch (err) {
|
||||
console.log(`Failed to load plugin info for ${plugin.name}`);
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
return pluginInfos;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
let pluginInfos = await loadPluginInfos();
|
||||
libConfig.registerPluginConfigGroups(pluginInfos);
|
||||
libConfig.finalizeConfigs();
|
||||
|
||||
let wsUrl = new URL(window.webRoot, document.location);
|
||||
wsUrl.protocol = wsUrl.protocol.replace("http", "ws");
|
||||
|
||||
let controlConnector = new ControlConnector(wsUrl, 10, localStorage.getItem("master_token"));
|
||||
// XXX come up with a better way of sharing this
|
||||
window.control = new Control(controlConnector, []);
|
||||
try {
|
||||
await controlConnector.connect();
|
||||
} catch (err) {
|
||||
// XXX: What now?
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
}
|
||||
|
||||
load().catch(console.log);
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Clusterio</title>
|
||||
<script>
|
||||
window.web_root = "__WEB_ROOT__";
|
||||
window.webRoot = "__WEB_ROOT__";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
import "./index.css";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import App from "./components/App";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
import("./bootstrap");
|
||||
@@ -36,7 +36,6 @@ export class Settings extends Component {
|
||||
{...layout}
|
||||
name="basic"
|
||||
initialValues={{
|
||||
master_url: localStorage.getItem("master_url"),
|
||||
master_token: localStorage.getItem("master_token"),
|
||||
}}
|
||||
onFinish={values => {
|
||||
@@ -48,18 +47,6 @@ export class Settings extends Component {
|
||||
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"
|
||||
|
||||
@@ -1,25 +1,7 @@
|
||||
import { Control, ControlConnector } from "./../util/websocket";
|
||||
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
|
||||
localStorage.getItem("master_token"),
|
||||
);
|
||||
control = new Control(controlConnector, []);
|
||||
try {
|
||||
await controlConnector.connect();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
})();
|
||||
// Note: control is created as in bootstrap.jsx as a global.
|
||||
|
||||
let listSlaves = createListFunction("listSlaves");
|
||||
let listInstances = createListFunction("listInstances");
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"use strict";
|
||||
const webpack = require("webpack");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
|
||||
module.exports = (env = {}) => ({
|
||||
mode: env.production ? "production" : "development",
|
||||
devtool: env.production ? "source-map" : "eval-source-map",
|
||||
performance: {
|
||||
maxAssetSize: 2**21,
|
||||
maxEntrypointSize: 2**21,
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.APP_ENV": JSON.stringify("browser"),
|
||||
}),
|
||||
|
||||
// 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: /\.js$/,
|
||||
resolve: {
|
||||
// XXX Required due to bug in babel-runtime
|
||||
// see: https://github.com/babel/babel/issues/12058
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.jsx$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-react"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".json", ".jsx"],
|
||||
fallback: {
|
||||
"events": require.resolve("events/"),
|
||||
"path": require.resolve("path-browserify"),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
module.exports = (env = {}) => ({
|
||||
const common = require("./webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
mode: env.production ? "production" : "development",
|
||||
devtool: env.production ? "source-map" : "eval-source-map",
|
||||
devServer: {
|
||||
contentBase: "./static",
|
||||
},
|
||||
@@ -14,58 +14,16 @@ module.exports = (env = {}) => ({
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
},
|
||||
performance: {
|
||||
maxAssetSize: 2**21,
|
||||
maxEntrypointSize: 2**21,
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.APP_ENV": JSON.stringify("browser"),
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "master",
|
||||
shared: {
|
||||
"@clusterio/lib": { singleton: true },
|
||||
"ajv": {},
|
||||
"antd": { singleton: true },
|
||||
"react": { singleton: true },
|
||||
"react-dom": { singleton: true },
|
||||
},
|
||||
}),
|
||||
|
||||
// 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",
|
||||
},
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
"license": "MIT",
|
||||
"repository": "clusterio/factorioClusterio",
|
||||
"scripts": {
|
||||
"build": "webpack-cli --env.production",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"bugs": {
|
||||
@@ -15,6 +16,18 @@
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@clusterio/master": "^2.0.0-alpha.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"events": "^3.2.0",
|
||||
"null-loader": "^4.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^5.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const common = require("@clusterio/master/webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "global_chat",
|
||||
library: { type: "var", name: "plugin_global_chat" },
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
"ajv": { import: false },
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
"license": "MIT",
|
||||
"repository": "clusterio/factorioClusterio",
|
||||
"scripts": {
|
||||
"build": "webpack-cli --env.production",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"bugs": {
|
||||
@@ -15,9 +16,21 @@
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
},
|
||||
"ependencies": {
|
||||
"dependencies": {
|
||||
"fs-extra": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@clusterio/master": "^2.0.0-alpha.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"events": "^3.2.0",
|
||||
"null-loader": "^4.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^5.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const common = require("@clusterio/master/webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "research_sync",
|
||||
library: { type: "var", name: "plugin_research_sync" },
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
"ajv": { import: false },
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
"license": "MIT",
|
||||
"repository": "clusterio/factorioClusterio",
|
||||
"scripts": {
|
||||
"build": "webpack-cli --env.production",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"bugs": {
|
||||
@@ -15,6 +16,18 @@
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@clusterio/master": "^2.0.0-alpha.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"events": "^3.2.0",
|
||||
"null-loader": "^4.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^5.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const common = require("@clusterio/master/webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "statistics_exporter",
|
||||
library: { type: "var", name: "plugin_statistics_exporter" },
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
"ajv": { import: false },
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
"license": "MIT",
|
||||
"repository": "clusterio/factorioClusterio",
|
||||
"scripts": {
|
||||
"build": "webpack-cli --env.production",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"bugs": {
|
||||
@@ -18,6 +19,21 @@
|
||||
"dependencies": {
|
||||
"fs-extra": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@clusterio/master": "^2.0.0-alpha.0",
|
||||
"antd": "^4.6.5",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"events": "^3.2.0",
|
||||
"null-loader": "^4.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^5.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const common = require("@clusterio/master/webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "subspace_storage",
|
||||
library: { type: "var", name: "plugin_subspace_storage" },
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
"ajv": { import: false },
|
||||
"antd": { import: false },
|
||||
"react": { import: false },
|
||||
"react-dom": { import: false },
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
Reference in new issue
Block a user