Compare commits
2 Commits
db056bbe2f
...
f8f35c5dcf
Author | SHA1 | Date |
---|---|---|
Sven Slootweg | f8f35c5dcf | 5 years ago |
Sven Slootweg | 37daf9a628 | 5 years ago |
@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const escapeStringRegexp = require("escape-string-regexp");
|
||||||
|
const assureArray = require("assure-array");
|
||||||
|
|
||||||
|
let viewPathRegexes = new Map();
|
||||||
|
|
||||||
|
function generateRegex(paths) {
|
||||||
|
let pathRegexes = assureArray(paths)
|
||||||
|
.map((path) => `^${escapeStringRegexp(path)}`)
|
||||||
|
.join("|");
|
||||||
|
|
||||||
|
return new RegExp(pathRegexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCacheByRegex(regex) {
|
||||||
|
for (let [key, entry] in Object.entries(regex)) {
|
||||||
|
if (regex.test(entry.filename) === true) {
|
||||||
|
delete require.cache[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function clearRequireCache(viewPaths) {
|
||||||
|
if (!viewPathRegexes.has(viewPaths)) {
|
||||||
|
viewPathRegexes.set(viewPathRegexes, generateRegex(viewPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCacheByRegex(viewPathRegexes.get(viewPathRegexes));
|
||||||
|
};
|
@ -0,0 +1,79 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const React = require("react");
|
||||||
|
const ReactDOMServer = require("react-dom/server");
|
||||||
|
const defaultValue = require("default-value");
|
||||||
|
const dotty = require("dotty");
|
||||||
|
|
||||||
|
const registerBabel = require("./register-babel");
|
||||||
|
const clearRequireCache = require("./clear-require-cache");
|
||||||
|
|
||||||
|
/* FILEBUG: Express does not copy symbol-keyed properties from app.locals, but it probably should? */
|
||||||
|
// let ExpressReact = Symbol("ExpressReact");
|
||||||
|
let ExpressReact = "__EXPRESS_REACT_SETTINGS";
|
||||||
|
let LocalsContext = React.createContext({});
|
||||||
|
|
||||||
|
function componentAtPath(moduleRoot, componentPath) {
|
||||||
|
if (componentPath == null) {
|
||||||
|
return moduleRoot;
|
||||||
|
} else {
|
||||||
|
return dotty.get(moduleRoot, componentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderComponent(component, locals, doctype = "<!DOCTYPE html>") {
|
||||||
|
let tree = React.createElement(LocalsContext.Provider, {value: locals},
|
||||||
|
React.createElement(component, locals)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* TODO: Expose internals? Like koa-react */
|
||||||
|
return doctype + ReactDOMServer.renderToStaticMarkup(tree);
|
||||||
|
} catch (err) {
|
||||||
|
if (/Element type is invalid:/.test(err.message)) {
|
||||||
|
throw new Error(`Expected a React component, but got '${component}' - maybe you forgot to specify a componentPath?`);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Settings: ExpressReact,
|
||||||
|
LocalsContext: LocalsContext,
|
||||||
|
createEngine: function createEngine({ prepare } = {}) {
|
||||||
|
let babelRegistered = false;
|
||||||
|
|
||||||
|
return function asyncRender(filename, options, callback) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
let viewPaths = options.settings.views;
|
||||||
|
|
||||||
|
if (!babelRegistered) {
|
||||||
|
registerBabel(viewPaths);
|
||||||
|
babelRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {componentPath} = defaultValue(options[ExpressReact], {});
|
||||||
|
|
||||||
|
return Promise.try(() => {
|
||||||
|
let templateFile = require(filename);
|
||||||
|
let moduleRoot = defaultValue(templateFile.exports, templateFile);
|
||||||
|
|
||||||
|
return Promise.try(() => {
|
||||||
|
if (prepare != null) {
|
||||||
|
return prepare(moduleRoot, options);
|
||||||
|
}
|
||||||
|
}).then((result) => {
|
||||||
|
let mergedOptions = Object.assign({}, options, result);
|
||||||
|
return renderComponent(componentAtPath(moduleRoot, componentPath), mergedOptions);
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
if (options.settings.env === "development") {
|
||||||
|
clearRequireCache(viewPaths);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).asCallback(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const assureArray = require("assure-array");
|
||||||
|
const babelRegister = require("@babel/register");
|
||||||
|
|
||||||
|
module.exports = function registerBabel(viewPaths, options = {}) {
|
||||||
|
let babelOptions = Object.assign({
|
||||||
|
only: assureArray(viewPaths),
|
||||||
|
presets: [
|
||||||
|
"@babel/preset-react",
|
||||||
|
["@babel/preset-env", {
|
||||||
|
targets: {
|
||||||
|
node: "current"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
babelRegister(babelOptions);
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
template: function ErrorPage({ error }) {
|
||||||
|
return (
|
||||||
|
<div className="error">
|
||||||
|
<h1>An error occurred.</h1>
|
||||||
|
<h2>{ error.message }</h2>
|
||||||
|
<pre>{ error.stack }</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,109 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
|
||||||
|
const Layout = require("../../layout");
|
||||||
|
const gql = require("../../../graphql/tag");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
hardware {
|
||||||
|
drives {
|
||||||
|
path
|
||||||
|
model
|
||||||
|
modelFamily
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
template: function StorageDeviceList({data}) {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="fancyStuff">
|
||||||
|
<ul>
|
||||||
|
{data.hardware.drives.map((drive) => {
|
||||||
|
return <li>
|
||||||
|
{drive.path}: {drive.model} ({drive.modelFamily})
|
||||||
|
</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// extends ../../layout
|
||||||
|
|
||||||
|
// block content
|
||||||
|
|
||||||
|
// h2 Fixed drives
|
||||||
|
|
||||||
|
// //- FIXME: Partitions with mountpoints
|
||||||
|
// table.drives
|
||||||
|
// tr
|
||||||
|
// th SMART
|
||||||
|
// th Device
|
||||||
|
// th Total size
|
||||||
|
// th RPM
|
||||||
|
// th Serial number
|
||||||
|
// th Model
|
||||||
|
// th Family
|
||||||
|
// th Firmware version
|
||||||
|
// for device in devices.filter((device) => device.removable === false)
|
||||||
|
// tr(class=(device.children.length > 0 ? "hasPartitions" : null))
|
||||||
|
// td(class=`smart ${device.smartStatus}`, rowspan=(1 + device.children.length))
|
||||||
|
// td= device.name
|
||||||
|
// td= device.size
|
||||||
|
// td #{device.information.rpm} RPM
|
||||||
|
// td= device.information.serialNumber
|
||||||
|
// td= device.information.model
|
||||||
|
// td= device.information.modelFamily
|
||||||
|
// td= device.information.firmwareVersion
|
||||||
|
|
||||||
|
// for partition, i in device.children
|
||||||
|
// tr.partition(class=(i === device.children.length - 1) ? "last" : null)
|
||||||
|
// td= partition.name
|
||||||
|
// td= partition.size
|
||||||
|
// td(colspan=5)
|
||||||
|
// if partition.mountpoint != null
|
||||||
|
// = partition.mountpoint
|
||||||
|
// else
|
||||||
|
// span.notMounted (not mounted)
|
||||||
|
|
||||||
|
|
||||||
|
// //- tr.partition
|
||||||
|
// //- td(colspan=8)= JSON.stringify(partition)
|
||||||
|
// tr
|
||||||
|
// th(colspan=2) Total
|
||||||
|
// td= totalFixedStorage
|
||||||
|
// td(colspan=5).hidden
|
||||||
|
// tr.smartStatus
|
||||||
|
// th(colspan=2).healthy Healthy
|
||||||
|
// td= totalHealthyFixedStorage
|
||||||
|
// td(colspan=5).hidden
|
||||||
|
// tr.smartStatus
|
||||||
|
// th(colspan=2).atRisk At-risk
|
||||||
|
// td= totalDeterioratingFixedStorage
|
||||||
|
// td(colspan=5).hidden
|
||||||
|
// tr.smartStatus
|
||||||
|
// th(colspan=2).failing Failing
|
||||||
|
// td= totalFailingFixedStorage
|
||||||
|
// td(colspan=5).hidden
|
||||||
|
|
||||||
|
// h2 Removable drives
|
||||||
|
|
||||||
|
// table
|
||||||
|
// tr
|
||||||
|
// th Path
|
||||||
|
// th Total size
|
||||||
|
// th Mounted at
|
||||||
|
// for device in devices.filter((device) => device.type === "loopDevice")
|
||||||
|
// tr
|
||||||
|
// td= device.path
|
||||||
|
// td= device.size
|
||||||
|
// td= device.mountpoint
|
@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
const classnames = require("classnames");
|
||||||
|
// const {LocalsContext} = require("../express-async-react");
|
||||||
|
|
||||||
|
function MenuItem({ path, children }) {
|
||||||
|
let isActive = false; // FIXME
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames("menu-item", {active: isActive})}>
|
||||||
|
<a href={path}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function Layout({ children }) {
|
||||||
|
// let locals = React.useContext(LocalsContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>CVM</title>
|
||||||
|
<link rel="stylesheet" href="/css/style.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div className="menu">
|
||||||
|
<h1>CVM</h1>
|
||||||
|
<MenuItem path="/disk-images">Disk Images</MenuItem>
|
||||||
|
<MenuItem path="/instances">Instances</MenuItem>
|
||||||
|
<MenuItem path="/users">Users</MenuItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="content">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/bundle.js" />
|
||||||
|
<script src="/budo/livereload.js" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue