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.
- package/package.json +2 -4
- package/src/deployManager/components/MachineDetailPage.tsx +2 -5
- package/src/deployManager/components/ServiceDetailPage.tsx +2 -5
- package/src/deployManager/machineApplyMainCode.ts +7 -0
- package/src/diagnostics/NodeViewer.tsx +4 -5
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +7 -7
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +221 -220
- package/src/diagnostics/logs/IndexedLogs/LogViewerParams.ts +21 -0
- package/src/diagnostics/logs/IndexedLogs/bufferMatcher.ts +3 -3
- package/src/diagnostics/logs/diskLogger.ts +0 -38
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +3 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +3 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +32 -22
- package/src/diagnostics/managementPages.tsx +0 -18
- package/test.ts +0 -5
- package/bin/error-email.js +0 -8
- package/bin/error-im.js +0 -8
- package/src/diagnostics/logs/FastArchiveAppendable.ts +0 -843
- package/src/diagnostics/logs/FastArchiveController.ts +0 -573
- package/src/diagnostics/logs/FastArchiveViewer.tsx +0 -1090
- package/src/diagnostics/logs/LogViewer2.tsx +0 -552
- package/src/diagnostics/logs/errorNotifications/ErrorDigestPage.tsx +0 -409
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +0 -756
- package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +0 -280
- package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +0 -254
- package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +0 -233
- package/src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx +0 -14
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +0 -292
- package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +0 -209
- package/src/diagnostics/logs/importLogsEntry.ts +0 -38
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +0 -150
- 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
|
-
}
|