-
Bug
-
Resolution: Unresolved
-
Critical
-
None
-
quay-v3.17.0
-
None
-
False
-
-
False
-
-
UI Enhancement: Hide Incompatible Policy Options for Org-Mirrored Repositories
Related Issues: PROJQUAY-10040 (Organization Mirroring), PROJQUAY-10672 (Credential Exposure)
Component: New UI (React)
Priority: Major
Problem Statement
When a Quay organization has Organization Mirroring enabled (is in "Mirror state"), the new UI currently shows repository policy configuration options that are incompatible with organization mirroring:
- Proxy Cache - Mutually exclusive with org mirroring
- Immutability Policy - Breaks mirror sync (prevents tag updates)
- Auto-Prune Policy - Conflicts with mirroring (creates sync/delete cycles)
Users can attempt to configure these policies, leading to:
- ❌ Configuration errors and confusion
- ❌ Broken mirror sync functionality
- ❌ Wasted bandwidth (auto-prune deleting mirrored images)
- ❌ Poor user experience
Current Behavior
New UI shows ALL repository settings regardless of org mirror state, allowing users to configure incompatible features.
Expected Behavior
When a repository is part of an active organization mirror:
- ❌ HIDE: Proxy Cache settings tab
- ❌ HIDE: Immutability Policy settings tab
- ❌ HIDE: Auto-Prune Policy settings tab
- ✅ SHOW: Banner indicating repository is org-mirrored
- ✅ SHOW: "Mirroring (Managed by Org)" as read-only
Implementation Requirements
Frontend Changes
New Hook: web/src/hooks/UseOrgMirror.ts
export function useIsOrgMirroredRepo(orgName: string, repoName: string) {
const {orgMirror} = useOrgMirror(orgName);
const {discoveredRepos} = useDiscoveredRepos(orgName);
const isOrgMirrored = useMemo(() => {
if (!orgMirror?.is_enabled) return false;
return discoveredRepos?.some(
repo => repo.repository_name === repoName
) ?? false;
}, [orgMirror, discoveredRepos, repoName]);
return {isOrgMirrored, orgMirrorConfig: orgMirror};
}
Settings Page: web/src/routes/RepositoryDetails/Settings/Settings.tsx
export function RepositorySettings() {
const {isOrgMirrored} = useIsOrgMirroredRepo(org, repo);
return (
<SettingsNav>
<NavItem to="general">General</NavItem>
<NavItem to="access">Access</NavItem>
{/* Hide incompatible features */}
{!isOrgMirrored && (
<>
<NavItem to="proxy-cache">Proxy Cache</NavItem>
<NavItem to="immutability">Immutability</NavItem>
<NavItem to="auto-prune">Auto-Prune</NavItem>
</>
)}
</SettingsNav>
);
}
Route Guards: Add guards to each policy settings page to block direct URL access
Files to Modify
- web/src/hooks/UseOrgMirror.ts
- web/src/routes/RepositoryDetails/Settings/Settings.tsx
- web/src/routes/RepositoryDetails/Settings/ProxyCache.tsx
- web/src/routes/RepositoryDetails/Settings/Immutability.tsx
- web/src/routes/RepositoryDetails/Settings/AutoPrune.tsx
- web/src/routes/OrganizationsList/Organization/Tabs/Mirroring/OrgMirroringConfig.tsx
Acceptance Criteria
✅ AC1: Proxy Cache, Immutability, and Auto-Prune tabs are HIDDEN for org-mirrored repositories
✅ AC2: Direct URL access to hidden features shows "Feature Not Available" error page
✅ AC3: Repository settings shows banner: "This repository is managed by org mirror"
✅ AC4: Organization mirroring page shows warning about disabled features
✅ AC5: Mirroring tab shown as read-only: "Mirroring (Managed by Org)"
✅ AC6: When org mirror disabled, all policy options become available
✅ AC7: Non-mirrored repos in same org show all options normally
User Flow
Step 1: User enables org mirror
POST /api/v1/organization/myorg/mirror
Step 2: Worker discovers 50 repositories
Step 3: User navigates to mirrored repo settings
URL: /repository/myorg/discovered-app/settings
Expected:
┌───────────────────────────────────┐
│ 🔄 Org-Mirrored Repository │
│ Source: harbor.example.com/... │
└───────────────────────────────────┘
Settings:
├─ General ✅
├─ Access ✅
├─ Mirroring (Managed by Org) 🔒
└─ [Proxy/Immutability/Auto-Prune HIDDEN]
Step 4: User tries direct URL
URL: /repository/myorg/app/settings/proxy-cache
Result: "Feature Not Available" error page
Testing
Unit Tests:
describe('useIsOrgMirroredRepo', () => { it('returns true for org-mirrored repo'); it('returns false for non-mirrored repo'); it('returns false when org mirror disabled'); });
E2E Tests:
it('hides policy options for mirrored repos', () => { cy.visit('/repository/testorg/mirrored-app/settings'); cy.contains('Proxy Cache').should('not.exist'); cy.contains('Immutability').should('not.exist'); cy.contains('Auto-Prune').should('not.exist'); }); it('blocks direct URL access', () => { cy.visit('/repository/testorg/mirrored-app/settings/proxy-cache'); cy.contains('Feature Not Available').should('exist'); });
Why This Matters
- ✅ Prevents user confusion
- ✅ Avoids misconfiguration
- ✅ Reduces support tickets
- ✅ Follows UX best practices
- ✅ Low implementation effort
- ✅ Critical for PROJQUAY-10040 usability
Alternative Approaches
Option 1: Show but disable (NOT recommended)
- Creates clutter
- Users confused why disabled
Option 2: Show with error on save (NOT recommended)
- Bad UX
- Wastes user time
Option 3: Hide completely (RECOMMENDED)
- Clean UI
- Prevents mistakes
- Clear expectations