M1.5 관리자 사이트: 슈퍼어드민/어드민 권한, 게임·사이트 설정, 방·사용자 관리, 기록
- 권한: 디스코드 ID로 요청마다 계산. 슈퍼어드민 293719842274541579(SUPERADMIN_DISCORD_IDS), 어드민은 슈퍼어드민이 디스코드 ID로 추가(로그인 전 미리 등록 가능), 고정 슈퍼어드민은 삭제 불가 - 게임 설정: 사용 여부, 표시 이름·설명·안내문, 원작 범위 안 인원, 모든 규칙 옵션 기본값, 옵션별 방장 변경 금지(잠금), 초기화. 입력 화면은 옵션 스키마(zod→JSON Schema)로 자동 생성 - 오목 옵션 전체에 한국어 제목·선택지 메타, 엔진 고정값(자동 착수 허용 횟수, 무승부 재제안 간격, 한 수 제한 초)을 옵션으로 꺼냄 - 사이트 설정: 이름, 공지, 점검 모드, 금지어, 방 수 제한, 연결 유예, 채팅 기본값 - 방 관리(목록·닫기), 사용자 관리(검색·닉네임 변경·이용 제한·강제 로그아웃·삭제, 슈퍼어드민 전용), 관리자 작업 기록(전/후 값) - 이용 제한: 세션 삭제 + 접속 종료 + 디스코드 재로그인 거부 - 테스트: 관리자 API 7개(권한·설정 반영·잠금·점검·금지어·방 닫기·이용 제한·기록), 오목 옵션 2개, e2e/admin.e2e.ts(슈퍼어드민 로그인→규칙 변경·잠금→어드민 추가→게스트 새 방 반영) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -335,8 +335,14 @@ function Options({ value: o, onChange }: OptionsProps<OmokOptions>) {
|
||||
<strong>시간</strong>
|
||||
<Seg
|
||||
value={timeKey(o.timeControl)}
|
||||
options={TIME_PRESETS.map(([k, l]) => [k, l] as [string, string])}
|
||||
onChange={(k) => set({ timeControl: { ...o.timeControl, ...TIME_PRESETS.find((p) => p[0] === k)![2] } })}
|
||||
options={[
|
||||
...TIME_PRESETS.map(([k, l]) => [k, l] as [string, string]),
|
||||
...(TIME_PRESETS.some((p) => p[0] === timeKey(o.timeControl)) ? [] : [[timeKey(o.timeControl), `${timeLabel(o.timeControl)}(관리자 설정)`] as [string, string]]),
|
||||
]}
|
||||
onChange={(k) => {
|
||||
const preset = TIME_PRESETS.find((p) => p[0] === k);
|
||||
if (preset) set({ timeControl: { ...o.timeControl, ...preset[2] } });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="stack" style={{ gap: 6 }}>
|
||||
@@ -388,8 +394,16 @@ function Options({ value: o, onChange }: OptionsProps<OmokOptions>) {
|
||||
);
|
||||
}
|
||||
|
||||
function timeLabel(tc: OmokOptions['timeControl']): string {
|
||||
if (tc.kind === 'none') return '시간 제한 없음';
|
||||
if (tc.kind === 'perMove') return `한 수 ${tc.perMoveSec}초`;
|
||||
const m = Math.floor(tc.baseSec / 60);
|
||||
const sec = tc.baseSec % 60;
|
||||
return `${m}분${sec ? ` ${sec}초` : ''} + ${tc.incSec}초`;
|
||||
}
|
||||
|
||||
function optionsSummary(o: OmokOptions): string {
|
||||
const t = TIME_PRESETS.find((p) => p[0] === timeKey(o.timeControl))?.[1] ?? '';
|
||||
const t = timeLabel(o.timeControl);
|
||||
return [RULE_KO[o.ruleSet], `${o.boardSize}줄`, t].join(' · ');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user