pinokiod 3.230.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 +91 -29
- 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,12 +705,11 @@ 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/dev?callback=${req.originalUrl}`)
|
|
@@ -849,7 +850,20 @@ class Server {
|
|
|
849
850
|
dev_link = "/d/" + posix_path
|
|
850
851
|
}
|
|
851
852
|
|
|
852
|
-
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
|
+
}
|
|
853
867
|
let dev_tab = "/p/" + name + "/dev"
|
|
854
868
|
let review_tab = "/p/" + name + "/review"
|
|
855
869
|
let files_tab = "/p/" + name + "/files"
|
|
@@ -888,6 +902,7 @@ class Server {
|
|
|
888
902
|
port: this.port,
|
|
889
903
|
// mem,
|
|
890
904
|
type,
|
|
905
|
+
autoselect,
|
|
891
906
|
platform,
|
|
892
907
|
running:this.kernel.api.running,
|
|
893
908
|
memory: this.kernel.memory,
|
|
@@ -1676,12 +1691,29 @@ class Server {
|
|
|
1676
1691
|
template = "editor"
|
|
1677
1692
|
}
|
|
1678
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")
|
|
1679
1703
|
let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
|
|
1680
|
-
|
|
1681
|
-
bin: this.kernel.bin.preset("dev"),
|
|
1704
|
+
bin: preset,
|
|
1682
1705
|
script: resolved
|
|
1683
1706
|
})
|
|
1684
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
|
+
|
|
1685
1717
|
//let requirements = this.kernel.bin.requirements(resolved)
|
|
1686
1718
|
//let requirements_pending = !this.kernel.bin.installed_initialized
|
|
1687
1719
|
//let install_required = true
|
|
@@ -3367,7 +3399,7 @@ class Server {
|
|
|
3367
3399
|
]
|
|
3368
3400
|
pushEntry({
|
|
3369
3401
|
host: hostMeta,
|
|
3370
|
-
name: `[
|
|
3402
|
+
name: `[Website] ${rewrite.name || key}`,
|
|
3371
3403
|
ip: externalIp || null,
|
|
3372
3404
|
httpUrl: externalIp,
|
|
3373
3405
|
httpsUrls: Array.from(new Set(httpsSources))
|
|
@@ -3400,26 +3432,26 @@ class Server {
|
|
|
3400
3432
|
})
|
|
3401
3433
|
}
|
|
3402
3434
|
|
|
3403
|
-
const installedApps = Array.isArray(hostInfo.installed) ? hostInfo.installed : []
|
|
3404
|
-
for (const app of installedApps) {
|
|
3405
|
-
if (!app) {
|
|
3406
|
-
continue
|
|
3407
|
-
}
|
|
3408
|
-
const httpHref = Array.isArray(app.http_href) ? app.http_href[0] : app.http_href
|
|
3409
|
-
const httpsCandidates = Array.from(new Set([
|
|
3410
|
-
...ensureArray(app.app_href),
|
|
3411
|
-
...ensureArray(app.https_href)
|
|
3412
|
-
]))
|
|
3413
|
-
pushEntry({
|
|
3414
|
-
host: hostMeta,
|
|
3415
|
-
name: app.title || app.name || app.folder,
|
|
3416
|
-
ip: httpHref ? httpHref.replace(/^https?:\/\//i, '') : null,
|
|
3417
|
-
httpUrl: httpHref || null,
|
|
3418
|
-
httpsUrls: httpsCandidates,
|
|
3419
|
-
description: app.description,
|
|
3420
|
-
icon: app.https_icon || app.http_icon || app.icon
|
|
3421
|
-
})
|
|
3422
|
-
}
|
|
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
|
+
// }
|
|
3423
3455
|
}
|
|
3424
3456
|
}
|
|
3425
3457
|
async terminals(filepath) {
|
|
@@ -3727,6 +3759,7 @@ class Server {
|
|
|
3727
3759
|
|
|
3728
3760
|
|
|
3729
3761
|
await this.kernel.init({ port: this.port})
|
|
3762
|
+
this.kernel.server_port = this.port
|
|
3730
3763
|
this.kernel.peer.start(this.kernel)
|
|
3731
3764
|
|
|
3732
3765
|
|
|
@@ -5493,6 +5526,18 @@ class Server {
|
|
|
5493
5526
|
requirements_pending,
|
|
5494
5527
|
})
|
|
5495
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
|
+
}))
|
|
5496
5541
|
this.app.get("/net/:name", ex(async (req, res) => {
|
|
5497
5542
|
let protocol = req.get('X-Forwarded-Proto') || "http"
|
|
5498
5543
|
let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
|
|
@@ -5546,6 +5591,7 @@ class Server {
|
|
|
5546
5591
|
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
5547
5592
|
let peer_qr = null
|
|
5548
5593
|
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
5594
|
+
const allow_dns_creation = req.params.name === this.kernel.peer.name
|
|
5549
5595
|
res.render("net", {
|
|
5550
5596
|
static_routes,
|
|
5551
5597
|
selected_name: req.params.name,
|
|
@@ -5566,6 +5612,8 @@ class Server {
|
|
|
5566
5612
|
current_host: this.kernel.peer.host,
|
|
5567
5613
|
peer_url,
|
|
5568
5614
|
peer_qr,
|
|
5615
|
+
cwd: this.kernel.path("api"),
|
|
5616
|
+
allow_dns_creation,
|
|
5569
5617
|
})
|
|
5570
5618
|
}))
|
|
5571
5619
|
this.app.get("/network", ex(async (req, res) => {
|
|
@@ -7102,7 +7150,13 @@ class Server {
|
|
|
7102
7150
|
return hosts[0]
|
|
7103
7151
|
}
|
|
7104
7152
|
|
|
7105
|
-
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) => {
|
|
7106
7160
|
const httpUrl = item.ip ? `http://${item.ip}` : null
|
|
7107
7161
|
let httpsHosts = []
|
|
7108
7162
|
if (preferHttps) {
|
|
@@ -7148,6 +7202,12 @@ class Server {
|
|
|
7148
7202
|
|
|
7149
7203
|
this.add_extra_urls(info)
|
|
7150
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
|
+
|
|
7151
7211
|
const toArray = (value) => {
|
|
7152
7212
|
if (!value) return []
|
|
7153
7213
|
return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean)
|
|
@@ -7428,7 +7488,9 @@ class Server {
|
|
|
7428
7488
|
this.app.get("/pinokio/browser/:name", ex(async (req, res) => {
|
|
7429
7489
|
await this.chrome(req, res, "run")
|
|
7430
7490
|
}))
|
|
7431
|
-
|
|
7491
|
+
this.app.get("/v/:name", ex(async (req, res) => {
|
|
7492
|
+
await this.chrome(req, res, "run", { no_autoselect: true })
|
|
7493
|
+
}))
|
|
7432
7494
|
|
|
7433
7495
|
this.app.get("/p/:name/review", ex(async (req, res) => {
|
|
7434
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
|
+
})()
|