Add Hardware sub-layout and submenu, implement auto-scaling of units,improve withData API, add filterable allBlockDevices property to drive API, WIP: convert drives page to JSX + GraphQL
parent
937ab5154d
commit
5be1872be3
@ -0,0 +1,8 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const matchOrError = require("./match-or-error");
|
||||||
|
|
||||||
|
module.exports = function deviceNameFromPath(path) {
|
||||||
|
let [name] = matchOrError(/^\/dev\/(.+)$/, path);
|
||||||
|
return name;
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = function linearizeTree(rootList, childrenProperty = "children") {
|
||||||
|
let linearizedItems = [];
|
||||||
|
|
||||||
|
function add(list) {
|
||||||
|
for (let item of list) {
|
||||||
|
linearizedItems.push(item);
|
||||||
|
|
||||||
|
if (item[childrenProperty] != null) {
|
||||||
|
add(item[childrenProperty]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(rootList);
|
||||||
|
|
||||||
|
return linearizedItems;
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = function prefixTitle(prefix, title) {
|
||||||
|
if (title == null) {
|
||||||
|
return title;
|
||||||
|
} else {
|
||||||
|
return `${prefix} ${title}`;
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
const classnames = require("classnames");
|
||||||
|
|
||||||
|
const {LocalsContext} = require("../../express-async-react");
|
||||||
|
const isUnderPrefix = require("../../is-under-prefix");
|
||||||
|
|
||||||
|
module.exports = function MenuItem({ path, children }) {
|
||||||
|
let {currentPath} = React.useContext(LocalsContext);
|
||||||
|
let isActive = isUnderPrefix(path, currentPath);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames("menuItem", {active: isActive})}>
|
||||||
|
<a href={path}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
|
||||||
|
const MainLayout = require("../layout");
|
||||||
|
const MenuItem = require("../components/menu-item");
|
||||||
|
|
||||||
|
const prefixTitle = require("../../prefix-title");
|
||||||
|
|
||||||
|
function Submenu() {
|
||||||
|
return (<>
|
||||||
|
<MenuItem path="/hardware/system-information">System Information</MenuItem>
|
||||||
|
<MenuItem path="/hardware/storage-devices">Storage Devices</MenuItem>
|
||||||
|
<MenuItem path="/hardware/network-interfaces">Network Interfaces</MenuItem>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function HardwareLayout({ children, title }) {
|
||||||
|
return (
|
||||||
|
<MainLayout submenu={<Submenu />} title={prefixTitle("Hardware >", title)}>
|
||||||
|
{children}
|
||||||
|
</MainLayout>
|
||||||
|
);
|
||||||
|
};
|
@ -1,109 +1,91 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
|
const classnames = require("classnames");
|
||||||
|
|
||||||
const Layout = require("../../layout");
|
const Layout = require("../layout");
|
||||||
const gql = require("../../../graphql/tag");
|
const gql = require("../../../graphql/tag");
|
||||||
|
|
||||||
|
function PartitionEntry({partition, isLast}) {
|
||||||
|
return (
|
||||||
|
<tr className={classnames("partition", {last: isLast})}>
|
||||||
|
<td>{partition.name}</td>
|
||||||
|
<td>{partition.size.toString()}</td>
|
||||||
|
<td colSpan={5}>
|
||||||
|
{(partition.mountpoint != null)
|
||||||
|
? partition.mountpoint
|
||||||
|
: <span className="notMounted">(not mounted)</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DriveEntry({drive}) {
|
||||||
|
let hasPartitions = (drive.partitions.length > 0);
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
<tr className={classnames({hasPartitions})}>
|
||||||
|
<td className={classnames("smart", drive.smartHealth)} rowSpan={1 + drive.partitions.length} />
|
||||||
|
<td>{drive.blockDevice.name}</td>
|
||||||
|
<td>{drive.size.toDisplay(2).toString()}</td>
|
||||||
|
<td>{drive.rpm} RPM</td>
|
||||||
|
<td>{drive.serialNumber}</td>
|
||||||
|
<td>{drive.model}</td>
|
||||||
|
<td>{drive.modelFamily}</td>
|
||||||
|
<td>{drive.firmwareVersion}</td>
|
||||||
|
</tr>
|
||||||
|
{drive.partitions.map((partition, i) => {
|
||||||
|
let isLast = (i === drive.partitions.length - 1);
|
||||||
|
|
||||||
|
return <PartitionEntry partition={partition} isLast={isLast} />;
|
||||||
|
})}
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
hardware {
|
hardware {
|
||||||
drives {
|
drives {
|
||||||
path
|
smartHealth
|
||||||
|
size
|
||||||
|
rpm
|
||||||
|
serialNumber
|
||||||
model
|
model
|
||||||
modelFamily
|
modelFamily
|
||||||
|
firmwareVersion
|
||||||
|
|
||||||
|
blockDevice {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
|
partitions: allBlockDevices(type: PARTITION) {
|
||||||
|
name
|
||||||
|
mountpoint
|
||||||
|
size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
template: function StorageDeviceList({data}) {
|
template: function StorageDeviceList({data}) {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout title="Storage Devices">
|
||||||
<div className="fancyStuff">
|
<table className="drives">
|
||||||
<ul>
|
<tr>
|
||||||
{data.hardware.drives.map((drive) => {
|
<th>SMART</th>
|
||||||
return <li>
|
<th>Device</th>
|
||||||
{drive.path}: {drive.model} ({drive.modelFamily})
|
<th>Total size</th>
|
||||||
</li>;
|
<th>RPM</th>
|
||||||
})}
|
<th>Serial number</th>
|
||||||
</ul>
|
<th>Model</th>
|
||||||
</div>
|
<th>Family</th>
|
||||||
|
<th>Firmware version</th>
|
||||||
|
</tr>
|
||||||
|
{data.hardware.drives.map((drive) => <DriveEntry drive={drive} />)}
|
||||||
|
</table>
|
||||||
</Layout>
|
</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,15 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
|
||||||
|
const Layout = require("./layout");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
template: function Index() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
Hello world!
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,4 +0,0 @@
|
|||||||
extends layout
|
|
||||||
|
|
||||||
block content
|
|
||||||
| Hello World!
|
|
@ -1,20 +0,0 @@
|
|||||||
mixin menu-item(prefix)
|
|
||||||
.menu-item(class=isUnderPrefix(prefix, "active"))
|
|
||||||
block
|
|
||||||
|
|
||||||
doctype html
|
|
||||||
head
|
|
||||||
title CVM
|
|
||||||
link(rel="stylesheet", href="/css/style.css")
|
|
||||||
body
|
|
||||||
.menu
|
|
||||||
h1 CVM
|
|
||||||
+menu-item("/disk-images"): a(href="/disk-images") Disk Images
|
|
||||||
+menu-item("/instances"): a(href="/instances") Instances
|
|
||||||
+menu-item("/users"): a(href="/users") Users
|
|
||||||
|
|
||||||
.content
|
|
||||||
block content
|
|
||||||
|
|
||||||
script(src="/js/bundle.js")
|
|
||||||
script(src="/budo/livereload.js")
|
|
Loading…
Reference in New Issue