Skip to content

Commit f85b3c4

Browse files
committed
update
1 parent 1d11616 commit f85b3c4

File tree

8 files changed

+202
-184
lines changed

8 files changed

+202
-184
lines changed

dashboard2/src/components/DeviceMetrics.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Fabricate, FabricateComponent } from 'fabricate.js';
22
import {
3-
AppState, DataPoint, MetricData, MetricName,
3+
AppState, DataPoint, MetricData,
44
} from '../types';
5-
import { fetchMetric } from '../util';
65
import Theme from '../theme';
76
import { BUCKET_SIZE } from '../constants';
7+
import { fetchMetric, sendConduitPacket } from '../services/conduitService';
88

99
declare const fabricate: Fabricate<AppState>;
1010

@@ -13,9 +13,12 @@ const LABEL_OFFSET = 3;
1313
/** Graph width based on length of a day */
1414
const GRAPH_WIDTH = Math.round(1440 / BUCKET_SIZE);
1515
/** Map of friendly metric names */
16-
const METRIC_NAME_MAP = {
16+
const METRIC_NAME_MAP: Record<string, string> = {
1717
cpu: 'CPU',
1818
memoryPerc: 'Memory (%)',
19+
memoryMb: 'Memory (MB)',
20+
diskGb: 'Disk (GB)',
21+
discPerc: 'Disk (%)',
1922
tempRaw: 'Temperature',
2023
freqPerc: 'CPU Frequency (%)',
2124
};
@@ -51,10 +54,10 @@ type PlotPoint = {
5154
* MetricGraph component.
5255
*
5356
* @param {object} props - Component props.
54-
* @param {MetricName} props.name - Metric name to fetch and graph.
57+
* @param {string} props.name - Metric name to fetch and graph.
5558
* @returns {FabricateComponent} MetricGraph component.
5659
*/
57-
const MetricGraph = ({ name } : { name: MetricName }) => {
60+
const MetricGraph = ({ name } : { name: string }) => {
5861
const dataKey = fabricate.buildKey('metricData', name);
5962
const canvas = fabricate('canvas') as unknown as HTMLCanvasElement;
6063

@@ -155,10 +158,10 @@ const MetricGraph = ({ name } : { name: MetricName }) => {
155158
* MetricContainer component.
156159
*
157160
* @param {object} props - Component props.
158-
* @param {MetricName} props.name - Metric name to fetch and graph.
161+
* @param {string} props.name - Metric name to fetch and graph.
159162
* @returns {FabricateComponent} MetricContainer component.
160163
*/
161-
const MetricContainer = ({ name } : { name: MetricName }) => fabricate('Column')
164+
const MetricContainer = ({ name } : { name: string }) => fabricate('Column')
162165
.setStyles(({ palette }) => ({
163166
margin: '15px',
164167
width: `${GRAPH_WIDTH}px`,
@@ -179,7 +182,7 @@ const MetricContainer = ({ name } : { name: MetricName }) => fabricate('Column')
179182
padding: '5px',
180183
borderBottom: `solid 2px ${palette.grey6}`,
181184
}))
182-
.setText(METRIC_NAME_MAP[name]),
185+
.setText(METRIC_NAME_MAP[name] || name),
183186
MetricGraph({ name }),
184187
]);
185188

@@ -193,20 +196,29 @@ const DeviceMetrics = () => fabricate('Row')
193196
margin: '15px',
194197
flexWrap: 'wrap',
195198
})
196-
.onUpdate(async (el, state) => {
197-
const { selectedDevice } = state;
199+
.onCreate(async (el, state) => {
200+
// Get the metrics available, each graph loads its own
201+
fabricate.update({ metricNames: [] });
202+
const {
203+
message: metricNames,
204+
} = await sendConduitPacket(state, { to: 'monitor', topic: 'getMetricNames' });
205+
fabricate.update({ metricNames });
206+
})
207+
.onUpdate(async (el, state, keys) => {
208+
const { selectedDevice, metricNames } = state;
198209

199210
if (!selectedDevice) {
200211
el.setChildren([NoMetricsLabel()]);
201212
return;
202213
}
203214

204-
el.setChildren([
205-
MetricContainer({ name: 'cpu' }),
206-
MetricContainer({ name: 'memoryPerc' }),
207-
MetricContainer({ name: 'tempRaw' }),
208-
MetricContainer({ name: 'freqPerc' }),
209-
]);
210-
}, [fabricate.StateKeys.Created, 'selectedDevice']);
215+
if (keys.includes('metricNames')) {
216+
el.setChildren(
217+
metricNames
218+
.filter((name) => !!METRIC_NAME_MAP[name])
219+
.map((name) => MetricContainer({ name })),
220+
);
221+
}
222+
}, [fabricate.StateKeys.Created, 'selectedDevice', 'metricNames']);
211223

212224
export default DeviceMetrics;

dashboard2/src/components/SideBar.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Fabricate } from 'fabricate.js';
22
import { AppState, Device } from '../types';
3-
import { fetchDeviceApps, sortDeviceByName } from '../util';
3+
import { sortDeviceByName } from '../util';
44
import { ICON_NAMES } from '../constants';
55
import AppLoader from './AppLoader';
6+
import { fetchDeviceApps } from '../services/conduitService';
67

78
declare const fabricate: Fabricate<AppState>;
89

@@ -87,9 +88,6 @@ const DeviceRow = ({ device }: { device: Device }) => {
8788
}));
8889
})
8990
.onClick((el, state) => {
90-
const { selectedDevice } = state;
91-
if (selectedDevice?.deviceName === deviceName) return;
92-
9391
// Select this device
9492
fabricate.update({ selectedDevice: device });
9593

dashboard2/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const INITIAL_STATE: AppState = {
1414
// Loaded data
1515
selectedDeviceApps: [],
1616
devices: [],
17+
metricNames: [],
1718

1819
// Selections
1920
selectedDevice: null,

dashboard2/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { Fabricate } from 'fabricate.js';
22
import { AppState } from './types';
33
import Theme from './theme';
44
import { INITIAL_STATE } from './constants';
5-
import { parseParams, fetchFleetList } from './util';
5+
import { parseParams } from './util';
66
import SideBar from './components/SideBar';
77
import AppArea from './components/AppArea';
8+
import { fetchFleetList } from './services/conduitService';
89

910
declare const fabricate: Fabricate<AppState>;
1011

dashboard2/src/services/conduitService.ts

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import { CONDUIT_PORT } from '../constants';
2-
import { AppState, Packet } from '../types';
1+
import { Fabricate } from 'fabricate.js';
2+
import {
3+
AppState, DataPoint, Device, DeviceApp,
4+
Packet,
5+
} from '../types';
6+
import { BUCKET_SIZE, CONDUIT_PORT, FLEET_HOST } from '../constants';
7+
import { shortDateTime, sortAppByName } from '../util';
8+
9+
declare const fabricate: Fabricate<AppState>;
10+
11+
/** Extra Y for visibility */
12+
const Y_EXTRA = 1.1;
313

414
/**
515
* Send a conduit packet.
@@ -54,3 +64,153 @@ export const sendConduitPacket = async (
5464
throw error;
5565
}
5666
};
67+
68+
/**
69+
* Re-load the devices list data.
70+
*
71+
* @param {object} state - App state.
72+
*/
73+
export const fetchFleetList = async (state: AppState) => {
74+
const { token } = state;
75+
fabricate.update({ devices: [] });
76+
77+
try {
78+
// Can't use sendConduitPacket, not a device by name
79+
const res = await fetch(`http://${FLEET_HOST}:${CONDUIT_PORT}/conduit`, {
80+
method: 'POST',
81+
headers: { 'Content-Type': 'application/json' },
82+
body: JSON.stringify({
83+
to: 'attic',
84+
topic: 'get',
85+
message: { app: 'conduit', key: 'fleetList' },
86+
auth: token || '',
87+
}),
88+
});
89+
const { message } = await res.json();
90+
fabricate.update({ devices: message.value });
91+
} catch (err) {
92+
console.error(err);
93+
alert(err);
94+
}
95+
};
96+
97+
/**
98+
* Load apps for all fleet devices.
99+
*
100+
* @param {AppState} state - App state.
101+
* @param {Device} device - Device to use.
102+
*/
103+
export const fetchDeviceApps = async (state: AppState, device: Device) => {
104+
const { deviceName } = device!;
105+
106+
fabricate.update({ selectedDeviceApps: [] });
107+
108+
try {
109+
const { message } = await sendConduitPacket(
110+
state,
111+
{ to: 'conduit', topic: 'getApps' },
112+
deviceName,
113+
);
114+
115+
let selectedDeviceApps: DeviceApp[] = [];
116+
if (message && message.error) {
117+
console.error(message.error);
118+
selectedDeviceApps = [];
119+
} else if (!message) {
120+
console.error('No response in fetchApps');
121+
selectedDeviceApps = [];
122+
} else {
123+
selectedDeviceApps = (message as DeviceApp[]).sort(sortAppByName);
124+
}
125+
126+
fabricate.update({ selectedDeviceApps });
127+
} catch (err: unknown) {
128+
console.error(err);
129+
fabricate.update({ selectedDeviceApps: [] });
130+
}
131+
};
132+
133+
/**
134+
* Fetch data for a metric.
135+
*
136+
* @param {AppState} state - App state.
137+
* @param {string} name - Metric name.
138+
*/
139+
export const fetchMetric = async (state: AppState, name: string) => {
140+
const dataKey = fabricate.buildKey('metricData', name);
141+
fabricate.update(
142+
dataKey,
143+
{
144+
buckets: [],
145+
minTime: 0,
146+
maxTime: 0,
147+
minValue: 0,
148+
maxValue: 0,
149+
},
150+
);
151+
152+
const res = await sendConduitPacket(
153+
state,
154+
{
155+
to: 'monitor',
156+
topic: 'getMetricToday',
157+
message: { name },
158+
},
159+
);
160+
const { message: newHistory } = res;
161+
if (res.error) console.log(res);
162+
if (!newHistory.length) return;
163+
164+
const type = Array.isArray(newHistory[0][1]) ? 'array' : 'number';
165+
166+
const minTime = shortDateTime(newHistory[0][0]);
167+
const maxTime = shortDateTime(newHistory[newHistory.length - 1][0]);
168+
let minValue = 0;
169+
let maxValue = 0;
170+
171+
if (type === 'number') {
172+
// Aggregate values
173+
minValue = name.includes('Perc')
174+
? 0
175+
: newHistory.reduce(
176+
// @ts-expect-error handled with 'type'
177+
(acc: number, [, value]: MetricPoint) => (value < acc ? value : acc),
178+
9999999,
179+
);
180+
maxValue = name.includes('Perc')
181+
? Math.round(100 * Y_EXTRA)
182+
: Math.round(
183+
newHistory.reduce(
184+
// @ts-expect-error handled with 'type'
185+
(acc: number, [, value]: MetricPoint) => (value > acc ? value : acc),
186+
0,
187+
) * Y_EXTRA,
188+
);
189+
} else {
190+
throw new Error('Unexpected metric data type');
191+
}
192+
193+
// Average into buckets
194+
const copy = [...newHistory];
195+
const buckets: DataPoint[] = [];
196+
while (copy.length) {
197+
const points = copy.splice(0, BUCKET_SIZE);
198+
const avgIndex = Math.floor(points.length / 2);
199+
buckets.push({
200+
value: points.reduce((acc, [, value]) => acc + value, 0) / points.length,
201+
timestamp: points[avgIndex][0],
202+
dateTime: new Date(points[avgIndex][0]).toISOString(),
203+
});
204+
}
205+
206+
fabricate.update(
207+
dataKey,
208+
{
209+
buckets,
210+
minTime,
211+
maxTime,
212+
minValue,
213+
maxValue,
214+
},
215+
);
216+
};

dashboard2/src/types.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ export type DataPoint = {
3838
dateTime: string;
3939
};
4040

41-
/** Available graphed metrics */
42-
export type MetricName = 'cpu' | 'memoryPerc' | 'tempRaw' | 'freqPerc';
43-
4441
/** Raw metric point */
4542
export type MetricPoint = [number, number];
4643

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

6866
// Selections
6967
selectedDevice: Device | null,

0 commit comments

Comments
 (0)