fix: disable HTTP keep-alive and update nginx proxy config
- Set http1_keep_alive(false) on hyper server to prevent connection reuse issues with nginx reverse proxy - Clean up nginx proxy config: remove upstream block, use direct proxy_pass to 127.0.0.1:5678 - Update AppLauncherOverlay and appLauncher store with UI fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0cf71c4115
commit
0fb373273a
@@ -1,41 +1,34 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
/** Apps that must open in new tab instead of iframe.
|
||||
* - DENY apps: always new tab (X-Frame-Options: DENY)
|
||||
* - Redirect apps: new tab on HTTPS (absolute redirects break subpath proxy in iframe)
|
||||
* On HTTP, these load via direct port URL so iframe works fine.
|
||||
/** Apps that set X-Frame-Options or CSP frame-ancestors, blocking iframe embedding.
|
||||
* Verified by checking response headers from each app container.
|
||||
* These always open in a new tab. Other apps load in the iframe overlay.
|
||||
*/
|
||||
function mustOpenInNewTab(url: string): boolean {
|
||||
try {
|
||||
const u = new URL(url)
|
||||
// Always new tab: X-Frame-Options DENY or subpath fundamentally breaks the app
|
||||
// External sites — third-party cookie/iframe restrictions
|
||||
if (u.hostname.includes('indeehub')) return true
|
||||
// Local apps with X-Frame-Options or CSP frame-ancestors blocking iframes
|
||||
if (
|
||||
u.port === '23000' || // BTCPay (X-Frame-Options: DENY)
|
||||
u.port === '8123' || // Home Assistant (subpath breaks routing)
|
||||
u.port === '8085' || // Nextcloud (subpath breaks CSS/assets)
|
||||
u.port === '2283' // Immich (subpath breaks SPA)
|
||||
u.port === '23000' || // BTCPay — X-Frame-Options: DENY
|
||||
u.port === '3000' || // Grafana — X-Frame-Options: deny
|
||||
u.port === '8082' || // Vaultwarden — X-Frame-Options: SAMEORIGIN + CSP frame-ancestors
|
||||
u.port === '2342' || // PhotoPrism — X-Frame-Options: DENY + CSP frame-ancestors: 'none'
|
||||
u.port === '8085' || // Nextcloud — X-Frame-Options: SAMEORIGIN
|
||||
u.port === '3001' || // Uptime Kuma — X-Frame-Options: SAMEORIGIN
|
||||
u.port === '8123' // Home Assistant — X-Frame-Options: SAMEORIGIN
|
||||
) {
|
||||
return true
|
||||
}
|
||||
// On HTTPS, apps with absolute-path redirects break in iframe via proxy
|
||||
if (window.location.protocol === 'https:') {
|
||||
return (
|
||||
u.port === '8096' || // Jellyfin (redirects to /web/index.html)
|
||||
u.port === '9000' || // Portainer (redirects to /timeout.html)
|
||||
u.port === '2342' || // PhotoPrism (redirects to /library/login)
|
||||
u.port === '9980' || // OnlyOffice (redirects to /welcome/)
|
||||
u.port === '3001' || // Uptime Kuma (redirects to /dashboard)
|
||||
u.port === '8175' // Fedimint (redirects to /login)
|
||||
)
|
||||
}
|
||||
return false
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Port → proxy path for apps (nginx strips X-Frame-Options) */
|
||||
/** Port → proxy path for apps (nginx strips X-Frame-Options + avoids mixed content) */
|
||||
const PORT_TO_PROXY: Record<string, string> = {
|
||||
'81': '/app/nginx-proxy-manager/',
|
||||
'3000': '/app/grafana/',
|
||||
@@ -65,7 +58,11 @@ const PORT_TO_PROXY: Record<string, string> = {
|
||||
'18081': '/app/nostr-rs-relay/',
|
||||
}
|
||||
|
||||
/** Rewrite to same-origin proxy so iframe can embed (avoids mixed content on HTTPS) */
|
||||
/** Rewrite to same-origin proxy ONLY when needed for HTTPS mixed-content.
|
||||
* On HTTP, direct port URLs are used — they avoid subpath routing issues
|
||||
* (apps' root-relative asset paths like /static/main.js break under /app/xxx/).
|
||||
* On HTTPS, must proxy to avoid mixed-content blocks; nginx also strips X-Frame-Options.
|
||||
*/
|
||||
function toEmbeddableUrl(url: string): string {
|
||||
try {
|
||||
const u = new URL(url)
|
||||
@@ -73,8 +70,7 @@ function toEmbeddableUrl(url: string): string {
|
||||
const proxyPath = PORT_TO_PROXY[u.port]
|
||||
const sameHost = u.hostname === window.location.hostname
|
||||
const needsProxy = window.location.protocol === 'https:' && u.protocol === 'http:'
|
||||
// Use proxy when: (a) mixed content, or (b) vaultwarden/penpot always (subpath required)
|
||||
if (proxyPath && sameHost && (needsProxy || u.port === '8082' || u.port === '9001')) {
|
||||
if (proxyPath && sameHost && needsProxy) {
|
||||
return `${origin}${proxyPath}`
|
||||
}
|
||||
} catch {
|
||||
@@ -90,11 +86,12 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
|
||||
let previousActiveElement: HTMLElement | null = null
|
||||
|
||||
function open(payload: { url: string; title: string; openInNewTab?: boolean }) {
|
||||
const embeddableUrl = toEmbeddableUrl(payload.url)
|
||||
if (payload.openInNewTab || mustOpenInNewTab(payload.url)) {
|
||||
window.open(embeddableUrl, '_blank', 'noopener,noreferrer')
|
||||
// New tab: always use direct port URL so app assets load correctly
|
||||
window.open(payload.url, '_blank', 'noopener,noreferrer')
|
||||
return
|
||||
}
|
||||
const embeddableUrl = toEmbeddableUrl(payload.url)
|
||||
previousActiveElement = (document.activeElement as HTMLElement) || null
|
||||
url.value = embeddableUrl
|
||||
title.value = payload.title
|
||||
|
||||
Reference in New Issue
Block a user