giftcon_dev/resources/views/admin/partials/dev_session_overlay.blade.php
2026-02-04 16:55:00 +09:00

205 lines
8.9 KiB
PHP

{{-- resources/views/admin/partials/dev_session_overlay.blade.php --}}
@php
// ✅ 개발 모드에서만 노출
$show = (config('app.debug') || app()->environment('local'));
// ✅ 관리자 도메인에서만 노출(실수로 web 도메인에 붙는 것 방지)
$adminHost = parse_url(env('APP_ADMIN_URL'), PHP_URL_HOST);
if ($adminHost) {
$show = $show && (request()->getHost() === $adminHost);
}
// ✅ 세션 전체
$sess = session()->all();
// ✅ admin은 기본 마스킹을 켜는 게 안전 (원하면 비워도 됨)
$maskKeys = [
'password','passwd','pw',
'token','access_token','refresh_token',
'api_key','secret','authorization',
'csrf','_token',
'g-recaptcha-response','recaptcha',
'otp','admin_otp',
'ci','di','phone','mobile','email',
'session','cookie',
];
$mask = function ($key, $val) use ($maskKeys) {
$k = strtolower((string)$key);
foreach ($maskKeys as $mk) {
if (str_contains($k, $mk)) return '***';
}
return $val;
};
// ✅ key:value 라인 생성(재귀)
$lines = [];
$dump = function ($data, $prefix = '') use (&$dump, &$lines, $mask) {
foreach ((array)$data as $k => $v) {
$key = $prefix . (string)$k;
if (is_array($v)) {
$lines[] = $key . ' : [';
$dump($v, $prefix . ' ');
$lines[] = $prefix . ']';
} else {
if (is_bool($v)) $v = $v ? 'true' : 'false';
if ($v === null) $v = 'null';
$vv = is_string($v) ? (mb_strlen($v) > 260 ? mb_substr($v, 0, 260) . '…' : $v) : (string)$v;
$vv = $mask($k, $vv);
$lines[] = $key . ' : ' . $vv;
}
}
};
$dump($sess);
$text = implode("\n", $lines);
// ✅ dev route 이름 (너가 만든 이름에 맞춰 사용)
// - 내가 권장했던 방식이면 admin.dev.session
$devRouteName = 'admin.dev.session';
@endphp
@if($show)
<div id="dev-session-overlay" style="
position: fixed; left: 12px; bottom: 12px; z-index: 999999;
width: 620px; max-width: calc(100vw - 24px);
background: rgba(10,10,10,.92); color: #eaeaea;
border: 1px solid rgba(255,255,255,.14);
border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.35);
font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
">
<div style="display:flex; align-items:center; justify-content:space-between; padding:10px 12px; gap:8px;">
<div style="font-weight:800;">
ADMIN SESSION
<span style="opacity:.65; font-weight:500;">({{ count($sess) }} keys)</span>
</div>
<div style="display:flex; gap:6px;">
<button type="button" id="devSessCopy" style="
border: 1px solid rgba(255,255,255,.18); background: rgba(255,255,255,.06);
color:#fff; padding:5px 10px; border-radius:10px; cursor:pointer;
">Copy</button>
<button type="button" id="devSessToggle" style="
border: 1px solid rgba(255,255,255,.18); background: rgba(255,255,255,.06);
color:#fff; padding:5px 10px; border-radius:10px; cursor:pointer;
">Hide</button>
</div>
</div>
<div id="devSessBody" style="padding:0 12px 12px 12px;">
<div style="opacity:.7; margin-bottom:8px;">
{{ request()->method() }} {{ request()->path() }}
</div>
{{-- Controls --}}
<div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:10px;">
<form method="POST" action="{{ route($devRouteName) }}" style="display:flex; gap:6px; align-items:center; margin:0;">
@csrf
<input type="hidden" name="_dev_sess_action" value="flush">
<input type="hidden" name="_dev_return" value="{{ url()->full() }}">
@if(env('DEV_LAB_TOKEN'))
<input type="hidden" name="_dev_token" value="{{ env('DEV_LAB_TOKEN') }}">
@endif
<button type="submit" style="
border: 1px solid rgba(255,90,90,.35);
background: rgba(255,90,90,.14);
color:#fff; padding:6px 10px; border-radius:10px; cursor:pointer;
" onclick="return confirm('세션을 초기화할까요? (dev 전용)');">세션 초기화</button>
</form>
<form method="POST" action="{{ route($devRouteName) }}" style="display:flex; gap:6px; align-items:center; margin:0; flex:1;">
@csrf
<input type="hidden" name="_dev_sess_action" value="put">
<input type="hidden" name="_dev_return" value="{{ url()->full() }}">
@if(env('DEV_LAB_TOKEN'))
<input type="hidden" name="_dev_token" value="{{ env('DEV_LAB_TOKEN') }}">
@endif
<input name="_dev_sess_key" placeholder="key (예: admin_otp.expires_at)" style="
flex: 0 0 240px; max-width: 45%;
border: 1px solid rgba(255,255,255,.16);
background: rgba(255,255,255,.06);
color:#fff; padding:6px 8px; border-radius:10px; outline:none;
">
<input name="_dev_sess_value" placeholder="value (기본은 문자열, route에서 파싱 가능)" style="
flex: 1 1 auto;
border: 1px solid rgba(255,255,255,.16);
background: rgba(255,255,255,.06);
color:#fff; padding:6px 8px; border-radius:10px; outline:none;
">
<button type="submit" style="
border: 1px solid rgba(90,180,255,.35);
background: rgba(90,180,255,.14);
color:#fff; padding:6px 10px; border-radius:10px; cursor:pointer;
">등록</button>
</form>
</div>
<div style="
padding:10px; border-radius:12px;
background: rgba(255,255,255,.05);
max-height: 360px; overflow:auto; white-space: pre;
border: 1px solid rgba(255,255,255,.10);
">
<pre id="devSessPre" style="margin:0;">{!! e($text) !!}</pre>
</div>
</div>
</div>
<script>
(function(){
const box = document.getElementById('dev-session-overlay');
const body = document.getElementById('devSessBody');
const toggle = document.getElementById('devSessToggle');
const copyBtn = document.getElementById('devSessCopy');
const pre = document.getElementById('devSessPre');
// 상태 기억 (도메인별 분리)
const key = 'devSessOverlayCollapsed:' + location.host;
const collapsed = localStorage.getItem(key) === '1';
if (collapsed) { body.style.display='none'; toggle.textContent='Show'; }
toggle.addEventListener('click', () => {
const isHidden = body.style.display === 'none';
body.style.display = isHidden ? '' : 'none';
toggle.textContent = isHidden ? 'Hide' : 'Show';
localStorage.setItem(key, isHidden ? '0' : '1');
});
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(pre.innerText);
copyBtn.textContent = 'Copied';
setTimeout(()=>copyBtn.textContent='Copy', 800);
} catch(e) {
alert('복사 실패(브라우저 권한 확인)');
}
});
// 드래그 이동
let dragging=false, sx=0, sy=0, ox=0, oy=0;
box.firstElementChild.style.cursor = 'move';
box.firstElementChild.addEventListener('mousedown', (e)=>{
dragging=true; sx=e.clientX; sy=e.clientY;
const r = box.getBoundingClientRect();
ox=r.left; oy=r.top;
box.style.right='auto'; box.style.bottom='auto';
document.body.style.userSelect='none';
});
window.addEventListener('mousemove', (e)=>{
if(!dragging) return;
const nx = ox + (e.clientX - sx);
const ny = oy + (e.clientY - sy);
box.style.left = Math.max(0, nx) + 'px';
box.style.top = Math.max(0, ny) + 'px';
});
window.addEventListener('mouseup', ()=>{
dragging=false;
document.body.style.userSelect='';
});
})();
</script>
@endif