pinokiod 3.231.0 → 3.232.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/kernel/bin/cuda.js +83 -76
- package/kernel/bin/setup.js +2 -0
- package/kernel/prototype.js +13 -0
- package/kernel/router/index.js +41 -0
- package/package.json +1 -1
- package/server/index.js +92 -31
- package/server/public/container-tab-link.js +115 -0
- package/server/public/style.css +4 -0
- package/server/public/tab-link-popover.css +118 -0
- package/server/public/tab-link-popover.js +1391 -0
- package/server/views/app.ejs +79 -1626
- package/server/views/container.ejs +3 -0
- package/server/views/index.ejs +6 -0
- package/server/views/net.ejs +449 -8
- package/server/views/network.ejs +5 -3
- package/server/views/partials/dynamic.ejs +1 -1
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/partials/running.ejs +1 -1
package/kernel/bin/cuda.js
CHANGED
|
@@ -3,100 +3,107 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
class Cuda {
|
|
5
5
|
async install(req, ondata) {
|
|
6
|
-
if (this.kernel.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for(let item of deactivate_list) {
|
|
26
|
-
const old_name = path.resolve(folder, item)
|
|
27
|
-
const new_name = path.resolve(folder, item + ".disabled")
|
|
28
|
-
console.log("rename", { old_name, new_name })
|
|
29
|
-
await fs.promises.rename(old_name, new_name)
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
await this.kernel.bin.exec({
|
|
33
|
-
message: [
|
|
34
|
-
"conda clean -y --all",
|
|
35
|
-
"conda install -y cudnn -c conda-forge",
|
|
36
|
-
]
|
|
37
|
-
}, ondata)
|
|
38
|
-
await this.kernel.bin.exec({
|
|
39
|
-
message: [
|
|
40
|
-
"conda clean -y --all",
|
|
41
|
-
"conda install -y nvidia/label/cuda-12.8.1::cuda"
|
|
6
|
+
if (this.kernel.gpu === "nvidia") {
|
|
7
|
+
if (this.kernel.platform === "win32") {
|
|
8
|
+
await this.kernel.bin.exec({
|
|
9
|
+
message: [
|
|
10
|
+
"conda clean -y --all",
|
|
11
|
+
"conda install -y cudnn libzlib-wapi -c conda-forge",
|
|
12
|
+
]
|
|
13
|
+
}, ondata)
|
|
14
|
+
await this.kernel.bin.exec({
|
|
15
|
+
message: [
|
|
16
|
+
"conda clean -y --all",
|
|
17
|
+
"conda install -y nvidia/label/cuda-12.8.1::cuda"
|
|
18
|
+
]
|
|
19
|
+
}, ondata)
|
|
20
|
+
const folder = this.kernel.bin.path("miniconda/etc/conda/activate.d")
|
|
21
|
+
let deactivate_list = [
|
|
22
|
+
"~cuda-nvcc_activate.bat",
|
|
23
|
+
"vs2019_compiler_vars.bat",
|
|
24
|
+
"vs2022_compiler_vars.bat",
|
|
42
25
|
]
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
for(let item of deactivate_list) {
|
|
27
|
+
const old_name = path.resolve(folder, item)
|
|
28
|
+
const new_name = path.resolve(folder, item + ".disabled")
|
|
29
|
+
console.log("rename", { old_name, new_name })
|
|
30
|
+
await fs.promises.rename(old_name, new_name)
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
45
33
|
await this.kernel.bin.exec({
|
|
46
34
|
message: [
|
|
47
|
-
"conda
|
|
35
|
+
"conda clean -y --all",
|
|
36
|
+
"conda install -y cudnn -c conda-forge",
|
|
48
37
|
]
|
|
49
38
|
}, ondata)
|
|
39
|
+
await this.kernel.bin.exec({
|
|
40
|
+
message: [
|
|
41
|
+
"conda clean -y --all",
|
|
42
|
+
"conda install -y nvidia/label/cuda-12.8.1::cuda"
|
|
43
|
+
]
|
|
44
|
+
}, ondata)
|
|
45
|
+
if (this.kernel.platform === "linux") {
|
|
46
|
+
await this.kernel.bin.exec({
|
|
47
|
+
message: [
|
|
48
|
+
"conda install -y -c conda-forge nccl"
|
|
49
|
+
]
|
|
50
|
+
}, ondata)
|
|
51
|
+
}
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
async installed() {
|
|
54
|
-
if (this.kernel.
|
|
55
|
-
if (this.kernel.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
56
|
+
if (this.kernel.gpu === "nvidia") {
|
|
57
|
+
if (this.kernel.platform === 'win32') {
|
|
58
|
+
if (this.kernel.bin.installed.conda.has("cudnn") && this.kernel.bin.installed.conda.has("cuda") && this.kernel.bin.installed.conda.has("libzlib-wapi")) {
|
|
59
|
+
let version = this.kernel.bin.installed.conda_versions.cuda
|
|
60
|
+
if (version) {
|
|
61
|
+
let coerced = semver.coerce(version)
|
|
62
|
+
console.log("cuda version", coerced)
|
|
63
|
+
if (semver.satisfies(coerced, ">=12.8.1")) {
|
|
64
|
+
console.log("cuda satisfied")
|
|
65
|
+
let deactivate_list = [
|
|
66
|
+
"~cuda-nvcc_activate.bat",
|
|
67
|
+
"vs2019_compiler_vars.bat",
|
|
68
|
+
"vs2022_compiler_vars.bat",
|
|
69
|
+
]
|
|
70
|
+
const folder = this.kernel.bin.path("miniconda/etc/conda/activate.d")
|
|
71
|
+
let at_least_one_exists = false
|
|
72
|
+
for(let item of deactivate_list) {
|
|
73
|
+
let exists = await this.kernel.exists("bin/miniconda/etc/conda/activate.d/" + item)
|
|
74
|
+
if (exists) {
|
|
75
|
+
// break if at least one exists
|
|
76
|
+
at_least_one_exists = true
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
console.log("nvcc_activate exists?", at_least_one_exists)
|
|
81
|
+
if (at_least_one_exists) {
|
|
82
|
+
return false
|
|
83
|
+
} else {
|
|
84
|
+
return true
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
if (this.kernel.bin.installed.conda.has("cudnn") && this.kernel.bin.installed.conda.has("cuda")) {
|
|
91
|
+
let version = this.kernel.bin.installed.conda_versions.cuda
|
|
92
|
+
if (version) {
|
|
93
|
+
let coerced = semver.coerce(version)
|
|
94
|
+
console.log("cuda version", coerced)
|
|
95
|
+
if (semver.satisfies(coerced, ">=12.8.1")) {
|
|
96
|
+
console.log("satisfied")
|
|
81
97
|
return true
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
100
|
}
|
|
85
101
|
}
|
|
102
|
+
return false
|
|
86
103
|
} else {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (version) {
|
|
90
|
-
let coerced = semver.coerce(version)
|
|
91
|
-
console.log("cuda version", coerced)
|
|
92
|
-
if (semver.satisfies(coerced, ">=12.8.1")) {
|
|
93
|
-
console.log("satisfied")
|
|
94
|
-
return true
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
104
|
+
// just return true for all other gpus so they can be avoided
|
|
105
|
+
return true
|
|
98
106
|
}
|
|
99
|
-
return false
|
|
100
107
|
}
|
|
101
108
|
env() {
|
|
102
109
|
return {
|
package/kernel/bin/setup.js
CHANGED
|
@@ -157,12 +157,14 @@ module.exports = {
|
|
|
157
157
|
{ name: "cli", },
|
|
158
158
|
{ name: "uv", },
|
|
159
159
|
{ name: "py", },
|
|
160
|
+
{ name: "huggingface" },
|
|
160
161
|
{ name: "browserless" },
|
|
161
162
|
])
|
|
162
163
|
let conda_requirements = [
|
|
163
164
|
zip_cmd,
|
|
164
165
|
"uv",
|
|
165
166
|
"node",
|
|
167
|
+
"huggingface",
|
|
166
168
|
"git",
|
|
167
169
|
]
|
|
168
170
|
return {
|
package/kernel/prototype.js
CHANGED
|
@@ -109,6 +109,19 @@ class Proto {
|
|
|
109
109
|
let mod = await this.kernel.require(mod_path)
|
|
110
110
|
let response = await mod(payload, ondata, this.kernel)
|
|
111
111
|
|
|
112
|
+
if (projectType === 'dns') {
|
|
113
|
+
try {
|
|
114
|
+
await this.kernel.dns({ path: payload.cwd })
|
|
115
|
+
} catch (dnsError) {
|
|
116
|
+
console.log('[proto] dns update failed', dnsError)
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await this.kernel.refresh(true)
|
|
120
|
+
} catch (refreshError) {
|
|
121
|
+
console.log('[proto] refresh failed after dns create', refreshError)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
// // copy readme
|
|
113
126
|
// let readme_path = this.kernel.path("prototype/PINOKIO.md")
|
|
114
127
|
// await fs.promises.cp(readme_path, path.resolve(cwd, name, "PINOKIO.md"))
|
package/kernel/router/index.js
CHANGED
|
@@ -38,6 +38,7 @@ class Router {
|
|
|
38
38
|
this.local_network_mapping = {}
|
|
39
39
|
this.custom_routers = {}
|
|
40
40
|
this.rewrite_mapping = {}
|
|
41
|
+
this.stream_close_delay = '10m'
|
|
41
42
|
}
|
|
42
43
|
async init() {
|
|
43
44
|
// if ~/pinokio/network doesn't exist, clone
|
|
@@ -258,6 +259,14 @@ class Router {
|
|
|
258
259
|
}
|
|
259
260
|
}
|
|
260
261
|
this.mapping = this._mapping
|
|
262
|
+
|
|
263
|
+
// set self origins => used for detecting all IPs resembling pinokiod itself
|
|
264
|
+
const basePort = Number(this.kernel.server_port || this.default_port)
|
|
265
|
+
const mappedPort = this.port_mapping && basePort ? Number(this.port_mapping[String(basePort)]) : null
|
|
266
|
+
const lanHost = (this.kernel.peer && this.kernel.peer.host) ? String(this.kernel.peer.host).trim() : ''
|
|
267
|
+
const hosts = ['127.0.0.1', 'localhost', lanHost].filter(Boolean)
|
|
268
|
+
const ports = [basePort, mappedPort].filter((value) => Number.isFinite(value))
|
|
269
|
+
this.kernel.selfOrigins = hosts.flatMap((host) => ports.map((port) => `${host}:${port}`))
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
fallback() {
|
|
@@ -299,8 +308,40 @@ class Router {
|
|
|
299
308
|
this.mapping = this._mapping
|
|
300
309
|
}
|
|
301
310
|
|
|
311
|
+
ensureStreamCloseDelay(target) {
|
|
312
|
+
const delay = this.stream_close_delay
|
|
313
|
+
if (!delay || !target) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
const seen = new WeakSet()
|
|
317
|
+
const visit = (node) => {
|
|
318
|
+
if (!node || (typeof node === 'object' && seen.has(node))) {
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
if (typeof node === 'object') {
|
|
322
|
+
seen.add(node)
|
|
323
|
+
}
|
|
324
|
+
if (Array.isArray(node)) {
|
|
325
|
+
for (const item of node) {
|
|
326
|
+
visit(item)
|
|
327
|
+
}
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
if (typeof node === 'object') {
|
|
331
|
+
if (node.handler === 'reverse_proxy' && typeof node.stream_close_delay === 'undefined') {
|
|
332
|
+
node.stream_close_delay = delay
|
|
333
|
+
}
|
|
334
|
+
for (const key of Object.keys(node)) {
|
|
335
|
+
visit(node[key])
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
visit(target)
|
|
340
|
+
}
|
|
341
|
+
|
|
302
342
|
// update caddy config
|
|
303
343
|
async update() {
|
|
344
|
+
this.ensureStreamCloseDelay(this.config)
|
|
304
345
|
if (JSON.stringify(this.config) === JSON.stringify(this.old_config)) {
|
|
305
346
|
// console.log("######### config hasn't updated")
|
|
306
347
|
} else {
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -441,6 +441,7 @@ class Server {
|
|
|
441
441
|
//browser_url = "/pinokio/browser/" + x.name
|
|
442
442
|
browser_url = "/p/" + x.name
|
|
443
443
|
}
|
|
444
|
+
let view_url = "/v/" + x.name
|
|
444
445
|
let dev_url = browser_url + "/dev"
|
|
445
446
|
let review_url = browser_url + "/review"
|
|
446
447
|
let files_url = "/asset/api/" + x.name
|
|
@@ -469,6 +470,7 @@ class Server {
|
|
|
469
470
|
url: browser_url,
|
|
470
471
|
path: uri,
|
|
471
472
|
dev_url,
|
|
473
|
+
view_url,
|
|
472
474
|
review_url,
|
|
473
475
|
files_url,
|
|
474
476
|
}
|
|
@@ -703,16 +705,14 @@ class Server {
|
|
|
703
705
|
// return current_urls
|
|
704
706
|
}
|
|
705
707
|
|
|
706
|
-
async chrome(req, res, type) {
|
|
708
|
+
async chrome(req, res, type, options) {
|
|
707
709
|
|
|
708
710
|
let d = Date.now()
|
|
709
711
|
let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
|
|
710
|
-
|
|
711
|
-
bin: this.kernel.bin.preset("ai"),
|
|
712
|
+
bin: this.kernel.bin.preset("dev"),
|
|
712
713
|
})
|
|
713
714
|
if (!requirements_pending && install_required) {
|
|
714
|
-
|
|
715
|
-
res.redirect(`/setup/ai?callback=${req.originalUrl}`)
|
|
715
|
+
res.redirect(`/setup/dev?callback=${req.originalUrl}`)
|
|
716
716
|
return
|
|
717
717
|
}
|
|
718
718
|
|
|
@@ -850,7 +850,20 @@ class Server {
|
|
|
850
850
|
dev_link = "/d/" + posix_path
|
|
851
851
|
}
|
|
852
852
|
|
|
853
|
-
let
|
|
853
|
+
let autoselect
|
|
854
|
+
let run_tab
|
|
855
|
+
if (type === "run") {
|
|
856
|
+
if (options && options.no_autoselect) {
|
|
857
|
+
run_tab = "/v/" + name
|
|
858
|
+
autoselect = false
|
|
859
|
+
} else {
|
|
860
|
+
run_tab = "/p/" + name
|
|
861
|
+
autoselect = true
|
|
862
|
+
}
|
|
863
|
+
} else {
|
|
864
|
+
run_tab = "/p/" + name
|
|
865
|
+
autoselect = false
|
|
866
|
+
}
|
|
854
867
|
let dev_tab = "/p/" + name + "/dev"
|
|
855
868
|
let review_tab = "/p/" + name + "/review"
|
|
856
869
|
let files_tab = "/p/" + name + "/files"
|
|
@@ -889,6 +902,7 @@ class Server {
|
|
|
889
902
|
port: this.port,
|
|
890
903
|
// mem,
|
|
891
904
|
type,
|
|
905
|
+
autoselect,
|
|
892
906
|
platform,
|
|
893
907
|
running:this.kernel.api.running,
|
|
894
908
|
memory: this.kernel.memory,
|
|
@@ -1677,12 +1691,29 @@ class Server {
|
|
|
1677
1691
|
template = "editor"
|
|
1678
1692
|
}
|
|
1679
1693
|
|
|
1694
|
+
let requires_bundle = null
|
|
1695
|
+
if (resolved && resolved.requires && !Array.isArray(resolved.requires)) {
|
|
1696
|
+
const bundle = resolved.requires.bundle
|
|
1697
|
+
if (typeof bundle === "string" && typeof Setup[bundle] === "function") {
|
|
1698
|
+
requires_bundle = bundle
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const preset = requires_bundle ? this.kernel.bin.preset(requires_bundle) : this.kernel.bin.preset("dev")
|
|
1680
1703
|
let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
|
|
1681
|
-
|
|
1682
|
-
bin: this.kernel.bin.preset("dev"),
|
|
1704
|
+
bin: preset,
|
|
1683
1705
|
script: resolved
|
|
1684
1706
|
})
|
|
1685
1707
|
|
|
1708
|
+
if (requires_bundle) {
|
|
1709
|
+
console.log({ requires_bundle, requirements_pending, install_required, })
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
if (requires_bundle && !requirements_pending && install_required) {
|
|
1713
|
+
res.redirect(`/setup/${requires_bundle}?callback=${req.originalUrl}`)
|
|
1714
|
+
return
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1686
1717
|
//let requirements = this.kernel.bin.requirements(resolved)
|
|
1687
1718
|
//let requirements_pending = !this.kernel.bin.installed_initialized
|
|
1688
1719
|
//let install_required = true
|
|
@@ -3368,7 +3399,7 @@ class Server {
|
|
|
3368
3399
|
]
|
|
3369
3400
|
pushEntry({
|
|
3370
3401
|
host: hostMeta,
|
|
3371
|
-
name: `[
|
|
3402
|
+
name: `[Website] ${rewrite.name || key}`,
|
|
3372
3403
|
ip: externalIp || null,
|
|
3373
3404
|
httpUrl: externalIp,
|
|
3374
3405
|
httpsUrls: Array.from(new Set(httpsSources))
|
|
@@ -3401,26 +3432,26 @@ class Server {
|
|
|
3401
3432
|
})
|
|
3402
3433
|
}
|
|
3403
3434
|
|
|
3404
|
-
const installedApps = Array.isArray(hostInfo.installed) ? hostInfo.installed : []
|
|
3405
|
-
for (const app of installedApps) {
|
|
3406
|
-
if (!app) {
|
|
3407
|
-
continue
|
|
3408
|
-
}
|
|
3409
|
-
const httpHref = Array.isArray(app.http_href) ? app.http_href[0] : app.http_href
|
|
3410
|
-
const httpsCandidates = Array.from(new Set([
|
|
3411
|
-
...ensureArray(app.app_href),
|
|
3412
|
-
...ensureArray(app.https_href)
|
|
3413
|
-
]))
|
|
3414
|
-
pushEntry({
|
|
3415
|
-
host: hostMeta,
|
|
3416
|
-
name: app.title || app.name || app.folder,
|
|
3417
|
-
ip: httpHref ? httpHref.replace(/^https?:\/\//i, '') : null,
|
|
3418
|
-
httpUrl: httpHref || null,
|
|
3419
|
-
httpsUrls: httpsCandidates,
|
|
3420
|
-
description: app.description,
|
|
3421
|
-
icon: app.https_icon || app.http_icon || app.icon
|
|
3422
|
-
})
|
|
3423
|
-
}
|
|
3435
|
+
// const installedApps = Array.isArray(hostInfo.installed) ? hostInfo.installed : []
|
|
3436
|
+
// for (const app of installedApps) {
|
|
3437
|
+
// if (!app) {
|
|
3438
|
+
// continue
|
|
3439
|
+
// }
|
|
3440
|
+
// const httpHref = Array.isArray(app.http_href) ? app.http_href[0] : app.http_href
|
|
3441
|
+
// const httpsCandidates = Array.from(new Set([
|
|
3442
|
+
// ...ensureArray(app.app_href),
|
|
3443
|
+
// ...ensureArray(app.https_href)
|
|
3444
|
+
// ]))
|
|
3445
|
+
// pushEntry({
|
|
3446
|
+
// host: hostMeta,
|
|
3447
|
+
// name: app.title || app.name || app.folder,
|
|
3448
|
+
// ip: httpHref ? httpHref.replace(/^https?:\/\//i, '') : null,
|
|
3449
|
+
// httpUrl: httpHref || null,
|
|
3450
|
+
// httpsUrls: httpsCandidates,
|
|
3451
|
+
// description: app.description,
|
|
3452
|
+
// icon: app.https_icon || app.http_icon || app.icon
|
|
3453
|
+
// })
|
|
3454
|
+
// }
|
|
3424
3455
|
}
|
|
3425
3456
|
}
|
|
3426
3457
|
async terminals(filepath) {
|
|
@@ -3728,6 +3759,7 @@ class Server {
|
|
|
3728
3759
|
|
|
3729
3760
|
|
|
3730
3761
|
await this.kernel.init({ port: this.port})
|
|
3762
|
+
this.kernel.server_port = this.port
|
|
3731
3763
|
this.kernel.peer.start(this.kernel)
|
|
3732
3764
|
|
|
3733
3765
|
|
|
@@ -5494,6 +5526,18 @@ class Server {
|
|
|
5494
5526
|
requirements_pending,
|
|
5495
5527
|
})
|
|
5496
5528
|
}))
|
|
5529
|
+
this.app.get("/net/:name/diff", ex(async (req, res) => {
|
|
5530
|
+
try {
|
|
5531
|
+
let processes = this.kernel.peer.info[this.kernel.peer.host].router_info
|
|
5532
|
+
let last_proc = JSON.stringify(this.kernel.last_processes)
|
|
5533
|
+
let current_proc = JSON.stringify(processes)
|
|
5534
|
+
this.kernel.last_processes = processes
|
|
5535
|
+
res.json({ diff: last_proc !== current_proc })
|
|
5536
|
+
} catch (e) {
|
|
5537
|
+
console.log("ERROR", e)
|
|
5538
|
+
res.json({ diff: true })
|
|
5539
|
+
}
|
|
5540
|
+
}))
|
|
5497
5541
|
this.app.get("/net/:name", ex(async (req, res) => {
|
|
5498
5542
|
let protocol = req.get('X-Forwarded-Proto') || "http"
|
|
5499
5543
|
let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
|
|
@@ -5547,6 +5591,7 @@ class Server {
|
|
|
5547
5591
|
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
5548
5592
|
let peer_qr = null
|
|
5549
5593
|
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
5594
|
+
const allow_dns_creation = req.params.name === this.kernel.peer.name
|
|
5550
5595
|
res.render("net", {
|
|
5551
5596
|
static_routes,
|
|
5552
5597
|
selected_name: req.params.name,
|
|
@@ -5567,6 +5612,8 @@ class Server {
|
|
|
5567
5612
|
current_host: this.kernel.peer.host,
|
|
5568
5613
|
peer_url,
|
|
5569
5614
|
peer_qr,
|
|
5615
|
+
cwd: this.kernel.path("api"),
|
|
5616
|
+
allow_dns_creation,
|
|
5570
5617
|
})
|
|
5571
5618
|
}))
|
|
5572
5619
|
this.app.get("/network", ex(async (req, res) => {
|
|
@@ -7103,7 +7150,13 @@ class Server {
|
|
|
7103
7150
|
return hosts[0]
|
|
7104
7151
|
}
|
|
7105
7152
|
|
|
7106
|
-
const
|
|
7153
|
+
const processes = Array.isArray(this.kernel.processes.info) ? this.kernel.processes.info : []
|
|
7154
|
+
const serverPid = Number(process.pid)
|
|
7155
|
+
const filteredProcesses = Number.isFinite(serverPid)
|
|
7156
|
+
? processes.filter((item) => Number(item && item.pid) !== serverPid)
|
|
7157
|
+
: processes.slice()
|
|
7158
|
+
|
|
7159
|
+
let info = filteredProcesses.map((item) => {
|
|
7107
7160
|
const httpUrl = item.ip ? `http://${item.ip}` : null
|
|
7108
7161
|
let httpsHosts = []
|
|
7109
7162
|
if (preferHttps) {
|
|
@@ -7149,6 +7202,12 @@ class Server {
|
|
|
7149
7202
|
|
|
7150
7203
|
this.add_extra_urls(info)
|
|
7151
7204
|
|
|
7205
|
+
if (Array.isArray(this.kernel.selfOrigins) && this.kernel.selfOrigins.length > 0) {
|
|
7206
|
+
info = info.filter((entry) => {
|
|
7207
|
+
return !this.kernel.selfOrigins.includes(entry.ip)
|
|
7208
|
+
})
|
|
7209
|
+
}
|
|
7210
|
+
|
|
7152
7211
|
const toArray = (value) => {
|
|
7153
7212
|
if (!value) return []
|
|
7154
7213
|
return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean)
|
|
@@ -7429,7 +7488,9 @@ class Server {
|
|
|
7429
7488
|
this.app.get("/pinokio/browser/:name", ex(async (req, res) => {
|
|
7430
7489
|
await this.chrome(req, res, "run")
|
|
7431
7490
|
}))
|
|
7432
|
-
|
|
7491
|
+
this.app.get("/v/:name", ex(async (req, res) => {
|
|
7492
|
+
await this.chrome(req, res, "run", { no_autoselect: true })
|
|
7493
|
+
}))
|
|
7433
7494
|
|
|
7434
7495
|
this.app.get("/p/:name/review", ex(async (req, res) => {
|
|
7435
7496
|
let gitRemote = null
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const api = window.PinokioTabLinkPopover
|
|
3
|
+
if (!api) {
|
|
4
|
+
return
|
|
5
|
+
}
|
|
6
|
+
const { renderTabLinkPopover, hideTabLinkPopover, isLocalHostLike } = api
|
|
7
|
+
|
|
8
|
+
const getPopoverEl = () => document.getElementById('tab-link-popover')
|
|
9
|
+
|
|
10
|
+
const ensureHttpUrl = (value) => {
|
|
11
|
+
if (typeof value !== 'string') {
|
|
12
|
+
return ''
|
|
13
|
+
}
|
|
14
|
+
let trimmed = value.trim()
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return ''
|
|
17
|
+
}
|
|
18
|
+
if (!/^https?:\/\//i.test(trimmed)) {
|
|
19
|
+
if (/^[a-z]+:\/\//i.test(trimmed)) {
|
|
20
|
+
return ''
|
|
21
|
+
}
|
|
22
|
+
trimmed = `http://${trimmed}`
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const parsed = new URL(trimmed)
|
|
26
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
27
|
+
return ''
|
|
28
|
+
}
|
|
29
|
+
return parsed.toString()
|
|
30
|
+
} catch (_) {
|
|
31
|
+
return ''
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isLocalUrl = (value) => {
|
|
36
|
+
if (!value) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = new URL(value)
|
|
41
|
+
return isLocalHostLike(parsed.hostname)
|
|
42
|
+
} catch (_) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolveCurrentUrl = (input) => {
|
|
48
|
+
if (input && typeof input.value === 'string' && input.value.trim().length > 0) {
|
|
49
|
+
const normalized = ensureHttpUrl(input.value)
|
|
50
|
+
if (normalized) {
|
|
51
|
+
return normalized
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const fallback = input?.getAttribute('value')
|
|
55
|
+
return ensureHttpUrl(fallback || '')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const showPopoverForAnchor = (anchor, urlInput) => {
|
|
59
|
+
if (!anchor) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
const currentUrl = resolveCurrentUrl(urlInput)
|
|
63
|
+
if (!currentUrl || !isLocalUrl(currentUrl)) {
|
|
64
|
+
hideTabLinkPopover({ immediate: true })
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
renderTabLinkPopover(anchor, {
|
|
68
|
+
hrefOverride: currentUrl,
|
|
69
|
+
requireAlternate: false,
|
|
70
|
+
restrictToBase: true,
|
|
71
|
+
forceCanonicalQr: true,
|
|
72
|
+
allowQrPortMismatch: true,
|
|
73
|
+
skipPeerFallback: true
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handleMouseLeave = (anchor, event) => {
|
|
78
|
+
const related = event.relatedTarget
|
|
79
|
+
const popover = getPopoverEl()
|
|
80
|
+
if (related && (anchor.contains(related) || (popover && popover.contains(related)))) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
hideTabLinkPopover()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const init = () => {
|
|
87
|
+
const container = document.querySelector('.url-input-container')
|
|
88
|
+
const urlInput = container ? container.querySelector('input[type="url"]') : null
|
|
89
|
+
const mobileButton = document.getElementById('mobile-link-button')
|
|
90
|
+
|
|
91
|
+
if (container) {
|
|
92
|
+
container.addEventListener('mouseover', () => showPopoverForAnchor(container, urlInput))
|
|
93
|
+
container.addEventListener('mouseout', (event) => handleMouseLeave(container, event))
|
|
94
|
+
const inputFocus = () => showPopoverForAnchor(container, urlInput)
|
|
95
|
+
const inputBlur = (event) => handleMouseLeave(container, event)
|
|
96
|
+
if (urlInput) {
|
|
97
|
+
urlInput.addEventListener('focus', inputFocus)
|
|
98
|
+
urlInput.addEventListener('blur', inputBlur)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (mobileButton) {
|
|
103
|
+
mobileButton.addEventListener('mouseover', () => showPopoverForAnchor(mobileButton, urlInput))
|
|
104
|
+
mobileButton.addEventListener('mouseout', (event) => handleMouseLeave(mobileButton, event))
|
|
105
|
+
mobileButton.addEventListener('focus', () => showPopoverForAnchor(mobileButton, urlInput))
|
|
106
|
+
mobileButton.addEventListener('blur', (event) => handleMouseLeave(mobileButton, event))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (document.readyState === 'loading') {
|
|
111
|
+
document.addEventListener('DOMContentLoaded', init)
|
|
112
|
+
} else {
|
|
113
|
+
init()
|
|
114
|
+
}
|
|
115
|
+
})()
|