querysub 0.312.0 → 0.313.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/.cursorrules +1 -1
- package/costsBenefits.txt +4 -1
- package/package.json +3 -2
- package/spec.txt +23 -18
- package/src/-0-hooks/hooks.ts +1 -1
- package/src/-a-archives/archives.ts +16 -3
- package/src/-a-archives/archivesBackBlaze.ts +51 -3
- package/src/-a-archives/archivesLimitedCache.ts +175 -0
- package/src/-a-archives/archivesPrivateFileSystem.ts +299 -0
- package/src/-a-auth/certs.ts +58 -31
- package/src/-b-authorities/cdnAuthority.ts +2 -2
- package/src/-b-authorities/dnsAuthority.ts +3 -2
- package/src/-c-identity/IdentityController.ts +3 -2
- package/src/-d-trust/NetworkTrust2.ts +17 -19
- package/src/-e-certs/EdgeCertController.ts +3 -4
- package/src/-e-certs/certAuthority.ts +1 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +9 -7
- package/src/-g-core-values/NodeCapabilities.ts +6 -1
- package/src/0-path-value-core/NodePathAuthorities.ts +1 -1
- package/src/0-path-value-core/PathValueCommitter.ts +3 -3
- package/src/0-path-value-core/PathValueController.ts +3 -3
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +15 -37
- package/src/0-path-value-core/pathValueCore.ts +4 -3
- package/src/3-path-functions/PathFunctionRunner.ts +2 -2
- package/src/4-dom/qreact.tsx +4 -3
- package/src/4-querysub/Querysub.ts +2 -2
- package/src/4-querysub/QuerysubController.ts +2 -2
- package/src/5-diagnostics/GenericFormat.tsx +1 -0
- package/src/5-diagnostics/Table.tsx +3 -0
- package/src/5-diagnostics/diskValueAudit.ts +2 -1
- package/src/5-diagnostics/nodeMetadata.ts +0 -1
- package/src/deployManager/components/MachineDetailPage.tsx +9 -1
- package/src/deployManager/components/ServiceDetailPage.tsx +10 -1
- package/src/diagnostics/NodeViewer.tsx +3 -4
- package/src/diagnostics/logs/FastArchiveAppendable.ts +748 -0
- package/src/diagnostics/logs/FastArchiveController.ts +524 -0
- package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
- package/src/diagnostics/logs/LogViewer2.tsx +349 -0
- package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
- package/src/diagnostics/logs/diskLogger.ts +135 -305
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
- package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
- package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
- package/src/diagnostics/logs/importLogsEntry.ts +38 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +151 -0
- package/src/diagnostics/managementPages.tsx +7 -16
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
- package/src/diagnostics/periodic.ts +5 -0
- package/src/diagnostics/watchdog.ts +2 -2
- package/src/functional/SocketChannel.ts +67 -0
- package/src/library-components/Input.tsx +1 -1
- package/src/library-components/InputLabel.tsx +5 -2
- package/src/misc.ts +111 -0
- package/src/src.d.ts +34 -1
- package/src/user-implementation/userData.ts +4 -3
- package/test.ts +13 -0
- package/testEntry2.ts +29 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
- package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
- package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
- package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
- package/src/diagnostics/logs/DiskLoggerPage.tsx +0 -613
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { cache, lazy } from "socket-function/src/caching";
|
|
2
|
-
import { timeInDay, timeInHour, timeInMinute } from "socket-function/src/misc";
|
|
3
|
-
import { getArchives } from "../../-a-archives/archives";
|
|
4
|
-
import { Zip } from "../../zip";
|
|
5
|
-
import yaml from "yaml";
|
|
6
|
-
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
7
|
-
|
|
8
|
-
export const MAX_EXAMPLE_LENGTH = 1024;
|
|
9
|
-
export const MAX_EXAMPLES = 16;
|
|
10
|
-
export const MAX_UNGROUPED_EXAMPLES = 1000;
|
|
11
|
-
export const LOG_FLUSH_INTERVAL = timeInHour;
|
|
12
|
-
export function getMaxExamples(classifier: LogClass): number {
|
|
13
|
-
if (isUngroupedClass(classifier)) return MAX_UNGROUPED_EXAMPLES;
|
|
14
|
-
return classifier.maxExamples ?? MAX_EXAMPLES;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const LOG_ARCHIVES = lazy(() => getArchives("notify-error-logs"));
|
|
18
|
-
export const LOG_CLASSES_PATH = "classes.yaml";
|
|
19
|
-
export const LOG_BLOCK_EXTENSION = ".log";
|
|
20
|
-
|
|
21
|
-
export const ungroupedPrefix = "ungrouped_";
|
|
22
|
-
|
|
23
|
-
export type LogRaw = {
|
|
24
|
-
time: number;
|
|
25
|
-
message: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type LogType = (
|
|
29
|
-
// NOTE: Most of these levels come fron the type of console[fnc] used
|
|
30
|
-
// - The type controls the rendering style, as well as the default suppression time.
|
|
31
|
-
"info" | "warn" | "error"
|
|
32
|
-
// NOTE: Fatal is any error that includes "FATAL" in the first line
|
|
33
|
-
// TODO: On (new) fatal errors, we should also send an email to the admin
|
|
34
|
-
| "fatal"
|
|
35
|
-
);
|
|
36
|
-
export type LogClass = {
|
|
37
|
-
id: string;
|
|
38
|
-
title: string;
|
|
39
|
-
description: string;
|
|
40
|
-
/** Defaults to 0. Higher means it will take logs before lower priority LogClasses. */
|
|
41
|
-
priority?: number;
|
|
42
|
-
createTime: number;
|
|
43
|
-
|
|
44
|
-
match: {
|
|
45
|
-
type: "includes";
|
|
46
|
-
// Filters for log.toLowerCase().includes(value),
|
|
47
|
-
// matching sanitizeMessageForMatch(message)
|
|
48
|
-
value: string;
|
|
49
|
-
}[];
|
|
50
|
-
|
|
51
|
-
// Suppress from notifications (in the UI, or email, or however it is setup)
|
|
52
|
-
// until time > suppressUntil.
|
|
53
|
-
// - By default "info" type logs set this to Number.MAX_SAFE_INTEGER.
|
|
54
|
-
suppressUntil: number;
|
|
55
|
-
|
|
56
|
-
// If set overrides MAX_EXAMPLES (useful for getting live logs).
|
|
57
|
-
maxExamples?: number;
|
|
58
|
-
|
|
59
|
-
type: LogType;
|
|
60
|
-
};
|
|
61
|
-
let allTypes: { [type in LogType]: 1 } = {
|
|
62
|
-
fatal: 1,
|
|
63
|
-
error: 1,
|
|
64
|
-
warn: 1,
|
|
65
|
-
info: 1,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export type LogBlock = {
|
|
69
|
-
classes: { [classId: string]: LogClassSummary; };
|
|
70
|
-
startTime: number;
|
|
71
|
-
endTime: number;
|
|
72
|
-
|
|
73
|
-
threadId: string;
|
|
74
|
-
machineId: string;
|
|
75
|
-
|
|
76
|
-
// NOTE: These counts are just for navigation of files, AND for the default type for unclassed values.
|
|
77
|
-
// Once a value has a class that determines the type (which might be different than the input class).
|
|
78
|
-
fatalCount: number;
|
|
79
|
-
errorCount: number;
|
|
80
|
-
warnCount: number;
|
|
81
|
-
infoCount: number;
|
|
82
|
-
|
|
83
|
-
seqNum: number;
|
|
84
|
-
|
|
85
|
-
count: number;
|
|
86
|
-
};
|
|
87
|
-
export type LogBlockInfo = {
|
|
88
|
-
startTime: number;
|
|
89
|
-
endTime: number;
|
|
90
|
-
|
|
91
|
-
seqNum: number;
|
|
92
|
-
|
|
93
|
-
count: number;
|
|
94
|
-
|
|
95
|
-
fatalCount: number;
|
|
96
|
-
errorCount: number;
|
|
97
|
-
warnCount: number;
|
|
98
|
-
infoCount: number;
|
|
99
|
-
|
|
100
|
-
threadId: string;
|
|
101
|
-
machineId: string;
|
|
102
|
-
|
|
103
|
-
fileName: string;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// NOTE: After enough classes we will start taking ungrouped values and
|
|
107
|
-
// putting them in an "Other" class.
|
|
108
|
-
export type LogClassSummary = {
|
|
109
|
-
count: number;
|
|
110
|
-
// NOTE: The
|
|
111
|
-
logClassId: string;
|
|
112
|
-
logClassType: LogType;
|
|
113
|
-
examples: LogRaw[];
|
|
114
|
-
firstTime: number;
|
|
115
|
-
lastTime: number;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export function sanitizeMessageForMatch(message: string): string {
|
|
119
|
-
message = message.toLowerCase();
|
|
120
|
-
// Replace multiple whitespaces with a single space
|
|
121
|
-
message = message.replace(/\s+/g, " ");
|
|
122
|
-
return message;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function isClassifierMatch(logClass: LogClass, log: LogRaw): boolean {
|
|
126
|
-
// TODO: Cache sanitizeMessageForMatch when looping thorugh logClasses
|
|
127
|
-
let logMessage = sanitizeMessageForMatch(log.message);
|
|
128
|
-
for (const match of logClass.match) {
|
|
129
|
-
if (logMessage.includes(match.value.toLowerCase())) {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function getLogClassCategorizer(logClasses: LogClass[]): (log: LogRaw, typeHint: LogType) => LogClass {
|
|
137
|
-
return measureWrap(function classifyRawLog(log, typeHint) {
|
|
138
|
-
for (const logClass of logClasses) {
|
|
139
|
-
if (isClassifierMatch(logClass, log)) {
|
|
140
|
-
return logClass;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return ungroupedClass(typeHint);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export const ungroupedClass = cache((type: LogType): LogClass => {
|
|
148
|
-
return {
|
|
149
|
-
id: `${ungroupedPrefix}${type}`,
|
|
150
|
-
title: `Ungrouped ${type}`,
|
|
151
|
-
createTime: 0,
|
|
152
|
-
description: "Ungrouped logs",
|
|
153
|
-
match: [],
|
|
154
|
-
suppressUntil: 0,
|
|
155
|
-
type,
|
|
156
|
-
};
|
|
157
|
-
});
|
|
158
|
-
export function isUngroupedClass(classObj: LogClass): boolean {
|
|
159
|
-
return classObj.id.startsWith(ungroupedPrefix);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function addToLogBlock(block: LogBlock, logClass: LogClass, log: LogRaw) {
|
|
163
|
-
let classSummary = block.classes[logClass.id];
|
|
164
|
-
let now = Date.now();
|
|
165
|
-
if (!classSummary) {
|
|
166
|
-
classSummary = createLogClassSummary(now, logClass);
|
|
167
|
-
block.classes[logClass.id] = classSummary;
|
|
168
|
-
}
|
|
169
|
-
let maxExamples = getMaxExamples(logClass);
|
|
170
|
-
if (classSummary.examples.length < maxExamples) {
|
|
171
|
-
if (log.message.length > MAX_EXAMPLE_LENGTH) {
|
|
172
|
-
log = { ...log };
|
|
173
|
-
log.message = log.message.slice(0, MAX_EXAMPLE_LENGTH - 3) + "...";
|
|
174
|
-
}
|
|
175
|
-
classSummary.examples.push(log);
|
|
176
|
-
}
|
|
177
|
-
classSummary.count++;
|
|
178
|
-
if (now > classSummary.lastTime) classSummary.lastTime = now;
|
|
179
|
-
if (now > block.endTime) block.endTime = now;
|
|
180
|
-
block.count++;
|
|
181
|
-
if (logClass.type === "fatal") block.fatalCount++;
|
|
182
|
-
if (logClass.type === "error") block.errorCount++;
|
|
183
|
-
if (logClass.type === "warn") block.warnCount++;
|
|
184
|
-
if (logClass.type === "info") block.infoCount++;
|
|
185
|
-
}
|
|
186
|
-
function createLogClassSummary(now: number, logClass: LogClass): LogClassSummary {
|
|
187
|
-
return {
|
|
188
|
-
count: 0,
|
|
189
|
-
logClassId: logClass.id,
|
|
190
|
-
examples: [],
|
|
191
|
-
firstTime: now,
|
|
192
|
-
lastTime: now,
|
|
193
|
-
logClassType: logClass.type,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export async function logBlockToBuffer(block: LogBlock): Promise<{
|
|
198
|
-
fileName: string;
|
|
199
|
-
buffer: Buffer;
|
|
200
|
-
}> {
|
|
201
|
-
let buffer = await Zip.gzip(Buffer.from(JSON.stringify(block)));
|
|
202
|
-
let info = getLogBlockInfoFromBlock(block);
|
|
203
|
-
let fileName = getLogBlockFileName(info);
|
|
204
|
-
return { fileName, buffer };
|
|
205
|
-
}
|
|
206
|
-
export async function logBlockFromBuffer(buffer: Buffer): Promise<LogBlock> {
|
|
207
|
-
buffer = await Zip.gunzip(buffer);
|
|
208
|
-
return JSON.parse(buffer.toString());
|
|
209
|
-
}
|
|
210
|
-
function getLogBlockInfoFromBlock(block: LogBlock): LogBlockInfo {
|
|
211
|
-
return {
|
|
212
|
-
startTime: block.startTime,
|
|
213
|
-
endTime: block.endTime,
|
|
214
|
-
seqNum: block.seqNum,
|
|
215
|
-
count: block.count,
|
|
216
|
-
fatalCount: block.fatalCount,
|
|
217
|
-
errorCount: block.errorCount,
|
|
218
|
-
warnCount: block.warnCount,
|
|
219
|
-
infoCount: block.infoCount,
|
|
220
|
-
threadId: block.threadId,
|
|
221
|
-
machineId: block.machineId,
|
|
222
|
-
fileName: [
|
|
223
|
-
"count " + block.count,
|
|
224
|
-
"seq " + block.seqNum,
|
|
225
|
-
"fatal " + block.fatalCount,
|
|
226
|
-
"errors " + block.errorCount,
|
|
227
|
-
"warns " + block.warnCount,
|
|
228
|
-
"infos " + block.infoCount,
|
|
229
|
-
"thread " + block.threadId,
|
|
230
|
-
"machine " + block.machineId,
|
|
231
|
-
block.startTime + " to " + block.endTime,
|
|
232
|
-
LOG_BLOCK_EXTENSION
|
|
233
|
-
].join(" "),
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
export function getLogBlockFileName(info: LogBlockInfo): string {
|
|
237
|
-
return info.fileName;
|
|
238
|
-
}
|
|
239
|
-
export function getLogBlockInfo(fileName: string): LogBlockInfo {
|
|
240
|
-
let parts = fileName.split(" ");
|
|
241
|
-
return {
|
|
242
|
-
count: Number(parts[0].split(" ")[1]),
|
|
243
|
-
seqNum: Number(parts[1].split(" ")[1]),
|
|
244
|
-
fatalCount: Number(parts[2].split(" ")[1]),
|
|
245
|
-
errorCount: Number(parts[3].split(" ")[1]),
|
|
246
|
-
warnCount: Number(parts[4].split(" ")[1]),
|
|
247
|
-
infoCount: Number(parts[5].split(" ")[1]),
|
|
248
|
-
threadId: parts[6].split(" ")[1],
|
|
249
|
-
machineId: parts[7].split(" ")[1],
|
|
250
|
-
startTime: Number(parts[8].split(" to ")[0]),
|
|
251
|
-
endTime: parseFloat(parts[8].split(" to ")[1]),
|
|
252
|
-
fileName,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function logClassesToString(logClasses: LogClass[]): string {
|
|
257
|
-
return lineYamlToString(logClasses.filter(x => !isUngroupedClass(x)));
|
|
258
|
-
}
|
|
259
|
-
export function logClassesFromString(file: string): LogClass[] {
|
|
260
|
-
let result = lineYamlFromString(file) as LogClass[];
|
|
261
|
-
for (let key in allTypes) {
|
|
262
|
-
result.push(ungroupedClass(key as LogType));
|
|
263
|
-
}
|
|
264
|
-
return result;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const LINE_DELIMITTER = "\n=============================\n";
|
|
268
|
-
export function lineYamlToString(values: unknown[]): string {
|
|
269
|
-
return values.map(value => yaml.stringify(value)).join(LINE_DELIMITTER);
|
|
270
|
-
}
|
|
271
|
-
export function lineYamlFromString<T>(file: string): T[] {
|
|
272
|
-
if (!file.trim()) return [];
|
|
273
|
-
return file.split(LINE_DELIMITTER).map(value => yaml.parse(value));
|
|
274
|
-
}
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import preact from "preact"; import { qreact } from "../../../src/4-dom/qreact";
|
|
2
|
-
import { LogViewerController, LogViewerSynced, formatTypeColor } from "./LogViewer";
|
|
3
|
-
import { getBrowserUrlNode } from "../../../src/-f-node-discovery/NodeDiscovery";
|
|
4
|
-
import { css } from "../../../src/4-dom/css";
|
|
5
|
-
import { LogClass, LogType, getMaxExamples, isUngroupedClass } from "../../../src/diagnostics/errorLogs/ErrorLogCore";
|
|
6
|
-
import { Table } from "../../../src/5-diagnostics/Table";
|
|
7
|
-
import { deepCloneJSON, nextId, sort, timeInDay } from "socket-function/src/misc";
|
|
8
|
-
import { FormatContext, JSXFormatter, formatValue, toSpaceCase } from "../../../src/5-diagnostics/GenericFormat";
|
|
9
|
-
import { Input } from "../../../src/library-components/Input";
|
|
10
|
-
import { InputLabel } from "../../../src/library-components/InputLabel";
|
|
11
|
-
import { fatalColor, errorColor, warnColor, infoColor } from "./logFiltering";
|
|
12
|
-
import { Button } from "../../../src/library-components/Button";
|
|
13
|
-
import { formatDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
14
|
-
import { errorMessage } from "../../../src/library-components/colors";
|
|
15
|
-
import { DropdownCustom } from "../../../src/library-components/DropdownCustom";
|
|
16
|
-
import { createURLSync } from "../../../src/library-components/URLParam";
|
|
17
|
-
import { Querysub } from "../../../src/4-querysub/Querysub";
|
|
18
|
-
|
|
19
|
-
export const logClassIdURL = createURLSync("id", "");
|
|
20
|
-
|
|
21
|
-
export class LogClassifiers extends qreact.Component {
|
|
22
|
-
state = {
|
|
23
|
-
add: ""
|
|
24
|
-
};
|
|
25
|
-
render() {
|
|
26
|
-
let controller = LogViewerSynced(getBrowserUrlNode());
|
|
27
|
-
let classes = controller.getClasses() || [];
|
|
28
|
-
classes = classes.filter(x => !isUngroupedClass(x));
|
|
29
|
-
sort(classes, x => -x.createTime || Number.MAX_SAFE_INTEGER);
|
|
30
|
-
|
|
31
|
-
let specificId = logClassIdURL.value;
|
|
32
|
-
if (specificId) {
|
|
33
|
-
classes = classes.filter(x => x.id === specificId);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let editFormatter: JSXFormatter<unknown, LogClass> = (value, context) => {
|
|
37
|
-
const columnName = context?.columnName;
|
|
38
|
-
if (!columnName) throw new Error(`Column name not provided to formatter.`);
|
|
39
|
-
const row = context?.row;
|
|
40
|
-
if (!row) throw new Error(`Row not provided to formatter.`);
|
|
41
|
-
|
|
42
|
-
let valueFormatted: preact.ComponentChild = formatValue(value);
|
|
43
|
-
if (columnName === "type") {
|
|
44
|
-
valueFormatted = formatTypeColor(value as LogType, String(value));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const updateValue = async function updateValue(newValue: unknown) {
|
|
48
|
-
let updateController = LogViewerController.nodes[getBrowserUrlNode()];
|
|
49
|
-
let newClass: LogClass = { ...row };
|
|
50
|
-
newClass[columnName] = newValue as never;
|
|
51
|
-
await updateController.updateClass(newClass);
|
|
52
|
-
controller.resetAll();
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (columnName === "type") {
|
|
56
|
-
return <DropdownCustom
|
|
57
|
-
optionClass={
|
|
58
|
-
css.backgroundColor("hsla(0, 0%, 100%, 0.5)", "important")
|
|
59
|
-
.padding("2px 6px", "important")
|
|
60
|
-
}
|
|
61
|
-
value={value}
|
|
62
|
-
options={[
|
|
63
|
-
{ label: () => formatTypeColor("fatal", "Fatal"), value: "fatal" },
|
|
64
|
-
{ label: () => formatTypeColor("error", "Error"), value: "error" },
|
|
65
|
-
{ label: () => formatTypeColor("warn", "Warn"), value: "warn" },
|
|
66
|
-
{ label: () => formatTypeColor("info", "Info"), value: "info" },
|
|
67
|
-
]}
|
|
68
|
-
onChange={(newValue) => updateValue(newValue)}
|
|
69
|
-
/>;
|
|
70
|
-
}
|
|
71
|
-
if (columnName === "match") {
|
|
72
|
-
let valueT = value as LogClass["match"];
|
|
73
|
-
valueT = deepCloneJSON(valueT);
|
|
74
|
-
return (
|
|
75
|
-
valueT.map((match, index) => {
|
|
76
|
-
let matchType = match.type;
|
|
77
|
-
if (matchType === "includes") {
|
|
78
|
-
return <Input
|
|
79
|
-
value={match.value}
|
|
80
|
-
onChangeValue={async newValue => {
|
|
81
|
-
let newMatch = { ...match, value: newValue };
|
|
82
|
-
let newMatches = [...valueT];
|
|
83
|
-
newMatches[index] = newMatch;
|
|
84
|
-
await updateValue(newMatches);
|
|
85
|
-
}}
|
|
86
|
-
/>;
|
|
87
|
-
}
|
|
88
|
-
let unhandledType = matchType;
|
|
89
|
-
return JSON.stringify(match);
|
|
90
|
-
})
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (columnName === "maxExamples" && !valueFormatted) {
|
|
95
|
-
value = getMaxExamples(row);
|
|
96
|
-
valueFormatted = String(value);
|
|
97
|
-
}
|
|
98
|
-
if (columnName === "priority") {
|
|
99
|
-
value = value ?? 0;
|
|
100
|
-
}
|
|
101
|
-
if (columnName === "createTime") {
|
|
102
|
-
return formatValue(Number(value) || "");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (columnName === "suppressUntil") {
|
|
106
|
-
return <SuppressUntil
|
|
107
|
-
type={row.type}
|
|
108
|
-
time={row.suppressUntil}
|
|
109
|
-
updateClass={async (newFields) => {
|
|
110
|
-
let updateController = LogViewerController.nodes[getBrowserUrlNode()];
|
|
111
|
-
let newClass: LogClass = { ...row, ...newFields };
|
|
112
|
-
await updateController.updateClass(newClass);
|
|
113
|
-
controller.resetAll();
|
|
114
|
-
}}
|
|
115
|
-
/>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let edit = true;
|
|
120
|
-
let isNumber = false;
|
|
121
|
-
let isTextArea = false;
|
|
122
|
-
let isDatetime = false;
|
|
123
|
-
if (typeof value === "number") {
|
|
124
|
-
isNumber = true;
|
|
125
|
-
}
|
|
126
|
-
if (columnName === "description") {
|
|
127
|
-
isTextArea = true;
|
|
128
|
-
}
|
|
129
|
-
return (
|
|
130
|
-
<InputLabel
|
|
131
|
-
edit={edit}
|
|
132
|
-
editValue={valueFormatted}
|
|
133
|
-
textarea={isTextArea}
|
|
134
|
-
isDatetime={isDatetime}
|
|
135
|
-
value={String(value)}
|
|
136
|
-
number={isNumber}
|
|
137
|
-
onChangeValue={async newValue => {
|
|
138
|
-
if (isNumber) {
|
|
139
|
-
newValue = Number(newValue) as any;
|
|
140
|
-
}
|
|
141
|
-
await updateValue(newValue);
|
|
142
|
-
}}
|
|
143
|
-
/>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const createClassifier = async () => {
|
|
148
|
-
let previewFilter = this.state.add;
|
|
149
|
-
// Wait for the blur to finish before resetting previewFilter
|
|
150
|
-
Querysub.onCommitFinished(() => {
|
|
151
|
-
setImmediate(() => {
|
|
152
|
-
Querysub.localCommit(() => {
|
|
153
|
-
this.state.add = "";
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
await LogViewerController.nodes[getBrowserUrlNode()].updateClass({
|
|
158
|
-
id: nextId(),
|
|
159
|
-
title: toSpaceCase(previewFilter),
|
|
160
|
-
createTime: Date.now(),
|
|
161
|
-
description: previewFilter,
|
|
162
|
-
match: [{ type: "includes", value: previewFilter }],
|
|
163
|
-
suppressUntil: 0,
|
|
164
|
-
type: "error",
|
|
165
|
-
});
|
|
166
|
-
controller.resetAll();
|
|
167
|
-
};
|
|
168
|
-
return (
|
|
169
|
-
<div class={css.pad2(10).vbox(10).fillWidth}>
|
|
170
|
-
{specificId &&
|
|
171
|
-
<div class={css.hbox(4).fontSize(20)}>
|
|
172
|
-
Viewing single classifer,
|
|
173
|
-
<Button flavor="large" onClick={() => logClassIdURL.value = ""}>
|
|
174
|
-
Reset to all classifiers
|
|
175
|
-
</Button>
|
|
176
|
-
</div>
|
|
177
|
-
}
|
|
178
|
-
<div class={css.hbox(4)}>
|
|
179
|
-
<InputLabel
|
|
180
|
-
label="Create Classifier"
|
|
181
|
-
value={this.state.add}
|
|
182
|
-
hot
|
|
183
|
-
onChangeValue={(value) => (this.state.add = value)}
|
|
184
|
-
onKeyDown={(e) => e.code === "Enter" && createClassifier()}
|
|
185
|
-
/>
|
|
186
|
-
{this.state.add && (
|
|
187
|
-
<Button class={css.hbox(6)} onClick={() => createClassifier()}>
|
|
188
|
-
<span>Create Classifier</span>
|
|
189
|
-
</Button>
|
|
190
|
-
)}
|
|
191
|
-
</div>
|
|
192
|
-
<Table
|
|
193
|
-
columns={{
|
|
194
|
-
title: { formatter: editFormatter },
|
|
195
|
-
match: { formatter: editFormatter },
|
|
196
|
-
priority: { formatter: editFormatter },
|
|
197
|
-
suppressUntil: { formatter: editFormatter },
|
|
198
|
-
maxExamples: { formatter: editFormatter },
|
|
199
|
-
type: { formatter: editFormatter },
|
|
200
|
-
createTime: { formatter: editFormatter },
|
|
201
|
-
description: { formatter: editFormatter },
|
|
202
|
-
["actions" as any]: {
|
|
203
|
-
formatter: (value: unknown, context: FormatContext) => {
|
|
204
|
-
return (
|
|
205
|
-
<Button class={errorMessage} onClick={async () => {
|
|
206
|
-
let updateController = LogViewerController.nodes[getBrowserUrlNode()];
|
|
207
|
-
await updateController.updateClass({ id: context?.row?.id as any, action: "delete" });
|
|
208
|
-
controller.resetAll();
|
|
209
|
-
}}>
|
|
210
|
-
Delete
|
|
211
|
-
</Button>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
} as any
|
|
215
|
-
}}
|
|
216
|
-
rows={classes || []}
|
|
217
|
-
/>
|
|
218
|
-
</div>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export class SuppressUntil extends qreact.Component<{
|
|
224
|
-
type: LogType;
|
|
225
|
-
time: number;
|
|
226
|
-
updateClass: (newFields: Partial<LogClass>) => void;
|
|
227
|
-
}> {
|
|
228
|
-
state = {
|
|
229
|
-
editting: false
|
|
230
|
-
};
|
|
231
|
-
render() {
|
|
232
|
-
let { time, type, updateClass } = this.props;
|
|
233
|
-
let label = "";
|
|
234
|
-
let now = Date.now();
|
|
235
|
-
time = time || 0;
|
|
236
|
-
if (!time) {
|
|
237
|
-
label = "Will fix in";
|
|
238
|
-
} else if (time < now) {
|
|
239
|
-
label = `Should have been fixed ${formatTime(now - time)} AGO`;
|
|
240
|
-
} else {
|
|
241
|
-
label = `Will fix within ${formatTime(time - now)}`;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
let changeLabel = "";
|
|
245
|
-
let changeTime = 0;
|
|
246
|
-
if (type === "fatal" || type === "error") {
|
|
247
|
-
changeLabel = "a day";
|
|
248
|
-
changeTime = now + timeInDay;
|
|
249
|
-
} else if (type === "warn" || type === "info") {
|
|
250
|
-
changeLabel = "a week";
|
|
251
|
-
changeTime = now + timeInDay * 7;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return (
|
|
255
|
-
<div class={css.hbox(8)}>
|
|
256
|
-
<div
|
|
257
|
-
class={css
|
|
258
|
-
.hbox(4)
|
|
259
|
-
.pad2(4, 2)
|
|
260
|
-
.wrap
|
|
261
|
-
.button
|
|
262
|
-
.hsla(0, 0, 100, 0.3)
|
|
263
|
-
.background("hsla(0, 0%, 100%, 0.6)", "hover")
|
|
264
|
-
}
|
|
265
|
-
onClick={(e) => {
|
|
266
|
-
// If not clicking on input
|
|
267
|
-
if ((e.target as HTMLElement).tagName === "INPUT") return;
|
|
268
|
-
this.state.editting = !this.state.editting;
|
|
269
|
-
}}
|
|
270
|
-
>
|
|
271
|
-
<span>{label}</span>
|
|
272
|
-
{this.state.editting && (
|
|
273
|
-
<>
|
|
274
|
-
<Button onClick={() => updateClass({ suppressUntil: changeTime })}>{changeLabel}</Button>
|
|
275
|
-
<InputLabel
|
|
276
|
-
class={css.hsl(0, 0, 100).hslcolor(0, 0, 7)}
|
|
277
|
-
value={time}
|
|
278
|
-
isDatetime
|
|
279
|
-
onChangeValue={(newValue) => {
|
|
280
|
-
updateClass({ suppressUntil: Number(newValue) });
|
|
281
|
-
this.state.editting = false;
|
|
282
|
-
}}
|
|
283
|
-
/>
|
|
284
|
-
</>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
{type !== "info" && <Button
|
|
288
|
-
onClick={() => updateClass({ suppressUntil: changeTime })}
|
|
289
|
-
class={css.hsl(infoColor.h, infoColor.s, infoColor.l)}
|
|
290
|
-
>
|
|
291
|
-
Fixed
|
|
292
|
-
</Button>}
|
|
293
|
-
{type !== "info" && <Button
|
|
294
|
-
onClick={() => updateClass({ type: "info" })}
|
|
295
|
-
class={css.hsl(infoColor.h, infoColor.s, infoColor.l)}
|
|
296
|
-
>
|
|
297
|
-
Not a Bug
|
|
298
|
-
</Button>}
|
|
299
|
-
{type === "info" && <Button
|
|
300
|
-
onClick={() => updateClass({ type: "error" })}
|
|
301
|
-
class={css.hsl(errorColor.h, errorColor.s, errorColor.l)}
|
|
302
|
-
>
|
|
303
|
-
Is a Bug
|
|
304
|
-
</Button>}
|
|
305
|
-
</div>
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import preact from "preact"; import { qreact } from "../../../src/4-dom/qreact";
|
|
2
|
-
import { getBrowserUrlNode } from "../../../src/-f-node-discovery/NodeDiscovery";
|
|
3
|
-
import { InputLabelURL } from "../../../src/library-components/InputLabel";
|
|
4
|
-
import { URLParam } from "../../../src/library-components/URLParam";
|
|
5
|
-
import { getPathFromStr, getPathStr } from "../../../src/path";
|
|
6
|
-
import { css } from "typesafecss";
|
|
7
|
-
import { InputPicker } from "../../../src/library-components/InputPicker";
|
|
8
|
-
import { LogViewerSynced } from "./LogViewer";
|
|
9
|
-
import { showOldestLogsFirstURL, showLiveLogsURL, filterToClassesURL, filterMachineIdsURL, filterThreadIdsURL, getPathArrayLax } from "./logFiltering";
|
|
10
|
-
|
|
11
|
-
export class LogFilterUI extends qreact.Component<{
|
|
12
|
-
allMachineIds: Set<string> | null;
|
|
13
|
-
allThreadIds: Set<string> | null;
|
|
14
|
-
}> {
|
|
15
|
-
render() {
|
|
16
|
-
let classList = LogViewerSynced(getBrowserUrlNode()).getClasses();
|
|
17
|
-
function strPicker(
|
|
18
|
-
label: preact.ComponentChild,
|
|
19
|
-
url: URLParam<string>,
|
|
20
|
-
options: {
|
|
21
|
-
value: string;
|
|
22
|
-
matchText?: string;
|
|
23
|
-
label?: preact.ComponentChild;
|
|
24
|
-
}[]
|
|
25
|
-
) {
|
|
26
|
-
return <InputPicker
|
|
27
|
-
label={label}
|
|
28
|
-
allowNonOptions
|
|
29
|
-
picked={getPathArrayLax(url.value)}
|
|
30
|
-
options={options}
|
|
31
|
-
addPicked={(value) => {
|
|
32
|
-
let picked = getPathArrayLax(url.value);
|
|
33
|
-
picked.push(value);
|
|
34
|
-
url.value = getPathStr(picked);
|
|
35
|
-
}}
|
|
36
|
-
removePicked={(value) => {
|
|
37
|
-
let picked = getPathArrayLax(url.value);
|
|
38
|
-
let index = picked.indexOf(value);
|
|
39
|
-
if (index !== -1) {
|
|
40
|
-
picked.splice(index, 1);
|
|
41
|
-
if (picked.length === 0) {
|
|
42
|
-
url.value = "";
|
|
43
|
-
} else {
|
|
44
|
-
url.value = getPathStr(picked);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}}
|
|
48
|
-
/>;
|
|
49
|
-
}
|
|
50
|
-
return (
|
|
51
|
-
<div class={css.vbox(10).flexExpand.wrap.alignItems("start")}>
|
|
52
|
-
<div class={css.hbox(10).alignItems("start")}>
|
|
53
|
-
<InputLabelURL
|
|
54
|
-
label="Oldest Logs First"
|
|
55
|
-
checkbox
|
|
56
|
-
url={showOldestLogsFirstURL}
|
|
57
|
-
/>
|
|
58
|
-
<InputLabelURL
|
|
59
|
-
label="Show Live Logs"
|
|
60
|
-
checkbox
|
|
61
|
-
url={showLiveLogsURL}
|
|
62
|
-
/>
|
|
63
|
-
</div>
|
|
64
|
-
<div class={css.hbox(10).alignItems("start")}>
|
|
65
|
-
{strPicker(
|
|
66
|
-
"Filter Classes",
|
|
67
|
-
filterToClassesURL,
|
|
68
|
-
classList?.map(x => ({ value: x.id, label: `${x.title} (${x.type})` })) || []
|
|
69
|
-
)}
|
|
70
|
-
{strPicker(
|
|
71
|
-
"Filter Machines",
|
|
72
|
-
filterMachineIdsURL,
|
|
73
|
-
Array.from(this.props.allMachineIds || []).map(x => ({ value: x })) || []
|
|
74
|
-
)}
|
|
75
|
-
{strPicker(
|
|
76
|
-
"Filter Threads",
|
|
77
|
-
filterThreadIdsURL,
|
|
78
|
-
Array.from(this.props.allThreadIds || []).map(x => ({ value: x })) || []
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
}
|