fix: migration hostnames hub point->tiret + bouton repli sidebar
This commit is contained in:
+24
-8
@@ -13,22 +13,38 @@ from app.personas import ensure_dirs
|
|||||||
|
|
||||||
def get_subdomain_target(host: str) -> Optional[Tuple[str, str]]:
|
def get_subdomain_target(host: str) -> Optional[Tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Inspects Host header and returns (target_backend_url, universe_id) if it matches a subdomain.
|
Inspects Host header and returns (target_backend_url, universe_id) if it matches a universe hostname.
|
||||||
Examples:
|
|
||||||
tt.hub.yesminedor.tn -> (HERMES_TT_URL, 'tt')
|
Hostnames (Dash-based 1st level):
|
||||||
nyora.hub.yesminedor.tn -> (HERMES_NYORA_URL, 'nyora')
|
tt-hub.yesminedor.tn -> (HERMES_TT_URL, 'tt')
|
||||||
perso.hub.yesminedor.tn -> (HERMES_PERSO_URL, 'perso')
|
nyora-hub.yesminedor.tn -> (HERMES_NYORA_URL, 'nyora')
|
||||||
nabil.hub.yesminedor.tn -> (HERMES_NABIL_URL, 'nabil')
|
perso-hub.yesminedor.tn -> (HERMES_PERSO_URL, 'perso')
|
||||||
dsh.hub.yesminedor.tn -> (DSH_FILEBROWSER_URL, 'nabil')
|
nabil-hub.yesminedor.tn -> (HERMES_NABIL_URL, 'nabil')
|
||||||
|
dsh-hub.yesminedor.tn -> (DSH_FILEBROWSER_URL, 'nabil')
|
||||||
|
|
||||||
|
Apex / Hub UI:
|
||||||
|
hub.yesminedor.tn -> None (Serves index.html Workspace Switcher)
|
||||||
"""
|
"""
|
||||||
if not host:
|
if not host:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
hostname = host.split(":")[0].lower()
|
hostname = host.split(":")[0].lower()
|
||||||
|
|
||||||
|
# The main hub switcher UI should NOT be proxied
|
||||||
|
if hostname in ("hub.yesminedor.tn", "localhost", "127.0.0.1"):
|
||||||
|
return None
|
||||||
|
|
||||||
sub = None
|
sub = None
|
||||||
if hostname.endswith(".hub.yesminedor.tn"):
|
# 1. New 1st level dash-based production hostnames (*-hub.yesminedor.tn)
|
||||||
|
if hostname.endswith("-hub.yesminedor.tn"):
|
||||||
|
sub = hostname.rsplit("-hub.yesminedor.tn", 1)[0]
|
||||||
|
# 2. Backward compatibility (*.hub.yesminedor.tn)
|
||||||
|
elif hostname.endswith(".hub.yesminedor.tn"):
|
||||||
sub = hostname.rsplit(".hub.yesminedor.tn", 1)[0]
|
sub = hostname.rsplit(".hub.yesminedor.tn", 1)[0]
|
||||||
|
# 3. Localhost dash development (*-hub.localhost)
|
||||||
|
elif hostname.endswith("-hub.localhost"):
|
||||||
|
sub = hostname.rsplit("-hub.localhost", 1)[0]
|
||||||
|
# 4. Localhost dot development (*.localhost)
|
||||||
elif hostname.endswith(".localhost"):
|
elif hostname.endswith(".localhost"):
|
||||||
sub = hostname.rsplit(".localhost", 1)[0]
|
sub = hostname.rsplit(".localhost", 1)[0]
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,18 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="hub-shell">
|
<div class="hub-shell">
|
||||||
<!-- Sidebar / Workspace Switcher -->
|
<!-- Sidebar / Workspace Switcher -->
|
||||||
<aside class="hub-sidebar">
|
<aside id="hub-sidebar" class="hub-sidebar">
|
||||||
<div class="hub-brand">
|
<div class="hub-brand">
|
||||||
<div class="hub-logo-icon">H</div>
|
<div class="hub-brand-left">
|
||||||
<div class="hub-brand-text">Hermes Hub</div>
|
<div class="hub-logo-icon">H</div>
|
||||||
<div class="hub-brand-badge">v1.0</div>
|
<div class="hub-brand-text">Hermes Hub</div>
|
||||||
|
<div class="hub-brand-badge">v1.0</div>
|
||||||
|
</div>
|
||||||
|
<button id="btn-toggle-sidebar" class="hub-collapse-btn" type="button" aria-label="Replier la barre latérale" title="Replier la barre latérale">
|
||||||
|
<svg class="hub-collapse-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="15 18 9 12 15 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hub-nav-section">Univers Hermes</div>
|
<div class="hub-nav-section">Univers Hermes</div>
|
||||||
@@ -22,6 +29,7 @@
|
|||||||
data-scope="{{ u.scope }}"
|
data-scope="{{ u.scope }}"
|
||||||
data-tagline="{{ u.tagline }}"
|
data-tagline="{{ u.tagline }}"
|
||||||
data-supports-files="{{ 'true' if u.supports_files else 'false' }}"
|
data-supports-files="{{ 'true' if u.supports_files else 'false' }}"
|
||||||
|
title="{{ u.name }} — {{ u.tagline }}"
|
||||||
style="--item-accent: var({{ u.accent_token }});"
|
style="--item-accent: var({{ u.accent_token }});"
|
||||||
>
|
>
|
||||||
<div class="hub-universe-avatar">
|
<div class="hub-universe-avatar">
|
||||||
@@ -39,11 +47,11 @@
|
|||||||
|
|
||||||
<!-- Sidebar Tools Footer -->
|
<!-- Sidebar Tools Footer -->
|
||||||
<div class="hub-sidebar-footer">
|
<div class="hub-sidebar-footer">
|
||||||
<button id="btn-edit-persona" class="hub-tool-btn" type="button">
|
<button id="btn-edit-persona" class="hub-tool-btn" type="button" title="Personnalité & Ton">
|
||||||
<span>⚙️</span>
|
<span>⚙️</span>
|
||||||
<span>Personnalité & Ton</span>
|
<span>Personnalité & Ton</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-clone-universe" class="hub-tool-btn" type="button">
|
<button id="btn-clone-universe" class="hub-tool-btn" type="button" title="Cloner ce profil">
|
||||||
<span>🧬</span>
|
<span>🧬</span>
|
||||||
<span>Cloner ce profil</span>
|
<span>Cloner ce profil</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+93
-3
@@ -33,16 +33,25 @@ html, body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
transition: width 0.2s ease;
|
transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-brand {
|
.hub-brand {
|
||||||
height: var(--hub-header-height);
|
height: var(--hub-header-height);
|
||||||
padding: 0 1.25rem;
|
padding: 0 0.85rem 0 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid var(--hub-border-subtle);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-brand-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
border-bottom: 1px solid var(--hub-border-subtle);
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-logo-icon {
|
.hub-logo-icon {
|
||||||
@@ -57,6 +66,7 @@ html, body {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: 0 0 12px var(--hub-accent-glow-current);
|
box-shadow: 0 0 12px var(--hub-accent-glow-current);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-brand-text {
|
.hub-brand-text {
|
||||||
@@ -64,6 +74,7 @@ html, body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
color: var(--hub-text-primary);
|
color: var(--hub-text-primary);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-brand-badge {
|
.hub-brand-badge {
|
||||||
@@ -75,6 +86,33 @@ html, body {
|
|||||||
border-radius: var(--hub-radius-full);
|
border-radius: var(--hub-radius-full);
|
||||||
color: var(--hub-text-secondary);
|
color: var(--hub-text-secondary);
|
||||||
border: 1px solid var(--hub-border-subtle);
|
border: 1px solid var(--hub-border-subtle);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Collapse Toggle Button */
|
||||||
|
.hub-collapse-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: var(--hub-radius-sm);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: var(--hub-text-muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-collapse-btn:hover {
|
||||||
|
background-color: var(--hub-bg-card-hover);
|
||||||
|
color: var(--hub-text-primary);
|
||||||
|
border-color: var(--hub-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-collapse-icon {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Universes Switcher List */
|
/* Universes Switcher List */
|
||||||
@@ -85,6 +123,7 @@ html, body {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
color: var(--hub-text-muted);
|
color: var(--hub-text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-universes-list {
|
.hub-universes-list {
|
||||||
@@ -95,6 +134,7 @@ html, body {
|
|||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-universe-btn {
|
.hub-universe-btn {
|
||||||
@@ -216,6 +256,7 @@ html, body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-tool-btn:hover {
|
.hub-tool-btn:hover {
|
||||||
@@ -223,6 +264,54 @@ html, body {
|
|||||||
color: var(--hub-text-primary);
|
color: var(--hub-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Collapsed Sidebar State
|
||||||
|
========================================================================= */
|
||||||
|
.hub-sidebar.collapsed {
|
||||||
|
width: var(--hub-sidebar-collapsed-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-brand {
|
||||||
|
padding: 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-brand-left,
|
||||||
|
.hub-sidebar.collapsed .hub-nav-section,
|
||||||
|
.hub-sidebar.collapsed .hub-universe-info,
|
||||||
|
.hub-sidebar.collapsed .hub-sidebar-footer .hub-tool-btn span:last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-collapse-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-collapse-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-universe-btn {
|
||||||
|
padding: 0.65rem 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-universe-btn.active::before {
|
||||||
|
left: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-status-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar.collapsed .hub-sidebar-footer .hub-tool-btn {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Main View Area */
|
/* Main View Area */
|
||||||
.hub-main {
|
.hub-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -231,6 +320,7 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--hub-bg-canvas);
|
background-color: var(--hub-bg-canvas);
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Universe Topbar */
|
/* Universe Topbar */
|
||||||
|
|||||||
+39
-10
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Hermes Hub — Client-side Workspace Switcher & Context Manager
|
* Hermes Hub — Client-side Workspace Switcher & Context Manager
|
||||||
* Routage natif par sous-domaine (tt.hub.yesminedor.tn, nyora.hub..., perso.hub..., nabil.hub..., dsh.hub...)
|
* Routage natif par sous-domaine 1er niveau (tt-hub.yesminedor.tn, nyora-hub..., perso-hub..., nabil-hub..., dsh-hub...)
|
||||||
* Résout à 100% le chargement des assets absolus sans réécriture fragile
|
* Résout à 100% le chargement des assets absolus et la couverture TLS wildcard
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
const nabilModeToggle = document.getElementById("nabil-mode-toggle");
|
const nabilModeToggle = document.getElementById("nabil-mode-toggle");
|
||||||
const tabSession = document.getElementById("tab-session");
|
const tabSession = document.getElementById("tab-session");
|
||||||
const tabFiles = document.getElementById("tab-files");
|
const tabFiles = document.getElementById("tab-files");
|
||||||
|
const sidebar = document.getElementById("hub-sidebar");
|
||||||
|
const toggleBtn = document.getElementById("btn-toggle-sidebar");
|
||||||
|
|
||||||
// Accent mappings
|
// Accent mappings
|
||||||
const ACCENT_MAP = {
|
const ACCENT_MAP = {
|
||||||
@@ -42,24 +44,24 @@
|
|||||||
function getTargetUrl(universeId, mode) {
|
function getTargetUrl(universeId, mode) {
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
|
|
||||||
// 1. Production *.hub.yesminedor.tn (Routage par sous-domaine natif)
|
// 1. Production *-hub.yesminedor.tn (Routage par sous-domaine tiret)
|
||||||
if (hostname === "hub.yesminedor.tn" || hostname.endsWith(".hub.yesminedor.tn")) {
|
if (hostname === "hub.yesminedor.tn" || hostname.endsWith(".yesminedor.tn")) {
|
||||||
if (universeId === "nabil" && mode === "files") {
|
if (universeId === "nabil" && mode === "files") {
|
||||||
return "https://dsh.hub.yesminedor.tn/";
|
return "https://dsh-hub.yesminedor.tn/";
|
||||||
}
|
}
|
||||||
return `https://${universeId}.hub.yesminedor.tn/`;
|
return `https://${universeId}-hub.yesminedor.tn/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Test local *.localhost
|
// 2. Test local *-hub.localhost ou *.localhost
|
||||||
if (hostname.endsWith(".localhost") || hostname === "localhost") {
|
if (hostname.endsWith(".localhost") || hostname === "localhost") {
|
||||||
const port = window.location.port ? `:${window.location.port}` : "";
|
const port = window.location.port ? `:${window.location.port}` : "";
|
||||||
if (universeId === "nabil" && mode === "files") {
|
if (universeId === "nabil" && mode === "files") {
|
||||||
return `${window.location.protocol}//dsh.localhost${port}/`;
|
return `${window.location.protocol}//dsh-hub.localhost${port}/`;
|
||||||
}
|
}
|
||||||
return `${window.location.protocol}//${universeId}.localhost${port}/`;
|
return `${window.location.protocol}//${universeId}-hub.localhost${port}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Fallback IP direct / chemin relatif (pour tests curl/SSH)
|
// 3. Fallback IP direct / chemin relatif (tests curl/SSH)
|
||||||
if (universeId === "nabil" && mode === "files") {
|
if (universeId === "nabil" && mode === "files") {
|
||||||
return "/u/nabil/files/";
|
return "/u/nabil/files/";
|
||||||
}
|
}
|
||||||
@@ -142,6 +144,22 @@
|
|||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sidebar Collapse / Expand Functionality
|
||||||
|
function setSidebarCollapsed(collapsed) {
|
||||||
|
if (!sidebar) return;
|
||||||
|
if (collapsed) {
|
||||||
|
sidebar.classList.add("collapsed");
|
||||||
|
toggleBtn?.setAttribute("aria-label", "Déplier la barre latérale");
|
||||||
|
toggleBtn?.setAttribute("title", "Déplier la barre latérale");
|
||||||
|
localStorage.setItem("hermes_hub_sidebar_collapsed", "true");
|
||||||
|
} else {
|
||||||
|
sidebar.classList.remove("collapsed");
|
||||||
|
toggleBtn?.setAttribute("aria-label", "Replier la barre latérale");
|
||||||
|
toggleBtn?.setAttribute("title", "Replier la barre latérale");
|
||||||
|
localStorage.setItem("hermes_hub_sidebar_collapsed", "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Écouter le chargement de l'iframe
|
// Écouter le chargement de l'iframe
|
||||||
if (iframe) {
|
if (iframe) {
|
||||||
iframe.addEventListener("load", () => {
|
iframe.addEventListener("load", () => {
|
||||||
@@ -262,6 +280,17 @@
|
|||||||
tabSession?.addEventListener("click", () => setMode("session"));
|
tabSession?.addEventListener("click", () => setMode("session"));
|
||||||
tabFiles?.addEventListener("click", () => setMode("files"));
|
tabFiles?.addEventListener("click", () => setMode("files"));
|
||||||
|
|
||||||
|
// Toggle Sidebar
|
||||||
|
toggleBtn?.addEventListener("click", () => {
|
||||||
|
const isCollapsed = sidebar?.classList.contains("collapsed");
|
||||||
|
setSidebarCollapsed(!isCollapsed);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore saved sidebar collapsed state
|
||||||
|
if (localStorage.getItem("hermes_hub_sidebar_collapsed") === "true") {
|
||||||
|
setSidebarCollapsed(true);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("btn-edit-persona")?.addEventListener("click", openPersonaModal);
|
document.getElementById("btn-edit-persona")?.addEventListener("click", openPersonaModal);
|
||||||
document.getElementById("btn-clone-universe")?.addEventListener("click", openCloneModal);
|
document.getElementById("btn-clone-universe")?.addEventListener("click", openCloneModal);
|
||||||
document.getElementById("btn-save-persona")?.addEventListener("click", savePersona);
|
document.getElementById("btn-save-persona")?.addEventListener("click", savePersona);
|
||||||
|
|||||||
Reference in New Issue
Block a user