- 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.
589 lines
15 KiB
Markdown
589 lines
15 KiB
Markdown
# 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
|
||
|
||
```css
|
||
.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)
|
||
|
||
```css
|
||
.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.)
|
||
|
||
```css
|
||
.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
|
||
|
||
```css
|
||
.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.)
|
||
|
||
```html
|
||
<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`)
|
||
|
||
```html
|
||
<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)
|
||
|
||
```html
|
||
<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)
|
||
|
||
```html
|
||
<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)
|
||
|
||
```html
|
||
<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)**
|
||
|
||
1. **`.glass-card`** - Main containers, sections, modals
|
||
- Gradient border, strong blur (24px), inset highlights
|
||
|
||
2. **`.info-card`** - Stat displays, status badges
|
||
- Gradient border, backdrop blur, **NO hover effects**
|
||
|
||
3. **`.info-card-button`** - Primary action buttons
|
||
- Same as `.info-card` in default state
|
||
- **WITH hover effects** (lift, brighten, enhanced gradient)
|
||
|
||
4. **`bg-white/5`** - Simple info rows
|
||
- Dark background, **NO borders**, **NO hover**
|
||
|
||
5. **`.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:
|
||
|
||
```rust
|
||
} 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
|
||
|
||
```dockerfile
|
||
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)
|
||
|
||
```nginx
|
||
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)
|
||
|
||
```nginx
|
||
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
|
||
|
||
```bash
|
||
# 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`:
|
||
|
||
```rust
|
||
} 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
|
||
|
||
1. **Consistency** - All service UIs look and feel the same
|
||
2. **Lightweight** - Nginx Alpine base (~10MB)
|
||
3. **Fast Development** - Copy template, customize content
|
||
4. **Mobile Responsive** - Works on all screen sizes
|
||
5. **Low Resource Usage** - Static files, minimal CPU/RAM
|
||
6. **Easy Maintenance** - Single pattern to update globally
|
||
|
||
---
|
||
|
||
## Creating a New Service UI
|
||
|
||
### Step-by-Step Process
|
||
|
||
1. **Create Directory Structure**
|
||
```bash
|
||
mkdir -p docker/{service}-ui
|
||
cd docker/{service}-ui
|
||
```
|
||
|
||
2. **Copy Template Files**
|
||
```bash
|
||
cp ../bitcoin-ui/index.html ./
|
||
cp ../bitcoin-ui/Dockerfile ./
|
||
cp ../bitcoin-ui/nginx.conf ./
|
||
```
|
||
|
||
3. **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
|
||
|
||
4. **Copy Assets**
|
||
```bash
|
||
cp ../../neode-ui/public/assets/img/app-icons/{service-icon} ./
|
||
cp ../../neode-ui/public/assets/img/bg-{theme}.jpg ./
|
||
```
|
||
|
||
5. **Update Nginx Config**
|
||
- Set unique port number
|
||
- Add RPC proxy if needed
|
||
|
||
6. **Update Dockerfile**
|
||
- Update asset COPY commands
|
||
- Verify port EXPOSE
|
||
|
||
7. **Build and Deploy**
|
||
```bash
|
||
# 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"
|
||
```
|
||
|
||
8. **Update Backend**
|
||
- Edit `core/archipelago/src/container/docker_packages.rs`
|
||
- Add port mapping for your service
|
||
|
||
---
|
||
|
||
## 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/`
|
||
|
||
---
|
||
|
||
**Version:** 1.0
|
||
**Maintained by:** Archipelago Development Team
|
||
**Last Updated:** 2026-02-03
|