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.
Files changed (69) hide show
  1. package/.cursorrules +1 -1
  2. package/costsBenefits.txt +4 -1
  3. package/package.json +3 -2
  4. package/spec.txt +23 -18
  5. package/src/-0-hooks/hooks.ts +1 -1
  6. package/src/-a-archives/archives.ts +16 -3
  7. package/src/-a-archives/archivesBackBlaze.ts +51 -3
  8. package/src/-a-archives/archivesLimitedCache.ts +175 -0
  9. package/src/-a-archives/archivesPrivateFileSystem.ts +299 -0
  10. package/src/-a-auth/certs.ts +58 -31
  11. package/src/-b-authorities/cdnAuthority.ts +2 -2
  12. package/src/-b-authorities/dnsAuthority.ts +3 -2
  13. package/src/-c-identity/IdentityController.ts +3 -2
  14. package/src/-d-trust/NetworkTrust2.ts +17 -19
  15. package/src/-e-certs/EdgeCertController.ts +3 -4
  16. package/src/-e-certs/certAuthority.ts +1 -2
  17. package/src/-f-node-discovery/NodeDiscovery.ts +9 -7
  18. package/src/-g-core-values/NodeCapabilities.ts +6 -1
  19. package/src/0-path-value-core/NodePathAuthorities.ts +1 -1
  20. package/src/0-path-value-core/PathValueCommitter.ts +3 -3
  21. package/src/0-path-value-core/PathValueController.ts +3 -3
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +15 -37
  23. package/src/0-path-value-core/pathValueCore.ts +4 -3
  24. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  25. package/src/4-dom/qreact.tsx +4 -3
  26. package/src/4-querysub/Querysub.ts +2 -2
  27. package/src/4-querysub/QuerysubController.ts +2 -2
  28. package/src/5-diagnostics/GenericFormat.tsx +1 -0
  29. package/src/5-diagnostics/Table.tsx +3 -0
  30. package/src/5-diagnostics/diskValueAudit.ts +2 -1
  31. package/src/5-diagnostics/nodeMetadata.ts +0 -1
  32. package/src/deployManager/components/MachineDetailPage.tsx +9 -1
  33. package/src/deployManager/components/ServiceDetailPage.tsx +10 -1
  34. package/src/diagnostics/NodeViewer.tsx +3 -4
  35. package/src/diagnostics/logs/FastArchiveAppendable.ts +748 -0
  36. package/src/diagnostics/logs/FastArchiveController.ts +524 -0
  37. package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
  38. package/src/diagnostics/logs/LogViewer2.tsx +349 -0
  39. package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
  40. package/src/diagnostics/logs/diskLogger.ts +135 -305
  41. package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
  42. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
  43. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
  44. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
  45. package/src/diagnostics/logs/importLogsEntry.ts +38 -0
  46. package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
  47. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
  48. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +151 -0
  49. package/src/diagnostics/managementPages.tsx +7 -16
  50. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
  51. package/src/diagnostics/periodic.ts +5 -0
  52. package/src/diagnostics/watchdog.ts +2 -2
  53. package/src/functional/SocketChannel.ts +67 -0
  54. package/src/library-components/Input.tsx +1 -1
  55. package/src/library-components/InputLabel.tsx +5 -2
  56. package/src/misc.ts +111 -0
  57. package/src/src.d.ts +34 -1
  58. package/src/user-implementation/userData.ts +4 -3
  59. package/test.ts +13 -0
  60. package/testEntry2.ts +29 -0
  61. package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
  62. package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
  63. package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
  64. package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
  65. package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
  66. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
  67. package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
  68. package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
  69. 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
- }