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.
@@ -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
- if (!/^https?:\/\//i.test(trimmed)) {
3666
- trimmed = `https://${trimmed}`
3667
- } else {
3668
- trimmed = trimmed.replace(/^http:/i, "https:")
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 parsed = new URL(trimmed)
3672
- if (!parsed.host) {
3714
+ const hostCandidate = trimmed.split('/')[0].toLowerCase()
3715
+ if (!hostCandidate || isIPv4Host(hostCandidate)) {
3673
3716
  return ""
3674
3717
  }
3675
- let pathname = parsed.pathname || ""
3676
- if (pathname === "/") {
3677
- pathname = ""
3718
+ if (!(hostCandidate === 'localhost' || hostCandidate.endsWith('.localhost') || hostCandidate.includes('.'))) {
3719
+ return ""
3678
3720
  }
3679
- const search = parsed.search || ""
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
- tabLinkRouterInfoPromise = fetch("/info/system", {
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
- mergeTargets(entry.external_ip)
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 Set()
4159
+ const httpCandidates = new Map() // url -> { qr: boolean }
4091
4160
  const httpsCandidates = new Set()
4092
4161
 
4093
4162
  if (isHttpUrl(baseHref)) {
4094
- httpCandidates.add(canonicalizeUrl(baseHref))
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.add(canonicalizeUrl(fallbackHttp))
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.add(canonical)
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
- const routerData = await ensureRouterInfoMapping()
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
- httpCandidates.add(canonicalizeUrl(httpUrl))
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
- addEntry("http", "HTTP", url)
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
- item.append(labelSpan, valueSpan)
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
- <button type="button" class="pinokio-history-latest-btn" data-history-return-head>
7914
- <i class="fa-solid fa-arrow-rotate-left"></i> Return to newest commit
7915
- </button>
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
- if (returnBtn && typeof showIframeView === 'function') {
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
- let checkoutTarget = null
7963
-
7964
- if (repoData && typeof repoData.branch === 'string' && repoData.branch.length > 0 && repoData.branch !== currentRef) {
7965
- checkoutTarget = repoData.branch
7966
- }
7967
- if (!checkoutTarget) {
7968
- const nonCurrent = branchEntries.find((entry) => entry && typeof entry.branch === 'string' && entry.branch.length > 0 && entry.branch !== currentRef)
7969
- if (nonCurrent) {
7970
- checkoutTarget = nonCurrent.branch
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 (!checkoutTarget && branchEntries.length > 0) {
7974
- checkoutTarget = branchEntries[0].branch
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
- if (checkoutCwd && checkoutTarget) {
7978
- const checkoutUrl = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(checkoutTarget)}&callback_target=parent&callback=$location.href`
7979
- returnBtn.addEventListener('click', () => {
7980
- openCheckoutTerminal(checkoutUrl)
7981
- })
7982
- } else {
7983
- returnBtn.disabled = true
7984
- if (banner) {
7985
- banner.classList.add('pinokio-history-latest-banner--disabled')
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) {
@@ -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>
@@ -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'>
@@ -125,6 +125,8 @@
125
125
  sessionId: sessionId || null
126
126
  }) %>
127
127
  </script>
128
+ <script src="/Socket.js"></script>
129
+ <script src="/common.js"></script>
128
130
  <script src="/layout.js"></script>
129
131
  </body>
130
132
  </html>
@@ -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>
@@ -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
- <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>
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>