mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
Add config to set URL for static assets
Add option in the controller config to set where static assets are loaded from in order to support deployments where the static assets are server via a CDN while the API is directly accessed. This is useful because some CDNs put low limits on how large requests bodies can be, which causes issues with uploading large mods and saves.
This commit is contained in:
1 parent
0f1fbb8fe5
commit
7c0d7faa24
9 files changed
+81
-7
No files matched your search
@@ -60,6 +60,17 @@ This should be a full URL ending with a /.
|
||||
Defaults to null meaning assume localhost.
|
||||
|
||||
|
||||
### controller.static_url
|
||||
|
||||
Used to override where static assets such as the web bundle is loaded from.
|
||||
This can be used if you want to put the static assets on a separate cdn url like `https://cdn.example.com/static/` while keeping the API hosted directly by clusterio.
|
||||
Due to a limitation on how the webpack assets are built the pathname component of the url have to be `/static/`.
|
||||
|
||||
See also the `clusteriocontroller copy-static` command for how to obtain the static asset files that need to be hosted.
|
||||
|
||||
Defaults to null meaning use the internally hosted assets.
|
||||
|
||||
|
||||
### controller.tls_certificate
|
||||
|
||||
Path to TLS certificate to use for the HTTPS server when controller.https_port is configured.
|
||||
|
||||
@@ -355,6 +355,8 @@ export default class Controller {
|
||||
this.onSystemMetricsIntervalChanged();
|
||||
} else if (field === "controller.trusted_proxies") {
|
||||
this.trustedProxies = this.parseTrustedProxies();
|
||||
} else if (field === "controller.static_url") {
|
||||
this.app.locals.staticRoot = this.config.get("controller.static_url");
|
||||
}
|
||||
lib.invokeHook(this.plugins, "onControllerConfigFieldChanged", field, curr, prev);
|
||||
});
|
||||
@@ -389,6 +391,7 @@ export default class Controller {
|
||||
}
|
||||
this.app.locals.mainBundle = manifest["main.js"] || "no_web_build";
|
||||
}
|
||||
this.app.locals.staticRoot = this.config.get("controller.static_url");
|
||||
|
||||
// Load plugins
|
||||
await this.loadPlugins();
|
||||
@@ -866,7 +869,7 @@ export default class Controller {
|
||||
);
|
||||
}
|
||||
|
||||
static addAppRoutes(app: Application, pluginInfos: any[]) {
|
||||
static addAppRoutes(app: Application, pluginInfos: lib.PluginNodeEnvInfo[]) {
|
||||
app.use((req: Request, res: Response, next) => {
|
||||
const startNs = process.hrtime.bigint();
|
||||
stream.finished(res, () => {
|
||||
@@ -915,9 +918,7 @@ export default class Controller {
|
||||
app.get(route, Controller.serveWeb(route));
|
||||
}
|
||||
|
||||
let pluginPackagePath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json"));
|
||||
let webPath = path.join(path.dirname(pluginPackagePath), "dist", "web", "static");
|
||||
app.use("/static", express.static(webPath, staticOptions));
|
||||
app.use("/static", express.static(pluginInfo.webStaticPath, staticOptions));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1231,7 +1232,7 @@ export default class Controller {
|
||||
return function(req: Request, res: Response, next: NextFunction) {
|
||||
let depth = routeDepth + Number(req.path.slice(-1) === "/");
|
||||
let webRoot = "../".repeat(depth) || "./";
|
||||
let staticRoot = `${webRoot}static/`;
|
||||
let staticRoot = res.app.locals.staticRoot ?? `${webRoot}static/`;
|
||||
let mainBundle: string = "";
|
||||
if (res.app.locals.mainBundle) {
|
||||
mainBundle = res.app.locals.mainBundle;
|
||||
|
||||
@@ -22,6 +22,7 @@ declare global {
|
||||
export interface Locals {
|
||||
controller: Controller,
|
||||
mainBundle: string,
|
||||
staticRoot: string | null,
|
||||
devPlugins: Map<string, number>,
|
||||
streams: Map<string, ProxyStream>
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<script id="script-bundle" src="__STATIC_ROOT____MAIN_BUNDLE__" defer></script>
|
||||
<script>
|
||||
webRoot = new URL("__WEB_ROOT__", document.location).pathname;
|
||||
staticRoot = new URL("__STATIC_ROOT__", document.location).pathname;
|
||||
staticRoot = new URL("__STATIC_ROOT__", document.location).href;
|
||||
function scriptError(err) {
|
||||
console.error(err);
|
||||
let bootstrapMsg = document.getElementById("bootstrap-msg");
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface ControllerConfigFields {
|
||||
"controller.bind_address": string | null;
|
||||
"controller.trusted_proxies": string | null;
|
||||
"controller.public_url": string | null;
|
||||
"controller.static_url": string | null;
|
||||
"controller.grafana_url": string | null;
|
||||
"controller.tls_certificate": string | null;
|
||||
"controller.tls_private_key": string | null;
|
||||
@@ -138,6 +139,21 @@ export class ControllerConfig extends classes.Config<ControllerConfigFields> {
|
||||
optional: true,
|
||||
// TODO extendedValidation, valid url including the protocol
|
||||
},
|
||||
"controller.static_url": {
|
||||
title: "Static URL",
|
||||
description: "Override the URL to where static assets are hosted (must have a path consisting of /static/)",
|
||||
type: "string",
|
||||
optional: true,
|
||||
validator: (value) => {
|
||||
if (value) {
|
||||
// This is unfortunately a limitiation of how the webpack build is set up right now.
|
||||
const pathname = new URL(value, "http://localhost/").pathname;
|
||||
if (pathname !== "/static/") {
|
||||
throw new Error(`Path component of static_url must be /static/ not ${pathname}.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"controller.grafana_url": {
|
||||
title: "Grafana URL",
|
||||
description: "URL of a Grafana dashboard to link from the overview page, including the protocol.",
|
||||
|
||||
@@ -41,6 +41,11 @@ export type PluginDeclaration = {
|
||||
}
|
||||
|
||||
export type PluginNodeEnvInfo = PluginDeclaration & {
|
||||
/**
|
||||
* Path to the folder with the static files that should be hosted on the web
|
||||
* server in order for the web interface to be able to load the plugin.
|
||||
*/
|
||||
webStaticPath: string;
|
||||
requirePath: string;
|
||||
version: string;
|
||||
manifest: any;
|
||||
|
||||
@@ -69,6 +69,12 @@ export async function loadPluginInfos(pluginList: Map<string, string>) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pluginInfo.webStaticPath = path.join(
|
||||
path.dirname(
|
||||
require.resolve(path.posix.join(pluginPath, "package.json"))
|
||||
),
|
||||
"dist", "web", "static",
|
||||
);
|
||||
pluginInfo.requirePath = pluginPath;
|
||||
pluginInfo.version = pluginPackage.version;
|
||||
pluginInfo.npmPackage = !pluginPackage.private && pluginPath === pluginPackage.name ? pluginPath : undefined;
|
||||
|
||||
@@ -184,7 +184,35 @@ describe("lib/config/validators", function() {
|
||||
|
||||
describe("lib/config/definitions/validators", function() {
|
||||
describe("Controller Config", function() {
|
||||
it("should validate controller.static_url", function() {
|
||||
const config = new ControllerConfig("controller");
|
||||
|
||||
// valid cases
|
||||
config.set("controller.static_url", "");
|
||||
config.set("controller.static_url", null);
|
||||
config.set("controller.static_url", "static/");
|
||||
config.set("controller.static_url", "./static/");
|
||||
config.set("controller.static_url", "/static/");
|
||||
config.set("controller.static_url", "http://example/static/");
|
||||
|
||||
// invalid cases
|
||||
assert.throws(
|
||||
() => config.set("controller.static_url", "/foo/"),
|
||||
"Path component of static_url must be \/static\/ not \/foo\/."
|
||||
);
|
||||
assert.throws(
|
||||
() => config.set("controller.static_url", "http://example/foo/static/"),
|
||||
"Path component of static_url must be \/static\/ not \/foo\/static\/."
|
||||
);
|
||||
assert.throws(
|
||||
() => config.set("controller.static_url", "http://example/static"),
|
||||
"Path component of static_url must be \/static\/ not \/static."
|
||||
);
|
||||
assert.throws(
|
||||
() => config.set("controller.static_url", "http://:80"),
|
||||
/Invalid URL/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Host Config", function() {
|
||||
|
||||
@@ -43,7 +43,13 @@ describe("lib/plugin_loader", function() {
|
||||
it("should load test plugin", async function() {
|
||||
assert.deepEqual(
|
||||
await lib.loadPluginInfos(new Map([["test", path.resolve(testPlugin)]])),
|
||||
[{ name: "test", version: "0.0.1", npmPackage: undefined, requirePath: path.resolve(testPlugin) }]
|
||||
[{
|
||||
name: "test",
|
||||
version: "0.0.1",
|
||||
npmPackage: undefined,
|
||||
requirePath: path.resolve(testPlugin),
|
||||
webStaticPath: path.resolve(path.join(testPlugin, "dist", "web", "static")),
|
||||
}]
|
||||
);
|
||||
});
|
||||
it("should reject on broken plugin", async function() {
|
||||
|
||||
Reference in new issue
Block a user