dcp-worker 4.3.7 → 4.4.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.
@@ -0,0 +1,346 @@
1
+ <!doctype html>
2
+ <!--
3
+ - @file small-dark.html - small, dark status screen for the worker
4
+ - @author Wes Garland, wes@distributive.network
5
+ - @date Feb 2026
6
+ -
7
+ - DCP logo is hidden by default; use ?show-logo to reveal
8
+ -
9
+ -->
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <title>DCP Worker - HUD</title>
14
+ <link rel="stylesheet" type="text/css" href="./hud-common.css">
15
+ <script src="/etc/dcp-config.js"></script>
16
+ <script src="/dcp-client/dcp-client.js"></script>
17
+ <link rel="stylesheet" type="text/css" dcp-cdn-href="/css/dcp-style.css">
18
+ <link rel="stylesheet" type="text/css" href="./dark.css">
19
+ <script cdn-src="/dom-tk.js?dcp-worker-version=4.3.7"></script>
20
+ <script type="module">
21
+ import * as hud from "./hud-common.mjs";
22
+ window.addEventListener('error', ev => dcp['dom-tk'].modals.alert(ev));
23
+ window.addEventListener('unhandledrejection', ev => dcp['dom-tk'].modals.alert(ev.reason));
24
+ </script>
25
+ <style type="text/css">
26
+ HTML, BODY {
27
+ height: 100%;
28
+ width: 100%;
29
+ min-height: 100%; /* opera */
30
+ margin: 0;
31
+ padding: 0;
32
+ overflow: hidden;
33
+ }
34
+
35
+ DIV#dcp-logo {
36
+ background-image: url(/dcp-client/assets/dcp-logo.png);
37
+ background-position: center;
38
+ background-repeat: no-repeat;
39
+ background-size: contain;
40
+ height: 20vh;
41
+ max-height: 60px;
42
+ margin-top: 1em;
43
+ }
44
+
45
+ BODY[show-logo="false"] DIV#dcp-logo-header {
46
+ display: none;
47
+ }
48
+
49
+ #logBlocker {
50
+ width: 100%;
51
+ background-color: black;
52
+ position: relative;
53
+ z-index: 1;
54
+ }
55
+
56
+ #loggerText {
57
+ white-space: pre-wrap;
58
+ position: absolute;
59
+ bottom: 0;
60
+ max-height: 100%;
61
+ width: 100%;
62
+ overflow-x: hidden;
63
+ overflow-y: scroll;
64
+ text-overflow: ellipsis;
65
+ text-wrap: nowrap;
66
+ padding: 5px;
67
+ }
68
+
69
+ TABLE
70
+ {
71
+ align: left;
72
+ width: 100%;
73
+ border-bottom: 1px dotted #ccc;
74
+ }
75
+
76
+ TD {
77
+ min-width: 20ch;
78
+ overflow: hidden;
79
+ text-overflow: ellipsis;
80
+ }
81
+
82
+ TH {
83
+ text-align: left;
84
+ text-wrap: nowrap;
85
+ }
86
+ </style>
87
+ <script src="/worker/run-info"></script>
88
+ <script>
89
+ 'use strict';
90
+ if (!runInfo)
91
+ setTimeout(() => window.location.reload(), 1500);
92
+ </script>
93
+ <script>
94
+ 'use strict';
95
+ const $ = document.querySelector.bind(document);
96
+ const $$ = document.querySelectorAll.bind(document);
97
+ function displayRunInfo(newRunInfo)
98
+ {
99
+ if (newRunInfo)
100
+ Object.assign(runInfo, newRunInfo);
101
+
102
+ runInfo.startTime = new Date(runInfo.startTime).toLocaleString();
103
+ runInfo.totalEarnings = new dcp.types.BigNumber(runInfo.totalEarnings.toFixed(3));
104
+ $$('[run-info]').forEach(element => {
105
+ const prop = element.getAttribute('run-info');
106
+ element.textContent = String(runInfo[prop]);
107
+ });
108
+ }
109
+
110
+ async function init()
111
+ {
112
+ displayRunInfo();
113
+ const worker = new DistributiveWorkerLink(window.location.origin);
114
+ worker.on('payment', (amount) => {
115
+ const el = $('[run-info="totalEarnings"]');
116
+ const bnTotal = new dcp.types.BigNumber(el.textContent);
117
+ const bnAmount = new dcp.types.BigNumber(amount);
118
+ el.textContent = String(bnTotal.plus(bnAmount));
119
+ });
120
+
121
+ worker.on('job', job => logger(`New job: ${job.name} ${job.id.slice(0,8)} ${job.description || ''} ${job.link || ''}`));
122
+ worker.on('sandbox', sandbox => logger('sbox', sandbox));
123
+ worker.on('connect', (runInfo, taskInfo) => {
124
+ displayRunInfo(runInfo);
125
+ logger(`Worker currently has ${Object.keys(taskInfo.jobs).length} jobs loaded`);
126
+ });
127
+ worker.on('stop', () => logger('Worker is preparing to shut down'));
128
+ worker.on('end', () => logger('Worker has stopped'));
129
+ }
130
+
131
+ const logLines = [];
132
+ const logSize = 100;
133
+ function logger(...args)
134
+ {
135
+ const logEl = $('#loggerText');
136
+ const msg = args.map(arg => String(arg)).join(' ');
137
+ logLines.push(msg);
138
+ if (logLines.length === logSize)
139
+ logLines.shift();
140
+ logEl.textContent = logLines.join('\n');
141
+ logEl.scrollTo({top: logEl.scrollHeight, behavior: 'smooth'});
142
+ }
143
+
144
+ class DistributiveWorkerLinkEvent
145
+ {
146
+ #cancelled = false;
147
+ #args;
148
+
149
+ constructor(args)
150
+ {
151
+ this.#args = args || [];
152
+ }
153
+
154
+ preventDefault()
155
+ {
156
+ }
157
+
158
+ stopPropagation()
159
+ {
160
+ this.#cancelled = true;
161
+ }
162
+
163
+ dispatch(callback)
164
+ {
165
+ var wev;
166
+ if (globalThis.window === globalThis)
167
+ {
168
+ wev = window.event;
169
+ window.event = this;
170
+ }
171
+ callback(...this.#args);
172
+ if (globalThis.window === globalThis)
173
+ window.event = wev;
174
+ return !this.#cancelled;
175
+ }
176
+ }
177
+
178
+ class DistributiveWorkerLink
179
+ {
180
+ #connectType = 'normal';
181
+ #location;
182
+ static #KVIN = (() => {
183
+ const KVIN = new dcp.kvin.KVIN();
184
+ KVIN.userCtors.BigNumber = dcp.types.BigNumber;
185
+ KVIN.userCtors.JobHandle = Object;
186
+ KVIN.userCtors.SanboxHandle = Object;
187
+ return KVIN;
188
+ })(/*iife*/);
189
+
190
+ constructor(location)
191
+ {
192
+ this.events = {};
193
+ this.#location = new URL(origin + '/dcp/');
194
+ this.#connect();
195
+ }
196
+
197
+ #connect()
198
+ {
199
+ var reqs = [];
200
+ var queue = []; /* queue up notifications that arrive during Promise.allSettled */
201
+
202
+ this.conn = new dcp.protocol.Connection({ location: this.#location });
203
+ this.conn.on('end', (ev) => {
204
+ if (this.conn.closeErrorCode === 'ECONNRESET') /* Worker went down and came back up */
205
+ window.location.reload();
206
+ });
207
+ this.conn.on('connect', async () => {
208
+ var taskInfo, runInfo;
209
+ const p$res = [
210
+ this.conn.request('task-info').then(res => taskInfo = res.payload),
211
+ this.conn.request('run-info') .then(res => runInfo = res.payload)
212
+ ];
213
+ try
214
+ {
215
+ await Promise.all(p$res);
216
+ this.#dispatchEvent('connect', [ runInfo, taskInfo ]);
217
+ this.#connectType = 'normal';
218
+ }
219
+ catch(error)
220
+ {
221
+ if (error.code === 'EADDRCHANGE')
222
+ this.#connectType = 'never';
223
+ else
224
+ this.#connectType = 'slow';
225
+ throw error;
226
+ }
227
+ });
228
+ this.conn.on('end', async () => {
229
+ switch(this.#connectType)
230
+ {
231
+ case 'never':
232
+ console.log('ignore connect request, reload document to reconnect');
233
+ return;
234
+ case 'slow':
235
+ await dcp.utils.a$sleep(10);
236
+ /* fallthrough */
237
+ case 'normal':
238
+ break;
239
+ }
240
+ this.#connect();
241
+ });
242
+ this.conn.on('notification', notification => {
243
+ if (reqs.length === 0)
244
+ this.#processNotification(notification);
245
+ else
246
+ queue.push(notification);
247
+ });
248
+
249
+ for (let eventName of Object.keys(this.events))
250
+ reqs.push(this.conn.request('event-on', eventName));
251
+ Promise.allSettled(reqs).then(async () => {
252
+ for (let eventName of Object.keys(this.events))
253
+ this.events[eventName].eventHandlerId = await reqs.shift();
254
+ for (let notification of queue)
255
+ this.#processNotification(notification);
256
+ });
257
+ }
258
+
259
+ #dispatchEvent(eventName, args)
260
+ {
261
+ if (!this.events[eventName])
262
+ return;
263
+ for (let event of this.events[eventName])
264
+ {
265
+ const ev = new DistributiveWorkerLinkEvent(args)
266
+ if (ev.dispatch(event.eventHandler) === false)
267
+ break;
268
+ }
269
+ }
270
+
271
+ #processNotification(notification)
272
+ {
273
+ if (notification.payload.data.type !== 'event')
274
+ throw new Error('invalid notification type: ' + notification.payload.data.type);
275
+ const eventName = notification.payload.data.name;
276
+ this.#dispatchEvent(eventName, this.constructor.#KVIN.parse(notification.payload.data.kvinArgs));
277
+ }
278
+
279
+ async on(eventName, eventHandler)
280
+ {
281
+ var eventHandlerId;
282
+
283
+ if (!this.events[eventName])
284
+ this.events[eventName] = [];
285
+ if (eventName === 'connect') /* internal event */
286
+ eventHandlerId = Math.random().toFixed(36) + Date.now();
287
+ else
288
+ {
289
+ try
290
+ {
291
+ const res = await this.conn.request('event-on', eventName);
292
+ eventHandlerId = res.payload.eventHandlerId;
293
+ }
294
+ catch(error)
295
+ {
296
+ if (error.code === 'EPIPE')
297
+ this.on(eventName, eventHandler); /* try again on new connection */
298
+ else
299
+ throw error;
300
+ }
301
+ }
302
+ this.events[eventName].push({ eventHandler, eventHandlerId });
303
+ }
304
+
305
+ off(eventName, eventHandler)
306
+ {
307
+ for (let i=0; i < this.events[eventName].length; i++)
308
+ {
309
+ event = this.events[eventName][i];
310
+ if (event.eventHandler === eventHandler)
311
+ {
312
+ conn.request('event-off', { eventName, eventHandlerId: event.eventHandlerId });
313
+ this.events[eventName].splice(i, 1);
314
+ break;
315
+ }
316
+ }
317
+ }
318
+
319
+ addEventListener = this.on;
320
+ removeEventListener = this.off;
321
+ }
322
+ </script>
323
+ </head>
324
+ <body onload="init();" show-logo="false">
325
+ <div id="logBlocker">
326
+ <div id="dcp-logo-header">
327
+ <div id="dcp-logo"></div>
328
+ <center><h3>DCP Worker</h3></center>
329
+ </div>
330
+ <table>
331
+ <tr><th>Running Since:</th> <td run-info="startTime"></td></tr>
332
+ <tr><th>Earnings Since Start:</th> <td run-info="totalEarnings" class="dcp-amount"></td></tr>
333
+ <tr><th>Earnings Account:</th> <td run-info="earningsAccount" class="dcp-fixed-font"></td></tr>
334
+ </table>
335
+ </div>
336
+ <div id="loggerText" class="dcp-fixed-font">connecting to worker...</div>
337
+ </body>
338
+ <script>
339
+ 'use strict';
340
+ {
341
+ const searchParams = new URL(window.location.href).searchParams;
342
+ if (searchParams.has('show-logo'))
343
+ document.body.setAttribute('show-logo', searchParams.get('show-logo') || 'true');
344
+ }
345
+ </script>
346
+ </html>
@@ -1,178 +0,0 @@
1
- /**
2
- * https://stackoverflow.com/questions/52843900/blessed-server-node-js-over-websocket-to-xterm-js-client-in-browser
3
- */
4
- const blessed = require('blessed');
5
- const contrib = require('blessed-contrib');
6
- const path = require('path');
7
- const fs = require('fs');
8
- const http = require('http');
9
-
10
- const progDir = path.dirname(require.main.filename);
11
- const wwwDir = path.resolve(progDir, '../www');
12
- const nmDir = path.resolve(progDir, '../node_modules');
13
-
14
- const magic = {
15
- html: 'text/html',
16
- txt: 'text/plain',
17
- js: 'application/javascript',
18
- css: 'text/css',
19
- gif: 'image/gif',
20
- jpeg: 'image/jpeg',
21
- jpg: 'image/jpeg',
22
- png: 'image/png',
23
- svg: 'image/svg+xml',
24
- kvin: 'application/x-kvin',
25
- }
26
-
27
- const clients = [ ];
28
- var worker; /* instance of DistributiveWorker, null (stopped), or undefined (not started) */
29
-
30
- /**
31
- * Return a copy of the local dcp config, with the current worker's config at dcpConfig.worker, but with
32
- * with sensitive information (eg compute group credentials) stripped.
33
- */
34
- function makeSafeConfig()
35
- {
36
- const conf = Object.assign({}, dcpConfig);
37
- conf.worker = Object.assign({}, worker.config || dcpConfig.worker?.config);
38
-
39
- /* Sanitize worker.computeGroups credentials */
40
- if (conf.worker?.computeGroups)
41
- {
42
- conf.worker.computeGroups = Object.assign({}, conf.worker.computeGroups);
43
- for (let key in conf.worker.computeGroups)
44
- {
45
- const group = conf.worker.computeGroups[key];
46
- if (group.joinKey)
47
- group = { joinKey: group.joinKey };
48
- else
49
- group = {};
50
- conf.worker.computeGroups[key] = group;
51
- }
52
- }
53
-
54
- return conf;
55
- }
56
-
57
- /**
58
- * HTTP server
59
- */
60
- function handleHttpRequest(request, response)
61
- {
62
- if (request.url === '/etc/dcp-config.js')
63
- {
64
- response.setHeader('Content-Type', magic.js);
65
- response.end(makeSkeletonConfig());
66
- return;
67
- }
68
-
69
- if (request.url === '/etc/dcp-config.kvin')
70
- {
71
- response.setHeader('Content-Type', magic.kvin);
72
- response.end('(' + JSON.stringify(dcpConfig) + ')');
73
- return;
74
- }
75
-
76
- var filename = (request.url.slice(1) || 'index.html');
77
- const contentType = magic[path.extname(filename).slice(1)] || 'text/plain';
78
- var errorStatus, contentDir;
79
-
80
- if (!filename.startsWith('node_modules/'))
81
- contentDir = wwwDir;
82
- else
83
- {
84
- contentDir = nmDir;
85
- filename = filename.slice(13);
86
- }
87
-
88
- filename = path.resolve(contentDir, filename);
89
- console.log({contentDir,filename,requestUrl:request.url});
90
- if (!filename.startsWith(contentDir) || filename[0] !== '/')
91
- errorStatus = 401;
92
- else if (!fs.existsSync(filename))
93
- errorStatus = 404;
94
-
95
- if (errorStatus)
96
- {
97
- response.setHeader('Content-Type', 'text/plain');
98
- response.statusCode = errorStatus;
99
- response.end(`${errorStatus} accessing ${filename}`);
100
- return;
101
- }
102
-
103
- response.setHeader('Content-Type', contentType);
104
- response.end(fs.readFileSync(filename));
105
- }
106
-
107
- exports.getConfig = function webConsole$$getConfig()
108
- {
109
- var config = { host: 'localhost', port: 9080 };
110
-
111
- if (dcpConfig.worker?.webConsole?.listen)
112
- {
113
- config.host = dcpConfig.worker.webConsole.listen.host;
114
- config.port = dcpConfig.worker.webConsole.listen.port || 80;
115
- }
116
-
117
- return config;
118
- }
119
-
120
- exports.a$init = function webConsole$$init()
121
- {
122
- var server = http.createServer(handleHttpRequest).unref();
123
- var resolve, reject;
124
- var a$Promise = new Promise((_resolve, _reject) => {
125
- resolve = _resolve;
126
- reject = _reject;
127
- });
128
-
129
- server.on('error', error => {
130
- console.error(` ! Web console not started; ${error.message}`);
131
- reject(error);
132
- });
133
- server.listen(exports.getConfig(), function webConsole$$ready() {
134
- resolve();
135
- console.log(` * httpd listening on ${server._connectionKey}`);
136
- });
137
-
138
- /**
139
- * WebSocket server
140
- */
141
- const WebSocketServer = require('websocket').server;
142
- const wsServer = new WebSocketServer({
143
- httpServer: server,
144
- autoAcceptConnections: false
145
- });
146
-
147
- wsServer.on('request', handleWssRequest);
148
- function handleWssRequest(request)
149
- {
150
- const connection = request.accept(null, request.origin);
151
- connection.socket.unref();
152
- clients.push(connection);
153
-
154
- const write = connection.send;
155
- const read = connection.socket.read;
156
-
157
- connection.send(JSON.stringify({ type: 'log', facility: 'debug', message: 'hello, world' }));
158
- connection.on('message', messageHandler);
159
- connection.on('close', closeHandler);
160
-
161
- function messageHandler(message)
162
- {
163
- console.log('message:', message)
164
- console.log('this:', this);
165
- }
166
-
167
- function closeHandler()
168
- {
169
- }
170
- }
171
-
172
- return a$Promise;
173
- }
174
-
175
- exports.setWorker = function webConsole$$setWorker(dw)
176
- {
177
- worker = dw;
178
- }