Compare commits
2 Commits
3c2ab70ef0
...
fix/upstre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a93bf84b83 | ||
|
|
2c123d8d0f |
@@ -11,10 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Upstream source connectivity test no longer follows redirects, fixing "Exceeded maximum allowed redirects" error with Artifactory proxies (#107)
|
- Upstream source connectivity test no longer follows redirects, fixing "Exceeded maximum allowed redirects" error with Artifactory proxies (#107)
|
||||||
- Upstream sources table now has dedicated "Test" column with OK/Error status badges (#107)
|
|
||||||
- Test runs automatically after saving a new or updated upstream source (#107)
|
- Test runs automatically after saving a new or updated upstream source (#107)
|
||||||
- Error states in upstream sources table are now clickable to show full error details in a modal (#107)
|
- Test status now shows as colored dots (green=success, red=error) instead of text badges (#107)
|
||||||
|
- Clicking red dot shows error details in a modal (#107)
|
||||||
- Source name column no longer wraps text for better table layout (#107)
|
- Source name column no longer wraps text for better table layout (#107)
|
||||||
|
- Renamed "Cache Management" page to "Upstream Sources" (#107)
|
||||||
|
- Moved Delete button from table row to edit modal for cleaner table layout (#107)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Removed `is_public` field from upstream sources - all sources are now treated as internal/private (#107)
|
- Removed `is_public` field from upstream sources - all sources are now treated as internal/private (#107)
|
||||||
@@ -22,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Removed seeding of public registry URLs (npm-public, pypi-public, maven-central, docker-hub) (#107)
|
- Removed seeding of public registry URLs (npm-public, pypi-public, maven-central, docker-hub) (#107)
|
||||||
- Removed "Public" badge and checkbox from upstream sources UI (#107)
|
- Removed "Public" badge and checkbox from upstream sources UI (#107)
|
||||||
- Removed "Allow Public Internet" toggle from cache settings UI (#107)
|
- Removed "Allow Public Internet" toggle from cache settings UI (#107)
|
||||||
|
- Removed "Global Settings" section from cache management UI - auto-create system projects is always enabled (#107)
|
||||||
|
- Removed unused CacheSettings frontend types and API functions (#107)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Added `ORCHARD_PURGE_SEED_DATA` environment variable support to stage helm values to remove seed data from long-running deployments (#107)
|
- Added `ORCHARD_PURGE_SEED_DATA` environment variable support to stage helm values to remove seed data from long-running deployments (#107)
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ import {
|
|||||||
UpstreamSourceCreate,
|
UpstreamSourceCreate,
|
||||||
UpstreamSourceUpdate,
|
UpstreamSourceUpdate,
|
||||||
UpstreamSourceTestResult,
|
UpstreamSourceTestResult,
|
||||||
CacheSettings,
|
|
||||||
CacheSettingsUpdate,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const API_BASE = '/api/v1';
|
const API_BASE = '/api/v1';
|
||||||
@@ -748,21 +746,3 @@ export async function testUpstreamSource(id: string): Promise<UpstreamSourceTest
|
|||||||
});
|
});
|
||||||
return handleResponse<UpstreamSourceTestResult>(response);
|
return handleResponse<UpstreamSourceTestResult>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache Settings Admin API
|
|
||||||
export async function getCacheSettings(): Promise<CacheSettings> {
|
|
||||||
const response = await fetch(`${API_BASE}/admin/cache-settings`, {
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
return handleResponse<CacheSettings>(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCacheSettings(data: CacheSettingsUpdate): Promise<CacheSettings> {
|
|
||||||
const response = await fetch(`${API_BASE}/admin/cache-settings`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
return handleResponse<CacheSettings>(response);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,74 +34,6 @@
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Settings Section */
|
|
||||||
.settings-section {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-name {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-primary);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-description {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button.on {
|
|
||||||
background-color: #28a745;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button.off {
|
|
||||||
background-color: #dc3545;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sources Section */
|
/* Sources Section */
|
||||||
.sources-section {
|
.sources-section {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
@@ -207,35 +139,37 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result {
|
.test-cell {
|
||||||
display: inline-flex;
|
text-align: center;
|
||||||
align-items: center;
|
width: 2rem;
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result.success {
|
.test-dot {
|
||||||
background-color: #e8f5e9;
|
font-size: 1rem;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-dot.success {
|
||||||
color: #2e7d32;
|
color: #2e7d32;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result.failure {
|
.test-dot.failure {
|
||||||
background-color: #ffebee;
|
|
||||||
color: #c62828;
|
color: #c62828;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result.failure:hover {
|
.test-dot.failure:hover {
|
||||||
background-color: #ffcdd2;
|
color: #b71c1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result.testing {
|
.test-dot.testing {
|
||||||
background-color: #e3f2fd;
|
|
||||||
color: #1976d2;
|
color: #1976d2;
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.4; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Error Modal */
|
/* Error Modal */
|
||||||
@@ -406,9 +340,14 @@
|
|||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
gap: 0.5rem;
|
align-items: center;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-actions-right {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ import {
|
|||||||
updateUpstreamSource,
|
updateUpstreamSource,
|
||||||
deleteUpstreamSource,
|
deleteUpstreamSource,
|
||||||
testUpstreamSource,
|
testUpstreamSource,
|
||||||
getCacheSettings,
|
|
||||||
updateCacheSettings,
|
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import { UpstreamSource, CacheSettings, SourceType, AuthType } from '../types';
|
import { UpstreamSource, SourceType, AuthType } from '../types';
|
||||||
import './AdminCachePage.css';
|
import './AdminCachePage.css';
|
||||||
|
|
||||||
const SOURCE_TYPES: SourceType[] = ['npm', 'pypi', 'maven', 'docker', 'helm', 'nuget', 'deb', 'rpm', 'generic'];
|
const SOURCE_TYPES: SourceType[] = ['npm', 'pypi', 'maven', 'docker', 'helm', 'nuget', 'deb', 'rpm', 'generic'];
|
||||||
@@ -25,11 +23,6 @@ function AdminCachePage() {
|
|||||||
const [loadingSources, setLoadingSources] = useState(true);
|
const [loadingSources, setLoadingSources] = useState(true);
|
||||||
const [sourcesError, setSourcesError] = useState<string | null>(null);
|
const [sourcesError, setSourcesError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Cache settings state
|
|
||||||
const [settings, setSettings] = useState<CacheSettings | null>(null);
|
|
||||||
const [loadingSettings, setLoadingSettings] = useState(true);
|
|
||||||
const [settingsError, setSettingsError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Create/Edit form state
|
// Create/Edit form state
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [editingSource, setEditingSource] = useState<UpstreamSource | null>(null);
|
const [editingSource, setEditingSource] = useState<UpstreamSource | null>(null);
|
||||||
@@ -53,9 +46,6 @@ function AdminCachePage() {
|
|||||||
// Delete confirmation state
|
// Delete confirmation state
|
||||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Settings update state
|
|
||||||
const [updatingSettings, setUpdatingSettings] = useState(false);
|
|
||||||
|
|
||||||
// Success message
|
// Success message
|
||||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -72,7 +62,6 @@ function AdminCachePage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && user.is_admin) {
|
if (user && user.is_admin) {
|
||||||
loadSources();
|
loadSources();
|
||||||
loadSettings();
|
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
@@ -96,19 +85,6 @@ function AdminCachePage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSettings() {
|
|
||||||
setLoadingSettings(true);
|
|
||||||
setSettingsError(null);
|
|
||||||
try {
|
|
||||||
const data = await getCacheSettings();
|
|
||||||
setSettings(data);
|
|
||||||
} catch (err) {
|
|
||||||
setSettingsError(err instanceof Error ? err.message : 'Failed to load settings');
|
|
||||||
} finally {
|
|
||||||
setLoadingSettings(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openCreateForm() {
|
function openCreateForm() {
|
||||||
setEditingSource(null);
|
setEditingSource(null);
|
||||||
setFormData({
|
setFormData({
|
||||||
@@ -255,30 +231,6 @@ function AdminCachePage() {
|
|||||||
setShowErrorModal(true);
|
setShowErrorModal(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSettingsToggle(field: 'auto_create_system_projects') {
|
|
||||||
if (!settings) return;
|
|
||||||
|
|
||||||
// Check if env override is active
|
|
||||||
const isOverridden = field === 'auto_create_system_projects' && settings.auto_create_system_projects_env_override !== null;
|
|
||||||
|
|
||||||
if (isOverridden) {
|
|
||||||
alert('This setting is overridden by an environment variable and cannot be changed via UI.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUpdatingSettings(true);
|
|
||||||
try {
|
|
||||||
const update = { [field]: !settings[field] };
|
|
||||||
const newSettings = await updateCacheSettings(update);
|
|
||||||
setSettings(newSettings);
|
|
||||||
setSuccessMessage(`Setting "${field}" updated`);
|
|
||||||
} catch (err) {
|
|
||||||
setSettingsError(err instanceof Error ? err.message : 'Failed to update settings');
|
|
||||||
} finally {
|
|
||||||
setUpdatingSettings(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authLoading) {
|
if (authLoading) {
|
||||||
return <div className="admin-cache-page">Loading...</div>;
|
return <div className="admin-cache-page">Loading...</div>;
|
||||||
}
|
}
|
||||||
@@ -293,49 +245,13 @@ function AdminCachePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-cache-page">
|
<div className="admin-cache-page">
|
||||||
<h1>Cache Management</h1>
|
<h1>Upstream Sources</h1>
|
||||||
|
|
||||||
{successMessage && <div className="success-message">{successMessage}</div>}
|
{successMessage && <div className="success-message">{successMessage}</div>}
|
||||||
|
|
||||||
{/* Cache Settings Section */}
|
|
||||||
<section className="settings-section">
|
|
||||||
<h2>Global Settings</h2>
|
|
||||||
{loadingSettings ? (
|
|
||||||
<p>Loading settings...</p>
|
|
||||||
) : settingsError ? (
|
|
||||||
<div className="error-message">{settingsError}</div>
|
|
||||||
) : settings ? (
|
|
||||||
<div className="settings-grid">
|
|
||||||
<div className="setting-item">
|
|
||||||
<label className="toggle-label">
|
|
||||||
<span className="setting-name">
|
|
||||||
Auto-create System Projects
|
|
||||||
{settings.auto_create_system_projects_env_override !== null && (
|
|
||||||
<span className="env-badge" title="Overridden by environment variable">
|
|
||||||
ENV
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span className="setting-description">
|
|
||||||
Automatically create system projects (_npm, _pypi, etc.) on first cache request.
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
className={`toggle-button ${settings.auto_create_system_projects ? 'on' : 'off'}`}
|
|
||||||
onClick={() => handleSettingsToggle('auto_create_system_projects')}
|
|
||||||
disabled={updatingSettings || settings.auto_create_system_projects_env_override !== null}
|
|
||||||
>
|
|
||||||
{settings.auto_create_system_projects ? 'Enabled' : 'Disabled'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Upstream Sources Section */}
|
{/* Upstream Sources Section */}
|
||||||
<section className="sources-section">
|
<section className="sources-section">
|
||||||
<div className="section-header">
|
<div className="section-header">
|
||||||
<h2>Upstream Sources</h2>
|
|
||||||
<button className="btn btn-primary" onClick={openCreateForm}>
|
<button className="btn btn-primary" onClick={openCreateForm}>
|
||||||
Add Source
|
Add Source
|
||||||
</button>
|
</button>
|
||||||
@@ -357,7 +273,7 @@ function AdminCachePage() {
|
|||||||
<th>Priority</th>
|
<th>Priority</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Source</th>
|
<th>Source</th>
|
||||||
<th>Test</th>
|
<th></th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -384,26 +300,20 @@ function AdminCachePage() {
|
|||||||
'Database'
|
'Database'
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td className="test-cell">
|
||||||
{testingId === source.id ? (
|
{testingId === source.id ? (
|
||||||
<span className="test-result testing">Testing...</span>
|
<span className="test-dot testing" title="Testing...">●</span>
|
||||||
) : testResults[source.id] ? (
|
) : testResults[source.id] ? (
|
||||||
testResults[source.id].success ? (
|
testResults[source.id].success ? (
|
||||||
<span className="test-result success" title={testResults[source.id].message}>
|
<span className="test-dot success" title={testResults[source.id].message}>●</span>
|
||||||
OK
|
|
||||||
</span>
|
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className="test-result failure"
|
className="test-dot failure"
|
||||||
title="Click to see details"
|
title="Click to see error"
|
||||||
onClick={() => showError(source.name, testResults[source.id].message)}
|
onClick={() => showError(source.name, testResults[source.id].message)}
|
||||||
>
|
>●</span>
|
||||||
Error
|
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : null}
|
||||||
<span className="test-result" style={{ opacity: 0.5 }}>—</span>
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
<td className="actions-cell">
|
<td className="actions-cell">
|
||||||
<button
|
<button
|
||||||
@@ -414,18 +324,9 @@ function AdminCachePage() {
|
|||||||
Test
|
Test
|
||||||
</button>
|
</button>
|
||||||
{source.source !== 'env' && (
|
{source.source !== 'env' && (
|
||||||
<>
|
|
||||||
<button className="btn btn-sm" onClick={() => openEditForm(source)}>
|
<button className="btn btn-sm" onClick={() => openEditForm(source)}>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-danger"
|
|
||||||
onClick={() => handleDelete(source)}
|
|
||||||
disabled={deletingId === source.id}
|
|
||||||
>
|
|
||||||
{deletingId === source.id ? 'Deleting...' : 'Delete'}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -561,6 +462,20 @@ function AdminCachePage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="form-actions">
|
<div className="form-actions">
|
||||||
|
{editingSource && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={() => {
|
||||||
|
handleDelete(editingSource);
|
||||||
|
setShowForm(false);
|
||||||
|
}}
|
||||||
|
disabled={deletingId === editingSource.id}
|
||||||
|
>
|
||||||
|
{deletingId === editingSource.id ? 'Deleting...' : 'Delete'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<div className="form-actions-right">
|
||||||
<button type="button" className="btn" onClick={() => setShowForm(false)}>
|
<button type="button" className="btn" onClick={() => setShowForm(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -568,6 +483,7 @@ function AdminCachePage() {
|
|||||||
{isSaving ? 'Saving...' : editingSource ? 'Update' : 'Create'}
|
{isSaving ? 'Saving...' : editingSource ? 'Update' : 'Create'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -557,15 +557,3 @@ export interface UpstreamSourceTestResult {
|
|||||||
source_id: string;
|
source_id: string;
|
||||||
source_name: string;
|
source_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache Settings types
|
|
||||||
export interface CacheSettings {
|
|
||||||
auto_create_system_projects: boolean;
|
|
||||||
auto_create_system_projects_env_override: boolean | null;
|
|
||||||
created_at: string | null;
|
|
||||||
updated_at: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CacheSettingsUpdate {
|
|
||||||
auto_create_system_projects?: boolean;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user