Repository navigation
Expand file tree
/
Copy pathplugin.d.ts
More file actions
321 lines (304 loc) · 15.3 KB
/
Copy pathplugin.d.ts
File metadata and controls
321 lines (304 loc) · 15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
declare global {
/** Colour a `chip` node is drawn in. */
type PluginChipTone = "ok" | "warn" | "bad" | "accent";
/** One note in a sound, played with a fast attack and a decay to silence. */
interface PluginTone {
/** Pitch in hertz, 20 to 12000. */
hz: number;
/** How long it lasts in milliseconds, 10 at the least. */
ms: number;
/** Waveform, one of `sine`, `square`, `sawtooth` or `triangle`, and `sine` by default. */
shape?: string;
/** Start it with the tone before rather than after it, which is how a chord is built. */
together?: boolean;
}
/**
* One piece of a view. PrintGuard draws every node with its own components,
* so a plugin inherits the dashboard's theme and can draw nothing else.
*/
type PluginNode =
/** Lays its children out left to right, or top to bottom. */
| { type: "row" | "col"; children: (PluginNode | null | false | undefined)[] }
/** A line of text. `muted` makes it secondary. */
| { type: "text"; value: string; muted?: boolean }
/** A small pill, for a status or a count. */
| { type: "chip"; value: string; tone?: PluginChipTone }
/** A live feed of a registered camera. Needs `camera:view`, and the video never enters the sandbox. */
| { type: "camera"; camera_id?: string }
/** An image you shipped, named as it appears in the manifest's `assets`. */
| { type: "image"; asset: string; label?: string }
/**
* A button that floats a camera in the browser's own picture-in-picture
* window. Needs `camera:view`, and PrintGuard floats it from the press
* itself, since a browser only allows that from something the user did.
*/
| { type: "float"; camera_id: string; label?: string; value?: string }
/** A field the user types into, committed on blur or Enter. Its `label` is drawn above it, and `secret` masks it. */
| { type: "input"; value?: string; label?: string; action: string; kind?: "text" | "number"; placeholder?: string; secret?: boolean }
/** A switch, handed `true` or `false` as the arg. */
| { type: "toggle"; on?: boolean; label?: string; action: string }
/** Calls your `action` handler with `action` as the name and `arg` as given. */
| { type: "button"; label: string; action: string; arg?: unknown }
/** Calls your `action` handler with the chosen option's value as the arg. Its `label` is drawn above it. */
| { type: "select"; value?: string; label?: string; action: string; options: { value: string; label: string }[] };
/** A monitor, as `state:read` allows you to see it. */
interface PluginMonitor {
id: string;
name: string;
/** The camera it watches, and the one a `camera` node should name. */
camera_id: string;
/** Empty when no printer is bound. */
printer_id: string;
enabled: boolean;
/** Whether inference is actually running, which a printer that is not printing stands down. */
watching: boolean;
/** The score a frame must reach to count as a defect, 0 to 1. */
threshold: number;
/** The last score, or null before the first one. */
result: { ts: number; score: number } | null;
/** The alert in force, absent until the monitor's first alert and null once one has cleared. */
alert?: { ts: number; score: number; action: string } | null;
}
/** A camera, as `state:read` allows you to see it. */
interface PluginCamera {
id: string;
name: string;
/** Whether the source is reachable. */
online: boolean;
/** Whether it is idling, kept open but not inferring. */
standby: boolean;
/** Whether a monitor is using it. */
in_use: boolean;
max_fps: number;
/** The rate of completed inferences on this camera, a second. */
achieved_fps: number;
}
/** A printer, as `state:read` allows you to see it. */
interface PluginPrinter {
id: string;
name: string;
/** The integration behind it, such as `octoprint` or `bambu`. */
provider: string;
online: boolean;
/** Null until the service has been polled once. */
device_state: PluginDeviceState | null;
}
/** A heater's reading and target in °C. A target of 0 means it is off. */
interface PluginHeater {
actual: number;
target: number;
}
/** What a printer's service last reported. */
interface PluginDeviceState {
/** `printing`, `paused`, `idle`, `error`, `offline` or `unknown`. */
status: string;
/** Job completion, 0 to 100. */
progress: number;
job: string | null;
/** Seconds the service expects the job to take from now, or null where it gives no estimate. */
remaining_s: number | null;
/** Null where the printer has no such heater, or is not reporting it. */
nozzle: PluginHeater | null;
bed: PluginHeater | null;
}
/**
* What your permissions let you read, refreshed for every call. A collection
* is missing entirely unless a permission names it, and credentials, notifier
* settings and API tokens are in no permission.
*/
interface PluginState {
/** The PrintGuard version running. */
version: string;
monitors?: PluginMonitor[];
cameras?: PluginCamera[];
printers?: PluginPrinter[];
}
/** The events `plugin.js` and `worker.js` can hook, and what each one carries. */
interface PluginEvents {
/** The answer to one of your own `ctx.http` calls, carrying the `tag` you named it with. */
http: { event: "http"; tag: string; status: number; body: unknown };
/** A socket you opened coming up, carrying a frame, or ending. */
socket: { event: "socket"; tag: string; state: "open" | "message" | "closed"; text: string };
/** A still you asked for with `camera.snapshot`, base64 JPEG. Needs `camera:frames`. */
frame: { event: "frame"; camera_id: string; jpeg: string };
/** Another plugin asking you something, on a channel your manifest offers. Answer by returning from `plugin.serve`. */
call: { event: "call"; from: string; channel: string; body: any; call_id: string };
/** The answer to one of your own `ctx.call`s, carrying the `tag` you named it with. */
answer: { event: "answer"; tag: string; from: string; channel: string; body: any };
/** Something a plugin you named in `consumes` published. */
message: { event: "message"; from: string; channel: string; body: any };
/** A monitor's risk history, answering `history.get`. Needs `history:read`. */
history: {
event: "history";
monitor_id: string;
now: number;
/** One per minute with a reading in it, oldest first. */
buckets: {
/** The minute's start, in seconds since the epoch. */
t: number;
/** How many frames were scored in it. */
n: number;
sum: number;
min: number;
max: number;
/** How many of them reached the threshold. */
defects: number;
/** Seconds of it the monitor was watching, never more than 60. */
watched: number;
}[];
alerts: { ts: number; score: number; action: string }[];
stats: {
current: number;
avg: number;
min: number;
max: number;
inferences: number;
defect_frames: number;
/** `defect_frames` as a percentage of `inferences`, to one decimal place. */
defect_pct: number;
/** How many alerts fired. */
alerts: number;
/** Whole minutes the readings spanned, with gaps over 30 seconds left out. */
watch_min: number;
/** How many alert snapshots are kept. */
snaps: number;
};
};
/** Every inference on a watched monitor, capped at 5 a second per monitor, before any threshold or streak logic. Needs `state:read`. */
result: { event: "result"; monitor_id: string; camera_id: string; score: number; prediction: "failure" | "success"; margin: number; ms: number; ts: number };
/** A defect held long enough to act on. `action` is what PrintGuard did to the printer. Needs `state:read`. */
alert: { event: "alert"; monitor_id: string; score: number; action: string; ts: number };
/** A watchdog condition, and again with `recovered` when it clears. Needs `state:read`. */
warning: { event: "warning"; monitor_id?: string; message: string; recovered: boolean };
/** A printer's status changed, carrying everything its `device_state` does. Needs `state:read`. */
device: { event: "device"; printer_id: string } & PluginDeviceState;
/** Anything that failed. Needs `state:read`. */
error: { event: "error"; message: string };
/** The snapshot your permissions allow, once a second. */
state: { event: "state" } & PluginState;
/** Your own timer, as often as `tick_s` in the manifest asks. */
tick: { event: "tick" };
}
/** A request to one of your routes, or one a gate is being asked about. */
interface PluginRequest {
method: string;
path: string;
query: Record<string, string>;
/** Cookie, authorization, accept, content-type, x-forwarded-for and user-agent, where present. */
headers: Record<string, string>;
/** Capped at 64 KB and empty for a request without one. Null when a gate is being asked. */
body: string | null;
}
/** What a route hands back. Defaults to an empty 200 of `text/plain`. */
interface PluginResponse {
status?: number;
/** Content type, such as `text/html`. */
type?: string;
body?: string;
/** Only these three are passed on. */
headers?: { "set-cookie"?: string; location?: string; "cache-control"?: string };
}
/**
* Everything a plugin can reach. It performs nothing itself: each call asks
* PrintGuard for something, which is checked against your granted
* permissions before it happens.
*/
interface PluginContext {
/** What your permissions let you read, fresh for this call. */
state: PluginState;
/** The monitor this view is being drawn for, on the `monitor` and `settings` surfaces, and undefined for your panel. */
target?: string;
/** Which surface is being drawn, `monitor` or `settings`, alongside `ctx.target`. */
surface?: string;
/** The text files you shipped, keyed by name. Images and audio stay with PrintGuard. */
assets: Record<string, string>;
/** Your own data. Assign to it and PrintGuard saves it, up to 16 KB. */
store: Record<string, any>;
/**
* Runs an engine command, such as `{ cmd: "printer.action", id, action: "pause" }`.
* Every command maps to a permission, and an ungranted one is refused.
*/
command(cmd: { cmd: string; [field: string]: unknown }): void;
/**
* Makes an HTTP request for you. Needs `net` and a URL your patterns cover.
* The answer arrives as an `http` event under the same `tag`. A redirect is
* not followed, so its 3xx status is the answer. A `Host` header is refused.
*/
http(request: { method?: string; url: string; headers?: Record<string, string>; json?: unknown; tag?: string }): void;
/** Opens a WebSocket PrintGuard holds for you, answering on `tag`. Needs `net` and a `ws` or `wss` pattern covering the URL. */
socket(request: { url: string; tag: string }): void;
/** Writes one text frame to a socket you opened. */
socketSend(tag: string, text: string): void;
/** Closes a socket you opened. */
socketClose(tag: string): void;
/** Raises a message on the dashboard. Needs `notify`, and does not use the user's alert channels. */
notify(text: string): void;
/**
* Sounds tones through the speakers of whoever is looking, one after the next
* unless a tone says `together`. Needs `sound`, stays quiet until they have
* pressed something in the page, and plays four seconds at most.
*/
sound(tones: PluginTone[] | PluginTone): void;
/** Plays an audio file you shipped, named as it appears in the manifest's `assets`. Needs `sound`. */
sound(asset: string): void;
/**
* Puts a picture behind the dashboard. Needs `background`, takes a base64 `data:` URL
* of a PNG, JPEG, WebP or GIF, and clears when passed anything else.
*/
background(image: string): void;
/**
* Asks another plugin for something, on a channel your manifest names in
* `consumes`. Needs `link:consume`, and answers on an `answer` event.
*/
call(request: { to: string; channel: string; body?: unknown; tag?: string }): void;
/** Publishes on one of your own channels, reaching every plugin that named it. Needs `link:provide`. */
publish(request: { channel: string; body?: unknown }): void;
/** Writes a line to PrintGuard's log. */
log(text: string): void;
}
/**
* Registers what your plugin does. `render` and `action` are for `plugin.js`
* alone, `route` and `gate` for `worker.js` alone, and `on` and `serve` work in either.
*/
interface PluginApi {
/**
* Draws the view, again on every state change and after every action, so
* keep it a plain function of `ctx`. On the `monitor` and `settings` surfaces it is
* called once more per monitor, with `ctx.target` naming which.
*/
render(view: (ctx: PluginContext) => PluginNode | null): void;
/** Handles a press or a choice, named by the node's `action` and given its `arg`. */
action(handler: (name: string, arg: any, ctx: PluginContext) => void): void;
/** Hooks an engine event, in `plugin.js` or `worker.js`. Name it in the manifest's `events` too, or it never fires. */
on<K extends keyof PluginEvents>(event: K, handler: (event: PluginEvents[K], ctx: PluginContext) => void): void;
/** Answers everything under `/plugins/<id>/` on the hub. Needs `routes`, and pages are served into a sandboxed origin. */
route(handler: (request: PluginRequest, ctx: PluginContext) => PluginResponse): void;
/** Approves or refuses every other request to the hub. Needs `gate`, and anything but `true` refuses. */
gate(handler: (request: PluginRequest, ctx: PluginContext) => boolean): void;
/** Answers another plugin asking on one of the channels your manifest offers, from `plugin.js` or `worker.js`. Needs `link:provide`. */
serve(handler: (request: PluginEvents["call"], ctx: PluginContext) => unknown): void;
}
/** Registers your plugin. In scope in `plugin.js` and `worker.js`, with nothing else beside it. */
const plugin: PluginApi;
/**
* What a `panel.html` reaches. It draws itself, so there are no nodes here,
* but everything else is the `ctx` the node tree gets and is checked against
* the same permissions.
*/
interface PluginPanel extends Omit<PluginContext, "target" | "surface" | "assets"> {
/** The dashboard's theme, the same custom properties it sets on its own `:root`. */
theme: Record<string, string>;
/** The names of the secrets your plugin holds, never their values. `oauth` is there once the user has signed in. */
secrets: string[];
/** Called once the panel is drawn, then on every state change and every event you named. */
on<K extends keyof PluginEvents | "ready" | "state">(
event: K,
handler: (event: K extends keyof PluginEvents ? PluginEvents[K] : PluginState) => void,
): void;
/** A URL for a file you shipped, good inside this panel and nowhere else. Point an `img` at it. Audio and video play only if the plugin was granted `sound`. */
asset(name: string): string;
}
/** Draws your own panel. In scope in `panel.html`, with no network and no reach into the page around it. */
const pg: PluginPanel;
}
export {};