chore: test deploy snapshot
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,14 @@ import type {
|
||||
ServerRestartReservation,
|
||||
ServerRestartReservationStatus,
|
||||
ServerRestartReservationWorkItem,
|
||||
TestServerDeploymentPhase,
|
||||
TestServerDeploymentState,
|
||||
TestServerDeploymentStep,
|
||||
TestServerDeploymentStepKey,
|
||||
WorkServerDeploymentPhase,
|
||||
WorkServerDeploymentState,
|
||||
WorkServerDeploymentStep,
|
||||
WorkServerDeploymentStepKey,
|
||||
} from './types';
|
||||
|
||||
export class ServerCommandApiError extends Error {
|
||||
@@ -259,6 +267,7 @@ function normalizeServerCommandItem(value: unknown): ServerCommandItem {
|
||||
commandScript: typeof item.commandScript === 'string' ? item.commandScript : '-',
|
||||
commandWorkingDirectory: typeof item.commandWorkingDirectory === 'string' ? item.commandWorkingDirectory : '-',
|
||||
errorMessage: typeof item.errorMessage === 'string' ? item.errorMessage : null,
|
||||
deployment: normalizeWorkServerDeploymentState(item.deployment),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -343,12 +352,14 @@ function extractServerCommandActionResult(response: unknown): ServerCommandActio
|
||||
commandOutput?: unknown;
|
||||
output?: unknown;
|
||||
restartState?: unknown;
|
||||
testDeployment?: unknown;
|
||||
data?: {
|
||||
item?: unknown;
|
||||
server?: unknown;
|
||||
commandOutput?: unknown;
|
||||
output?: unknown;
|
||||
restartState?: unknown;
|
||||
testDeployment?: unknown;
|
||||
};
|
||||
};
|
||||
const nestedData = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) ? payload.data : null;
|
||||
@@ -365,9 +376,235 @@ function extractServerCommandActionResult(response: unknown): ServerCommandActio
|
||||
item: normalizeServerCommandItem(item),
|
||||
commandOutput: typeof commandOutput === 'string' ? commandOutput : null,
|
||||
restartState: restartState === 'accepted' ? 'accepted' : 'completed',
|
||||
deployment: normalizeWorkServerDeploymentState(payload.deployment ?? nestedData?.deployment),
|
||||
testDeployment: normalizeTestServerDeploymentState(payload.testDeployment ?? nestedData?.testDeployment),
|
||||
};
|
||||
}
|
||||
|
||||
const WORK_SERVER_DEPLOYMENT_STEP_KEYS = [
|
||||
'build-target-slot',
|
||||
'verify-target-health',
|
||||
'switch-proxy',
|
||||
'drain-previous-slot',
|
||||
'rebuild-previous-slot',
|
||||
'recover-interrupted-chat',
|
||||
] as const;
|
||||
|
||||
function normalizeWorkServerDeploymentStepKey(value: unknown): WorkServerDeploymentStepKey | null {
|
||||
return WORK_SERVER_DEPLOYMENT_STEP_KEYS.includes(value as WorkServerDeploymentStepKey)
|
||||
? (value as WorkServerDeploymentStepKey)
|
||||
: null;
|
||||
}
|
||||
|
||||
function normalizeWorkServerDeploymentPhase(value: unknown): WorkServerDeploymentPhase {
|
||||
return value === 'build-target-slot'
|
||||
|| value === 'verify-target-health'
|
||||
|| value === 'switch-proxy'
|
||||
|| value === 'drain-previous-slot'
|
||||
|| value === 'rebuild-previous-slot'
|
||||
|| value === 'recover-interrupted-chat'
|
||||
|| value === 'completed'
|
||||
|| value === 'failed'
|
||||
? value
|
||||
: 'idle';
|
||||
}
|
||||
|
||||
function normalizeWorkServerDeploymentSteps(value: unknown): WorkServerDeploymentStep[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return WORK_SERVER_DEPLOYMENT_STEP_KEYS.map((key) => ({
|
||||
key,
|
||||
status: 'pending',
|
||||
detail: null,
|
||||
updatedAt: null,
|
||||
}));
|
||||
}
|
||||
|
||||
const normalizedByKey = new Map<WorkServerDeploymentStepKey, WorkServerDeploymentStep>();
|
||||
|
||||
value.forEach((item) => {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const candidate = item as Record<string, unknown>;
|
||||
const key = normalizeWorkServerDeploymentStepKey(candidate.key);
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
normalizedByKey.set(key, {
|
||||
key,
|
||||
status:
|
||||
candidate.status === 'running'
|
||||
|| candidate.status === 'completed'
|
||||
|| candidate.status === 'failed'
|
||||
|| candidate.status === 'pending'
|
||||
? candidate.status
|
||||
: 'pending',
|
||||
detail: typeof candidate.detail === 'string' ? candidate.detail : null,
|
||||
updatedAt: typeof candidate.updatedAt === 'string' ? candidate.updatedAt : null,
|
||||
});
|
||||
});
|
||||
|
||||
return WORK_SERVER_DEPLOYMENT_STEP_KEYS.map((key) => normalizedByKey.get(key) ?? {
|
||||
key,
|
||||
status: 'pending',
|
||||
detail: null,
|
||||
updatedAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeWorkServerDeploymentState(value: unknown): WorkServerDeploymentState | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = value as Record<string, unknown>;
|
||||
|
||||
return {
|
||||
status: item.status === 'running' || item.status === 'completed' || item.status === 'failed' ? item.status : 'idle',
|
||||
phase: normalizeWorkServerDeploymentPhase(item.phase),
|
||||
summary: typeof item.summary === 'string' ? item.summary : null,
|
||||
startedAt: typeof item.startedAt === 'string' ? item.startedAt : null,
|
||||
updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,
|
||||
completedAt: typeof item.completedAt === 'string' ? item.completedAt : null,
|
||||
activeSlot: item.activeSlot === 'blue' || item.activeSlot === 'green' ? item.activeSlot : null,
|
||||
targetSlot: item.targetSlot === 'blue' || item.targetSlot === 'green' ? item.targetSlot : null,
|
||||
previousSlot: item.previousSlot === 'blue' || item.previousSlot === 'green' ? item.previousSlot : null,
|
||||
targetContainer: typeof item.targetContainer === 'string' ? item.targetContainer : null,
|
||||
previousContainer: typeof item.previousContainer === 'string' ? item.previousContainer : null,
|
||||
previousSlotActiveChatRequestCount: typeof item.previousSlotActiveChatRequestCount === 'number' ? item.previousSlotActiveChatRequestCount : null,
|
||||
previousSlotQueuedChatRequestCount: typeof item.previousSlotQueuedChatRequestCount === 'number' ? item.previousSlotQueuedChatRequestCount : null,
|
||||
recoveredSessionCount: typeof item.recoveredSessionCount === 'number' ? item.recoveredSessionCount : null,
|
||||
recoveredRestartedCount: typeof item.recoveredRestartedCount === 'number' ? item.recoveredRestartedCount : null,
|
||||
recoveredRequeuedCount: typeof item.recoveredRequeuedCount === 'number' ? item.recoveredRequeuedCount : null,
|
||||
lastError: typeof item.lastError === 'string' ? item.lastError : null,
|
||||
logExcerpt: typeof item.logExcerpt === 'string' ? item.logExcerpt : null,
|
||||
steps: normalizeWorkServerDeploymentSteps(item.steps),
|
||||
};
|
||||
}
|
||||
|
||||
function extractWorkServerDeploymentState(response: unknown): WorkServerDeploymentState | null {
|
||||
if (!response || typeof response !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = response as {
|
||||
item?: unknown;
|
||||
data?: {
|
||||
item?: unknown;
|
||||
};
|
||||
};
|
||||
const nestedData = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) ? payload.data : null;
|
||||
return normalizeWorkServerDeploymentState(payload.item ?? nestedData?.item ?? null);
|
||||
}
|
||||
|
||||
const TEST_SERVER_DEPLOYMENT_STEP_KEYS = [
|
||||
'commit-main-worktree',
|
||||
'push-origin-main',
|
||||
'build-test-app',
|
||||
'deploy-test-server',
|
||||
] as const;
|
||||
|
||||
function normalizeTestServerDeploymentStepKey(value: unknown): TestServerDeploymentStepKey | null {
|
||||
return TEST_SERVER_DEPLOYMENT_STEP_KEYS.includes(value as TestServerDeploymentStepKey)
|
||||
? (value as TestServerDeploymentStepKey)
|
||||
: null;
|
||||
}
|
||||
|
||||
function normalizeTestServerDeploymentPhase(value: unknown): TestServerDeploymentPhase {
|
||||
return value === 'commit-main-worktree'
|
||||
|| value === 'push-origin-main'
|
||||
|| value === 'build-test-app'
|
||||
|| value === 'deploy-test-server'
|
||||
|| value === 'completed'
|
||||
|| value === 'failed'
|
||||
? value
|
||||
: 'idle';
|
||||
}
|
||||
|
||||
function normalizeTestServerDeploymentSteps(value: unknown): TestServerDeploymentStep[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return TEST_SERVER_DEPLOYMENT_STEP_KEYS.map((key) => ({
|
||||
key,
|
||||
status: 'pending',
|
||||
detail: null,
|
||||
updatedAt: null,
|
||||
}));
|
||||
}
|
||||
|
||||
const normalizedByKey = new Map<TestServerDeploymentStepKey, TestServerDeploymentStep>();
|
||||
|
||||
value.forEach((item) => {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const candidate = item as Record<string, unknown>;
|
||||
const key = normalizeTestServerDeploymentStepKey(candidate.key);
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
normalizedByKey.set(key, {
|
||||
key,
|
||||
status:
|
||||
candidate.status === 'running'
|
||||
|| candidate.status === 'completed'
|
||||
|| candidate.status === 'failed'
|
||||
|| candidate.status === 'pending'
|
||||
? candidate.status
|
||||
: 'pending',
|
||||
detail: typeof candidate.detail === 'string' ? candidate.detail : null,
|
||||
updatedAt: typeof candidate.updatedAt === 'string' ? candidate.updatedAt : null,
|
||||
});
|
||||
});
|
||||
|
||||
return TEST_SERVER_DEPLOYMENT_STEP_KEYS.map((key) => normalizedByKey.get(key) ?? {
|
||||
key,
|
||||
status: 'pending',
|
||||
detail: null,
|
||||
updatedAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeTestServerDeploymentState(value: unknown): TestServerDeploymentState | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = value as Record<string, unknown>;
|
||||
|
||||
return {
|
||||
status: item.status === 'running' || item.status === 'completed' || item.status === 'failed' ? item.status : 'idle',
|
||||
phase: normalizeTestServerDeploymentPhase(item.phase),
|
||||
summary: typeof item.summary === 'string' ? item.summary : null,
|
||||
startedAt: typeof item.startedAt === 'string' ? item.startedAt : null,
|
||||
updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,
|
||||
completedAt: typeof item.completedAt === 'string' ? item.completedAt : null,
|
||||
lastError: typeof item.lastError === 'string' ? item.lastError : null,
|
||||
logExcerpt: typeof item.logExcerpt === 'string' ? item.logExcerpt : null,
|
||||
steps: normalizeTestServerDeploymentSteps(item.steps),
|
||||
};
|
||||
}
|
||||
|
||||
function extractTestServerDeploymentState(response: unknown): TestServerDeploymentState | null {
|
||||
if (!response || typeof response !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = response as {
|
||||
item?: unknown;
|
||||
data?: {
|
||||
item?: unknown;
|
||||
};
|
||||
};
|
||||
const nestedData = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) ? payload.data : null;
|
||||
return normalizeTestServerDeploymentState(payload.item ?? nestedData?.item ?? null);
|
||||
}
|
||||
|
||||
function normalizeServerRestartReservationStatus(value: unknown): ServerRestartReservationStatus {
|
||||
return value === 'waiting'
|
||||
|| value === 'ready'
|
||||
@@ -538,6 +775,42 @@ export async function restartServerCommand(key: ServerCommandKey, options?: { si
|
||||
return extractServerCommandActionResult(response);
|
||||
}
|
||||
|
||||
export async function deployWorkServerCommand(options?: { signal?: AbortSignal; shareToken?: string | null }) {
|
||||
const response = await request<unknown>('/server-commands/work-server/actions/deploy', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({}),
|
||||
signal: options?.signal,
|
||||
timeoutMs: 12000,
|
||||
}, { shareToken: options?.shareToken });
|
||||
|
||||
return extractServerCommandActionResult(response);
|
||||
}
|
||||
|
||||
export async function deployTestServerCommand(options?: { signal?: AbortSignal; shareToken?: string | null }) {
|
||||
const response = await request<unknown>('/server-commands/test/actions/deploy', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({}),
|
||||
signal: options?.signal,
|
||||
timeoutMs: 120000,
|
||||
}, { shareToken: options?.shareToken });
|
||||
|
||||
return extractServerCommandActionResult(response);
|
||||
}
|
||||
|
||||
export async function fetchWorkServerDeploymentStatus(options?: { signal?: AbortSignal; shareToken?: string | null }) {
|
||||
const response = await request<unknown>('/server-commands/work-server/deployment', {
|
||||
signal: options?.signal,
|
||||
}, { shareToken: options?.shareToken });
|
||||
return extractWorkServerDeploymentState(response);
|
||||
}
|
||||
|
||||
export async function fetchTestServerDeploymentStatus(options?: { signal?: AbortSignal; shareToken?: string | null }) {
|
||||
const response = await request<unknown>('/server-commands/test/deployment', {
|
||||
signal: options?.signal,
|
||||
}, { shareToken: options?.shareToken });
|
||||
return extractTestServerDeploymentState(response);
|
||||
}
|
||||
|
||||
export async function fetchServerRestartReservation(options?: { signal?: AbortSignal; shareToken?: string | null }) {
|
||||
const response = await request<unknown>('/server-commands/restart-reservation', {
|
||||
signal: options?.signal,
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
overscroll-behavior: none;
|
||||
overscroll-behavior-y: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y;
|
||||
padding: 0 0 calc(16px + env(safe-area-inset-bottom, 0px));
|
||||
background: #f3f6fb;
|
||||
}
|
||||
|
||||
.server-command-page.ant-space,
|
||||
@@ -22,77 +26,37 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__alert-body {
|
||||
.server-command-page__surface {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.server-command-page__alert-code {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: #fff2f0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
.server-command-page__surface--toolbar {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.server-command-page__card,
|
||||
.server-command-page__server-card {
|
||||
border-radius: 24px;
|
||||
.server-command-page__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.server-command-page__reservation-card {
|
||||
border: 1px solid #d6e4ff;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.server-command-page__card--shared {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(15, 23, 42, 0.96) 0%, rgba(30, 41, 59, 0.94) 52%, rgba(37, 99, 235, 0.88) 100%);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.server-command-page__card--shared .server-command-page__title.ant-typography,
|
||||
.server-command-page__card--shared .server-command-page__copy.ant-typography {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.server-command-page__server-card {
|
||||
.server-command-page__toolbar-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.server-command-page__server-card--shared {
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 250, 252, 0.96) 100%);
|
||||
box-shadow:
|
||||
0 20px 44px rgba(15, 23, 42, 0.12),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.server-command-page__server-card--shared-compact {
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 12px 28px rgba(15, 23, 42, 0.08),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.server-command-page__server-card--online {
|
||||
border-color: rgba(147, 197, 253, 0.72);
|
||||
}
|
||||
|
||||
.server-command-page__server-card--degraded {
|
||||
border-color: rgba(253, 230, 138, 0.92);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 251, 235, 0.95) 0%, rgba(255, 255, 255, 0.98) 100%);
|
||||
}
|
||||
|
||||
.server-command-page__server-card--offline {
|
||||
border-color: rgba(254, 202, 202, 0.9);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(254, 242, 242, 0.96) 0%, rgba(255, 255, 255, 0.98) 100%);
|
||||
.server-command-page__toolbar-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.server-command-page__title.ant-typography,
|
||||
@@ -100,300 +64,267 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-command-page__title-row {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.server-command-page__title-row .ant-space-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.server-command-page__copy.ant-typography {
|
||||
max-width: 760px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-command-page__shared-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.server-command-page__shared-toolbar-chips {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #e2e8f0;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip-label {
|
||||
color: rgba(226, 232, 240, 0.72);
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip-value {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip--online,
|
||||
.server-command-page__toolbar-chip--latest {
|
||||
background: rgba(37, 99, 235, 0.2);
|
||||
color: #bfdbfe;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip--degraded,
|
||||
.server-command-page__toolbar-chip--update-available {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
color: #fde68a;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip--offline,
|
||||
.server-command-page__toolbar-chip--build-required {
|
||||
background: rgba(220, 38, 38, 0.2);
|
||||
color: #fecaca;
|
||||
}
|
||||
|
||||
.server-command-page__toolbar-chip--unknown,
|
||||
.server-command-page__toolbar-chip--info {
|
||||
background: rgba(148, 163, 184, 0.18);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.server-command-page__status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.14);
|
||||
}
|
||||
|
||||
.server-command-page__status-dot--online,
|
||||
.server-command-page__status-dot--latest {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.server-command-page__status-dot--degraded,
|
||||
.server-command-page__status-dot--update-available {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.server-command-page__status-dot--offline,
|
||||
.server-command-page__status-dot--build-required {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.server-command-page__status-dot--unknown {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
.server-command-page__summary-grid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.server-command-page__shared-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: rgba(226, 232, 240, 0.7);
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.server-command-page__shared-pill--online,
|
||||
.server-command-page__shared-pill--latest {
|
||||
background: rgba(219, 234, 254, 0.95);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.server-command-page__shared-pill--degraded,
|
||||
.server-command-page__shared-pill--update-available {
|
||||
background: rgba(254, 240, 138, 0.55);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.server-command-page__shared-pill--offline,
|
||||
.server-command-page__shared-pill--build-required {
|
||||
background: rgba(254, 226, 226, 0.92);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.server-command-page__shared-stat {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(248, 250, 252, 0.92);
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-summary.ant-typography,
|
||||
.server-command-page__shared-server-footer.ant-typography {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-summary.ant-typography {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.server-command-page__restart-button--shared-compact {
|
||||
min-width: 88px;
|
||||
height: 36px;
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.server-command-page__summary-grid .ant-statistic {
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
background: #f7faff;
|
||||
}
|
||||
|
||||
.server-command-page__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.server-command-page__copy.ant-typography,
|
||||
.server-command-page__summary.ant-typography,
|
||||
.server-command-page__preview.ant-typography,
|
||||
.server-command-page__shared-server-footer.ant-typography,
|
||||
.server-command-page__work-detail.ant-typography,
|
||||
.server-command-page__command.ant-typography {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-command-page__copy.ant-typography {
|
||||
margin-top: 2px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-height: 28px;
|
||||
padding: 0 9px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 999px;
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item strong {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item--online {
|
||||
color: #166534;
|
||||
background: #f0fdf4;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item--latest {
|
||||
color: #1d4ed8;
|
||||
background: #eff6ff;
|
||||
border-color: #bfdbfe;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item--degraded,
|
||||
.server-command-page__status-summary-item--update-available {
|
||||
color: #b45309;
|
||||
background: #fffbeb;
|
||||
border-color: #fde68a;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item--offline,
|
||||
.server-command-page__status-summary-item--build-required {
|
||||
color: #b91c1c;
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
.server-command-page__status-summary-item--unknown,
|
||||
.server-command-page__status-summary-item--info {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.server-command-page__reservation-panel {
|
||||
border-color: #dbeafe;
|
||||
background: #fcfdff;
|
||||
}
|
||||
|
||||
.server-command-page__alert-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__alert-code,
|
||||
.server-command-page__preview,
|
||||
.server-command-page__command {
|
||||
.server-command-page__work-item {
|
||||
display: block;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: #f7faff;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.server-command-page__preview-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__alert-code {
|
||||
background: #fff2f0;
|
||||
}
|
||||
|
||||
.server-command-page__alert-text {
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-webkit-touch-callout: default;
|
||||
}
|
||||
|
||||
.server-command-page__compact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.server-command-page__compact-item {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 12px;
|
||||
background: #fbfcfe;
|
||||
}
|
||||
|
||||
.server-command-page__restart-button {
|
||||
min-width: 96px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.server-command-page__work-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__work-item {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: #f7faff;
|
||||
.server-command-page__control-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.server-command-page__work-detail.ant-typography {
|
||||
.server-command-page__control-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
border-color: #d7deea;
|
||||
}
|
||||
|
||||
.server-command-page__control-main {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.server-command-page__control-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.server-command-page__control-title.ant-typography {
|
||||
margin-bottom: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.server-command-page__status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #cbd5e1;
|
||||
background: #f8fafc;
|
||||
color: #334155;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.server-command-page__status-badge--online,
|
||||
.server-command-page__status-badge--latest {
|
||||
border-color: #93c5fd;
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.server-command-page__status-badge--degraded,
|
||||
.server-command-page__status-badge--update-available {
|
||||
border-color: #fcd34d;
|
||||
background: #fef3c7;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.server-command-page__status-badge--offline,
|
||||
.server-command-page__status-badge--build-required {
|
||||
border-color: #fca5a5;
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.server-command-page__status-badge--unknown,
|
||||
.server-command-page__status-badge--info {
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.server-command-page__control-meta {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.server-command-page__control-meta + .server-command-page__control-meta {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.server-command-page__control-meta .ant-typography {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-command-page__meta .ant-descriptions-item-label {
|
||||
width: 104px;
|
||||
}
|
||||
|
||||
.server-command-page__restart-button--shared {
|
||||
min-width: 180px;
|
||||
height: 44px;
|
||||
border-radius: 14px;
|
||||
font-weight: 700;
|
||||
.server-command-page__control-button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.server-command-page__shared-server-head {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.server-command-page__shared-server-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.server-command-page__shared-toolbar {
|
||||
.server-command-page__toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.server-command-page__restart-button--shared {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__restart-button--shared-compact {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.server-command-page__server-card .ant-card-head {
|
||||
padding-inline: 16px;
|
||||
}
|
||||
|
||||
.server-command-page__server-card .ant-card-head-wrapper {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.server-command-page__server-card .ant-card-extra {
|
||||
margin-inline-start: 0;
|
||||
.server-command-page__toolbar-side {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.server-command-page__restart-button {
|
||||
width: 100%;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.server-command-page__server-card .ant-card-body {
|
||||
padding-inline: 16px;
|
||||
.server-command-page__control-card {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.server-command-page__grid {
|
||||
grid-template-columns: 1fr;
|
||||
.server-command-page__control-button {
|
||||
min-width: 72px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,97 @@ export type ServerCommandItem = {
|
||||
commandScript: string;
|
||||
commandWorkingDirectory: string;
|
||||
errorMessage: string | null;
|
||||
deployment: WorkServerDeploymentState | null;
|
||||
};
|
||||
|
||||
export type ServerCommandActionResult = {
|
||||
item: ServerCommandItem;
|
||||
commandOutput: string | null;
|
||||
restartState: 'completed' | 'accepted';
|
||||
deployment?: WorkServerDeploymentState | null;
|
||||
testDeployment?: TestServerDeploymentState | null;
|
||||
};
|
||||
|
||||
export type WorkServerDeploymentStepKey =
|
||||
| 'build-target-slot'
|
||||
| 'verify-target-health'
|
||||
| 'switch-proxy'
|
||||
| 'drain-previous-slot'
|
||||
| 'rebuild-previous-slot'
|
||||
| 'recover-interrupted-chat';
|
||||
|
||||
export type WorkServerDeploymentStepStatus = 'pending' | 'running' | 'completed' | 'failed';
|
||||
|
||||
export type WorkServerDeploymentStep = {
|
||||
key: WorkServerDeploymentStepKey;
|
||||
status: WorkServerDeploymentStepStatus;
|
||||
detail: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type WorkServerDeploymentPhase =
|
||||
| 'idle'
|
||||
| 'build-target-slot'
|
||||
| 'verify-target-health'
|
||||
| 'switch-proxy'
|
||||
| 'drain-previous-slot'
|
||||
| 'rebuild-previous-slot'
|
||||
| 'recover-interrupted-chat'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
|
||||
export type WorkServerDeploymentState = {
|
||||
status: 'idle' | 'running' | 'completed' | 'failed';
|
||||
phase: WorkServerDeploymentPhase;
|
||||
summary: string | null;
|
||||
startedAt: string | null;
|
||||
updatedAt: string | null;
|
||||
completedAt: string | null;
|
||||
activeSlot: 'blue' | 'green' | null;
|
||||
targetSlot: 'blue' | 'green' | null;
|
||||
previousSlot: 'blue' | 'green' | null;
|
||||
targetContainer: string | null;
|
||||
previousContainer: string | null;
|
||||
previousSlotActiveChatRequestCount: number | null;
|
||||
previousSlotQueuedChatRequestCount: number | null;
|
||||
recoveredSessionCount: number | null;
|
||||
recoveredRestartedCount: number | null;
|
||||
recoveredRequeuedCount: number | null;
|
||||
lastError: string | null;
|
||||
logExcerpt: string | null;
|
||||
steps: WorkServerDeploymentStep[];
|
||||
};
|
||||
|
||||
export type TestServerDeploymentStepKey = 'commit-main-worktree' | 'push-origin-main' | 'build-test-app' | 'deploy-test-server';
|
||||
|
||||
export type TestServerDeploymentStepStatus = 'pending' | 'running' | 'completed' | 'failed';
|
||||
|
||||
export type TestServerDeploymentStep = {
|
||||
key: TestServerDeploymentStepKey;
|
||||
status: TestServerDeploymentStepStatus;
|
||||
detail: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type TestServerDeploymentPhase =
|
||||
| 'idle'
|
||||
| 'commit-main-worktree'
|
||||
| 'push-origin-main'
|
||||
| 'build-test-app'
|
||||
| 'deploy-test-server'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
|
||||
export type TestServerDeploymentState = {
|
||||
status: 'idle' | 'running' | 'completed' | 'failed';
|
||||
phase: TestServerDeploymentPhase;
|
||||
summary: string | null;
|
||||
startedAt: string | null;
|
||||
updatedAt: string | null;
|
||||
completedAt: string | null;
|
||||
lastError: string | null;
|
||||
logExcerpt: string | null;
|
||||
steps: TestServerDeploymentStep[];
|
||||
};
|
||||
|
||||
export type ServerRestartReservationStatus =
|
||||
|
||||
Reference in New Issue
Block a user