querysub 0.377.0 → 0.379.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 +2 -0
- package/bin/error-watch-public.js +7 -0
- package/bin/error-watch.js +6 -0
- package/package.json +7 -4
- package/src/-f-node-discovery/NodeDiscovery.ts +7 -0
- package/src/-g-core-values/NodeCapabilities.ts +28 -14
- package/src/3-path-functions/PathFunctionRunnerMain.ts +0 -4
- package/src/diagnostics/MachineThreadInfo.tsx +33 -10
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +24 -1
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +6 -2
- package/src/diagnostics/logs/diskLogger.ts +26 -27
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +4 -0
- package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +505 -0
- package/src/diagnostics/logs/errorNotifications2/ErrorWarning.tsx +32 -0
- package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +629 -0
- package/src/diagnostics/logs/errorNotifications2/errorWatchEntry.ts +13 -0
- package/src/diagnostics/logs/errorNotifications2/errorWatcher.ts +168 -0
- package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +104 -0
- package/src/diagnostics/logs/errorNotifications2/openRouterHelper.ts +77 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +17 -19
- package/src/diagnostics/managementPages.tsx +12 -2
- package/src/server.ts +0 -8
- package/src/user-implementation/SecurityPage.tsx +6 -0
- package/src/user-implementation/userData.ts +6 -1
- package/test.ts +16 -6
- package/test2.ts +20 -0
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -9
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -0
- /package/src/library-components/{errorNotifications.tsx → uncaughtToast.tsx} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { lazy } from "socket-function/src/caching";
|
|
3
|
+
import { nextId } from "socket-function/src/misc";
|
|
4
|
+
import { t } from "../../../2-proxy/schema2";
|
|
5
|
+
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
6
|
+
import { LogDatum } from "../diskLogger";
|
|
7
|
+
import { SuppressionMatch, watchUnmatchedErrors, ErrorNotificationsController } from "./errorNotifications";
|
|
8
|
+
|
|
9
|
+
let errorNotificationsLocalSchema = Querysub.createLocalSchema("errorNotifications", {
|
|
10
|
+
unmatchedErrors: t.atomic<LogDatum[]>([]),
|
|
11
|
+
suppressionMatches: t.atomic<Map<string, SuppressionMatch>>(new Map()),
|
|
12
|
+
suppressionEntries: t.atomic<Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
notes?: string;
|
|
15
|
+
pattern: string;
|
|
16
|
+
timeout: number;
|
|
17
|
+
createdTime: number;
|
|
18
|
+
lastUpdatedTime: number;
|
|
19
|
+
}>>([]),
|
|
20
|
+
isLoading: t.atomic<boolean>(true),
|
|
21
|
+
initError: t.atomic<string | undefined>(undefined),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export let getErrorNotificationsManager = lazy(() => {
|
|
25
|
+
let state = errorNotificationsLocalSchema();
|
|
26
|
+
|
|
27
|
+
void (async () => {
|
|
28
|
+
try {
|
|
29
|
+
await watchUnmatchedErrors({
|
|
30
|
+
errorCallback: (datums: LogDatum[]) => {
|
|
31
|
+
Querysub.commit(() => {
|
|
32
|
+
state.unmatchedErrors = [...state.unmatchedErrors, ...datums];
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
suppressionCallback: (matches) => {
|
|
36
|
+
Querysub.commit(() => {
|
|
37
|
+
let newMatches = new Map(state.suppressionMatches);
|
|
38
|
+
let hasNewSuppression = false;
|
|
39
|
+
for (let match of matches) {
|
|
40
|
+
newMatches.set(match.id, match);
|
|
41
|
+
if (!state.suppressionEntries.find(e => e.id === match.id)) {
|
|
42
|
+
hasNewSuppression = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
state.suppressionMatches = newMatches;
|
|
46
|
+
if (hasNewSuppression) {
|
|
47
|
+
void reloadData();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
54
|
+
let data = await controller.getData.promise();
|
|
55
|
+
let suppressionEntries = await controller.getSuppressionEntries.promise();
|
|
56
|
+
|
|
57
|
+
Querysub.commit(() => {
|
|
58
|
+
state.unmatchedErrors = data.unmatchedErrors;
|
|
59
|
+
state.suppressionMatches = data.suppressionMatches;
|
|
60
|
+
state.suppressionEntries = suppressionEntries;
|
|
61
|
+
state.initError = undefined;
|
|
62
|
+
state.isLoading = false;
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
Querysub.commit(() => {
|
|
66
|
+
state.initError = error instanceof Error && error.message || String(error);
|
|
67
|
+
state.isLoading = false;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
async function reloadData() {
|
|
73
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
74
|
+
let data = await controller.getData.promise();
|
|
75
|
+
let suppressionEntries = await controller.getSuppressionEntries.promise();
|
|
76
|
+
Querysub.commit(() => {
|
|
77
|
+
state.unmatchedErrors = data.unmatchedErrors;
|
|
78
|
+
state.suppressionMatches = data.suppressionMatches;
|
|
79
|
+
state.suppressionEntries = suppressionEntries;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
state,
|
|
85
|
+
async addSuppression(pattern: string, timeout: number) {
|
|
86
|
+
if (!pattern) {
|
|
87
|
+
throw new Error("Pattern is required");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let now = Date.now();
|
|
91
|
+
let newEntry = {
|
|
92
|
+
id: nextId(),
|
|
93
|
+
pattern,
|
|
94
|
+
timeout,
|
|
95
|
+
createdTime: now,
|
|
96
|
+
lastUpdatedTime: now,
|
|
97
|
+
};
|
|
98
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
99
|
+
await controller.setSuppressionEntry.promise(newEntry);
|
|
100
|
+
await reloadData();
|
|
101
|
+
},
|
|
102
|
+
async updateSuppression(id: string, pattern: string) {
|
|
103
|
+
if (!pattern) {
|
|
104
|
+
throw new Error("Pattern is required");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const existingEntry = Querysub.fastRead(() => state.suppressionEntries.find((s: { id: string; }) => s.id === id));
|
|
108
|
+
if (!existingEntry) {
|
|
109
|
+
throw new Error(`Suppression with id ${id} not found`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let updatedEntry = {
|
|
113
|
+
id: existingEntry.id,
|
|
114
|
+
notes: existingEntry.notes,
|
|
115
|
+
pattern,
|
|
116
|
+
timeout: existingEntry.timeout,
|
|
117
|
+
createdTime: existingEntry.createdTime,
|
|
118
|
+
lastUpdatedTime: Date.now(),
|
|
119
|
+
};
|
|
120
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
121
|
+
await controller.setSuppressionEntry.promise(updatedEntry);
|
|
122
|
+
await reloadData();
|
|
123
|
+
},
|
|
124
|
+
async updateNotes(id: string, notes: string | undefined) {
|
|
125
|
+
const existingEntry = Querysub.fastRead(() => state.suppressionEntries.find((s: { id: string; }) => s.id === id));
|
|
126
|
+
if (!existingEntry) {
|
|
127
|
+
throw new Error(`Suppression with id ${id} not found`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Querysub.commit(() => {
|
|
131
|
+
const idx = state.suppressionEntries.findIndex((s: { id: string; }) => s.id === id);
|
|
132
|
+
if (idx !== -1) {
|
|
133
|
+
let updated = [...state.suppressionEntries];
|
|
134
|
+
updated[idx] = { ...updated[idx], notes, lastUpdatedTime: Date.now() };
|
|
135
|
+
state.suppressionEntries = updated;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
140
|
+
await controller.updateSuppressionNotes.promise(id, notes);
|
|
141
|
+
},
|
|
142
|
+
async updateTimeout(id: string, timeout: number) {
|
|
143
|
+
const existingEntry = Querysub.fastRead(() => state.suppressionEntries.find((s: { id: string; }) => s.id === id));
|
|
144
|
+
if (!existingEntry) {
|
|
145
|
+
throw new Error(`Suppression with id ${id} not found`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let updatedEntry = {
|
|
149
|
+
...existingEntry,
|
|
150
|
+
timeout,
|
|
151
|
+
lastUpdatedTime: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
155
|
+
await controller.setSuppressionEntry.promise(updatedEntry);
|
|
156
|
+
await reloadData();
|
|
157
|
+
},
|
|
158
|
+
async deleteSuppression(id: string) {
|
|
159
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
160
|
+
await controller.deleteSuppressionEntry.promise(id);
|
|
161
|
+
await reloadData();
|
|
162
|
+
},
|
|
163
|
+
async logTestError(message: string) {
|
|
164
|
+
let controller = ErrorNotificationsController(SocketFunction.browserNodeId());
|
|
165
|
+
await controller.logTestError.promise(message);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { isNodeIdLocal, isOwnNodeId, watchDeltaNodeIds } from "../../../-f-node-discovery/NodeDiscovery";
|
|
3
|
+
import { IndexedLogs, loggerByName } from "../IndexedLogs/IndexedLogs";
|
|
4
|
+
import { nextId } from "socket-function/src/misc";
|
|
5
|
+
import { assertIsManagementUser, isManagementUser } from "../../managementPages";
|
|
6
|
+
import { ignoreErrors } from "../../../errors";
|
|
7
|
+
import { blue } from "socket-function/src/formatting/logColors";
|
|
8
|
+
import { SocketChannel } from "../../../functional/SocketChannel";
|
|
9
|
+
import { batchFunction } from "socket-function/src/batching";
|
|
10
|
+
import { isPublic } from "../../../config";
|
|
11
|
+
|
|
12
|
+
export async function* watchAllValues<T>(logs: IndexedLogs<T>): AsyncGenerator<T> {
|
|
13
|
+
let callbackId = nextId();
|
|
14
|
+
let buffer: T[] = [];
|
|
15
|
+
let resolveNext: ((value: T) => void) | undefined;
|
|
16
|
+
|
|
17
|
+
clientCallbacks.set(callbackId, (datums: unknown[]) => {
|
|
18
|
+
for (let i = 1; i < datums.length; i++) {
|
|
19
|
+
buffer.push(datums[i] as T);
|
|
20
|
+
}
|
|
21
|
+
let datum = datums[0] as T;
|
|
22
|
+
if (resolveNext) {
|
|
23
|
+
resolveNext(datum as T);
|
|
24
|
+
resolveNext = undefined;
|
|
25
|
+
} else {
|
|
26
|
+
buffer.push(datum as T);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
watchDeltaNodeIds((delta) => {
|
|
31
|
+
for (let nodeId of delta.newNodeIds) {
|
|
32
|
+
if (isOwnNodeId(nodeId)) continue;
|
|
33
|
+
let isNodeLocal = isNodeIdLocal(nodeId);
|
|
34
|
+
if (isNodeLocal !== isPublic()) continue;
|
|
35
|
+
ignoreErrors(ErrorNotificationControllerRegistered.nodes[nodeId].watchErrors({
|
|
36
|
+
loggerName: logs.config.name,
|
|
37
|
+
callbackId,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
while (true) {
|
|
44
|
+
if (buffer.length > 0) {
|
|
45
|
+
yield buffer.shift()!;
|
|
46
|
+
} else {
|
|
47
|
+
yield await new Promise<T>((resolve) => {
|
|
48
|
+
resolveNext = resolve;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} finally {
|
|
53
|
+
clientCallbacks.delete(callbackId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
let clientCallbacks = new Map<string, (datums: unknown[]) => void>();
|
|
60
|
+
|
|
61
|
+
class ErrorNotificationController {
|
|
62
|
+
public async onError(config: {
|
|
63
|
+
callbackId: string;
|
|
64
|
+
datums: unknown[];
|
|
65
|
+
}) {
|
|
66
|
+
let callback = clientCallbacks.get(config.callbackId);
|
|
67
|
+
if (!callback) {
|
|
68
|
+
throw new Error(`Callback ${config.callbackId} not found`);
|
|
69
|
+
}
|
|
70
|
+
callback(config.datums);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public async watchErrors(config: {
|
|
74
|
+
loggerName: string;
|
|
75
|
+
callbackId: string;
|
|
76
|
+
}): Promise<void> {
|
|
77
|
+
let caller = SocketFunction.getCaller();
|
|
78
|
+
let logger = loggerByName.get(config.loggerName);
|
|
79
|
+
if (!logger) {
|
|
80
|
+
throw new Error(`Logger ${config.loggerName} not found`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let unsubscribe = logger.watchErrors(batchFunction({ delay: 100 }, (datums: unknown[]) => {
|
|
84
|
+
ErrorNotificationControllerRegistered.nodes[caller.nodeId].onError({
|
|
85
|
+
callbackId: config.callbackId,
|
|
86
|
+
datums,
|
|
87
|
+
}).catch(() => {
|
|
88
|
+
unsubscribe();
|
|
89
|
+
});
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const ErrorNotificationControllerRegistered = SocketFunction.register(
|
|
95
|
+
"ErrorNotificationController-019c9cae-8333-7708-a4e7-500f5fc23173",
|
|
96
|
+
new ErrorNotificationController(),
|
|
97
|
+
() => ({
|
|
98
|
+
onError: {},
|
|
99
|
+
watchErrors: {}
|
|
100
|
+
}),
|
|
101
|
+
() => ({
|
|
102
|
+
hooks: [assertIsManagementUser],
|
|
103
|
+
})
|
|
104
|
+
);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export async function callOpenRouter(apiKey: string, prompt: string): Promise<string> {
|
|
2
|
+
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
3
|
+
method: "POST",
|
|
4
|
+
headers: {
|
|
5
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
6
|
+
"Content-Type": "application/json",
|
|
7
|
+
},
|
|
8
|
+
body: JSON.stringify({
|
|
9
|
+
model: "google/gemini-3-flash-preview",
|
|
10
|
+
messages: [
|
|
11
|
+
{
|
|
12
|
+
role: "user",
|
|
13
|
+
content: prompt,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
const errorText = await response.text();
|
|
21
|
+
throw new Error(`OpenRouter API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
|
|
26
|
+
if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
|
|
27
|
+
throw new Error(`Unexpected OpenRouter API response format: ${JSON.stringify(data)}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return data.choices[0].message.content.trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function callOpenRouterJSON<T>(apiKey: string, prompt: string): Promise<T> {
|
|
34
|
+
const maxRetries = 3;
|
|
35
|
+
let lastError: Error | undefined;
|
|
36
|
+
|
|
37
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: {
|
|
42
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
model: "google/gemini-3-flash-preview",
|
|
47
|
+
messages: [
|
|
48
|
+
{
|
|
49
|
+
role: "user",
|
|
50
|
+
content: prompt,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
response_format: { type: "json_object" },
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const errorText = await response.text();
|
|
59
|
+
throw new Error(`OpenRouter API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
|
|
64
|
+
if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
|
|
65
|
+
throw new Error(`Unexpected OpenRouter API response format: ${JSON.stringify(data)}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const content = data.choices[0].message.content.trim();
|
|
69
|
+
const parsed = JSON.parse(content) as T;
|
|
70
|
+
return parsed;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error(`Failed after ${maxRetries} attempts: ${lastError?.message}`);
|
|
77
|
+
}
|
|
@@ -20,34 +20,22 @@ IMPORTANT! Now I am properly calling shutdown, so none of the streamed logs shou
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
//todonext
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
5) Deploy watcher service to remote (yarn error-watch-public)
|
|
26
|
+
6) Deploy CYOA
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
- Try to get a rare value in that is in the logs, but is hard to find
|
|
29
|
-
Good rare search. Checks for sending emails, which we do very rarely. If we pack in 10GB of logs after this, it will really stress our indexing system.
|
|
30
|
-
- At the moment our pending index has a good hit rate (0 false positives out of 1720). We'll see if this continues for our remote index.
|
|
31
|
-
https://127-0-0-1.querysubtest.com:7007/?hot&readPublicLogs&showingmanagement&machineview=services&managementpage=LogViewer3&searchText=Sending%20email&selectedFields=%7B%22path%22%3Atrue%7D
|
|
32
|
-
|
|
28
|
+
5) Test on actual server, with actual logs
|
|
33
29
|
|
|
34
30
|
|
|
35
|
-
Rewrite error notification code
|
|
36
|
-
- Single service, which talks to all machines
|
|
37
|
-
- We'll have a dev mode, mostly for testing, although we won't usually run it.
|
|
38
|
-
- BROADCASTS messages to all http servers so they show them
|
|
39
|
-
- Broadcast messages only has one type now "error notification", but add TODO for more types, which may display in different ways (via toast, etc)
|
|
40
|
-
- Maybe only streaming? We'll miss some, but... not that much...
|
|
41
|
-
- ALSO owns the discord messaging code
|
|
42
|
-
- Suppression... will work in the service itself. Because there shouldn't be THAT many errors happening!
|
|
43
|
-
- Suppression will use AI to group?
|
|
44
|
-
- BUT, we will still have page that shows suppression list, with counts and times
|
|
45
|
-
- Page links to search, which tries to find those errors (we should use the same format for suppression as search, so it should be fairly easy to do that)
|
|
46
31
|
|
|
47
32
|
|
|
48
33
|
|
|
49
34
|
|
|
50
35
|
0) Add LZ4 compression to socket-function by default
|
|
36
|
+
- Setup local tester for it to start
|
|
37
|
+
- with non-synced endpoint, just surely socket-function
|
|
38
|
+
- purely nodejs, just talking to itself
|
|
51
39
|
- Allow setting "compress" to "none" or "lz4" or "zip" or "zip0" or "zip3", etc, for levels.
|
|
52
40
|
- default is "lz4"
|
|
53
41
|
- REQUIRES feature checking the remote, to make sure it is new enough to accept this.
|
|
@@ -76,6 +64,16 @@ Rewrite error notification code
|
|
|
76
64
|
|
|
77
65
|
|
|
78
66
|
|
|
67
|
+
//todonext
|
|
68
|
+
// Reorganize our lifecycles ideas
|
|
69
|
+
// - Scans over past logs, and only finished, never pending
|
|
70
|
+
// - Probably... realtime creating cache, BUT, if we add new lifecycles, we then only add them on demand. So when we are in the debugging mindset, and adding and changing them, it's fast (only as needed), but when we come back later when we aren't in the middle of debugging, it'll be super fast so we can fix the bug quickly and move on.
|
|
71
|
+
// - OR... we can do pending dynamically, but we don't cache it? Hmm... then what's the point? Hmm...
|
|
72
|
+
// - MAYBE, no caching, but we restrict the time by a lot? HMM...
|
|
73
|
+
// - I mean, we have to implement it without caching anyways to start, so we can just do that, and then cache it later if we see the need...
|
|
74
|
+
// - WELL, we need SOME kind of caching... Maybe... we DO use a strict time range, and then... if we've scanned a pending file in the past, we can cache that, because we know we've got all of it's values. Hmm...
|
|
75
|
+
// - Use lifecycles to debug rejections. To a point, until it is likely we are out of sync, then we should write the sync verification code.
|
|
76
|
+
|
|
79
77
|
|
|
80
78
|
// todonext;
|
|
81
79
|
// Hmm... so... should we index it, so we can search it? HMM... I think we might want to?
|
|
@@ -89,6 +89,12 @@ export async function registerManagementPages2(config: {
|
|
|
89
89
|
componentName: "LogViewer3",
|
|
90
90
|
getModule: () => import("./logs/IndexedLogs/LogViewer3"),
|
|
91
91
|
});
|
|
92
|
+
inputPages.push({
|
|
93
|
+
title: "Error Notifications",
|
|
94
|
+
componentName: "ErrorNotificationPage",
|
|
95
|
+
controllerName: "",
|
|
96
|
+
getModule: () => import("./logs/errorNotifications2/ErrorNotificationPage"),
|
|
97
|
+
});
|
|
92
98
|
inputPages.push({
|
|
93
99
|
title: "Security",
|
|
94
100
|
componentName: "SecurityPage",
|
|
@@ -269,6 +275,8 @@ export function renderIsManagementUser() {
|
|
|
269
275
|
return isManagementUserValue().value;
|
|
270
276
|
}
|
|
271
277
|
|
|
278
|
+
const ErrorWarning = createLazyComponent(() => import("./logs/errorNotifications2/ErrorWarning"))("ErrorWarning");
|
|
279
|
+
|
|
272
280
|
class ManagementRoot extends qreact.Component {
|
|
273
281
|
state = {
|
|
274
282
|
ready: false
|
|
@@ -313,6 +321,7 @@ class ManagementRoot extends qreact.Component {
|
|
|
313
321
|
.color("white")
|
|
314
322
|
.color("hsl(0, 0%, 7%)")
|
|
315
323
|
.pointerEvents("none")
|
|
324
|
+
+ " ManagementRoot-page"
|
|
316
325
|
}>
|
|
317
326
|
<style>
|
|
318
327
|
{`
|
|
@@ -321,7 +330,8 @@ class ManagementRoot extends qreact.Component {
|
|
|
321
330
|
}
|
|
322
331
|
`}
|
|
323
332
|
</style>
|
|
324
|
-
<div class={css.fillWidth.hbox(30, 10).wrap.hsl(245, 25,
|
|
333
|
+
<div class={css.fillWidth.hbox(30, 10).wrap.hsl(245, 25, 80).pad2(10).pointerEvents("all")}>
|
|
334
|
+
<ErrorWarning />
|
|
325
335
|
{pages.map(page =>
|
|
326
336
|
<ATag values={[{ param: managementPageURL, value: page.componentName }]}>{page.title}</ATag>
|
|
327
337
|
)}
|
|
@@ -331,7 +341,7 @@ class ManagementRoot extends qreact.Component {
|
|
|
331
341
|
class={
|
|
332
342
|
css.fillBoth.vbox0.overflowAuto.hsl(145, 50, 75)
|
|
333
343
|
.pointerEvents("all")
|
|
334
|
-
|
|
344
|
+
|
|
335
345
|
}
|
|
336
346
|
onKeyDown={e => {
|
|
337
347
|
if (e.currentTarget === e.target) {
|
package/src/server.ts
CHANGED
|
@@ -13,16 +13,8 @@ import { formatTime } from "socket-function/src/formatting/format";
|
|
|
13
13
|
// we DO want to register some static diagnostics from Querysub, so... import it explicitly.
|
|
14
14
|
import "./4-querysub/Querysub";
|
|
15
15
|
import { getOurAuthorities } from "./config2";
|
|
16
|
-
import { getDomain, isPublic } from "./config";
|
|
17
16
|
|
|
18
|
-
import yargs from "yargs";
|
|
19
17
|
import { Querysub } from "./4-querysub/Querysub";
|
|
20
|
-
import { createSourceCheck } from "./user-implementation/canSeeSource";
|
|
21
|
-
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
22
|
-
.option("authority", { type: "string", desc: `Defines the base paths we are an authority on (the domain is prepended to them). Either a file path to a JSON(AuthorityPath[]), or a base64 representation of the JSON(AuthorityPath[]).` })
|
|
23
|
-
.option("verbose", { type: "boolean", desc: "Log all writes and reads" })
|
|
24
|
-
.argv || {}
|
|
25
|
-
;
|
|
26
18
|
|
|
27
19
|
logErrors(main());
|
|
28
20
|
|
|
@@ -59,6 +59,12 @@ class ConfigTab extends qreact.Component {
|
|
|
59
59
|
value={user_data().secure.notifyDiscordWebhookURL}
|
|
60
60
|
onChangeValue={value => user_functions.setNotifyDiscordWebhookURL({ webhookURL: value })}
|
|
61
61
|
/>
|
|
62
|
+
<InputLabel
|
|
63
|
+
label={"OpenRouter API Key"}
|
|
64
|
+
edit
|
|
65
|
+
value={user_data().secure.openRouterAPIKey}
|
|
66
|
+
onChangeValue={value => user_functions.setOpenRouterAPIKey({ apiKey: value })}
|
|
67
|
+
/>
|
|
62
68
|
<Button onClick={() => user_functions.testSendDiscordMessage()}>
|
|
63
69
|
Test Discord Message
|
|
64
70
|
</Button>
|
|
@@ -13,7 +13,7 @@ import { MAX_ACCEPTED_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
|
|
|
13
13
|
import { createURLSync } from "../library-components/URLParam";
|
|
14
14
|
import { devDebugbreak, getDomain, getEmailDomain, isDevDebugbreak, isRecovery } from "../config";
|
|
15
15
|
import { delay } from "socket-function/src/batching";
|
|
16
|
-
import { enableErrorNotifications } from "../library-components/
|
|
16
|
+
import { enableErrorNotifications } from "../library-components/uncaughtToast";
|
|
17
17
|
import { clamp } from "../misc";
|
|
18
18
|
import { sha256 } from "js-sha256";
|
|
19
19
|
import { logDisk } from "../diagnostics/logs/diskLogger";
|
|
@@ -166,6 +166,7 @@ const { data, functions } = Querysub.syncSchema<{
|
|
|
166
166
|
signupsOpen?: boolean;
|
|
167
167
|
postmarkAPIKey?: string;
|
|
168
168
|
notifyDiscordWebhookURL?: string;
|
|
169
|
+
openRouterAPIKey?: string;
|
|
169
170
|
|
|
170
171
|
// NOTE: For now these will only be blocked at a user level. If we run into issues, we can
|
|
171
172
|
// make Querysub have configurable IP blocking that accepts a synced read function, which
|
|
@@ -223,6 +224,7 @@ const { data, functions } = Querysub.syncSchema<{
|
|
|
223
224
|
specialSetInviteCount,
|
|
224
225
|
setPostmarkAPIKey,
|
|
225
226
|
setNotifyDiscordWebhookURL,
|
|
227
|
+
setOpenRouterAPIKey,
|
|
226
228
|
specialSetUserType,
|
|
227
229
|
registerPageLoadTime,
|
|
228
230
|
banUser, unbanUser,
|
|
@@ -978,6 +980,9 @@ function setPostmarkAPIKey(config: { apiKey: string; }) {
|
|
|
978
980
|
function setNotifyDiscordWebhookURL(config: { webhookURL: string; }) {
|
|
979
981
|
data().secure.notifyDiscordWebhookURL = config.webhookURL;
|
|
980
982
|
}
|
|
983
|
+
function setOpenRouterAPIKey(config: { apiKey: string; }) {
|
|
984
|
+
data().secure.openRouterAPIKey = config.apiKey;
|
|
985
|
+
}
|
|
981
986
|
|
|
982
987
|
|
|
983
988
|
function banUser(config: { userId: string; }) {
|
package/test.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import { chdir } from "process";
|
|
2
|
+
chdir("D:/repos/qs-cyoa/");
|
|
3
|
+
|
|
4
|
+
import "./inject";
|
|
5
|
+
|
|
1
6
|
import { Querysub } from "./src/4-querysub/QuerysubController";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
import { getErrorLogs, getLoggers2Async } from "./src/diagnostics/logs/diskLogger";
|
|
8
|
+
import { watchAllValues } from "./src/diagnostics/logs/errorNotifications2/logWatcher";
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
await Querysub.hostService("testwatcher");
|
|
12
|
+
let errorLogs = await getErrorLogs();
|
|
13
|
+
for await (let error of watchAllValues(errorLogs)) {
|
|
14
|
+
process.stdout.write(JSON.stringify(error) + "\n");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
5
17
|
|
|
6
|
-
|
|
7
|
-
process.exit(0);
|
|
8
|
-
});
|
|
18
|
+
main().catch(console.error).finally(() => process.exit(0));
|
package/test2.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { chdir } from "process";
|
|
2
|
+
chdir("D:/repos/qs-cyoa/");
|
|
3
|
+
|
|
4
|
+
import "./inject";
|
|
5
|
+
|
|
6
|
+
import { timeInSecond } from "socket-function/src/misc";
|
|
7
|
+
import { getErrorLogs, getLoggers2Async } from "./src/diagnostics/logs/diskLogger";
|
|
8
|
+
import { watchAllValues } from "./src/diagnostics/logs/errorNotifications2/logWatcher";
|
|
9
|
+
import { delay } from "socket-function/src/batching";
|
|
10
|
+
import { Querysub } from "./src/4-querysub/QuerysubController";
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
await Querysub.hostService("test");
|
|
14
|
+
while (true) {
|
|
15
|
+
console.error("Test error");
|
|
16
|
+
await delay(timeInSecond * 5);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
main().catch(console.error).finally(() => process.exit(0));
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
//todonext
|
|
3
|
-
|
|
4
|
-
// single service, no history search, discord messaging, suppression and suppression page
|
|
5
|
-
// - Storing suppression history just in memory? (although suppression values, obviously, on disk)
|
|
6
|
-
// - receive all errors, and service will do suppression
|
|
7
|
-
|
|
8
|
-
// Start with page itself showing errors and suppressing them, for debugging/testing
|
|
9
|
-
// - Ai to automatically create suppression searches, and combine existing ones, etc
|
|
File without changes
|
|
File without changes
|