런처 관리자 바로가기 정리

This commit is contained in:
seo
2026-07-02 19:05:22 +09:00
parent 61c0f52564
commit e47a42462a
2 changed files with 63 additions and 15 deletions
+3 -1
View File
@@ -37,7 +37,9 @@
## 운영 메모 ## 운영 메모
메인 버튼은 기존 4개를 유지합니다. Rhymix 사이트 관리자 로그인 상태에서는 미니앱 실행 시 공통 암호를 묻지 않습니다. 이때 `/custom/launcher/api.php?adminSession=1` 또는 `session=1` 응답에서 관리자 공통 접근 쿠키를 함께 발급해, 이동한 미니앱 내부 세션 검사에서도 바로 통과합니다. 일반 사용자 화면은 나의 찾기, 콕 찌르기, 니모, GIT 4개 버튼만 표시합니다. PC 켜기 항목은 운영 코드에 주석으로 보존해 두고 화면에서는 제외합니다.
Rhymix 사이트 관리자 로그인 상태에서는 나의 찾기, 콕 찌르기, 니모, 프로젝트, Block, Notice, Weblog, Speed Stats를 4개씩 2줄로 표시합니다. 관리자 상태에서 미니앱을 실행하면 공통 암호를 묻지 않습니다. 이때 `/custom/launcher/api.php?adminSession=1` 또는 `session=1` 응답에서 관리자 공통 접근 쿠키를 함께 발급해, 이동한 미니앱 내부 세션 검사에서도 바로 통과합니다.
공통 암호로 로그인한 일반 인증 쿠키는 30분만 유지합니다. 사이트 관리자 세션이 확인되어 발급된 관리자 인증 쿠키는 반영구 운영값으로 10년 유지합니다. `/custom/launcher/api.php?session=1`은 현재 브라우저가 가진 공통 쿠키 또는 사이트 관리자 세션을 확인하고, `/custom/launcher/api.php?adminSession=1`은 관리 버튼 노출처럼 실제 Rhymix 관리자 세션이 필요한 화면에서만 사용합니다. 공통 암호로 로그인한 일반 인증 쿠키는 30분만 유지합니다. 사이트 관리자 세션이 확인되어 발급된 관리자 인증 쿠키는 반영구 운영값으로 10년 유지합니다. `/custom/launcher/api.php?session=1`은 현재 브라우저가 가진 공통 쿠키 또는 사이트 관리자 세션을 확인하고, `/custom/launcher/api.php?adminSession=1`은 관리 버튼 노출처럼 실제 Rhymix 관리자 세션이 필요한 화면에서만 사용합니다.
+60 -14
View File
@@ -4,19 +4,21 @@ let failIndex = 0;
const apiUrl = '/custom/launcher/api.php'; const apiUrl = '/custom/launcher/api.php';
const PIN_LENGTH = 4; const PIN_LENGTH = 4;
const iconItems = [ const userIconItems = [
{ {
id: 'findmydevice', id: 'findmydevice',
img: '/custom/launcher/img/find-my.svg', img: '/custom/launcher/img/find-my.svg',
label: '나의 찾기', label: '나의 찾기',
onclick: () => requireAccess(() => location.href = '/findmydevice') onclick: () => requireAccess(() => location.href = '/findmydevice')
}, },
/*
{ {
id: 'wakeonlan', id: 'wakeonlan',
img: '/custom/launcher/img/wakeonlan-svgrepo-com.svg', img: '/custom/launcher/img/wakeonlan-svgrepo-com.svg',
label: 'PC 켜기', label: 'PC 켜기',
onclick: () => requireAccess(wakeComputer) onclick: () => requireAccess(wakeComputer)
}, },
*/
{ {
id: 'poke', id: 'poke',
img: '/custom/launcher/img/R1280x0.jpg', img: '/custom/launcher/img/R1280x0.jpg',
@@ -28,14 +30,59 @@ const iconItems = [
img: '/custom/launcher/img/car.png', img: '/custom/launcher/img/car.png',
label: '니모', label: '니모',
onclick: () => requireAccess(() => location.href = 'https://seo.chaegeon.com/car/monitor.php') onclick: () => requireAccess(() => location.href = 'https://seo.chaegeon.com/car/monitor.php')
},
{
id: 'git',
img: 'https://git.chaegeon.com/assets/img/favicon.svg',
label: 'GIT',
onclick: () => requireAccess(() => location.href = 'https://git.chaegeon.com')
}
];
const adminIconItems = [
userIconItems[0],
userIconItems[1],
userIconItems[2],
{
id: 'project',
img: 'https://git.chaegeon.com/assets/img/favicon.svg',
label: '프로젝트',
onclick: () => requireAccess(() => location.href = 'https://git.chaegeon.com')
},
{
id: 'block',
img: '/admin/favicon/block/favicon.svg',
label: 'Block',
onclick: () => requireAccess(() => location.href = 'https://chaegeon.com/admin/index.php')
},
{
id: 'notice',
img: '/admin/favicon/notice/favicon.svg',
label: 'Notice',
onclick: () => requireAccess(() => location.href = 'https://chaegeon.com/admin/notice.php')
},
{
id: 'weblog',
img: '/weblog/favicon.svg',
label: 'Weblog',
onclick: () => requireAccess(() => location.href = 'https://chaegeon.com/weblog/index.php')
},
{
id: 'speedstats',
img: '/custom/speedtest/favicon.ico',
label: 'Speed Stats',
onclick: () => requireAccess(() => location.href = 'https://chaegeon.com/custom/speedtest/results/stats.php')
} }
]; ];
async function renderIcons() { async function renderIcons() {
shortcut.className = 'find-container'; const session = await getSession();
const isAdmin = !!session.admin;
const items = isAdmin ? adminIconItems : userIconItems;
shortcut.className = `find-container ${isAdmin ? 'launcher-admin-grid' : ''}`;
shortcut.innerHTML = ''; shortcut.innerHTML = '';
iconItems.forEach(item => { items.forEach(item => {
const div = document.createElement('div'); const div = document.createElement('div');
div.className = 'iconContainer'; div.className = 'iconContainer';
div.id = item.id; div.id = item.id;
@@ -54,7 +101,7 @@ async function renderIcons() {
shortcut.appendChild(div); shortcut.appendChild(div);
}); });
if (!await hasAdminSession()) { if (!isAdmin) {
return; return;
} }
@@ -67,23 +114,22 @@ async function renderIcons() {
} }
async function hasAdminSession() { async function hasAdminSession() {
try { const session = await getSession();
const session = await fetch(`${apiUrl}?adminSession=1`, { credentials: 'same-origin' }).then(res => res.json());
return !!session.admin; return !!session.admin;
}
async function getSession() {
try {
return await fetch(`${apiUrl}?session=1`, { credentials: 'same-origin' }).then(res => res.json());
} catch (_) { } catch (_) {
return false; return { authenticated: false, admin: false };
} }
} }
async function requireAccess(nextAction) { async function requireAccess(nextAction) {
try { try {
if (await hasAdminSession()) { const session = await getSession();
nextAction && nextAction(); if (session.admin || session.authenticated) {
return;
}
const session = await fetch(`${apiUrl}?session=1`, { credentials: 'same-origin' }).then(res => res.json());
if (session.authenticated) {
nextAction && nextAction(); nextAction && nextAction();
return; return;
} }