pinokiod 3.180.0 → 3.182.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/favicon.js +91 -34
- package/kernel/peer.js +73 -0
- package/kernel/util.js +28 -4
- package/package.json +1 -1
- package/server/index.js +237 -35
- package/server/public/common.js +677 -240
- package/server/public/files-app/app.css +64 -0
- package/server/public/files-app/app.js +87 -0
- package/server/public/install.js +8 -1
- package/server/public/layout.js +124 -0
- package/server/public/nav.js +227 -64
- package/server/public/sound/beep.mp3 +0 -0
- package/server/public/sound/bell.mp3 +0 -0
- package/server/public/sound/bright-ring.mp3 +0 -0
- package/server/public/sound/clap.mp3 +0 -0
- package/server/public/sound/deep-ring.mp3 +0 -0
- package/server/public/sound/gasp.mp3 +0 -0
- package/server/public/sound/hehe.mp3 +0 -0
- package/server/public/sound/levelup.mp3 +0 -0
- package/server/public/sound/light-pop.mp3 +0 -0
- package/server/public/sound/light-ring.mp3 +0 -0
- package/server/public/sound/meow.mp3 +0 -0
- package/server/public/sound/piano.mp3 +0 -0
- package/server/public/sound/pop.mp3 +0 -0
- package/server/public/sound/uhoh.mp3 +0 -0
- package/server/public/sound/whistle.mp3 +0 -0
- package/server/public/style.css +195 -4
- package/server/public/tab-idle-notifier.js +700 -4
- package/server/public/terminal-settings.js +1131 -0
- package/server/public/urldropdown.css +28 -1
- package/server/socket.js +71 -4
- package/server/views/{terminals.ejs → agents.ejs} +108 -32
- package/server/views/app.ejs +321 -104
- package/server/views/bootstrap.ejs +8 -0
- package/server/views/connect.ejs +10 -1
- package/server/views/d.ejs +172 -18
- package/server/views/editor.ejs +8 -0
- package/server/views/file_browser.ejs +4 -0
- package/server/views/index.ejs +10 -1
- package/server/views/init/index.ejs +18 -3
- package/server/views/install.ejs +8 -0
- package/server/views/layout.ejs +2 -0
- package/server/views/net.ejs +10 -1
- package/server/views/network.ejs +10 -1
- package/server/views/pro.ejs +8 -0
- package/server/views/prototype/index.ejs +8 -0
- package/server/views/screenshots.ejs +10 -2
- package/server/views/settings.ejs +10 -2
- package/server/views/shell.ejs +8 -0
- package/server/views/terminal.ejs +8 -0
- package/server/views/tools.ejs +10 -2
package/server/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const ini = require('ini')
|
|
|
38
38
|
const ejs = require('ejs');
|
|
39
39
|
|
|
40
40
|
const DEFAULT_PORT = 42000
|
|
41
|
+
const NOTIFICATION_SOUND_EXTENSIONS = new Set(['.aac', '.flac', '.m4a', '.mp3', '.ogg', '.wav', '.webm'])
|
|
41
42
|
|
|
42
43
|
const ex = fn => (req, res, next) => {
|
|
43
44
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
@@ -1509,10 +1510,15 @@ class Server {
|
|
|
1509
1510
|
drive: path.resolve(this.kernel.homedir, "drive"),
|
|
1510
1511
|
}
|
|
1511
1512
|
}
|
|
1513
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
1514
|
+
let peer_qr = null
|
|
1515
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
1512
1516
|
let list = this.getPeers()
|
|
1513
1517
|
res.render("settings", {
|
|
1514
1518
|
list,
|
|
1515
1519
|
current_host: this.kernel.peer.host,
|
|
1520
|
+
peer_url,
|
|
1521
|
+
peer_qr,
|
|
1516
1522
|
platform,
|
|
1517
1523
|
version: this.version,
|
|
1518
1524
|
logo: this.logo,
|
|
@@ -2234,6 +2240,10 @@ class Server {
|
|
|
2234
2240
|
qr_cloudflare = await QRCode.toDataURL(this.cloudflare_pub)
|
|
2235
2241
|
}
|
|
2236
2242
|
|
|
2243
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
2244
|
+
let peer_qr = null
|
|
2245
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
2246
|
+
|
|
2237
2247
|
// custom theme
|
|
2238
2248
|
let exists = await fse.pathExists(this.kernel.path("web"))
|
|
2239
2249
|
if (exists) {
|
|
@@ -2263,6 +2273,8 @@ class Server {
|
|
|
2263
2273
|
res.render("index", {
|
|
2264
2274
|
list,
|
|
2265
2275
|
current_host: this.kernel.peer.host,
|
|
2276
|
+
peer_url,
|
|
2277
|
+
peer_qr,
|
|
2266
2278
|
current_urls,
|
|
2267
2279
|
portal: this.portal,
|
|
2268
2280
|
install: this.install,
|
|
@@ -3433,8 +3445,8 @@ class Server {
|
|
|
3433
3445
|
}
|
|
3434
3446
|
terminal = {
|
|
3435
3447
|
icon: "fa-solid fa-terminal",
|
|
3436
|
-
title: "
|
|
3437
|
-
subtitle: "Open
|
|
3448
|
+
title: "Shell",
|
|
3449
|
+
subtitle: "Open an interactive terminal in the browser",
|
|
3438
3450
|
menu: terminals
|
|
3439
3451
|
}
|
|
3440
3452
|
} else {
|
|
@@ -3901,6 +3913,46 @@ class Server {
|
|
|
3901
3913
|
getTheme: () => this.theme,
|
|
3902
3914
|
exists: (target) => this.exists(target),
|
|
3903
3915
|
});
|
|
3916
|
+
|
|
3917
|
+
this.app.get('/pinokio/notification-sounds', ex(async (req, res) => {
|
|
3918
|
+
const soundRoot = path.resolve(__dirname, 'public', 'sound');
|
|
3919
|
+
let entries = [];
|
|
3920
|
+
try {
|
|
3921
|
+
const dirEntries = await fs.promises.readdir(soundRoot, { withFileTypes: true });
|
|
3922
|
+
entries = dirEntries
|
|
3923
|
+
.filter((entry) => entry.isFile())
|
|
3924
|
+
.map((entry) => entry.name)
|
|
3925
|
+
.filter((name) => NOTIFICATION_SOUND_EXTENSIONS.has(path.extname(name).toLowerCase()))
|
|
3926
|
+
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
|
3927
|
+
} catch (error) {
|
|
3928
|
+
if (error && error.code === 'ENOENT') {
|
|
3929
|
+
return res.json({ sounds: [] });
|
|
3930
|
+
}
|
|
3931
|
+
return res.status(500).json({
|
|
3932
|
+
error: 'Failed to enumerate notification sounds',
|
|
3933
|
+
details: error && error.message ? error.message : String(error || ''),
|
|
3934
|
+
});
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
const normalizeLabel = (filename) => {
|
|
3938
|
+
const withoutExt = filename.replace(/\.[^.]+$/, '');
|
|
3939
|
+
return withoutExt
|
|
3940
|
+
.replace(/[-_]+/g, ' ')
|
|
3941
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
3942
|
+
};
|
|
3943
|
+
|
|
3944
|
+
const sounds = entries.map((filename) => {
|
|
3945
|
+
const encoded = filename.split('/').map(encodeURIComponent).join('/');
|
|
3946
|
+
return {
|
|
3947
|
+
id: filename,
|
|
3948
|
+
label: normalizeLabel(filename),
|
|
3949
|
+
url: `/sound/${encoded}`,
|
|
3950
|
+
filename,
|
|
3951
|
+
};
|
|
3952
|
+
});
|
|
3953
|
+
|
|
3954
|
+
res.json({ sounds });
|
|
3955
|
+
}));
|
|
3904
3956
|
/*
|
|
3905
3957
|
this.app.get("/asset/*", ex((req, res) => {
|
|
3906
3958
|
let pathComponents = req.params[0].split("/")
|
|
@@ -3924,6 +3976,9 @@ class Server {
|
|
|
3924
3976
|
}))
|
|
3925
3977
|
*/
|
|
3926
3978
|
this.app.get("/tools", ex(async (req, res) => {
|
|
3979
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
3980
|
+
let peer_qr = null
|
|
3981
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
3927
3982
|
let list = this.getPeers()
|
|
3928
3983
|
let installs = []
|
|
3929
3984
|
for(let key in this.kernel.bin.installed) {
|
|
@@ -3955,6 +4010,8 @@ class Server {
|
|
|
3955
4010
|
}
|
|
3956
4011
|
res.render("tools", {
|
|
3957
4012
|
current_host: this.kernel.peer.host,
|
|
4013
|
+
peer_url,
|
|
4014
|
+
peer_qr,
|
|
3958
4015
|
pending,
|
|
3959
4016
|
installs,
|
|
3960
4017
|
bundles,
|
|
@@ -3966,7 +4023,7 @@ class Server {
|
|
|
3966
4023
|
list,
|
|
3967
4024
|
})
|
|
3968
4025
|
}))
|
|
3969
|
-
this.app.get("/
|
|
4026
|
+
this.app.get("/agents", ex(async (req, res) => {
|
|
3970
4027
|
if (!this.kernel.plugin.config) {
|
|
3971
4028
|
try {
|
|
3972
4029
|
await this.kernel.plugin.init()
|
|
@@ -4039,9 +4096,14 @@ class Server {
|
|
|
4039
4096
|
return (a.name || '').localeCompare(b.name || '')
|
|
4040
4097
|
})
|
|
4041
4098
|
|
|
4099
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
4100
|
+
let peer_qr = null
|
|
4101
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
4042
4102
|
const list = this.getPeers()
|
|
4043
|
-
res.render("
|
|
4103
|
+
res.render("agents", {
|
|
4044
4104
|
current_host: this.kernel.peer.host,
|
|
4105
|
+
peer_url,
|
|
4106
|
+
peer_qr,
|
|
4045
4107
|
pluginMenu,
|
|
4046
4108
|
apps,
|
|
4047
4109
|
portal: this.portal,
|
|
@@ -4051,13 +4113,35 @@ class Server {
|
|
|
4051
4113
|
list,
|
|
4052
4114
|
})
|
|
4053
4115
|
}))
|
|
4116
|
+
this.app.get("/api/plugin/menu", ex(async (req, res) => {
|
|
4117
|
+
try {
|
|
4118
|
+
if (!this.kernel.plugin.config) {
|
|
4119
|
+
await this.kernel.plugin.init()
|
|
4120
|
+
}
|
|
4121
|
+
const pluginMenu = this.kernel.plugin && this.kernel.plugin.config && Array.isArray(this.kernel.plugin.config.menu)
|
|
4122
|
+
? this.kernel.plugin.config.menu
|
|
4123
|
+
: []
|
|
4124
|
+
res.json({ menu: pluginMenu })
|
|
4125
|
+
} catch (error) {
|
|
4126
|
+
console.warn('Failed to load plugin menu for create launcher modal', error)
|
|
4127
|
+
res.json({ menu: [] })
|
|
4128
|
+
}
|
|
4129
|
+
}))
|
|
4054
4130
|
this.app.get("/plugins", (req, res) => {
|
|
4055
|
-
res.redirect(301, "/
|
|
4131
|
+
res.redirect(301, "/agents")
|
|
4132
|
+
})
|
|
4133
|
+
this.app.get("/terminals", (req, res) => {
|
|
4134
|
+
res.redirect(301, "/agents")
|
|
4056
4135
|
})
|
|
4057
4136
|
this.app.get("/screenshots", ex(async (req, res) => {
|
|
4137
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
4138
|
+
let peer_qr = null
|
|
4139
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
4058
4140
|
let list = this.getPeers()
|
|
4059
4141
|
res.render("screenshots", {
|
|
4060
4142
|
current_host: this.kernel.peer.host,
|
|
4143
|
+
peer_url,
|
|
4144
|
+
peer_qr,
|
|
4061
4145
|
version: this.version,
|
|
4062
4146
|
portal: this.portal,
|
|
4063
4147
|
logo: this.logo,
|
|
@@ -4144,6 +4228,7 @@ class Server {
|
|
|
4144
4228
|
// if <app_name>.localhost
|
|
4145
4229
|
// otherwise => redirect
|
|
4146
4230
|
|
|
4231
|
+
console.log("Chunks", chunks)
|
|
4147
4232
|
|
|
4148
4233
|
if (chunks.length >= 2) {
|
|
4149
4234
|
|
|
@@ -4190,29 +4275,31 @@ class Server {
|
|
|
4190
4275
|
} else {
|
|
4191
4276
|
nameChunks = chunks
|
|
4192
4277
|
}
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4278
|
+
if (nameChunks) {
|
|
4279
|
+
let name = nameChunks.join(".")
|
|
4280
|
+
let api_path = this.kernel.path("api", name)
|
|
4281
|
+
let exists = await this.exists(api_path)
|
|
4282
|
+
if (exists) {
|
|
4283
|
+
let meta = await this.kernel.api.meta(name)
|
|
4284
|
+
let launcher = await this.kernel.api.launcher(name)
|
|
4285
|
+
let pinokio = launcher.script
|
|
4286
|
+
let launchable = false
|
|
4287
|
+
if (pinokio && pinokio.menu && pinokio.menu.length > 0) {
|
|
4288
|
+
launchable = true
|
|
4289
|
+
}
|
|
4290
|
+
res.render("start", {
|
|
4291
|
+
url,
|
|
4292
|
+
launchable,
|
|
4293
|
+
autolaunch,
|
|
4294
|
+
logo: this.logo,
|
|
4295
|
+
theme: this.theme,
|
|
4296
|
+
agent: req.agent,
|
|
4297
|
+
name: meta.title,
|
|
4298
|
+
image: meta.icon,
|
|
4299
|
+
link: `/p/${name}?autolaunch=${autolaunch ? "1" : "0"}`,
|
|
4300
|
+
})
|
|
4301
|
+
return
|
|
4302
|
+
}
|
|
4216
4303
|
}
|
|
4217
4304
|
}
|
|
4218
4305
|
res.render("start", {
|
|
@@ -4325,9 +4412,14 @@ class Server {
|
|
|
4325
4412
|
drive: path.resolve(this.kernel.homedir, "drive"),
|
|
4326
4413
|
}
|
|
4327
4414
|
}
|
|
4415
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
4416
|
+
let peer_qr = null
|
|
4417
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
4328
4418
|
let list = this.getPeers()
|
|
4329
4419
|
res.render("settings", {
|
|
4330
4420
|
current_host: this.kernel.peer.host,
|
|
4421
|
+
peer_url,
|
|
4422
|
+
peer_qr,
|
|
4331
4423
|
list,
|
|
4332
4424
|
platform,
|
|
4333
4425
|
version: this.version,
|
|
@@ -4443,6 +4535,9 @@ class Server {
|
|
|
4443
4535
|
return
|
|
4444
4536
|
}
|
|
4445
4537
|
|
|
4538
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
4539
|
+
let peer_qr = null
|
|
4540
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
4446
4541
|
let list = this.getPeers()
|
|
4447
4542
|
let ai = await this.kernel.proto.ai()
|
|
4448
4543
|
ai = [{
|
|
@@ -4456,6 +4551,8 @@ class Server {
|
|
|
4456
4551
|
list,
|
|
4457
4552
|
ai,
|
|
4458
4553
|
current_host: this.kernel.peer.host,
|
|
4554
|
+
peer_url,
|
|
4555
|
+
peer_qr,
|
|
4459
4556
|
cwd: this.kernel.path("api"),
|
|
4460
4557
|
name: null,
|
|
4461
4558
|
// name: req.params.name,
|
|
@@ -4556,9 +4653,14 @@ class Server {
|
|
|
4556
4653
|
} catch (e) {
|
|
4557
4654
|
}
|
|
4558
4655
|
}
|
|
4656
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
4657
|
+
let peer_qr = null
|
|
4658
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
4559
4659
|
res.render(`connect`, {
|
|
4560
4660
|
current_urls,
|
|
4561
4661
|
current_host: this.kernel.peer.host,
|
|
4662
|
+
peer_url,
|
|
4663
|
+
peer_qr,
|
|
4562
4664
|
list,
|
|
4563
4665
|
portal: this.portal,
|
|
4564
4666
|
logo: this.logo,
|
|
@@ -4700,6 +4802,13 @@ class Server {
|
|
|
4700
4802
|
this.app.post("/push", ex(async (req, res) => {
|
|
4701
4803
|
try {
|
|
4702
4804
|
const payload = { ...(req.body || {}) }
|
|
4805
|
+
// Normalise audience and device targeting
|
|
4806
|
+
if (typeof payload.audience === 'string') {
|
|
4807
|
+
payload.audience = payload.audience.trim() || undefined
|
|
4808
|
+
}
|
|
4809
|
+
if (typeof payload.device_id === 'string') {
|
|
4810
|
+
payload.device_id = payload.device_id.trim() || undefined
|
|
4811
|
+
}
|
|
4703
4812
|
const resolveAssetPath = (raw) => {
|
|
4704
4813
|
if (typeof raw !== 'string') {
|
|
4705
4814
|
return null
|
|
@@ -4782,6 +4891,19 @@ class Server {
|
|
|
4782
4891
|
}
|
|
4783
4892
|
delete payload.soundUrl
|
|
4784
4893
|
delete payload.soundPath
|
|
4894
|
+
// For device-scoped notifications, suppress host OS notifier for remote origins,
|
|
4895
|
+
// but allow it when the request originates from the local machine
|
|
4896
|
+
if (payload.audience === 'device' && typeof payload.device_id === 'string' && payload.device_id) {
|
|
4897
|
+
try {
|
|
4898
|
+
if (this.socket && typeof this.socket.isLocalDevice === 'function') {
|
|
4899
|
+
payload.host = !!this.socket.isLocalDevice(payload.device_id)
|
|
4900
|
+
} else {
|
|
4901
|
+
payload.host = false
|
|
4902
|
+
}
|
|
4903
|
+
} catch (_) {
|
|
4904
|
+
payload.host = false
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4785
4907
|
Util.push(payload)
|
|
4786
4908
|
res.json({ success: true })
|
|
4787
4909
|
} catch (e) {
|
|
@@ -5343,6 +5465,9 @@ class Server {
|
|
|
5343
5465
|
let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
|
|
5344
5466
|
return this.kernel.router.rewrite_mapping[key]
|
|
5345
5467
|
})
|
|
5468
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
5469
|
+
let peer_qr = null
|
|
5470
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
5346
5471
|
res.render("net", {
|
|
5347
5472
|
static_routes,
|
|
5348
5473
|
selected_name: req.params.name,
|
|
@@ -5361,6 +5486,8 @@ class Server {
|
|
|
5361
5486
|
peer,
|
|
5362
5487
|
protocol,
|
|
5363
5488
|
current_host: this.kernel.peer.host,
|
|
5489
|
+
peer_url,
|
|
5490
|
+
peer_qr,
|
|
5364
5491
|
})
|
|
5365
5492
|
}))
|
|
5366
5493
|
this.app.get("/network", ex(async (req, res) => {
|
|
@@ -5502,6 +5629,9 @@ class Server {
|
|
|
5502
5629
|
let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
|
|
5503
5630
|
return this.kernel.router.rewrite_mapping[key]
|
|
5504
5631
|
})
|
|
5632
|
+
const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
|
|
5633
|
+
let peer_qr = null
|
|
5634
|
+
try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
|
|
5505
5635
|
res.render("network", {
|
|
5506
5636
|
static_routes,
|
|
5507
5637
|
host,
|
|
@@ -5527,6 +5657,8 @@ class Server {
|
|
|
5527
5657
|
peer_active: this.kernel.peer.active,
|
|
5528
5658
|
port_mapping: this.kernel.router.port_mapping,
|
|
5529
5659
|
// port_mapping: this.kernel.caddy.port_mapping,
|
|
5660
|
+
peer_url,
|
|
5661
|
+
peer_qr,
|
|
5530
5662
|
// ip_mapping: this.kernel.caddy.ip_mapping,
|
|
5531
5663
|
lan: this.kernel.router.local_network_mapping,
|
|
5532
5664
|
agent: req.agent,
|
|
@@ -6417,6 +6549,40 @@ class Server {
|
|
|
6417
6549
|
let exec_menus = []
|
|
6418
6550
|
let shell_menus = []
|
|
6419
6551
|
let href_menus = []
|
|
6552
|
+
const normalizeForSort = (value) => {
|
|
6553
|
+
if (typeof value !== 'string') {
|
|
6554
|
+
return ''
|
|
6555
|
+
}
|
|
6556
|
+
return value.trim().toLocaleLowerCase()
|
|
6557
|
+
}
|
|
6558
|
+
const compareMenuItems = (a = {}, b = {}) => {
|
|
6559
|
+
const titleDiff = normalizeForSort(a.title).localeCompare(normalizeForSort(b.title))
|
|
6560
|
+
if (titleDiff !== 0) {
|
|
6561
|
+
return titleDiff
|
|
6562
|
+
}
|
|
6563
|
+
const subtitleDiff = normalizeForSort(a.subtitle).localeCompare(normalizeForSort(b.subtitle))
|
|
6564
|
+
if (subtitleDiff !== 0) {
|
|
6565
|
+
return subtitleDiff
|
|
6566
|
+
}
|
|
6567
|
+
return normalizeForSort(a.href || a.link).localeCompare(normalizeForSort(b.href || b.link))
|
|
6568
|
+
}
|
|
6569
|
+
const sortMenuEntries = (menuArray) => {
|
|
6570
|
+
if (!Array.isArray(menuArray) || menuArray.length < 2) {
|
|
6571
|
+
return
|
|
6572
|
+
}
|
|
6573
|
+
menuArray.sort(compareMenuItems)
|
|
6574
|
+
}
|
|
6575
|
+
const sortNestedMenus = (menuArray) => {
|
|
6576
|
+
if (!Array.isArray(menuArray)) {
|
|
6577
|
+
return
|
|
6578
|
+
}
|
|
6579
|
+
sortMenuEntries(menuArray)
|
|
6580
|
+
for (const entry of menuArray) {
|
|
6581
|
+
if (entry && Array.isArray(entry.menu)) {
|
|
6582
|
+
sortNestedMenus(entry.menu)
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6420
6586
|
if (plugin_menu.length > 0) {
|
|
6421
6587
|
for(let item of plugin_menu) {
|
|
6422
6588
|
// if shell.run method exists
|
|
@@ -6444,30 +6610,37 @@ class Server {
|
|
|
6444
6610
|
href_menus.push(item)
|
|
6445
6611
|
}
|
|
6446
6612
|
}
|
|
6447
|
-
exec_menus
|
|
6448
|
-
shell_menus
|
|
6449
|
-
href_menus
|
|
6613
|
+
sortNestedMenus(exec_menus)
|
|
6614
|
+
sortNestedMenus(shell_menus)
|
|
6615
|
+
sortNestedMenus(href_menus)
|
|
6450
6616
|
}
|
|
6451
6617
|
|
|
6452
6618
|
// let terminal = await this.terminals(filepath)
|
|
6453
6619
|
// let online_terminal = await this.getPluginGlobal(req, terminal, filepath)
|
|
6454
6620
|
// console.log("online_terminal", online_terminal)
|
|
6455
6621
|
terminal.menus = href_menus
|
|
6622
|
+
sortNestedMenus(terminal.menu)
|
|
6623
|
+
sortNestedMenus(terminal.menus)
|
|
6456
6624
|
let dynamic = [
|
|
6457
6625
|
terminal,
|
|
6458
6626
|
{
|
|
6459
6627
|
icon: "fa-solid fa-robot",
|
|
6460
|
-
title: "
|
|
6461
|
-
subtitle: "
|
|
6628
|
+
title: "Terminal Agents",
|
|
6629
|
+
subtitle: "Start a session in Pinokio",
|
|
6462
6630
|
menu: shell_menus
|
|
6463
6631
|
},
|
|
6464
6632
|
{
|
|
6465
6633
|
icon: "fa-solid fa-arrow-up-right-from-square",
|
|
6466
|
-
title: "
|
|
6467
|
-
subtitle: "Open
|
|
6634
|
+
title: "IDE Agents",
|
|
6635
|
+
subtitle: "Open the project in external IDEs",
|
|
6468
6636
|
menu: exec_menus
|
|
6469
6637
|
},
|
|
6470
6638
|
]
|
|
6639
|
+
for (const item of dynamic) {
|
|
6640
|
+
if (item && Array.isArray(item.menu)) {
|
|
6641
|
+
sortNestedMenus(item.menu)
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6471
6644
|
|
|
6472
6645
|
let spec = ""
|
|
6473
6646
|
try {
|
|
@@ -7059,6 +7232,35 @@ class Server {
|
|
|
7059
7232
|
let current_peer_info = await this.kernel.peer.current_host()
|
|
7060
7233
|
res.json(current_peer_info)
|
|
7061
7234
|
}))
|
|
7235
|
+
this.app.get("/info/router", ex(async (req, res) => {
|
|
7236
|
+
try {
|
|
7237
|
+
// Lightweight router mapping without favicon or installed scans
|
|
7238
|
+
const https_active = this.kernel.peer.https_active
|
|
7239
|
+
const router_info = await this.kernel.peer.router_info_lite()
|
|
7240
|
+
const rewrite_mapping = this.kernel.router.rewrite_mapping
|
|
7241
|
+
const router = this.kernel.router.published()
|
|
7242
|
+
res.json({ https_active, router_info, rewrite_mapping, router })
|
|
7243
|
+
} catch (err) {
|
|
7244
|
+
res.json({ https_active: false, router_info: [], rewrite_mapping: {}, router: {} })
|
|
7245
|
+
}
|
|
7246
|
+
}))
|
|
7247
|
+
this.app.get("/qr", ex(async (req, res) => {
|
|
7248
|
+
try {
|
|
7249
|
+
const data = typeof req.query.data === 'string' ? req.query.data : ''
|
|
7250
|
+
if (!data) {
|
|
7251
|
+
res.status(400).json({ error: 'Missing data parameter' })
|
|
7252
|
+
return
|
|
7253
|
+
}
|
|
7254
|
+
const scale = Math.max(2, Math.min(10, parseInt(req.query.s || '4', 10) || 4))
|
|
7255
|
+
const margin = Math.max(0, Math.min(4, parseInt(req.query.m || '0', 10) || 0))
|
|
7256
|
+
const buf = await QRCode.toBuffer(data, { type: 'png', scale, margin })
|
|
7257
|
+
res.setHeader('Content-Type', 'image/png')
|
|
7258
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
7259
|
+
res.send(buf)
|
|
7260
|
+
} catch (err) {
|
|
7261
|
+
res.status(500).json({ error: 'Failed to generate QR' })
|
|
7262
|
+
}
|
|
7263
|
+
}))
|
|
7062
7264
|
this.app.get("/info/api", ex(async (req,res) => {
|
|
7063
7265
|
// api related info
|
|
7064
7266
|
let repo = this.kernel.git.find(req.query.git)
|