version update
This commit is contained in:
24
.env.example
24
.env.example
@@ -1,24 +0,0 @@
|
||||
# Docker Registry Browser Environment Configuration
|
||||
# Copy this file to .env and update with your values
|
||||
|
||||
# Registry host and port (without protocol)
|
||||
REGISTRY_HOST=your-registry.example.com:5000
|
||||
|
||||
# Protocol to use for registry connection
|
||||
REGISTRY_PROTOCOL=https
|
||||
|
||||
# Optional: Registry authentication
|
||||
REGISTRY_USERNAME=your-username
|
||||
REGISTRY_PASSWORD=your-password
|
||||
|
||||
# Development Examples:
|
||||
# For local registry: REGISTRY_HOST=localhost:5000
|
||||
# For Harbor: REGISTRY_HOST=harbor.example.com
|
||||
# For Docker Hub: REGISTRY_HOST=registry-1.docker.io (not typically needed)
|
||||
# For AWS ECR: REGISTRY_HOST=123456789012.dkr.ecr.us-west-2.amazonaws.com
|
||||
|
||||
# Development Usage:
|
||||
# 1. Copy this file: cp .env.example .env
|
||||
# 2. Edit .env with your registry details
|
||||
# 3. Start development server: npm start
|
||||
# 4. Or use convenience scripts: ./start-dev.sh
|
||||
40
Dockerfile
40
Dockerfile
@@ -1,4 +1,4 @@
|
||||
# Multi-stage build for optimized production image
|
||||
# Multi-stage build for Docker Registry Browser with Node.js proxy
|
||||
FROM node:18-alpine AS build
|
||||
|
||||
# Set working directory
|
||||
@@ -14,41 +14,43 @@ COPY . .
|
||||
# Build the Angular application
|
||||
RUN npm run build -- --configuration production
|
||||
|
||||
# Production stage
|
||||
FROM nginx:1.25-alpine
|
||||
# Production stage with Node.js proxy server
|
||||
FROM node:18-alpine
|
||||
|
||||
# Install curl for health checks
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install production dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production --silent
|
||||
|
||||
# Copy built application
|
||||
COPY --from=build /app/dist/docker-registry-browser /usr/share/nginx/html
|
||||
COPY --from=build /app/dist ./dist
|
||||
|
||||
# Copy nginx configuration
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Copy entrypoint script
|
||||
# Copy server and entrypoint
|
||||
COPY server.js ./
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Create assets directory for env.js and set permissions
|
||||
RUN mkdir -p /usr/share/nginx/html/assets && \
|
||||
chown -R nginx:nginx /usr/share/nginx/html && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
chown -R nginx:nginx /etc/nginx/conf.d && \
|
||||
chmod -R 755 /usr/share/nginx/html
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1001 -S appuser && \
|
||||
adduser -S -D -H -u 1001 -h /app -s /sbin/nologin -G appuser appuser && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
# Add labels for better container management
|
||||
LABEL maintainer="Your Name <your.email@example.com>"
|
||||
LABEL description="Docker Registry Browser - A web interface for browsing Docker registries"
|
||||
LABEL version="1.0.0"
|
||||
LABEL description="Docker Registry Browser with Node.js proxy - A web interface for browsing Docker registries"
|
||||
LABEL version="1.1.0"
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:80/ || exit 1
|
||||
CMD curl -f http://localhost:80/health || exit 1
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Use custom entrypoint to generate environment config (run as root for file creation)
|
||||
# Use Node.js proxy entrypoint
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
316
README.md
316
README.md
@@ -1,252 +1,186 @@
|
||||
# Docker Registry Browser - Production Deployment
|
||||
# Docker Registry Browser
|
||||
|
||||
A modern, responsive web interface for browsing Docker registries with support for both Docker v2 and OCI manifest formats.
|
||||
A simple, reliable web interface for browsing Docker registries with a Node.js proxy to handle CORS issues. Perfect for Unraid users who want to easily view and manage their Docker registry.
|
||||
|
||||
## Features
|
||||
|
||||
- Browse repositories and tags
|
||||
- View detailed image information (layers, environment, labels, etc.)
|
||||
- Copy docker pull commands
|
||||
- Push command generator with examples
|
||||
- Dark/Light mode toggle
|
||||
- Fully responsive design
|
||||
- Support for OCI and Docker v2 manifests
|
||||
- Multi-platform image support
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker installed and running
|
||||
- Access to a Docker registry (local or remote)
|
||||
- Network connectivity to the registry
|
||||
- Browse repositories and tags in your Docker registry
|
||||
- View detailed image information (size, layers, creation date, etc.)
|
||||
- Copy docker pull commands with one click
|
||||
- Clean, responsive web interface
|
||||
- **CORS-free** - Uses Node.js proxy to avoid browser restrictions
|
||||
- Works with any Docker Registry v2 compatible registry
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: Docker Run
|
||||
|
||||
### Build and Run
|
||||
```bash
|
||||
docker run -d \
|
||||
--name docker-registry-browser \
|
||||
-p 8080:80 \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
-e REGISTRY_HOST=localhost:5000 \
|
||||
-e REGISTRY_PROTOCOL=http \
|
||||
programmingpug/docker-registry-browser:latest
|
||||
```
|
||||
|
||||
### Option 2: Docker Compose
|
||||
|
||||
```bash
|
||||
git clone https://github.com/programmingPug/docker-registry-browser.git
|
||||
cd docker-registry-browser
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Option 3: Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/programmingPug/docker-registry-browser.git
|
||||
cd docker-registry-browser
|
||||
# Build the image
|
||||
docker build -t docker-registry-browser .
|
||||
docker run -d -p 8080:80 --add-host=host.docker.internal:host-gateway docker-registry-browser
|
||||
|
||||
# Run with your registry
|
||||
docker run -d -p 8080:80 \
|
||||
-e REGISTRY_HOST=your-registry:5000 \
|
||||
-e REGISTRY_PROTOCOL=http \
|
||||
docker-registry-browser
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
```bash
|
||||
# Set your registry in .env or environment
|
||||
export REGISTRY_HOST=192.168.1.100:5000
|
||||
export REGISTRY_PROTOCOL=http
|
||||
|
||||
# Start with compose
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Unraid Installation
|
||||
|
||||
### Method 1: Community Applications (Recommended)
|
||||
### 1. Community Applications (Recommended)
|
||||
- Go to **Apps** tab in Unraid
|
||||
- Search for "Docker Registry Browser"
|
||||
- Click **Install**
|
||||
|
||||
1. In Unraid, go to **Apps** tab
|
||||
2. Search for "Docker Registry Browser"
|
||||
3. Click **Install**
|
||||
4. Configure the settings and click **Apply**
|
||||
### 2. Manual Configuration
|
||||
Use these settings in the Unraid template:
|
||||
|
||||
### Method 2: Manual Template
|
||||
#### Basic Configuration
|
||||
- **Registry Host**: `192.168.1.100:5000` (your Unraid IP and registry port)
|
||||
- **Registry Protocol**: `http` (or `https` for secure registries)
|
||||
|
||||
1. In Unraid, go to **Docker** tab
|
||||
2. Click **Add Container**
|
||||
3. Set **Template** to the template URL or upload the XML template
|
||||
4. Configure the required settings
|
||||
5. Click **Apply**
|
||||
#### For Authenticated Registries (Optional)
|
||||
- **Username**: Your registry username
|
||||
- **Password**: Your registry password
|
||||
|
||||
### Method 3: Docker Compose (Unraid 6.12+)
|
||||
### 3. Common Unraid Examples
|
||||
|
||||
1. Install the "Compose Manager" plugin
|
||||
2. Create a new compose stack with the provided `docker-compose.yml`
|
||||
3. Deploy the stack
|
||||
#### Local Registry on Unraid Host
|
||||
```
|
||||
Registry Host: 192.168.1.100:5000
|
||||
Registry Protocol: http
|
||||
```
|
||||
|
||||
## Supported Registries
|
||||
#### Registry Container on Unraid
|
||||
```
|
||||
Registry Host: registry:5000
|
||||
Registry Protocol: http
|
||||
```
|
||||
|
||||
This browser works with any Docker Registry v2 compatible registry, including:
|
||||
|
||||
- **Local Docker Registry** - Self-hosted registry containers
|
||||
- **Harbor** - Open source cloud native registry
|
||||
- **AWS ECR** - Amazon Elastic Container Registry
|
||||
- **Azure Container Registry** - Microsoft's container registry
|
||||
- **Google Container Registry** - Google Cloud's container registry
|
||||
- **GitLab Container Registry** - GitLab's integrated registry
|
||||
- **Nexus Repository** - Sonatype's repository manager
|
||||
- **Artifactory** - JFrog's universal repository manager
|
||||
- **Docker Hub** - (limited support for browsing)
|
||||
|
||||
### Registry Requirements
|
||||
|
||||
- Docker Registry API v2 support
|
||||
- CORS headers configured (for web access)
|
||||
- Network accessibility from the browser container
|
||||
#### Remote Secure Registry
|
||||
```
|
||||
Registry Host: my-registry.com:443
|
||||
Registry Protocol: https
|
||||
Username: myuser
|
||||
Password: mypass
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `REGISTRY_HOST` | `localhost:5000` | Docker registry hostname and port |
|
||||
| `REGISTRY_PROTOCOL` | `http` | Protocol (http/https) |
|
||||
| `REGISTRY_USERNAME` | - | Registry username (optional) |
|
||||
| `REGISTRY_PASSWORD` | - | Registry password (optional) |
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `REGISTRY_HOST` | Yes | `localhost:5000` | Registry hostname and port |
|
||||
| `REGISTRY_PROTOCOL` | Yes | `http` | `http` or `https` |
|
||||
| `REGISTRY_USERNAME` | No | - | Username for authenticated registries |
|
||||
| `REGISTRY_PASSWORD` | No | - | Password for authenticated registries |
|
||||
|
||||
### Unraid Configuration
|
||||
## How It Works
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| **WebUI Port** | `8080` | Port for web interface |
|
||||
| **Registry Host** | `localhost:5000` | Your registry address |
|
||||
| **Registry Protocol** | `http` | http or https |
|
||||
| **Registry Username** | - | Optional authentication |
|
||||
| **Registry Password** | - | Optional authentication |
|
||||
The application uses a **Node.js Express server** with `http-proxy-middleware` to proxy all registry API calls. This solves CORS issues that would otherwise prevent browsers from accessing Docker registries directly.
|
||||
|
||||
## Usage
|
||||
### Request Flow
|
||||
```
|
||||
Browser → /api/v2/_catalog → Node.js Proxy → Registry
|
||||
Browser ← Response with CORS headers ← Node.js Proxy ← Registry
|
||||
```
|
||||
|
||||
1. Access the web interface at `http://your-server:8080`
|
||||
2. Browse repositories on the left panel
|
||||
3. Select a repository to view its tags
|
||||
4. Click the info button to view detailed image information
|
||||
5. Use the menu for push commands and settings
|
||||
6. Toggle dark/light mode using the theme button in the toolbar
|
||||
### Why This Works
|
||||
- **No CORS Issues**: Proxy adds proper CORS headers automatically
|
||||
- **Authentication**: Basic auth handled transparently by proxy
|
||||
- **Any Registry**: Works with any Docker Registry v2 compatible API
|
||||
- **Production Ready**: Uses proven `http-proxy-middleware` library
|
||||
|
||||
## Features Guide
|
||||
## Supported Registries
|
||||
|
||||
### Dark Mode
|
||||
- Toggle between light and dark themes using the moon/sun icon in the toolbar
|
||||
- Theme preference is saved locally and persists between sessions
|
||||
- All UI components are properly themed for optimal visibility in both modes
|
||||
|
||||
### Browsing Images
|
||||
- Repository list shows all available repositories
|
||||
- Click a repository to load its tags
|
||||
- Search repositories using the search field
|
||||
- View tag details by clicking the info button
|
||||
|
||||
### Push Commands
|
||||
- Access via the menu (three dots) in the toolbar
|
||||
- Get step-by-step instructions for pushing images
|
||||
- Copy commands to clipboard
|
||||
- Includes multi-architecture build instructions
|
||||
|
||||
### Image Details
|
||||
- View comprehensive image information
|
||||
- See layer details, environment variables, labels
|
||||
- Check image size, architecture, and creation date
|
||||
- Inspect exposed ports and volumes
|
||||
Works with any Docker Registry v2 compatible registry:
|
||||
- **Docker Registry** (open source)
|
||||
- **Harbor**
|
||||
- **AWS ECR**
|
||||
- **Azure Container Registry**
|
||||
- **Google Container Registry**
|
||||
- **GitLab Container Registry**
|
||||
- **Nexus Repository**
|
||||
- **Artifactory**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Registry Connection Issues
|
||||
### "Cannot connect to registry proxy"
|
||||
- Ensure you're accessing `http://your-server:8080`, not a dev server
|
||||
- Check container logs: `docker logs container-name`
|
||||
- Verify `REGISTRY_HOST` and `REGISTRY_PROTOCOL` are correct
|
||||
|
||||
**Problem**: Cannot connect to registry
|
||||
**Solutions**:
|
||||
- Verify `REGISTRY_HOST` is correct (hostname:port format)
|
||||
- Check if registry is accessible from container
|
||||
- For local registries, ensure `--add-host=host.docker.internal:host-gateway` is set
|
||||
### "Registry server error (502)"
|
||||
- Registry is not accessible from the container
|
||||
- Check if registry is running: `docker ps | grep registry`
|
||||
- Test registry connectivity: `curl http://your-registry:5000/v2/`
|
||||
- For Unraid: Use server IP, not `localhost`
|
||||
|
||||
### CORS Issues
|
||||
|
||||
**Problem**: API requests blocked by CORS
|
||||
**Solutions**:
|
||||
- The nginx configuration includes CORS headers
|
||||
- Ensure your registry allows cross-origin requests
|
||||
- For development, use the included proxy configuration
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
**Problem**: 401 Unauthorized errors
|
||||
**Solutions**:
|
||||
- Set `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` environment variables
|
||||
- Verify credentials are correct for your registry
|
||||
- Check if registry requires authentication
|
||||
|
||||
### Manifest Issues
|
||||
|
||||
**Problem**: "OCI index found" or manifest parsing errors
|
||||
**Solutions**:
|
||||
- Current version supports OCI manifests
|
||||
- Ensure registry supports Docker Registry API v2
|
||||
- Check if image manifests are properly formatted
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
**Problem**: Port 8080 already in use
|
||||
**Solutions**:
|
||||
- Change the host port: `-p 8081:80` instead of `-p 8080:80`
|
||||
- Stop conflicting services or use different ports
|
||||
- Check what's using the port: `netstat -tlnp | grep 8080`
|
||||
### "No repositories found"
|
||||
- Registry is accessible but empty (no images pushed yet)
|
||||
- Authentication may be required but not configured
|
||||
- Check registry has repositories: `curl http://your-registry:5000/v2/_catalog`
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
### Build from Source
|
||||
```bash
|
||||
git clone https://github.com/your-username/docker-registry-browser.git
|
||||
cd docker-registry-browser
|
||||
docker build -t docker-registry-browser .
|
||||
```
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Development server
|
||||
# Start Angular dev server (for frontend development)
|
||||
npm start
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Build Docker image
|
||||
docker build -t docker-registry-browser .
|
||||
# Start Node.js proxy server (for backend testing)
|
||||
npm run server
|
||||
```
|
||||
|
||||
### Contributing
|
||||
## Health Monitoring
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Test thoroughly
|
||||
5. Submit a pull request
|
||||
|
||||
## Health Check
|
||||
|
||||
The container includes a health check endpoint at `/health` that returns:
|
||||
- `200 OK` with "healthy" response when running properly
|
||||
- Checks every 30 seconds with 3 retries
|
||||
The container provides health check endpoints:
|
||||
- **`/health`** - Application health status
|
||||
- **`/proxy-status`** - Proxy configuration status
|
||||
|
||||
## Security
|
||||
|
||||
- Runs as non-root user (nginx:nginx)
|
||||
- Includes security headers
|
||||
- Runs as non-root user
|
||||
- No sensitive data stored in container
|
||||
- Registry credentials passed via environment variables
|
||||
- Registry credentials only used for API authentication
|
||||
- All communication proxied through secure server
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
|
||||
## Changelog
|
||||
## Support
|
||||
|
||||
### v1.1.0
|
||||
- Updated Angular to v17
|
||||
- Improved error handling
|
||||
- Enhanced UI/UX
|
||||
- Better multi-platform support
|
||||
- Optimized build process
|
||||
- **GitHub Issues**: Report bugs and request features
|
||||
- **Unraid Forums**: Community support for Unraid-specific questions
|
||||
- **Documentation**: Check this README for common solutions
|
||||
|
||||
### v1.0.0
|
||||
- Initial release
|
||||
- Support for Docker v2 and OCI manifests
|
||||
- Responsive design
|
||||
- Push command generation
|
||||
- Dark/Light mode
|
||||
- Multi-platform image support
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Frontend**: Angular 17 with Material Design
|
||||
- **Backend**: Node.js Express with http-proxy-middleware
|
||||
- **Container**: Alpine Linux for minimal size
|
||||
- **Architecture**: Multi-stage Docker build for optimized production image
|
||||
96
build.bat
96
build.bat
@@ -1,96 +0,0 @@
|
||||
@echo off
|
||||
REM Docker Registry Browser - Build and Deploy Script for Windows
|
||||
REM Usage: build.bat [tag] [registry]
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Configuration
|
||||
set IMAGE_NAME=docker-registry-browser
|
||||
set DEFAULT_TAG=latest
|
||||
set DEFAULT_REGISTRY=
|
||||
|
||||
REM Parse arguments
|
||||
if "%1"=="" (
|
||||
set TAG=%DEFAULT_TAG%
|
||||
) else (
|
||||
set TAG=%1
|
||||
)
|
||||
|
||||
if "%2"=="" (
|
||||
set REGISTRY=%DEFAULT_REGISTRY%
|
||||
) else (
|
||||
set REGISTRY=%2
|
||||
)
|
||||
|
||||
REM Build the full image name
|
||||
if "%REGISTRY%"=="" (
|
||||
set FULL_IMAGE_NAME=%IMAGE_NAME%:%TAG%
|
||||
) else (
|
||||
set FULL_IMAGE_NAME=%REGISTRY%/%IMAGE_NAME%:%TAG%
|
||||
)
|
||||
|
||||
echo Building Docker Registry Browser...
|
||||
echo Image: %FULL_IMAGE_NAME%
|
||||
echo.
|
||||
|
||||
REM Build the Docker image
|
||||
echo Building Docker image...
|
||||
docker build -t "%FULL_IMAGE_NAME%" .
|
||||
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo Build failed!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Build completed successfully!
|
||||
echo.
|
||||
echo To run the container with environment variables:
|
||||
echo docker run -d --name docker-registry-browser ^
|
||||
echo -p 8080:80 ^
|
||||
echo --add-host=host.docker.internal:host-gateway ^
|
||||
echo -e REGISTRY_HOST=your-registry.com:5000 ^
|
||||
echo -e REGISTRY_PROTOCOL=https ^
|
||||
echo %FULL_IMAGE_NAME%
|
||||
echo.
|
||||
echo Or use docker-compose with .env file:
|
||||
echo copy .env.example .env
|
||||
echo # Edit .env with your values
|
||||
echo docker-compose up -d
|
||||
echo.
|
||||
echo To push to registry (if configured):
|
||||
if "%REGISTRY%"=="" (
|
||||
echo Please specify a registry: build.bat %TAG% your-registry.com
|
||||
) else (
|
||||
echo docker push %FULL_IMAGE_NAME%
|
||||
)
|
||||
echo.
|
||||
|
||||
REM Optional: Run the container immediately
|
||||
set /p REPLY="Do you want to run the container now? (y/N): "
|
||||
if /i "%REPLY%"=="y" (
|
||||
echo Starting container...
|
||||
|
||||
REM Check if .env file exists
|
||||
if exist ".env" (
|
||||
echo Using .env file for configuration...
|
||||
docker run -d --name docker-registry-browser -p 8080:80 --add-host=host.docker.internal:host-gateway --env-file .env "%FULL_IMAGE_NAME%"
|
||||
) else (
|
||||
echo No .env file found, using default values...
|
||||
echo Copy .env.example to .env and edit it for your registry configuration.
|
||||
docker run -d --name docker-registry-browser -p 8080:80 --add-host=host.docker.internal:host-gateway -e REGISTRY_HOST=localhost:5000 -e REGISTRY_PROTOCOL=http "%FULL_IMAGE_NAME%"
|
||||
)
|
||||
|
||||
if %ERRORLEVEL% equ 0 (
|
||||
echo.
|
||||
echo Container started successfully!
|
||||
echo Access the application at: http://localhost:8080
|
||||
echo View container logs: docker logs docker-registry-browser
|
||||
echo Stop container: docker stop docker-registry-browser
|
||||
) else (
|
||||
echo Failed to start container!
|
||||
)
|
||||
)
|
||||
|
||||
pause
|
||||
88
build.sh
88
build.sh
@@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker Registry Browser - Build and Deploy Script
|
||||
# Usage: ./build.sh [tag] [registry]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
IMAGE_NAME="docker-registry-browser"
|
||||
DEFAULT_TAG="latest"
|
||||
DEFAULT_REGISTRY=""
|
||||
|
||||
# Parse arguments
|
||||
TAG=${1:-$DEFAULT_TAG}
|
||||
REGISTRY=${2:-$DEFAULT_REGISTRY}
|
||||
|
||||
# Build the full image name
|
||||
if [ -n "$REGISTRY" ]; then
|
||||
FULL_IMAGE_NAME="$REGISTRY/$IMAGE_NAME:$TAG"
|
||||
else
|
||||
FULL_IMAGE_NAME="$IMAGE_NAME:$TAG"
|
||||
fi
|
||||
|
||||
echo "Building Docker Registry Browser..."
|
||||
echo "Image: $FULL_IMAGE_NAME"
|
||||
echo ""
|
||||
|
||||
# Build the Docker image
|
||||
echo "Building Docker image..."
|
||||
docker build -t "$FULL_IMAGE_NAME" .
|
||||
|
||||
echo ""
|
||||
echo "Build completed successfully!"
|
||||
echo ""
|
||||
echo "To run the container with environment variables:"
|
||||
echo "docker run -d --name docker-registry-browser \\"
|
||||
echo " -p 8080:80 \\"
|
||||
echo " --add-host=host.docker.internal:host-gateway \\"
|
||||
echo " -e REGISTRY_HOST=your-registry.com:5000 \\"
|
||||
echo " -e REGISTRY_PROTOCOL=https \\"
|
||||
echo " $FULL_IMAGE_NAME"
|
||||
echo ""
|
||||
echo "Or use docker-compose with .env file:"
|
||||
echo "cp .env.example .env"
|
||||
echo "# Edit .env with your values"
|
||||
echo "docker-compose up -d"
|
||||
echo ""
|
||||
echo "To push to registry (if configured):"
|
||||
if [ -n "$REGISTRY" ]; then
|
||||
echo "docker push $FULL_IMAGE_NAME"
|
||||
else
|
||||
echo "Please specify a registry: ./build.sh $TAG your-registry.com"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Optional: Run the container immediately
|
||||
read -p "Do you want to run the container now? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Starting container..."
|
||||
|
||||
# Check if .env file exists
|
||||
if [ -f ".env" ]; then
|
||||
echo "Using .env file for configuration..."
|
||||
docker run -d \
|
||||
--name docker-registry-browser \
|
||||
-p 8080:80 \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
--env-file .env \
|
||||
"$FULL_IMAGE_NAME"
|
||||
else
|
||||
echo "No .env file found, using default values..."
|
||||
echo "Copy .env.example to .env and edit it for your registry configuration."
|
||||
docker run -d \
|
||||
--name docker-registry-browser \
|
||||
-p 8080:80 \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
-e REGISTRY_HOST=localhost:5000 \
|
||||
-e REGISTRY_PROTOCOL=http \
|
||||
"$FULL_IMAGE_NAME"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Container started successfully!"
|
||||
echo "Access the application at: http://localhost:8080"
|
||||
echo "View container logs: docker logs docker-registry-browser"
|
||||
echo "Stop container: docker stop docker-registry-browser"
|
||||
fi
|
||||
@@ -14,6 +14,8 @@ services:
|
||||
# Optional: Basic auth if your registry requires it
|
||||
- REGISTRY_USERNAME=${REGISTRY_USERNAME:-}
|
||||
- REGISTRY_PASSWORD=${REGISTRY_PASSWORD:-}
|
||||
# Node.js configuration
|
||||
- NODE_ENV=production
|
||||
extra_hosts:
|
||||
# For accessing host services (like local registry)
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
@@ -1,20 +1,87 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Generate environment configuration for Docker Registry Browser
|
||||
# This script is run at container startup to inject environment variables
|
||||
# Docker Registry Browser with Node.js Express Proxy
|
||||
# Reliable CORS-handling solution
|
||||
|
||||
# Create env.js with environment variables
|
||||
cat <<EOF > /usr/share/nginx/html/assets/env.js
|
||||
# Set default values
|
||||
REGISTRY_HOST=${REGISTRY_HOST:-localhost:5000}
|
||||
REGISTRY_PROTOCOL=${REGISTRY_PROTOCOL:-http}
|
||||
REGISTRY_USERNAME=${REGISTRY_USERNAME:-}
|
||||
REGISTRY_PASSWORD=${REGISTRY_PASSWORD:-}
|
||||
|
||||
echo "=== Docker Registry Browser with Node.js Proxy ==="
|
||||
echo "Registry Host: ${REGISTRY_HOST}"
|
||||
echo "Registry Protocol: ${REGISTRY_PROTOCOL}"
|
||||
echo "Registry URL: ${REGISTRY_PROTOCOL}://${REGISTRY_HOST}"
|
||||
|
||||
# Validate configuration
|
||||
if [ -z "$REGISTRY_HOST" ]; then
|
||||
echo "ERROR: REGISTRY_HOST is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test registry connectivity if possible
|
||||
echo "Testing registry connectivity..."
|
||||
REGISTRY_URL="${REGISTRY_PROTOCOL}://${REGISTRY_HOST}"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
echo "Testing connection to: ${REGISTRY_URL}/v2/"
|
||||
|
||||
CURL_CMD="curl -f -s --connect-timeout 10"
|
||||
if [ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ]; then
|
||||
CURL_CMD="$CURL_CMD -u ${REGISTRY_USERNAME}:${REGISTRY_PASSWORD}"
|
||||
fi
|
||||
|
||||
if $CURL_CMD "${REGISTRY_URL}/v2/" > /dev/null; then
|
||||
echo "SUCCESS: Registry is accessible"
|
||||
else
|
||||
echo "WARNING: Registry connection test failed"
|
||||
echo "This might be normal - the Node.js proxy will still attempt to connect"
|
||||
fi
|
||||
else
|
||||
echo "curl not available, skipping connectivity test"
|
||||
fi
|
||||
|
||||
# Create environment configuration for the frontend
|
||||
echo "Creating frontend environment configuration..."
|
||||
mkdir -p /app/dist/docker-registry-browser/assets
|
||||
|
||||
cat > /app/dist/docker-registry-browser/assets/env.js <<EOF
|
||||
// Node.js Express Proxy Configuration
|
||||
window.env = {
|
||||
REGISTRY_HOST: '${REGISTRY_HOST:-localhost:5000}',
|
||||
REGISTRY_PROTOCOL: '${REGISTRY_PROTOCOL:-http}',
|
||||
REGISTRY_USERNAME: '${REGISTRY_USERNAME:-}',
|
||||
REGISTRY_PASSWORD: '${REGISTRY_PASSWORD:-}'
|
||||
REGISTRY_HOST: '${REGISTRY_HOST}',
|
||||
REGISTRY_PROTOCOL: '${REGISTRY_PROTOCOL}',
|
||||
REGISTRY_USERNAME: '${REGISTRY_USERNAME}',
|
||||
REGISTRY_PASSWORD: '${REGISTRY_PASSWORD}',
|
||||
USE_PROXY: true
|
||||
};
|
||||
|
||||
console.log('Docker Registry Browser with Node.js Express Proxy');
|
||||
console.log('Registry Target:', '${REGISTRY_PROTOCOL}://${REGISTRY_HOST}');
|
||||
console.log('Proxy Endpoint: /api/*');
|
||||
console.log('Proxy Method: http-proxy-middleware');
|
||||
EOF
|
||||
|
||||
echo "Environment configuration generated:"
|
||||
cat /usr/share/nginx/html/assets/env.js
|
||||
# Set proper permissions
|
||||
chown -R appuser:appuser /app
|
||||
chmod 644 /app/dist/docker-registry-browser/assets/env.js
|
||||
|
||||
# Start nginx
|
||||
exec nginx -g 'daemon off;'
|
||||
echo "=== Starting Node.js Express Proxy Server ==="
|
||||
echo "Proxy: /api/* -> ${REGISTRY_PROTOCOL}://${REGISTRY_HOST}/*"
|
||||
echo "Web interface: http://localhost:80"
|
||||
echo "Health check: http://localhost:80/health"
|
||||
echo "Proxy status: http://localhost:80/proxy-status"
|
||||
if [ -n "$REGISTRY_USERNAME" ]; then
|
||||
echo "Using basic authentication"
|
||||
fi
|
||||
|
||||
# Export environment variables for Node.js
|
||||
export REGISTRY_HOST
|
||||
export REGISTRY_PROTOCOL
|
||||
export REGISTRY_USERNAME
|
||||
export REGISTRY_PASSWORD
|
||||
export NODE_ENV=production
|
||||
|
||||
# Switch to non-root user and start Node.js server
|
||||
echo "Switching to non-root user and starting server..."
|
||||
su appuser -s /bin/sh -c "cd /app && node server.js"
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Get registry configuration from environment variables
|
||||
const registryHost = process.env.REGISTRY_HOST || 'localhost:5000';
|
||||
const registryProtocol = process.env.REGISTRY_PROTOCOL || 'http';
|
||||
const registryUrl = `${registryProtocol}://${registryHost}`;
|
||||
|
||||
console.log(`Configuring proxy for registry: ${registryUrl}`);
|
||||
|
||||
// Create dynamic proxy configuration
|
||||
const proxyConfig = {
|
||||
"/api/*": {
|
||||
"target": registryUrl,
|
||||
"secure": registryProtocol === 'https',
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug",
|
||||
"headers": {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
||||
},
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add basic auth if credentials are provided
|
||||
if (process.env.REGISTRY_USERNAME && process.env.REGISTRY_PASSWORD) {
|
||||
const auth = Buffer.from(`${process.env.REGISTRY_USERNAME}:${process.env.REGISTRY_PASSWORD}`).toString('base64');
|
||||
proxyConfig["/api/*"].headers.Authorization = `Basic ${auth}`;
|
||||
console.log('Added basic authentication to proxy');
|
||||
}
|
||||
|
||||
// Write the configuration file
|
||||
const configPath = path.join(__dirname, 'proxy.conf.json');
|
||||
fs.writeFileSync(configPath, JSON.stringify(proxyConfig, null, 2));
|
||||
|
||||
console.log('Proxy configuration updated successfully');
|
||||
console.log('Configuration:', JSON.stringify(proxyConfig, null, 2));
|
||||
|
||||
module.exports = proxyConfig;
|
||||
132
nginx.conf
132
nginx.conf
@@ -1,132 +0,0 @@
|
||||
# nginx.conf for production deployment
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Logging
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
# Basic Settings
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 100m;
|
||||
|
||||
# Gzip Settings
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/atom+xml
|
||||
image/svg+xml;
|
||||
|
||||
# Security Headers
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Security: Hide nginx version
|
||||
server_tokens off;
|
||||
|
||||
# Main application
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
|
||||
# Cache control for HTML files (no cache)
|
||||
location ~* \.html$ {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires "0";
|
||||
}
|
||||
}
|
||||
|
||||
# Static assets with long-term caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
|
||||
# Proxy for Docker Registry API (configurable via environment)
|
||||
location /api/ {
|
||||
# Remove /api prefix and forward to registry
|
||||
rewrite ^/api/(.*)$ /$1 break;
|
||||
|
||||
# Default to localhost:5000, but can be overridden
|
||||
proxy_pass http://host.docker.internal:5000;
|
||||
|
||||
# Proxy headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# CORS headers for registry API
|
||||
add_header Access-Control-Allow-Origin "*" always;
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" always;
|
||||
|
||||
# Handle preflight requests
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization";
|
||||
add_header Access-Control-Max-Age 1728000;
|
||||
add_header Content-Type "text/plain charset=UTF-8";
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
add_header Content-Type text/plain;
|
||||
return 200 "healthy\n";
|
||||
}
|
||||
|
||||
# 404 error handling
|
||||
error_page 404 /index.html;
|
||||
|
||||
# 50x error handling
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@
|
||||
"start:dev": "npm run start",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"serve": "ng serve --host 0.0.0.0 --port 4200"
|
||||
"serve": "ng serve --host 0.0.0.0 --port 4200",
|
||||
"server": "node server.js"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@@ -21,6 +22,8 @@
|
||||
"@angular/platform-browser": "^17.0.0",
|
||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||
"@angular/router": "^17.0.0",
|
||||
"express": "^4.18.2",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"/api/*": {
|
||||
"target": "http://localhost:5000",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug",
|
||||
"headers": {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
||||
},
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
160
server.js
Normal file
160
server.js
Normal file
@@ -0,0 +1,160 @@
|
||||
// server.js - Node.js Express proxy server for Docker Registry Browser
|
||||
const express = require('express');
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
|
||||
// Environment variables
|
||||
const REGISTRY_HOST = process.env.REGISTRY_HOST || 'localhost:5000';
|
||||
const REGISTRY_PROTOCOL = process.env.REGISTRY_PROTOCOL || 'http';
|
||||
const REGISTRY_USERNAME = process.env.REGISTRY_USERNAME || '';
|
||||
const REGISTRY_PASSWORD = process.env.REGISTRY_PASSWORD || '';
|
||||
|
||||
const registryUrl = `${REGISTRY_PROTOCOL}://${REGISTRY_HOST}`;
|
||||
|
||||
console.log('=== Docker Registry Browser Node.js Proxy ===');
|
||||
console.log('Target Registry:', registryUrl);
|
||||
console.log('Has Authentication:', !!(REGISTRY_USERNAME && REGISTRY_PASSWORD));
|
||||
|
||||
// Serve static files from the Angular build
|
||||
app.use(express.static(path.join(__dirname, 'dist/docker-registry-browser')));
|
||||
|
||||
// Create proxy middleware with comprehensive options
|
||||
const proxyOptions = {
|
||||
target: registryUrl,
|
||||
changeOrigin: true,
|
||||
secure: false, // Allow self-signed certificates
|
||||
pathRewrite: {
|
||||
'^/api': '', // Remove /api prefix
|
||||
},
|
||||
timeout: 30000, // 30 second timeout
|
||||
proxyTimeout: 30000,
|
||||
|
||||
onProxyReq: (proxyReq, req, res) => {
|
||||
// Add auth header if configured
|
||||
if (REGISTRY_USERNAME && REGISTRY_PASSWORD) {
|
||||
const auth = Buffer.from(`${REGISTRY_USERNAME}:${REGISTRY_PASSWORD}`).toString('base64');
|
||||
proxyReq.setHeader('Authorization', `Basic ${auth}`);
|
||||
}
|
||||
|
||||
// Log requests for debugging
|
||||
console.log(`[PROXY] ${req.method} ${req.url} -> ${registryUrl}${req.url.replace('/api', '')}`);
|
||||
|
||||
// Ensure proper headers
|
||||
proxyReq.setHeader('User-Agent', 'Docker-Registry-Browser/1.0');
|
||||
},
|
||||
|
||||
onProxyRes: (proxyRes, req, res) => {
|
||||
// Add comprehensive CORS headers
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST, PUT, DELETE');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Pragma');
|
||||
res.setHeader('Access-Control-Expose-Headers', 'Content-Length, Content-Range, Docker-Content-Digest, Docker-Distribution-Api-Version, X-Registry-Auth, WWW-Authenticate');
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Access-Control-Max-Age', '86400');
|
||||
|
||||
// Log successful responses
|
||||
console.log(`[PROXY] Response: ${proxyRes.statusCode} ${proxyRes.statusMessage}`);
|
||||
},
|
||||
|
||||
onError: (err, req, res) => {
|
||||
console.error(`[PROXY ERROR] ${req.method} ${req.url}:`, err.message);
|
||||
|
||||
// Send detailed error response
|
||||
res.status(502).json({
|
||||
error: 'Registry proxy error',
|
||||
message: err.message,
|
||||
code: err.code,
|
||||
target: registryUrl,
|
||||
url: req.url,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Registry API proxy - this handles all /api/* requests
|
||||
app.use('/api', createProxyMiddleware(proxyOptions));
|
||||
|
||||
// Handle CORS preflight requests explicitly
|
||||
app.options('/api/*', (req, res) => {
|
||||
console.log('[CORS] Handling OPTIONS preflight request');
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST, PUT, DELETE');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Pragma');
|
||||
res.header('Access-Control-Max-Age', '86400');
|
||||
res.sendStatus(204);
|
||||
});
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
registry: registryUrl,
|
||||
hasAuth: !!(REGISTRY_USERNAME && REGISTRY_PASSWORD),
|
||||
timestamp: new Date().toISOString(),
|
||||
version: '1.1.0'
|
||||
});
|
||||
});
|
||||
|
||||
// Proxy status endpoint for debugging
|
||||
app.get('/proxy-status', (req, res) => {
|
||||
res.json({
|
||||
proxy_target: registryUrl,
|
||||
status: 'configured',
|
||||
has_auth: !!(REGISTRY_USERNAME && REGISTRY_PASSWORD),
|
||||
proxy_middleware: 'http-proxy-middleware',
|
||||
node_version: process.version,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Test registry connection endpoint
|
||||
app.get('/test-registry', (req, res) => {
|
||||
// Simple test endpoint - actual testing is done by the proxy
|
||||
res.json({
|
||||
message: 'Registry connection testing is handled by the proxy',
|
||||
registry: registryUrl,
|
||||
hasAuth: !!(REGISTRY_USERNAME && REGISTRY_PASSWORD),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Catch-all handler: send back Angular's index.html file for any non-API routes
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'dist/docker-registry-browser/index.html'));
|
||||
});
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('[SERVER ERROR]:', err);
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: err.message,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Start the server
|
||||
const PORT = process.env.PORT || 80;
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`=== Server Started ===`);
|
||||
console.log(`Port: ${PORT}`);
|
||||
console.log(`Registry proxy: /api/* -> ${registryUrl}/*`);
|
||||
console.log(`Health check: http://localhost:${PORT}/health`);
|
||||
console.log(`Proxy status: http://localhost:${PORT}/proxy-status`);
|
||||
console.log('======================');
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SIGTERM received, shutting down gracefully');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('SIGINT received, shutting down gracefully');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
@@ -11,7 +11,6 @@ import { PushCommandsDialogComponent } from './components/push-commands-dialog.c
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
registryUrl = '/api'; // Using proxy for CORS
|
||||
repositories: Repository[] = [];
|
||||
selectedRepo: Repository | null = null;
|
||||
tags: Tag[] = [];
|
||||
@@ -22,6 +21,7 @@ export class AppComponent implements OnInit {
|
||||
copyMessage = '';
|
||||
selectedTag: Tag | null = null;
|
||||
showingDetails = false;
|
||||
connectionStatus: { success: boolean; message: string } | null = null;
|
||||
|
||||
constructor(
|
||||
private registryService: DockerRegistryService,
|
||||
@@ -30,13 +30,117 @@ export class AppComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadRepositories();
|
||||
this.testConnectionAndLoad();
|
||||
}
|
||||
|
||||
get registryInfo() {
|
||||
return this.environmentService.getRegistryInfo();
|
||||
}
|
||||
|
||||
get registryHost(): string {
|
||||
return this.environmentService.displayHost;
|
||||
}
|
||||
|
||||
async testConnectionAndLoad() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
// Test connection first
|
||||
console.log('Testing registry connection...');
|
||||
this.connectionStatus = await this.registryService.testConnection();
|
||||
|
||||
if (this.connectionStatus.success) {
|
||||
console.log('Connection successful, loading repositories...');
|
||||
await this.loadRepositories();
|
||||
} else {
|
||||
this.error = `Registry connection failed: ${this.connectionStatus.message}`;
|
||||
console.error('Connection failed:', this.connectionStatus.message);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.error = `Failed to connect to registry: ${err.message}`;
|
||||
console.error('Connection error:', err);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadRepositories() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
console.log('Loading repositories...');
|
||||
this.repositories = await this.registryService.getRepositories();
|
||||
console.log(`Loaded ${this.repositories.length} repositories`);
|
||||
|
||||
if (this.repositories.length === 0) {
|
||||
this.error = 'No repositories found in the registry. Make sure your registry contains some images.';
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.error = err.message;
|
||||
this.repositories = [];
|
||||
console.error('Failed to load repositories:', err);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadTags(repo: Repository) {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
this.selectedRepo = repo;
|
||||
this.selectedTag = null;
|
||||
this.showingDetails = false;
|
||||
|
||||
try {
|
||||
console.log(`Loading tags for repository: ${repo.name}`);
|
||||
this.tags = await this.registryService.getTags(repo.name);
|
||||
console.log(`Loaded ${this.tags.length} tags`);
|
||||
|
||||
if (this.tags.length === 0) {
|
||||
this.error = `No tags found for repository ${repo.name}`;
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.error = err.message;
|
||||
this.tags = [];
|
||||
console.error('Failed to load tags:', err);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadImageDetails(tag: Tag) {
|
||||
if (!this.selectedRepo) return;
|
||||
|
||||
this.loadingDetails = true;
|
||||
this.selectedTag = tag;
|
||||
this.showingDetails = true;
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
if (!tag.details) {
|
||||
console.log(`Loading details for: ${this.selectedRepo.name}:${tag.name}`);
|
||||
tag.details = await this.registryService.getImageDetails(
|
||||
this.selectedRepo.name,
|
||||
tag.name
|
||||
);
|
||||
console.log('Image details loaded:', tag.details);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.error = err.message;
|
||||
console.error('Failed to load image details:', err);
|
||||
}
|
||||
|
||||
this.loadingDetails = false;
|
||||
}
|
||||
|
||||
closeDetails() {
|
||||
this.selectedTag = null;
|
||||
this.showingDetails = false;
|
||||
this.error = '';
|
||||
}
|
||||
|
||||
openPushCommandsDialog() {
|
||||
this.dialog.open(PushCommandsDialogComponent, {
|
||||
width: '800px',
|
||||
@@ -49,69 +153,22 @@ export class AppComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
async loadRepositories() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
try {
|
||||
this.repositories = await this.registryService.getRepositories(this.registryUrl);
|
||||
} catch (err: any) {
|
||||
this.error = `Failed to fetch repositories: ${err.message}`;
|
||||
this.repositories = [];
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadTags(repo: Repository) {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
this.selectedRepo = repo;
|
||||
this.selectedTag = null;
|
||||
this.showingDetails = false;
|
||||
try {
|
||||
this.tags = await this.registryService.getTags(this.registryUrl, repo.name);
|
||||
} catch (err: any) {
|
||||
this.error = `Failed to fetch tags: ${err.message}`;
|
||||
this.tags = [];
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadImageDetails(tag: Tag) {
|
||||
if (!this.selectedRepo) return;
|
||||
|
||||
this.loadingDetails = true;
|
||||
this.selectedTag = tag;
|
||||
this.showingDetails = true;
|
||||
|
||||
try {
|
||||
if (!tag.details) {
|
||||
tag.details = await this.registryService.getImageDetails(
|
||||
this.registryUrl,
|
||||
this.selectedRepo.name,
|
||||
tag.name
|
||||
);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.error = `Failed to fetch image details: ${err.message}`;
|
||||
}
|
||||
this.loadingDetails = false;
|
||||
}
|
||||
|
||||
closeDetails() {
|
||||
this.selectedTag = null;
|
||||
this.showingDetails = false;
|
||||
}
|
||||
|
||||
getDockerPullCommand(repoName: string, tagName: string): string {
|
||||
return `docker pull ${this.registryHost}/${repoName}:${tagName}`;
|
||||
}
|
||||
|
||||
copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text);
|
||||
this.copyMessage = 'Copied to clipboard!';
|
||||
setTimeout(() => {
|
||||
this.copyMessage = '';
|
||||
}, 2000);
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.copyMessage = 'Copied to clipboard!';
|
||||
setTimeout(() => {
|
||||
this.copyMessage = '';
|
||||
}, 2000);
|
||||
}).catch(() => {
|
||||
this.copyMessage = 'Failed to copy';
|
||||
setTimeout(() => {
|
||||
this.copyMessage = '';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
formatBytes = (bytes: number): string => {
|
||||
@@ -139,4 +196,14 @@ export class AppComponent implements OnInit {
|
||||
repo.name.toLowerCase().includes(this.searchTerm.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// Retry connection
|
||||
async retryConnection() {
|
||||
await this.testConnectionAndLoad();
|
||||
}
|
||||
|
||||
// Manual refresh
|
||||
async refresh() {
|
||||
await this.loadRepositories();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,120 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||
import { firstValueFrom, timeout } from 'rxjs';
|
||||
import { Repository, Tag, RegistryResponse, TagsResponse, ImageDetails, ManifestResponse, ConfigResponse } from '../models/registry.model';
|
||||
import { EnvironmentService } from './environment.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DockerRegistryService {
|
||||
constructor(private http: HttpClient) {}
|
||||
private readonly requestTimeout = 30000; // 30 seconds
|
||||
|
||||
async getRepositories(registryUrl: string): Promise<Repository[]> {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private environmentService: EnvironmentService
|
||||
) {}
|
||||
|
||||
private get baseUrl(): string {
|
||||
// ALWAYS use proxy - this solves CORS issues
|
||||
console.log('Using proxy endpoint /api to avoid CORS issues');
|
||||
console.log('Proxy target:', this.environmentService.fullRegistryUrl);
|
||||
return '/api';
|
||||
}
|
||||
|
||||
private getRequestOptions(): { headers: HttpHeaders } {
|
||||
// When using proxy, don't add auth headers - the server handles it
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
return { headers };
|
||||
}
|
||||
|
||||
private handleError(error: any, operation: string): never {
|
||||
console.error(`${operation} failed:`, error);
|
||||
|
||||
let errorMessage = `${operation} failed`;
|
||||
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
switch (error.status) {
|
||||
case 0:
|
||||
errorMessage = `Cannot connect to registry proxy. Please check if the Docker Registry Browser server is running properly. Target registry: ${this.environmentService.fullRegistryUrl}`;
|
||||
break;
|
||||
case 401:
|
||||
errorMessage = 'Registry authentication failed. Please check your username and password in the container environment.';
|
||||
break;
|
||||
case 404:
|
||||
errorMessage = `Registry endpoint not found. Please verify your registry host and protocol. Target: ${this.environmentService.fullRegistryUrl}`;
|
||||
break;
|
||||
case 502:
|
||||
errorMessage = `Proxy cannot reach the registry at ${this.environmentService.fullRegistryUrl}. Please check your REGISTRY_HOST and REGISTRY_PROTOCOL settings.`;
|
||||
break;
|
||||
case 500:
|
||||
case 503:
|
||||
case 504:
|
||||
errorMessage = `Registry server error. Please check if your registry at ${this.environmentService.fullRegistryUrl} is running correctly.`;
|
||||
break;
|
||||
default:
|
||||
errorMessage = `Registry error (${error.status}): ${error.message || 'Unknown error'}`;
|
||||
}
|
||||
|
||||
// Add helpful debugging info
|
||||
if (error.status === 0) {
|
||||
errorMessage += '\n\nTroubleshooting:\n1. Make sure you\'re accessing the app through the Docker container, not the dev server\n2. Check that REGISTRY_HOST and REGISTRY_PROTOCOL are correct\n3. Verify your registry is accessible';
|
||||
}
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
async getRepositories(): Promise<Repository[]> {
|
||||
try {
|
||||
const url = `${this.baseUrl}/v2/_catalog`;
|
||||
console.log('Fetching repositories from:', url);
|
||||
console.log('Registry target (via proxy):', this.environmentService.fullRegistryUrl);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.http.get<RegistryResponse>(`${registryUrl}/v2/_catalog`)
|
||||
this.http.get<RegistryResponse>(url, this.getRequestOptions())
|
||||
.pipe(timeout(this.requestTimeout))
|
||||
);
|
||||
return (response?.repositories || []).map(name => ({ name }));
|
||||
|
||||
console.log('Registry response:', response);
|
||||
|
||||
const repositories = (response?.repositories || []).map(name => ({ name }));
|
||||
console.log(`Found ${repositories.length} repositories`);
|
||||
|
||||
return repositories;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to connect to registry: ${error}`);
|
||||
this.handleError(error, 'Fetching repositories');
|
||||
}
|
||||
}
|
||||
|
||||
async getTags(registryUrl: string, repositoryName: string): Promise<Tag[]> {
|
||||
async getTags(repositoryName: string): Promise<Tag[]> {
|
||||
try {
|
||||
const url = `${this.baseUrl}/v2/${repositoryName}/tags/list`;
|
||||
console.log(`Fetching tags for repository: ${repositoryName} from: ${url}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.http.get<TagsResponse>(`${registryUrl}/v2/${repositoryName}/tags/list`)
|
||||
this.http.get<TagsResponse>(url, this.getRequestOptions())
|
||||
.pipe(timeout(this.requestTimeout))
|
||||
);
|
||||
return (response?.tags || []).map(name => ({ name }));
|
||||
|
||||
const tags = (response?.tags || []).map(name => ({ name }));
|
||||
console.log(`Found ${tags.length} tags for ${repositoryName}`);
|
||||
|
||||
return tags;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch tags for ${repositoryName}: ${error}`);
|
||||
this.handleError(error, `Fetching tags for ${repositoryName}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getImageDetails(registryUrl: string, repositoryName: string, tag: string): Promise<ImageDetails> {
|
||||
async getImageDetails(repositoryName: string, tag: string): Promise<ImageDetails> {
|
||||
try {
|
||||
console.log(`Fetching image details for: ${repositoryName}:${tag}`);
|
||||
|
||||
// Enhanced Accept header to support both Docker v2 and OCI manifest formats
|
||||
const manifestHeaders = new HttpHeaders({
|
||||
'Accept': [
|
||||
@@ -42,29 +124,27 @@ export class DockerRegistryService {
|
||||
'application/vnd.oci.image.index.v1+json'
|
||||
].join(', ')
|
||||
});
|
||||
|
||||
const url = `${this.baseUrl}/v2/${repositoryName}/manifests/${tag}`;
|
||||
console.log('Fetching manifest from:', url);
|
||||
|
||||
const manifest = await firstValueFrom(
|
||||
this.http.get<any>(
|
||||
`${registryUrl}/v2/${repositoryName}/manifests/${tag}`,
|
||||
{ headers: manifestHeaders }
|
||||
)
|
||||
this.http.get<any>(url, { headers: manifestHeaders })
|
||||
.pipe(timeout(this.requestTimeout))
|
||||
);
|
||||
|
||||
console.log('Manifest response:', manifest);
|
||||
|
||||
// Handle different manifest types
|
||||
if (this.isManifestList(manifest)) {
|
||||
// Handle manifest lists (multi-platform images)
|
||||
return await this.handleManifestList(registryUrl, repositoryName, tag, manifest);
|
||||
return await this.handleManifestList(repositoryName, tag, manifest);
|
||||
} else if (this.isImageManifest(manifest)) {
|
||||
// Handle single platform manifests
|
||||
return await this.handleImageManifest(registryUrl, repositoryName, manifest);
|
||||
return await this.handleImageManifest(repositoryName, manifest);
|
||||
} else {
|
||||
throw new Error(`Unsupported manifest type: ${manifest.mediaType}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching image details:', error);
|
||||
throw new Error(`Failed to fetch image details for ${repositoryName}:${tag}: ${error}`);
|
||||
this.handleError(error, `Fetching image details for ${repositoryName}:${tag}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,16 +158,15 @@ export class DockerRegistryService {
|
||||
manifest.mediaType === 'application/vnd.oci.image.manifest.v1+json';
|
||||
}
|
||||
|
||||
private async handleManifestList(registryUrl: string, repositoryName: string, tag: string, manifestList: any): Promise<ImageDetails> {
|
||||
// For manifest lists, we'll use the first manifest (usually linux/amd64)
|
||||
// You could enhance this to let users choose the platform
|
||||
private async handleManifestList(repositoryName: string, tag: string, manifestList: any): Promise<ImageDetails> {
|
||||
// For manifest lists, use the first manifest (usually linux/amd64)
|
||||
const firstManifest = manifestList.manifests?.[0];
|
||||
|
||||
if (!firstManifest) {
|
||||
throw new Error('No manifests found in manifest list');
|
||||
}
|
||||
|
||||
console.log('Using manifest from list:', firstManifest);
|
||||
console.log('Using first manifest from list:', firstManifest);
|
||||
|
||||
// Fetch the actual manifest using its digest
|
||||
const manifestHeaders = new HttpHeaders({
|
||||
@@ -97,17 +176,18 @@ export class DockerRegistryService {
|
||||
].join(', ')
|
||||
});
|
||||
|
||||
const url = `${this.baseUrl}/v2/${repositoryName}/manifests/${firstManifest.digest}`;
|
||||
console.log('Fetching actual manifest from:', url);
|
||||
|
||||
const actualManifest = await firstValueFrom(
|
||||
this.http.get<any>(
|
||||
`${registryUrl}/v2/${repositoryName}/manifests/${firstManifest.digest}`,
|
||||
{ headers: manifestHeaders }
|
||||
)
|
||||
this.http.get<any>(url, { headers: manifestHeaders })
|
||||
.pipe(timeout(this.requestTimeout))
|
||||
);
|
||||
|
||||
return await this.handleImageManifest(registryUrl, repositoryName, actualManifest, firstManifest);
|
||||
return await this.handleImageManifest(repositoryName, actualManifest, firstManifest);
|
||||
}
|
||||
|
||||
private async handleImageManifest(registryUrl: string, repositoryName: string, manifest: any, platformInfo?: any): Promise<ImageDetails> {
|
||||
private async handleImageManifest(repositoryName: string, manifest: any, platformInfo?: any): Promise<ImageDetails> {
|
||||
try {
|
||||
// Get the config blob
|
||||
const configDigest = manifest.config?.digest;
|
||||
@@ -115,10 +195,12 @@ export class DockerRegistryService {
|
||||
throw new Error('No config digest found in manifest');
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}/v2/${repositoryName}/blobs/${configDigest}`;
|
||||
console.log('Fetching config from:', url);
|
||||
|
||||
const config = await firstValueFrom(
|
||||
this.http.get<ConfigResponse>(
|
||||
`${registryUrl}/v2/${repositoryName}/blobs/${configDigest}`
|
||||
)
|
||||
this.http.get<ConfigResponse>(url, this.getRequestOptions())
|
||||
.pipe(timeout(this.requestTimeout))
|
||||
);
|
||||
|
||||
console.log('Config response:', config);
|
||||
@@ -165,35 +247,33 @@ export class DockerRegistryService {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get available platforms for a manifest list
|
||||
async getAvailablePlatforms(registryUrl: string, repositoryName: string, tag: string): Promise<any[]> {
|
||||
// Test registry connectivity via proxy
|
||||
async testConnection(): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const manifestHeaders = new HttpHeaders({
|
||||
'Accept': [
|
||||
'application/vnd.docker.distribution.manifest.list.v2+json',
|
||||
'application/vnd.oci.image.index.v1+json'
|
||||
].join(', ')
|
||||
});
|
||||
const url = `${this.baseUrl}/v2/`;
|
||||
console.log('Testing connection to:', url);
|
||||
console.log('Target registry (via proxy):', this.environmentService.fullRegistryUrl);
|
||||
|
||||
const manifest = await firstValueFrom(
|
||||
this.http.get<any>(
|
||||
`${registryUrl}/v2/${repositoryName}/manifests/${tag}`,
|
||||
{ headers: manifestHeaders }
|
||||
)
|
||||
await firstValueFrom(
|
||||
this.http.get(url, this.getRequestOptions())
|
||||
.pipe(timeout(10000)) // 10 second timeout for connectivity test
|
||||
);
|
||||
|
||||
if (this.isManifestList(manifest)) {
|
||||
return manifest.manifests?.map((m: any) => ({
|
||||
digest: m.digest,
|
||||
platform: m.platform,
|
||||
size: m.size
|
||||
})) || [];
|
||||
}
|
||||
|
||||
return [];
|
||||
return { success: true, message: 'Registry connection successful via proxy' };
|
||||
} catch (error) {
|
||||
console.error('Error getting platforms:', error);
|
||||
return [];
|
||||
let errorMsg = 'Connection failed: Unable to reach registry via proxy';
|
||||
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
if (error.status === 0) {
|
||||
errorMsg = 'Cannot connect to proxy server. Make sure you\'re accessing the app through the Docker container.';
|
||||
} else if (error.status === 502) {
|
||||
errorMsg = `Proxy cannot reach registry at ${this.environmentService.fullRegistryUrl}. Check REGISTRY_HOST and REGISTRY_PROTOCOL.`;
|
||||
} else {
|
||||
errorMsg = `Connection failed (${error.status}): ${error.message || 'Unknown error'}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Connection test failed:', error);
|
||||
return { success: false, message: errorMsg };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
env: {
|
||||
REGISTRY_HOST: string;
|
||||
REGISTRY_PROTOCOL: string;
|
||||
REGISTRY_USERNAME: string;
|
||||
REGISTRY_PASSWORD: string;
|
||||
env?: {
|
||||
REGISTRY_HOST?: string;
|
||||
REGISTRY_PROTOCOL?: string;
|
||||
REGISTRY_USERNAME?: string;
|
||||
REGISTRY_PASSWORD?: string;
|
||||
REGISTRY_DISPLAY_URL?: string;
|
||||
USE_PROXY?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,10 +22,21 @@ export class EnvironmentService {
|
||||
constructor() {
|
||||
// Load configuration from window object (set by env.js)
|
||||
this.config = window.env || {};
|
||||
|
||||
// Log configuration for debugging (without sensitive data)
|
||||
console.log('Registry Configuration:', {
|
||||
host: this.registryHost,
|
||||
protocol: this.registryProtocol,
|
||||
hasUsername: !!this.registryUsername,
|
||||
hasPassword: !!this.registryPassword,
|
||||
useProxy: this.config.USE_PROXY || false
|
||||
});
|
||||
}
|
||||
|
||||
get registryHost(): string {
|
||||
return this.config.REGISTRY_HOST || 'localhost:5000';
|
||||
const host = this.config.REGISTRY_HOST || 'localhost:5000';
|
||||
// Ensure we don't have protocol in the host
|
||||
return host.replace(/^https?:\/\//, '');
|
||||
}
|
||||
|
||||
get registryProtocol(): string {
|
||||
@@ -38,12 +51,33 @@ export class EnvironmentService {
|
||||
return this.config.REGISTRY_PASSWORD || '';
|
||||
}
|
||||
|
||||
get useProxy(): boolean {
|
||||
return this.config.USE_PROXY || false;
|
||||
}
|
||||
|
||||
// Get the full registry URL (used for proxy target and direct connection)
|
||||
get fullRegistryUrl(): string {
|
||||
return `${this.registryProtocol}://${this.registryHost}`;
|
||||
}
|
||||
|
||||
// Get display host (without protocol) for commands
|
||||
// Get display host for docker commands (without protocol)
|
||||
get displayHost(): string {
|
||||
return this.registryHost;
|
||||
}
|
||||
|
||||
// Check if authentication is configured
|
||||
get hasAuthentication(): boolean {
|
||||
return !!(this.registryUsername && this.registryPassword);
|
||||
}
|
||||
|
||||
// Get registry info for display
|
||||
getRegistryInfo(): { host: string; protocol: string; secure: boolean; hasAuth: boolean; useProxy: boolean } {
|
||||
return {
|
||||
host: this.registryHost,
|
||||
protocol: this.registryProtocol,
|
||||
secure: this.registryProtocol === 'https',
|
||||
hasAuth: this.hasAuthentication,
|
||||
useProxy: this.useProxy
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
@echo off
|
||||
REM Development start script for Docker Registry Browser
|
||||
REM Usage: start-dev.bat [registry_host] [protocol]
|
||||
|
||||
REM Set default values
|
||||
set REGISTRY_HOST=%1
|
||||
set REGISTRY_PROTOCOL=%2
|
||||
|
||||
if "%REGISTRY_HOST%"=="" set REGISTRY_HOST=localhost:5000
|
||||
if "%REGISTRY_PROTOCOL%"=="" set REGISTRY_PROTOCOL=http
|
||||
|
||||
echo Starting Docker Registry Browser development server...
|
||||
echo Registry: %REGISTRY_PROTOCOL%://%REGISTRY_HOST%
|
||||
echo.
|
||||
|
||||
REM Check if .env file exists and load it
|
||||
if exist ".env" (
|
||||
echo Loading environment from .env file...
|
||||
for /f "delims== tokens=1,2" %%a in (.env) do (
|
||||
if not "%%a"=="" if not "%%b"=="" (
|
||||
set %%a=%%b
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM Generate proxy configuration and start server
|
||||
npm run start
|
||||
|
||||
pause
|
||||
25
start-dev.sh
25
start-dev.sh
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Development start script for Docker Registry Browser
|
||||
# Usage: ./start-dev.sh [registry_host] [protocol]
|
||||
|
||||
# Set default values
|
||||
REGISTRY_HOST=${1:-"localhost:5000"}
|
||||
REGISTRY_PROTOCOL=${2:-"http"}
|
||||
|
||||
echo "Starting Docker Registry Browser development server..."
|
||||
echo "Registry: $REGISTRY_PROTOCOL://$REGISTRY_HOST"
|
||||
echo ""
|
||||
|
||||
# Export environment variables
|
||||
export REGISTRY_HOST="$REGISTRY_HOST"
|
||||
export REGISTRY_PROTOCOL="$REGISTRY_PROTOCOL"
|
||||
|
||||
# Check if .env file exists and source it
|
||||
if [ -f ".env" ]; then
|
||||
echo "Loading environment from .env file..."
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Generate proxy configuration and start server
|
||||
npm run start
|
||||
@@ -9,22 +9,28 @@
|
||||
<Privileged>false</Privileged>
|
||||
<Support>https://github.com/programmingPug/docker-registry-browser</Support>
|
||||
<Project>https://github.com/programmingPug/docker-registry-browser</Project>
|
||||
<Overview>Docker Registry Browser - A modern web interface for browsing and managing Docker registries. Features include repository browsing, tag listing, image details inspection, and push command generation.</Overview>
|
||||
<Overview>Docker Registry Browser - A simple web interface for browsing Docker registries. Browse repositories, view tags, and get pull commands. Perfect for managing your local Docker registry.</Overview>
|
||||
<Category>Tools:</Category>
|
||||
<WebUI>http://[IP]:[PORT:8080]/</WebUI>
|
||||
<TemplateURL>https://raw.githubusercontent.com/programmingPug/docker-registry-browser/main/unraid-template.xml</TemplateURL>
|
||||
<Icon>https://raw.githubusercontent.com/docker/docs/main/assets/images/docker-icon.png</Icon>
|
||||
<ExtraParams>--add-host=host.docker.internal:host-gateway</ExtraParams>
|
||||
<ExtraParams></ExtraParams>
|
||||
<PostArgs/>
|
||||
<CPUset/>
|
||||
<DateInstalled/>
|
||||
<DonateText/>
|
||||
<DonateLink/>
|
||||
<Requires/>
|
||||
|
||||
<!-- Simplified configuration - only the essentials -->
|
||||
<Config Name="WebUI Port" Target="80" Default="8080" Mode="tcp" Description="Port for accessing the web interface" Type="Port" Display="always" Required="true" Mask="false">8080</Config>
|
||||
<Config Name="Registry Host" Target="REGISTRY_HOST" Default="localhost:5000" Mode="" Description="Docker registry host and port (e.g., localhost:5000, registry.example.com:5000)" Type="Variable" Display="always" Required="true" Mask="false">localhost:5000</Config>
|
||||
<Config Name="Registry Protocol" Target="REGISTRY_PROTOCOL" Default="http" Mode="" Description="Protocol to use for registry connection (http or https)" Type="Variable" Display="always" Required="true" Mask="false">http</Config>
|
||||
<Config Name="Registry Username" Target="REGISTRY_USERNAME" Default="" Mode="" Description="Username for registry authentication (leave empty for no auth)" Type="Variable" Display="always" Required="false" Mask="false"></Config>
|
||||
<Config Name="Registry Password" Target="REGISTRY_PASSWORD" Default="" Mode="" Description="Password for registry authentication (leave empty for no auth)" Type="Variable" Display="always" Required="false" Mask="true"></Config>
|
||||
<Config Name="App Data" Target="/app/data" Default="/mnt/user/appdata/docker-registry-browser" Mode="rw" Description="Application data directory" Type="Path" Display="advanced" Required="false" Mask="false">/mnt/user/appdata/docker-registry-browser</Config>
|
||||
|
||||
<Config Name="Registry Host" Target="REGISTRY_HOST" Default="" Mode="" Description="Your Docker registry host and port. Examples: 192.168.1.100:5000 (for Unraid host), registry:5000 (for container), hub.example.com (for remote)" Type="Variable" Display="always" Required="true" Mask="false"></Config>
|
||||
|
||||
<Config Name="Registry Protocol" Target="REGISTRY_PROTOCOL" Default="http" Mode="" Description="http for local/insecure registries, https for secure registries" Type="Variable" Display="always" Required="true" Mask="false">http</Config>
|
||||
|
||||
<!-- Optional authentication - most users won't need this -->
|
||||
<Config Name="Username (Optional)" Target="REGISTRY_USERNAME" Default="" Mode="" Description="Leave empty if your registry doesn't require authentication" Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
|
||||
|
||||
<Config Name="Password (Optional)" Target="REGISTRY_PASSWORD" Default="" Mode="" Description="Leave empty if your registry doesn't require authentication" Type="Variable" Display="advanced" Required="false" Mask="true"></Config>
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user