chore: test deploy snapshot

This commit is contained in:
2026-05-27 10:43:01 +09:00
parent c1d0f4c1db
commit 4c4b3c8d2c
78 changed files with 10392 additions and 2301 deletions

View File

@@ -1,10 +1,12 @@
import { DeleteOutlined, EditOutlined, PlusOutlined, SaveOutlined, UnorderedListOutlined } from '@ant-design/icons';
import { Alert, App, Button, Card, Checkbox, Descriptions, Empty, Form, Input, InputNumber, List, Modal, Space, Switch, Tabs, Tag, Typography } from 'antd';
import { Alert, App, Button, Card, Checkbox, Descriptions, Empty, Form, Input, InputNumber, List, Modal, Space, Switch, Table, Tabs, Tag, Typography } from 'antd';
import { useEffect, useMemo, useRef, useState } from 'react';
import { getReadyPlayAppEntries } from '../../views/play/apps/apps/appsRegistry';
import { confirmWithKeyboard } from './modalKeyboard';
import {
deleteTokenSetting,
fetchTokenSettingActivities,
type TokenSettingActivityRecord,
type TokenSettingRecord,
upsertTokenSetting,
useTokenSettingRegistry,
@@ -54,6 +56,7 @@ type SharedTokenSettingAccess = {
const MANAGEMENT_APP_OPTIONS: AppOption[] = [
{ value: 'chat-live', label: 'Codex Live', description: '채팅방 조회와 응답 흐름 진입', category: '관리' },
{ value: 'chat-room-settings', label: '채팅방 설정', description: 'Codex Live 채팅방 Context 설정 편집 접근', category: '관리' },
{ value: 'text-memo-widget', label: '메모', description: '공유채팅 Apps에서 메모 컴포넌트 실행', category: '관리' },
{ value: 'token-setting', label: '토큰관리 설정', description: '토큰 설정 목록과 상세 편집 접근', category: '관리' },
{ value: 'shared-resource', label: '공유 리소스 관리', description: '공유 링크와 권한, 활동 이력 관리', category: '관리' },
{ value: 'plan-board', label: '자동화 현황', description: '작업 현황과 요청 보드 접근', category: '관리' },
@@ -219,7 +222,9 @@ export function TokenSettingManagementPage({
const [isSaving, setIsSaving] = useState(false);
const [saveErrorMessage, setSaveErrorMessage] = useState('');
const [saveSuccessMessage, setSaveSuccessMessage] = useState('');
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'quota' | 'apps'>('basic');
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'quota' | 'apps' | 'history'>('basic');
const [activities, setActivities] = useState<TokenSettingActivityRecord[]>([]);
const [isActivityLoading, setIsActivityLoading] = useState(false);
const [form] = Form.useForm<TokenSettingFormValue>();
const [modalApi, modalContextHolder] = Modal.useModal();
const lastHydratedFormKeyRef = useRef('');
@@ -264,6 +269,83 @@ export function TokenSettingManagementPage({
form.setFieldsValue(toFormValue(isCreating ? null : effectiveSelectedTokenSetting));
}, [detailMode, effectiveSelectedTokenSetting?.id, form, isCreating]);
useEffect(() => {
if (detailMode !== 'detail' || isCreating || !effectiveSelectedTokenSetting?.id || isSharedPreviewMode) {
setActivities([]);
setIsActivityLoading(false);
return;
}
let cancelled = false;
setIsActivityLoading(true);
void fetchTokenSettingActivities(
effectiveSelectedTokenSetting.id,
isSharedManageMode ? { shareToken: sharedAccess?.shareToken } : undefined,
)
.then((nextItems) => {
if (!cancelled) {
setActivities(nextItems);
}
})
.catch(() => {
if (!cancelled) {
setActivities([]);
}
})
.finally(() => {
if (!cancelled) {
setIsActivityLoading(false);
}
});
return () => {
cancelled = true;
};
}, [detailMode, effectiveSelectedTokenSetting?.id, isCreating, isSharedManageMode, isSharedPreviewMode, sharedAccess?.shareToken]);
const activityColumns = useMemo(
() => [
{
title: '시각',
dataIndex: 'createdAt',
key: 'createdAt',
render: (value: string) => new Date(value).toLocaleString('ko-KR'),
},
{
title: '유형',
dataIndex: 'activityType',
key: 'activityType',
render: (value: TokenSettingActivityRecord['activityType']) => <Tag>{value}</Tag>,
},
{
title: '내용',
dataIndex: 'summary',
key: 'summary',
},
{
title: '변경 상세',
dataIndex: 'detail',
key: 'detail',
render: (value: string | null) => value || '-',
},
{
title: 'IP',
key: 'ip',
render: (_value: unknown, item: TokenSettingActivityRecord) => {
const lines = [
item.externalIp ? `외부 ${item.externalIp}` : null,
item.clientIp ? `서버 ${item.clientIp}` : null,
item.forwardedFor ? `XFF ${item.forwardedFor}` : null,
item.clientId ? `client ${item.clientId}` : null,
].filter(Boolean);
return lines.length > 0 ? <Text style={{ whiteSpace: 'pre-line' }}>{lines.join('\n')}</Text> : '-';
},
},
],
[],
);
const openCreateForm = () => {
setIsCreating(true);
setSelectedTokenSettingId(null);
@@ -522,7 +604,7 @@ export function TokenSettingManagementPage({
<div className="chat-type-management-page__editor-scroll">
<Tabs
activeKey={activeDetailTab}
onChange={(key) => setActiveDetailTab(key as 'basic' | 'quota' | 'apps')}
onChange={(key) => setActiveDetailTab(key as 'basic' | 'quota' | 'apps' | 'history')}
className="token-setting-management-page__detail-tabs"
items={[
{
@@ -680,6 +762,35 @@ export function TokenSettingManagementPage({
</div>
),
},
{
key: 'history',
label: '변경 이력',
children: (
<div className="token-setting-management-page__section-scroll">
{isCreating ? (
<Alert
showIcon
type="info"
message="신규 등록 전에는 변경 이력이 없습니다."
description="설정을 먼저 저장하면 이후 수정/삭제 이력과 IP 기록이 여기에 쌓입니다."
/>
) : isActivityLoading ? (
<Paragraph> ...</Paragraph>
) : activities.length > 0 ? (
<Table
size="small"
rowKey="id"
columns={activityColumns}
dataSource={activities}
pagination={{ pageSize: 8, hideOnSinglePage: true }}
scroll={{ x: 760 }}
/>
) : (
<Empty description="기록된 변경 이력이 없습니다." />
)}
</div>
),
},
]}
/>
</div>