pinokiod 3.180.0 → 3.181.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 +13 -2
- package/package.json +1 -1
- package/server/index.js +126 -23
- package/server/public/common.js +244 -0
- package/server/public/layout.js +114 -0
- package/server/public/nav.js +227 -64
- package/server/public/style.css +22 -2
- package/server/public/tab-idle-notifier.js +3 -0
- package/server/socket.js +71 -4
- package/server/views/app.ejs +226 -56
- package/server/views/connect.ejs +9 -0
- package/server/views/index.ejs +9 -0
- package/server/views/init/index.ejs +9 -2
- package/server/views/layout.ejs +2 -0
- package/server/views/net.ejs +9 -0
- package/server/views/network.ejs +9 -0
- package/server/views/screenshots.ejs +9 -0
- package/server/views/settings.ejs +9 -0
- package/server/views/terminals.ejs +12 -3
- package/server/views/tools.ejs +10 -1
package/server/views/app.ejs
CHANGED
|
@@ -905,6 +905,9 @@ body.dark .tab-link-popover .tab-link-popover-header {
|
|
|
905
905
|
background: transparent;
|
|
906
906
|
cursor: pointer;
|
|
907
907
|
}
|
|
908
|
+
.tab-link-popover .tab-link-popover-item.qr-inline { flex-direction: row; align-items: center; gap: 10px; }
|
|
909
|
+
.tab-link-popover .tab-link-popover-item.qr-inline .textcol { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
|
|
910
|
+
.tab-link-popover .tab-link-popover-item.qr-inline .qr { width: 64px; height: 64px; image-rendering: pixelated; flex: 0 0 auto; margin-left: auto; }
|
|
908
911
|
.tab-link-popover .tab-link-popover-item:hover,
|
|
909
912
|
.tab-link-popover .tab-link-popover-item:focus-visible {
|
|
910
913
|
background: rgba(15, 23, 42, 0.06);
|
|
@@ -2232,6 +2235,19 @@ body.dark {
|
|
|
2232
2235
|
cursor: not-allowed;
|
|
2233
2236
|
}
|
|
2234
2237
|
|
|
2238
|
+
.pinokio-history-actions {
|
|
2239
|
+
display: inline-flex;
|
|
2240
|
+
align-items: center;
|
|
2241
|
+
gap: 10px;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
.pinokio-history-branch-select.pinokio-modal-input {
|
|
2245
|
+
width: auto;
|
|
2246
|
+
min-width: 160px;
|
|
2247
|
+
padding: 6px 10px;
|
|
2248
|
+
font-size: 13px;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2235
2251
|
.pinokio-pill {
|
|
2236
2252
|
display: inline-flex;
|
|
2237
2253
|
align-items: center;
|
|
@@ -2957,6 +2973,15 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2957
2973
|
overflow: auto;
|
|
2958
2974
|
flex-wrap: nowrap;
|
|
2959
2975
|
}
|
|
2976
|
+
/* Keep minimized header horizontal and compact on small screens */
|
|
2977
|
+
header.navheader.minimized,
|
|
2978
|
+
header.navheader.minimized h1 {
|
|
2979
|
+
display: inline-flex;
|
|
2980
|
+
flex-direction: row;
|
|
2981
|
+
}
|
|
2982
|
+
header.navheader.minimized h1 .btn2 {
|
|
2983
|
+
width: auto;
|
|
2984
|
+
}
|
|
2960
2985
|
.appcanvas {
|
|
2961
2986
|
margin-left: 0;
|
|
2962
2987
|
flex: 1 1 auto;
|
|
@@ -3508,6 +3533,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3508
3533
|
return false
|
|
3509
3534
|
}
|
|
3510
3535
|
|
|
3536
|
+
const isIPv4Host = (host) => /^(\d{1,3}\.){3}\d{1,3}$/.test((host || '').trim())
|
|
3537
|
+
|
|
3511
3538
|
const extractProjectSlug = (node) => {
|
|
3512
3539
|
if (!node) {
|
|
3513
3540
|
return ""
|
|
@@ -3662,22 +3689,36 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3662
3689
|
if (!trimmed) {
|
|
3663
3690
|
return ""
|
|
3664
3691
|
}
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3692
|
+
// If it's already a URL, ensure it's HTTPS and not an IP host
|
|
3693
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
3694
|
+
try {
|
|
3695
|
+
const parsed = new URL(trimmed)
|
|
3696
|
+
const host = (parsed.hostname || '').toLowerCase()
|
|
3697
|
+
if (!host || isIPv4Host(host)) {
|
|
3698
|
+
return ""
|
|
3699
|
+
}
|
|
3700
|
+
// Only accept domains (prefer *.localhost) for HTTPS targets
|
|
3701
|
+
if (!(host === 'localhost' || host.endsWith('.localhost') || host.includes('.'))) {
|
|
3702
|
+
return ""
|
|
3703
|
+
}
|
|
3704
|
+
let pathname = parsed.pathname || ""
|
|
3705
|
+
if (pathname === "/") pathname = ""
|
|
3706
|
+
const search = parsed.search || ""
|
|
3707
|
+
return `https://${host}${pathname}${search}`
|
|
3708
|
+
} catch (_) {
|
|
3709
|
+
return ""
|
|
3710
|
+
}
|
|
3669
3711
|
}
|
|
3712
|
+
// Not a full URL: accept plain domains (prefer *.localhost), reject IPs
|
|
3670
3713
|
try {
|
|
3671
|
-
const
|
|
3672
|
-
if (!
|
|
3714
|
+
const hostCandidate = trimmed.split('/')[0].toLowerCase()
|
|
3715
|
+
if (!hostCandidate || isIPv4Host(hostCandidate)) {
|
|
3673
3716
|
return ""
|
|
3674
3717
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
pathname = ""
|
|
3718
|
+
if (!(hostCandidate === 'localhost' || hostCandidate.endsWith('.localhost') || hostCandidate.includes('.'))) {
|
|
3719
|
+
return ""
|
|
3678
3720
|
}
|
|
3679
|
-
|
|
3680
|
-
return `https://${parsed.host}${pathname}${search}`
|
|
3721
|
+
return `https://${hostCandidate}`
|
|
3681
3722
|
} catch (_) {
|
|
3682
3723
|
return ""
|
|
3683
3724
|
}
|
|
@@ -3736,7 +3777,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3736
3777
|
const ensureRouterInfoMapping = async () => {
|
|
3737
3778
|
const now = Date.now()
|
|
3738
3779
|
if (!tabLinkRouterInfoPromise || now > tabLinkRouterInfoExpiry) {
|
|
3739
|
-
|
|
3780
|
+
// Use lightweight router mapping to avoid favicon/installed overhead
|
|
3781
|
+
tabLinkRouterInfoPromise = fetch("/info/router", {
|
|
3740
3782
|
method: "GET",
|
|
3741
3783
|
headers: {
|
|
3742
3784
|
"Accept": "application/json"
|
|
@@ -3758,6 +3800,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3758
3800
|
: []
|
|
3759
3801
|
const portMap = new Map()
|
|
3760
3802
|
const hostPortMap = new Map()
|
|
3803
|
+
const externalHttpByExtPort = new Map() // ext port -> Set of host:port (external_ip)
|
|
3804
|
+
const externalHttpByIntPort = new Map() // internal port -> Set of host:port (external_ip)
|
|
3761
3805
|
const hostAliasPortMap = new Map()
|
|
3762
3806
|
if (data?.router && typeof data.router === "object") {
|
|
3763
3807
|
Object.entries(data.router).forEach(([dial, hosts]) => {
|
|
@@ -3902,11 +3946,29 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3902
3946
|
mergeTargets(entry.external_domain)
|
|
3903
3947
|
mergeTargets(entry.https_href)
|
|
3904
3948
|
mergeTargets(entry.app_href)
|
|
3905
|
-
|
|
3906
|
-
mergeTargets(entry.internal_router)
|
|
3907
|
-
mergeTargets(entry.match)
|
|
3908
|
-
mergeTargets(entry.host)
|
|
3949
|
+
// Some rewrite mapping entries expose domain candidates under `hosts`
|
|
3909
3950
|
mergeTargets(entry.hosts)
|
|
3951
|
+
// Internal router can also include domain aliases (e.g., comfyui.localhost)
|
|
3952
|
+
mergeTargets(entry.internal_router)
|
|
3953
|
+
|
|
3954
|
+
// Record external http host:port candidates by external and internal ports for later
|
|
3955
|
+
if (entry.external_ip && typeof entry.external_ip === 'string') {
|
|
3956
|
+
const parsed = parseHostPort(entry.external_ip)
|
|
3957
|
+
if (parsed && parsed.port) {
|
|
3958
|
+
const keyExt = parsed.port
|
|
3959
|
+
if (!externalHttpByExtPort.has(keyExt)) {
|
|
3960
|
+
externalHttpByExtPort.set(keyExt, new Set())
|
|
3961
|
+
}
|
|
3962
|
+
externalHttpByExtPort.get(keyExt).add(`${parsed.host}:${parsed.port}`)
|
|
3963
|
+
const keyInt = String(entry.internal_port || '')
|
|
3964
|
+
if (keyInt) {
|
|
3965
|
+
if (!externalHttpByIntPort.has(keyInt)) {
|
|
3966
|
+
externalHttpByIntPort.set(keyInt, new Set())
|
|
3967
|
+
}
|
|
3968
|
+
externalHttpByIntPort.get(keyInt).add(`${parsed.host}:${parsed.port}`)
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3910
3972
|
|
|
3911
3973
|
if (httpsTargets.size === 0) {
|
|
3912
3974
|
return
|
|
@@ -3982,14 +4044,18 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3982
4044
|
|
|
3983
4045
|
return {
|
|
3984
4046
|
portMap,
|
|
3985
|
-
hostPortMap
|
|
4047
|
+
hostPortMap,
|
|
4048
|
+
externalHttpByExtPort,
|
|
4049
|
+
externalHttpByIntPort
|
|
3986
4050
|
}
|
|
3987
4051
|
})
|
|
3988
4052
|
.catch(() => {
|
|
3989
4053
|
tabLinkRouterHttpsActive = null
|
|
3990
4054
|
return {
|
|
3991
4055
|
portMap: new Map(),
|
|
3992
|
-
hostPortMap: new Map()
|
|
4056
|
+
hostPortMap: new Map(),
|
|
4057
|
+
externalHttpByExtPort: new Map(),
|
|
4058
|
+
externalHttpByIntPort: new Map()
|
|
3993
4059
|
}
|
|
3994
4060
|
})
|
|
3995
4061
|
tabLinkRouterInfoExpiry = now + 3000
|
|
@@ -4042,7 +4108,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4042
4108
|
const projectSlug = extractProjectSlug(link).toLowerCase()
|
|
4043
4109
|
const entries = []
|
|
4044
4110
|
const entryByUrl = new Map()
|
|
4045
|
-
const addEntry = (type, label, url) => {
|
|
4111
|
+
const addEntry = (type, label, url, opts = {}) => {
|
|
4046
4112
|
if (!url) {
|
|
4047
4113
|
return
|
|
4048
4114
|
}
|
|
@@ -4067,13 +4133,16 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4067
4133
|
return
|
|
4068
4134
|
}
|
|
4069
4135
|
if (entryByUrl.has(canonical)) {
|
|
4136
|
+
const existing = entryByUrl.get(canonical)
|
|
4137
|
+
if (opts && opts.qr === true) existing.qr = true
|
|
4070
4138
|
return
|
|
4071
4139
|
}
|
|
4072
4140
|
const entry = {
|
|
4073
4141
|
type,
|
|
4074
4142
|
label,
|
|
4075
4143
|
url: canonical,
|
|
4076
|
-
display: formatDisplayUrl(canonical)
|
|
4144
|
+
display: formatDisplayUrl(canonical),
|
|
4145
|
+
qr: opts && opts.qr === true
|
|
4077
4146
|
}
|
|
4078
4147
|
entryByUrl.set(canonical, entry)
|
|
4079
4148
|
entries.push(entry)
|
|
@@ -4087,11 +4156,11 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4087
4156
|
addEntry("url", "URL", baseHref)
|
|
4088
4157
|
}
|
|
4089
4158
|
|
|
4090
|
-
const httpCandidates = new
|
|
4159
|
+
const httpCandidates = new Map() // url -> { qr: boolean }
|
|
4091
4160
|
const httpsCandidates = new Set()
|
|
4092
4161
|
|
|
4093
4162
|
if (isHttpUrl(baseHref)) {
|
|
4094
|
-
httpCandidates.
|
|
4163
|
+
httpCandidates.set(canonicalizeUrl(baseHref), { qr: false })
|
|
4095
4164
|
} else if (isHttpsUrl(baseHref)) {
|
|
4096
4165
|
httpsCandidates.add(canonicalizeUrl(baseHref))
|
|
4097
4166
|
}
|
|
@@ -4109,7 +4178,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4109
4178
|
const normalizedPath = pathname.toLowerCase()
|
|
4110
4179
|
if (normalizedPath.includes(`/asset/api/${projectSlug}`)) {
|
|
4111
4180
|
const fallbackHttp = `http://127.0.0.1:42000${pathname}`
|
|
4112
|
-
httpCandidates.
|
|
4181
|
+
httpCandidates.set(canonicalizeUrl(fallbackHttp), { qr: false })
|
|
4113
4182
|
}
|
|
4114
4183
|
} catch (_) {
|
|
4115
4184
|
// ignore fallback errors
|
|
@@ -4133,15 +4202,16 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4133
4202
|
if (isHttpsUrl(canonical)) {
|
|
4134
4203
|
httpsCandidates.add(canonical)
|
|
4135
4204
|
} else if (isHttpUrl(canonical)) {
|
|
4136
|
-
httpCandidates.
|
|
4205
|
+
const prev = httpCandidates.get(canonical)
|
|
4206
|
+
httpCandidates.set(canonical, { qr: prev ? prev.qr === true : false })
|
|
4137
4207
|
}
|
|
4138
4208
|
})
|
|
4139
4209
|
})
|
|
4140
4210
|
}
|
|
4141
4211
|
|
|
4212
|
+
const routerData = await ensureRouterInfoMapping()
|
|
4142
4213
|
if (httpCandidates.size > 0) {
|
|
4143
|
-
|
|
4144
|
-
httpCandidates.forEach((httpUrl) => {
|
|
4214
|
+
Array.from(httpCandidates.keys()).forEach((httpUrl) => {
|
|
4145
4215
|
const mapped = collectHttpsUrlsFromRouter(httpUrl, routerData)
|
|
4146
4216
|
mapped.forEach((httpsUrl) => {
|
|
4147
4217
|
httpsCandidates.add(httpsUrl)
|
|
@@ -4149,6 +4219,28 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4149
4219
|
})
|
|
4150
4220
|
}
|
|
4151
4221
|
|
|
4222
|
+
// Add external 192.168.* http host:port candidates mapped from the same internal port as base HTTP
|
|
4223
|
+
try {
|
|
4224
|
+
const base = new URL(baseHref, location.origin)
|
|
4225
|
+
let basePort = base.port
|
|
4226
|
+
if (!basePort) {
|
|
4227
|
+
basePort = base.protocol.toLowerCase() === 'https:' ? '443' : '80'
|
|
4228
|
+
}
|
|
4229
|
+
const samePortHosts = routerData && routerData.externalHttpByIntPort ? routerData.externalHttpByIntPort.get(basePort) : null
|
|
4230
|
+
if (samePortHosts && samePortHosts.size > 0) {
|
|
4231
|
+
samePortHosts.forEach((hostport) => {
|
|
4232
|
+
try {
|
|
4233
|
+
const hpUrl = `http://${hostport}${base.pathname || '/'}${base.search || ''}`
|
|
4234
|
+
const canonical = canonicalizeUrl(hpUrl)
|
|
4235
|
+
if (isHttpUrl(canonical)) {
|
|
4236
|
+
const prev = httpCandidates.get(canonical)
|
|
4237
|
+
httpCandidates.set(canonical, { qr: true || (prev ? prev.qr === true : false) })
|
|
4238
|
+
}
|
|
4239
|
+
} catch (_) {}
|
|
4240
|
+
})
|
|
4241
|
+
}
|
|
4242
|
+
} catch (_) {}
|
|
4243
|
+
|
|
4152
4244
|
const httpsList = Array.from(httpsCandidates).sort()
|
|
4153
4245
|
|
|
4154
4246
|
if (httpsList.length > 0) {
|
|
@@ -4163,17 +4255,20 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4163
4255
|
}
|
|
4164
4256
|
const hostPort = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname
|
|
4165
4257
|
const httpUrl = `http://${hostPort}${parsed.pathname || "/"}${parsed.search || ""}`
|
|
4166
|
-
|
|
4258
|
+
const key = canonicalizeUrl(httpUrl)
|
|
4259
|
+
const prev = httpCandidates.get(key)
|
|
4260
|
+
httpCandidates.set(key, { qr: prev ? prev.qr === true : false })
|
|
4167
4261
|
} catch (_) {
|
|
4168
4262
|
// ignore failures
|
|
4169
4263
|
}
|
|
4170
4264
|
})
|
|
4171
4265
|
}
|
|
4172
4266
|
|
|
4173
|
-
const httpList = Array.from(httpCandidates).sort()
|
|
4267
|
+
const httpList = Array.from(httpCandidates.keys()).sort()
|
|
4174
4268
|
|
|
4175
4269
|
httpList.forEach((url) => {
|
|
4176
|
-
|
|
4270
|
+
const meta = httpCandidates.get(url) || { qr: false }
|
|
4271
|
+
addEntry("http", "HTTP", url, { qr: meta.qr === true })
|
|
4177
4272
|
})
|
|
4178
4273
|
httpsList.forEach((url) => {
|
|
4179
4274
|
addEntry("https", "HTTPS", url)
|
|
@@ -4273,6 +4368,26 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4273
4368
|
tabLinkHideTimer = null
|
|
4274
4369
|
}
|
|
4275
4370
|
|
|
4371
|
+
// Show lightweight loading popover immediately while mapping fetch runs
|
|
4372
|
+
try {
|
|
4373
|
+
const pop = ensureTabLinkPopoverEl()
|
|
4374
|
+
pop.innerHTML = ''
|
|
4375
|
+
const header = document.createElement('div')
|
|
4376
|
+
header.className = 'tab-link-popover-header'
|
|
4377
|
+
header.innerHTML = `<i class="fa-solid fa-arrow-up-right-from-square"></i><span>Open in browser</span>`
|
|
4378
|
+
const item = document.createElement('div')
|
|
4379
|
+
item.className = 'tab-link-popover-item'
|
|
4380
|
+
const label = document.createElement('span')
|
|
4381
|
+
label.className = 'label'
|
|
4382
|
+
label.textContent = 'Loading…'
|
|
4383
|
+
const value = document.createElement('span')
|
|
4384
|
+
value.className = 'value muted'
|
|
4385
|
+
value.textContent = 'Discovering routes'
|
|
4386
|
+
item.append(label, value)
|
|
4387
|
+
pop.append(header, item)
|
|
4388
|
+
positionTabLinkPopover(pop, link)
|
|
4389
|
+
} catch (_) {}
|
|
4390
|
+
|
|
4276
4391
|
let entries
|
|
4277
4392
|
try {
|
|
4278
4393
|
entries = await buildTabLinkEntries(link)
|
|
@@ -4356,7 +4471,6 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4356
4471
|
entries.forEach((entry) => {
|
|
4357
4472
|
const item = document.createElement("button")
|
|
4358
4473
|
item.type = "button"
|
|
4359
|
-
item.className = "tab-link-popover-item"
|
|
4360
4474
|
item.setAttribute("data-url", entry.url)
|
|
4361
4475
|
const labelSpan = document.createElement("span")
|
|
4362
4476
|
labelSpan.className = "label"
|
|
@@ -4364,7 +4478,24 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4364
4478
|
const valueSpan = document.createElement("span")
|
|
4365
4479
|
valueSpan.className = "value"
|
|
4366
4480
|
valueSpan.textContent = entry.display
|
|
4367
|
-
|
|
4481
|
+
|
|
4482
|
+
if (entry.type === 'http' && entry.qr === true) {
|
|
4483
|
+
item.className = "tab-link-popover-item qr-inline"
|
|
4484
|
+
const textCol = document.createElement('div')
|
|
4485
|
+
textCol.className = 'textcol'
|
|
4486
|
+
textCol.append(labelSpan, valueSpan)
|
|
4487
|
+
const qrImg = document.createElement('img')
|
|
4488
|
+
qrImg.className = 'qr'
|
|
4489
|
+
qrImg.alt = 'QR'
|
|
4490
|
+
qrImg.decoding = 'async'
|
|
4491
|
+
qrImg.loading = 'lazy'
|
|
4492
|
+
qrImg.src = `/qr?data=${encodeURIComponent(entry.url)}&s=4&m=0`
|
|
4493
|
+
item.append(textCol, qrImg)
|
|
4494
|
+
} else {
|
|
4495
|
+
item.className = "tab-link-popover-item"
|
|
4496
|
+
// Keep label and value as direct children so column layout applies
|
|
4497
|
+
item.append(labelSpan, valueSpan)
|
|
4498
|
+
}
|
|
4368
4499
|
popover.appendChild(item)
|
|
4369
4500
|
})
|
|
4370
4501
|
|
|
@@ -7879,6 +8010,10 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7879
8010
|
const commits = historyData.log || []
|
|
7880
8011
|
const remote = historyData.remote || ''
|
|
7881
8012
|
const currentRef = historyData.ref || 'HEAD'
|
|
8013
|
+
const branchEntries = Array.isArray(historyData.branches) ? historyData.branches : []
|
|
8014
|
+
const isOid = (s) => typeof s === 'string' && /^[0-9a-f]{7,40}$/i.test(s)
|
|
8015
|
+
const realBranchEntries = branchEntries.filter((entry) => entry && typeof entry.branch === 'string' && entry.branch.length > 0 && !isOid(entry.branch))
|
|
8016
|
+
const selectedBranchName = (historyData && typeof historyData.branch === 'string' && !isOid(historyData.branch)) ? historyData.branch : null
|
|
7882
8017
|
|
|
7883
8018
|
const commitCountLabel = `${commits.length} commit${commits.length === 1 ? '' : 's'}`
|
|
7884
8019
|
const lastCommit = commits[0]
|
|
@@ -7910,9 +8045,21 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7910
8045
|
<div class="pinokio-history-latest-text">
|
|
7911
8046
|
Currently viewing ${escapeHtml(currentRef)}
|
|
7912
8047
|
</div>
|
|
7913
|
-
<
|
|
7914
|
-
<
|
|
7915
|
-
|
|
8048
|
+
<div class="pinokio-history-actions">
|
|
8049
|
+
<button type="button" class="pinokio-history-latest-btn" data-history-return-head>
|
|
8050
|
+
<i class="fa-solid fa-arrow-rotate-left"></i> Return to newest commit
|
|
8051
|
+
</button>
|
|
8052
|
+
${realBranchEntries.length ? `
|
|
8053
|
+
<select class="pinokio-modal-input pinokio-history-branch-select" data-history-branch-select aria-label="Select branch">
|
|
8054
|
+
${realBranchEntries.map(e => `
|
|
8055
|
+
<option value="${escapeHtml(e.branch)}"${selectedBranchName === e.branch ? ' selected' : ''}>${escapeHtml(e.branch)}</option>
|
|
8056
|
+
`).join('')}
|
|
8057
|
+
</select>
|
|
8058
|
+
<button type="button" class="pinokio-history-latest-btn" data-history-branch-switch>
|
|
8059
|
+
<i class="fa-solid fa-code-branch"></i> Switch
|
|
8060
|
+
</button>
|
|
8061
|
+
` : ''}
|
|
8062
|
+
</div>
|
|
7916
8063
|
</div>
|
|
7917
8064
|
<div class="pinokio-modal-body pinokio-modal-body--history">
|
|
7918
8065
|
${commits.length === 0 ?
|
|
@@ -7949,7 +8096,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7949
8096
|
|
|
7950
8097
|
const returnBtn = document.querySelector('[data-history-return-head]')
|
|
7951
8098
|
const banner = document.querySelector('[data-history-latest-banner]')
|
|
7952
|
-
|
|
8099
|
+
const branchSelect = document.querySelector('[data-history-branch-select]')
|
|
8100
|
+
const branchSwitchBtn = document.querySelector('[data-history-branch-switch]')
|
|
8101
|
+
if (typeof showIframeView === 'function') {
|
|
7953
8102
|
const repoParam = options && typeof options === 'object' ? options.repoParam : null
|
|
7954
8103
|
let checkoutCwd = null
|
|
7955
8104
|
if (historyData && typeof historyData.dir === 'string' && historyData.dir.length > 0) {
|
|
@@ -7958,31 +8107,52 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7958
8107
|
checkoutCwd = repoParam
|
|
7959
8108
|
}
|
|
7960
8109
|
|
|
8110
|
+
const isOid = (s) => typeof s === 'string' && /^[0-9a-f]{7,40}$/i.test(s)
|
|
7961
8111
|
const branchEntries = Array.isArray(historyData.branches) ? historyData.branches : []
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
if (
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
8112
|
+
const realBranches = branchEntries
|
|
8113
|
+
.map((entry) => entry && typeof entry.branch === 'string' ? entry.branch : null)
|
|
8114
|
+
.filter((name) => name && !isOid(name))
|
|
8115
|
+
|
|
8116
|
+
// Wire the branch selector
|
|
8117
|
+
if (branchSelect && checkoutCwd) {
|
|
8118
|
+
branchSelect.addEventListener('change', () => {
|
|
8119
|
+
const value = branchSelect.value
|
|
8120
|
+
if (!value) return
|
|
8121
|
+
const url = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(value)}&callback_target=parent&callback=$location.href`
|
|
8122
|
+
openCheckoutTerminal(url)
|
|
8123
|
+
})
|
|
7972
8124
|
}
|
|
7973
|
-
if (
|
|
7974
|
-
|
|
8125
|
+
if (branchSwitchBtn && branchSelect && checkoutCwd) {
|
|
8126
|
+
branchSwitchBtn.addEventListener('click', () => {
|
|
8127
|
+
const value = branchSelect.value
|
|
8128
|
+
if (!value) return
|
|
8129
|
+
const url = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(value)}&callback_target=parent&callback=$location.href`
|
|
8130
|
+
openCheckoutTerminal(url)
|
|
8131
|
+
})
|
|
7975
8132
|
}
|
|
7976
8133
|
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
if (
|
|
7985
|
-
|
|
8134
|
+
// Fix "Return to newest commit" target selection
|
|
8135
|
+
if (returnBtn) {
|
|
8136
|
+
let checkoutTarget = null
|
|
8137
|
+
if (repoData && typeof repoData.branch === 'string' && repoData.branch.length > 0 && !isOid(repoData.branch)) {
|
|
8138
|
+
checkoutTarget = repoData.branch
|
|
8139
|
+
} else if (historyData && typeof historyData.branch === 'string' && historyData.branch.length > 0 && !isOid(historyData.branch)) {
|
|
8140
|
+
checkoutTarget = historyData.branch
|
|
8141
|
+
} else if (realBranches.length > 0) {
|
|
8142
|
+
const prefer = ['main', 'master', 'develop', 'dev']
|
|
8143
|
+
checkoutTarget = prefer.find((n) => realBranches.includes(n)) || realBranches[0]
|
|
8144
|
+
}
|
|
8145
|
+
|
|
8146
|
+
if (checkoutCwd && checkoutTarget) {
|
|
8147
|
+
const checkoutUrl = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(checkoutTarget)}&callback_target=parent&callback=$location.href`
|
|
8148
|
+
returnBtn.addEventListener('click', () => {
|
|
8149
|
+
openCheckoutTerminal(checkoutUrl)
|
|
8150
|
+
})
|
|
8151
|
+
} else {
|
|
8152
|
+
returnBtn.disabled = true
|
|
8153
|
+
if (banner) {
|
|
8154
|
+
banner.classList.add('pinokio-history-latest-banner--disabled')
|
|
8155
|
+
}
|
|
7986
8156
|
}
|
|
7987
8157
|
}
|
|
7988
8158
|
} else if (banner) {
|
package/server/views/connect.ejs
CHANGED
|
@@ -924,6 +924,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
924
924
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
925
925
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
926
926
|
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
927
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
928
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
929
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
930
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
931
|
+
<div class='caption'>Scan to open</div>
|
|
932
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
933
|
+
</a>
|
|
934
|
+
</div>
|
|
935
|
+
<% } %>
|
|
927
936
|
</aside>
|
|
928
937
|
</main>
|
|
929
938
|
</body>
|
package/server/views/index.ejs
CHANGED
|
@@ -731,6 +731,15 @@ body.dark aside .current.selected {
|
|
|
731
731
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
732
732
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
733
733
|
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
734
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
735
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
736
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
737
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
738
|
+
<div class='caption'>Scan to open</div>
|
|
739
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
740
|
+
</a>
|
|
741
|
+
</div>
|
|
742
|
+
<% } %>
|
|
734
743
|
</aside>
|
|
735
744
|
</main>
|
|
736
745
|
<script>
|
|
@@ -1587,7 +1587,6 @@ body.dark .ace-editor {
|
|
|
1587
1587
|
<div><h5>Download</h5></div>
|
|
1588
1588
|
</div>
|
|
1589
1589
|
</button>
|
|
1590
|
-
<!--
|
|
1591
1590
|
<div class='category-label'>Create a web domain</div>
|
|
1592
1591
|
<button type="button" class="tab-button" data-tab="dns">
|
|
1593
1592
|
<div class='row'>
|
|
@@ -1595,7 +1594,6 @@ body.dark .ace-editor {
|
|
|
1595
1594
|
<div><h5>Web Domain</h5></div>
|
|
1596
1595
|
</div>
|
|
1597
1596
|
</button>
|
|
1598
|
-
-->
|
|
1599
1597
|
</div>
|
|
1600
1598
|
|
|
1601
1599
|
<form id="bootstrap-form">
|
|
@@ -1953,6 +1951,15 @@ body.dark .ace-editor {
|
|
|
1953
1951
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
1954
1952
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
1955
1953
|
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
1954
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
1955
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
1956
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
1957
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
1958
|
+
<div class='caption'>Scan to open</div>
|
|
1959
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
1960
|
+
</a>
|
|
1961
|
+
</div>
|
|
1962
|
+
<% } %>
|
|
1956
1963
|
</aside>
|
|
1957
1964
|
</main>
|
|
1958
1965
|
<div id='terminal-container' class='hidden'>
|
package/server/views/layout.ejs
CHANGED
package/server/views/net.ejs
CHANGED
|
@@ -823,6 +823,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
823
823
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
824
824
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
825
825
|
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
826
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
827
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
828
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
829
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
830
|
+
<div class='caption'>Scan to open</div>
|
|
831
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
832
|
+
</a>
|
|
833
|
+
</div>
|
|
834
|
+
<% } %>
|
|
826
835
|
</aside>
|
|
827
836
|
</main>
|
|
828
837
|
<script>
|
package/server/views/network.ejs
CHANGED
|
@@ -1184,6 +1184,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
1184
1184
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
1185
1185
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
1186
1186
|
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
1187
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
1188
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
1189
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
1190
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
1191
|
+
<div class='caption'>Scan to open</div>
|
|
1192
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
1193
|
+
</a>
|
|
1194
|
+
</div>
|
|
1195
|
+
<% } %>
|
|
1187
1196
|
</aside>
|
|
1188
1197
|
</main>
|
|
1189
1198
|
<script>
|
|
@@ -776,6 +776,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
776
776
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
777
777
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
778
778
|
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
779
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
780
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
781
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
782
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
783
|
+
<div class='caption'>Scan to open</div>
|
|
784
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
785
|
+
</a>
|
|
786
|
+
</div>
|
|
787
|
+
<% } %>
|
|
779
788
|
</aside>
|
|
780
789
|
</main>
|
|
781
790
|
<script>
|
|
@@ -571,6 +571,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
571
571
|
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
572
572
|
<a class='tab' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
573
573
|
<a class='tab selected' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
574
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
575
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
576
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
577
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
578
|
+
<div class='caption'>Scan to open</div>
|
|
579
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
580
|
+
</a>
|
|
581
|
+
</div>
|
|
582
|
+
<% } %>
|
|
574
583
|
</aside>
|
|
575
584
|
</main>
|
|
576
585
|
<script>
|
|
@@ -447,9 +447,18 @@ body.dark .plugin-option:hover {
|
|
|
447
447
|
<a class='tab' id='genlog'><i class="fa-solid fa-laptop-code"></i><div class='caption'>Logs</div></a>
|
|
448
448
|
<a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><i class="fa-solid fa-download"></i><div class='caption'>Download logs</div></a>
|
|
449
449
|
<a class='tab' href="/screenshots"><i class="fa-solid fa-camera"></i><div class='caption'>Screenshots</div></a>
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
450
|
+
<a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
|
|
451
|
+
<a class='tab selected' href="/terminals"><i class="fa-solid fa-desktop"></i><div class='caption'>Terminals</div></a>
|
|
452
|
+
<a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
|
|
453
|
+
<% if (typeof peer_qr !== 'undefined' && peer_qr) { %>
|
|
454
|
+
<div class='qr' style='padding:12px 10px; text-align:center;'>
|
|
455
|
+
<a href="<%=peer_url%>" target="_blank" style="text-decoration:none; color:inherit; display:block;">
|
|
456
|
+
<img src="<%=peer_qr%>" alt="Open <%=peer_url%>" style="width:128px; height:128px; image-rendering: pixelated;"/>
|
|
457
|
+
<div class='caption'>Scan to open</div>
|
|
458
|
+
<div class='caption' style='font-size:10px; opacity:0.7;'><%=peer_url%></div>
|
|
459
|
+
</a>
|
|
460
|
+
</div>
|
|
461
|
+
<% } %>
|
|
453
462
|
</aside>
|
|
454
463
|
</main>
|
|
455
464
|
<script type="application/json" id="plugin-data"><%- JSON.stringify(serializedPlugins) %></script>
|