pinokiod 3.150.0 → 3.152.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/router/common.js +1 -0
- package/kernel/router/connector.js +2 -0
- package/kernel/router/localhost_home_router.js +1 -0
- package/package.json +1 -1
- package/server/index.js +170 -49
- package/server/public/urldropdown.css +59 -0
- package/server/public/urldropdown.js +140 -90
- package/server/views/net.ejs +3 -4
package/kernel/router/common.js
CHANGED
|
@@ -16,6 +16,7 @@ class Connector extends Processor {
|
|
|
16
16
|
"headers": {
|
|
17
17
|
"request": {
|
|
18
18
|
"set": {
|
|
19
|
+
"Host": ["{http.request.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
package/server/index.js
CHANGED
|
@@ -3190,61 +3190,180 @@ class Server {
|
|
|
3190
3190
|
}
|
|
3191
3191
|
}
|
|
3192
3192
|
add_extra_urls(info) {
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3193
|
+
if (!this.kernel.peer || !this.kernel.peer.info) {
|
|
3194
|
+
return
|
|
3195
|
+
}
|
|
3196
3196
|
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
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
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
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
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
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
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
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
|
-
|
|
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, '<')
|
|
156
|
+
.replace(/>/g, '>');
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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="${
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
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
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
});
|
package/server/views/net.ejs
CHANGED
|
@@ -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
|
-
<% }
|
|
708
|
-
|
|
709
|
-
|
|
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>
|