querysub 0.374.0 → 0.375.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.
Files changed (33) hide show
  1. package/package.json +2 -4
  2. package/src/deployManager/components/MachineDetailPage.tsx +2 -5
  3. package/src/deployManager/components/ServiceDetailPage.tsx +2 -5
  4. package/src/deployManager/machineApplyMainCode.ts +7 -0
  5. package/src/diagnostics/NodeViewer.tsx +4 -5
  6. package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +1 -1
  7. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +7 -7
  8. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +221 -220
  9. package/src/diagnostics/logs/IndexedLogs/LogViewerParams.ts +21 -0
  10. package/src/diagnostics/logs/IndexedLogs/bufferMatcher.ts +3 -3
  11. package/src/diagnostics/logs/diskLogger.ts +0 -38
  12. package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +3 -0
  13. package/src/diagnostics/logs/injectFileLocationToConsole.ts +3 -0
  14. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +32 -22
  15. package/src/diagnostics/managementPages.tsx +0 -18
  16. package/test.ts +0 -5
  17. package/bin/error-email.js +0 -8
  18. package/bin/error-im.js +0 -8
  19. package/src/diagnostics/logs/FastArchiveAppendable.ts +0 -843
  20. package/src/diagnostics/logs/FastArchiveController.ts +0 -573
  21. package/src/diagnostics/logs/FastArchiveViewer.tsx +0 -1090
  22. package/src/diagnostics/logs/LogViewer2.tsx +0 -552
  23. package/src/diagnostics/logs/errorNotifications/ErrorDigestPage.tsx +0 -409
  24. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +0 -756
  25. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +0 -280
  26. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +0 -254
  27. package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +0 -233
  28. package/src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx +0 -14
  29. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +0 -292
  30. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +0 -209
  31. package/src/diagnostics/logs/importLogsEntry.ts +0 -38
  32. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +0 -150
  33. package/src/diagnostics/logs/logViewerExtractField.ts +0 -36
@@ -1,280 +0,0 @@
1
- import { SocketFunction } from "socket-function/SocketFunction";
2
- import { qreact } from "../../../4-dom/qreact";
3
- import { NOT_AN_ERROR_EXPIRE_TIME, RecentErrorsController, SuppressionEntry, SuppressionListController, getSuppressEntryChecker } from "./ErrorNotificationController";
4
- import { t } from "../../../2-proxy/schema2";
5
- import { css } from "../../../4-dom/css";
6
- import { InputLabel } from "../../../library-components/InputLabel";
7
- import { Button } from "../../../library-components/Button";
8
- import { Querysub } from "../../../4-querysub/QuerysubController";
9
- import { nextId, sort, timeInDay } from "socket-function/src/misc";
10
- import { formatNumber, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
11
- import { formatDateJSX } from "../../../misc/formatJSX";
12
- import { LogDatum } from "../diskLogger";
13
- import { measureFnc } from "socket-function/src/profiling/measure";
14
- import { throttleRender } from "../../../functional/throttleRender";
15
- import { matchFilter } from "../../../misc";
16
-
17
- export class ErrorSuppressionUI extends qreact.Component<{
18
- dataSeqNum: number;
19
- suppressionCounts: Map<string, number>;
20
- expiredSuppressionCounts: Map<string, number>;
21
- datums: LogDatum[];
22
- rerunFilters: () => void;
23
- }> {
24
- state = t.state({
25
- matchedInput: t.string(""),
26
- renderLimit: t.number(10),
27
- filter: t.string(""),
28
- });
29
-
30
- @measureFnc
31
- private calculatePreviewMatchCount(pattern: string): number {
32
- if (!pattern.trim()) return 0;
33
-
34
- try {
35
- let entry: SuppressionEntry = {
36
- key: "preview",
37
- match: pattern,
38
- comment: "",
39
- lastUpdateTime: Date.now(),
40
- expiresAt: Date.now() + timeInDay,
41
- };
42
- let checker = getSuppressEntryChecker(entry);
43
-
44
- let matchCount = 0;
45
- for (let datum of this.props.datums.slice(0, 1000)) {
46
- let datumStr = JSON.stringify(datum);
47
- let buffer = Buffer.from(datumStr);
48
- if (checker.fnc(buffer, 0, buffer.length)) {
49
- matchCount++;
50
- }
51
- }
52
- return matchCount;
53
- } catch (error) {
54
- return 0;
55
- }
56
- }
57
-
58
- public render() {
59
- if (throttleRender({ key: "ErrorSuppressionUI", frameDelay: 30 })) return undefined;
60
-
61
- this.props.dataSeqNum;
62
- const controller = SuppressionListController(SocketFunction.browserNodeId());
63
- let entries = (controller.getSuppressionList() || []);
64
- sort(entries, x => -x.lastUpdateTime);
65
- sort(entries, x => -(this.props.suppressionCounts.get(x.key) || 0));
66
- sort(entries, x => -(this.props.expiredSuppressionCounts.get(x.key) || 0));
67
-
68
- let totalCount = entries.length;
69
- entries = entries.filter(x => matchFilter({ value: this.state.filter }, x.match));
70
-
71
- const previewMatchCount = this.calculatePreviewMatchCount(this.state.matchedInput);
72
-
73
- return <div className={css.vbox(16).pad2(16).fillWidth.bord2(0, 0, 50, 5).hsl(0, 0, 80)}>
74
- <div className={css.fontSize(18)}>Error Suppression List ({formatNumber(entries.length)} / {formatNumber(totalCount)})</div>
75
-
76
- <div className={css.hbox(8).fillWidth}>
77
- <InputLabel
78
- label="Add Match"
79
- value={this.state.matchedInput}
80
- hot
81
- onChangeValue={(value) => this.state.matchedInput = value}
82
- onKeyDown={e => {
83
- if (e.key === "Enter") {
84
- let value = e.currentTarget.value;
85
- e.currentTarget.value = "";
86
- void Querysub.onCommitFinished(async () => {
87
- await controller.setSuppressionEntry.promise({
88
- key: nextId(),
89
- match: value,
90
- comment: "",
91
- lastUpdateTime: Date.now(),
92
- expiresAt: Date.now() + timeInDay,
93
- });
94
- if (!e.shiftKey) {
95
- Querysub.commit(() => {
96
- this.props.rerunFilters();
97
- });
98
- }
99
- });
100
- }
101
- }}
102
- fillWidth
103
- placeholder="* is supported as a wildcard (enter to submit and refresh, shift+enter to submit and do not refresh)"
104
- />
105
- {this.state.matchedInput.trim() && <span className={css.opacity(0.6).fontSize(12).flexShrink0}>
106
- matches {formatNumber(previewMatchCount)} (in top 1000)
107
- </span>}
108
-
109
- <Button
110
- onClick={() => {
111
- let value = this.state.matchedInput;
112
- this.state.matchedInput = "";
113
- void Querysub.onCommitFinished(async () => {
114
- await controller.setSuppressionEntry.promise({
115
- key: nextId(),
116
- match: value,
117
- comment: "",
118
- lastUpdateTime: Date.now(),
119
- expiresAt: Date.now() + timeInDay,
120
- });
121
- Querysub.commit(() => {
122
- this.props.rerunFilters();
123
- });
124
- });
125
- }}
126
- title="Fixed, or will fix within a day"
127
- >
128
- Fixed
129
- </Button>
130
- <Button
131
- onClick={() => {
132
- let value = this.state.matchedInput;
133
- this.state.matchedInput = "";
134
- void Querysub.onCommitFinished(async () => {
135
- await controller.setSuppressionEntry.promise({
136
- key: nextId(),
137
- match: value,
138
- comment: "",
139
- lastUpdateTime: Date.now(),
140
- expiresAt: Date.now(),
141
- });
142
- Querysub.commit(() => {
143
- this.props.rerunFilters();
144
- });
145
- });
146
- }}
147
- title="Fixed immediately, any future errors even that happen right now will trigger again. "
148
- >
149
- Fixed Now
150
- </Button>
151
- <Button onClick={() => {
152
- let value = this.state.matchedInput;
153
- this.state.matchedInput = "";
154
- void Querysub.onCommitFinished(async () => {
155
- await controller.setSuppressionEntry.promise({
156
- key: nextId(),
157
- match: value,
158
- comment: "",
159
- lastUpdateTime: Date.now(),
160
- expiresAt: NOT_AN_ERROR_EXPIRE_TIME,
161
- });
162
- Querysub.commit(() => {
163
- this.props.rerunFilters();
164
- });
165
- });
166
- }}>
167
- Not a bug
168
- </Button>
169
- </div>
170
-
171
-
172
- <InputLabel
173
- label="Filter"
174
- value={this.state.filter}
175
- hot
176
- onChangeValue={value => this.state.filter = value}
177
- fillWidth
178
- />
179
-
180
-
181
- <div className={css.pad2(12).bord2(200, 40, 85).hsl(200, 40, 95).fillWidth}>
182
- <strong>Note:</strong> Suppression time updates don't automatically rerun the search. Click Run to rerun the search.
183
- </div>
184
-
185
- <div className={css.vbox(8).fillWidth.overflowAuto.maxHeight("20vh")}>
186
- {entries.slice(0, this.state.renderLimit).map((entry) => {
187
- const updateEntry = (changes: Partial<SuppressionEntry>) => {
188
- let newEntry = { ...entry, ...changes };
189
- void Querysub.onCommitFinished(async () => {
190
- await controller.setSuppressionEntry.promise(newEntry);
191
- });
192
- };
193
- let count = this.props.suppressionCounts.get(entry.key) || 0;
194
- let expiredCount = this.props.expiredSuppressionCounts.get(entry.key) || 0;
195
- return <div
196
- key={entry.match}
197
- className={
198
- css.hbox(8).pad2(12).bord2(0, 0, 10).fillWidth
199
- //+ (entry.expiresAt < Date.now() && expiredCount > 0 && css.opacity(0.5))
200
- + ((expiredCount === 0) && css.opacity(0.6))
201
- + (
202
- count > 0 && entry.expiresAt !== NOT_AN_ERROR_EXPIRE_TIME && css.hsla(0, 50, 50, 0.5)
203
- || css.hsla(0, 0, 0, 0.1)
204
- )
205
- }
206
- >
207
- <span className={css.width(100).hbox(2) + (expiredCount === 0 && count === 0 && css.opacity(0.8))}>
208
- ({formatNumber(count)})
209
- {
210
- expiredCount > 0 && <span className={css.hsl(0, 50, 50).pad2(4, 2).colorhsl(0, 50, 95)}>
211
- + {formatNumber(expiredCount)} EXPIRED
212
- </span>
213
- }
214
- </span>
215
- <InputLabel
216
- label="Match Pattern"
217
- value={entry.match}
218
- onChangeValue={value => updateEntry({ match: value })}
219
- fillWidth={0.5}
220
- />
221
- <InputLabel
222
- label="Comment"
223
- value={entry.comment}
224
- onChangeValue={value => updateEntry({ comment: value })}
225
- fillWidth
226
- />
227
-
228
- <Button
229
- onClick={() => updateEntry({ expiresAt: Date.now() + timeInDay })}
230
- title="Fixed, or will fix within a day"
231
- >
232
- Fixed
233
- </Button>
234
- <Button onClick={() => updateEntry({ expiresAt: Date.now() + timeInDay * 7 })}
235
- title="Ignore for a week"
236
- >
237
- Ignore (for a week)
238
- </Button>
239
- <Button onClick={() => updateEntry({ expiresAt: NOT_AN_ERROR_EXPIRE_TIME })}>
240
- Not a bug
241
- </Button>
242
- <Button
243
- onClick={() => updateEntry({ expiresAt: Date.now() - timeInDay * 7 })}
244
- title="Unignore, so past errors show up again as errors"
245
- >
246
- Unignore
247
- </Button>
248
- <Button
249
- onClick={() => updateEntry({ expiresAt: Date.now() })}
250
- title="Set ignore time to now, so any future errors will receive notifications"
251
- >
252
- Ignore previous
253
- </Button>
254
- {entry.expiresAt === NOT_AN_ERROR_EXPIRE_TIME && <span>
255
- Not a bug
256
- </span>}
257
- {entry.expiresAt < Date.now() && <span>
258
- Expired {formatDateJSX(entry.expiresAt)}
259
- </span>}
260
- {entry.expiresAt > Date.now() && entry.expiresAt !== NOT_AN_ERROR_EXPIRE_TIME && <span>
261
- Expires {formatDateJSX(entry.expiresAt)}
262
- </span>}
263
- <span className={css.opacity(0.5)}>
264
- (Last Updated: {formatDateJSX(entry.lastUpdateTime)})
265
- </span>
266
- <Button
267
- onClick={() => controller.removeSuppressionEntry.promise(entry.key)}
268
- hue={120}
269
- >
270
- Remove
271
- </Button>
272
- </div>;
273
- })}
274
- {entries.length > this.state.renderLimit && <Button onClick={() => this.state.renderLimit *= 2}>
275
- Load More
276
- </Button>}
277
- </div>
278
- </div>;
279
- }
280
- }
@@ -1,254 +0,0 @@
1
- import { SocketFunction } from "socket-function/SocketFunction";
2
- import { qreact } from "../../../4-dom/qreact";
3
- import { css } from "../../../4-dom/css";
4
- import { isCurrentUserSuperUser, user_data } from "../../../user-implementation/userData";
5
- import { RecentErrorsController, SuppressionListController, watchRecentErrors, MAX_RECENT_ERRORS, NOT_AN_ERROR_EXPIRE_TIME, SuppressionEntry } from "./ErrorNotificationController";
6
- import { t } from "../../../2-proxy/schema2";
7
- import { InputLabel } from "../../../library-components/InputLabel";
8
- import { Button } from "../../../library-components/Button";
9
- import { ATag, Anchor, URLOverride } from "../../../library-components/ATag";
10
- import { managementPageURL, showingManagementURL } from "../../managementPages";
11
- import { errorNotifyToggleURL } from "../LogViewer2";
12
- import { Querysub } from "../../../4-querysub/QuerysubController";
13
- import { deepCloneJSON, nextId, timeInDay, timeInHour } from "socket-function/src/misc";
14
- import { formatNumber } from "socket-function/src/formatting/format";
15
- import { Icon } from "../../../library-components/icons";
16
- import { filterParam } from "../FastArchiveViewer";
17
- import { endTimeParam, startTimeParam } from "../TimeRangeSelector";
18
- import { formatDateJSX } from "../../../misc/formatJSX";
19
- import { atomic } from "../../../2-proxy/PathValueProxyWatcher";
20
-
21
- export function getErrorLogsLink(config?: {
22
- startTime?: number;
23
- endTime?: number;
24
- }): URLOverride[] {
25
- let startTime = config?.startTime ?? Date.now() - timeInDay * 1;
26
- let endTime = config?.endTime ?? Date.now() + timeInHour * 2;
27
- return [
28
- showingManagementURL.getOverride(true),
29
- managementPageURL.getOverride("LogViewer2"),
30
- errorNotifyToggleURL.getOverride(true),
31
- filterParam.getOverride(""),
32
-
33
- // NOTE: While loading a weeks worth of logs clientside is a bit slow. Scanning serverside is not nearly as bad, as it can be done over hours, but... we want the page to be snappy, loading in seconds, so... just use a day, and we might reduce it even further if needed...
34
- startTimeParam.getOverride(startTime),
35
- endTimeParam.getOverride(endTime),
36
- ];
37
- }
38
-
39
- export class ErrorWarning extends qreact.Component {
40
- state = t.state({
41
- suppressionInput: t.string(""),
42
- reloading: t.boolean(false),
43
- });
44
-
45
- public render() {
46
- if (!isCurrentUserSuperUser()) return undefined;
47
- watchRecentErrors();
48
-
49
- let recentController = RecentErrorsController(SocketFunction.browserNodeId());
50
- let suppressionController = SuppressionListController(SocketFunction.browserNodeId());
51
-
52
- let recentErrors = recentController.getRecentErrors();
53
-
54
- const refreshButton =
55
- <Button
56
- onClick={() => {
57
- this.state.reloading = true;
58
- void Querysub.onCommitFinished(async () => {
59
- try {
60
- await recentController.rescanAllErrorsNow.promise();
61
- } finally {
62
- Querysub.commit(() => {
63
- this.state.reloading = false;
64
- });
65
- }
66
- });
67
- }}
68
- flavor="noui"
69
- className={
70
- css.button.hsla(0, 0, 50, 1).pad2(4, 1).borderRadius(2)
71
- + (this.state.reloading && css.animation("ErrorWarning-rotate 1s linear infinite"))
72
- }
73
- >
74
-
75
- <style>
76
- {
77
- `@keyframes ErrorWarning-rotate {
78
- from {
79
- transform: rotate(0deg);
80
- }
81
- to {
82
- transform: rotate(360deg);
83
- }
84
- }
85
- `}
86
- </style>
87
- </Button>;
88
-
89
- let discordURLWarning: qreact.ComponentChildren = undefined;
90
- if (!atomic(user_data().secure.notifyDiscordWebhookURL)) {
91
- discordURLWarning = (
92
- <Anchor
93
- target="_blank"
94
- title="Can't send application notifications to developers due to missing Discord hook URL. Click here and set it."
95
- values={[
96
- showingManagementURL.getOverride(true),
97
- managementPageURL.getOverride("SecurityPage"),
98
- ]}
99
- >
100
- <Button hue={0}>
101
- ⚠️ Missing Discord Hook URL <span className={css.filter("invert(1)")}>📞</span>
102
- </Button>
103
- </Anchor>
104
- );
105
- }
106
-
107
- const logLink = getErrorLogsLink();
108
-
109
- if (!recentErrors || recentErrors.length === 0) {
110
- return (
111
- <span className={css.hbox(8)}>
112
- <ATag target="_blank" values={logLink}>
113
- No Errors
114
- </ATag>
115
- {refreshButton}
116
- {discordURLWarning}
117
- </span>
118
- );
119
- }
120
-
121
- // Count unique files
122
- let fileSet = new Set<string>();
123
- for (let error of recentErrors) {
124
- let file = String(error.__FILE__) || "";
125
- if (file) {
126
- fileSet.add(file);
127
- }
128
- }
129
- let fileCount = fileSet.size;
130
- let fileCountText = `${formatNumber(fileCount)} files matched`;
131
- if (recentErrors.length >= MAX_RECENT_ERRORS) {
132
- fileCountText = "> " + fileCountText;
133
- }
134
-
135
- const addSuppressionEntry = async (match: string, expiresAt: number) => {
136
- await suppressionController.setSuppressionEntry.promise({
137
- key: nextId(),
138
- match: match,
139
- comment: "",
140
- lastUpdateTime: Date.now(),
141
- expiresAt: expiresAt,
142
- });
143
- };
144
-
145
- let suppressionList = suppressionController.getSuppressionList() || [];
146
- let topExpired: SuppressionEntry | undefined = undefined;
147
- if (recentErrors[0].__matchedOutdatedSuppressionKey) {
148
- let key = recentErrors[0].__matchedOutdatedSuppressionKey;
149
- topExpired = suppressionList.find(x => x.key === key);
150
- }
151
- function showSpecialCharacters(text: string) {
152
- return text.replaceAll("\n", "\\n");
153
- }
154
-
155
- return (
156
- <div className={css.vbox(8).fillWidth}>
157
- <div className={css.hbox(8).wrap}>
158
- <div className={css}>
159
- ⚠️ {fileCountText} files with errors
160
- </div>
161
-
162
- <ATag target="_blank" values={logLink}>
163
- View Logs
164
- </ATag>
165
- {refreshButton}
166
- {discordURLWarning}
167
- </div>
168
-
169
- {topExpired &&
170
- <div className={css.hbox(8)}>
171
- <Button onClick={() => {
172
- let newObj = deepCloneJSON({
173
- ...topExpired!,
174
- expiresAt: Date.now() + timeInDay,
175
- });
176
- void Querysub.onCommitFinished(async () => {
177
- await suppressionController.setSuppressionEntry.promise(newObj);
178
- });
179
- }}>
180
- Ignore Again ({formatDateJSX(topExpired.expiresAt)})
181
- </Button>
182
- <div>
183
- Match Pattern =
184
- </div>
185
- <div className={css.hsl(200, 40, 40).pad2(4, 2).colorhsl(0, 0, 95)}>
186
- {showSpecialCharacters(topExpired.match)}
187
- </div>
188
- </div>
189
- }
190
- <div className={css.hbox(8).hsl(0, 50, 50).pad2(4, 2).colorhsl(0, 50, 95)}>
191
- ({formatDateJSX(recentErrors[0].time)}) {showSpecialCharacters(String(recentErrors[0].param0))} ({recentErrors[0].__NAME__})
192
- </div>
193
-
194
- <div className={css.hbox(8).fillWidth}>
195
- <InputLabel
196
- value={this.state.suppressionInput}
197
- noEnterKeyBlur
198
- onChangeValue={(value: string) => this.state.suppressionInput = value}
199
- onKeyDown={e => {
200
- if (e.key === "Enter") {
201
- let value = e.currentTarget.value;
202
- if (value.trim()) {
203
- e.currentTarget.value = "";
204
- void Querysub.onCommitFinished(async () => {
205
- await addSuppressionEntry(value, Date.now() + timeInDay);
206
- Querysub.commit(() => {
207
- this.state.suppressionInput = "";
208
- });
209
- });
210
- }
211
- }
212
- }}
213
- placeholder="Quick suppress pattern (press enter to add)"
214
- fillWidth
215
- />
216
-
217
-
218
- <Button
219
- onClick={() => {
220
- let value = this.state.suppressionInput;
221
- if (value.trim()) {
222
- void Querysub.onCommitFinished(async () => {
223
- await addSuppressionEntry(value, Date.now() + timeInDay);
224
- Querysub.commit(() => {
225
- this.state.suppressionInput = "";
226
- });
227
- });
228
- }
229
- }}
230
- className={css.fontSize(12).pad2(4, 6)}
231
- >
232
- Fixed
233
- </Button>
234
- <Button
235
- onClick={() => {
236
- let value = this.state.suppressionInput;
237
- if (value.trim()) {
238
- void Querysub.onCommitFinished(async () => {
239
- await addSuppressionEntry(value, NOT_AN_ERROR_EXPIRE_TIME);
240
- Querysub.commit(() => {
241
- this.state.suppressionInput = "";
242
- });
243
- });
244
- }
245
- }}
246
- className={css.fontSize(12).pad2(4, 6)}
247
- >
248
- Not a bug
249
- </Button>
250
- </div>
251
- </div>
252
- );
253
- }
254
- }