Files
Gnuboard7/resources/views/admin.blade.php
T
HeuJung bfaf551d41 fix(engine): 페이지 로드 요청 1건의 일시 실패가 앱 전체를 죽이는 문제
모바일에서 새로고침을 연타하면 진행 중이던 요청이 취소되는데, 화면 구성에
필요한 요청 하나만 그렇게 끊겨도 전면 "초기화 실패" 로 끝났다. 재시도가 전혀
없었고, 로컬은 왕복이 수 ms 라 재현되지 않아 오래 남아 있었다.

fetch 는 4xx/5xx 로 reject 하지 않고 Response.ok=false 로 resolve 한다.
따라서 TypeError 는 "응답 자체가 없었다"(취소·커넥션 유실)는 뜻이며, 그 경우만
재시도해야 한다. HTTP 응답은 그대로 호출부에 넘겨 기존 상태코드 분기와 이중으로
겹치지 않게 했다.

- routes.json / components.json / 레이아웃 JSON: 네트워크 실패에만 지수 백오프 재시도
- 실패한 promise 가 레이아웃 캐시에 남아 영구 실패가 되던 문제 수정
- 문서 이탈(pagehide) 중에는 에러 화면을 그리지 않는다 (버려질 문서)
- 확장 에셋 로더의 실패 계약을 resolve → reject 로 (실패를 발생 지점에서 표면화)
- 번들 로드가 실패로 확정되면 오지 않을 핸들러를 5초간 기다리지 않는다
- 미등록 핸들러의 raw 내부 식별자가 토스트로 노출되던 것을 차단
- 번들이 끝내 부재해도 백지 대신 새로고침 버튼이 있는 안내 화면

정상 경로 성능은 종전과 같다. script 태그는 정적으로 유지해 프리로드 스캐너
이점을 보존하고, 실패했을 때만 onerror 에서 동적 재시도를 건다.
2026-07-12 15:39:07 +09:00

94 lines
5.2 KiB
PHP

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
{!! g7_meta_generator_tag() !!}
<title>{{ config('app.name', '그누보드7') }} - Admin</title>
<!-- 템플릿 외부 리소스 (template.json의 externals) -->
@include('partials.template-externals-head')
<!-- Fallback UI 스타일 -->
@if(empty($activeAdminTemplate))
@include('partials.error-fallback-styles')
@endif
<!-- 템플릿 컴포넌트 스타일 -->
@if(!empty($activeAdminTemplate))
<link rel="stylesheet" href="/api/templates/assets/{{ $activeAdminTemplate }}/css/components.css?v={{ $extensionCacheVersion }}">
@endif
</head>
<body>
<!-- React 렌더링 루트 -->
<div id="app" data-template-id="{{ $activeAdminTemplate ?? '' }}">
<!-- Progressive Enhancement: 템플릿 없음 Fallback UI -->
@if(empty($activeAdminTemplate))
@include('partials.error-fallback-ui')
@endif
</div>
@if(!empty($activeAdminTemplate))
<!-- G7 설정 전역 변수 -->
<script>
window.G7Config = {
settings: @json($frontendSettings ?? []),
plugins: @json($pluginSettings ?? []),
modules: @json($moduleSettings ?? []),
moduleAssets: @json($moduleAssets ?? []),
pluginAssets: @json($pluginAssets ?? []),
bundleUrls: @json($bundleUrls ?? null),
activeModules: @json($activeModulesMeta ?? []),
activePlugins: @json($activePluginsMeta ?? []),
appConfig: @json($appConfig ?? []),
// 레이아웃 편집기 lazy 번들 URL — `/admin/layout-editor/*` 진입 시에만 런타임
// <script> 주입으로 로드된다(초기 접속 payload 에 미포함). filemtime 캐시버스팅,
// 미빌드 상태 대비 file_exists 가드.
coreEditorAsset: '{{ asset('build/core/layout-editor.min.js') }}?v={{ file_exists(public_path('build/core/layout-editor.min.js')) ? filemtime(public_path('build/core/layout-editor.min.js')) : 0 }}',
// DevTools lazy 번들 URL — 디버그 모드에서만 런타임 <script> 주입으로 로드.
coreDevToolsAsset: '{{ asset('build/core/devtools.min.js') }}?v={{ file_exists(public_path('build/core/devtools.min.js')) ? filemtime(public_path('build/core/devtools.min.js')) : 0 }}',
// 확장(코어/모듈/플러그인) 캐시 버전 SSoT — install/activate/deactivate/update 시 bump.
// 클라이언트 fetch (`?v=`) 가 이 값을 동반해야 백엔드 `template.routes.{id}.v{N}`
// 키가 새 버전으로 전환되어 routes.json/lang 변경이 즉시 가시화된다.
// 미주입 시 클라이언트가 항상 `v0` 으로 호출 → `template:cache-clear` 가 v 와일드카드를
// 처리하지 못해 캐시가 영구 stale 되는 결함이 발생.
cache_version: {{ (int) ($extensionCacheVersion ?? 0) }}
};
@if(isset($errorCode) && isset($errorLayout))
// 에러 상태 정보 (503 의존성 미충족 등)
window.G7Error = {
code: {{ $errorCode }},
layout: '{{ $errorLayout }}',
data: @json($unmetDependencies ?? [])
};
@endif
</script>
@include('partials.template-externals-scripts', ['position' => 'before-core'])
{{-- 코어 엔진 + 템플릿 컴포넌트 번들 로드 → 초기화 (재시도 + 폴백 UI) --}}
@include('partials.bootstrap-scripts', [
'templateType' => 'admin',
'coreEngineSrc' => asset('build/core/template-engine.min.js') . '?v=' . filemtime(public_path('build/core/template-engine.min.js')),
'componentsSrc' => '/api/templates/assets/' . $activeAdminTemplate . '/js/components.iife.js?v=' . $extensionCacheVersion,
'initConfig' => array_filter([
'templateId' => $activeAdminTemplate,
'templateType' => 'admin',
'locale' => app()->getLocale(),
'debug' => (bool) config('app.debug'),
'websocket' => config('broadcasting.connections.reverb.key') ? [
'appKey' => config('broadcasting.connections.reverb.key'),
'host' => config('g7.websocket.client.host', config('broadcasting.connections.reverb.options.host', 'localhost')),
'port' => (int) config('g7.websocket.client.port', config('broadcasting.connections.reverb.options.port', 80)),
'scheme' => config('g7.websocket.client.scheme', config('broadcasting.connections.reverb.options.scheme', 'https')),
] : null,
], fn ($value) => $value !== null),
])
@include('partials.template-externals-scripts', ['position' => 'body-end'])
@endif
</body>
</html>