- Updated the glass-card and glass-button styles for improved aesthetics and functionality, including gradient borders and hover effects. - Introduced new info-card and info-card-button components with enhanced styling and interactive features. - Refactored existing HTML structure to utilize new card components, improving consistency across the UI. - Enhanced button interactions for better user experience during settings and logs access.
15 KiB
Archipelago App UI Standards - For Apps Without Native UIs
Version: 1.0
Last Updated: 2026-02-03
Overview
This document defines the standard UI pattern for containerized applications that don't have their own web interface (e.g., Bitcoin Core, LND, Core Lightning, mempool backend services, etc.).
These UIs provide a simple, elegant way to:
- Monitor service status and metrics
- View connection information (RPC, REST, gRPC endpoints)
- Access logs and settings
- Copy configuration details for external tools
Architecture
┌─────────────────────────────────────┐
│ Nginx Container (Alpine) │
│ - Serves static HTML/CSS/JS │
│ - Port 8XXX (unique per service) │
│ - Optional: Proxies RPC/API calls │
└─────────────────────────────────────┘
File Structure
docker/
├── {service-name}-ui/
│ ├── index.html # Main UI file
│ ├── Dockerfile # Container build
│ ├── nginx.conf # Nginx config
│ ├── {service-icon} # App icon (svg/webp/png)
│ └── bg-{theme}.jpg # Background image
Standard UI Components
1. CSS Class System
.glass-card - Main Container Cards
Used for: Header, main sections, modals
.glass-card {
position: relative;
background: rgba(0, 0, 0, 0.60);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.22);
border-radius: 1rem;
overflow-x: hidden;
overflow-y: visible;
border: none;
}
.glass-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
z-index: 1;
}
.glass-card > * {
position: relative;
z-index: 2;
}
.info-card - Stat Display Cards
Used for: Status badges, metric displays (non-interactive)
.info-card {
position: relative;
background: rgba(0, 0, 0, 0.60);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.22);
border-radius: 16px;
padding: 12px;
border: none;
}
.info-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
No hover effects - These are display-only elements.
.info-card-button - Interactive Action Buttons
Used for: Primary action buttons (Copy Info, View Logs, etc.)
.info-card-button {
/* Same base styles as .info-card */
position: relative;
background: rgba(0, 0, 0, 0.60);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.22);
border-radius: 16px;
padding: 12px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
color: rgba(255, 255, 255, 0.9);
}
.info-card-button::before {
/* Same gradient as .info-card */
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
transition: all 0.3s ease;
}
/* Hover state - lifts and brightens */
.info-card-button:hover {
transform: translateY(-2px);
background: rgba(0, 0, 0, 0.35);
box-shadow:
0 12px 32px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.25);
color: rgba(255, 255, 255, 1);
}
.info-card-button:hover::before {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
}
/* Active state - press down */
.info-card-button:active {
transform: translateY(1px);
}
.glass-button - Secondary Buttons
Used for: Settings, Close (×), secondary actions
.glass-button {
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(255, 255, 255, 0.18);
color: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
}
.glass-button:hover {
color: white;
background-color: rgba(0, 0, 0, 0.7);
}
Simple Info Rows - bg-white/5
Used for: Non-interactive info rows (RPC Host, Network, Status, etc.)
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
<div class="flex items-center gap-3">
<svg class="w-5 h-5 text-white/60"><!-- icon --></svg>
<span class="text-white/80 text-sm">Label</span>
</div>
<span class="text-white/60 text-sm">Value</span>
</div>
No gradient borders - These are simple read-only display elements.
Standard Layout Pattern
1. Header Section (.glass-card)
<div class="glass-card p-6 mb-6">
<div class="flex flex-col md:flex-row items-center md:items-center gap-4 md:gap-6">
<!-- Logo (left) -->
<div class="flex-shrink-0">
<div class="logo-gradient-border">
<img src="/assets/img/app-icons/{service-icon}" alt="{Service Name}" />
</div>
</div>
<!-- Title and Description (center) -->
<div class="flex-1 min-w-0">
<h1 class="text-3xl font-bold text-white mb-2">{Service Name}</h1>
<p class="text-white/70">{Service Description}</p>
</div>
<!-- Status Info (right) - OPTIONAL for headers with status -->
<div class="w-full md:w-auto flex flex-col md:flex-row gap-3 md:gap-4">
<div class="info-card flex items-center gap-3">
<!-- Status info -->
</div>
</div>
</div>
</div>
2. Quick Status Bar (.glass-card with .info-card grid)
<div class="glass-card p-6 mb-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="info-card flex items-center justify-between">
<!-- Status indicator -->
</div>
<!-- ... more status cards -->
</div>
</div>
3. Main Content Sections (.glass-card grid)
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Service 1: Node Status -->
<div class="glass-card p-6">
<div class="flex items-start gap-4 mb-4">
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
<svg><!-- icon --></svg>
</div>
<div class="flex-1">
<h2 class="text-xl font-semibold text-white mb-2">Section Title</h2>
<p class="text-white/70 text-sm mb-4">Section description</p>
</div>
</div>
<div class="space-y-3">
<!-- Info rows (bg-white/5) -->
</div>
<button class="mt-4 w-full info-card-button text-sm font-medium" onclick="action()">
Action Button
</button>
</div>
<!-- ... more sections -->
</div>
4. Modals (.glass-card with backdrop)
<div class="modal hidden fixed inset-0 bg-black/80 backdrop-blur-sm z-50 items-center justify-center p-4" id="modalId">
<div class="glass-card p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold text-white">Modal Title</h2>
<button onclick="closeModal()" class="glass-button px-3 py-2 rounded-lg text-xl font-medium">×</button>
</div>
<!-- Modal content -->
</div>
</div>
Visual Hierarchy
Container Importance (Most → Least)
-
.glass-card- Main containers, sections, modals- Gradient border, strong blur (24px), inset highlights
-
.info-card- Stat displays, status badges- Gradient border, backdrop blur, NO hover effects
-
.info-card-button- Primary action buttons- Same as
.info-cardin default state - WITH hover effects (lift, brighten, enhanced gradient)
- Same as
-
bg-white/5- Simple info rows- Dark background, NO borders, NO hover
-
.glass-button- Secondary buttons- Simple glass effect, minimal hover
Port Assignments
Reserve unique ports for each service UI:
8334 - Bitcoin Knots UI
8081 - LND UI
8082 - Core Lightning UI (future)
8083 - Mempool UI (future)
8084 - BTCPay Server UI (future)
...
Update backend's docker_packages.rs to map these ports:
} else if app_id == "lnd" {
Some("http://localhost:8081".to_string())
} else if app_id == "bitcoin-knots" {
Some("http://localhost:8334".to_string())
}
Dockerfile Template
FROM docker.io/library/nginx:alpine
# Copy the HTML file
COPY index.html /usr/share/nginx/html/
# Create directories for assets
RUN mkdir -p /usr/share/nginx/html/assets/img/app-icons && \
mkdir -p /usr/share/nginx/html/assets/img
# Copy assets
COPY {service-icon} /usr/share/nginx/html/assets/img/app-icons/
COPY bg-{theme}.jpg /usr/share/nginx/html/assets/img/
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Nginx Config Template
Simple Static Serving (LND, most services)
server {
listen 8XXX; # Unique port for this service
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
With RPC Proxy (Bitcoin Knots)
server {
listen 8334;
server_name _;
root /usr/share/nginx/html;
index index.html;
# RPC proxy to avoid CORS issues
location /bitcoin-rpc/ {
proxy_pass http://127.0.0.1:8332/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization "Basic {BASE64_ENCODED_CREDS}";
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
if ($request_method = OPTIONS) {
return 204;
}
}
location / {
try_files $uri $uri/ /index.html;
}
}
Deployment
Build and Run
# Build the image
cd docker/{service}-ui
sudo podman build -t {service}-ui:latest .
# Run the container
sudo podman run -d \
--name {service}-ui \
--restart unless-stopped \
--network=host \
--label 'com.archipelago.parent-app={service-id}' \
{service}-ui:latest
Backend Integration
Update core/archipelago/src/container/docker_packages.rs:
} else if app_id == "{service-id}" {
Some("http://localhost:8XXX".to_string())
}
Reference Implementations
✅ Bitcoin Knots UI
- Location:
docker/bitcoin-ui/ - Port: 8334
- Features:
- Live sync status with animations
- RPC proxy for CORS handling
- Real-time block updates
- Connection info display
✅ LND UI
- Location:
docker/lnd-ui/ - Port: 8081
- Features:
- Node status monitoring
- Channel count display
- REST API + gRPC info
- Settings and logs modals
Benefits of This Approach
- Consistency - All service UIs look and feel the same
- Lightweight - Nginx Alpine base (~10MB)
- Fast Development - Copy template, customize content
- Mobile Responsive - Works on all screen sizes
- Low Resource Usage - Static files, minimal CPU/RAM
- Easy Maintenance - Single pattern to update globally
Creating a New Service UI
Step-by-Step Process
-
Create Directory Structure
mkdir -p docker/{service}-ui cd docker/{service}-ui -
Copy Template Files
cp ../bitcoin-ui/index.html ./ cp ../bitcoin-ui/Dockerfile ./ cp ../bitcoin-ui/nginx.conf ./ -
Customize
index.html- Update title, service name, description
- Modify status cards for your service's metrics
- Update connection info sections (RPC/REST/gRPC)
- Adjust modal content
-
Copy Assets
cp ../../neode-ui/public/assets/img/app-icons/{service-icon} ./ cp ../../neode-ui/public/assets/img/bg-{theme}.jpg ./ -
Update Nginx Config
- Set unique port number
- Add RPC proxy if needed
-
Update Dockerfile
- Update asset COPY commands
- Verify port EXPOSE
-
Build and Deploy
# Deploy to dev server sshpass -p "archipelago" rsync -avz --delete ./ archipelago@192.168.1.228:/tmp/{service}-ui-build/ # Build on server sshpass -p "archipelago" ssh archipelago@192.168.1.228 \ "cd /tmp/{service}-ui-build && \ sudo podman build -t {service}-ui:latest . && \ sudo podman stop {service}-ui 2>/dev/null || true && \ sudo podman rm {service}-ui 2>/dev/null || true && \ sudo podman run -d --name {service}-ui --restart unless-stopped \ --network=host --label 'com.archipelago.parent-app={service-id}' \ {service}-ui:latest" -
Update Backend
- Edit
core/archipelago/src/container/docker_packages.rs - Add port mapping for your service
- Edit
Testing Checklist
- UI loads correctly at
http://{server}:8XXX/ - Logo displays properly
- Background image loads
- All status cards show correct info
- Buttons have proper hover effects
- Modals open and close correctly
- Mobile responsive (test on phone)
- Glass effects render correctly
- Gradient borders visible
- Cache busting works (no stale content)
Future Enhancements
- Live Data Updates - WebSocket connections for real-time status
- Interactive Charts - Add Chart.js for visualizing metrics
- Theme Variations - Allow users to select background themes
- Dark/Light Mode - Toggle between color schemes
- Internationalization - Support multiple languages
- Accessibility - Improve screen reader support
File Locations
- UI Standards Doc:
/Users/dorian/Projects/archy/.cursor/rules/APP-UI-STANDARDS.md - Global UI Standards:
/Users/dorian/Projects/archy/.cursor/rules/UI-STANDARDS.md - Reference Implementations:
- Bitcoin UI:
/Users/dorian/Projects/archy/docker/bitcoin-ui/ - LND UI:
/Users/dorian/Projects/archy/docker/lnd-ui/
- Bitcoin UI:
Version: 1.0
Maintained by: Archipelago Development Team
Last Updated: 2026-02-03