feat: update codex live runtime and restart flow
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Drawer,
|
||||
Dropdown,
|
||||
Grid,
|
||||
@@ -869,6 +870,7 @@ export function MainHeader({
|
||||
void contentExpanded;
|
||||
void onToggleContentExpanded;
|
||||
const screens = useBreakpoint();
|
||||
const [modalApi, modalContextHolder] = Modal.useModal();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
@@ -911,9 +913,12 @@ export function MainHeader({
|
||||
const [clientResetFeedback, setClientResetFeedback] = useState<InlineFeedback | null>(null);
|
||||
const [clientResetCopyFeedback, setClientResetCopyFeedback] = useState<InlineFeedback | null>(null);
|
||||
const [testServerStatus, setTestServerStatus] = useState<ServerCommandItem | null>(null);
|
||||
const [prodServerStatus, setProdServerStatus] = useState<ServerCommandItem | null>(null);
|
||||
const [workServerStatus, setWorkServerStatus] = useState<ServerCommandItem | null>(null);
|
||||
const [workServerStatusLoading, setWorkServerStatusLoading] = useState(false);
|
||||
const [serverRestartingKey, setServerRestartingKey] = useState<'test' | 'work-server' | 'all' | null>(null);
|
||||
const [serverRestartingKey, setServerRestartingKey] = useState<
|
||||
'test' | 'prod' | 'work-server' | 'command-runner' | 'all' | null
|
||||
>(null);
|
||||
const [serverRestartFeedback, setServerRestartFeedback] = useState<InlineFeedback | null>(null);
|
||||
const [serverRestartCopyFeedback, setServerRestartCopyFeedback] = useState<InlineFeedback | null>(null);
|
||||
const { registeredToken, hasAccess } = useTokenAccess();
|
||||
@@ -941,9 +946,11 @@ export function MainHeader({
|
||||
: 'app-header__status-dot--inactive';
|
||||
const testServerPendingUpdateCount =
|
||||
testServerStatus && (testServerStatus.updateAvailable || testServerStatus.buildRequired) ? 1 : 0;
|
||||
const prodServerPendingUpdateCount =
|
||||
prodServerStatus && (prodServerStatus.updateAvailable || prodServerStatus.buildRequired) ? 1 : 0;
|
||||
const workServerPendingUpdateCount =
|
||||
workServerStatus && (workServerStatus.updateAvailable || workServerStatus.buildRequired) ? 1 : 0;
|
||||
const totalPendingUpdateCount = testServerPendingUpdateCount + workServerPendingUpdateCount;
|
||||
const totalPendingUpdateCount = testServerPendingUpdateCount + prodServerPendingUpdateCount + workServerPendingUpdateCount;
|
||||
const settingsStatusClassName =
|
||||
totalPendingUpdateCount >= 2
|
||||
? 'app-header__status-dot--inactive'
|
||||
@@ -989,6 +996,8 @@ export function MainHeader({
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
searchParams.set('topMenu', 'chat');
|
||||
searchParams.set('sessionId', sessionId);
|
||||
searchParams.delete('chatView');
|
||||
searchParams.delete('runtimeRequestId');
|
||||
navigate({
|
||||
pathname: buildChatPath('live'),
|
||||
search: `?${searchParams.toString()}`,
|
||||
@@ -1482,18 +1491,22 @@ export function MainHeader({
|
||||
const refreshServerStatuses = async () => {
|
||||
const items = await fetchServerCommands();
|
||||
const nextTestServerStatus = items.find((item) => item.key === 'test') ?? null;
|
||||
const nextProdServerStatus = items.find((item) => item.key === 'prod') ?? null;
|
||||
const nextWorkServerStatus = items.find((item) => item.key === 'work-server') ?? null;
|
||||
setTestServerStatus(nextTestServerStatus);
|
||||
setProdServerStatus(nextProdServerStatus);
|
||||
setWorkServerStatus(nextWorkServerStatus);
|
||||
return {
|
||||
test: nextTestServerStatus,
|
||||
prod: nextProdServerStatus,
|
||||
'work-server': nextWorkServerStatus,
|
||||
} satisfies Record<'test' | 'work-server', ServerCommandItem | null>;
|
||||
} satisfies Record<'test' | 'prod' | 'work-server', ServerCommandItem | null>;
|
||||
};
|
||||
|
||||
const refreshUpdateTargets = async (silent = false) => {
|
||||
if (!hasAccess) {
|
||||
setTestServerStatus(null);
|
||||
setProdServerStatus(null);
|
||||
setWorkServerStatus(null);
|
||||
if (!silent) {
|
||||
setUpdateCheckFeedback({ tone: 'warning', message: '업데이트 확인은 권한 토큰 등록 후 사용할 수 있습니다.' });
|
||||
@@ -1515,7 +1528,7 @@ export function MainHeader({
|
||||
if (!silent) {
|
||||
setUpdateCheckFeedback({
|
||||
tone: 'error',
|
||||
message: error instanceof Error ? error.message : 'TEST/WORK 서버 업데이트 상태를 불러오지 못했습니다.',
|
||||
message: error instanceof Error ? error.message : 'TEST/PROD/WORK 서버 업데이트 상태를 불러오지 못했습니다.',
|
||||
});
|
||||
}
|
||||
return null;
|
||||
@@ -1524,7 +1537,7 @@ export function MainHeader({
|
||||
}
|
||||
};
|
||||
|
||||
const waitForServerRestart = async (key: 'test' | 'work-server', baseline: ServerCommandItem | null) => {
|
||||
const waitForServerRestart = async (key: 'test' | 'prod' | 'work-server', baseline: ServerCommandItem | null) => {
|
||||
for (let attempt = 0; attempt < 16; attempt += 1) {
|
||||
await waitForDuration(2500);
|
||||
|
||||
@@ -1549,7 +1562,10 @@ export function MainHeader({
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: false, item: key === 'test' ? testServerStatus : workServerStatus };
|
||||
return {
|
||||
ok: false,
|
||||
item: key === 'test' ? testServerStatus : key === 'prod' ? prodServerStatus : workServerStatus,
|
||||
};
|
||||
};
|
||||
|
||||
const handleResetClientState = async () => {
|
||||
@@ -1589,14 +1605,19 @@ export function MainHeader({
|
||||
}
|
||||
};
|
||||
|
||||
const restartServerWithVerification = async (key: 'test' | 'work-server', busyKey: 'test' | 'work-server' | 'all') => {
|
||||
const baseline = key === 'test' ? testServerStatus : workServerStatus;
|
||||
const targetLabel = key === 'test' ? 'TEST 서버' : 'WORK 서버';
|
||||
const restartServerWithVerification = async (
|
||||
key: 'test' | 'prod' | 'work-server',
|
||||
busyKey: 'test' | 'prod' | 'work-server' | 'all',
|
||||
) => {
|
||||
const baseline = key === 'test' ? testServerStatus : key === 'prod' ? prodServerStatus : workServerStatus;
|
||||
const targetLabel = key === 'test' ? 'TEST 서버' : key === 'prod' ? 'PROD 컨테이너' : 'WORK 서버';
|
||||
|
||||
const result = await restartServerCommand(key);
|
||||
|
||||
if (key === 'test') {
|
||||
setTestServerStatus(result.item);
|
||||
} else if (key === 'prod') {
|
||||
setProdServerStatus(result.item);
|
||||
} else {
|
||||
setWorkServerStatus(result.item);
|
||||
}
|
||||
@@ -1627,7 +1648,7 @@ export function MainHeader({
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleRestartSingleServer = async (key: 'test' | 'work-server') => {
|
||||
const handleRestartSingleServer = async (key: 'test' | 'prod' | 'work-server') => {
|
||||
if (!hasAccess || serverRestartingKey) {
|
||||
return false;
|
||||
}
|
||||
@@ -1639,7 +1660,7 @@ export function MainHeader({
|
||||
try {
|
||||
return await restartServerWithVerification(key, key);
|
||||
} catch (error) {
|
||||
const targetLabel = key === 'test' ? 'TEST 서버' : 'WORK 서버';
|
||||
const targetLabel = key === 'test' ? 'TEST 서버' : key === 'prod' ? 'PROD 컨테이너' : 'WORK 서버';
|
||||
setServerRestartFeedback({
|
||||
tone: 'error',
|
||||
message: error instanceof Error ? error.message : `${targetLabel} 재기동에 실패했습니다.`,
|
||||
@@ -1650,6 +1671,67 @@ export function MainHeader({
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmRestartProdServer = () => {
|
||||
if (!hasAccess || serverRestartingKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
modalApi.confirm({
|
||||
title: 'PROD 빌드 반영',
|
||||
content: 'PROD 컨테이너를 빌드 후 재기동합니다. 진행할까요?',
|
||||
okText: '빌드 및 재기동',
|
||||
cancelText: '취소',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
await handleRestartSingleServer('prod');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestartCommandRunner = async () => {
|
||||
if (!hasAccess || serverRestartingKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
setServerRestartCopyFeedback(null);
|
||||
setServerRestartFeedback(null);
|
||||
setServerRestartingKey('command-runner');
|
||||
|
||||
try {
|
||||
const result = await restartServerCommand('command-runner');
|
||||
setServerRestartFeedback({
|
||||
tone: 'success',
|
||||
message:
|
||||
result.restartState === 'accepted'
|
||||
? 'Command runner 배포 및 재기동 요청을 접수했습니다.'
|
||||
: 'Command runner 배포 및 재기동을 완료했습니다.',
|
||||
});
|
||||
} catch (error) {
|
||||
setServerRestartFeedback({
|
||||
tone: 'error',
|
||||
message: error instanceof Error ? error.message : 'Command runner 배포 및 재기동에 실패했습니다.',
|
||||
});
|
||||
} finally {
|
||||
setServerRestartingKey(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmRestartCommandRunner = () => {
|
||||
if (!hasAccess || serverRestartingKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
modalApi.confirm({
|
||||
title: 'Command runner 배포 및 재기동',
|
||||
content: '현재 command runner를 다시 배포하고 재기동합니다. 진행할까요?',
|
||||
okText: '배포 및 재기동',
|
||||
cancelText: '취소',
|
||||
onOk: async () => {
|
||||
await handleRestartCommandRunner();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestartBothServers = async () => {
|
||||
if (!hasAccess || serverRestartingKey) {
|
||||
return;
|
||||
@@ -1737,7 +1819,7 @@ export function MainHeader({
|
||||
};
|
||||
|
||||
const handleResetNotificationIdentity = () => {
|
||||
Modal.confirm({
|
||||
modalApi.confirm({
|
||||
title: '알림 클라이언트 초기화',
|
||||
content: '현재 클라이언트의 알림 토큰/클라이언트 ID를 초기화합니다. 다시 접속하면 새로 등록됩니다.',
|
||||
okText: '초기화',
|
||||
@@ -2718,6 +2800,7 @@ export function MainHeader({
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalContextHolder}
|
||||
<Header className="app-header">
|
||||
<Space size={12} className="app-header__row">
|
||||
<Space size={12} className="app-header__menu-side">
|
||||
@@ -3027,6 +3110,24 @@ export function MainHeader({
|
||||
{activeAppSettingsSection === 'worklogAutomation' ? worklogAutomationPanel : null}
|
||||
{activeAppSettingsSection === 'automationNotifications' ? automationNotificationPanel : null}
|
||||
{activeAppSettingsSection === 'gestureShortcuts' ? gestureShortcutsPanel : null}
|
||||
<Divider style={{ marginBlock: 4 }} />
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Text strong>Command runner</Text>
|
||||
<Text type="secondary">
|
||||
별도 명시적 요청이 있을 때만 command runner 배포 및 재기동을 실행합니다.
|
||||
</Text>
|
||||
{renderFeedback(serverRestartFeedback, serverRestartCopyFeedback, setServerRestartCopyFeedback)}
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
icon={serverRestartingKey === 'command-runner' ? <ReloadOutlined spin /> : <ReloadOutlined />}
|
||||
loading={serverRestartingKey === 'command-runner'}
|
||||
disabled={!canRestartServers}
|
||||
onClick={handleConfirmRestartCommandRunner}
|
||||
>
|
||||
command runner 배포 및 재기동
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
) : null}
|
||||
{activeSettingsModal === 'notification' ? (
|
||||
@@ -3154,6 +3255,17 @@ export function MainHeader({
|
||||
<Text type="secondary">
|
||||
소스 수정일: {getServerLastSourceChangedDateLabel(workServerStatus)}
|
||||
</Text>
|
||||
<Text type="secondary">
|
||||
운영
|
||||
<span
|
||||
className={`app-header__server-version-indicator ${getServerVersionStatusClassName(prodServerStatus)}`}
|
||||
aria-label={getServerVersionStatusTitle(prodServerStatus, '운영')}
|
||||
title={getServerVersionStatusTitle(prodServerStatus, '운영')}
|
||||
style={{ marginInlineStart: 8, verticalAlign: 'middle' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Text>
|
||||
<Text type="secondary">{formatDateTimeLabel(prodServerStatus?.runningBuiltAt ?? null)}</Text>
|
||||
{renderFeedback(updateCheckFeedback, updateCheckCopyFeedback, setUpdateCheckCopyFeedback)}
|
||||
<Button
|
||||
block
|
||||
@@ -3188,6 +3300,7 @@ export function MainHeader({
|
||||
<Text strong style={{ marginTop: 8 }}>
|
||||
서버 재기동
|
||||
</Text>
|
||||
<Text type="secondary">전체 재기동은 TEST와 WORK 서버만 순서대로 진행합니다.</Text>
|
||||
<Text type="secondary">
|
||||
테스트 마지막 확인: {formatDateTimeLabel(testServerStatus?.checkedAt ?? null)}
|
||||
</Text>
|
||||
@@ -3234,6 +3347,24 @@ export function MainHeader({
|
||||
전체 재기동
|
||||
</Button>
|
||||
</Space>
|
||||
<Text strong style={{ marginTop: 8 }}>
|
||||
PROD 빌드 반영
|
||||
</Text>
|
||||
<Text type="secondary">운영 마지막 확인: {formatDateTimeLabel(prodServerStatus?.checkedAt ?? null)}</Text>
|
||||
<Text type="secondary">
|
||||
PROD 컨테이너는 전체 재기동에 포함하지 않고, 별도 확인 후 빌드와 재기동을 진행합니다.
|
||||
</Text>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
block
|
||||
icon={serverRestartingKey === 'prod' ? <ReloadOutlined spin /> : <ReloadOutlined />}
|
||||
loading={serverRestartingKey === 'prod'}
|
||||
disabled={!canRestartServers}
|
||||
onClick={handleConfirmRestartProdServer}
|
||||
>
|
||||
PROD 빌드 반영
|
||||
</Button>
|
||||
</Space>
|
||||
) : null}
|
||||
</Space>
|
||||
|
||||
Reference in New Issue
Block a user