querysub 0.701.0 → 0.702.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.701.0",
3
+ "version": "0.702.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -134,13 +134,21 @@ export class MachineDetailPage extends qreact.Component {
134
134
  <ShowMore className={css.whiteSpace("pre-wrap")} maxHeight={80}>
135
135
  {(() => {
136
136
  if (typeof value === "object") {
137
- return <UsageBar label={value.label || value.type} value={value.value} max={value.max} {...getUsageThresholds(value.type)} />;
137
+ return <UsageBar label={value.label || value.type} value={value.value} max={value.max} subValue={value.subValue} subLabel={value.subLabel} {...getUsageThresholds(value.type)} />;
138
138
  }
139
139
  return value;
140
140
  })()}
141
141
  </ShowMore>
142
142
  </div>
143
143
  ))}
144
+ {machine.meminfo && (
145
+ <div className={css.hbox(10).hsla(0, 0, 0, 0.1).pad2(10, 2)}>
146
+ <b className={css.flexShrink0}>meminfo:</b>
147
+ <ShowMore className={css.whiteSpace("pre-wrap")} maxHeight={80}>
148
+ {Object.entries(machine.meminfo).map(([key, bytes]) => `${key}: ${formatNumber(bytes)}B`).join("\n")}
149
+ </ShowMore>
150
+ </div>
151
+ )}
144
152
  </div>
145
153
  </div>
146
154
 
@@ -187,7 +187,7 @@ export class MachinesListPage extends qreact.Component {
187
187
  <RenderGitRefInfo gitRef={machineInfo.gitRef} />
188
188
  {Object.values(machineInfo.info).map(value => {
189
189
  if (typeof value === "string") return undefined;
190
- return <UsageBar label={value.label || value.type} value={value.value} max={value.max} {...getUsageThresholds(value.type)} />;
190
+ return <UsageBar label={value.label || value.type} value={value.value} max={value.max} subValue={value.subValue} subLabel={value.subLabel} {...getUsageThresholds(value.type)} />;
191
191
  })}
192
192
  </div>
193
193
  <div className={css.vbox(4).flexGrow(1)}>
@@ -63,6 +63,45 @@ const getMemoryInfo = measureWrap(async function getMemoryInfo(): Promise<{ memo
63
63
  });
64
64
 
65
65
 
66
+ const MEMINFO_LINE_REGEX = /^(\S+):\s+(\d+)(?:\s+(\S+))?\s*$/;
67
+ const MEMINFO_UNIT_MULTIPLIERS: Record<string, number> = {
68
+ b: 1,
69
+ kb: 1024,
70
+ mb: 1024 * 1024,
71
+ gb: 1024 * 1024 * 1024,
72
+ };
73
+
74
+ export function parseMeminfo(raw: string): Record<string, number> {
75
+ let result: Record<string, number> = {};
76
+ for (let line of raw.split("\n")) {
77
+ let match = MEMINFO_LINE_REGEX.exec(line);
78
+ if (!match) continue;
79
+ let [, key, valueText, unit] = match;
80
+ let multiplier = unit ? MEMINFO_UNIT_MULTIPLIERS[unit.toLowerCase()] : 1;
81
+ if (multiplier === undefined) continue;
82
+ let value = parseInt(valueText);
83
+ if (!(value >= 0)) continue;
84
+ result[key] = value * multiplier;
85
+ }
86
+ return result;
87
+ }
88
+
89
+ const getMeminfo = measureWrap(async function getMeminfo(): Promise<Record<string, number> | undefined> {
90
+ if (os.platform() === "win32") {
91
+ throw new Error("Windows is not supported for machine resource monitoring");
92
+ }
93
+ try {
94
+ let raw = await fs.promises.readFile("/proc/meminfo", "utf8");
95
+ let parsed = parseMeminfo(raw);
96
+ if (Object.keys(parsed).length > 0) {
97
+ return parsed;
98
+ }
99
+ } catch (e: any) {
100
+ console.warn(`Error reading /proc/meminfo: ${e.stack}`);
101
+ }
102
+ return undefined;
103
+ });
104
+
66
105
  const LARGE_DISK_MIN_BYTES = 100 * 1024 * 1024 * 1024;
67
106
  const DF_LINE_REGEX = /^(\S+)\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+(.+?)\s*$/;
68
107
 
@@ -117,9 +156,10 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
117
156
  };
118
157
 
119
158
  // Get system resource information
120
- let [memoryInfo, diskInfos] = await Promise.all([
159
+ let [memoryInfo, diskInfos, meminfo] = await Promise.all([
121
160
  getMemoryInfo(),
122
- getDiskInfos()
161
+ getDiskInfos(),
162
+ getMeminfo(),
123
163
  ]);
124
164
 
125
165
  if (memoryInfo?.memory) {
@@ -127,9 +167,15 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
127
167
  type: "MEMORY",
128
168
  value: memoryInfo.memory.value,
129
169
  max: memoryInfo.memory.max,
170
+ subValue: meminfo?.Active,
171
+ subLabel: "active",
130
172
  };
131
173
  }
132
174
 
175
+ if (meminfo) {
176
+ machineInfo.meminfo = meminfo;
177
+ }
178
+
133
179
  if (memoryInfo?.swap) {
134
180
  machineInfo.info.swap = {
135
181
  type: "SWAP",
@@ -64,8 +64,12 @@ export type MachineInfo = {
64
64
  value: number;
65
65
  max: number;
66
66
  label?: string;
67
+ subValue?: number;
68
+ subLabel?: string;
67
69
  }>;
68
70
 
71
+ meminfo?: Record<string, number>;
72
+
69
73
  repoUrl: string;
70
74
  gitRef: string;
71
75
 
@@ -28,12 +28,15 @@ export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
28
28
  tooltip?: string;
29
29
 
30
30
  fillWidth?: boolean | number;
31
+ // Height a textarea opens at in edit mode; EDIT_TEXTAREA_MIN_HEIGHT when unset.
32
+ editMinHeight?: number;
31
33
  };
32
34
 
33
35
  // A checkbox is a small square that reads as belonging to the words beside it, so it sits tight against
34
36
  // its label; every other input is a box of its own and wants room between the two.
35
37
  const LABEL_GAP = 8;
36
38
  const CHECKBOX_LABEL_GAP = 2;
39
+ export const EDIT_TEXTAREA_MIN_HEIGHT = 80;
37
40
 
38
41
  function roundToDecimals(value: number, decimals: number) {
39
42
  return Math.round(value * 10 ** decimals) / 10 ** decimals;
@@ -134,6 +137,10 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
134
137
  };
135
138
  // Default focus on mount, as editting it is the only reason to click on it.
136
139
  props.focusOnMount = props.focusOnMount ?? true;
140
+ if (props.textarea) {
141
+ props.fillWidth = props.fillWidth ?? true;
142
+ style.minHeight = style.minHeight ?? props.editMinHeight ?? EDIT_TEXTAREA_MIN_HEIGHT;
143
+ }
137
144
  }
138
145
  let input = <Input
139
146
  {...props as any}
@@ -141,8 +148,8 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
141
148
  style={style}
142
149
  />;
143
150
  if (props.edit && !this.state.editting) {
144
- input = <span class={css.hbox(2).overflowHidden + " trigger-hover"}>
145
- <span class={props.editClass}>
151
+ input = <span class={css.hbox(2).overflowHidden + (props.textarea && css.fillWidth.alignItems("start") || "") + " trigger-hover"}>
152
+ <span class={(props.textarea && css.whiteSpace("pre-wrap").fillWidth || "") + " " + props.editClass}>
146
153
  {props.editValue ?? props.value}
147
154
  </span>
148
155
  <span class={css.opacity(0.1).opacity(1, "hover")}>
@@ -3,6 +3,8 @@ import { css } from "typesafecss";
3
3
  import { formatNumber } from "socket-function/src/formatting/format";
4
4
 
5
5
  const FLASH_CLASS_NAME = "UsageBar-flash";
6
+ const SUB_BAR_HEIGHT_PERCENT = 35;
7
+ const SUB_BAR_ALPHA = 0.3;
6
8
 
7
9
  export const MEMORY_WARNING_THRESHOLD = 0.7;
8
10
  export const MEMORY_ERROR_THRESHOLD = 0.85;
@@ -36,11 +38,14 @@ export class UsageBar extends qreact.Component<{
36
38
  warningThreshold?: number;
37
39
  /** Fraction of max at which the bar fill turns red and flashes. */
38
40
  errorThreshold?: number;
41
+ subValue?: number;
42
+ subLabel?: string;
39
43
  }> {
40
44
  render() {
41
- let { label, value, max, unit, warningThreshold, errorThreshold } = this.props;
45
+ let { label, value, max, unit, warningThreshold, errorThreshold, subValue, subLabel } = this.props;
42
46
  let suffix = unit || "";
43
47
  let fraction = max && value / max || 0;
48
+ let subFraction = subValue !== undefined && max && subValue / max || 0;
44
49
  let isError = errorThreshold !== undefined && fraction >= errorThreshold;
45
50
  let isWarning = !isError && warningThreshold !== undefined && fraction >= warningThreshold;
46
51
  let fillClass = isError && css.hsl(0, 80, 60) || isWarning && css.hsl(45, 90, 60) || css.hsl(0, 0, 70);
@@ -50,8 +55,12 @@ export class UsageBar extends qreact.Component<{
50
55
  + fillClass
51
56
  + (isError && (" " + FLASH_CLASS_NAME) || "")
52
57
  } />
58
+ {subValue !== undefined && <div className={
59
+ css.absolute.pos(0, `${100 - SUB_BAR_HEIGHT_PERCENT}%`).size(`${Math.min(subFraction, 1) * 100}%`, `${SUB_BAR_HEIGHT_PERCENT}%`).hsla(0, 0, 0, SUB_BAR_ALPHA)
60
+ } />}
53
61
  <div className={css.relative}>
54
62
  {label} ({formatNumber(value)}{suffix} / {formatNumber(max)}{suffix})
63
+ {subValue !== undefined && <span className={css.opacity(0.75)}>, {subLabel || "sub"} {formatNumber(subValue)}{suffix}</span>}
55
64
  </div>
56
65
  {isError && <style>{`
57
66
  @keyframes ${FLASH_CLASS_NAME}-anim {