mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 23:32:19 +00:00
feat: allow local Docker Hub, GHCR, and custom registry credentials on Community (#1338)
Private registry credentials are no longer paid-only. Community admins can add and manage Docker Hub, GHCR, and custom/self-hosted registry credentials, stored locally on the node. AWS ECR (short-lived token refresh, AWS region) stays on the paid tier. Backend gates ECR per-type via a single helper applied at create, update (using the effective type so an existing row cannot be switched to ECR), the per-id connection test (gated on the stored type), and the stateless test. The admin role and API-token-scope rejection are unchanged on every route. The frontend drops the blanket paywall on the Registries section, filters ECR out of the type selector for Community, and states the ECR requirement inline.
This commit is contained in:
@@ -8,7 +8,7 @@ import { Combobox } from '@/components/ui/combobox';
|
||||
import { ConfirmModal } from '@/components/ui/modal';
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { PaidGate } from './PaidGate';
|
||||
import { useLicense } from '@/context/LicenseContext';
|
||||
import { CapabilityGate } from './CapabilityGate';
|
||||
import { Database, Plus, Trash2, Pencil, RefreshCw, CheckCircle, XCircle, Clock, Zap } from 'lucide-react';
|
||||
import { SettingsPrimaryButton } from './settings/SettingsActions';
|
||||
@@ -88,6 +88,10 @@ function formatDate(ts: number): string {
|
||||
}
|
||||
|
||||
export function RegistriesSection() {
|
||||
// Docker Hub, GHCR, and custom registry credentials are free; AWS ECR
|
||||
// (short-lived token refresh) stays paid, so the ECR type is gated.
|
||||
const { isPaid } = useLicense();
|
||||
const typeOptions = isPaid ? TYPE_OPTIONS : TYPE_OPTIONS.filter(o => o.value !== 'ecr');
|
||||
const [registries, setRegistries] = useState<RegistryItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -283,8 +287,7 @@ export function RegistriesSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<PaidGate>
|
||||
<CapabilityGate capability="registries" featureName="Private Registries">
|
||||
<CapabilityGate capability="registries" featureName="Private Registries">
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-end">
|
||||
<SettingsPrimaryButton size="sm" onClick={() => { resetForm(); setShowForm(true); }}>
|
||||
@@ -298,12 +301,15 @@ export function RegistriesSection() {
|
||||
<div className="space-y-2">
|
||||
<Label>Registry Type</Label>
|
||||
<Combobox
|
||||
options={TYPE_OPTIONS}
|
||||
options={typeOptions}
|
||||
value={formType}
|
||||
onValueChange={(v) => handleTypeChange(v as RegistryType)}
|
||||
placeholder="Select a registry type"
|
||||
searchPlaceholder="Search types..."
|
||||
/>
|
||||
{!isPaid && (
|
||||
<p className="text-xs text-stat-subtitle">AWS ECR requires Admiral.</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Name</Label>
|
||||
@@ -473,7 +479,6 @@ export function RegistriesSection() {
|
||||
</p>
|
||||
</ConfirmModal>
|
||||
</div>
|
||||
</CapabilityGate>
|
||||
</PaidGate>
|
||||
</CapabilityGate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,8 +66,9 @@ describe('settings registry', () => {
|
||||
expect(byId.get('security')?.label).toBe('Vulnerability Scanning');
|
||||
});
|
||||
|
||||
it('preserves the paid gate on Registries', () => {
|
||||
it('opens Registries to Community while keeping it admin-only', () => {
|
||||
const registries = SETTINGS_ITEMS.find(i => i.id === 'registries');
|
||||
expect(registries?.tier).toBe('paid');
|
||||
expect(registries?.tier).toBeNull();
|
||||
expect(registries?.adminOnly).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,7 +139,7 @@ export const SETTINGS_ITEMS: readonly SettingsItemMeta[] = [
|
||||
label: 'Registries',
|
||||
description: 'Private Docker registries and pull credentials.',
|
||||
keywords: ['docker', 'ghcr', 'ecr', 'private', 'pull', 'auth'],
|
||||
tier: 'paid',
|
||||
tier: null,
|
||||
scope: 'global',
|
||||
adminOnly: true,
|
||||
hiddenOnRemote: true,
|
||||
|
||||
Reference in New Issue
Block a user