mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 07:24:52 +09:00
Add Plugins page to web interface
Add page listing the plugins installed on the master along with a detailed page for each plugin with fields from their package.json. Failure to load the module and version missmatch is also reported. This adds ./packages.json as required part of the webpack module.
This commit is contained in:
1 parent
3a3a7597bf
commit
6c9d3d8893
11 files changed
+150
-2
No files matched your search
@@ -55,6 +55,7 @@ Version 2.0.0
|
||||
them to be displayed by name in queries that join with the mapping.
|
||||
- Added option to configure timeout for exported Prometheus metrics.
|
||||
- Added option to set address to bind HTTP(S) port to.
|
||||
- Added an overview of the installed plugins to the web interface.
|
||||
|
||||
### Changes
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ app.get("/api/plugins", (req, res) => {
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
let name = pluginInfo.name;
|
||||
let enabled = masterPlugins.has(name) && masterConfig.group(name).get("enabled");
|
||||
plugins.push({ name, enabled });
|
||||
plugins.push({ name, version: pluginInfo.version, enabled });
|
||||
}
|
||||
res.send(plugins);
|
||||
});
|
||||
|
||||
@@ -11,5 +11,6 @@ module.exports = [
|
||||
"/users/:id/view",
|
||||
"/roles",
|
||||
"/roles/:id/view",
|
||||
"/settings",
|
||||
"/plugins",
|
||||
"/plugins/:name/view",
|
||||
];
|
||||
@@ -44,9 +44,11 @@ async function loadPlugins() {
|
||||
let container = window[`plugin_${meta.name}`];
|
||||
await container.init(__webpack_share_scopes__.default);
|
||||
let pluginInfo = (await container.get("./info"))();
|
||||
let pluginPackage = (await container.get("./package.json"))();
|
||||
plugins.push({
|
||||
meta,
|
||||
info: pluginInfo,
|
||||
package: pluginPackage,
|
||||
container,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { useEffect, useContext, useState } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { Alert, Descriptions } from "antd";
|
||||
|
||||
import PluginsContext from "./PluginsContext";
|
||||
import PageLayout from "./PageLayout";
|
||||
import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
|
||||
export default function PluginViewPage() {
|
||||
let params = useParams();
|
||||
let plugins = useContext(PluginsContext);
|
||||
let pluginName = params.name;
|
||||
|
||||
let plugin = plugins.find(plugin => plugin.meta.name === pluginName);
|
||||
let pluginTitle = plugin.info ? plugin.info.title : plugin.meta.name;
|
||||
let nav = [{ name: "Plugins", path: "/plugins" }, { name: pluginTitle }];
|
||||
if (!plugin) {
|
||||
return <PageLayout nav={nav}>
|
||||
<h2>Plugin not found</h2>
|
||||
<p>Plugin with name {pluginName} was not found on the master server.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
if (!plugin.info) {
|
||||
return <PageLayout nav={nav}>
|
||||
<Descriptions
|
||||
bordered
|
||||
size="small"
|
||||
title={pluginTitle}
|
||||
>
|
||||
<Descriptions.Item label="Version">{plugin.meta.version}</Descriptions.Item>
|
||||
<Descriptions.Item label="Enabled">{plugin.meta.enabled ? "Yes" : "No"}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Alert
|
||||
style={{
|
||||
marginTop: "1em",
|
||||
}}
|
||||
message="Error loading web module"
|
||||
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 "+
|
||||
"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">{plugin.meta.version}</Descriptions.Item>
|
||||
<Descriptions.Item label="Enabled" span={2}>{plugin.meta.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>
|
||||
</Descriptions.Item> : null}
|
||||
{plugin.package.author ? <Descriptions.Item label="Author" span={3}>
|
||||
{plugin.package.author}
|
||||
</Descriptions.Item> : null}
|
||||
<Descriptions.Item label="License" span={3}>{plugin.package.license}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{plugin.meta.version !== plugin.package.version ? <Alert
|
||||
style={{
|
||||
marginTop: "1em",
|
||||
}}
|
||||
message="Version missmatch detected"
|
||||
description={
|
||||
`The version of the web interface module for this plugin (${plugin.package.version}) `+
|
||||
`does not match the version running on the master server (${plugin.meta.version}), `+
|
||||
"spurious errors may occur. This usually happens when the plugin is updated but the "+
|
||||
"master server has not been restarted yet, but may also be due to an outdated build."
|
||||
}
|
||||
type="warning"
|
||||
showIcon
|
||||
/> : null}
|
||||
</PageLayout>;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import React, { useContext } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Table } from "antd";
|
||||
|
||||
import PluginsContext from "./PluginsContext";
|
||||
import PageLayout from "./PageLayout";
|
||||
|
||||
|
||||
export default function PluginsPage() {
|
||||
let plugins = useContext(PluginsContext);
|
||||
let history = useHistory();
|
||||
|
||||
return <PageLayout nav={[{ name: "Plugins" }]}>
|
||||
<h2>Plugins</h2>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
title: "Name",
|
||||
key: "name",
|
||||
render: plugin => plugin.info ? plugin.info.title : plugin.meta.name,
|
||||
},
|
||||
{
|
||||
title: "Version",
|
||||
key: "version",
|
||||
render: plugin => {
|
||||
if (!plugin.package) {
|
||||
return "Error loading module";
|
||||
}
|
||||
if (plugin.package.version !== plugin.meta.version) {
|
||||
return "Version missmatched";
|
||||
}
|
||||
return plugin.package.version;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Enabled",
|
||||
dataIndex: ["meta", "enabled"],
|
||||
render: enabled => enabled ? "Yes" : null,
|
||||
responsive: ["sm"],
|
||||
},
|
||||
]}
|
||||
dataSource={plugins}
|
||||
rowKey={plugin => plugin.meta.name}
|
||||
pagination={false}
|
||||
onRow={plugin => ({
|
||||
onClick: event => {
|
||||
history.push(`/plugins/${plugin.meta.name}/view`);
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</PageLayout>;
|
||||
}
|
||||
@@ -17,11 +17,14 @@ import UsersPage from "./UsersPage";
|
||||
import UserViewPage from "./UserViewPage";
|
||||
import RolesPage from "./RolesPage";
|
||||
import RoleViewPage from "./RoleViewPage";
|
||||
import PluginsPage from "./PluginsPage";
|
||||
import PluginViewPage from "./PluginViewPage";
|
||||
|
||||
const pages = [
|
||||
{ path: "/instances/:id/view", sidebarPath: "/instances", content: <InstanceViewPage /> },
|
||||
{ path: "/users/:name/view", sidebarPath: "/users", content: <UserViewPage /> },
|
||||
{ path: "/roles/:id/view", sidebarPath: "/roles", content: <RoleViewPage /> },
|
||||
{ path: "/plugins/:name/view", sidebarPath: "/plugins", content: <PluginViewPage /> },
|
||||
];
|
||||
|
||||
const sidebar = [
|
||||
@@ -30,6 +33,7 @@ const sidebar = [
|
||||
{ name: "Instances", path: "/instances", content: <InstancesPage />},
|
||||
{ name: "Users", path: "/users", content: <UsersPage /> },
|
||||
{ name: "Roles", path: "/roles", content: <RolesPage /> },
|
||||
{ name: "Plugins", path: "/plugins", content: <PluginsPage /> },
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
|
||||
Reference in new issue
Block a user