Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
C-D-Lewis committed Oct 20, 2024
1 parent 1d11616 commit f85b3c4
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 184 deletions.
46 changes: 29 additions & 17 deletions dashboard2/src/components/DeviceMetrics.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Fabricate, FabricateComponent } from 'fabricate.js';
import {
AppState, DataPoint, MetricData, MetricName,
AppState, DataPoint, MetricData,
} from '../types';
import { fetchMetric } from '../util';
import Theme from '../theme';
import { BUCKET_SIZE } from '../constants';
import { fetchMetric, sendConduitPacket } from '../services/conduitService';

declare const fabricate: Fabricate<AppState>;

Expand All @@ -13,9 +13,12 @@ const LABEL_OFFSET = 3;
/** Graph width based on length of a day */
const GRAPH_WIDTH = Math.round(1440 / BUCKET_SIZE);
/** Map of friendly metric names */
const METRIC_NAME_MAP = {
const METRIC_NAME_MAP: Record<string, string> = {
cpu: 'CPU',
memoryPerc: 'Memory (%)',
memoryMb: 'Memory (MB)',
diskGb: 'Disk (GB)',
discPerc: 'Disk (%)',
tempRaw: 'Temperature',
freqPerc: 'CPU Frequency (%)',
};
Expand Down Expand Up @@ -51,10 +54,10 @@ type PlotPoint = {
* MetricGraph component.
*
* @param {object} props - Component props.
* @param {MetricName} props.name - Metric name to fetch and graph.
* @param {string} props.name - Metric name to fetch and graph.
* @returns {FabricateComponent} MetricGraph component.
*/
const MetricGraph = ({ name } : { name: MetricName }) => {
const MetricGraph = ({ name } : { name: string }) => {
const dataKey = fabricate.buildKey('metricData', name);
const canvas = fabricate('canvas') as unknown as HTMLCanvasElement;

Expand Down Expand Up @@ -155,10 +158,10 @@ const MetricGraph = ({ name } : { name: MetricName }) => {
* MetricContainer component.
*
* @param {object} props - Component props.
* @param {MetricName} props.name - Metric name to fetch and graph.
* @param {string} props.name - Metric name to fetch and graph.
* @returns {FabricateComponent} MetricContainer component.
*/
const MetricContainer = ({ name } : { name: MetricName }) => fabricate('Column')
const MetricContainer = ({ name } : { name: string }) => fabricate('Column')
.setStyles(({ palette }) => ({
margin: '15px',
width: `${GRAPH_WIDTH}px`,
Expand All @@ -179,7 +182,7 @@ const MetricContainer = ({ name } : { name: MetricName }) => fabricate('Column')
padding: '5px',
borderBottom: `solid 2px ${palette.grey6}`,
}))
.setText(METRIC_NAME_MAP[name]),
.setText(METRIC_NAME_MAP[name] || name),
MetricGraph({ name }),
]);

Expand All @@ -193,20 +196,29 @@ const DeviceMetrics = () => fabricate('Row')
margin: '15px',
flexWrap: 'wrap',
})
.onUpdate(async (el, state) => {
const { selectedDevice } = state;
.onCreate(async (el, state) => {
// Get the metrics available, each graph loads its own
fabricate.update({ metricNames: [] });
const {
message: metricNames,
} = await sendConduitPacket(state, { to: 'monitor', topic: 'getMetricNames' });
fabricate.update({ metricNames });
})
.onUpdate(async (el, state, keys) => {
const { selectedDevice, metricNames } = state;

if (!selectedDevice) {
el.setChildren([NoMetricsLabel()]);
return;
}

el.setChildren([
MetricContainer({ name: 'cpu' }),
MetricContainer({ name: 'memoryPerc' }),
MetricContainer({ name: 'tempRaw' }),
MetricContainer({ name: 'freqPerc' }),
]);
}, [fabricate.StateKeys.Created, 'selectedDevice']);
if (keys.includes('metricNames')) {
el.setChildren(
metricNames
.filter((name) => !!METRIC_NAME_MAP[name])
.map((name) => MetricContainer({ name })),
);
}
}, [fabricate.StateKeys.Created, 'selectedDevice', 'metricNames']);

export default DeviceMetrics;
6 changes: 2 additions & 4 deletions dashboard2/src/components/SideBar.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Fabricate } from 'fabricate.js';
import { AppState, Device } from '../types';
import { fetchDeviceApps, sortDeviceByName } from '../util';
import { sortDeviceByName } from '../util';
import { ICON_NAMES } from '../constants';
import AppLoader from './AppLoader';
import { fetchDeviceApps } from '../services/conduitService';

declare const fabricate: Fabricate<AppState>;

Expand Down Expand Up @@ -87,9 +88,6 @@ const DeviceRow = ({ device }: { device: Device }) => {
}));
})
.onClick((el, state) => {
const { selectedDevice } = state;
if (selectedDevice?.deviceName === deviceName) return;

// Select this device
fabricate.update({ selectedDevice: device });

Expand Down
1 change: 1 addition & 0 deletions dashboard2/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const INITIAL_STATE: AppState = {
// Loaded data
selectedDeviceApps: [],
devices: [],
metricNames: [],

// Selections
selectedDevice: null,
Expand Down
3 changes: 2 additions & 1 deletion dashboard2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Fabricate } from 'fabricate.js';
import { AppState } from './types';
import Theme from './theme';
import { INITIAL_STATE } from './constants';
import { parseParams, fetchFleetList } from './util';
import { parseParams } from './util';
import SideBar from './components/SideBar';
import AppArea from './components/AppArea';
import { fetchFleetList } from './services/conduitService';

declare const fabricate: Fabricate<AppState>;

Expand Down
164 changes: 162 additions & 2 deletions dashboard2/src/services/conduitService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { CONDUIT_PORT } from '../constants';
import { AppState, Packet } from '../types';
import { Fabricate } from 'fabricate.js';
import {
AppState, DataPoint, Device, DeviceApp,
Packet,
} from '../types';
import { BUCKET_SIZE, CONDUIT_PORT, FLEET_HOST } from '../constants';
import { shortDateTime, sortAppByName } from '../util';

declare const fabricate: Fabricate<AppState>;

/** Extra Y for visibility */
const Y_EXTRA = 1.1;

/**
* Send a conduit packet.
Expand Down Expand Up @@ -54,3 +64,153 @@ export const sendConduitPacket = async (
throw error;
}
};

/**
* Re-load the devices list data.
*
* @param {object} state - App state.
*/
export const fetchFleetList = async (state: AppState) => {
const { token } = state;
fabricate.update({ devices: [] });

try {
// Can't use sendConduitPacket, not a device by name
const res = await fetch(`http://${FLEET_HOST}:${CONDUIT_PORT}/conduit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: 'attic',
topic: 'get',
message: { app: 'conduit', key: 'fleetList' },
auth: token || '',
}),
});
const { message } = await res.json();
fabricate.update({ devices: message.value });
} catch (err) {
console.error(err);
alert(err);
}
};

/**
* Load apps for all fleet devices.
*
* @param {AppState} state - App state.
* @param {Device} device - Device to use.
*/
export const fetchDeviceApps = async (state: AppState, device: Device) => {
const { deviceName } = device!;

fabricate.update({ selectedDeviceApps: [] });

try {
const { message } = await sendConduitPacket(
state,
{ to: 'conduit', topic: 'getApps' },
deviceName,
);

let selectedDeviceApps: DeviceApp[] = [];
if (message && message.error) {
console.error(message.error);
selectedDeviceApps = [];
} else if (!message) {
console.error('No response in fetchApps');
selectedDeviceApps = [];
} else {
selectedDeviceApps = (message as DeviceApp[]).sort(sortAppByName);
}

fabricate.update({ selectedDeviceApps });
} catch (err: unknown) {
console.error(err);
fabricate.update({ selectedDeviceApps: [] });
}
};

/**
* Fetch data for a metric.
*
* @param {AppState} state - App state.
* @param {string} name - Metric name.
*/
export const fetchMetric = async (state: AppState, name: string) => {
const dataKey = fabricate.buildKey('metricData', name);
fabricate.update(
dataKey,
{
buckets: [],
minTime: 0,
maxTime: 0,
minValue: 0,
maxValue: 0,
},
);

const res = await sendConduitPacket(
state,
{
to: 'monitor',
topic: 'getMetricToday',
message: { name },
},
);
const { message: newHistory } = res;
if (res.error) console.log(res);
if (!newHistory.length) return;

const type = Array.isArray(newHistory[0][1]) ? 'array' : 'number';

const minTime = shortDateTime(newHistory[0][0]);
const maxTime = shortDateTime(newHistory[newHistory.length - 1][0]);
let minValue = 0;
let maxValue = 0;

if (type === 'number') {
// Aggregate values
minValue = name.includes('Perc')
? 0
: newHistory.reduce(
// @ts-expect-error handled with 'type'
(acc: number, [, value]: MetricPoint) => (value < acc ? value : acc),
9999999,
);
maxValue = name.includes('Perc')
? Math.round(100 * Y_EXTRA)
: Math.round(
newHistory.reduce(
// @ts-expect-error handled with 'type'
(acc: number, [, value]: MetricPoint) => (value > acc ? value : acc),
0,
) * Y_EXTRA,
);
} else {
throw new Error('Unexpected metric data type');
}

// Average into buckets
const copy = [...newHistory];
const buckets: DataPoint[] = [];
while (copy.length) {
const points = copy.splice(0, BUCKET_SIZE);
const avgIndex = Math.floor(points.length / 2);
buckets.push({
value: points.reduce((acc, [, value]) => acc + value, 0) / points.length,
timestamp: points[avgIndex][0],
dateTime: new Date(points[avgIndex][0]).toISOString(),
});
}

fabricate.update(
dataKey,
{
buckets,
minTime,
maxTime,
minValue,
maxValue,
},
);
};
4 changes: 1 addition & 3 deletions dashboard2/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ export type DataPoint = {
dateTime: string;
};

/** Available graphed metrics */
export type MetricName = 'cpu' | 'memoryPerc' | 'tempRaw' | 'freqPerc';

/** Raw metric point */
export type MetricPoint = [number, number];

Expand All @@ -64,6 +61,7 @@ export type AppState = {
// Loaded data
selectedDeviceApps: DeviceApp[];
devices: Device[];
metricNames: string[];

// Selections
selectedDevice: Device | null,
Expand Down
Loading

0 comments on commit f85b3c4

Please sign in to comment.