pinokiod 2.15.11 → 2.15.12
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/kernel/api/fs/index.js +13 -3
- package/kernel/api/index.js +32 -12
- package/kernel/api/local/index.js +2 -0
- package/kernel/bin/huggingface.js +25 -0
- package/kernel/bin/playwright.js +76 -0
- package/kernel/index.js +28 -2
- package/kernel/lproxy.js +29 -4
- package/package.json +1 -1
- package/server/index.js +49 -6
- package/server/public/lmstudio.jpg +0 -0
- package/server/public/style.css +12 -1
- package/server/views/browser-experimental.ejs +1 -1
- package/server/views/disposable.ejs +313 -0
- package/server/views/editor.ejs +8 -1
- package/server/views/explore.ejs +1 -1
- package/server/views/fullscreen_editor.ejs +8 -1
- package/server/views/index.ejs +1 -1
- package/server/views/proxy.ejs +2 -2
- package/server/views/settings.ejs +1 -1
package/kernel/api/fs/index.js
CHANGED
|
@@ -67,6 +67,7 @@ class FS {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async link(req, ondata, kernel) {
|
|
70
|
+
console.log("FS.link", req)
|
|
70
71
|
let response = await this.share(req, ondata, kernel)
|
|
71
72
|
return response
|
|
72
73
|
}
|
|
@@ -275,14 +276,22 @@ class FS {
|
|
|
275
276
|
parent: site_packages_root,
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if (req.params.options) {
|
|
282
|
+
d.options = req.params.options
|
|
283
|
+
} else {
|
|
284
|
+
d.options = {
|
|
285
|
+
overwrite: true
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
278
289
|
// if the version is NOT final, overwrite.
|
|
279
290
|
let re = /^\d+(\.\d+)*$/ // regex for testing final versions like 1.2, 1.1.1, 1.2.1.1, etc.
|
|
280
291
|
if (re.test(version)) {
|
|
281
292
|
// final version
|
|
282
293
|
} else {
|
|
283
|
-
d.options =
|
|
284
|
-
overwrite: true
|
|
285
|
-
}
|
|
294
|
+
d.options.overwrite = true
|
|
286
295
|
}
|
|
287
296
|
|
|
288
297
|
for(let relpath of copy) {
|
|
@@ -302,6 +311,7 @@ class FS {
|
|
|
302
311
|
ondata({
|
|
303
312
|
raw: "\r\nLINKING DRIVES:\r\n" + JSON.stringify(d, null, 2).replace(/\n/g, "\r\n")
|
|
304
313
|
})
|
|
314
|
+
console.log("CREATE", JSON.stringify(d, null, 2))
|
|
305
315
|
await drive.create(d)
|
|
306
316
|
ondata({
|
|
307
317
|
raw: "Done.\r\n"
|
package/kernel/api/index.js
CHANGED
|
@@ -25,6 +25,21 @@ class Api {
|
|
|
25
25
|
this.proxies = {}
|
|
26
26
|
this.lproxy = new Lproxy()
|
|
27
27
|
}
|
|
28
|
+
get_proxy_url(root, port) {
|
|
29
|
+
if (this.proxies) {
|
|
30
|
+
let proxies = this.proxies[root]
|
|
31
|
+
if (proxies) {
|
|
32
|
+
let filtered = proxies.filter((p) => {
|
|
33
|
+
let re = new RegExp(":"+port+"$")
|
|
34
|
+
return re.test(p.target)
|
|
35
|
+
})
|
|
36
|
+
if (filtered.length > 0) {
|
|
37
|
+
return filtered[0].proxy
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return `http://localhost:${port}`
|
|
42
|
+
}
|
|
28
43
|
checkProxy(config) {
|
|
29
44
|
// if config.name exists, throw error
|
|
30
45
|
// if config.uri exists, throw error
|
|
@@ -41,12 +56,14 @@ class Api {
|
|
|
41
56
|
this.checkProxy({ uri, name })
|
|
42
57
|
// if the name exists, throw error
|
|
43
58
|
// if the uri exists, throw error
|
|
59
|
+
console.log("lproxy start", uri, options)
|
|
44
60
|
let proxy_uri = await this.lproxy.start(uri, options)
|
|
45
61
|
if (this.proxies[scriptPath]) {
|
|
46
62
|
this.proxies[scriptPath].push({ target: uri, proxy: proxy_uri, name})
|
|
47
63
|
} else {
|
|
48
64
|
this.proxies[scriptPath] = [{ target: uri, proxy: proxy_uri, name }]
|
|
49
65
|
}
|
|
66
|
+
console.log("this.proxies", JSON.stringify(this.proxies, null, 2))
|
|
50
67
|
return {
|
|
51
68
|
target: uri,
|
|
52
69
|
proxy: proxy_uri
|
|
@@ -75,6 +92,7 @@ class Api {
|
|
|
75
92
|
})
|
|
76
93
|
}
|
|
77
94
|
}
|
|
95
|
+
console.log("this.proxies", JSON.stringify(this.proxies, null, 2))
|
|
78
96
|
}
|
|
79
97
|
async init() {
|
|
80
98
|
if (this.kernel.homedir) {
|
|
@@ -501,6 +519,7 @@ class Api {
|
|
|
501
519
|
mods.push(r)
|
|
502
520
|
}
|
|
503
521
|
let mod = _.zipObjectDeep(paths, mods)
|
|
522
|
+
console.log("MOD", mod)
|
|
504
523
|
return mod
|
|
505
524
|
|
|
506
525
|
}
|
|
@@ -1184,18 +1203,19 @@ class Api {
|
|
|
1184
1203
|
return path.resolve(cwd, x)
|
|
1185
1204
|
})
|
|
1186
1205
|
|
|
1187
|
-
let merge = await this.construct(cwd, includeFiles, { exclude: [filename], is_sub: false })
|
|
1188
|
-
|
|
1189
|
-
// merge them into the original module
|
|
1190
|
-
|
|
1191
|
-
//exclude "run" attributes
|
|
1192
|
-
|
|
1193
|
-
if (script.run) {
|
|
1194
|
-
if (merge.run) {
|
|
1195
|
-
delete merge.run
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
script = Object.assign(script, merge)
|
|
1206
|
+
// let merge = await this.construct(cwd, includeFiles, { exclude: [filename], is_sub: false })
|
|
1207
|
+
//
|
|
1208
|
+
// // merge them into the original module
|
|
1209
|
+
//
|
|
1210
|
+
// //exclude "run" attributes
|
|
1211
|
+
//
|
|
1212
|
+
// if (script.run) {
|
|
1213
|
+
// if (merge.run) {
|
|
1214
|
+
// delete merge.run
|
|
1215
|
+
// }
|
|
1216
|
+
// }
|
|
1217
|
+
// script = Object.assign(script, merge)
|
|
1218
|
+
console.log("SCRIPT", script)
|
|
1199
1219
|
|
|
1200
1220
|
return { cwd, script }
|
|
1201
1221
|
}
|
|
@@ -25,6 +25,7 @@ class Local {
|
|
|
25
25
|
return res
|
|
26
26
|
}
|
|
27
27
|
async set(req, ondata, kernel) {
|
|
28
|
+
console.log("SET", req)
|
|
28
29
|
/*
|
|
29
30
|
req := {
|
|
30
31
|
"method": "local.set",
|
|
@@ -95,6 +96,7 @@ class Local {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
const p = new Proxy()
|
|
99
|
+
console.log("> proxy", req)
|
|
98
100
|
await p.start(req, ondata, kernel)
|
|
99
101
|
}
|
|
100
102
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Huggingface {
|
|
2
|
+
async install(req, ondata) {
|
|
3
|
+
await this.kernel.bin.exec({
|
|
4
|
+
message: "conda install -y -c conda-forge huggingface_hub"
|
|
5
|
+
}, ondata)
|
|
6
|
+
}
|
|
7
|
+
async installed() {
|
|
8
|
+
return this.kernel.bin.installed.conda.has("huggingface_hub")
|
|
9
|
+
}
|
|
10
|
+
// env() {
|
|
11
|
+
// return {
|
|
12
|
+
// GIT_CONFIG_GLOBAL: gitconfig_path
|
|
13
|
+
//export CUDA_HOME=/usr/local/cuda
|
|
14
|
+
//export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/usr/local/cuda/extras/CUPTI/lib64
|
|
15
|
+
//export PATH=$PATH:$CUDA_HOME/bin
|
|
16
|
+
//
|
|
17
|
+
// }
|
|
18
|
+
// }
|
|
19
|
+
async uninstall(req, ondata) {
|
|
20
|
+
await this.kernel.bin.exec({
|
|
21
|
+
message: "conda remove huggingface_hub",
|
|
22
|
+
}, ondata)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
module.exports = Huggingface
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const { rimraf } = require('rimraf')
|
|
3
|
+
class Playwright {
|
|
4
|
+
async install(req, ondata) {
|
|
5
|
+
await this.kernel.exec({
|
|
6
|
+
message: [
|
|
7
|
+
// "npm init -y",
|
|
8
|
+
`npm config set prefix ${this.kernel.bin.path("miniconda")}`,
|
|
9
|
+
],
|
|
10
|
+
path: this.kernel.bin.path("playwright"),
|
|
11
|
+
env: {
|
|
12
|
+
PLAYWRIGHT_BROWSERS_PATH: this.kernel.bin.path("playwright/browsers")
|
|
13
|
+
}
|
|
14
|
+
}, ondata)
|
|
15
|
+
await this.kernel.exec({
|
|
16
|
+
message: [
|
|
17
|
+
// "npm init -y",
|
|
18
|
+
"npm install -g playwright@1.49.0",
|
|
19
|
+
// "playwright install chromium",
|
|
20
|
+
],
|
|
21
|
+
//path: this.kernel.bin.path("playwright"),
|
|
22
|
+
env: {
|
|
23
|
+
PLAYWRIGHT_BROWSERS_PATH: this.kernel.bin.path("playwright/browsers")
|
|
24
|
+
}
|
|
25
|
+
}, ondata)
|
|
26
|
+
await this.kernel.exec({
|
|
27
|
+
message: [
|
|
28
|
+
// "npm init -y",
|
|
29
|
+
"playwright install firefox",
|
|
30
|
+
],
|
|
31
|
+
//path: this.kernel.bin.path("playwright"),
|
|
32
|
+
env: {
|
|
33
|
+
PLAYWRIGHT_BROWSERS_PATH: this.kernel.bin.path("playwright/browsers")
|
|
34
|
+
}
|
|
35
|
+
}, ondata)
|
|
36
|
+
// await this.kernel.exec({
|
|
37
|
+
// message: [
|
|
38
|
+
// "npm init -y",
|
|
39
|
+
// "npm install @playwright/browser-chromium@1.47.0",
|
|
40
|
+
// "npm install @playwright/browser-firefox@1.47.0",
|
|
41
|
+
// "npm install @playwright/browser-webkit@1.47.0"
|
|
42
|
+
//// "npx playwright install chromium"
|
|
43
|
+
// ],
|
|
44
|
+
// path: this.kernel.bin.path("playwright"),
|
|
45
|
+
// env: {
|
|
46
|
+
// PLAYWRIGHT_BROWSERS_PATH: this.kernel.bin.path("playwright/browsers")
|
|
47
|
+
// }
|
|
48
|
+
// }, ondata)
|
|
49
|
+
await this.kernel.exec({
|
|
50
|
+
message: [
|
|
51
|
+
"pip install playwright==1.48.0",
|
|
52
|
+
// "playwright install"
|
|
53
|
+
// "npm install playwright --ignore-scripts"
|
|
54
|
+
],
|
|
55
|
+
env: {
|
|
56
|
+
PLAYWRIGHT_BROWSERS_PATH: this.kernel.bin.path("playwright/browsers")
|
|
57
|
+
}
|
|
58
|
+
}, ondata)
|
|
59
|
+
|
|
60
|
+
let pwpath = this.kernel.bin.path("miniconda/lib/node_modules/playwright")
|
|
61
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH = this.kernel.bin.path("playwright/browsers")
|
|
62
|
+
this.kernel.playwright = (await this.kernel.loader.load(pwpath)).resolved
|
|
63
|
+
}
|
|
64
|
+
async installed() {
|
|
65
|
+
let p = this.kernel.bin.path("playwright/browsers")
|
|
66
|
+
let e = await this.kernel.bin.exists(p)
|
|
67
|
+
return e
|
|
68
|
+
}
|
|
69
|
+
async uninstall(req, ondata) {
|
|
70
|
+
ondata({ raw: "cleaning up\r\n" })
|
|
71
|
+
const folder = this.kernel.bin.path("playwright")
|
|
72
|
+
await rimraf(folder)
|
|
73
|
+
ondata({ raw: "finished cleaning up\r\n" })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
module.exports = Playwright
|
package/kernel/index.js
CHANGED
|
@@ -35,7 +35,8 @@ const VARS = {
|
|
|
35
35
|
//const memwatch = require('@airbnb/node-memwatch');
|
|
36
36
|
class Kernel {
|
|
37
37
|
//schema = ">=1.0.0"
|
|
38
|
-
schema = "<=2.1.0"
|
|
38
|
+
//schema = "<=2.1.0"
|
|
39
|
+
schema = "<=2.2.0"
|
|
39
40
|
constructor(store) {
|
|
40
41
|
this.fetch = fetch
|
|
41
42
|
this.store = store
|
|
@@ -222,6 +223,26 @@ class Kernel {
|
|
|
222
223
|
await waitOn(options)
|
|
223
224
|
}
|
|
224
225
|
|
|
226
|
+
async synchronize_proxies() {
|
|
227
|
+
let proxy_registry_path = path.resolve(this.homedir, "proxy_registry")
|
|
228
|
+
let exists = await this.exists(proxy_registry_path)
|
|
229
|
+
if (exists) {
|
|
230
|
+
await this.exec({
|
|
231
|
+
message: "git pull",
|
|
232
|
+
path: proxy_registry_path,
|
|
233
|
+
}, (e) => {
|
|
234
|
+
process.stdout.write(e.raw)
|
|
235
|
+
})
|
|
236
|
+
} else {
|
|
237
|
+
await this.exec({
|
|
238
|
+
message: "git clone https://github.com/pinokiocomputer/proxy_registry"
|
|
239
|
+
path: this.homedir
|
|
240
|
+
}, (e) => {
|
|
241
|
+
process.stdout.write(e.raw)
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
225
246
|
async init(options) {
|
|
226
247
|
let home = this.store.get("home")
|
|
227
248
|
|
|
@@ -337,13 +358,18 @@ class Kernel {
|
|
|
337
358
|
system,
|
|
338
359
|
platform: this.platform,
|
|
339
360
|
arch: this.arch,
|
|
361
|
+
proxy: (port) => {
|
|
362
|
+
return this.api.get_proxy_url("/proxy", port)
|
|
363
|
+
},
|
|
340
364
|
...info
|
|
341
365
|
})
|
|
342
366
|
await this.update_sysinfo()
|
|
343
367
|
}
|
|
344
368
|
|
|
345
369
|
|
|
346
|
-
|
|
370
|
+
let pwpath = this.bin.path("miniconda/lib/node_modules/playwright")
|
|
371
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH = this.bin.path("playwright/browsers")
|
|
372
|
+
this.playwright = (await this.loader.load(pwpath)).resolved
|
|
347
373
|
|
|
348
374
|
// await this.template.init()
|
|
349
375
|
|
package/kernel/lproxy.js
CHANGED
|
@@ -16,6 +16,7 @@ const proxies = proxy.all()
|
|
|
16
16
|
*/
|
|
17
17
|
const httpProxy = require('http-proxy');
|
|
18
18
|
const portfinder = require('portfinder-cp');
|
|
19
|
+
const cors = require('cors')
|
|
19
20
|
const os = require('os')
|
|
20
21
|
class Lproxy {
|
|
21
22
|
constructor() {
|
|
@@ -32,23 +33,47 @@ class Lproxy {
|
|
|
32
33
|
let o = Object.assign({
|
|
33
34
|
target: url,
|
|
34
35
|
ws: true,
|
|
35
|
-
|
|
36
|
+
changeOrigin: true,
|
|
37
|
+
// localAddress: '0.0.0.0',
|
|
36
38
|
}, options)
|
|
37
39
|
console.log("proxy object", o)
|
|
38
40
|
const proxy = httpProxy.createProxyServer(o)
|
|
41
|
+
|
|
39
42
|
// get a new port
|
|
40
43
|
if (!port) {
|
|
41
44
|
port = await portfinder.getPortPromise({
|
|
42
45
|
//port: 42420
|
|
43
|
-
port:
|
|
46
|
+
port: 50000
|
|
47
|
+
//port: 44000
|
|
44
48
|
})
|
|
45
49
|
}
|
|
46
50
|
proxy.listen(port)
|
|
47
51
|
proxy.on('close', (res, socket, head) => {
|
|
48
52
|
console.log('Client disconnected');
|
|
49
53
|
});
|
|
50
|
-
proxy.on(
|
|
51
|
-
|
|
54
|
+
proxy.on("error", function (err, req, res) {
|
|
55
|
+
res.writeHead(500, {
|
|
56
|
+
'Content-Type': 'text/plain',
|
|
57
|
+
});
|
|
58
|
+
res.end(err.stack)
|
|
59
|
+
});
|
|
60
|
+
proxy.on('proxyRes', (proxyRes, req, res) => {
|
|
61
|
+
console.log("proxyRes", { proxyRes, req, res })
|
|
62
|
+
if (req.headers.origin) {
|
|
63
|
+
res.setHeader('access-control-allow-origin', req.headers.origin);
|
|
64
|
+
}
|
|
65
|
+
res.setHeader('access-control-allow-credentials', 'true');
|
|
66
|
+
if (req.headers['access-control-request-method']) {
|
|
67
|
+
res.setHeader('access-control-allow-methods', req.headers['access-control-request-method']);
|
|
68
|
+
}
|
|
69
|
+
if (req.headers['access-control-request-headers']) {
|
|
70
|
+
res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers']);
|
|
71
|
+
}
|
|
72
|
+
res.setHeader('access-control-max-age', 60 * 60 * 24 * 30);
|
|
73
|
+
if (req.method === 'OPTIONS') {
|
|
74
|
+
res.send(200);
|
|
75
|
+
res.end();
|
|
76
|
+
}
|
|
52
77
|
});
|
|
53
78
|
|
|
54
79
|
this.proxies[url] = proxy
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -449,6 +449,10 @@ class Server {
|
|
|
449
449
|
name: "py"
|
|
450
450
|
}, {
|
|
451
451
|
name: "cloudflared"
|
|
452
|
+
}, {
|
|
453
|
+
name: "playwright"
|
|
454
|
+
}, {
|
|
455
|
+
name: "huggingface"
|
|
452
456
|
}])
|
|
453
457
|
|
|
454
458
|
let requirements_pending = !this.kernel.bin.installed_initialized
|
|
@@ -722,6 +726,10 @@ class Server {
|
|
|
722
726
|
name: "py"
|
|
723
727
|
}, {
|
|
724
728
|
name: "cloudflared"
|
|
729
|
+
}, {
|
|
730
|
+
name: "playwright"
|
|
731
|
+
}, {
|
|
732
|
+
name: "huggingface"
|
|
725
733
|
}])
|
|
726
734
|
// if (platform === "linux") {
|
|
727
735
|
// requirements.push({
|
|
@@ -1138,6 +1146,7 @@ class Server {
|
|
|
1138
1146
|
if (this.cloudflare_pub) {
|
|
1139
1147
|
qr_cloudflare = await QRCode.toDataURL(this.cloudflare_pub)
|
|
1140
1148
|
}
|
|
1149
|
+
console.log("****************", { items })
|
|
1141
1150
|
|
|
1142
1151
|
res.render("index", {
|
|
1143
1152
|
home_url: `http://localhost:${this.port}`,
|
|
@@ -2115,11 +2124,12 @@ class Server {
|
|
|
2115
2124
|
icon: "ollama.webp",
|
|
2116
2125
|
name: "Ollama",
|
|
2117
2126
|
target: 'http://127.0.0.1:11434',
|
|
2118
|
-
port:
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2127
|
+
port: 44002
|
|
2128
|
+
}, {
|
|
2129
|
+
icon: "lmstudio.jpg",
|
|
2130
|
+
name: "LMStudio",
|
|
2131
|
+
target: 'http://127.0.0.1:1234',
|
|
2132
|
+
port: 44003
|
|
2123
2133
|
}]
|
|
2124
2134
|
for(let i=0; i<proxies.length; i++) {
|
|
2125
2135
|
proxies[i].running = false
|
|
@@ -2253,7 +2263,7 @@ class Server {
|
|
|
2253
2263
|
await this.kernel.api.startProxy("/", `http://127.0.0.1:${this.port}`, "/")
|
|
2254
2264
|
}
|
|
2255
2265
|
} else {
|
|
2256
|
-
await this.kernel.api.startProxy("/", `http://127.0.0.1:${this.port}`, "/")
|
|
2266
|
+
await this.kernel.api.startProxy("/", `http://127.0.0.1:${this.port}`, "/", { port: 44001 })
|
|
2257
2267
|
}
|
|
2258
2268
|
console.log("started proxy")
|
|
2259
2269
|
} else if (req.body.type === "cloudflare") {
|
|
@@ -2311,6 +2321,9 @@ class Server {
|
|
|
2311
2321
|
const items = await Util.parse_env_detail(filepath)
|
|
2312
2322
|
let p = path.resolve(this.kernel.homedir, env_path, "pinokio.js")
|
|
2313
2323
|
let config = (await this.kernel.loader.load(p)).resolved
|
|
2324
|
+
|
|
2325
|
+
|
|
2326
|
+
console.log(">>>>>>>>>>>>>>>>>", { config })
|
|
2314
2327
|
if (config.icon) {
|
|
2315
2328
|
config.icon = `/${env_path}/${config.icon}?raw=true`
|
|
2316
2329
|
}
|
|
@@ -2320,6 +2333,35 @@ class Server {
|
|
|
2320
2333
|
name = env_path.split("/")[1]
|
|
2321
2334
|
}
|
|
2322
2335
|
|
|
2336
|
+
//if (config.run) {
|
|
2337
|
+
// let configStr = await fs.promises.readFile(p, "utf8")
|
|
2338
|
+
// res.render("disposable", {
|
|
2339
|
+
// home: null,
|
|
2340
|
+
// config,
|
|
2341
|
+
// name,
|
|
2342
|
+
// init: req.query ? req.query.init : null,
|
|
2343
|
+
// editorpath,
|
|
2344
|
+
// items,
|
|
2345
|
+
// theme: this.theme,
|
|
2346
|
+
// filepath,
|
|
2347
|
+
// agent: this.agent,
|
|
2348
|
+
// path: "/api/" + name + "/pinokio.js",
|
|
2349
|
+
// _path: "/_api/" + name,
|
|
2350
|
+
// str: configStr
|
|
2351
|
+
// })
|
|
2352
|
+
//} else {
|
|
2353
|
+
// res.render("env_editor", {
|
|
2354
|
+
// home: null,
|
|
2355
|
+
// config,
|
|
2356
|
+
// name,
|
|
2357
|
+
// init: req.query ? req.query.init : null,
|
|
2358
|
+
// editorpath,
|
|
2359
|
+
// items,
|
|
2360
|
+
// theme: this.theme,
|
|
2361
|
+
// filepath,
|
|
2362
|
+
// agent: this.agent,
|
|
2363
|
+
// })
|
|
2364
|
+
//}
|
|
2323
2365
|
res.render("env_editor", {
|
|
2324
2366
|
home: null,
|
|
2325
2367
|
config,
|
|
@@ -2331,6 +2373,7 @@ class Server {
|
|
|
2331
2373
|
filepath,
|
|
2332
2374
|
agent: this.agent,
|
|
2333
2375
|
})
|
|
2376
|
+
|
|
2334
2377
|
}))
|
|
2335
2378
|
this.app.get("/pre/api/:name", ex(async (req, res) => {
|
|
2336
2379
|
let p = path.resolve(this.kernel.homedir, "api", req.params.name, "pinokio.js")
|
|
Binary file
|
package/server/public/style.css
CHANGED
|
@@ -1618,9 +1618,10 @@ footer.status {
|
|
|
1618
1618
|
flex-grow: 1;
|
|
1619
1619
|
}
|
|
1620
1620
|
#error-screen-footer {
|
|
1621
|
+
display: flex;
|
|
1621
1622
|
padding: 10px;
|
|
1622
1623
|
box-sizing: border-box;
|
|
1623
|
-
text-align:
|
|
1624
|
+
text-align: right;
|
|
1624
1625
|
background: royalblue;
|
|
1625
1626
|
}
|
|
1626
1627
|
#error-screen-close {
|
|
@@ -1640,7 +1641,10 @@ footer.status {
|
|
|
1640
1641
|
padding: 15px 30px;
|
|
1641
1642
|
font-weight: bold;
|
|
1642
1643
|
background: firebrick;
|
|
1644
|
+
/*
|
|
1643
1645
|
border-radius: 50px;
|
|
1646
|
+
*/
|
|
1647
|
+
margin: 5px;
|
|
1644
1648
|
text-transform: uppercase;
|
|
1645
1649
|
}
|
|
1646
1650
|
|
|
@@ -1651,3 +1655,10 @@ mark{
|
|
|
1651
1655
|
display: inline-block;
|
|
1652
1656
|
margin: 5px 0;
|
|
1653
1657
|
}
|
|
1658
|
+
#close-error-screen {
|
|
1659
|
+
border-radius: 50px;
|
|
1660
|
+
background: white !important;
|
|
1661
|
+
padding: 15px !important;
|
|
1662
|
+
color: royalblue !important;
|
|
1663
|
+
cursor: pointer;
|
|
1664
|
+
}
|
|
@@ -590,7 +590,7 @@ body.dark .loader-container {
|
|
|
590
590
|
</div>
|
|
591
591
|
-->
|
|
592
592
|
<div class='grid-btns'>
|
|
593
|
-
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-
|
|
593
|
+
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-question"></i></div><div>Help</div></a>
|
|
594
594
|
<button class='btn2' id='genlog'><div><i class="fa-solid fa-laptop-code"></i></div><div>Debug</div></button>
|
|
595
595
|
<a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><div><i class="fa-solid fa-download"></i></div><div>Download logs</div></a>
|
|
596
596
|
<a class='btn2' href="/"><div><i class="fa-solid fa-house"></i></div><div>Home</div></a>
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
<html class='columns'>
|
|
2
|
+
<head>
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
|
4
|
+
<script src="/hotkeys.min.js"></script>
|
|
5
|
+
<script src="/ace/ace.js"></script>
|
|
6
|
+
<script src="/noty.js"></script>
|
|
7
|
+
<script src="/notyq.js"></script>
|
|
8
|
+
<script src="/xterm.js"></script>
|
|
9
|
+
<script src="/xterm-addon-fit.js"></script>
|
|
10
|
+
<script src="/xterm-addon-web-links.js"></script>
|
|
11
|
+
<script src="/xterm-theme.js"></script>
|
|
12
|
+
<script src="/xterm-addon-search.js"></script>
|
|
13
|
+
<script src="/xterm-addon-search-bar.js"></script>
|
|
14
|
+
<script src="/sweetalert2.js"></script>
|
|
15
|
+
<script src="/Socket.js"></script>
|
|
16
|
+
<script src="/common.js"></script>
|
|
17
|
+
<script src="/he.js"></script>
|
|
18
|
+
<script src="/opener.js"></script>
|
|
19
|
+
<script src="/nav.js"></script>
|
|
20
|
+
<script src="/autoexpand.js"></script>
|
|
21
|
+
<script src="/modalinput.js"></script>
|
|
22
|
+
<script src="/simplemodal.js"></script>
|
|
23
|
+
<script src="/waitmodal.js"></script>
|
|
24
|
+
<script src="/notifyinput.js"></script>
|
|
25
|
+
<script src="/report.js"></script>
|
|
26
|
+
<link href="/xterm.min.css" rel="stylesheet" />
|
|
27
|
+
<link href="/css/fontawesome.min.css" rel="stylesheet">
|
|
28
|
+
<link href="/css/solid.min.css" rel="stylesheet">
|
|
29
|
+
<link href="/css/regular.min.css" rel="stylesheet">
|
|
30
|
+
<link href="/css/brands.min.css" rel="stylesheet">
|
|
31
|
+
<link href="/style.css" rel="stylesheet"/>
|
|
32
|
+
<link href="/noty.css" rel="stylesheet"/>
|
|
33
|
+
<% if (agent === "electron") { %>
|
|
34
|
+
<link href="/electron.css" rel="stylesheet"/>
|
|
35
|
+
<% } %>
|
|
36
|
+
<style>
|
|
37
|
+
body.frozen {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
height: 100%;
|
|
41
|
+
}
|
|
42
|
+
#table > table {
|
|
43
|
+
border-left: 2px solid black;
|
|
44
|
+
padding-left: 5px;
|
|
45
|
+
margin: 10px 0;
|
|
46
|
+
}
|
|
47
|
+
.highlight {
|
|
48
|
+
background: yellow;
|
|
49
|
+
width: 100px;
|
|
50
|
+
}
|
|
51
|
+
th {
|
|
52
|
+
background: rgba(0,0,0,0.1);
|
|
53
|
+
padding: 10px;
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
}
|
|
56
|
+
td {
|
|
57
|
+
font-size: 12px;
|
|
58
|
+
padding: 10px;
|
|
59
|
+
}
|
|
60
|
+
td.val {
|
|
61
|
+
background: rgba(0,0,0,0.06);
|
|
62
|
+
}
|
|
63
|
+
td.key {
|
|
64
|
+
background: rgba(0,0,0,0.1);
|
|
65
|
+
}
|
|
66
|
+
.button {
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
background: rgba(0,0,0,0.8);
|
|
69
|
+
color: white;
|
|
70
|
+
padding: 5px 10px;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
}
|
|
73
|
+
.button:hover {
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
<% if (init) { %>
|
|
77
|
+
main {
|
|
78
|
+
width: 100%;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
max-width: 600px;
|
|
81
|
+
padding: 20px;
|
|
82
|
+
margin: 0 auto;
|
|
83
|
+
}
|
|
84
|
+
<% } else { %>
|
|
85
|
+
#editor {
|
|
86
|
+
width: 100%;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
max-width: 600px;
|
|
89
|
+
padding: 20px;
|
|
90
|
+
margin: 0 auto;
|
|
91
|
+
}
|
|
92
|
+
<% } %>
|
|
93
|
+
|
|
94
|
+
header .btn {
|
|
95
|
+
font-family: Sans-serif;
|
|
96
|
+
display: block;
|
|
97
|
+
margin-left: 10px;
|
|
98
|
+
font-style: normal;
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
font-weight: normal;
|
|
101
|
+
padding: 5px 10px;
|
|
102
|
+
}
|
|
103
|
+
.disabled {
|
|
104
|
+
opacity: 0.7;
|
|
105
|
+
}
|
|
106
|
+
body.dark .env-item input[type=text] {
|
|
107
|
+
background: white;
|
|
108
|
+
}
|
|
109
|
+
.env-item input[type=text] {
|
|
110
|
+
width: 100%;
|
|
111
|
+
padding: 10px;
|
|
112
|
+
box-sizing: border-box;
|
|
113
|
+
border: none;
|
|
114
|
+
background: rgba(0,0,100, 0.1);
|
|
115
|
+
}
|
|
116
|
+
body.dark .env-item label {
|
|
117
|
+
color: cornflowerblue;
|
|
118
|
+
}
|
|
119
|
+
.env-item label {
|
|
120
|
+
color: royalblue;
|
|
121
|
+
display: block;
|
|
122
|
+
margin-bottom: 5px;
|
|
123
|
+
font-weight: bold;
|
|
124
|
+
font-size: 18px;
|
|
125
|
+
}
|
|
126
|
+
.env-item {
|
|
127
|
+
padding-top: 10px;
|
|
128
|
+
padding-bottom: 30px;
|
|
129
|
+
}
|
|
130
|
+
body.dark .comment {
|
|
131
|
+
border-left: 2px solid white;
|
|
132
|
+
}
|
|
133
|
+
.comment {
|
|
134
|
+
opacity: 0.8;
|
|
135
|
+
border-left: 2px solid black;
|
|
136
|
+
padding-left: 10px;
|
|
137
|
+
}
|
|
138
|
+
#save {
|
|
139
|
+
background: royalblue;
|
|
140
|
+
}
|
|
141
|
+
a#customize {
|
|
142
|
+
text-decoration: none;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
}
|
|
145
|
+
.e {
|
|
146
|
+
padding: 10px 0;
|
|
147
|
+
}
|
|
148
|
+
#editor {
|
|
149
|
+
padding: 20px 0;
|
|
150
|
+
}
|
|
151
|
+
.center {
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
justify-content: center;
|
|
155
|
+
}
|
|
156
|
+
body {
|
|
157
|
+
display: block;
|
|
158
|
+
}
|
|
159
|
+
.card {
|
|
160
|
+
display: flex;
|
|
161
|
+
padding: 10px 0;
|
|
162
|
+
box-sizing: border-box;
|
|
163
|
+
}
|
|
164
|
+
.card img {
|
|
165
|
+
width: 100px;
|
|
166
|
+
border-radius: 10px;
|
|
167
|
+
}
|
|
168
|
+
.card .description {
|
|
169
|
+
opacity: 0.8;
|
|
170
|
+
}
|
|
171
|
+
.card .title {
|
|
172
|
+
font-weight: bold;
|
|
173
|
+
font-size: 30px;
|
|
174
|
+
letter-spacing: -1px;
|
|
175
|
+
}
|
|
176
|
+
.card .col {
|
|
177
|
+
padding-left: 10px;
|
|
178
|
+
}
|
|
179
|
+
h1 {
|
|
180
|
+
font-size: 50px;
|
|
181
|
+
font-family: georgia;
|
|
182
|
+
font-style: italic;
|
|
183
|
+
padding-bottom: 20px;
|
|
184
|
+
}
|
|
185
|
+
footer {
|
|
186
|
+
background: none;
|
|
187
|
+
padding: 0;
|
|
188
|
+
text-align: right;
|
|
189
|
+
display: flex;
|
|
190
|
+
margin: 10px 0 20px 0;
|
|
191
|
+
}
|
|
192
|
+
footer .btn {
|
|
193
|
+
margin-left: 10px;
|
|
194
|
+
display: inline-block;
|
|
195
|
+
-webkit-appearance: button;
|
|
196
|
+
-moz-appearance: button;
|
|
197
|
+
appearance: button;
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
footer .btn.go-home {
|
|
201
|
+
margin-left: 0px;
|
|
202
|
+
}
|
|
203
|
+
.btn.home-button {
|
|
204
|
+
background: none !important;
|
|
205
|
+
margin: 0 !important;
|
|
206
|
+
font-size: 16px !important;
|
|
207
|
+
}
|
|
208
|
+
#editor {
|
|
209
|
+
height: auto;
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
212
|
+
<script>
|
|
213
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
214
|
+
const n = new N()
|
|
215
|
+
document.querySelector("#save").addEventListener("click", async (e) => {
|
|
216
|
+
// save
|
|
217
|
+
const vals = {}
|
|
218
|
+
const els = document.querySelectorAll(".env-item")
|
|
219
|
+
for(let el of els) {
|
|
220
|
+
let input = el.querySelector("input[type=text]")
|
|
221
|
+
vals[input.id] = input.value
|
|
222
|
+
}
|
|
223
|
+
console.log("vals", vals)
|
|
224
|
+
let res = await fetch("/env", {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
"Content-Type": "application/json"
|
|
228
|
+
},
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
filepath: location.pathname.slice(5),
|
|
231
|
+
vals
|
|
232
|
+
})
|
|
233
|
+
}).then((res) => {
|
|
234
|
+
return res.json()
|
|
235
|
+
})
|
|
236
|
+
console.log("res" ,res)
|
|
237
|
+
<% if (init && name) { %>
|
|
238
|
+
location.href = "/pinokio/browser/<%=name%>"
|
|
239
|
+
<% } else { %>
|
|
240
|
+
n.Noty({
|
|
241
|
+
text: `Updated!`
|
|
242
|
+
})
|
|
243
|
+
<% } %>
|
|
244
|
+
})
|
|
245
|
+
<% if (init) { %>
|
|
246
|
+
/*
|
|
247
|
+
document.querySelector("#customize").addEventListener("click", (e) => {
|
|
248
|
+
e.preventDefault()
|
|
249
|
+
e.stopPropagation()
|
|
250
|
+
document.querySelector("#editor").classList.toggle("hidden")
|
|
251
|
+
})
|
|
252
|
+
let editor = ace.edit("editor");
|
|
253
|
+
editor.setTheme("ace/theme/nord_dark");
|
|
254
|
+
editor.setOptions({
|
|
255
|
+
maxLines: Infinity, // set to a large number
|
|
256
|
+
minLines: 1 // set to a small number
|
|
257
|
+
});
|
|
258
|
+
editor.session.setMode("ace/mode/javascript");
|
|
259
|
+
editor.clearSelection()
|
|
260
|
+
*/
|
|
261
|
+
<% } %>
|
|
262
|
+
})
|
|
263
|
+
</script>
|
|
264
|
+
</head>
|
|
265
|
+
<body class='<%=theme%> <%=init ? 'center' : ''%>'>
|
|
266
|
+
<main>
|
|
267
|
+
<% if (init) { %>
|
|
268
|
+
<div>
|
|
269
|
+
<h1>Run</h1>
|
|
270
|
+
<% if (config) { %>
|
|
271
|
+
<div class='card'>
|
|
272
|
+
<% if (config.icon) { %>
|
|
273
|
+
<img src="<%=config.icon%>">
|
|
274
|
+
<% } %>
|
|
275
|
+
<div class='col'>
|
|
276
|
+
<div class='title'><%=config.title%></div>
|
|
277
|
+
<div class='description'><%=config.description%></div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
<% } %>
|
|
281
|
+
<footer>
|
|
282
|
+
<a href="/" class='btn go-home'><i class='fa-solid fa-home'></i> home</a>
|
|
283
|
+
<div class='flexible'></div>
|
|
284
|
+
<a id='customize' class='btn' target="_blank" href='<%=_path%>'><i class='fa-solid fa-code'></i> View Script</a>
|
|
285
|
+
<!--
|
|
286
|
+
<button id='customize' class='btn'><i class="fa-solid fa-code"></i> View Script</button>
|
|
287
|
+
-->
|
|
288
|
+
<a id='save' href="<%=path%>" class='btn'>
|
|
289
|
+
<span class='save'><i class="fa-solid fa-check"></i> Run</span>
|
|
290
|
+
</a>
|
|
291
|
+
</footer>
|
|
292
|
+
</div>
|
|
293
|
+
<% } else { %>
|
|
294
|
+
<header class='navheader grabbable'>
|
|
295
|
+
<h1>
|
|
296
|
+
<% if (home) { %>
|
|
297
|
+
<a class='home-button home' href="/"><i class='fa-solid fa-home'></i></a>
|
|
298
|
+
<% } %>
|
|
299
|
+
<div class='flexible'></div>
|
|
300
|
+
<div class='nav-btns'>
|
|
301
|
+
<a class='btn' href="<%=editorpath%>"><i class="fa-solid fa-pen-to-square"></i> Edit file</a>
|
|
302
|
+
<button class='btn' id='open-fs' data-filepath="<%=filepath%>"><i class="fa-solid fa-eye"></i> Open in File Explorer</button>
|
|
303
|
+
<div id='save' class='btn'>
|
|
304
|
+
<span class='save'><i class="fa-solid fa-check"></i> Save</span>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
</h1>
|
|
308
|
+
</header>
|
|
309
|
+
<% } %>
|
|
310
|
+
<div id='editor' class='<%=init ? "hidden" : ''%>'><%=str%></div>
|
|
311
|
+
</main>
|
|
312
|
+
</body>
|
|
313
|
+
</html>
|
package/server/views/editor.ejs
CHANGED
|
@@ -739,6 +739,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
739
739
|
}
|
|
740
740
|
})
|
|
741
741
|
}
|
|
742
|
+
document.querySelector("#close-error-screen").addEventListener('click', (e) => {
|
|
743
|
+
document.querySelector("#error-screen").classList.add("hidden")
|
|
744
|
+
})
|
|
742
745
|
})
|
|
743
746
|
const reloadMemory = async () => {
|
|
744
747
|
let url = "/pinokio/memory?filepath=" + encodeURI("<%-filepath%>")
|
|
@@ -888,8 +891,12 @@ const reloadMemory = async () => {
|
|
|
888
891
|
<div id='error-screen' class='hidden'>
|
|
889
892
|
<pre></pre>
|
|
890
893
|
<div id='error-screen-footer'>
|
|
894
|
+
<a class='btn' id='close-error-screen'>
|
|
895
|
+
<div><i class="fa-solid fa-xmark"></i></div>
|
|
896
|
+
</a>
|
|
897
|
+
<div class='flexible'></div>
|
|
891
898
|
<a class='btn' href="https://portal.pinokio.computer" target="_blank">
|
|
892
|
-
<div><i class="fa-solid fa-
|
|
899
|
+
<div><i class="fa-solid fa-question"></i> Ask Community</div>
|
|
893
900
|
</a>
|
|
894
901
|
</div>
|
|
895
902
|
</div>
|
package/server/views/explore.ejs
CHANGED
|
@@ -132,7 +132,7 @@ body main iframe {
|
|
|
132
132
|
<div class='flexible'></div>
|
|
133
133
|
<div class='nav-btns'>
|
|
134
134
|
<a class='btn2' id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
|
|
135
|
-
<a class='btn2' href="
|
|
135
|
+
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-question"></i></div><div>Help</div></a>
|
|
136
136
|
<a class='btn2' href="/?mode=settings"><div><i class="fa-solid fa-gear"></i></div><div>Settings</div></a>
|
|
137
137
|
<button id='new-window' title='open a new window' class='btn2'><div><i class="fa-solid fa-plus"></i></div><div>Window</div></button>
|
|
138
138
|
</div>
|
|
@@ -703,6 +703,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
703
703
|
}
|
|
704
704
|
})
|
|
705
705
|
}
|
|
706
|
+
document.querySelector("#close-error-screen").addEventListener('click', (e) => {
|
|
707
|
+
document.querySelector("#error-screen").classList.add("hidden")
|
|
708
|
+
})
|
|
706
709
|
})
|
|
707
710
|
const reloadMemory = async () => {
|
|
708
711
|
let url = "/pinokio/memory?filepath=<%=encodedFilePath%>"
|
|
@@ -826,8 +829,12 @@ const reloadMemory = async () => {
|
|
|
826
829
|
<div id='error-screen' class='hidden'>
|
|
827
830
|
<pre></pre>
|
|
828
831
|
<div id='error-screen-footer'>
|
|
832
|
+
<a class='btn' id='close-error-screen'>
|
|
833
|
+
<div><i class="fa-solid fa-xmark"></i></div>
|
|
834
|
+
</a>
|
|
835
|
+
<div class='flexible'></div>
|
|
829
836
|
<a class='btn' href="https://portal.pinokio.computer" target="_blank">
|
|
830
|
-
<div><i class="fa-solid fa-
|
|
837
|
+
<div><i class="fa-solid fa-question"></i> Ask Community</div>
|
|
831
838
|
</a>
|
|
832
839
|
</div>
|
|
833
840
|
</div>
|
package/server/views/index.ejs
CHANGED
|
@@ -183,7 +183,7 @@ body.dark .browser-options-row {
|
|
|
183
183
|
<% if (ishome) { %>
|
|
184
184
|
<div class='nav-btns'>
|
|
185
185
|
<a class='btn2' id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
|
|
186
|
-
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-
|
|
186
|
+
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-question"></i></div><div>Help</div></a>
|
|
187
187
|
<% if (agent === "electron") { %>
|
|
188
188
|
<a class='btn2' data-filepath="<%=filepath%>"><div><i class="fa-regular fa-folder-open"></i></div><div>Files</div></a>
|
|
189
189
|
<% } %>
|
package/server/views/proxy.ejs
CHANGED
|
@@ -219,7 +219,7 @@ body.dark .browser-options-row {
|
|
|
219
219
|
<% if (proxy) { %>
|
|
220
220
|
<div id='wifi-stop' data-proxy="<%=proxy.proxy%>" class='btn'><div><i class="fa-solid fa-stop"></i> Stop</div></div>
|
|
221
221
|
<% } else { %>
|
|
222
|
-
<div id='wifi' class='btn'><div><i class="fa-solid fa-play"></i> Start</div></div>
|
|
222
|
+
<div id='wifi' class='btn'><div><i class="fa-solid fa-play"></i> Start at port 44001</div></div>
|
|
223
223
|
<div id='wifi-starting' class='btn disabled hidden'><div><i class="fa-solid fa-circle-notch fa-spin"></i> Starting</div></div>
|
|
224
224
|
<% } %>
|
|
225
225
|
</div>
|
|
@@ -242,7 +242,7 @@ body.dark .browser-options-row {
|
|
|
242
242
|
<% if (item.running) { %>
|
|
243
243
|
<div data-port="<%=item.port%>" data-name="<%=item.name%>" data-target="<%=item.target%>" class='stop btn'><i class="fa-solid fa-stop"></i> Stop</div>
|
|
244
244
|
<% } else { %>
|
|
245
|
-
<div data-port="<%=item.port%>" data-name="<%=item.name%>" data-target="<%=item.target%>" class='start btn'><i class="fa-solid fa-play"></i> Start
|
|
245
|
+
<div data-port="<%=item.port%>" data-name="<%=item.name%>" data-target="<%=item.target%>" class='start btn'><i class="fa-solid fa-play"></i> Start at port <%=item.port%></div>
|
|
246
246
|
<% } %>
|
|
247
247
|
</div>
|
|
248
248
|
</div>
|
|
@@ -234,7 +234,7 @@ hr {
|
|
|
234
234
|
<div class='flexible'></div>
|
|
235
235
|
<div class='nav-btns'>
|
|
236
236
|
<a class='btn2' id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
|
|
237
|
-
<a class='btn2' href="
|
|
237
|
+
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-question"></i></div><div>Help</div></a>
|
|
238
238
|
<a class='btn2' href="/?mode=settings"><div><i class="fa-solid fa-gear"></i></div><div>Settings</div></a>
|
|
239
239
|
<button id='new-window' title='open a new window' class='btn2'><div><i class="fa-solid fa-plus"></i></div><div>Window</div></button>
|
|
240
240
|
</div>
|