-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathplugin.d.ts
More file actions
272 lines (257 loc) · 13.3 KB
/
Copy pathplugin.d.ts
File metadata and controls
272 lines (257 loc) · 13.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
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, or null when nothing is wrong. */
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;
/** What inference is managing, against the monitor's target. */
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: { status: string; progress: number; job: string | null } | 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 {
/** `hub` for the self-hosted server, `local` for the browser. */
mode: "hub" | "local";
/** The PrintGuard version running. */
version: string;
monitors?: PluginMonitor[];
cameras?: PluginCamera[];
printers?: PluginPrinter[];
}
/** The events a worker 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;
buckets: { t: number; n: number; sum: number; min: number; max: number; defects: number }[];
alerts: { ts: number; score: number; action: string }[];
stats: { current: number; avg: number; min: number; max: number; inferences: number; defect_frames: number };
};
/** Every inference on a watched monitor, capped at 5 a second per monitor, before any threshold or streak logic. */
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. */
alert: { event: "alert"; monitor_id: string; score: number; action: string; ts: number };
/** A watchdog condition, and again with `recovered` when it clears. */
warning: { event: "warning"; monitor_id?: string; message: string; recovered: boolean };
/** A printer's status changed. */
device: { event: "device"; printer_id: string; status: string; progress: number; job: string | null };
/** Anything that failed. */
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>;
/** Null for a request without one, and capped at 64 KB. */
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`.
*/
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 `data:` URL,
* and clears when passed nothing.
*/
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 the panel half
* in `plugin.js`, the rest are the worker half in `worker.js`.
*/
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` surface 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;
/** Wakes the worker on an engine event. 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. 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>;
/** 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` or a `video` at it. */
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 {};