@promptbook/cli 0.113.0-4 → 0.113.0-8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/agents-server/src/app/admin/update/AdvancedOriginRepositoryPanel.tsx +76 -0
- package/apps/agents-server/src/app/admin/update/CurrentDeploymentCard.tsx +160 -0
- package/apps/agents-server/src/app/admin/update/PendingCommitsCard.tsx +86 -0
- package/apps/agents-server/src/app/admin/update/TargetEnvironmentCard.tsx +331 -0
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +20 -873
- package/apps/agents-server/src/app/admin/update/UpdateJobCard.tsx +329 -0
- package/apps/agents-server/src/app/admin/update/UpdateOverview.ts +74 -0
- package/apps/agents-server/src/app/admin/update/buildDeploymentTimeBehindLabel.ts +29 -0
- package/apps/agents-server/src/app/admin/update/formatHumanReadableTimestamp.ts +18 -0
- package/apps/agents-server/src/app/admin/update/getUpdateJobFailureMessage.ts +13 -0
- package/apps/agents-server/src/app/admin/update/getUpdateJobSuccessMessage.ts +13 -0
- package/apps/agents-server/src/app/admin/update/useUpdateClientState.ts +441 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +13 -1
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageClient.tsx +14 -559
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageHeader.tsx +84 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageSection.tsx +52 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedCodePreview.tsx +138 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedHarnessSection.tsx +98 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/SourceBookSection.tsx +66 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeErrorBanner.tsx +30 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeExportWarningBanner.tsx +62 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/Transpiler.ts +9 -0
- package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/useAgentCodeExportState.ts +478 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/cloneMockedChatPreset.ts +17 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/confirmDeleteMockedChat.ts +16 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedMessage.ts +81 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedParticipant.ts +146 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedSettings.ts +19 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/createDuplicatedDraft.ts +66 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/findMockedChatById.ts +51 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/persistMockedChats.ts +51 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/useMockedChatsEditorState.ts +16 -426
- package/apps/agents-server/src/components/Homepage/buildFreeGraphBoxLayout.ts +446 -0
- package/apps/agents-server/src/components/Homepage/buildGraphLayoutNodes.ts +415 -157
- package/apps/agents-server/src/components/Homepage/useAgentsGraphCanvasState.ts +2 -1
- package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +3 -1541
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteQueryBuilder.ts +623 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteSupabaseClient.ts +64 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/ensureTable.ts +93 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteFilters.ts +202 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteMutationPayload.ts +131 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteRowOperations.ts +102 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteSql.ts +59 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTableSchema.ts +329 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTypes.ts +84 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteValueCodec.ts +63 -0
- package/apps/agents-server/src/database/sqlite/localSqliteSupabase/normalizeSqliteError.ts +22 -0
- package/apps/agents-server/src/search/createDefaultServerSearchProviders/loadLocalOrganizationSearchDataset.ts +12 -1
- package/apps/agents-server/src/tools/send_email.ts +1 -2
- package/apps/agents-server/src/utils/agentOrganization/loadAgentOrganizationState.ts +13 -1
- package/apps/agents-server/src/utils/backup/createServerBackupZipStream.ts +7 -1382
- package/apps/agents-server/src/utils/backup/serverBackup/appendAgentBackupEntriesToZip.ts +27 -0
- package/apps/agents-server/src/utils/backup/serverBackup/appendConversationBackupEntriesToZip.ts +140 -0
- package/apps/agents-server/src/utils/backup/serverBackup/appendFileBackupEntriesToZip.ts +147 -0
- package/apps/agents-server/src/utils/backup/serverBackup/appendMessageBackupEntriesToZip.ts +65 -0
- package/apps/agents-server/src/utils/backup/serverBackup/appendMetadataBackupEntriesToZip.ts +39 -0
- package/apps/agents-server/src/utils/backup/serverBackup/appendSectionEntriesToZip.ts +92 -0
- package/apps/agents-server/src/utils/backup/serverBackup/appendUserBackupEntriesToZip.ts +80 -0
- package/apps/agents-server/src/utils/backup/serverBackup/createRedactedWalletBackupRecord.ts +34 -0
- package/apps/agents-server/src/utils/backup/serverBackup/createServerBackupManifest.ts +82 -0
- package/apps/agents-server/src/utils/backup/serverBackup/downloadBackupBinaryContent.ts +39 -0
- package/apps/agents-server/src/utils/backup/serverBackup/resolveFeedbackThreadMessages.ts +53 -0
- package/apps/agents-server/src/utils/backup/serverBackup/serverBackupAttachments.ts +151 -0
- package/apps/agents-server/src/utils/backup/serverBackup/serverBackupContext.ts +167 -0
- package/apps/agents-server/src/utils/backup/serverBackup/serverBackupFilenames.ts +161 -0
- package/apps/agents-server/src/utils/backup/serverBackup/serverBackupPreviews.ts +97 -0
- package/apps/agents-server/src/utils/backup/serverBackup/serverBackupRowUtilities.ts +158 -0
- package/apps/agents-server/src/utils/backup/serverBackup/serverBackupTypes.ts +59 -0
- package/apps/agents-server/src/utils/createLocalAgentSourceImporter.ts +159 -0
- package/apps/agents-server/src/utils/createMissingImportedAgentFallback.ts +60 -0
- package/apps/agents-server/src/utils/customDomainRouting.ts +157 -12
- package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +1 -1
- package/apps/agents-server/src/utils/importAgentWithFallback.ts +1 -58
- package/apps/agents-server/src/utils/localAgentRouteReferences.ts +167 -0
- package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +1 -1
- package/apps/agents-server/src/utils/managementApi/managementApiAgents.ts +17 -3
- package/apps/agents-server/src/utils/messages/sendMessage.ts +5 -3
- package/apps/agents-server/src/utils/resolveAgentStateFromSource.ts +7 -1
- package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +54 -5
- package/apps/agents-server/src/utils/resolveServerAgentContext.ts +12 -1
- package/apps/agents-server/src/utils/resolveStoredAgentState.ts +2 -1
- package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition/SpeechToTextFailoverRecognitionProviderRuntime.ts +0 -1
- package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition.ts +34 -2
- package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +39 -9
- package/apps/agents-server/tests/e2e/support/ChatHistoryNavigationSupport.ts +5 -0
- package/esm/index.es.js +875 -60
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
- package/esm/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
- package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +20 -0
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +5 -0
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +29 -0
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +4 -0
- package/esm/src/avatars/renderAvatarVisualAsciiArt.d.ts +75 -0
- package/esm/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
- package/esm/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
- package/esm/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
- package/esm/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
- package/esm/src/utils/ascii-art/$detectTerminalAnsiColorDepth.d.ts +15 -0
- package/esm/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +80 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/avatars/avatarAnimationScheduler.ts +2 -1
- package/src/avatars/renderAvatarVisualAsciiArt.ts +138 -0
- package/src/avatars/visuals/octopus3d3AvatarVisual.ts +6 -2
- package/src/avatars/visuals/octopus3d4AvatarVisual.ts +14 -18
- package/src/book-components/Chat/Chat/insertDictationChunk.ts +3 -3
- package/src/book-components/Chat/Chat/learnDictationDictionary.ts +1 -1
- package/src/book-components/Chat/Chat/useChatInputAreaDictation.ts +133 -26
- package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +3 -1
- package/src/cli/cli-commands/coder/boilerplateTemplates.ts +14 -21
- package/src/cli/cli-commands/coder/find-refactor-candidates.ts +9 -2
- package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +1 -4
- package/src/cli/cli-commands/coder/run.ts +3 -27
- package/src/cli/cli-commands/common/createPositiveIntegerOptionParser.ts +31 -0
- package/src/commitments/TEAM/TEAM.ts +5 -5
- package/src/other/templates/getTemplatesPipelineCollection.ts +770 -1169
- package/src/utils/ascii-art/$detectTerminalAnsiColorDepth.ts +45 -0
- package/src/utils/ascii-art/convertImageDataToAsciiArt.ts +396 -0
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/index.umd.js +875 -60
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
- package/umd/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
- package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +20 -0
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +5 -0
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +29 -0
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +4 -0
- package/umd/src/avatars/renderAvatarVisualAsciiArt.d.ts +75 -0
- package/umd/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
- package/umd/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
- package/umd/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
- package/umd/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
- package/umd/src/utils/ascii-art/$detectTerminalAnsiColorDepth.d.ts +15 -0
- package/umd/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +80 -0
- package/umd/src/version.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +0 -6
- package/umd/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +0 -6
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ClipboardCopy, FileDown, Loader2 } from 'lucide-react';
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { AdminXtermTerminal } from '../../../components/AdminTerminal/AdminXtermTerminal';
|
|
6
|
+
import { Card } from '../../../components/Homepage/Card';
|
|
7
|
+
import type { ServerLanguageCode } from '../../../languages/ServerLanguageRegistry';
|
|
8
|
+
import { downloadBlob, parseFilenameFromContentDisposition } from '../../../utils/download/browserFileDownload';
|
|
9
|
+
import { formatHumanReadableTimestamp } from './formatHumanReadableTimestamp';
|
|
10
|
+
import { getUpdateJobFailureMessage } from './getUpdateJobFailureMessage';
|
|
11
|
+
import { getUpdateJobSuccessMessage } from './getUpdateJobSuccessMessage';
|
|
12
|
+
import type { UpdateJobSnapshot } from './UpdateOverview';
|
|
13
|
+
import type { UpdateClientState } from './useUpdateClientState';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Duration (ms) of the transient "Copied!" / "Saved!" feedback shown next to log action buttons.
|
|
17
|
+
*/
|
|
18
|
+
const UPDATE_LOG_ACTION_FEEDBACK_DURATION_MS = 2500;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default download filename used when the server does not provide one via `Content-Disposition`.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_UPDATE_LOG_DOWNLOAD_FILENAME = 'self-update.log';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Props for the update job card.
|
|
27
|
+
*
|
|
28
|
+
* @private type of `<UpdateJobCard/>`
|
|
29
|
+
*/
|
|
30
|
+
type UpdateJobCardProps = {
|
|
31
|
+
readonly state: UpdateClientState;
|
|
32
|
+
readonly language: ServerLanguageCode;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Props for one update job metric.
|
|
37
|
+
*
|
|
38
|
+
* @private type of `<UpdateJobCard/>`
|
|
39
|
+
*/
|
|
40
|
+
type UpdateJobMetricProps = {
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly value: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Update job status value rendered by the job badge.
|
|
47
|
+
*
|
|
48
|
+
* @private type of `<UpdateJobCard/>`
|
|
49
|
+
*/
|
|
50
|
+
type UpdateJobStatus = UpdateJobSnapshot['status'];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Shows the latest standalone VPS update job status and persisted log tail.
|
|
54
|
+
*
|
|
55
|
+
* @private internal component of `<UpdateClient/>`
|
|
56
|
+
*/
|
|
57
|
+
export function UpdateJobCard({ state, language }: UpdateJobCardProps) {
|
|
58
|
+
const job = state.overview?.job ?? null;
|
|
59
|
+
const jobStatus = job?.status ?? 'idle';
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Card className="hover:border-gray-200 hover:shadow-md">
|
|
63
|
+
<div className="space-y-4">
|
|
64
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
65
|
+
<div>
|
|
66
|
+
<h2 className="text-lg font-semibold text-slate-900">Update job</h2>
|
|
67
|
+
<p className="mt-1 text-sm text-slate-500">
|
|
68
|
+
The update runs in the background so the browser request can finish cleanly before pm2
|
|
69
|
+
restarts the server.
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
<UpdateJobStatusBadge status={jobStatus} />
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<UpdateJobSummaryGrid job={job} language={language} />
|
|
76
|
+
<UpdateJobStatusMessage job={job} />
|
|
77
|
+
|
|
78
|
+
{job?.logFilePath && (
|
|
79
|
+
<div className="text-xs text-slate-500">
|
|
80
|
+
Installer log:
|
|
81
|
+
<span className="ml-2 font-mono text-slate-700">{job.logFilePath}</span>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
{job?.status === 'failed' && <UpdateJobLogActions />}
|
|
85
|
+
<AdminXtermTerminal
|
|
86
|
+
terminalId={state.updateTerminalId}
|
|
87
|
+
output={job?.logTail || ''}
|
|
88
|
+
emptyState={state.updateTerminalEmptyState}
|
|
89
|
+
isReadOnly
|
|
90
|
+
isPlainTextOutput
|
|
91
|
+
ariaLabel="Standalone VPS update log"
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</Card>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Renders the current update job status badge.
|
|
100
|
+
*
|
|
101
|
+
* @private internal component of `<UpdateJobCard/>`
|
|
102
|
+
*/
|
|
103
|
+
function UpdateJobStatusBadge({ status }: { readonly status: UpdateJobStatus }) {
|
|
104
|
+
return (
|
|
105
|
+
<span
|
|
106
|
+
className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-semibold uppercase tracking-wide ${getUpdateJobStatusClassName(
|
|
107
|
+
status,
|
|
108
|
+
)}`}
|
|
109
|
+
>
|
|
110
|
+
{status}
|
|
111
|
+
</span>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Renders the fixed update job metric grid.
|
|
117
|
+
*
|
|
118
|
+
* @private internal component of `<UpdateJobCard/>`
|
|
119
|
+
*/
|
|
120
|
+
function UpdateJobSummaryGrid({
|
|
121
|
+
job,
|
|
122
|
+
language,
|
|
123
|
+
}: {
|
|
124
|
+
readonly job: UpdateJobSnapshot | null;
|
|
125
|
+
readonly language: ServerLanguageCode;
|
|
126
|
+
}) {
|
|
127
|
+
return (
|
|
128
|
+
<dl className="grid gap-4 text-sm text-slate-600 md:grid-cols-2 xl:grid-cols-4">
|
|
129
|
+
<UpdateJobMetric label="Target" value={job?.targetEnvironment.label || 'Production'} />
|
|
130
|
+
<UpdateJobMetric label="Step" value={job?.currentStep || 'Idle'} />
|
|
131
|
+
<UpdateJobMetric label="Started" value={formatHumanReadableTimestamp(job?.startedAt, language)} />
|
|
132
|
+
<UpdateJobMetric label="Finished" value={formatHumanReadableTimestamp(job?.finishedAt, language)} />
|
|
133
|
+
</dl>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Renders one update job metric.
|
|
139
|
+
*
|
|
140
|
+
* @private internal component of `<UpdateJobCard/>`
|
|
141
|
+
*/
|
|
142
|
+
function UpdateJobMetric({ label, value }: UpdateJobMetricProps) {
|
|
143
|
+
return (
|
|
144
|
+
<div className="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3">
|
|
145
|
+
<dt className="text-xs font-semibold uppercase tracking-wide text-slate-500">{label}</dt>
|
|
146
|
+
<dd className="mt-1 text-slate-900">{value}</dd>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Renders the job completion or failure message.
|
|
153
|
+
*
|
|
154
|
+
* @private internal component of `<UpdateJobCard/>`
|
|
155
|
+
*/
|
|
156
|
+
function UpdateJobStatusMessage({ job }: { readonly job: UpdateJobSnapshot | null }) {
|
|
157
|
+
if (job?.status === 'succeeded') {
|
|
158
|
+
return (
|
|
159
|
+
<div className="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
|
|
160
|
+
{getUpdateJobSuccessMessage(job)}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (job?.status === 'failed') {
|
|
166
|
+
return (
|
|
167
|
+
<div className="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
|
|
168
|
+
{getUpdateJobFailureMessage(job)}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Renders the copy / download actions for the persisted standalone VPS self-update log file.
|
|
178
|
+
*
|
|
179
|
+
* Shown when the latest update job failed so the super admin can quickly share the full log with the developers.
|
|
180
|
+
*
|
|
181
|
+
* @private internal component of `<UpdateJobCard/>`
|
|
182
|
+
*/
|
|
183
|
+
function UpdateJobLogActions() {
|
|
184
|
+
const [actionFeedback, setActionFeedback] = useState<string | null>(null);
|
|
185
|
+
const [actionErrorMessage, setActionErrorMessage] = useState<string | null>(null);
|
|
186
|
+
const [isCopying, setIsCopying] = useState(false);
|
|
187
|
+
const [isDownloading, setIsDownloading] = useState(false);
|
|
188
|
+
const feedbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
189
|
+
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
return () => {
|
|
192
|
+
if (feedbackTimeoutRef.current !== null) {
|
|
193
|
+
clearTimeout(feedbackTimeoutRef.current);
|
|
194
|
+
feedbackTimeoutRef.current = null;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}, []);
|
|
198
|
+
|
|
199
|
+
function showActionFeedback(feedbackMessage: string): void {
|
|
200
|
+
if (feedbackTimeoutRef.current !== null) {
|
|
201
|
+
clearTimeout(feedbackTimeoutRef.current);
|
|
202
|
+
}
|
|
203
|
+
setActionFeedback(feedbackMessage);
|
|
204
|
+
setActionErrorMessage(null);
|
|
205
|
+
feedbackTimeoutRef.current = setTimeout(() => {
|
|
206
|
+
setActionFeedback(null);
|
|
207
|
+
feedbackTimeoutRef.current = null;
|
|
208
|
+
}, UPDATE_LOG_ACTION_FEEDBACK_DURATION_MS);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function fetchUpdateLogResponse(): Promise<Response> {
|
|
212
|
+
const response = await fetch('/api/admin/update/log', { cache: 'no-store' });
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
const errorPayload = (await response.json().catch(() => null)) as { readonly error?: string } | null;
|
|
215
|
+
throw new Error(errorPayload?.error || 'Failed to load the self-update log file.');
|
|
216
|
+
}
|
|
217
|
+
return response;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function handleCopyLog(): Promise<void> {
|
|
221
|
+
if (isCopying || isDownloading) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
setIsCopying(true);
|
|
227
|
+
setActionErrorMessage(null);
|
|
228
|
+
if (!navigator.clipboard?.writeText) {
|
|
229
|
+
throw new Error('The browser clipboard API is unavailable in this context.');
|
|
230
|
+
}
|
|
231
|
+
const response = await fetchUpdateLogResponse();
|
|
232
|
+
const logContent = await response.text();
|
|
233
|
+
await navigator.clipboard.writeText(logContent);
|
|
234
|
+
showActionFeedback('Self-update log copied to clipboard.');
|
|
235
|
+
} catch (error) {
|
|
236
|
+
setActionErrorMessage(error instanceof Error ? error.message : 'Failed to copy the self-update log.');
|
|
237
|
+
} finally {
|
|
238
|
+
setIsCopying(false);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function handleDownloadLog(): Promise<void> {
|
|
243
|
+
if (isCopying || isDownloading) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
setIsDownloading(true);
|
|
249
|
+
setActionErrorMessage(null);
|
|
250
|
+
const response = await fetchUpdateLogResponse();
|
|
251
|
+
const logFilename =
|
|
252
|
+
parseFilenameFromContentDisposition(response.headers.get('Content-Disposition')) ||
|
|
253
|
+
DEFAULT_UPDATE_LOG_DOWNLOAD_FILENAME;
|
|
254
|
+
const logBlob = await response.blob();
|
|
255
|
+
downloadBlob(logBlob, logFilename);
|
|
256
|
+
showActionFeedback(`Saved self-update log as ${logFilename}.`);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
setActionErrorMessage(error instanceof Error ? error.message : 'Failed to download the self-update log.');
|
|
259
|
+
} finally {
|
|
260
|
+
setIsDownloading(false);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div className="flex flex-wrap items-center gap-3 rounded-xl border border-rose-200 bg-rose-50/40 px-4 py-3 text-sm text-rose-700">
|
|
266
|
+
<span className="font-medium">Share the log with the developers:</span>
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
onClick={() => void handleCopyLog()}
|
|
270
|
+
disabled={isCopying || isDownloading}
|
|
271
|
+
className="inline-flex items-center gap-2 rounded-md border border-rose-200 bg-white px-3 py-1.5 text-xs font-semibold text-rose-700 hover:bg-rose-100 disabled:cursor-not-allowed disabled:opacity-60"
|
|
272
|
+
>
|
|
273
|
+
{isCopying ? (
|
|
274
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
275
|
+
) : (
|
|
276
|
+
<ClipboardCopy className="h-3.5 w-3.5" />
|
|
277
|
+
)}
|
|
278
|
+
Copy log
|
|
279
|
+
</button>
|
|
280
|
+
<button
|
|
281
|
+
type="button"
|
|
282
|
+
onClick={() => void handleDownloadLog()}
|
|
283
|
+
disabled={isCopying || isDownloading}
|
|
284
|
+
className="inline-flex items-center gap-2 rounded-md border border-rose-200 bg-white px-3 py-1.5 text-xs font-semibold text-rose-700 hover:bg-rose-100 disabled:cursor-not-allowed disabled:opacity-60"
|
|
285
|
+
>
|
|
286
|
+
{isDownloading ? (
|
|
287
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
288
|
+
) : (
|
|
289
|
+
<FileDown className="h-3.5 w-3.5" />
|
|
290
|
+
)}
|
|
291
|
+
Download log
|
|
292
|
+
</button>
|
|
293
|
+
{actionFeedback && (
|
|
294
|
+
<span className="text-xs text-rose-600" role="status" aria-live="polite">
|
|
295
|
+
{actionFeedback}
|
|
296
|
+
</span>
|
|
297
|
+
)}
|
|
298
|
+
{actionErrorMessage && (
|
|
299
|
+
<span className="text-xs text-rose-700" role="alert">
|
|
300
|
+
{actionErrorMessage}
|
|
301
|
+
</span>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Resolves status badge styling for the update job status.
|
|
309
|
+
*
|
|
310
|
+
* @param status - Current update job status.
|
|
311
|
+
* @returns Tailwind class list for the status badge.
|
|
312
|
+
*
|
|
313
|
+
* @private function of `<UpdateJobCard/>`
|
|
314
|
+
*/
|
|
315
|
+
function getUpdateJobStatusClassName(status: UpdateJobStatus): string {
|
|
316
|
+
if (status === 'running') {
|
|
317
|
+
return 'border-blue-200 bg-blue-50 text-blue-700';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (status === 'failed') {
|
|
321
|
+
return 'border-rose-200 bg-rose-50 text-rose-700';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (status === 'succeeded') {
|
|
325
|
+
return 'border-emerald-200 bg-emerald-50 text-emerald-700';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return 'border-slate-200 bg-slate-50 text-slate-500';
|
|
329
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe environment option returned by the update API.
|
|
3
|
+
*
|
|
4
|
+
* @private type of `<UpdateClient/>`
|
|
5
|
+
*/
|
|
6
|
+
export type UpdateEnvironmentOption = {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly branch: string;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly description: string;
|
|
11
|
+
readonly isCustom: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Browser-safe latest update-job snapshot.
|
|
16
|
+
*
|
|
17
|
+
* @private type of `<UpdateClient/>`
|
|
18
|
+
*/
|
|
19
|
+
export type UpdateJobSnapshot = {
|
|
20
|
+
readonly status: 'idle' | 'running' | 'succeeded' | 'failed';
|
|
21
|
+
readonly pid: number | null;
|
|
22
|
+
readonly targetBranch: string | null;
|
|
23
|
+
readonly targetEnvironment: UpdateEnvironmentOption;
|
|
24
|
+
readonly currentStep: string | null;
|
|
25
|
+
readonly currentCommitSha: string | null;
|
|
26
|
+
readonly targetCommitSha: string | null;
|
|
27
|
+
readonly errorMessage: string | null;
|
|
28
|
+
readonly startedAt: string | null;
|
|
29
|
+
readonly finishedAt: string | null;
|
|
30
|
+
readonly isStale: boolean;
|
|
31
|
+
readonly logTail: string | null;
|
|
32
|
+
readonly logFilePath: string | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Browser-safe summary of one commit between the deployed checkout and the latest remote commit.
|
|
37
|
+
*
|
|
38
|
+
* @private type of `<UpdateClient/>`
|
|
39
|
+
*/
|
|
40
|
+
export type UpdatePendingCommit = {
|
|
41
|
+
readonly commitSha: string;
|
|
42
|
+
readonly shortCommitSha: string;
|
|
43
|
+
readonly subject: string;
|
|
44
|
+
readonly authoredAt: string | null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Browser-safe self-update overview returned by the super-admin API.
|
|
49
|
+
*
|
|
50
|
+
* @private type of `<UpdateClient/>`
|
|
51
|
+
*/
|
|
52
|
+
export type UpdateOverview = {
|
|
53
|
+
readonly isAvailable: boolean;
|
|
54
|
+
readonly unavailableReason: string | null;
|
|
55
|
+
readonly environments: ReadonlyArray<UpdateEnvironmentOption>;
|
|
56
|
+
readonly currentEnvironment: UpdateEnvironmentOption;
|
|
57
|
+
readonly repositoryDirectory: string | null;
|
|
58
|
+
readonly currentCommitSha: string | null;
|
|
59
|
+
readonly currentCommitShortSha: string | null;
|
|
60
|
+
readonly currentCommitMessage: string | null;
|
|
61
|
+
readonly currentCommitDate: string | null;
|
|
62
|
+
readonly latestRemoteCommitSha: string | null;
|
|
63
|
+
readonly latestRemoteCommitShortSha: string | null;
|
|
64
|
+
readonly latestRemoteCommitDate: string | null;
|
|
65
|
+
readonly latestRemoteCommitMessage: string | null;
|
|
66
|
+
readonly commitsBehindCount: number | null;
|
|
67
|
+
readonly pendingCommits: ReadonlyArray<UpdatePendingCommit>;
|
|
68
|
+
readonly isUpdateAvailable: boolean;
|
|
69
|
+
readonly originRepositoryUrl: string;
|
|
70
|
+
readonly isOriginRepositoryDefault: boolean;
|
|
71
|
+
readonly defaultOriginRepositoryUrl: string;
|
|
72
|
+
readonly job: UpdateJobSnapshot;
|
|
73
|
+
readonly error?: string;
|
|
74
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ServerLanguageCode } from '../../../languages/ServerLanguageRegistry';
|
|
2
|
+
import { createServerLanguageMoment } from '../../../utils/localization/createServerLanguageMoment';
|
|
3
|
+
import type { UpdateOverview } from './UpdateOverview';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds the localized time-behind portion of the deployment drift label (e.g. `3 days behind`).
|
|
7
|
+
*
|
|
8
|
+
* @param overview - Current overview snapshot.
|
|
9
|
+
* @param language - Active UI language for moment localization.
|
|
10
|
+
* @returns Time-behind label or empty string when either side of the comparison is missing.
|
|
11
|
+
*
|
|
12
|
+
* @private function of `<UpdateClient/>`
|
|
13
|
+
*/
|
|
14
|
+
export function buildDeploymentTimeBehindLabel(
|
|
15
|
+
overview: UpdateOverview | null,
|
|
16
|
+
language: ServerLanguageCode,
|
|
17
|
+
): string {
|
|
18
|
+
if (!overview?.currentCommitDate || !overview.latestRemoteCommitDate) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const currentMoment = createServerLanguageMoment(overview.currentCommitDate, language);
|
|
23
|
+
const latestMoment = createServerLanguageMoment(overview.latestRemoteCommitDate, language);
|
|
24
|
+
if (!currentMoment.isValid() || !latestMoment.isValid()) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${currentMoment.from(latestMoment, true)} behind`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ServerLanguageCode } from '../../../languages/ServerLanguageRegistry';
|
|
2
|
+
import { formatServerLanguageHumanReadableDate } from '../../../utils/localization/formatServerLanguageHumanReadableDate';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Formats optional timestamps for the update status cards using `moment.js` with the active UI language.
|
|
6
|
+
*
|
|
7
|
+
* @param value - ISO timestamp or `null`.
|
|
8
|
+
* @param language - Active UI language code.
|
|
9
|
+
* @returns Localized human-friendly timestamp or fallback text.
|
|
10
|
+
*
|
|
11
|
+
* @private function of `<UpdateClient/>`
|
|
12
|
+
*/
|
|
13
|
+
export function formatHumanReadableTimestamp(value: string | null | undefined, language: ServerLanguageCode): string {
|
|
14
|
+
return formatServerLanguageHumanReadableDate(value, language, {
|
|
15
|
+
fallbackLabel: 'Not available',
|
|
16
|
+
isExactDateIncluded: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UpdateJobSnapshot } from './UpdateOverview';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds the failure message for a failed standalone VPS update.
|
|
5
|
+
*
|
|
6
|
+
* @param job - Failed update job snapshot.
|
|
7
|
+
* @returns Human-readable failure message.
|
|
8
|
+
*
|
|
9
|
+
* @private function of `<UpdateClient/>`
|
|
10
|
+
*/
|
|
11
|
+
export function getUpdateJobFailureMessage(job: UpdateJobSnapshot): string {
|
|
12
|
+
return job.errorMessage || 'The standalone VPS update failed. Review the installer log for details.';
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UpdateJobSnapshot } from './UpdateOverview';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds the success message for a finished standalone VPS update.
|
|
5
|
+
*
|
|
6
|
+
* @param job - Completed update job snapshot.
|
|
7
|
+
* @returns Human-readable success message.
|
|
8
|
+
*
|
|
9
|
+
* @private function of `<UpdateClient/>`
|
|
10
|
+
*/
|
|
11
|
+
export function getUpdateJobSuccessMessage(job: UpdateJobSnapshot): string {
|
|
12
|
+
return job.currentStep || 'Standalone VPS update finished successfully.';
|
|
13
|
+
}
|