querysub 0.701.0 → 0.703.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/4-dom/qreact.tsx +10 -4
- 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 +11 -3
package/package.json
CHANGED
package/src/4-dom/qreact.tsx
CHANGED
|
@@ -760,11 +760,11 @@ class QRenderClass {
|
|
|
760
760
|
if (equalJSON) {
|
|
761
761
|
// ALSO, we need to make sure the change WAS only because of props...
|
|
762
762
|
let changes = renderWatcher.triggeredByChanges;
|
|
763
|
-
let propsPath =
|
|
763
|
+
let propsPath = getProxyPath(() => self.data().props);
|
|
764
764
|
let onlyPropsChanges = (
|
|
765
|
-
Array.from(changes?.newParentsSynced || []).
|
|
765
|
+
!Array.from(changes?.newParentsSynced || []).some(p => p !== propsPath)
|
|
766
766
|
&& !changes?.extraReasons?.length
|
|
767
|
-
&& Array.from(changes?.paths || []).
|
|
767
|
+
&& !Array.from(changes?.paths || []).some(p => p !== propsPath)
|
|
768
768
|
);
|
|
769
769
|
if (onlyPropsChanges) {
|
|
770
770
|
proxyWatcher.reuseLastWatches();
|
|
@@ -864,6 +864,10 @@ class QRenderClass {
|
|
|
864
864
|
props = syncedData.props;
|
|
865
865
|
});
|
|
866
866
|
} else {
|
|
867
|
+
let atomicProps = statics.atomicSetProps || statics.jsonComparePropUpdates;
|
|
868
|
+
if (atomicProps) {
|
|
869
|
+
syncedData.props = vNode.props;
|
|
870
|
+
}
|
|
867
871
|
// Have each prop be a synced field, so conditional props work (accessing a prop not
|
|
868
872
|
// provided returns undefined). Atomic writes is on, so write objects
|
|
869
873
|
// aren't destructured (this allows passing functions, exotics, etc).
|
|
@@ -873,7 +877,9 @@ class QRenderClass {
|
|
|
873
877
|
if (key === "className") {
|
|
874
878
|
value = String(value);
|
|
875
879
|
}
|
|
876
|
-
|
|
880
|
+
if (!atomicProps) {
|
|
881
|
+
syncedData.props[key] = value;
|
|
882
|
+
}
|
|
877
883
|
// We then need to use a getter in the props, so that it causes a synced read
|
|
878
884
|
// UNLESS that prop is not defined, then it is just undefined
|
|
879
885
|
Object.defineProperty(props, key, {
|
|
@@ -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_HUE = 210;
|
|
7
|
+
const SUB_BAR_ALPHA = 0.35;
|
|
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,11 @@ export class UsageBar extends qreact.Component<{
|
|
|
50
55
|
+ fillClass
|
|
51
56
|
+ (isError && (" " + FLASH_CLASS_NAME) || "")
|
|
52
57
|
} />
|
|
53
|
-
<div className={
|
|
54
|
-
|
|
58
|
+
{subValue !== undefined && <div className={
|
|
59
|
+
css.absolute.pos(0, 0).size(`${Math.min(subFraction, 1) * 100}%`, "100%").hsla(SUB_BAR_HUE, 80, 55, SUB_BAR_ALPHA)
|
|
60
|
+
} />}
|
|
61
|
+
<div className={css.relative} title={subValue !== undefined ? `${subLabel || "sub"} / used / total` : "used / total"}>
|
|
62
|
+
{label} ({subValue !== undefined && <span className={css.opacity(0.75)}>{formatNumber(subValue)}{suffix} / </span>}{formatNumber(value)}{suffix} / {formatNumber(max)}{suffix})
|
|
55
63
|
</div>
|
|
56
64
|
{isError && <style>{`
|
|
57
65
|
@keyframes ${FLASH_CLASS_NAME}-anim {
|