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 +1 -1
- package/src/deployManager/components/MachineDetailPage.tsx +9 -1
- package/src/deployManager/components/MachinesListPage.tsx +1 -1
- package/src/deployManager/machineApplyMainCode.ts +48 -2
- package/src/deployManager/machineSchema.ts +4 -0
- package/src/library-components/InputLabel.tsx +9 -2
- package/src/library-components/UsageBar.tsx +10 -1
package/package.json
CHANGED
|
@@ -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",
|
|
@@ -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 {
|