feat: refresh shared Git config mounts live

- Use profile-scoped canonical Git clone sources with locked refreshes
- Mount shared Git configuration read-only and isolate profile content
- Add API and desktop/mobile actions for live Git mount refresh

Quality gates: frontend build and backend py_compile passed.
Skipped: backend pytest/Ruff unavailable; Docker/manual checks not approved.
This commit is contained in:
Developer
2026-07-21 14:37:42 +00:00
parent 3c25fffd49
commit 886c863260
11 changed files with 254 additions and 81 deletions
+10 -1
View File
@@ -2,7 +2,7 @@ import { apiClient } from "./client";
export interface ConfigProfileRefreshOutcome {
instance_id: string;
status: "compatible" | "restart_required" | "incompatible_permissions";
status: "compatible" | "refreshed" | "restart_required" | "incompatible_permissions";
reason?: string;
}
@@ -145,6 +145,15 @@ export const deleteConfigProfile = async (id: string): Promise<void> => {
await apiClient.delete(`/config-profiles/${id}`);
};
export const refreshConfigProfileGitMounts = async (
id: string,
): Promise<{ refresh_outcomes: ConfigProfileRefreshOutcome[] }> => {
const response = await apiClient.post<{
refresh_outcomes: ConfigProfileRefreshOutcome[];
}>(`/config-profiles/${id}/refresh-git-mounts`);
return response.data;
};
export const updateProfileIncludes = async (
id: string,
data: UpdateIncludesRequest,
@@ -23,6 +23,7 @@ interface Props {
onSubmit: (e?: React.FormEvent) => void;
onReset: () => void;
onPreview: () => void;
onRefreshGitMounts: () => void;
onAddInclude: (id: string) => void;
onRemoveInclude: (index: number) => void;
onDragStart: (e: React.DragEvent, index: number) => void;
@@ -63,6 +64,7 @@ export const ConfigProfileEditorPanel = ({
onSubmit,
onReset,
onPreview,
onRefreshGitMounts,
onAddInclude,
onRemoveInclude,
onDragStart,
@@ -114,6 +116,9 @@ export const ConfigProfileEditorPanel = ({
</div>
{!isCreating && selectedProfile && (
<div className="row row-sm">
<button className="btn btn-secondary" onClick={onRefreshGitMounts}>
<Icon name="refresh" size="sm" /> Refresh Git mounts
</button>
<button className="btn btn-secondary" onClick={onPreview} disabled={previewingId === selectedProfile.id}>
{previewingId === selectedProfile.id ? (
<><Icon name="loading" size="sm" /> Previewing...</>
@@ -64,6 +64,7 @@ interface Props {
) => void;
onRemoveMountFile: (mountIndex: number, path: string) => void;
onPreview: (id: string) => void;
onRefreshGitMounts: (id: string) => void;
onClosePreview: () => void;
}
@@ -104,6 +105,7 @@ export const ConfigProfilesMobileView = ({
onUpdateMountFile,
onRemoveMountFile,
onPreview,
onRefreshGitMounts,
onClosePreview,
}: Props) => {
const [isSaving, setIsSaving] = useState(false);
@@ -363,6 +365,13 @@ export const ConfigProfilesMobileView = ({
onDelete={handleDeleteClick}
>
<div className="mobile-detail-actions-extra">
<button
type="button"
className="secondary-button"
onClick={() => onRefreshGitMounts(selectedProfile.id)}
>
<Icon name="refresh" size="sm" /> Refresh Git mounts
</button>
<button
type="button"
className="secondary-button"
+21
View File
@@ -5,6 +5,7 @@ import {
deleteConfigProfile,
listConfigProfiles,
previewConfigProfile,
refreshConfigProfileGitMounts,
updateConfigProfile,
updateProfileIncludes,
type ConfigProfile,
@@ -273,6 +274,25 @@ export const useConfigProfiles = () => {
}
};
const handleRefreshGitMounts = async (id: string): Promise<boolean> => {
setError(null);
try {
const result = await refreshConfigProfileGitMounts(id);
const refreshed = result.refresh_outcomes.filter(
(outcome) => outcome.status === "refreshed",
);
setError(
refreshed.length
? `Refreshed Git mounts for ${refreshed.length} running instance${refreshed.length === 1 ? "" : "s"}.`
: "No running instances currently use this profile's Git mounts.",
);
return true;
} catch (err) {
setError(extractErrorMessage(err));
return false;
}
};
const handlePreview = async (id: string) => {
try {
setPreviewingId(id);
@@ -434,6 +454,7 @@ export const useConfigProfiles = () => {
handleSubmit,
handleDelete,
handlePreview,
handleRefreshGitMounts,
updateFormField,
addEnvVar,
updateEnvVar,
@@ -32,6 +32,7 @@ export const ConfigProfilesPage = () => {
handleSubmit,
handleDelete,
handlePreview,
handleRefreshGitMounts,
updateFormField,
addEnvVar,
updateEnvVar,
@@ -135,6 +136,7 @@ export const ConfigProfilesPage = () => {
onUpdateMountFile={updateMountFile}
onRemoveMountFile={removeMountFile}
onPreview={handlePreview}
onRefreshGitMounts={(id) => void handleRefreshGitMounts(id)}
onClosePreview={() => setPreviewData(null)}
/>
);
@@ -176,6 +178,7 @@ export const ConfigProfilesPage = () => {
onSubmit={handleSubmit}
onReset={handleReset}
onPreview={() => selectedProfile && handlePreview(selectedProfile.id)}
onRefreshGitMounts={() => selectedProfile && handleRefreshGitMounts(selectedProfile.id)}
onAddInclude={addInclude}
onRemoveInclude={removeInclude}
onDragStart={handleDragStart}