Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions packages/web/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { GraphSelector } from './GraphSelector';
import { useAuth } from '../contexts/AuthContext';
import { McpHealthIndicator } from './McpHealthIndicator';
import FloatingConsole from './FloatingConsole';
import { TlsStatusIndicator, TlsSecurityBanner } from './TlsStatusIndicator';
import { InsecureConnectionBanner } from './TlsStatusIndicator';
import { APP_VERSION } from '../utils/version';

interface LayoutProps {
Expand All @@ -31,12 +31,16 @@ export function Layout({ children }: LayoutProps) {
];

return (
<div
className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 relative overflow-hidden"
<div
className="h-screen flex flex-col bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 relative overflow-hidden"
style={{
'--sidebar-width': desktopSidebarCollapsed ? '4rem' : '16rem'
} as React.CSSProperties}
>
{/* Insecure-connection warning — an in-flow strip at the very top, so it
reserves its own space and never overlaps the app (only over HTTP). */}
<InsecureConnectionBanner className="relative z-30" />

{/* Static gradient background - optimized for all browsers */}
<div className="lagoon-caustics"></div>

Expand All @@ -58,7 +62,7 @@ export function Layout({ children }: LayoutProps) {
</div>
</div>

<div className="flex">
<div className="flex flex-1 min-h-0">
{/* Sidebar */}
<div className={`
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
Expand Down Expand Up @@ -293,7 +297,7 @@ export function Layout({ children }: LayoutProps) {

{/* Main content */}
<div className="flex-1 flex flex-col min-w-0 relative z-20">
<main className="flex-1 select-none">
<main className="flex-1 select-none min-h-0">
{children}
</main>
</div>
Expand All @@ -305,9 +309,6 @@ export function Layout({ children }: LayoutProps) {
onToggle={() => setShowFloatingConsole(!showFloatingConsole)}
onClose={() => setShowFloatingConsole(false)}
/>

{/* TLS/SSL Status Indicator */}
<TlsStatusIndicator />
</div>
);
}
133 changes: 74 additions & 59 deletions packages/web/src/components/TlsStatusIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,87 @@
import React from 'react';
import { Shield, ShieldOff, AlertTriangle } from 'lucide-react';
import { createPortal } from 'react-dom';
import { ShieldOff, AlertTriangle, X } from 'lucide-react';

interface TlsStatusIndicatorProps {
className?: string;
}
const DISMISS_KEY = 'tlsBannerDismissed';

export function TlsStatusIndicator({ className = '' }: TlsStatusIndicatorProps) {
// Detect if we're running over HTTPS
function readConnection() {
const isSecure = window.location.protocol === 'https:';
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
const isLocalhost =
window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
return { isSecure, isLocalhost };
}

// Don't show anything if we're on HTTPS (secure)
if (isSecure) {
return null;
}
interface InsecureConnectionBannerProps {
/** Render as a fixed full-width strip pinned to the very top (for pages that
* have no app chrome to sit under, e.g. the auth screens). Default is an
* in-flow strip that pushes the content below it down. */
fixed?: boolean;
/** Called when the user dismisses the strip, so a parent can drop any layout
* offset it added to make room for the (fixed) banner. */
onDismiss?: () => void;
className?: string;
}

return (
<div className={`fixed top-4 right-4 z-50 ${className}`}>
<div className={`
flex items-center gap-2 px-3 py-2 rounded-lg shadow-lg border backdrop-blur-sm
${isLocalhost
? 'bg-yellow-500/90 border-yellow-600 text-yellow-900'
: 'bg-red-500/90 border-red-600 text-red-100'
}
`}>
{isLocalhost ? (
<>
<AlertTriangle size={16} />
<span className="text-sm font-medium">Development Mode (HTTP)</span>
</>
) : (
<>
<ShieldOff size={16} />
<span className="text-sm font-medium">Insecure Connection (HTTP)</span>
</>
)}
</div>
</div>
);
/** Whether the current connection should trigger an insecure-connection warning
* (i.e. not HTTPS). Lets a layout reserve space for the fixed banner. */
export function isInsecureConnection(): boolean {
return typeof window !== 'undefined' && window.location.protocol !== 'https:';
}

// For authenticated users, show a more prominent security status
export function TlsSecurityBanner({ className = '' }: TlsStatusIndicatorProps) {
const isSecure = window.location.protocol === 'https:';
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
/**
* A slim, dismissible warning strip shown ONLY when the connection is not
* encrypted (HTTP). It lives in the document flow (or pinned to the top edge
* for chrome-less pages) instead of floating in a corner, so it never overlaps
* the rest of the UI. Dismissal is remembered for the browser session.
*/
export function InsecureConnectionBanner({ fixed = false, onDismiss, className = '' }: InsecureConnectionBannerProps) {
const { isSecure, isLocalhost } = readConnection();
const [dismissed, setDismissed] = React.useState(
() => typeof sessionStorage !== 'undefined' && sessionStorage.getItem(DISMISS_KEY) === '1'
);

if (isSecure) {
return (
<div className={`flex items-center gap-2 ${className}`}>
<Shield size={14} className="text-green-400" />
<span className="text-xs text-green-400">Secure Connection</span>
</div>
);
}
// Nothing to warn about on HTTPS, or once the user has dismissed it.
if (isSecure || dismissed) return null;

if (isLocalhost) {
return (
<div className={`flex items-center gap-2 ${className}`}>
<AlertTriangle size={14} className="text-yellow-400" />
<span className="text-xs text-yellow-400">Development Mode</span>
</div>
);
}
const dismiss = () => {
try {
sessionStorage.setItem(DISMISS_KEY, '1');
} catch {
/* storage may be unavailable; dismissing for this mount is enough */
}
setDismissed(true);
onDismiss?.();
};

return (
<div className={`flex items-center gap-2 ${className}`}>
<ShieldOff size={14} className="text-red-400" />
<span className="text-xs text-red-400">Insecure Connection</span>
const tone = isLocalhost
? 'bg-yellow-500/15 border-yellow-600/40 text-yellow-200'
: 'bg-red-500/15 border-red-600/40 text-red-200';
const position = fixed ? 'fixed top-0 inset-x-0 z-[60]' : 'w-full';

const strip = (
<div
role="status"
data-testid="insecure-connection-banner"
className={`${position} flex items-center justify-center gap-2 border-b px-4 py-1.5 text-xs sm:text-sm backdrop-blur-sm ${tone} ${className}`}
>
{isLocalhost ? <AlertTriangle size={14} className="flex-shrink-0" /> : <ShieldOff size={14} className="flex-shrink-0" />}
<span className="text-center">
{isLocalhost
? 'Development mode — this connection is not encrypted (HTTP).'
: 'Insecure connection — this site is served over HTTP, not HTTPS.'}
</span>
<button
onClick={dismiss}
title="Dismiss"
aria-label="Dismiss insecure-connection warning"
className="ml-1 flex-shrink-0 rounded p-0.5 opacity-70 transition-opacity hover:opacity-100 hover:bg-white/10"
>
<X size={14} />
</button>
</div>
);
}

// When pinned, portal to <body> so a transformed/blur ancestor (route
// transitions, backdrop-filter) can't turn `fixed` into a clipped/offset box.
return fixed && typeof document !== 'undefined' ? createPortal(strip, document.body) : strip;
}
2 changes: 1 addition & 1 deletion packages/web/src/pages/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function Admin() {
// Redirect if not ADMIN
if (currentUser?.role !== 'ADMIN') {
return (
<div className="h-screen flex items-center justify-center">
<div className="h-full flex items-center justify-center">
<div className="text-center">
<Shield className="h-16 w-16 text-red-400 mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-100 mb-2">Access Denied</h1>
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/pages/Agents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export function Agents() {
const totalActiveCount = activeAgents.length + activeMcpServers.length;

return (
<div className="h-screen flex flex-col">
<div className="h-full flex flex-col">
{/* Header */}
<div className="bg-gray-900/30 backdrop-blur-md border-b border-gray-700/30 px-6 py-4">
<div className="flex items-center justify-between">
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/pages/Analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function Analytics() {
];

return (
<div className="h-screen flex flex-col">
<div className="h-full flex flex-col">
{/* Header */}
<div className="bg-gray-900/30 backdrop-blur-md border-b border-gray-700/30 px-6 py-4">
<div className="flex items-center justify-between">
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/pages/Backend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ export function Backend() {
};

return (
<div className="h-screen flex flex-col">
<div className="h-full flex flex-col">
{/* Header */}
<div className="bg-gray-900/30 backdrop-blur-md border-b border-gray-700/30 px-6 py-4">
<div className="flex items-center justify-between">
Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/pages/ForgotPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Mail, ArrowLeft, CheckCircle, XCircle, Shield } from 'lucide-react';
import { TlsStatusIndicator } from '../components/TlsStatusIndicator';
import { InsecureConnectionBanner } from '../components/TlsStatusIndicator';
import { CodeCaptcha } from '../components/CodeCaptcha';
import { isValidEmail } from '../utils/validation';

Expand Down Expand Up @@ -246,8 +246,8 @@ export function ForgotPassword() {
</div>
</div>

{/* TLS/SSL Status Indicator */}
<TlsStatusIndicator />
{/* Insecure-connection warning (top strip, only over HTTP) */}
<InsecureConnectionBanner fixed />
</div>
);
}
2 changes: 1 addition & 1 deletion packages/web/src/pages/Ontology.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function Ontology() {
};

return (
<div className="h-screen flex flex-col">
<div className="h-full flex flex-col">
{/* Header */}
<div className="bg-gray-900/30 backdrop-blur-md border-b border-gray-700/30 px-6 py-4">
<div className="flex items-center justify-between">
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/pages/ResetPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useSearchParams, useNavigate, Link } from 'react-router-dom';
import { Lock, Eye, EyeOff, CheckCircle, XCircle, ArrowLeft } from 'lucide-react';
import { TlsStatusIndicator } from '../components/TlsStatusIndicator';
import { InsecureConnectionBanner } from '../components/TlsStatusIndicator';
import { CodeCaptcha } from '../components/CodeCaptcha';
import { PasswordRequirements } from '../components/PasswordRequirements';
import { validatePassword, getPasswordStrength } from '../utils/validation';
Expand Down Expand Up @@ -255,7 +255,7 @@ export function ResetPassword() {
</div>
</div>

<TlsStatusIndicator />
<InsecureConnectionBanner fixed />
</div>
);
}
2 changes: 1 addition & 1 deletion packages/web/src/pages/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Settings() {
const { tier, override, setOverride } = useAdaptiveQuality();

return (
<div className="h-screen flex flex-col">
<div className="h-full flex flex-col">
{/* Header */}
<div className="bg-gray-900/30 backdrop-blur-md border-b border-gray-700/30 px-6 py-4">
<div className="flex items-center justify-between">
Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/pages/Signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { useMutation, useQuery, gql } from '@apollo/client';
import { Eye, EyeOff, ArrowRight, Mail, Lock, Users, Github, Zap, Check, CheckCircle, XCircle, AlertTriangle, Shield } from 'lucide-react';
import { useAuth } from '../contexts/AuthContext';
import { TlsStatusIndicator } from '../components/TlsStatusIndicator';
import { InsecureConnectionBanner } from '../components/TlsStatusIndicator';
import { GuestModeDialog } from '../components/GuestModeDialog';
import { PasswordRequirements } from '../components/PasswordRequirements';
import { isValidEmail } from '../utils/validation';
Expand Down Expand Up @@ -992,8 +992,8 @@ export function Signin() {

</div>

{/* TLS/SSL Status Indicator */}
<TlsStatusIndicator />
{/* Insecure-connection warning (top strip, only over HTTP) */}
<InsecureConnectionBanner fixed />
</div>
);
}
6 changes: 3 additions & 3 deletions packages/web/src/pages/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useMutation, gql } from '@apollo/client';
import { Eye, EyeOff, ArrowRight, CheckCircle, XCircle, Github, Mail, Info, Shield } from 'lucide-react';
import { TlsStatusIndicator } from '../components/TlsStatusIndicator';
import { InsecureConnectionBanner } from '../components/TlsStatusIndicator';
import { PasswordRequirements } from '../components/PasswordRequirements';
import { isValidEmail, getPasswordStrength } from '../utils/validation';
import { CodeCaptcha } from '../components/CodeCaptcha';
Expand Down Expand Up @@ -729,8 +729,8 @@ export function Signup() {
)}
</div>

{/* TLS/SSL Status Indicator */}
<TlsStatusIndicator />
{/* Insecure-connection warning (top strip, only over HTTP) */}
<InsecureConnectionBanner fixed />
</div>
);
}
2 changes: 1 addition & 1 deletion packages/web/src/pages/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function Workspace() {
const actualEdgeCount = edgesData?.edges?.length || 0;

return (
<div className="h-screen flex flex-col">
<div className="h-full flex flex-col">
{/* Header with Graph Context */}
<div className="bg-gray-900/30 backdrop-blur-md border-b border-gray-700/30 px-6 py-4">
{/* Responsive Layout Container */}
Expand Down
Loading
Loading