mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Improve error reporting of plugins in the Web UI (#557)
Keep the error message and display it in the plugin page when loading the plugin info or the plugin itself fails to load in the Web UI.
This commit is contained in:
1 parent
f702c956a5
commit
9842705eb9
5 files changed
+46
-21
No files matched your search
@@ -43,9 +43,10 @@ export type PluginNodeEnvInfo = PluginDeclaration & {
|
||||
};
|
||||
|
||||
export type PluginWebpackEnvInfo = PluginDeclaration & {
|
||||
container: any;
|
||||
package: any;
|
||||
container?: any;
|
||||
package?: any;
|
||||
enabled?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ async function loadPluginInfos(): Promise<lib.PluginWebpackEnvInfo[]> {
|
||||
pluginList = [];
|
||||
}
|
||||
|
||||
let pluginInfos = [];
|
||||
let pluginInfos: lib.PluginWebpackEnvInfo[] = [];
|
||||
await __webpack_init_sharing__("default");
|
||||
for (let meta of pluginList) {
|
||||
if (meta.web.error) {
|
||||
@@ -59,6 +59,12 @@ async function loadPluginInfos(): Promise<lib.PluginWebpackEnvInfo[]> {
|
||||
pluginInfos.push(pluginInfo);
|
||||
|
||||
} catch (err: any) {
|
||||
pluginInfos.push({
|
||||
name: meta.name,
|
||||
title: meta.name,
|
||||
enabled: false,
|
||||
error: err.message,
|
||||
});
|
||||
logger.error(`Failed to load plugin info for ${meta.name}`);
|
||||
if (err.stack) {
|
||||
logger.error(err.stack);
|
||||
@@ -79,7 +85,8 @@ async function loadPlugins(pluginInfos: lib.PluginWebpackEnvInfo[], control: Con
|
||||
if (pluginInfo.webEntrypoint) {
|
||||
let webModule = (await pluginInfo.container.get(pluginInfo.webEntrypoint))();
|
||||
if (!webModule.WebPlugin) {
|
||||
throw new Error("Plugin webEntrypoint does not export WebPlugin class");
|
||||
pluginInfo.error = "Plugin webEntrypoint does not export WebPlugin class";
|
||||
throw new Error(pluginInfo.error);
|
||||
}
|
||||
WebPluginClass = webModule.WebPlugin;
|
||||
}
|
||||
@@ -89,6 +96,7 @@ async function loadPlugins(pluginInfos: lib.PluginWebpackEnvInfo[], control: Con
|
||||
plugins.set(pluginInfo.name, plugin);
|
||||
|
||||
} catch (err: any) {
|
||||
pluginInfo.error = `Error loading plugin: ${err.message}`;
|
||||
logger.error(`Failed to load plugin ${pluginInfo.name}`);
|
||||
if (err.stack) {
|
||||
logger.error(err.stack);
|
||||
@@ -124,7 +132,7 @@ export default async function bootstrap() {
|
||||
|
||||
let wsUrl = new URL(webRoot, document.location.href);
|
||||
let controlConnector = new ControlConnector(wsUrl.href, 120, undefined);
|
||||
let control = new Control(controlConnector);
|
||||
let control = new Control(controlConnector, new Map(pluginInfos.map(p => [p.name, p])));
|
||||
control.plugins = await loadPlugins(pluginInfos, control);
|
||||
control.inputComponents = inputComponentsFromPlugins(control.plugins);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import notify from "../util/notify";
|
||||
|
||||
export default function PluginViewPage() {
|
||||
let params = useParams();
|
||||
let plugins = useContext(ControlContext).plugins;
|
||||
const control = useContext(ControlContext);
|
||||
let [pluginList, setPluginList] = useState<PluginWebApi[]>();
|
||||
let pluginName = String(params.name);
|
||||
|
||||
@@ -25,8 +25,9 @@ export default function PluginViewPage() {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
let plugin = plugins.get(pluginName);
|
||||
let pluginTitle = plugin ? plugin.info.title : pluginName;
|
||||
const plugin = control.plugins.get(pluginName);
|
||||
const pluginInfo = control.pluginInfos.get(pluginName);
|
||||
const pluginTitle = pluginInfo ? pluginInfo.title : pluginName;
|
||||
let nav = [{ name: "Plugins", path: "/plugins" }, { name: pluginTitle }];
|
||||
if (!pluginList) {
|
||||
return <PageLayout nav={nav}>
|
||||
@@ -43,33 +44,42 @@ export default function PluginViewPage() {
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
const pluginError = pluginMeta.web.error ?? pluginInfo?.error;
|
||||
if (!plugin) {
|
||||
return <PageLayout nav={nav}>
|
||||
<Descriptions bordered size="small" title={pluginTitle}>
|
||||
<Descriptions.Item label="Version">{pluginMeta.version}</Descriptions.Item>
|
||||
<Descriptions.Item label="Loaded">{pluginMeta.loaded ? "Yes" : "No"}</Descriptions.Item>
|
||||
<Descriptions.Item label="Loaded in Web UI" span={2}>No</Descriptions.Item>
|
||||
<Descriptions.Item label="Loaded on controller">{pluginMeta.loaded ? "Yes" : "No"}</Descriptions.Item>
|
||||
<Descriptions.Item label="Enabled on controller" span={2}>
|
||||
{pluginMeta.enabled ? "Yes" : "No"}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Alert
|
||||
{pluginError && <Alert
|
||||
style={{
|
||||
marginTop: "1em",
|
||||
}}
|
||||
message={pluginMeta.web.error || "Error loading web module"}
|
||||
message={pluginError}
|
||||
description={
|
||||
"The web interface was unable to load the webpack module for this plugin. This is "+
|
||||
"usually due to an incorrect or missing webpack build for the plugin. Due to the "+
|
||||
"module not being loaded, the configs and web controls defined by this plugin will "+
|
||||
"module not being loaded, the configs and web controls defined by this plugin may "+
|
||||
"not be available in the web interface."
|
||||
}
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
/>}
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
return <PageLayout nav={nav}>
|
||||
<Descriptions bordered size="small" title={pluginTitle}>
|
||||
<Descriptions.Item label="Version">{pluginMeta.version}</Descriptions.Item>
|
||||
<Descriptions.Item label="Loaded" span={2}>{pluginMeta.loaded ? "Yes" : "No"}</Descriptions.Item>
|
||||
<Descriptions.Item label="Loaded in Web UI" span={2}>Yes</Descriptions.Item>
|
||||
<Descriptions.Item label="Loaded on controller">{pluginMeta.loaded ? "Yes" : "No"}</Descriptions.Item>
|
||||
<Descriptions.Item label="Enabled on controller" span={2}>
|
||||
{pluginMeta.enabled ? "Yes" : "No"}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Description" span={3}>{plugin.info.description}</Descriptions.Item>
|
||||
{plugin.package.homepage ? <Descriptions.Item label="Homepage" span={3}>
|
||||
<a href={plugin.package.homepage}>{plugin.package.homepage}</a>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Table } from "antd";
|
||||
import CloseCircleFilled from "@ant-design/icons/CloseCircleFilled";
|
||||
import InfoCircleFilled from "@ant-design/icons/InfoCircleFilled";
|
||||
|
||||
import type { PluginWebApi } from "@clusterio/lib";
|
||||
|
||||
@@ -12,7 +14,7 @@ const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base"
|
||||
|
||||
|
||||
export default function PluginsPage() {
|
||||
let plugins = useContext(ControlContext).plugins;
|
||||
const control = useContext(ControlContext);
|
||||
let navigate = useNavigate();
|
||||
let [pluginList, setPluginList] = useState<PluginWebApi[]>([]);
|
||||
|
||||
@@ -29,8 +31,8 @@ export default function PluginsPage() {
|
||||
|
||||
let tableContents = [];
|
||||
for (let meta of pluginList) {
|
||||
if (plugins.has(meta.name)) {
|
||||
let plugin = plugins.get(meta.name)!;
|
||||
if (control.plugins.has(meta.name)) {
|
||||
let plugin = control.plugins.get(meta.name)!;
|
||||
tableContents.push({
|
||||
meta,
|
||||
info: plugin.info,
|
||||
@@ -39,6 +41,7 @@ export default function PluginsPage() {
|
||||
} else {
|
||||
tableContents.push({
|
||||
meta,
|
||||
info: control.pluginInfos.get(meta.name),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,8 +61,11 @@ export default function PluginsPage() {
|
||||
title: "Version",
|
||||
key: "version",
|
||||
render: (_, plugin) => {
|
||||
if (!plugin.meta.enabled) {
|
||||
return <><InfoCircleFilled style={{ color: "#1668dc" }} /> Disabled on controller</>;
|
||||
}
|
||||
if (!plugin.package) {
|
||||
return "Error loading module";
|
||||
return <><CloseCircleFilled style={{ color: "#dc4446" }} /> Error loading module</>;
|
||||
}
|
||||
if (plugin.package.version !== plugin.meta.version) {
|
||||
return "Version missmatched";
|
||||
@@ -70,9 +76,8 @@ export default function PluginsPage() {
|
||||
},
|
||||
{
|
||||
title: "Loaded",
|
||||
dataIndex: ["meta", "loaded"],
|
||||
render: loaded => (loaded ? "Yes" : null),
|
||||
sorter: (a, b) => Number(a.meta.loaded) - Number(b.meta.loaded),
|
||||
render: (_, plugin) => (plugin.package ? "Yes" : null),
|
||||
sorter: (a, b) => Number(Boolean(a.package)) - Number(Boolean(b.package)),
|
||||
responsive: ["sm"],
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -72,6 +72,7 @@ export class Control extends lib.Link {
|
||||
|
||||
constructor(
|
||||
connector: ControlConnector,
|
||||
public pluginInfos = new Map<string, lib.PluginWebpackEnvInfo>(),
|
||||
) {
|
||||
super(connector);
|
||||
|
||||
|
||||
Reference in new issue
Block a user