pinokiod 2.15.8 → 2.15.9
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/index.js +1 -1
- package/kernel/index.js +1 -1
- package/kernel/lproxy.js +76 -0
- package/kernel/shell.js +7 -1
- package/kernel/shells.js +99 -54
- package/kernel/util.js +27 -1
- package/package.json +2 -3
- package/server/index.js +84 -5
- package/server/public/ollama.webp +0 -0
- package/server/public/style.css +19 -0
- package/server/views/browser-experimental.ejs +5 -7
- package/server/views/editor.ejs +8 -0
- package/server/views/fullscreen_editor.ejs +9 -1
- package/server/views/index.ejs +24 -15
- package/server/views/proxy.ejs +357 -0
package/kernel/api/index.js
CHANGED
|
@@ -4,7 +4,7 @@ const path = require('path')
|
|
|
4
4
|
const _ = require('lodash')
|
|
5
5
|
const git = require('isomorphic-git')
|
|
6
6
|
const http = require('isomorphic-git/http/node')
|
|
7
|
-
const Lproxy = require('lproxy')
|
|
7
|
+
const Lproxy = require('../lproxy')
|
|
8
8
|
//const { glob, globSync, globStream, globStreamSync, Glob, } = require('glob')
|
|
9
9
|
const { glob, sync, hasMagic } = require('glob-gitignore')
|
|
10
10
|
const fastq = require('fastq')
|
package/kernel/index.js
CHANGED
package/kernel/lproxy.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
const proxy = new Minproxy()
|
|
3
|
+
|
|
4
|
+
// 1. start the proxy for url
|
|
5
|
+
const generated_url = await proxy.start(url)
|
|
6
|
+
|
|
7
|
+
// 2. stop the proxy for url
|
|
8
|
+
proxy.stop(url)
|
|
9
|
+
|
|
10
|
+
// 3. get the proxy url for a given url
|
|
11
|
+
const proxy_url = proxy.url(url)
|
|
12
|
+
|
|
13
|
+
// 4. get all proxies
|
|
14
|
+
const proxies = proxy.all()
|
|
15
|
+
|
|
16
|
+
*/
|
|
17
|
+
const httpProxy = require('http-proxy');
|
|
18
|
+
const portfinder = require('portfinder-cp');
|
|
19
|
+
const os = require('os')
|
|
20
|
+
class Lproxy {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.proxies = {}
|
|
23
|
+
this.ip = this.local()
|
|
24
|
+
}
|
|
25
|
+
async start (url, options) {
|
|
26
|
+
let port
|
|
27
|
+
if (options && options.port) {
|
|
28
|
+
port = options.port
|
|
29
|
+
console.log("start proxy at port", port)
|
|
30
|
+
delete options.port
|
|
31
|
+
}
|
|
32
|
+
let o = Object.assign({
|
|
33
|
+
target: url,
|
|
34
|
+
ws: true,
|
|
35
|
+
localAddress: '0.0.0.0',
|
|
36
|
+
}, options)
|
|
37
|
+
console.log("proxy object", o)
|
|
38
|
+
const proxy = httpProxy.createProxyServer(o)
|
|
39
|
+
// get a new port
|
|
40
|
+
if (!port) {
|
|
41
|
+
port = await portfinder.getPortPromise({
|
|
42
|
+
//port: 42420
|
|
43
|
+
port: 44000
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
proxy.listen(port)
|
|
47
|
+
proxy.on('close', (res, socket, head) => {
|
|
48
|
+
console.log('Client disconnected');
|
|
49
|
+
});
|
|
50
|
+
proxy.on('error', (e) => {
|
|
51
|
+
console.log('Proxy Error', e)
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.proxies[url] = proxy
|
|
55
|
+
return `http://${this.ip}:${port}`
|
|
56
|
+
}
|
|
57
|
+
async stop (url) {
|
|
58
|
+
console.log("proxies", this.proxies, url)
|
|
59
|
+
let p = this.proxies[url]
|
|
60
|
+
if (p) {
|
|
61
|
+
p.close()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
local() {
|
|
65
|
+
const interfaces = Object.values(os.networkInterfaces())
|
|
66
|
+
let addr
|
|
67
|
+
for (let iface of interfaces) {
|
|
68
|
+
for (let alias of iface) {
|
|
69
|
+
if (alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal) {
|
|
70
|
+
return alias.address
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
module.exports = Lproxy
|
package/kernel/shell.js
CHANGED
|
@@ -439,7 +439,13 @@ class Shell {
|
|
|
439
439
|
return params.message
|
|
440
440
|
} else if (Array.isArray(params.message)) {
|
|
441
441
|
// if params.message is empty, filter out
|
|
442
|
-
let delimiter = " && "
|
|
442
|
+
//let delimiter = " && "
|
|
443
|
+
let delimiter
|
|
444
|
+
if (this.platform === "win32") {
|
|
445
|
+
delimiter = " & ";
|
|
446
|
+
} else {
|
|
447
|
+
delimiter = " ; ";
|
|
448
|
+
}
|
|
443
449
|
return params.message.filter((m) => {
|
|
444
450
|
return m && !/^\s+$/.test(m)
|
|
445
451
|
}).join(delimiter)
|
package/kernel/shells.js
CHANGED
|
@@ -43,11 +43,17 @@ class Shells {
|
|
|
43
43
|
params.on = []
|
|
44
44
|
}
|
|
45
45
|
// default error
|
|
46
|
-
|
|
46
|
+
const defaultHandlers = [{
|
|
47
47
|
event: "/error:/i",
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
48
|
+
break: true
|
|
49
|
+
}, {
|
|
50
|
+
event: "/errno /i",
|
|
51
|
+
break: true
|
|
52
|
+
}, {
|
|
53
|
+
event: "/error:.*triton/i",
|
|
54
|
+
break: false
|
|
55
|
+
}]
|
|
56
|
+
params.on = params.on.concat(defaultHandlers)
|
|
51
57
|
let response = await sh.start(params, async (stream) => {
|
|
52
58
|
/*
|
|
53
59
|
{
|
|
@@ -61,32 +67,6 @@ class Shells {
|
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
*/
|
|
64
|
-
// if (params.error && Array.isArray(params.error)) {
|
|
65
|
-
// for(let handler of params.error) {
|
|
66
|
-
// if (handler.event) {
|
|
67
|
-
// let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
68
|
-
// if (!/g/.test(matches[2])) {
|
|
69
|
-
// matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
70
|
-
// }
|
|
71
|
-
// let re = new RegExp(matches[1], matches[2])
|
|
72
|
-
// let line = stream.cleaned.replaceAll(/[\r\n]/g, "")
|
|
73
|
-
// //let rendered_event = [...stream.cleaned.matchAll(re)]
|
|
74
|
-
//
|
|
75
|
-
// let rendered_event = [...line.matchAll(re)]
|
|
76
|
-
// // 3. if the rendered expression is truthy, run the "run" script
|
|
77
|
-
// if (rendered_event.length > 0) {
|
|
78
|
-
//
|
|
79
|
-
// // if error matched, shouldn't move forard
|
|
80
|
-
//// error = rendered_event
|
|
81
|
-
// console.log({ rendered_event })
|
|
82
|
-
// for(let e of rendered_event) {
|
|
83
|
-
// errors.add(e[0])
|
|
84
|
-
// }
|
|
85
|
-
//
|
|
86
|
-
// }
|
|
87
|
-
// }
|
|
88
|
-
// }
|
|
89
|
-
// }
|
|
90
70
|
if (params.on && Array.isArray(params.on)) {
|
|
91
71
|
for(let handler of params.on) {
|
|
92
72
|
// regexify
|
|
@@ -113,39 +93,104 @@ class Shells {
|
|
|
113
93
|
m = rendered_event[0]
|
|
114
94
|
sh.continue()
|
|
115
95
|
}
|
|
116
|
-
if (handler.debug) {
|
|
117
|
-
for(let e of rendered_event) {
|
|
118
|
-
errors.add(e[0])
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
96
|
}
|
|
122
|
-
// } else if (handler.pattern === "url") {
|
|
123
|
-
// let re = /https?:\/\/[a-zA-Z0-9.:]+/g
|
|
124
|
-
// let line = stream.cleaned.replaceAll(/[\r\n]/g, "")
|
|
125
|
-
// let rendered_event = [...line.matchAll(re)]
|
|
126
|
-
// if (rendered_event.length > 0) {
|
|
127
|
-
// stream.matches = rendered_event
|
|
128
|
-
// if (handler.kill) {
|
|
129
|
-
// m = new URL(rendered_event[0])
|
|
130
|
-
// console.log("MM", m)
|
|
131
|
-
// sh.kill()
|
|
132
|
-
// }
|
|
133
|
-
// if (handler.done) {
|
|
134
|
-
// m = new URL(rendered_event[0])
|
|
135
|
-
// console.log("MM", m)
|
|
136
|
-
// sh.continue()
|
|
137
|
-
// }
|
|
138
|
-
// }
|
|
139
97
|
}
|
|
140
98
|
}
|
|
141
99
|
}
|
|
142
100
|
ondata(stream)
|
|
143
101
|
})
|
|
144
102
|
|
|
103
|
+
/*
|
|
104
|
+
{
|
|
105
|
+
method: "shell.run",
|
|
106
|
+
params: {
|
|
107
|
+
message,
|
|
108
|
+
on: [{
|
|
109
|
+
event: <regex>,
|
|
110
|
+
break: true|false
|
|
111
|
+
}],
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
- true: break with matched
|
|
116
|
+
- false: do not break (default)
|
|
117
|
+
- TEMPLATE: parse the template
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
let errors = new Set()
|
|
121
|
+
if (response) {
|
|
122
|
+
if (params.on && Array.isArray(params.on)) {
|
|
123
|
+
|
|
124
|
+
let line = response.replaceAll(/[\r\n]/g, "")
|
|
125
|
+
// 1. find all break event handlers
|
|
126
|
+
let breakPoints = params.on.filter((x) => {
|
|
127
|
+
return x.event && x.hasOwnProperty("break")
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// 2. first find the `break: false` handlers and replace the patterns with blank => so they won't be matched in the next step
|
|
131
|
+
//let line = response.replaceAll(/[\r\n]/g, "")
|
|
132
|
+
for(let handler of breakPoints) {
|
|
133
|
+
if (handler.event) {
|
|
134
|
+
let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
135
|
+
if (!/g/.test(matches[2])) {
|
|
136
|
+
matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
137
|
+
}
|
|
138
|
+
let re = new RegExp(matches[1], matches[2])
|
|
139
|
+
|
|
140
|
+
// only the "break: false" ones => these need to be ignored
|
|
141
|
+
if (!handler.break) {
|
|
142
|
+
line = line.replaceAll(re, "")
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. Now with all the `break: false` (ignored patterns) gone, look for the `break: true` patterns
|
|
148
|
+
for(let handler of breakPoints) {
|
|
149
|
+
if (handler.event) {
|
|
150
|
+
let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
151
|
+
if (!/g/.test(matches[2])) {
|
|
152
|
+
matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
153
|
+
}
|
|
154
|
+
let re = new RegExp(matches[1], matches[2])
|
|
155
|
+
|
|
156
|
+
// only the "break: true" ones
|
|
157
|
+
if (handler.break) {
|
|
158
|
+
|
|
159
|
+
let match;
|
|
160
|
+
// Keep executing the regex on the text until no more matches are found
|
|
161
|
+
while ((match = re.exec(line)) !== null) {
|
|
162
|
+
errors.add(match[0])
|
|
163
|
+
// errors.add(match.slice(1));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
145
172
|
if (errors.size > 0) {
|
|
173
|
+
// try replacing the shortest pattern from the text one by one and narrow down the errors
|
|
174
|
+
// so it doesn't accidently contain a very long match
|
|
175
|
+
|
|
176
|
+
let errs = Array.from(errors)
|
|
177
|
+
errs.sort((x, y) => {
|
|
178
|
+
return x.length - y.length
|
|
179
|
+
})
|
|
180
|
+
let line = response.replaceAll(/[\r\n]/g, "")
|
|
181
|
+
let shortened_errs = []
|
|
182
|
+
// at this point, errs is sorted in ascending order of length, so the shortest match is the first item
|
|
183
|
+
for(let err of errs) {
|
|
184
|
+
// 1. check the err still matches the line => The first time this is run, it will always be included. But from the next time it might not be
|
|
185
|
+
if (line.includes(err)) {
|
|
186
|
+
// 2. remove the first pattern
|
|
187
|
+
shortened_errs.push(err)
|
|
188
|
+
line = line.replaceAll(err, "")
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
146
192
|
// don't return if there's an error
|
|
147
|
-
|
|
148
|
-
return { id: sh.id, response, stdout: response, event: m, error: Array.from(errors) }
|
|
193
|
+
return { id: sh.id, response, stdout: response, event: m, error: shortened_errs }
|
|
149
194
|
} else {
|
|
150
195
|
// need to make a request
|
|
151
196
|
return { id: sh.id, response, stdout: response, event: m }
|
package/kernel/util.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const os = require('os')
|
|
3
|
+
const net = require('node:net')
|
|
3
4
|
const path = require('path')
|
|
4
5
|
const dotenv = require('dotenv')
|
|
5
6
|
const child_process = require('node:child_process');
|
|
@@ -13,6 +14,31 @@ const h = "[^\\S\\r\\n]"; // simulate `\h`
|
|
|
13
14
|
const returnPattern = /\r/g;
|
|
14
15
|
const returnReplacement = "\\r";
|
|
15
16
|
|
|
17
|
+
|
|
18
|
+
const port_running = async (host, port) => {
|
|
19
|
+
const timeout = 1000
|
|
20
|
+
const promise = new Promise((resolve, reject) => {
|
|
21
|
+
const socket = new net.Socket();
|
|
22
|
+
const onError = (e) => {
|
|
23
|
+
socket.destroy();
|
|
24
|
+
reject();
|
|
25
|
+
};
|
|
26
|
+
socket.setTimeout(timeout);
|
|
27
|
+
socket.once('error', onError);
|
|
28
|
+
socket.once('timeout', onError);
|
|
29
|
+
socket.connect(port, host, () => {
|
|
30
|
+
socket.end();
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
await promise;
|
|
36
|
+
return true
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
const parse_env = async (filename) => {
|
|
17
43
|
try {
|
|
18
44
|
const buf = await fs.promises.readFile(filename)
|
|
@@ -142,5 +168,5 @@ const update_env = async (filepath, changes) => {
|
|
|
142
168
|
await fs.promises.writeFile(filepath, newval)
|
|
143
169
|
};
|
|
144
170
|
module.exports = {
|
|
145
|
-
parse_env, api_path, update_env, parse_env_detail, openfs
|
|
171
|
+
parse_env, api_path, update_env, parse_env_detail, openfs, port_running
|
|
146
172
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinokiod",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.9",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,16 +31,15 @@
|
|
|
31
31
|
"fs-extra": "^11.1.1",
|
|
32
32
|
"glob": "^10.3.3",
|
|
33
33
|
"glob-gitignore": "^1.0.14",
|
|
34
|
+
"http-proxy": "^1.18.1",
|
|
34
35
|
"http-proxy-middleware": "^3.0.0",
|
|
35
36
|
"http-terminator": "^3.2.0",
|
|
36
37
|
"isomorphic-git": "^1.23.0",
|
|
37
38
|
"jimini": "^0.0.6",
|
|
38
39
|
"jsdom": "^24.0.0",
|
|
39
|
-
"jsonpath-plus": "^7.2.0",
|
|
40
40
|
"key-store": "^1.2.0",
|
|
41
41
|
"kleur": "^4.1.5",
|
|
42
42
|
"lodash": "^4.17.21",
|
|
43
|
-
"lproxy": "^0.0.13",
|
|
44
43
|
"marked": "^5.0.1",
|
|
45
44
|
"mime-types": "^2.1.35",
|
|
46
45
|
"multer": "^1.4.5-lts.1",
|
package/server/index.js
CHANGED
|
@@ -71,7 +71,6 @@ class Server {
|
|
|
71
71
|
process.env[PATH_KEY]
|
|
72
72
|
].join(path.delimiter)
|
|
73
73
|
}
|
|
74
|
-
|
|
75
74
|
}
|
|
76
75
|
stop() {
|
|
77
76
|
this.server.close()
|
|
@@ -257,11 +256,11 @@ class Server {
|
|
|
257
256
|
|
|
258
257
|
await this.init_env("api/" + name)
|
|
259
258
|
|
|
259
|
+
|
|
260
260
|
let mode = "run"
|
|
261
261
|
if (req.query && req.query.mode) {
|
|
262
262
|
mode = req.query.mode
|
|
263
263
|
}
|
|
264
|
-
|
|
265
264
|
const env = await this.kernel.env("api/" + name)
|
|
266
265
|
|
|
267
266
|
//res.render("browser", {
|
|
@@ -1868,7 +1867,11 @@ class Server {
|
|
|
1868
1867
|
} else {
|
|
1869
1868
|
const primary_port = 80
|
|
1870
1869
|
const secondary_port = 42000
|
|
1871
|
-
const
|
|
1870
|
+
const running1 = await Util.port_running("localhost", primary_port)
|
|
1871
|
+
const running2 = await Util.port_running("127.0.0.1", primary_port)
|
|
1872
|
+
const running = running1 || running2
|
|
1873
|
+
const available = !running
|
|
1874
|
+
//const available = await portfinder.isAvailablePromise({ host: "0.0.0.0", port: primary_port })
|
|
1872
1875
|
console.log("check available", { primary_port, available })
|
|
1873
1876
|
if (available) {
|
|
1874
1877
|
this.port = primary_port
|
|
@@ -2105,7 +2108,83 @@ class Server {
|
|
|
2105
2108
|
Util.openfs(req.body.path)
|
|
2106
2109
|
res.json({ success: true })
|
|
2107
2110
|
}))
|
|
2111
|
+
this.app.get("/proxy", ex(async (req, res) => {
|
|
2112
|
+
let live_proxies = this.kernel.api.proxies["/proxy"]
|
|
2113
|
+
if (!live_proxies) live_proxies = []
|
|
2114
|
+
let proxies = [{
|
|
2115
|
+
icon: "ollama.webp",
|
|
2116
|
+
name: "Ollama",
|
|
2117
|
+
target: 'http://127.0.0.1:11434',
|
|
2118
|
+
port: 42420
|
|
2119
|
+
// }, {
|
|
2120
|
+
// name: "LMStudio",
|
|
2121
|
+
// target: 'http://127.0.0.1:1234',
|
|
2122
|
+
// port: 42421
|
|
2123
|
+
}]
|
|
2124
|
+
for(let i=0; i<proxies.length; i++) {
|
|
2125
|
+
proxies[i].running = false
|
|
2126
|
+
for(let live_proxy of live_proxies) {
|
|
2127
|
+
if (live_proxy.name === proxies[i].name) {
|
|
2128
|
+
proxies[i].running = true
|
|
2129
|
+
proxies[i].proxy = live_proxy.proxy
|
|
2130
|
+
proxies[i].qr = await QRCode.toDataURL(live_proxy.proxy)
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
let pinokio_proxy = this.kernel.api.proxies["/"]
|
|
2136
|
+
let pinokio_cloudflare = this.cloudflare_pub
|
|
2137
|
+
|
|
2138
|
+
let qr = null
|
|
2139
|
+
let qr_cloudflare = null
|
|
2140
|
+
let home_proxy = null
|
|
2141
|
+
if (pinokio_proxy && pinokio_proxy.length > 0) {
|
|
2142
|
+
qr = await QRCode.toDataURL(pinokio_proxy[0].proxy)
|
|
2143
|
+
home_proxy = pinokio_proxy[0]
|
|
2144
|
+
}
|
|
2108
2145
|
|
|
2146
|
+
let icon
|
|
2147
|
+
if (this.theme === "dark") {
|
|
2148
|
+
icon = "pinokio-white.png"
|
|
2149
|
+
} else {
|
|
2150
|
+
icon = "pinokio-black.png"
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
res.render("proxy", {
|
|
2154
|
+
agent: this.agent,
|
|
2155
|
+
theme: this.theme,
|
|
2156
|
+
items: proxies,
|
|
2157
|
+
qr,
|
|
2158
|
+
proxy: home_proxy,
|
|
2159
|
+
localhost: `http://localhost:${this.port}`,
|
|
2160
|
+
icon
|
|
2161
|
+
})
|
|
2162
|
+
}))
|
|
2163
|
+
this.app.post("/proxy", ex(async (req, res) => {
|
|
2164
|
+
/*
|
|
2165
|
+
req.body := {
|
|
2166
|
+
action: "start"|"stop",
|
|
2167
|
+
name: <name>,
|
|
2168
|
+
target: <target url>,
|
|
2169
|
+
port: <proxy port>
|
|
2170
|
+
}
|
|
2171
|
+
*/
|
|
2172
|
+
if (req.body && req.body.target && req.body.action && req.body.name) {
|
|
2173
|
+
//let port = new URL(req.body.target).port
|
|
2174
|
+
//let port = await this.kernel.port()
|
|
2175
|
+
if (req.body.action === "start") {
|
|
2176
|
+
let port = req.body.port
|
|
2177
|
+
console.log("start proxy")
|
|
2178
|
+
await this.kernel.api.startProxy("/proxy", req.body.target, req.body.name, { port })
|
|
2179
|
+
console.log(this.kernel.api.proxies)
|
|
2180
|
+
} else if (req.body.action === "stop") {
|
|
2181
|
+
await this.kernel.api.stopProxy({
|
|
2182
|
+
uri: req.body.target
|
|
2183
|
+
})
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
res.json({ success: true })
|
|
2187
|
+
}))
|
|
2109
2188
|
this.app.post("/unpublish", ex(async (req, res) => {
|
|
2110
2189
|
/*
|
|
2111
2190
|
req.body := {
|
|
@@ -2667,8 +2746,8 @@ class Server {
|
|
|
2667
2746
|
fs.uri("api", "https://github.com/cocktailpeanut/llamacpp.pinokio.git/icon.png")
|
|
2668
2747
|
fs.uri("bin", "python/bin")
|
|
2669
2748
|
|
|
2670
|
-
1. Git URI: http://localhost
|
|
2671
|
-
2. Local path: http://localhost
|
|
2749
|
+
1. Git URI: http://localhost/pinokio/fs?drive=api&path=https://github.com/cocktailpeanut/llamacpp.pinokio.git/icon.png
|
|
2750
|
+
2. Local path: http://localhost/pinokio/fs?drive=api&path=test/icon.png
|
|
2672
2751
|
*/
|
|
2673
2752
|
this.app.get("/pinokio/fs", ex((req, res) => {
|
|
2674
2753
|
// serve reaw files
|
|
Binary file
|
package/server/public/style.css
CHANGED
|
@@ -1601,17 +1601,36 @@ footer.status {
|
|
|
1601
1601
|
bottom: 0;
|
|
1602
1602
|
right: 0;
|
|
1603
1603
|
z-index: 100000000;
|
|
1604
|
+
display: flex;
|
|
1605
|
+
flex-direction: column;
|
|
1604
1606
|
}
|
|
1605
1607
|
#error-screen pre {
|
|
1606
1608
|
padding: 20px;
|
|
1607
1609
|
box-sizing: border-box;
|
|
1608
1610
|
width: 100%;
|
|
1611
|
+
/*
|
|
1609
1612
|
height: 100%;
|
|
1613
|
+
*/
|
|
1610
1614
|
margin: 0;
|
|
1611
1615
|
background: royalblue;
|
|
1612
1616
|
color: white;
|
|
1613
1617
|
overflow: auto;
|
|
1618
|
+
flex-grow: 1;
|
|
1619
|
+
}
|
|
1620
|
+
#error-screen-footer {
|
|
1621
|
+
padding: 10px;
|
|
1622
|
+
box-sizing: border-box;
|
|
1623
|
+
text-align: center;
|
|
1624
|
+
background: royalblue;
|
|
1625
|
+
}
|
|
1626
|
+
#error-screen-close {
|
|
1627
|
+
background: rgba(0, 0, 0, 0.6);
|
|
1628
|
+
font-size: 16px;
|
|
1629
|
+
padding: 10px 20px;
|
|
1630
|
+
font-weight: bold;
|
|
1631
|
+
width: 100%;
|
|
1614
1632
|
}
|
|
1633
|
+
|
|
1615
1634
|
mark{
|
|
1616
1635
|
background: red;
|
|
1617
1636
|
color: white;
|
|
@@ -676,9 +676,6 @@ const subUrlOf = (s, f) => {
|
|
|
676
676
|
if (!fullParsed.searchParams.has(key)) {
|
|
677
677
|
return false
|
|
678
678
|
}
|
|
679
|
-
console.log("key", key)
|
|
680
|
-
console.log("f", fullParsed.searchParams.get(key))
|
|
681
|
-
console.log("s", val)
|
|
682
679
|
if (fullParsed.searchParams.get(key) !== val) {
|
|
683
680
|
return false
|
|
684
681
|
}
|
|
@@ -691,7 +688,6 @@ const renderCursor = () => {
|
|
|
691
688
|
el.classList.remove("cursor")
|
|
692
689
|
})
|
|
693
690
|
const target = document.querySelector(".selectable[data-index='" + cursorIndex + "']")
|
|
694
|
-
console.log({ cursorIndex, target })
|
|
695
691
|
target.classList.add("cursor")
|
|
696
692
|
}
|
|
697
693
|
const nav = (type) => {
|
|
@@ -794,7 +790,6 @@ const renderSelection = (e, force) => {
|
|
|
794
790
|
//
|
|
795
791
|
// => select that tab and frame
|
|
796
792
|
|
|
797
|
-
console.log("target", target)
|
|
798
793
|
if (target) {
|
|
799
794
|
|
|
800
795
|
// display the menu button at the top
|
|
@@ -809,12 +804,10 @@ const renderSelection = (e, force) => {
|
|
|
809
804
|
}
|
|
810
805
|
|
|
811
806
|
if (target) {
|
|
812
|
-
console.log("# 3")
|
|
813
807
|
|
|
814
808
|
|
|
815
809
|
// Instantiate a frame with the selected target's href
|
|
816
810
|
let url = target.href
|
|
817
|
-
console.log("> url", url)
|
|
818
811
|
document.querySelector("#open-browser").href = url
|
|
819
812
|
document.querySelector("#clone-tab").setAttribute("data-href", url)
|
|
820
813
|
document.querySelector("#location").value = url
|
|
@@ -1409,6 +1402,11 @@ const refresh = async (silent) => {
|
|
|
1409
1402
|
// render the selected frame only if not silent
|
|
1410
1403
|
if (silent) {
|
|
1411
1404
|
} else {
|
|
1405
|
+
await new Promise((resolve, reject) => {
|
|
1406
|
+
setTimeout(() => {
|
|
1407
|
+
resolve()
|
|
1408
|
+
}, 1000)
|
|
1409
|
+
})
|
|
1412
1410
|
renderSelection()
|
|
1413
1411
|
}
|
|
1414
1412
|
}
|
package/server/views/editor.ejs
CHANGED
|
@@ -490,6 +490,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
490
490
|
separateWordSearch: false,
|
|
491
491
|
accuracy: "exactly"
|
|
492
492
|
})
|
|
493
|
+
/*
|
|
494
|
+
document.querySelector("#error-screen").addEventListener("click", (e) => {
|
|
495
|
+
document.querySelector("#error-screen").classList.add("hidden")
|
|
496
|
+
})
|
|
497
|
+
*/
|
|
493
498
|
console.log("instance", instance)
|
|
494
499
|
console.log("EVENT", packet.event)
|
|
495
500
|
instance.mark(packet.event)
|
|
@@ -882,6 +887,9 @@ const reloadMemory = async () => {
|
|
|
882
887
|
<% } %>
|
|
883
888
|
<div id='error-screen' class='hidden'>
|
|
884
889
|
<pre></pre>
|
|
890
|
+
<div id='error-screen-footer'>
|
|
891
|
+
<button id='error-screen-close' class='btn'>Close</button>
|
|
892
|
+
</div>
|
|
885
893
|
</div>
|
|
886
894
|
</body>
|
|
887
895
|
</html>
|
|
@@ -449,6 +449,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
449
449
|
separateWordSearch: false,
|
|
450
450
|
accuracy: "exactly"
|
|
451
451
|
})
|
|
452
|
+
/*
|
|
453
|
+
document.querySelector("#error-screen").addEventListener("click", (e) => {
|
|
454
|
+
document.querySelector("#error-screen").classList.add("hidden")
|
|
455
|
+
})
|
|
456
|
+
*/
|
|
452
457
|
console.log("instance", instance)
|
|
453
458
|
console.log("EVENT", packet.event)
|
|
454
459
|
instance.mark(packet.event)
|
|
@@ -500,7 +505,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
500
505
|
document.querySelector(".run .starting").classList.add("hidden")
|
|
501
506
|
document.querySelector(".run .stop").classList.add("hidden")
|
|
502
507
|
}, 0)
|
|
503
|
-
this.socket.close()
|
|
508
|
+
//this.socket.close()
|
|
504
509
|
}
|
|
505
510
|
}
|
|
506
511
|
|
|
@@ -820,6 +825,9 @@ const reloadMemory = async () => {
|
|
|
820
825
|
<% } %>
|
|
821
826
|
<div id='error-screen' class='hidden'>
|
|
822
827
|
<pre></pre>
|
|
828
|
+
<div id='error-screen-footer' class='hidden'>
|
|
829
|
+
<button id='error-screen-close' class='btn'>Close</button>
|
|
830
|
+
</div>
|
|
823
831
|
</div>
|
|
824
832
|
</body>
|
|
825
833
|
</html>
|
package/server/views/index.ejs
CHANGED
|
@@ -182,6 +182,7 @@ body.dark .browser-options-row {
|
|
|
182
182
|
</form>
|
|
183
183
|
<% if (ishome) { %>
|
|
184
184
|
<div class='nav-btns'>
|
|
185
|
+
<a class='btn2' href='/proxy'><div><i class="fa-solid fa-wifi"></i></div><div>Share</div></a>
|
|
185
186
|
<a class='btn2' id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
|
|
186
187
|
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-face-smile-beam"></i></div><div>Community</div></a>
|
|
187
188
|
<% if (agent === "electron") { %>
|
|
@@ -235,24 +236,32 @@ body.dark .browser-options-row {
|
|
|
235
236
|
</div>
|
|
236
237
|
</div>
|
|
237
238
|
<% } %>
|
|
239
|
+
<!--
|
|
238
240
|
<div class='browser-options-row'>
|
|
239
|
-
|
|
240
|
-
<
|
|
241
|
-
<div
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<a id='wifi-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
241
|
+
<a href="/proxy" class='flexible'>
|
|
242
|
+
<h3><i class="fa-solid fa-wifi"></i> Local Share</h3>
|
|
243
|
+
<div>Access Pinokio, Ollama, and other 3rd party apps from any device on the same network. By default it's off.</div>
|
|
244
|
+
</a>
|
|
245
|
+
<% if (false) { %>
|
|
246
|
+
<% if (proxy) { %>
|
|
247
|
+
<a id='wifi-stop' data-proxy="<%=proxy.proxy%>" class='btn'><div><i class="fa-solid fa-stop"></i> Stop</div></a>
|
|
248
|
+
<div class='flexible'>
|
|
249
|
+
<h3><i class="fa-solid fa-wifi"></i> Local Share</h3>
|
|
250
|
+
<div>Access Pinokio from any device on the same network. By default it's off.</div>
|
|
251
|
+
<span class='badge'><i class="fa-solid fa-circle-notch fa-spin"></i> Running at <a href="<%=proxy.proxy%>" target="_blank" features="browser"><%=proxy.proxy%></a></span>
|
|
252
|
+
<a id='wifi-qr' class='badge'><i class="fa-solid fa-qrcode"></i> Scan QR Code</a>
|
|
253
|
+
</div>
|
|
254
|
+
<% } else { %>
|
|
255
|
+
<a id='wifi' class='btn'><div><i class="fa-solid fa-play"></i> Start</div></a>
|
|
256
|
+
<a id='wifi-starting' class='btn disabled hidden'><div><i class="fa-solid fa-circle-notch fa-spin"></i> Starting</div></a>
|
|
257
|
+
<div class='flexible'>
|
|
258
|
+
<h3><i class="fa-solid fa-wifi"></i> Local Share</h3>
|
|
259
|
+
<div>Access Pinokio from any device on the same network. By default it's off.</div>
|
|
260
|
+
</div>
|
|
261
|
+
<% } %>
|
|
254
262
|
<% } %>
|
|
255
263
|
</div>
|
|
264
|
+
-->
|
|
256
265
|
<div class='browser-options-row hidden'>
|
|
257
266
|
<% if (cloudflare_pub) { %>
|
|
258
267
|
<a id='cloudflare-stop' class='btn'><div><i class="fa-solid fa-stop"></i> Stop</div></a>
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Pinokio</title>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
|
6
|
+
<link href="/xterm.min.css" rel="stylesheet" />
|
|
7
|
+
<link href="/css/fontawesome.min.css" rel="stylesheet">
|
|
8
|
+
<link href="/css/solid.min.css" rel="stylesheet">
|
|
9
|
+
<link href="/css/regular.min.css" rel="stylesheet">
|
|
10
|
+
<link href="/css/brands.min.css" rel="stylesheet">
|
|
11
|
+
<link href="/markdown.css" rel="stylesheet"/>
|
|
12
|
+
<link href="/noty.css" rel="stylesheet"/>
|
|
13
|
+
<link href="/style.css" rel="stylesheet"/>
|
|
14
|
+
<script src="/sweetalert2.js"></script>
|
|
15
|
+
<% if (agent === "electron") { %>
|
|
16
|
+
<link href="/electron.css" rel="stylesheet"/>
|
|
17
|
+
<% } %>
|
|
18
|
+
<style>
|
|
19
|
+
.line2 {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
background: rgba(0,0,100,0.04);
|
|
24
|
+
}
|
|
25
|
+
.line2 a {
|
|
26
|
+
text-decoration: none;
|
|
27
|
+
color: black;
|
|
28
|
+
}
|
|
29
|
+
.status {
|
|
30
|
+
padding: 10px;
|
|
31
|
+
margin: 10px;
|
|
32
|
+
border-radius: 10px;
|
|
33
|
+
}
|
|
34
|
+
.status.offline {
|
|
35
|
+
background: silver;
|
|
36
|
+
}
|
|
37
|
+
.status.online {
|
|
38
|
+
background: yellowgreen;
|
|
39
|
+
}
|
|
40
|
+
.switch {
|
|
41
|
+
padding: 10px;
|
|
42
|
+
margin: 10px 0;
|
|
43
|
+
}
|
|
44
|
+
.switch[data-online=true] {
|
|
45
|
+
color: yellowgreen;
|
|
46
|
+
}
|
|
47
|
+
.button {
|
|
48
|
+
padding: 10px;
|
|
49
|
+
}
|
|
50
|
+
.on, .off {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
}
|
|
54
|
+
header .runner {
|
|
55
|
+
padding: 10px 0 0;
|
|
56
|
+
}
|
|
57
|
+
.proxy {
|
|
58
|
+
padding: 0 10px;
|
|
59
|
+
text-align: right;
|
|
60
|
+
}
|
|
61
|
+
.proxy a {
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
display: inline-block;
|
|
64
|
+
padding: 5px 10px;
|
|
65
|
+
border-radius: 2px;
|
|
66
|
+
background: black;
|
|
67
|
+
color: white;
|
|
68
|
+
}
|
|
69
|
+
.browser-options-btns {
|
|
70
|
+
display: flex;
|
|
71
|
+
padding-top: 5px;
|
|
72
|
+
}
|
|
73
|
+
.browser-options-row {
|
|
74
|
+
border-top: 1px solid rgba(0,0,0,0.1);
|
|
75
|
+
border-bottom: 1px solid rgba(0,0,0,0.1);
|
|
76
|
+
display: flex;
|
|
77
|
+
padding: 20px 0;
|
|
78
|
+
align-items: flex-start;
|
|
79
|
+
}
|
|
80
|
+
.browser-options-row h3 {
|
|
81
|
+
margin: 0;
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
margin-bottom: 3px;
|
|
84
|
+
}
|
|
85
|
+
.browser-options-row .btn.disabled {
|
|
86
|
+
opacity: 0.7;
|
|
87
|
+
}
|
|
88
|
+
.browser-options-row .btn {
|
|
89
|
+
font-weight: bold;
|
|
90
|
+
width: 100px;
|
|
91
|
+
text-align: center;
|
|
92
|
+
display: block;
|
|
93
|
+
margin-right: 10px;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
padding: 5px;
|
|
96
|
+
}
|
|
97
|
+
.badge {
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
padding: 5px 10px;
|
|
100
|
+
background: rgba(0,0,100,0.1);
|
|
101
|
+
border-radius: 5px;
|
|
102
|
+
display: inline-block;
|
|
103
|
+
margin-top: 5px;
|
|
104
|
+
}
|
|
105
|
+
a.badge {
|
|
106
|
+
color: royalblue;
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
}
|
|
110
|
+
.badge a {
|
|
111
|
+
color: royalblue;
|
|
112
|
+
text-decoration: none;
|
|
113
|
+
}
|
|
114
|
+
body.dark .btn {
|
|
115
|
+
color: white;
|
|
116
|
+
border: 1px solid rgba(255,255,255,0.3);
|
|
117
|
+
}
|
|
118
|
+
body.dark .badge {
|
|
119
|
+
background: rgba(255,255,255,0.1);
|
|
120
|
+
}
|
|
121
|
+
body.dark .browser-options-row {
|
|
122
|
+
border-top: 1px solid rgba(255,255,255,0.05);
|
|
123
|
+
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
124
|
+
}
|
|
125
|
+
.description a {
|
|
126
|
+
color: royalblue;
|
|
127
|
+
text-decoration: none;
|
|
128
|
+
}
|
|
129
|
+
.line.align-top h3 {
|
|
130
|
+
align-items: flex-start;
|
|
131
|
+
}
|
|
132
|
+
.line a:hover {
|
|
133
|
+
color: red;
|
|
134
|
+
}
|
|
135
|
+
.btn-container {
|
|
136
|
+
margin-top: 10px;
|
|
137
|
+
}
|
|
138
|
+
.header-label {
|
|
139
|
+
font-size: 30px;
|
|
140
|
+
letter-spacing: -1px;
|
|
141
|
+
padding: 15px;
|
|
142
|
+
text-align: center;
|
|
143
|
+
}
|
|
144
|
+
@media only screen and (max-width: 480px) {
|
|
145
|
+
.btn2 {
|
|
146
|
+
padding: 5px;
|
|
147
|
+
font-size: 11px;
|
|
148
|
+
}
|
|
149
|
+
.nav-btns {
|
|
150
|
+
flex-grow: 1;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
padding: 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
</style>
|
|
156
|
+
<body class='<%=theme%>'>
|
|
157
|
+
<header class='navheader grabbable'>
|
|
158
|
+
<h1>
|
|
159
|
+
<a class='home' href="/"><i class="fa-solid fa-house"></i></a>
|
|
160
|
+
<button class='btn2' id='back'><i class="fa-solid fa-chevron-left"></i></button>
|
|
161
|
+
<button class='btn2' id='forward'><i class="fa-solid fa-chevron-right"></i></button>
|
|
162
|
+
<div class='flexible'></div>
|
|
163
|
+
<div class='nav-btns'>
|
|
164
|
+
<a class='btn2' id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
|
|
165
|
+
<a class='btn2' href="https://portal.pinokio.computer/" target="_blank"><div><i class="fa-solid fa-face-smile-beam"></i></div><div>Community</div></a>
|
|
166
|
+
<button class='btn2' id='genlog'><div><i class="fa-solid fa-laptop-code"></i></div><div>Debug</div></button>
|
|
167
|
+
<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>
|
|
168
|
+
<a class='btn2' href="/?mode=settings"><div><i class="fa-solid fa-gear"></i></div><div>Settings</div></a>
|
|
169
|
+
<button id='new-window' title='open a new window' class='btn2'><div><i class="fa-solid fa-plus"></i></div><div>Window</div></button>
|
|
170
|
+
</div>
|
|
171
|
+
</h1>
|
|
172
|
+
</header>
|
|
173
|
+
<main>
|
|
174
|
+
<div class='container'>
|
|
175
|
+
<div class='not-running-apps'>
|
|
176
|
+
<div class='header-label'><i class='fa-solid fa-wifi'></i> Platform Local Sharing</div>
|
|
177
|
+
<ol>
|
|
178
|
+
<li><strong>PLATFORM LOCAL SHARING:</strong> Press "Start" to make the app accessible from any device on the same network</li>
|
|
179
|
+
<li><strong>APP LOCAL SHARING:</strong> This dashboard only lets you locally share the platforms. To do this for apps, look inside each app's Configure tab</li>
|
|
180
|
+
</ol>
|
|
181
|
+
<div class='line align-top'>
|
|
182
|
+
<h3>
|
|
183
|
+
<img class='icon' src="<%=icon%>">
|
|
184
|
+
<div class='col'>
|
|
185
|
+
<div class='title'>Pinokio</div>
|
|
186
|
+
<% if (proxy) { %>
|
|
187
|
+
<div class='description'><a href="<%=proxy.proxy%>" target="_blank"><%=proxy.proxy%></a> → <a href="<%=localhost%>" target="_blank"><%=localhost%></a></div>
|
|
188
|
+
<a class='wifi-qr badge' data-qr="<%=qr%>" data-url="<%=proxy.proxy%>"><i class="fa-solid fa-qrcode"></i> Scan QR Code</a>
|
|
189
|
+
<% } else { %>
|
|
190
|
+
<div class='description'>Share Pinokio <a href="<%=localhost%>" target="_blank"><%=localhost%></a> with other devices on the same network</div>
|
|
191
|
+
<% } %>
|
|
192
|
+
<div class='btn-container'>
|
|
193
|
+
<% if (proxy) { %>
|
|
194
|
+
<div id='wifi-stop' data-proxy="<%=proxy.proxy%>" class='btn'><div><i class="fa-solid fa-stop"></i> Stop</div></div>
|
|
195
|
+
<% } else { %>
|
|
196
|
+
<div id='wifi' class='btn'><div><i class="fa-solid fa-play"></i> Start</div></div>
|
|
197
|
+
<div id='wifi-starting' class='btn disabled hidden'><div><i class="fa-solid fa-circle-notch fa-spin"></i> Starting</div></div>
|
|
198
|
+
<% } %>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</h3>
|
|
202
|
+
</div>
|
|
203
|
+
<% items.forEach((item) => { %>
|
|
204
|
+
<div class='line align-top'>
|
|
205
|
+
<h3>
|
|
206
|
+
<img class='icon' src="<%=item.icon%>">
|
|
207
|
+
<div class='col'>
|
|
208
|
+
<div class='title'><%=item.name%></div>
|
|
209
|
+
<% if (item.proxy) { %>
|
|
210
|
+
<div class='description'><a href="<%=item.proxy%>" target="_blank"><%=item.proxy%></a> → <a href="<%=item.target%>" target="_blank"><%=item.target%></a></div>
|
|
211
|
+
<a class='wifi-qr badge' data-qr="<%=item.qr%>" data-url="<%=item.proxy%>"><i class="fa-solid fa-qrcode"></i> Scan QR Code</a>
|
|
212
|
+
<% } else { %>
|
|
213
|
+
<div class='description'>Share <%=item.target%> with other devices on the same network</div>
|
|
214
|
+
<% } %>
|
|
215
|
+
<div class='btn-container'>
|
|
216
|
+
<% if (item.running) { %>
|
|
217
|
+
<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>
|
|
218
|
+
<% } else { %>
|
|
219
|
+
<div data-port="<%=item.port%>" data-name="<%=item.name%>" data-target="<%=item.target%>" class='start btn'><i class="fa-solid fa-play"></i> Start</div>
|
|
220
|
+
<% } %>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</h3>
|
|
224
|
+
</div>
|
|
225
|
+
<% }) %>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</main>
|
|
230
|
+
<script>
|
|
231
|
+
document.querySelector(".container").addEventListener("click", async (e) => {
|
|
232
|
+
let target
|
|
233
|
+
if (e.target.classList.contains("stop")) {
|
|
234
|
+
target = e.target
|
|
235
|
+
} else {
|
|
236
|
+
target = e.target.closest(".stop")
|
|
237
|
+
}
|
|
238
|
+
if (target) {
|
|
239
|
+
// stop
|
|
240
|
+
let res = await fetch("/proxy", {
|
|
241
|
+
method: "post",
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "application/json"
|
|
244
|
+
},
|
|
245
|
+
body: JSON.stringify({
|
|
246
|
+
action: "stop",
|
|
247
|
+
target: target.getAttribute("data-target"),
|
|
248
|
+
name: target.getAttribute("data-name")
|
|
249
|
+
})
|
|
250
|
+
}).then((res) => {
|
|
251
|
+
return res.json()
|
|
252
|
+
})
|
|
253
|
+
location.href = location.href
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (e.target.classList.contains("start")) {
|
|
258
|
+
target = e.target
|
|
259
|
+
} else {
|
|
260
|
+
target = e.target.closest(".start")
|
|
261
|
+
}
|
|
262
|
+
if (target) {
|
|
263
|
+
let res = await fetch("/proxy", {
|
|
264
|
+
method: "post",
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/json"
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
action: "start",
|
|
270
|
+
target: target.getAttribute("data-target"),
|
|
271
|
+
name: target.getAttribute("data-name"),
|
|
272
|
+
port: target.getAttribute("data-port")
|
|
273
|
+
})
|
|
274
|
+
}).then((res) => {
|
|
275
|
+
return res.json()
|
|
276
|
+
})
|
|
277
|
+
location.href = location.href
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (e.target.classList.contains("wifi-qr")) {
|
|
281
|
+
target = e.target
|
|
282
|
+
} else {
|
|
283
|
+
target = e.target.closest(".wifi-qr")
|
|
284
|
+
}
|
|
285
|
+
if (target) {
|
|
286
|
+
let url = target.getAttribute("data-url")
|
|
287
|
+
let qr = target.getAttribute("data-qr")
|
|
288
|
+
console.log({ url, qr })
|
|
289
|
+
await Swal.fire({
|
|
290
|
+
title: "Local Share",
|
|
291
|
+
customClass: {
|
|
292
|
+
popup: "min-popup",
|
|
293
|
+
title: "min-title"
|
|
294
|
+
},
|
|
295
|
+
showConfirmButton: false,
|
|
296
|
+
html: `<div class='min-modal'>
|
|
297
|
+
<div>Access Pinokio from</div>
|
|
298
|
+
<div>any local network device</div>
|
|
299
|
+
<div><img src="${qr}"></div>
|
|
300
|
+
<a href="${url}" target="_blank">${url}</a>
|
|
301
|
+
</div>`
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
})
|
|
306
|
+
if (document.querySelector("#back")) {
|
|
307
|
+
document.querySelector("#back").addEventListener("click", (e) => {
|
|
308
|
+
history.back()
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
if (document.querySelector("#forward")) {
|
|
312
|
+
document.querySelector("#forward").addEventListener("click", (e) => {
|
|
313
|
+
history.forward()
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
<% if (proxy) { %>
|
|
317
|
+
if (document.querySelector("#wifi-stop")) {
|
|
318
|
+
document.querySelector("#wifi-stop").addEventListener("click", async (e) => {
|
|
319
|
+
let res = await fetch("/unpublish", {
|
|
320
|
+
method: "post",
|
|
321
|
+
headers: {
|
|
322
|
+
"Content-Type": "application/json"
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
type: "local"
|
|
326
|
+
})
|
|
327
|
+
}).then((res) => {
|
|
328
|
+
return res.json()
|
|
329
|
+
})
|
|
330
|
+
console.log(res)
|
|
331
|
+
location.href = location.href
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
<% } else { %>
|
|
335
|
+
if (document.querySelector("#wifi")) {
|
|
336
|
+
document.querySelector("#wifi").addEventListener("click", async (e) => {
|
|
337
|
+
document.querySelector("#wifi").classList.add("hidden")
|
|
338
|
+
document.querySelector("#wifi-starting").classList.remove("hidden")
|
|
339
|
+
let res = await fetch("/publish", {
|
|
340
|
+
method: "post",
|
|
341
|
+
headers: {
|
|
342
|
+
"Content-Type": "application/json"
|
|
343
|
+
},
|
|
344
|
+
body: JSON.stringify({
|
|
345
|
+
type: "local"
|
|
346
|
+
})
|
|
347
|
+
}).then((res) => {
|
|
348
|
+
return res.json()
|
|
349
|
+
})
|
|
350
|
+
console.log(res)
|
|
351
|
+
location.href = location.href
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
<% } %>
|
|
355
|
+
</script>
|
|
356
|
+
</body>
|
|
357
|
+
</html>
|