@@ -420,15 +420,13 @@ def _offline_html_response() -> Response:
420420 padding: 2rem;
421421 }
422422
423- /* Terminal window */
424423 .terminal {
425424 width: 100%;
426425 max-width: 580px;
427426 border: 2px solid #374151;
428427 border-radius: 2px;
429428 }
430429
431- /* Title bar */
432430 .titlebar {
433431 background: #1f2937;
434432 padding: 0.5rem 0.75rem;
@@ -438,7 +436,6 @@ def _offline_html_response() -> Response:
438436 border-bottom: 1px solid #374151;
439437 }
440438
441- /* Square traffic-light buttons (not circles) */
442439 .btn { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; }
443440 .btn-red { background: #ef4444; }
444441 .btn-yellow { background: #eab308; }
@@ -449,47 +446,22 @@ def _offline_html_response() -> Response:
449446 text-align: center;
450447 font-size: 0.75rem;
451448 color: #6b7280;
452- margin-right: 36px; /* offset for buttons width so label is visually centred */
449+ margin-right: 36px;
453450 }
454451
455- /* Terminal body */
456452 .body {
457453 background: #111827;
458454 padding: 1.5rem;
459455 min-height: 200px;
460456 font-size: 0.875rem;
461457 color: #d1d5db;
462- }
463-
464- /* History lines (completed) */
465- .history { margin-bottom: 0.25rem; }
466- .history .prompt { color: #10b981; }
467- .history .text { color: #6b7280; }
468-
469- /* Active typing line */
470- .active {
471458 display: flex;
472- align-items: center ;
459+ align-items: flex-start ;
473460 gap: 0.375rem;
474461 }
462+
475463 .prompt { color: #10b981; user-select: none; }
476- #typed { color: #d1d5db; }
477-
478- /* Block cursor */
479- .cursor {
480- display: inline-block;
481- width: 8px;
482- height: 1.1em;
483- background: #10b981;
484- vertical-align: text-bottom;
485- animation: blink 1s step-start infinite;
486- }
487- @keyframes blink {
488- 0%, 100% { opacity: 1; }
489- 50% { opacity: 0; }
490- }
491464
492- /* Tagline */
493465 .tagline {
494466 font-size: 0.8125rem;
495467 color: #4b5563;
@@ -506,61 +478,11 @@ def _offline_html_response() -> Response:
506478 <span class="title-label">crafting_table</span>
507479 </div>
508480 <div class="body">
509- <div id="history"></div>
510- <div class="active">
511- <span class="prompt">/></span>
512- <span id="typed"></span><span class="cursor"></span>
513- </div>
481+ <span class="prompt">/></span>
482+ <span>Sandbox is asleep...</span>
514483 </div>
515484 </div>
516485 <p class="tagline">Ask the owner to open their Craft session to wake it up.</p>
517-
518- <script>
519- var messages = [
520- "Sandbox is asleep...",
521- "Waiting for owner to wake it up...",
522- "Ask the owner to open their Craft session.",
523- "This page will refresh automatically.",
524- "/status: idle"
525- ];
526-
527- var msgIndex = 0;
528- var charIndex = 0;
529- var typedEl = document.getElementById("typed");
530- var historyEl = document.getElementById("history");
531- var HISTORY_MAX = 3;
532- var history = [];
533-
534- function addHistory(text) {
535- history.push(text);
536- if (history.length > HISTORY_MAX) history.shift();
537- historyEl.innerHTML = history.map(function(t) {
538- return '<div class="history"><span class="prompt">/> </span><span class="text">' +
539- t.replace(/&/g,"&").replace(/</g,"<") + "</span></div>";
540- }).join("");
541- }
542-
543- function typeChar() {
544- var msg = messages[msgIndex];
545- if (charIndex < msg.length) {
546- typedEl.textContent += msg[charIndex];
547- charIndex++;
548- setTimeout(typeChar, 55 + Math.random() * 40);
549- } else {
550- setTimeout(nextMessage, 1600);
551- }
552- }
553-
554- function nextMessage() {
555- addHistory(messages[msgIndex]);
556- msgIndex = (msgIndex + 1) % messages.length;
557- charIndex = 0;
558- typedEl.textContent = "";
559- setTimeout(typeChar, 300);
560- }
561-
562- typeChar();
563- </script>
564486</body>
565487</html>"""
566488 return Response (content = html , status_code = 503 , media_type = "text/html" )
@@ -616,49 +538,6 @@ def get_webapp_path(
616538 raise
617539
618540
619- # Separate router for Next.js static assets at /_next/*
620- # This is needed because Next.js apps may reference assets with root-relative paths
621- # that don't get rewritten. The session_id is extracted from the Referer header.
622- nextjs_assets_router = APIRouter ()
623-
624-
625- def _extract_session_from_referer (request : Request ) -> UUID | None :
626- """Extract session_id from the Referer header.
627-
628- Expects Referer to contain /api/build/sessions/{session_id}/webapp
629- """
630- import re
631-
632- referer = request .headers .get ("referer" , "" )
633- match = re .search (r"/api/build/sessions/([a-f0-9-]+)/webapp" , referer )
634- if match :
635- try :
636- return UUID (match .group (1 ))
637- except ValueError :
638- return None
639- return None
640-
641-
642- @nextjs_assets_router .get ("/_next/{path:path}" , response_model = None )
643- def get_nextjs_assets (
644- path : str ,
645- request : Request ,
646- user : User | None = Depends (optional_user ),
647- db_session : Session = Depends (get_session ),
648- ) -> StreamingResponse | Response :
649- """Proxy Next.js static assets at root /_next/ path.
650- Session is determined from the Referer header.
651- """
652- session_id = _extract_session_from_referer (request )
653- if not session_id :
654- raise HTTPException (
655- status_code = 400 ,
656- detail = "Could not determine session from request context" ,
657- )
658- _check_webapp_access (session_id , user , db_session )
659- return _proxy_request (f"_next/{ path } " , request , session_id , db_session )
660-
661-
662541# =============================================================================
663542# Sandbox Management Endpoints
664543# =============================================================================
0 commit comments