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";
|
||||
|
||||
const React = require("react");
|
||||
const classnames = require("classnames");
|
||||
|
||||
const Layout = require("../../layout");
|
||||
const Layout = require("../layout");
|
||||
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 = {
|
||||
query: gql`
|
||||
query {
|
||||
hardware {
|
||||
drives {
|
||||
path
|
||||
smartHealth
|
||||
size
|
||||
rpm
|
||||
serialNumber
|
||||
model
|
||||
modelFamily
|
||||
firmwareVersion
|
||||
|
||||
blockDevice {
|
||||
name
|
||||
}
|
||||
|
||||
partitions: allBlockDevices(type: PARTITION) {
|
||||
name
|
||||
mountpoint
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
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 title="Storage Devices">
|
||||
<table className="drives">
|
||||
<tr>
|
||||
<th>SMART</th>
|
||||
<th>Device</th>
|
||||
<th>Total size</th>
|
||||
<th>RPM</th>
|
||||
<th>Serial number</th>
|
||||
<th>Model</th>
|
||||
<th>Family</th>
|
||||
<th>Firmware version</th>
|
||||
</tr>
|
||||
{data.hardware.drives.map((drive) => <DriveEntry drive={drive} />)}
|
||||
</table>
|
||||
</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