pinokiod 3.150.0 → 3.151.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.
@@ -11,6 +11,7 @@ class Common extends Processor {
11
11
  "headers": {
12
12
  "request": {
13
13
  "set": {
14
+ "Host": ["{http.request.host}"],
14
15
  "X-Forwarded-Proto": ["https"],
15
16
  "X-Forwarded-Host": ["{http.request.host}"]
16
17
  }
@@ -16,6 +16,7 @@ class Connector extends Processor {
16
16
  "headers": {
17
17
  "request": {
18
18
  "set": {
19
+ "Host": ["{http.request.header.X-Forwarded-Host}"],
19
20
  "X-Forwarded-Proto": ["{http.request.header.X-Forwarded-Proto}"],
20
21
  //"X-Forwarded-Proto": ["{http.request.scheme}"],
21
22
  //"X-Forwarded-Proto": ["https"],
@@ -55,6 +56,7 @@ class Connector extends Processor {
55
56
  "headers": {
56
57
  "request": {
57
58
  "set": {
59
+ "Host": ["{http.request.host}"],
58
60
  //"X-Forwarded-Proto": ["https"],
59
61
  //"Origin": "localhost",
60
62
  "X-Forwarded-Proto": ["{http.request.scheme}"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.150.0",
3
+ "version": "3.151.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -3190,61 +3190,180 @@ class Server {
3190
3190
  }
3191
3191
  }
3192
3192
  add_extra_urls(info) {
3193
- // get only the parts not from this peer
3194
- for(let host in this.kernel.peer.info) {
3195
- let host_info = this.kernel.peer.info[host]
3193
+ if (!this.kernel.peer || !this.kernel.peer.info) {
3194
+ return
3195
+ }
3196
3196
 
3197
- let host_rewrites = host_info.rewrite_mapping
3198
- for(let key in host_rewrites) {
3199
- info.push({
3200
- online: true,
3201
- host: {
3202
- ip: host,
3203
- local: this.kernel.peer.host === host,
3204
- name: host_info.name,
3205
- platform: host_info.platform,
3206
- arch: host_info.arch
3207
- },
3208
- name: `[Files] ${host_rewrites[key].name}`,
3209
- ip: host_rewrites[key].external_ip
3210
- })
3197
+ const ensureArray = (value) => {
3198
+ if (!value) return []
3199
+ return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean)
3200
+ }
3201
+
3202
+ const normalizeHttpUrl = (value) => {
3203
+ if (!value) return null
3204
+ const str = String(value).trim()
3205
+ if (!str) return null
3206
+ if (/^https?:\/\//i.test(str)) {
3207
+ return str.replace(/^https:/i, 'http:')
3211
3208
  }
3212
- if (this.kernel.peer.host !== host) {
3213
- let host_routers = host_info.router_info
3214
- for(let host_router of host_routers) {
3215
- let ip
3216
- // the peer sharing works only if external_ip is available (caddy is installed)
3217
- if (host_router.external_ip) {
3218
- ip = host_router.external_ip
3219
- } else {
3220
- // if caddy is not turned on, set ip as null, so that it suggests turning on peer sharing
3221
- ip = null
3209
+ return `http://${str}`
3210
+ }
3211
+
3212
+ const normalizeHttpsUrl = (value) => {
3213
+ if (!value) return null
3214
+ const str = String(value).trim()
3215
+ if (!str) return null
3216
+ if (/^https?:\/\//i.test(str)) {
3217
+ return str.replace(/^http:/i, 'https:')
3218
+ }
3219
+ return `https://${str}`
3220
+ }
3221
+
3222
+ const seen = new Set()
3223
+
3224
+ const pushEntry = ({ host, name, ip, httpUrl, httpsUrls, description, icon, online = true }) => {
3225
+ const normalizedHttp = normalizeHttpUrl(httpUrl)
3226
+ const hostNameForAlias = host && host.name ? String(host.name).trim() : ''
3227
+ const peerSuffix = hostNameForAlias ? `.${hostNameForAlias}.localhost` : null
3228
+
3229
+ const normalizedHttpsSet = new Set(
3230
+ ensureArray(httpsUrls)
3231
+ .map(normalizeHttpsUrl)
3232
+ .filter(Boolean)
3233
+ )
3234
+
3235
+ if (host && host.local && peerSuffix) {
3236
+ for (const originalUrl of normalizedHttpsSet) {
3237
+ try {
3238
+ const parsed = new URL(originalUrl)
3239
+ if (parsed.hostname.endsWith(peerSuffix)) {
3240
+ const aliasHost = `${parsed.hostname.slice(0, -peerSuffix.length)}.localhost`
3241
+ if (aliasHost && aliasHost !== parsed.hostname) {
3242
+ let pathname = parsed.pathname || ''
3243
+ if (pathname === '/' || pathname === '') {
3244
+ pathname = ''
3245
+ }
3246
+ const aliasUrl = `${parsed.protocol}//${aliasHost}${pathname}${parsed.search}${parsed.hash}`
3247
+ normalizedHttpsSet.add(aliasUrl)
3248
+ }
3249
+ }
3250
+ } catch (_) {
3251
+ // continue
3222
3252
  }
3223
- info.push({
3224
- online: true,
3225
- host: {
3226
- ip: host,
3227
- name: host_info.name,
3228
- platform: host_info.platform,
3229
- arch: host_info.arch
3230
- },
3231
- name: host_router.title || host_router.name,
3232
- ip
3253
+ }
3254
+ }
3255
+
3256
+ const normalizedHttps = Array.from(normalizedHttpsSet)
3257
+ const ipValue = typeof ip === 'string' ? ip : (normalizedHttp ? normalizedHttp.replace(/^https?:\/\//i, '') : null)
3258
+ const selectedUrl = normalizedHttps[0] || normalizedHttp || null
3259
+ const protocol = normalizedHttps.length > 0 ? 'https' : (normalizedHttp ? 'http' : undefined)
3260
+ const hostKey = host && (host.ip || host.name) ? `${host.ip || host.name}` : 'unknown'
3261
+ const uniquenessKey = `${hostKey}|${name}|${ipValue || ''}|${selectedUrl || ''}`
3262
+ if (seen.has(uniquenessKey)) {
3263
+ return
3264
+ }
3265
+ seen.add(uniquenessKey)
3266
+
3267
+ const entry = {
3268
+ online,
3269
+ host: { ...host },
3270
+ name,
3271
+ ip: ipValue,
3272
+ url: selectedUrl,
3273
+ protocol,
3274
+ urls: {
3275
+ http: normalizedHttp,
3276
+ https: normalizedHttps
3277
+ }
3278
+ }
3279
+ if (description) {
3280
+ entry.description = description
3281
+ }
3282
+ if (icon) {
3283
+ entry.icon = icon
3284
+ }
3285
+ info.push(entry)
3286
+ }
3287
+
3288
+ for (const host of Object.keys(this.kernel.peer.info)) {
3289
+ const hostInfo = this.kernel.peer.info[host]
3290
+ if (!hostInfo) {
3291
+ continue
3292
+ }
3293
+
3294
+ const hostMeta = {
3295
+ ip: host,
3296
+ local: this.kernel.peer.host === host,
3297
+ name: hostInfo.name,
3298
+ platform: hostInfo.platform,
3299
+ arch: hostInfo.arch
3300
+ }
3301
+
3302
+ const rewrites = hostInfo.rewrite_mapping
3303
+ if (rewrites && typeof rewrites === 'object') {
3304
+ for (const key of Object.keys(rewrites)) {
3305
+ const rewrite = rewrites[key]
3306
+ if (!rewrite) {
3307
+ continue
3308
+ }
3309
+ const externalIp = Array.isArray(rewrite.external_ip) ? rewrite.external_ip[0] : rewrite.external_ip
3310
+ const httpsSources = [
3311
+ ...ensureArray(rewrite.external_router),
3312
+ ...ensureArray(rewrite.internal_router)
3313
+ ]
3314
+ pushEntry({
3315
+ host: hostMeta,
3316
+ name: `[Files] ${rewrite.name || key}`,
3317
+ ip: externalIp || null,
3318
+ httpUrl: externalIp,
3319
+ httpsUrls: Array.from(new Set(httpsSources))
3233
3320
  })
3234
3321
  }
3235
3322
  }
3236
3323
 
3237
- for(let app of host_info.installed) {
3238
- info.push({
3239
- host: {
3240
- ip: host,
3241
- local: this.kernel.peer.host === host,
3242
- name: host_info.name,
3243
- platform: host_info.platform,
3244
- arch: host_info.arch
3245
- },
3246
- name: app.title || app.folder,
3247
- ip: app.http_href.replace("http://", "")
3324
+ const hostRouters = Array.isArray(hostInfo.router_info) ? hostInfo.router_info : []
3325
+ for (const route of hostRouters) {
3326
+ if (!route) {
3327
+ continue
3328
+ }
3329
+ const externalIp = Array.isArray(route.external_ip) ? route.external_ip[0] : route.external_ip
3330
+ const httpUrl = externalIp || null
3331
+ const httpsCandidates = Array.from(new Set([
3332
+ ...ensureArray(route.external_router),
3333
+ ...ensureArray(route.internal_router)
3334
+ ]))
3335
+ if (httpsCandidates.length === 0 && !httpUrl) {
3336
+ continue
3337
+ }
3338
+ pushEntry({
3339
+ host: hostMeta,
3340
+ name: route.title || route.name,
3341
+ ip: externalIp || null,
3342
+ httpUrl,
3343
+ httpsUrls: httpsCandidates,
3344
+ description: route.description,
3345
+ icon: route.icon || route.https_icon || route.http_icon
3346
+ })
3347
+ }
3348
+
3349
+ const installedApps = Array.isArray(hostInfo.installed) ? hostInfo.installed : []
3350
+ for (const app of installedApps) {
3351
+ if (!app) {
3352
+ continue
3353
+ }
3354
+ const httpHref = Array.isArray(app.http_href) ? app.http_href[0] : app.http_href
3355
+ const httpsCandidates = Array.from(new Set([
3356
+ ...ensureArray(app.app_href),
3357
+ ...ensureArray(app.https_href)
3358
+ ]))
3359
+ pushEntry({
3360
+ host: hostMeta,
3361
+ name: app.title || app.name || app.folder,
3362
+ ip: httpHref ? httpHref.replace(/^https?:\/\//i, '') : null,
3363
+ httpUrl: httpHref || null,
3364
+ httpsUrls: httpsCandidates,
3365
+ description: app.description,
3366
+ icon: app.https_icon || app.http_icon || app.icon
3248
3367
  })
3249
3368
  }
3250
3369
  }
@@ -6623,6 +6742,7 @@ class Server {
6623
6742
  if (preferHttps) {
6624
6743
  httpsHosts = resolveHttpsHosts(item)
6625
6744
  }
6745
+ console.log("httpsHosts", httpsHosts)
6626
6746
  const httpsUrls = httpsHosts.map((host) => {
6627
6747
  if (!host) {
6628
6748
  return null
@@ -6633,6 +6753,7 @@ class Server {
6633
6753
  }
6634
6754
  return `https://${trimmed}`
6635
6755
  }).filter(Boolean)
6756
+ console.log("httpsUrls", httpsUrls)
6636
6757
 
6637
6758
  const preferredHttpsUrl = preferFriendlyHost(httpsHosts)
6638
6759
  const displayHttpsUrl = preferredHttpsUrl
@@ -6661,7 +6782,7 @@ class Server {
6661
6782
  }
6662
6783
  })
6663
6784
 
6664
- // this.add_extra_urls(info)
6785
+ this.add_extra_urls(info)
6665
6786
  res.json({
6666
6787
  info
6667
6788
  })
@@ -97,6 +97,65 @@ body.dark .url-dropdown-name {
97
97
  body.dark .url-dropdown-url {
98
98
  color: #a0aec0;
99
99
  }
100
+ .url-dropdown-url-list {
101
+ display: flex;
102
+ flex-wrap: wrap;
103
+ gap: 6px;
104
+ }
105
+ .url-chip {
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: 6px;
109
+ padding: 4px 8px;
110
+ border-radius: 6px;
111
+ border: 1px solid #e2e8f0;
112
+ background: #f8fafc;
113
+ color: #2d3748;
114
+ font-size: 12px;
115
+ font-weight: 500;
116
+ cursor: pointer;
117
+ transition: background 0.15s ease, border-color 0.15s ease;
118
+ }
119
+ .url-chip:hover {
120
+ background: #edf2f7;
121
+ border-color: #cbd5f5;
122
+ }
123
+ body.dark .url-chip {
124
+ border-color: rgba(255,255,255,0.15);
125
+ background: rgba(255,255,255,0.06);
126
+ color: #e2e8f0;
127
+ }
128
+ body.dark .url-chip:hover {
129
+ background: rgba(255,255,255,0.1);
130
+ border-color: rgba(255,255,255,0.22);
131
+ }
132
+ .url-chip .chip-mark {
133
+ font-size: 10px;
134
+ font-weight: 600;
135
+ letter-spacing: 0.05em;
136
+ text-transform: uppercase;
137
+ padding: 2px 4px;
138
+ border-radius: 4px;
139
+ }
140
+ .url-chip-https .chip-mark {
141
+ background: rgba(72, 187, 120, 0.18);
142
+ color: #276749;
143
+ }
144
+ body.dark .url-chip-https .chip-mark {
145
+ background: rgba(72, 187, 120, 0.25);
146
+ color: #9ae6b4;
147
+ }
148
+ .url-chip-http .chip-mark {
149
+ background: rgba(237, 137, 54, 0.18);
150
+ color: #9c4221;
151
+ }
152
+ body.dark .url-chip-http .chip-mark {
153
+ background: rgba(237, 137, 54, 0.25);
154
+ color: #fbd38d;
155
+ }
156
+ .url-chip .chip-label {
157
+ font-weight: 500;
158
+ }
100
159
  .url-dropdown-loading {
101
160
  padding: 12px 16px;
102
161
  font-size: 14px;
@@ -122,6 +122,84 @@ function initUrlDropdown(config = {}) {
122
122
  return Array.from(urls);
123
123
  };
124
124
 
125
+ const ensureContainerHref = (value) => {
126
+ if (!value || typeof value !== 'string') {
127
+ return value;
128
+ }
129
+ if (value.startsWith('/container?url=')) {
130
+ return value;
131
+ }
132
+ return `/container?url=${encodeURIComponent(value)}`;
133
+ };
134
+
135
+ const openUrlWithType = (url, type) => {
136
+ if (!url) {
137
+ return;
138
+ }
139
+ if (!type || type === 'current') {
140
+ location.href = url;
141
+ return;
142
+ }
143
+ const target = ensureContainerHref(url);
144
+ location.href = target;
145
+ };
146
+
147
+ const escapeAttribute = (value) => {
148
+ if (value === null || value === undefined) {
149
+ return '';
150
+ }
151
+ return String(value)
152
+ .replace(/&/g, '&')
153
+ .replace(/"/g, '"')
154
+ .replace(/'/g, ''')
155
+ .replace(/</g, '&lt;')
156
+ .replace(/>/g, '&gt;');
157
+ };
158
+
159
+ const formatUrlLabel = (url) => {
160
+ if (!url) {
161
+ return '';
162
+ }
163
+ return url.replace(/^https?:\/\//i, '');
164
+ };
165
+
166
+ const createUrlChip = (url, type, scheme) => {
167
+ if (!url) {
168
+ return '';
169
+ }
170
+ const normalizedType = type || 'remote';
171
+ const classSuffix = scheme === 'HTTPS' ? 'https' : 'http';
172
+ const label = formatUrlLabel(url);
173
+ return `
174
+ <button type="button" class="url-chip url-chip-${classSuffix}" data-url="${escapeAttribute(url)}" data-host-type="${escapeAttribute(normalizedType)}">
175
+ <span class="chip-mark">${scheme}</span>
176
+ <span class="chip-label">${escapeHtml(label)}</span>
177
+ </button>
178
+ `;
179
+ };
180
+
181
+ const buildProcessUrlList = (process) => {
182
+ const { httpUrl, httpsUrls } = getProcessUrls(process);
183
+ const hostType = (process && process.host && process.host.local) ? 'local' : 'remote';
184
+ const parts = [];
185
+ const seen = new Set();
186
+ httpsUrls.forEach((candidate) => {
187
+ if (!candidate || seen.has(candidate)) {
188
+ return;
189
+ }
190
+ parts.push(createUrlChip(candidate, hostType, 'HTTPS'));
191
+ seen.add(candidate);
192
+ });
193
+ if (httpUrl && !seen.has(httpUrl)) {
194
+ parts.push(createUrlChip(httpUrl, hostType, 'HTTP'));
195
+ seen.add(httpUrl);
196
+ }
197
+ if (parts.length === 0) {
198
+ return '';
199
+ }
200
+ return `<div class="url-dropdown-url-list">${parts.join('')}</div>`;
201
+ };
202
+
125
203
  let isDropdownVisible = false;
126
204
  let allProcesses = []; // Store all processes for filtering
127
205
  let filteredProcesses = []; // Store currently filtered processes
@@ -164,26 +242,16 @@ function initUrlDropdown(config = {}) {
164
242
 
165
243
  if (document.querySelector(".urlbar")) {
166
244
  document.querySelector(".urlbar").addEventListener("submit", (e) => {
167
- e.preventDefault()
168
- e.stopPropagation()
169
- let el = e.target.querySelector("input[type=url]")
170
- let val = el.value
171
- let type = el.getAttribute("data-host-type")
172
- if (type === "local") {
173
- let redirect_uri = "/container?url=" + val
174
- location.href = redirect_uri
175
- } else {
176
- let u = new URL(val)
177
- if (String(u.port) === "42000") {
178
- // pinokio app => open the url itself
179
- window.open(val, "_blank", 'self')
180
- } else {
181
- // other servers => open in pinokio redirect frame
182
- let redirect_uri = "/container?url=" + val
183
- location.href = redirect_uri
184
- }
245
+ e.preventDefault();
246
+ e.stopPropagation();
247
+ const el = e.target.querySelector("input[type=url]");
248
+ if (!el) {
249
+ return;
185
250
  }
186
- })
251
+ const val = el.value;
252
+ const type = el.getAttribute("data-host-type");
253
+ openUrlWithType(val, type);
254
+ });
187
255
  }
188
256
 
189
257
 
@@ -391,7 +459,7 @@ function initUrlDropdown(config = {}) {
391
459
  <div class="url-dropdown-host-header current-tab">
392
460
  <span class="host-name">Current tab</span>
393
461
  </div>
394
- <div class="url-dropdown-item" data-url="${escapeHtml(currentUrl)}" data-host-type="current">
462
+ <div class="url-dropdown-item" data-url="${escapeAttribute(currentUrl)}" data-host-type="current">
395
463
  <div class="url-dropdown-name">
396
464
  <span>
397
465
  <i class="fa-solid fa-clone"></i>
@@ -407,32 +475,7 @@ function initUrlDropdown(config = {}) {
407
475
  html += createEmptyStateHtml(getEmptyStateMessage(urlInput));
408
476
  dropdown.innerHTML = html;
409
477
  attachCreateButtonHandler(dropdown, urlInput);
410
- dropdown.querySelectorAll('.url-dropdown-item:not(.non-selectable)').forEach(item => {
411
- item.addEventListener('click', function() {
412
- const url = this.getAttribute('data-url');
413
- const type = this.getAttribute('data-host-type');
414
- urlInput.value = url;
415
- urlInput.setAttribute("data-host-type", type || 'current');
416
- hideDropdown();
417
-
418
- if (type === "local") {
419
- let redirect_uri = "/container?url=" + url;
420
- location.href = redirect_uri;
421
- } else {
422
- if (!type || type === 'current') {
423
- location.href = url;
424
- return;
425
- }
426
- let u = new URL(url);
427
- if (String(u.port) === "42000") {
428
- window.open(url, "_blank", 'self');
429
- } else {
430
- let redirect_uri = "/container?url=" + url;
431
- location.href = redirect_uri;
432
- }
433
- }
434
- });
435
- });
478
+ attachUrlItemHandlers(dropdown);
436
479
  return;
437
480
  }
438
481
 
@@ -442,14 +485,14 @@ function initUrlDropdown(config = {}) {
442
485
  Object.keys(groupedProcesses).forEach(hostKey => {
443
486
  const hostData = groupedProcesses[hostKey];
444
487
  const hostInfo = hostData.host;
445
- const processes = hostData.processes;
488
+ const hostProcesses = hostData.processes;
446
489
  const isLocal = hostData.isLocal;
447
490
 
448
491
  // Add host header
449
492
  html += createHostHeader(hostInfo, isLocal);
450
493
 
451
494
  // Add processes for this host
452
- processes.forEach(process => {
495
+ hostProcesses.forEach(process => {
453
496
  const onlineIndicator = process.online ?
454
497
  '<div class="status-circle online"></div>' :
455
498
  '<div class="status-circle offline"></div>';
@@ -461,63 +504,70 @@ function initUrlDropdown(config = {}) {
461
504
  <div class="url-dropdown-item non-selectable">
462
505
  <div class="url-dropdown-name">
463
506
  ${onlineIndicator}
464
- <button class="peer-network-button" data-network-url="${networkUrl}"><i class="fa-solid fa-toggle-on"></i> Turn on peer network</button>
465
- ${escapeHtml(process.name)}
466
- </div>
467
- </div>
468
- `;
469
- } else {
470
- // Normal selectable item
471
- const url = getProcessDisplayUrl(process);
472
- if (!url) {
473
- return;
474
- }
475
- html += `
476
- <div class="url-dropdown-item" data-url="${url}" data-host-type="${process.host.local ? "local" : "remote"}">
477
- <div class="url-dropdown-name">
478
- ${onlineIndicator}
507
+ <button class="peer-network-button" data-network-url="${escapeAttribute(networkUrl)}"><i class="fa-solid fa-toggle-on"></i> Turn on peer network</button>
479
508
  ${escapeHtml(process.name)}
480
509
  </div>
481
- <div class="url-dropdown-url">${escapeHtml(url)}</div>
482
510
  </div>
483
511
  `;
512
+ return;
484
513
  }
514
+
515
+ const displayUrl = getProcessDisplayUrl(process);
516
+ if (!displayUrl) {
517
+ return;
518
+ }
519
+ const selectionType = (process.host && process.host.local) ? 'local' : 'remote';
520
+ const urlListHtml = buildProcessUrlList(process);
521
+ html += `
522
+ <div class="url-dropdown-item" data-url="${escapeAttribute(displayUrl)}" data-host-type="${escapeAttribute(selectionType)}">
523
+ <div class="url-dropdown-name">
524
+ ${onlineIndicator}
525
+ ${escapeHtml(process.name)}
526
+ </div>
527
+ <div class="url-dropdown-url">${escapeHtml(displayUrl)}</div>
528
+ ${urlListHtml}
529
+ </div>
530
+ `;
485
531
  });
486
532
  });
487
533
 
488
534
  dropdown.innerHTML = html;
535
+ attachUrlItemHandlers(dropdown);
536
+ }
537
+
538
+ function handleSelection(url, type, options = {}) {
539
+ if (!url) return;
540
+ if (urlInput && options.updateInput !== false) {
541
+ urlInput.value = url;
542
+ urlInput.setAttribute('data-host-type', type || 'remote');
543
+ }
544
+ hideDropdown();
545
+ if (options.navigate === false) {
546
+ return;
547
+ }
548
+ openUrlWithType(url, type);
549
+ }
489
550
 
490
- // Add click handlers to dropdown items
491
- dropdown.querySelectorAll('.url-dropdown-item:not(.non-selectable)').forEach(item => {
551
+ function attachUrlItemHandlers(container) {
552
+ if (!container) return;
553
+ container.querySelectorAll('.url-dropdown-item:not(.non-selectable)').forEach(item => {
492
554
  item.addEventListener('click', function() {
493
555
  const url = this.getAttribute('data-url');
494
556
  const type = this.getAttribute('data-host-type');
495
- urlInput.value = url;
496
- urlInput.setAttribute("data-host-type", type || 'remote');
497
- hideDropdown();
498
-
499
- // Navigate directly instead of dispatching submit event
500
- if (type === "local") {
501
- let redirect_uri = "/container?url=" + url;
502
- location.href = redirect_uri;
503
- } else if (type === 'current') {
504
- location.href = url;
505
- } else {
506
- let u = new URL(url);
507
- if (String(u.port) === "42000") {
508
- window.open(url, "_blank", 'self');
509
- } else {
510
- let redirect_uri = "/container?url=" + url;
511
- location.href = redirect_uri;
512
- }
513
- }
557
+ handleSelection(url, type);
514
558
  });
515
559
  });
516
-
517
- // Add click handlers to peer network buttons
518
- dropdown.querySelectorAll('.peer-network-button').forEach(button => {
519
- button.addEventListener('click', function(e) {
520
- e.stopPropagation();
560
+ container.querySelectorAll('.url-chip').forEach(button => {
561
+ button.addEventListener('click', function(event) {
562
+ event.stopPropagation();
563
+ const url = this.getAttribute('data-url');
564
+ const type = this.getAttribute('data-host-type');
565
+ handleSelection(url, type);
566
+ });
567
+ });
568
+ container.querySelectorAll('.peer-network-button').forEach(button => {
569
+ button.addEventListener('click', function(event) {
570
+ event.stopPropagation();
521
571
  const networkUrl = this.getAttribute('data-network-url');
522
572
  window.open(networkUrl, '_blank', 'self');
523
573
  });
@@ -704,10 +704,9 @@ document.addEventListener('DOMContentLoaded', function() {
704
704
  <% item.external_router.forEach((domain) => { %>
705
705
  <a class='net' target="_blank" href="https://<%=domain%>"><span class='mark'>HTTPS</span><span><%=domain%></span></a>
706
706
  <% }) %>
707
- <% } else { %>
708
- <% if (item.external_ip) { %>
709
- <a class='net' target="_blank" href="http://<%=item.external_ip%>"><span class='mark'>HTTP</span><span><%=item.external_ip%></span></a>
710
- <% } %>
707
+ <% } %>
708
+ <% if (item.external_ip) { %>
709
+ <a class='net' target="_blank" href="http://<%=item.external_ip%>"><span class='mark'>HTTP</span><span><%=item.external_ip%></span></a>
711
710
  <% } %>
712
711
  </div>
713
712
  </div>