Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 900a8e47a5 | |||
| 25e870ba43 |
@@ -14,6 +14,7 @@ interface Props {
|
|||||||
saveStatus: "idle" | "saving" | "saved" | "error";
|
saveStatus: "idle" | "saving" | "saved" | "error";
|
||||||
previewData: ResolvedProfile | null;
|
previewData: ResolvedProfile | null;
|
||||||
previewingId: string | null;
|
previewingId: string | null;
|
||||||
|
isRefreshingGitMounts: boolean;
|
||||||
projects: ProjectWithRepos[];
|
projects: ProjectWithRepos[];
|
||||||
toolTypes: ToolType[];
|
toolTypes: ToolType[];
|
||||||
availableProfiles: ConfigProfile[];
|
availableProfiles: ConfigProfile[];
|
||||||
@@ -55,6 +56,7 @@ export const ConfigProfileEditorPanel = ({
|
|||||||
saveStatus,
|
saveStatus,
|
||||||
previewData,
|
previewData,
|
||||||
previewingId,
|
previewingId,
|
||||||
|
isRefreshingGitMounts,
|
||||||
projects,
|
projects,
|
||||||
toolTypes,
|
toolTypes,
|
||||||
availableProfiles,
|
availableProfiles,
|
||||||
@@ -116,8 +118,16 @@ export const ConfigProfileEditorPanel = ({
|
|||||||
</div>
|
</div>
|
||||||
{!isCreating && selectedProfile && (
|
{!isCreating && selectedProfile && (
|
||||||
<div className="row row-sm">
|
<div className="row row-sm">
|
||||||
<button className="btn btn-secondary" onClick={onRefreshGitMounts}>
|
<button className="btn btn-secondary" onClick={onRefreshGitMounts} disabled={isRefreshingGitMounts}>
|
||||||
|
{isRefreshingGitMounts ? (
|
||||||
|
<>
|
||||||
|
<Icon name="loading" size="sm" /> Refreshing Git mounts...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Icon name="refresh" size="sm" /> Refresh Git mounts
|
<Icon name="refresh" size="sm" /> Refresh Git mounts
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn-secondary" onClick={onPreview} disabled={previewingId === selectedProfile.id}>
|
<button className="btn btn-secondary" onClick={onPreview} disabled={previewingId === selectedProfile.id}>
|
||||||
{previewingId === selectedProfile.id ? (
|
{previewingId === selectedProfile.id ? (
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface Props {
|
|||||||
availableProfiles: ConfigProfile[];
|
availableProfiles: ConfigProfile[];
|
||||||
previewData: ResolvedProfile | null;
|
previewData: ResolvedProfile | null;
|
||||||
previewingId: string | null;
|
previewingId: string | null;
|
||||||
|
isRefreshingGitMounts: boolean;
|
||||||
saveStatus: "idle" | "saving" | "saved" | "error";
|
saveStatus: "idle" | "saving" | "saved" | "error";
|
||||||
error: string | null;
|
error: string | null;
|
||||||
onViewChange: (view: MobileView) => void;
|
onViewChange: (view: MobileView) => void;
|
||||||
@@ -80,6 +81,7 @@ export const ConfigProfilesMobileView = ({
|
|||||||
availableProfiles,
|
availableProfiles,
|
||||||
previewData,
|
previewData,
|
||||||
previewingId,
|
previewingId,
|
||||||
|
isRefreshingGitMounts,
|
||||||
saveStatus,
|
saveStatus,
|
||||||
error,
|
error,
|
||||||
onViewChange,
|
onViewChange,
|
||||||
@@ -368,9 +370,19 @@ export const ConfigProfilesMobileView = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="secondary-button"
|
className="secondary-button"
|
||||||
|
disabled={isRefreshingGitMounts}
|
||||||
onClick={() => onRefreshGitMounts(selectedProfile.id)}
|
onClick={() => onRefreshGitMounts(selectedProfile.id)}
|
||||||
>
|
>
|
||||||
|
{isRefreshingGitMounts ? (
|
||||||
|
<>
|
||||||
|
<Icon name="loading" size="sm" />
|
||||||
|
Refreshing Git mounts...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Icon name="refresh" size="sm" /> Refresh Git mounts
|
<Icon name="refresh" size="sm" /> Refresh Git mounts
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const useConfigProfiles = () => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [previewData, setPreviewData] = useState<ResolvedProfile | null>(null);
|
const [previewData, setPreviewData] = useState<ResolvedProfile | null>(null);
|
||||||
const [previewingId, setPreviewingId] = useState<string | null>(null);
|
const [previewingId, setPreviewingId] = useState<string | null>(null);
|
||||||
|
const [isRefreshingGitMounts, setIsRefreshingGitMounts] = useState(false);
|
||||||
const [formData, setFormData] =
|
const [formData, setFormData] =
|
||||||
useState<CreateConfigProfileRequest>(defaultForm);
|
useState<CreateConfigProfileRequest>(defaultForm);
|
||||||
const [includedProfileIds, setIncludedProfileIds] = useState<string[]>([]);
|
const [includedProfileIds, setIncludedProfileIds] = useState<string[]>([]);
|
||||||
@@ -275,7 +276,10 @@ export const useConfigProfiles = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRefreshGitMounts = async (id: string): Promise<boolean> => {
|
const handleRefreshGitMounts = async (id: string): Promise<boolean> => {
|
||||||
|
if (isRefreshingGitMounts) return false;
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setIsRefreshingGitMounts(true);
|
||||||
try {
|
try {
|
||||||
const result = await refreshConfigProfileGitMounts(id);
|
const result = await refreshConfigProfileGitMounts(id);
|
||||||
const refreshed = result.refresh_outcomes.filter(
|
const refreshed = result.refresh_outcomes.filter(
|
||||||
@@ -290,6 +294,8 @@ export const useConfigProfiles = () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(extractErrorMessage(err));
|
setError(extractErrorMessage(err));
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
setIsRefreshingGitMounts(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -445,6 +451,7 @@ export const useConfigProfiles = () => {
|
|||||||
error,
|
error,
|
||||||
previewData,
|
previewData,
|
||||||
previewingId,
|
previewingId,
|
||||||
|
isRefreshingGitMounts,
|
||||||
formData,
|
formData,
|
||||||
includedProfileIds,
|
includedProfileIds,
|
||||||
dragOverIndex,
|
dragOverIndex,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const ConfigProfilesPage = () => {
|
|||||||
error,
|
error,
|
||||||
previewData,
|
previewData,
|
||||||
previewingId,
|
previewingId,
|
||||||
|
isRefreshingGitMounts,
|
||||||
formData,
|
formData,
|
||||||
includedProfileIds,
|
includedProfileIds,
|
||||||
dragOverIndex,
|
dragOverIndex,
|
||||||
@@ -111,6 +112,7 @@ export const ConfigProfilesPage = () => {
|
|||||||
availableProfiles={availableProfilesForInclude()}
|
availableProfiles={availableProfilesForInclude()}
|
||||||
previewData={previewData}
|
previewData={previewData}
|
||||||
previewingId={previewingId}
|
previewingId={previewingId}
|
||||||
|
isRefreshingGitMounts={isRefreshingGitMounts}
|
||||||
saveStatus={saveStatus}
|
saveStatus={saveStatus}
|
||||||
error={error}
|
error={error}
|
||||||
onViewChange={setMobileView}
|
onViewChange={setMobileView}
|
||||||
@@ -169,6 +171,7 @@ export const ConfigProfilesPage = () => {
|
|||||||
saveStatus={saveStatus}
|
saveStatus={saveStatus}
|
||||||
previewData={previewData}
|
previewData={previewData}
|
||||||
previewingId={previewingId}
|
previewingId={previewingId}
|
||||||
|
isRefreshingGitMounts={isRefreshingGitMounts}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
toolTypes={toolTypes}
|
toolTypes={toolTypes}
|
||||||
availableProfiles={availableProfilesForInclude()}
|
availableProfiles={availableProfilesForInclude()}
|
||||||
@@ -178,7 +181,9 @@ export const ConfigProfilesPage = () => {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
onPreview={() => selectedProfile && handlePreview(selectedProfile.id)}
|
onPreview={() => selectedProfile && handlePreview(selectedProfile.id)}
|
||||||
onRefreshGitMounts={() => selectedProfile && handleRefreshGitMounts(selectedProfile.id)}
|
onRefreshGitMounts={() =>
|
||||||
|
selectedProfile && handleRefreshGitMounts(selectedProfile.id)
|
||||||
|
}
|
||||||
onAddInclude={addInclude}
|
onAddInclude={addInclude}
|
||||||
onRemoveInclude={removeInclude}
|
onRemoveInclude={removeInclude}
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Move Git Config Profile mounts to profile-scoped canonical host clones. Bind the
|
|||||||
- In-place refresh for existing directory mounts only.
|
- In-place refresh for existing directory mounts only.
|
||||||
- Explicit outcomes for live refresh, restart-required topology changes, and refresh failures.
|
- Explicit outcomes for live refresh, restart-required topology changes, and refresh failures.
|
||||||
- Read-only container Git config mounts.
|
- Read-only container Git config mounts.
|
||||||
|
- An in-progress indicator that prevents duplicate refresh requests in desktop and mobile Config Profile views.
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
|
|
||||||
@@ -21,3 +22,4 @@ Move Git Config Profile mounts to profile-scoped canonical host clones. Bind the
|
|||||||
- Global cross-user clone sharing.
|
- Global cross-user clone sharing.
|
||||||
- Live mount-topology changes, direct-file mappings, or glob match-set changes.
|
- Live mount-topology changes, direct-file mappings, or glob match-set changes.
|
||||||
- Atomic all-files revision switching for processes already reading the mount.
|
- Atomic all-files revision switching for processes already reading the mount.
|
||||||
|
- Automatic refresh of an editor buffer that has already loaded a mounted file.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Each selected Config Profile owns canonical Git clone directories beneath:
|
|||||||
3. Clone into a temporary sibling, then rename on initial creation.
|
3. Clone into a temporary sibling, then rename on initial creation.
|
||||||
4. For refresh, fetch and update the existing working tree in place.
|
4. For refresh, fetch and update the existing working tree in place.
|
||||||
5. Bind directory mappings read-only. Existing containers see changed directory contents without recreation.
|
5. Bind directory mappings read-only. Existing containers see changed directory contents without recreation.
|
||||||
|
6. When profile and Git mount paths overlap, the instance-local composite source must be synchronized in place during refresh; replacing its root directory would leave a running bind mount attached to the old inode.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ Each selected Config Profile owns canonical Git clone directories beneath:
|
|||||||
- Refresh failure is reported without mutating a known-good checkout.
|
- Refresh failure is reported without mutating a known-good checkout.
|
||||||
- No non-Git profile content may be copied into a Git checkout; overlapping targets are rejected or reported.
|
- No non-Git profile content may be copied into a Git checkout; overlapping targets are rejected or reported.
|
||||||
- Containers must not write to shared Git mount sources.
|
- Containers must not write to shared Git mount sources.
|
||||||
|
- A browser editor that already has a file open is not a filesystem watcher; the user must reload that editor buffer after the mounted source changes.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
- [x] Make Git Config Profile mounts read-only and prevent profile-content copy into Git sources.
|
- [x] Make Git Config Profile mounts read-only and prevent profile-content copy into Git sources.
|
||||||
- [x] Add lock-protected clone/fetch/ref checkout refresh that preserves a known-good checkout on failure.
|
- [x] Add lock-protected clone/fetch/ref checkout refresh that preserves a known-good checkout on failure.
|
||||||
- [x] Add save/refresh outcomes for live refresh, restart-required topology, and failures.
|
- [x] Add save/refresh outcomes for live refresh, restart-required topology, and failures.
|
||||||
- [x] Add desktop/mobile feedback for refresh outcomes.
|
- [x] Add an in-progress desktop/mobile indicator that disables duplicate Git-mount refresh requests.
|
||||||
|
- [ ] Synchronize affected instance-local composite mounts in place so live refresh reaches running containers.
|
||||||
- [ ] Add focused resolver/service/API/frontend tests.
|
- [ ] Add focused resolver/service/API/frontend tests.
|
||||||
- [x] Run available verification and document skipped checks.
|
- [x] Run available verification and document skipped checks.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user