mirror of
https://github.com/kossakovsky/n8n-install.git
synced 2026-03-07 14:23:08 +00:00
feat: redesign welcome page with supabase-inspired theme
- fix json generation bug in generate_welcome_page.sh (missing commas) - add supabase-style dark theme with green accents (#3ECF8E) - add make commands section to welcome page - move make commands from final report to welcome page - make next steps conditional based on active profiles - update poweredby to yury kossakovsky - format generated json with proper indentation for readability
This commit is contained in:
@@ -46,24 +46,6 @@ echo " Installation Complete!"
|
||||
echo "======================================================================="
|
||||
echo
|
||||
|
||||
# --- Make Commands ---
|
||||
echo "================================= Make Commands ========================="
|
||||
echo
|
||||
echo " make logs View logs (all services)"
|
||||
echo " make logs s=<service> View logs for specific service"
|
||||
echo " make status Show container status"
|
||||
echo " make monitor Live CPU/memory monitoring"
|
||||
echo " make restarts Show restart count per container"
|
||||
echo
|
||||
echo " make update Update system and services"
|
||||
echo " make update-preview Preview available updates (dry-run)"
|
||||
echo " make doctor Run system diagnostics"
|
||||
echo " make clean Remove unused Docker resources"
|
||||
echo
|
||||
echo " make switch-beta Switch to beta (develop branch)"
|
||||
echo " make switch-stable Switch to stable (main branch)"
|
||||
echo
|
||||
|
||||
# --- Welcome Page ---
|
||||
echo "================================= Welcome Page =========================="
|
||||
echo
|
||||
@@ -88,12 +70,20 @@ echo "1. Visit your Welcome Page to view all service credentials"
|
||||
echo " https://${WELCOME_HOSTNAME:-welcome.${USER_DOMAIN_NAME}}"
|
||||
echo
|
||||
echo "2. Store the Welcome Page credentials securely"
|
||||
echo " (Username: ${WELCOME_USERNAME:-<not_set>})"
|
||||
echo
|
||||
echo "3. Configure services as needed:"
|
||||
if is_profile_active "n8n"; then
|
||||
echo " - n8n: Complete first-run setup with your email"
|
||||
fi
|
||||
if is_profile_active "portainer"; then
|
||||
echo " - Portainer: Create admin account on first login"
|
||||
fi
|
||||
if is_profile_active "flowise"; then
|
||||
echo " - Flowise: Register and create your account"
|
||||
fi
|
||||
if is_profile_active "open-webui"; then
|
||||
echo " - Open WebUI: Register your account"
|
||||
fi
|
||||
echo
|
||||
echo "4. Run 'make doctor' if you experience any issues"
|
||||
echo
|
||||
|
||||
@@ -55,426 +55,433 @@ json_escape() {
|
||||
# Start building JSON
|
||||
GENERATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Build services object (key-based)
|
||||
SERVICES_JSON=""
|
||||
# Build services array - each entry is a formatted JSON block
|
||||
declare -a SERVICES_ARRAY
|
||||
|
||||
# n8n
|
||||
if is_profile_active "n8n"; then
|
||||
N8N_WORKER_COUNT_VAL="${N8N_WORKER_COUNT:-1}"
|
||||
SERVICES_JSON+='"n8n": {
|
||||
"hostname": "'"$(json_escape "$N8N_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Use the email you provided during installation"
|
||||
SERVICES_ARRAY+=(" \"n8n\": {
|
||||
\"hostname\": \"$(json_escape "$N8N_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Use the email you provided during installation\"
|
||||
},
|
||||
"extra": {
|
||||
"workers": "'"$N8N_WORKER_COUNT_VAL"'"
|
||||
\"extra\": {
|
||||
\"workers\": \"$N8N_WORKER_COUNT_VAL\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Flowise
|
||||
if is_profile_active "flowise"; then
|
||||
SERVICES_JSON+='"flowise": {
|
||||
"hostname": "'"$(json_escape "$FLOWISE_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$FLOWISE_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$FLOWISE_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"flowise\": {
|
||||
\"hostname\": \"$(json_escape "$FLOWISE_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$FLOWISE_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$FLOWISE_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Open WebUI
|
||||
if is_profile_active "open-webui"; then
|
||||
SERVICES_JSON+='"open-webui": {
|
||||
"hostname": "'"$(json_escape "$WEBUI_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Create account on first login"
|
||||
SERVICES_ARRAY+=(" \"open-webui\": {
|
||||
\"hostname\": \"$(json_escape "$WEBUI_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Create account on first login\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Grafana (monitoring)
|
||||
if is_profile_active "monitoring"; then
|
||||
SERVICES_JSON+='"grafana": {
|
||||
"hostname": "'"$(json_escape "$GRAFANA_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "admin",
|
||||
"password": "'"$(json_escape "$GRAFANA_ADMIN_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"grafana\": {
|
||||
\"hostname\": \"$(json_escape "$GRAFANA_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"admin\",
|
||||
\"password\": \"$(json_escape "$GRAFANA_ADMIN_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
SERVICES_JSON+='"prometheus": {
|
||||
"hostname": "'"$(json_escape "$PROMETHEUS_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$PROMETHEUS_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$PROMETHEUS_PASSWORD")"'"
|
||||
}")
|
||||
SERVICES_ARRAY+=(" \"prometheus\": {
|
||||
\"hostname\": \"$(json_escape "$PROMETHEUS_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$PROMETHEUS_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$PROMETHEUS_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Portainer
|
||||
if is_profile_active "portainer"; then
|
||||
SERVICES_JSON+='"portainer": {
|
||||
"hostname": "'"$(json_escape "$PORTAINER_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Create admin account on first login"
|
||||
SERVICES_ARRAY+=(" \"portainer\": {
|
||||
\"hostname\": \"$(json_escape "$PORTAINER_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Create admin account on first login\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Postgresus
|
||||
if is_profile_active "postgresus"; then
|
||||
SERVICES_JSON+='"postgresus": {
|
||||
"hostname": "'"$(json_escape "$POSTGRESUS_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Uses PostgreSQL credentials from .env"
|
||||
SERVICES_ARRAY+=(" \"postgresus\": {
|
||||
\"hostname\": \"$(json_escape "$POSTGRESUS_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Uses PostgreSQL credentials from .env\"
|
||||
},
|
||||
"extra": {
|
||||
"pg_host": "postgres",
|
||||
"pg_port": "'"${POSTGRES_PORT:-5432}"'",
|
||||
"pg_user": "'"$(json_escape "${POSTGRES_USER:-postgres}")"'",
|
||||
"pg_password": "'"$(json_escape "$POSTGRES_PASSWORD")"'",
|
||||
"pg_db": "'"$(json_escape "${POSTGRES_DB:-postgres}")"'"
|
||||
\"extra\": {
|
||||
\"pg_host\": \"postgres\",
|
||||
\"pg_port\": \"${POSTGRES_PORT:-5432}\",
|
||||
\"pg_user\": \"$(json_escape "${POSTGRES_USER:-postgres}")\",
|
||||
\"pg_password\": \"$(json_escape "$POSTGRES_PASSWORD")\",
|
||||
\"pg_db\": \"$(json_escape "${POSTGRES_DB:-postgres}")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Langfuse
|
||||
if is_profile_active "langfuse"; then
|
||||
SERVICES_JSON+='"langfuse": {
|
||||
"hostname": "'"$(json_escape "$LANGFUSE_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$LANGFUSE_INIT_USER_EMAIL")"'",
|
||||
"password": "'"$(json_escape "$LANGFUSE_INIT_USER_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"langfuse\": {
|
||||
\"hostname\": \"$(json_escape "$LANGFUSE_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$LANGFUSE_INIT_USER_EMAIL")\",
|
||||
\"password\": \"$(json_escape "$LANGFUSE_INIT_USER_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Supabase
|
||||
if is_profile_active "supabase"; then
|
||||
SERVICES_JSON+='"supabase": {
|
||||
"hostname": "'"$(json_escape "$SUPABASE_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$DASHBOARD_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$DASHBOARD_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"supabase\": {
|
||||
\"hostname\": \"$(json_escape "$SUPABASE_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$DASHBOARD_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$DASHBOARD_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://kong:8000",
|
||||
"service_role_key": "'"$(json_escape "$SERVICE_ROLE_KEY")"'"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://kong:8000\",
|
||||
\"service_role_key\": \"$(json_escape "$SERVICE_ROLE_KEY")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Dify
|
||||
if is_profile_active "dify"; then
|
||||
SERVICES_JSON+='"dify": {
|
||||
"hostname": "'"$(json_escape "$DIFY_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Create account on first login"
|
||||
SERVICES_ARRAY+=(" \"dify\": {
|
||||
\"hostname\": \"$(json_escape "$DIFY_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Create account on first login\"
|
||||
},
|
||||
"extra": {
|
||||
"api_endpoint": "https://'"$(json_escape "$DIFY_HOSTNAME")"'/v1",
|
||||
"internal_api": "http://dify-api:5001"
|
||||
\"extra\": {
|
||||
\"api_endpoint\": \"https://$(json_escape "$DIFY_HOSTNAME")/v1\",
|
||||
\"internal_api\": \"http://dify-api:5001\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Qdrant
|
||||
if is_profile_active "qdrant"; then
|
||||
SERVICES_JSON+='"qdrant": {
|
||||
"hostname": "'"$(json_escape "$QDRANT_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"api_key": "'"$(json_escape "$QDRANT_API_KEY")"'"
|
||||
SERVICES_ARRAY+=(" \"qdrant\": {
|
||||
\"hostname\": \"$(json_escape "$QDRANT_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"api_key\": \"$(json_escape "$QDRANT_API_KEY")\"
|
||||
},
|
||||
"extra": {
|
||||
"dashboard": "https://'"$(json_escape "$QDRANT_HOSTNAME")"'/dashboard",
|
||||
"internal_api": "http://qdrant:6333"
|
||||
\"extra\": {
|
||||
\"dashboard\": \"https://$(json_escape "$QDRANT_HOSTNAME")/dashboard\",
|
||||
\"internal_api\": \"http://qdrant:6333\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Weaviate
|
||||
if is_profile_active "weaviate"; then
|
||||
SERVICES_JSON+='"weaviate": {
|
||||
"hostname": "'"$(json_escape "$WEAVIATE_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"api_key": "'"$(json_escape "$WEAVIATE_API_KEY")"'",
|
||||
"username": "'"$(json_escape "$WEAVIATE_USERNAME")"'"
|
||||
SERVICES_ARRAY+=(" \"weaviate\": {
|
||||
\"hostname\": \"$(json_escape "$WEAVIATE_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"api_key\": \"$(json_escape "$WEAVIATE_API_KEY")\",
|
||||
\"username\": \"$(json_escape "$WEAVIATE_USERNAME")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Neo4j
|
||||
if is_profile_active "neo4j"; then
|
||||
SERVICES_JSON+='"neo4j": {
|
||||
"hostname": "'"$(json_escape "$NEO4J_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$NEO4J_AUTH_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$NEO4J_AUTH_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"neo4j\": {
|
||||
\"hostname\": \"$(json_escape "$NEO4J_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$NEO4J_AUTH_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$NEO4J_AUTH_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"bolt_port": "7687"
|
||||
\"extra\": {
|
||||
\"bolt_port\": \"7687\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# SearXNG
|
||||
if is_profile_active "searxng"; then
|
||||
SERVICES_JSON+='"searxng": {
|
||||
"hostname": "'"$(json_escape "$SEARXNG_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$SEARXNG_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$SEARXNG_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"searxng\": {
|
||||
\"hostname\": \"$(json_escape "$SEARXNG_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$SEARXNG_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$SEARXNG_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# RAGApp
|
||||
if is_profile_active "ragapp"; then
|
||||
SERVICES_JSON+='"ragapp": {
|
||||
"hostname": "'"$(json_escape "$RAGAPP_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$RAGAPP_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$RAGAPP_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"ragapp\": {
|
||||
\"hostname\": \"$(json_escape "$RAGAPP_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$RAGAPP_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$RAGAPP_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"admin": "https://'"$(json_escape "$RAGAPP_HOSTNAME")"'/admin",
|
||||
"docs": "https://'"$(json_escape "$RAGAPP_HOSTNAME")"'/docs",
|
||||
"internal_api": "http://ragapp:8000"
|
||||
\"extra\": {
|
||||
\"admin\": \"https://$(json_escape "$RAGAPP_HOSTNAME")/admin\",
|
||||
\"docs\": \"https://$(json_escape "$RAGAPP_HOSTNAME")/docs\",
|
||||
\"internal_api\": \"http://ragapp:8000\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# RAGFlow
|
||||
if is_profile_active "ragflow"; then
|
||||
SERVICES_JSON+='"ragflow": {
|
||||
"hostname": "'"$(json_escape "$RAGFLOW_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Create account on first login"
|
||||
SERVICES_ARRAY+=(" \"ragflow\": {
|
||||
\"hostname\": \"$(json_escape "$RAGFLOW_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Create account on first login\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://ragflow:80"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://ragflow:80\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# LightRAG
|
||||
if is_profile_active "lightrag"; then
|
||||
SERVICES_JSON+='"lightrag": {
|
||||
"hostname": "'"$(json_escape "$LIGHTRAG_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$LIGHTRAG_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$LIGHTRAG_PASSWORD")"'",
|
||||
"api_key": "'"$(json_escape "$LIGHTRAG_API_KEY")"'"
|
||||
SERVICES_ARRAY+=(" \"lightrag\": {
|
||||
\"hostname\": \"$(json_escape "$LIGHTRAG_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$LIGHTRAG_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$LIGHTRAG_PASSWORD")\",
|
||||
\"api_key\": \"$(json_escape "$LIGHTRAG_API_KEY")\"
|
||||
},
|
||||
"extra": {
|
||||
"docs": "https://'"$(json_escape "$LIGHTRAG_HOSTNAME")"'/docs",
|
||||
"internal_api": "http://lightrag:9621"
|
||||
\"extra\": {
|
||||
\"docs\": \"https://$(json_escape "$LIGHTRAG_HOSTNAME")/docs\",
|
||||
\"internal_api\": \"http://lightrag:9621\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Letta
|
||||
if is_profile_active "letta"; then
|
||||
SERVICES_JSON+='"letta": {
|
||||
"hostname": "'"$(json_escape "$LETTA_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"api_key": "'"$(json_escape "$LETTA_SERVER_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"letta\": {
|
||||
\"hostname\": \"$(json_escape "$LETTA_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"api_key\": \"$(json_escape "$LETTA_SERVER_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# ComfyUI
|
||||
if is_profile_active "comfyui"; then
|
||||
SERVICES_JSON+='"comfyui": {
|
||||
"hostname": "'"$(json_escape "$COMFYUI_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$COMFYUI_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$COMFYUI_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"comfyui\": {
|
||||
\"hostname\": \"$(json_escape "$COMFYUI_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$COMFYUI_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$COMFYUI_PASSWORD")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# LibreTranslate
|
||||
if is_profile_active "libretranslate"; then
|
||||
SERVICES_JSON+='"libretranslate": {
|
||||
"hostname": "'"$(json_escape "$LT_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$LT_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$LT_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"libretranslate\": {
|
||||
\"hostname\": \"$(json_escape "$LT_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$LT_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$LT_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://libretranslate:5000"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://libretranslate:5000\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Docling
|
||||
if is_profile_active "docling"; then
|
||||
SERVICES_JSON+='"docling": {
|
||||
"hostname": "'"$(json_escape "$DOCLING_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$DOCLING_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$DOCLING_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"docling\": {
|
||||
\"hostname\": \"$(json_escape "$DOCLING_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$DOCLING_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$DOCLING_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"ui": "https://'"$(json_escape "$DOCLING_HOSTNAME")"'/ui",
|
||||
"docs": "https://'"$(json_escape "$DOCLING_HOSTNAME")"'/docs",
|
||||
"internal_api": "http://docling:5001"
|
||||
\"extra\": {
|
||||
\"ui\": \"https://$(json_escape "$DOCLING_HOSTNAME")/ui\",
|
||||
\"docs\": \"https://$(json_escape "$DOCLING_HOSTNAME")/docs\",
|
||||
\"internal_api\": \"http://docling:5001\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# PaddleOCR
|
||||
if is_profile_active "paddleocr"; then
|
||||
SERVICES_JSON+='"paddleocr": {
|
||||
"hostname": "'"$(json_escape "$PADDLEOCR_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$PADDLEOCR_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$PADDLEOCR_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"paddleocr\": {
|
||||
\"hostname\": \"$(json_escape "$PADDLEOCR_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$PADDLEOCR_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$PADDLEOCR_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://paddleocr:8080"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://paddleocr:8080\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Postiz
|
||||
if is_profile_active "postiz"; then
|
||||
SERVICES_JSON+='"postiz": {
|
||||
"hostname": "'"$(json_escape "$POSTIZ_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"note": "Create account on first login"
|
||||
SERVICES_ARRAY+=(" \"postiz\": {
|
||||
\"hostname\": \"$(json_escape "$POSTIZ_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"note\": \"Create account on first login\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://postiz:5000"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://postiz:5000\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# WAHA
|
||||
if is_profile_active "waha"; then
|
||||
SERVICES_JSON+='"waha": {
|
||||
"hostname": "'"$(json_escape "$WAHA_HOSTNAME")"'",
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "$WAHA_DASHBOARD_USERNAME")"'",
|
||||
"password": "'"$(json_escape "$WAHA_DASHBOARD_PASSWORD")"'",
|
||||
"api_key": "'"$(json_escape "$WAHA_API_KEY_PLAIN")"'"
|
||||
SERVICES_ARRAY+=(" \"waha\": {
|
||||
\"hostname\": \"$(json_escape "$WAHA_HOSTNAME")\",
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "$WAHA_DASHBOARD_USERNAME")\",
|
||||
\"password\": \"$(json_escape "$WAHA_DASHBOARD_PASSWORD")\",
|
||||
\"api_key\": \"$(json_escape "$WAHA_API_KEY_PLAIN")\"
|
||||
},
|
||||
"extra": {
|
||||
"dashboard": "https://'"$(json_escape "$WAHA_HOSTNAME")"'/dashboard",
|
||||
"swagger_user": "'"$(json_escape "$WHATSAPP_SWAGGER_USERNAME")"'",
|
||||
"swagger_pass": "'"$(json_escape "$WHATSAPP_SWAGGER_PASSWORD")"'",
|
||||
"internal_api": "http://waha:3000"
|
||||
\"extra\": {
|
||||
\"dashboard\": \"https://$(json_escape "$WAHA_HOSTNAME")/dashboard\",
|
||||
\"swagger_user\": \"$(json_escape "$WHATSAPP_SWAGGER_USERNAME")\",
|
||||
\"swagger_pass\": \"$(json_escape "$WHATSAPP_SWAGGER_PASSWORD")\",
|
||||
\"internal_api\": \"http://waha:3000\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Crawl4AI (internal only)
|
||||
if is_profile_active "crawl4ai"; then
|
||||
SERVICES_JSON+='"crawl4ai": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"note": "Internal service only"
|
||||
SERVICES_ARRAY+=(" \"crawl4ai\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"note\": \"Internal service only\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://crawl4ai:11235"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://crawl4ai:11235\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Gotenberg (internal only)
|
||||
if is_profile_active "gotenberg"; then
|
||||
SERVICES_JSON+='"gotenberg": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"note": "Internal service only"
|
||||
SERVICES_ARRAY+=(" \"gotenberg\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"note\": \"Internal service only\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://gotenberg:3000",
|
||||
"docs": "https://gotenberg.dev/docs"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://gotenberg:3000\",
|
||||
\"docs\": \"https://gotenberg.dev/docs\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Ollama (internal only)
|
||||
if is_profile_active "cpu" || is_profile_active "gpu-nvidia" || is_profile_active "gpu-amd"; then
|
||||
SERVICES_JSON+='"ollama": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"note": "Internal service only"
|
||||
SERVICES_ARRAY+=(" \"ollama\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"note\": \"Internal service only\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_api": "http://ollama:11434"
|
||||
\"extra\": {
|
||||
\"internal_api\": \"http://ollama:11434\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Redis/Valkey (internal only, shown if n8n or langfuse active)
|
||||
if is_profile_active "n8n" || is_profile_active "langfuse"; then
|
||||
SERVICES_JSON+='"redis": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"password": "'"$(json_escape "$REDIS_AUTH")"'"
|
||||
SERVICES_ARRAY+=(" \"redis\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"password\": \"$(json_escape "$REDIS_AUTH")\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_host": "'"${REDIS_HOST:-redis}"'",
|
||||
"internal_port": "'"${REDIS_PORT:-6379}"'"
|
||||
\"extra\": {
|
||||
\"internal_host\": \"${REDIS_HOST:-redis}\",
|
||||
\"internal_port\": \"${REDIS_PORT:-6379}\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# PostgreSQL (internal only, shown if n8n or langfuse active)
|
||||
if is_profile_active "n8n" || is_profile_active "langfuse"; then
|
||||
SERVICES_JSON+='"postgres": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"username": "'"$(json_escape "${POSTGRES_USER:-postgres}")"'",
|
||||
"password": "'"$(json_escape "$POSTGRES_PASSWORD")"'"
|
||||
SERVICES_ARRAY+=(" \"postgres\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"username\": \"$(json_escape "${POSTGRES_USER:-postgres}")\",
|
||||
\"password\": \"$(json_escape "$POSTGRES_PASSWORD")\"
|
||||
},
|
||||
"extra": {
|
||||
"internal_host": "postgres",
|
||||
"internal_port": "'"${POSTGRES_PORT:-5432}"'",
|
||||
"database": "'"$(json_escape "${POSTGRES_DB:-postgres}")"'"
|
||||
\"extra\": {
|
||||
\"internal_host\": \"postgres\",
|
||||
\"internal_port\": \"${POSTGRES_PORT:-5432}\",
|
||||
\"database\": \"$(json_escape "${POSTGRES_DB:-postgres}")\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Python Runner (internal only)
|
||||
if is_profile_active "python-runner"; then
|
||||
SERVICES_JSON+='"python-runner": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"note": "Internal service only"
|
||||
SERVICES_ARRAY+=(" \"python-runner\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"note\": \"Internal service only\"
|
||||
},
|
||||
"extra": {
|
||||
"logs_command": "docker compose -p localai logs -f python-runner"
|
||||
\"extra\": {
|
||||
\"logs_command\": \"docker compose -p localai logs -f python-runner\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Cloudflare Tunnel
|
||||
if is_profile_active "cloudflare-tunnel"; then
|
||||
SERVICES_JSON+='"cloudflare-tunnel": {
|
||||
"hostname": null,
|
||||
"credentials": {
|
||||
"note": "Zero-trust access via Cloudflare network"
|
||||
SERVICES_ARRAY+=(" \"cloudflare-tunnel\": {
|
||||
\"hostname\": null,
|
||||
\"credentials\": {
|
||||
\"note\": \"Zero-trust access via Cloudflare network\"
|
||||
},
|
||||
"extra": {
|
||||
"recommendation": "Close ports 80, 443, 7687 in your VPS firewall after confirming tunnel connectivity"
|
||||
\"extra\": {
|
||||
\"recommendation\": \"Close ports 80, 443, 7687 in your VPS firewall after confirming tunnel connectivity\"
|
||||
}
|
||||
},'
|
||||
}")
|
||||
fi
|
||||
|
||||
# Remove trailing comma from services JSON
|
||||
SERVICES_JSON=$(echo "$SERVICES_JSON" | sed 's/,$//')
|
||||
# Join array with commas and newlines
|
||||
SERVICES_JSON=""
|
||||
for i in "${!SERVICES_ARRAY[@]}"; do
|
||||
if [ $i -gt 0 ]; then
|
||||
SERVICES_JSON+=",
|
||||
"
|
||||
fi
|
||||
SERVICES_JSON+="${SERVICES_ARRAY[$i]}"
|
||||
done
|
||||
|
||||
# Write final JSON
|
||||
# Write final JSON with proper formatting
|
||||
cat > "$OUTPUT_FILE" << EOF
|
||||
{
|
||||
"domain": "$(json_escape "$USER_DOMAIN_NAME")",
|
||||
"generated_at": "$GENERATED_AT",
|
||||
"services": {
|
||||
$SERVICES_JSON
|
||||
$SERVICES_JSON
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
108
welcome/app.js
108
welcome/app.js
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* n8n-install Welcome Page
|
||||
* Dynamic rendering of services and credentials from data.json
|
||||
* Supabase-inspired design
|
||||
*/
|
||||
|
||||
(function() {
|
||||
@@ -26,7 +27,7 @@
|
||||
name: 'Open WebUI',
|
||||
description: 'ChatGPT-like Interface',
|
||||
icon: 'AI',
|
||||
color: 'bg-green-500',
|
||||
color: 'bg-emerald-500',
|
||||
category: 'ai'
|
||||
},
|
||||
'grafana': {
|
||||
@@ -227,9 +228,25 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Make commands data
|
||||
const COMMANDS = [
|
||||
{ cmd: 'make status', desc: 'Show container status' },
|
||||
{ cmd: 'make logs', desc: 'View logs (all services)' },
|
||||
{ cmd: 'make logs s=<service>', desc: 'View logs for specific service' },
|
||||
{ cmd: 'make monitor', desc: 'Live CPU/memory monitoring' },
|
||||
{ cmd: 'make restarts', desc: 'Show restart count per container' },
|
||||
{ cmd: 'make doctor', desc: 'Run system diagnostics' },
|
||||
{ cmd: 'make update', desc: 'Update system and services' },
|
||||
{ cmd: 'make update-preview', desc: 'Preview available updates' },
|
||||
{ cmd: 'make clean', desc: 'Remove unused Docker resources' },
|
||||
{ cmd: 'make switch-beta', desc: 'Switch to beta (develop branch)' },
|
||||
{ cmd: 'make switch-stable', desc: 'Switch to stable (main branch)' }
|
||||
];
|
||||
|
||||
// DOM Elements
|
||||
const servicesContainer = document.getElementById('services-container');
|
||||
const quickstartContainer = document.getElementById('quickstart-container');
|
||||
const commandsContainer = document.getElementById('commands-container');
|
||||
const domainInfo = document.getElementById('domain-info');
|
||||
const errorToast = document.getElementById('error-toast');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
@@ -258,16 +275,16 @@
|
||||
container.className = 'flex items-center gap-1';
|
||||
|
||||
const passwordSpan = document.createElement('span');
|
||||
passwordSpan.className = 'font-mono text-sm select-all';
|
||||
passwordSpan.className = 'font-mono text-sm select-all text-gray-300';
|
||||
passwordSpan.textContent = '*'.repeat(Math.min(password.length, 12));
|
||||
passwordSpan.dataset.password = password;
|
||||
passwordSpan.dataset.hidden = 'true';
|
||||
|
||||
// Toggle visibility button (eye icon)
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = 'p-1 rounded hover:bg-gray-200 dark:hover:bg-slate-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500';
|
||||
toggleBtn.className = 'p-1.5 rounded-lg hover:bg-surface-400 transition-colors focus:outline-none focus:ring-2 focus:ring-brand/50';
|
||||
toggleBtn.innerHTML = `
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 text-gray-500 hover:text-brand transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
@@ -292,12 +309,12 @@
|
||||
|
||||
// Copy button
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'p-1 rounded hover:bg-gray-200 dark:hover:bg-slate-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500';
|
||||
copyBtn.className = 'p-1.5 rounded-lg hover:bg-surface-400 transition-colors focus:outline-none focus:ring-2 focus:ring-brand/50';
|
||||
copyBtn.innerHTML = `
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 text-gray-500 hover:text-brand transition-colors copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<svg class="w-4 h-4 text-green-500 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 text-brand check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
`;
|
||||
@@ -336,11 +353,11 @@
|
||||
name: key,
|
||||
description: '',
|
||||
icon: key.substring(0, 2).toUpperCase(),
|
||||
color: 'bg-slate-500'
|
||||
color: 'bg-gray-600'
|
||||
};
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700 p-5 hover:shadow-lg transition-shadow';
|
||||
card.className = 'bg-surface-100 rounded-xl border border-surface-400 p-5 hover:border-brand/30 hover:bg-surface-200 transition-all';
|
||||
|
||||
// Build credentials section
|
||||
let credentialsHtml = '';
|
||||
@@ -349,8 +366,8 @@
|
||||
|
||||
if (creds.note) {
|
||||
credentialsHtml = `
|
||||
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-slate-700">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">${escapeHtml(creds.note)}</p>
|
||||
<div class="mt-4 pt-4 border-t border-surface-400">
|
||||
<p class="text-sm text-gray-500 italic">${escapeHtml(creds.note)}</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
@@ -358,29 +375,29 @@
|
||||
if (creds.username) {
|
||||
fields.push(`
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm">Username:</span>
|
||||
<span class="font-mono text-sm select-all">${escapeHtml(creds.username)}</span>
|
||||
<span class="text-gray-500 text-sm">Username:</span>
|
||||
<span class="font-mono text-sm select-all text-gray-300">${escapeHtml(creds.username)}</span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
if (creds.password) {
|
||||
fields.push(`
|
||||
<div class="flex justify-between items-center" id="pwd-${key}">
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm">Password:</span>
|
||||
<span class="text-gray-500 text-sm">Password:</span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
if (creds.api_key) {
|
||||
fields.push(`
|
||||
<div class="flex justify-between items-center" id="api-${key}">
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm">API Key:</span>
|
||||
<span class="text-gray-500 text-sm">API Key:</span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
if (fields.length > 0) {
|
||||
credentialsHtml = `
|
||||
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-slate-700 space-y-2">
|
||||
<div class="mt-4 pt-4 border-t border-surface-400 space-y-2">
|
||||
${fields.join('')}
|
||||
</div>
|
||||
`;
|
||||
@@ -395,13 +412,13 @@
|
||||
const extra = serviceData.extra;
|
||||
|
||||
if (extra.internal_api) {
|
||||
extraItems.push(`<span class="text-xs text-gray-400 dark:text-gray-500">Internal: ${escapeHtml(extra.internal_api)}</span>`);
|
||||
extraItems.push(`<span class="text-xs text-gray-600 font-mono">Internal: ${escapeHtml(extra.internal_api)}</span>`);
|
||||
}
|
||||
if (extra.workers) {
|
||||
extraItems.push(`<span class="text-xs text-gray-400 dark:text-gray-500">Workers: ${escapeHtml(extra.workers)}</span>`);
|
||||
extraItems.push(`<span class="text-xs text-gray-600">Workers: ${escapeHtml(extra.workers)}</span>`);
|
||||
}
|
||||
if (extra.recommendation) {
|
||||
extraItems.push(`<span class="text-xs text-amber-600 dark:text-amber-400">${escapeHtml(extra.recommendation)}</span>`);
|
||||
extraItems.push(`<span class="text-xs text-brand">${escapeHtml(extra.recommendation)}</span>`);
|
||||
}
|
||||
|
||||
if (extraItems.length > 0) {
|
||||
@@ -411,21 +428,21 @@
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="${metadata.color} w-12 h-12 rounded-lg flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
|
||||
<div class="${metadata.color} w-11 h-11 rounded-lg flex items-center justify-center text-white font-bold text-sm flex-shrink-0 shadow-lg">
|
||||
${metadata.icon}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-semibold text-lg">${escapeHtml(metadata.name)}</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">${escapeHtml(metadata.description)}</p>
|
||||
<h3 class="font-semibold text-white">${escapeHtml(metadata.name)}</h3>
|
||||
<p class="text-sm text-gray-500 mb-2">${escapeHtml(metadata.description)}</p>
|
||||
${serviceData.hostname ? `
|
||||
<a href="https://${escapeHtml(serviceData.hostname)}" target="_blank" rel="noopener"
|
||||
class="text-blue-500 hover:text-blue-600 text-sm font-medium inline-flex items-center gap-1 group">
|
||||
class="text-brand hover:text-brand-400 text-sm font-medium inline-flex items-center gap-1 group transition-colors">
|
||||
${escapeHtml(serviceData.hostname)}
|
||||
<svg class="w-3 h-3 group-hover:translate-x-0.5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
` : '<span class="text-sm text-gray-400 dark:text-gray-500 italic">Internal service</span>'}
|
||||
` : '<span class="text-sm text-gray-600 italic">Internal service</span>'}
|
||||
${extraHtml}
|
||||
</div>
|
||||
</div>
|
||||
@@ -463,7 +480,7 @@
|
||||
|
||||
if (!services || Object.keys(services).length === 0) {
|
||||
servicesContainer.innerHTML = `
|
||||
<div class="col-span-full text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<div class="col-span-full text-center py-12 text-gray-500">
|
||||
<p>No services configured. Run the installer to set up services.</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -500,15 +517,15 @@
|
||||
|
||||
steps.forEach(item => {
|
||||
const stepEl = document.createElement('div');
|
||||
stepEl.className = 'flex items-start gap-4 p-4 bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700';
|
||||
stepEl.className = 'flex items-start gap-4 p-4 bg-surface-100 rounded-xl border border-surface-400 hover:border-brand/30 transition-all';
|
||||
|
||||
stepEl.innerHTML = `
|
||||
<div class="w-8 h-8 rounded-full bg-green-500 text-white flex items-center justify-center font-bold text-sm flex-shrink-0">
|
||||
<div class="w-8 h-8 rounded-full bg-brand/20 border border-brand/30 text-brand flex items-center justify-center font-semibold text-sm flex-shrink-0">
|
||||
${item.step}
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold">${escapeHtml(item.title)}</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">${escapeHtml(item.description)}</p>
|
||||
<h4 class="font-semibold text-white">${escapeHtml(item.title)}</h4>
|
||||
<p class="text-sm text-gray-500">${escapeHtml(item.description)}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -516,6 +533,30 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render make commands
|
||||
*/
|
||||
function renderCommands() {
|
||||
commandsContainer.innerHTML = '';
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'grid gap-4 sm:grid-cols-2 lg:grid-cols-3';
|
||||
|
||||
COMMANDS.forEach(item => {
|
||||
const cmdEl = document.createElement('div');
|
||||
cmdEl.className = 'flex flex-col gap-1 p-3 rounded-lg bg-surface-200/50 border border-surface-400 hover:border-brand/30 transition-all';
|
||||
|
||||
cmdEl.innerHTML = `
|
||||
<code class="text-brand font-mono text-sm">${escapeHtml(item.cmd)}</code>
|
||||
<span class="text-gray-500 text-xs">${escapeHtml(item.desc)}</span>
|
||||
`;
|
||||
|
||||
grid.appendChild(cmdEl);
|
||||
});
|
||||
|
||||
commandsContainer.appendChild(grid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
@@ -530,6 +571,9 @@
|
||||
* Load data and render page
|
||||
*/
|
||||
async function init() {
|
||||
// Always render commands (static content)
|
||||
renderCommands();
|
||||
|
||||
try {
|
||||
const response = await fetch('data.json');
|
||||
|
||||
@@ -559,12 +603,12 @@
|
||||
|
||||
// Show error in UI
|
||||
servicesContainer.innerHTML = `
|
||||
<div class="col-span-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-6 text-center">
|
||||
<div class="col-span-full bg-red-900/20 border border-red-800/50 rounded-xl p-6 text-center">
|
||||
<svg class="w-12 h-12 mx-auto text-red-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
<h3 class="font-semibold text-red-700 dark:text-red-400 mb-2">Unable to load service data</h3>
|
||||
<p class="text-sm text-red-600 dark:text-red-300">Make sure the installation completed successfully and data.json was generated.</p>
|
||||
<h3 class="font-semibold text-red-400 mb-2">Unable to load service data</h3>
|
||||
<p class="text-sm text-red-300/80">Make sure the installation completed successfully and data.json was generated.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome to n8n-install</title>
|
||||
<title>Welcome | n8n-install</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
// Auto dark mode detection
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
document.documentElement.classList.toggle('dark', e.matches);
|
||||
});
|
||||
|
||||
// Tailwind config for dark mode
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8'
|
||||
// Supabase-inspired color scheme
|
||||
brand: {
|
||||
DEFAULT: '#3ECF8E',
|
||||
50: '#E8FBF2',
|
||||
100: '#D1F7E5',
|
||||
200: '#A3EFCB',
|
||||
300: '#75E7B1',
|
||||
400: '#47DF97',
|
||||
500: '#3ECF8E',
|
||||
600: '#24B374',
|
||||
700: '#1A8557',
|
||||
800: '#11573A',
|
||||
900: '#07291B'
|
||||
},
|
||||
surface: {
|
||||
DEFAULT: '#1C1C1C',
|
||||
50: '#171717',
|
||||
100: '#1C1C1C',
|
||||
200: '#232323',
|
||||
300: '#2A2A2A',
|
||||
400: '#313131',
|
||||
500: '#3B3B3B'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,91 +40,140 @@
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* Custom scrollbar for dark mode */
|
||||
.dark ::-webkit-scrollbar { width: 8px; }
|
||||
.dark ::-webkit-scrollbar-track { background: #1e293b; }
|
||||
.dark ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
|
||||
.dark ::-webkit-scrollbar-thumb:hover { background: #64748b; }
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: #1C1C1C; }
|
||||
::-webkit-scrollbar-thumb { background: #3B3B3B; border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #4B4B4B; }
|
||||
|
||||
/* Subtle glow effect for brand elements */
|
||||
.brand-glow {
|
||||
box-shadow: 0 0 20px rgba(62, 207, 142, 0.15);
|
||||
}
|
||||
|
||||
/* Border gradient effect */
|
||||
.border-gradient {
|
||||
position: relative;
|
||||
}
|
||||
.border-gradient::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 1px;
|
||||
background: linear-gradient(135deg, rgba(62, 207, 142, 0.3), transparent 50%);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen transition-colors duration-200">
|
||||
<div class="max-w-5xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
|
||||
<body class="bg-[#111111] text-gray-100 min-h-screen antialiased">
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
|
||||
<!-- Header -->
|
||||
<header class="text-center mb-12">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent mb-3">
|
||||
Welcome to n8n-install
|
||||
<header class="text-center mb-16">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-surface-200 border border-surface-400 text-sm text-gray-400 mb-6">
|
||||
<span class="w-2 h-2 rounded-full bg-brand animate-pulse"></span>
|
||||
System Online
|
||||
</div>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-4 tracking-tight">
|
||||
Welcome to <span class="text-brand">n8n-install</span>
|
||||
</h1>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400">
|
||||
Your self-hosted automation platform is ready
|
||||
<p class="text-lg text-gray-400 max-w-2xl mx-auto">
|
||||
Your self-hosted automation platform is ready to use
|
||||
</p>
|
||||
<p id="domain-info" class="text-sm text-gray-500 dark:text-gray-500 mt-2"></p>
|
||||
<p id="domain-info" class="text-sm text-gray-500 mt-4 font-mono"></p>
|
||||
</header>
|
||||
|
||||
<!-- Services Section -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-semibold mb-6 flex items-center gap-2">
|
||||
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
||||
</svg>
|
||||
Your Services
|
||||
</h2>
|
||||
<section class="mb-16">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-brand/10 border border-brand/20 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-semibold text-white">Your Services</h2>
|
||||
</div>
|
||||
<div id="services-container" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- Services will be injected here by JavaScript -->
|
||||
<div class="animate-pulse bg-gray-200 dark:bg-slate-800 rounded-xl h-48"></div>
|
||||
<div class="animate-pulse bg-gray-200 dark:bg-slate-800 rounded-xl h-48"></div>
|
||||
<div class="animate-pulse bg-gray-200 dark:bg-slate-800 rounded-xl h-48"></div>
|
||||
<div class="animate-pulse bg-surface-200 rounded-xl h-48 border border-surface-400"></div>
|
||||
<div class="animate-pulse bg-surface-200 rounded-xl h-48 border border-surface-400"></div>
|
||||
<div class="animate-pulse bg-surface-200 rounded-xl h-48 border border-surface-400"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Start Section -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-semibold mb-6 flex items-center gap-2">
|
||||
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
Quick Start
|
||||
</h2>
|
||||
<div id="quickstart-container" class="space-y-4">
|
||||
<section class="mb-16">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-brand/10 border border-brand/20 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-semibold text-white">Quick Start</h2>
|
||||
</div>
|
||||
<div id="quickstart-container" class="space-y-3">
|
||||
<!-- Quick start steps will be injected here -->
|
||||
<div class="animate-pulse bg-gray-200 dark:bg-slate-800 rounded-xl h-20"></div>
|
||||
<div class="animate-pulse bg-gray-200 dark:bg-slate-800 rounded-xl h-20"></div>
|
||||
<div class="animate-pulse bg-surface-200 rounded-xl h-20 border border-surface-400"></div>
|
||||
<div class="animate-pulse bg-surface-200 rounded-xl h-20 border border-surface-400"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Commands Section -->
|
||||
<section class="mb-16">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-brand/10 border border-brand/20 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-semibold text-white">Make Commands</h2>
|
||||
</div>
|
||||
<div id="commands-container" class="bg-surface-100 rounded-xl border border-surface-400 p-6">
|
||||
<!-- Commands will be injected here by JavaScript -->
|
||||
<div class="animate-pulse h-32"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Documentation Section -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-semibold mb-6 flex items-center gap-2">
|
||||
<svg class="w-6 h-6 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
Documentation
|
||||
</h2>
|
||||
<section class="mb-16">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-brand/10 border border-brand/20 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-semibold text-white">Documentation</h2>
|
||||
</div>
|
||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<a href="https://docs.n8n.io/" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-3 p-4 bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors group">
|
||||
<span class="text-gray-700 dark:text-gray-300 group-hover:text-blue-500 transition-colors">n8n Docs</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-400 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
class="flex items-center gap-3 p-4 bg-surface-100 rounded-xl border border-surface-400 hover:border-brand/50 hover:bg-surface-200 transition-all group">
|
||||
<span class="text-gray-300 group-hover:text-brand transition-colors">n8n Docs</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-500 group-hover:text-brand transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://docs.n8n.io/advanced-ai/intro-tutorial/" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-3 p-4 bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors group">
|
||||
<span class="text-gray-700 dark:text-gray-300 group-hover:text-blue-500 transition-colors">AI Tutorial</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-400 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
class="flex items-center gap-3 p-4 bg-surface-100 rounded-xl border border-surface-400 hover:border-brand/50 hover:bg-surface-200 transition-all group">
|
||||
<span class="text-gray-300 group-hover:text-brand transition-colors">AI Tutorial</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-500 group-hover:text-brand transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://n8n.io/workflows/" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-3 p-4 bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors group">
|
||||
<span class="text-gray-700 dark:text-gray-300 group-hover:text-blue-500 transition-colors">Templates</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-400 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
class="flex items-center gap-3 p-4 bg-surface-100 rounded-xl border border-surface-400 hover:border-brand/50 hover:bg-surface-200 transition-all group">
|
||||
<span class="text-gray-300 group-hover:text-brand transition-colors">Templates</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-500 group-hover:text-brand transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://github.com/kossakovsky/n8n-install" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-3 p-4 bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors group">
|
||||
<span class="text-gray-700 dark:text-gray-300 group-hover:text-blue-500 transition-colors">GitHub</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-400 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
class="flex items-center gap-3 p-4 bg-surface-100 rounded-xl border border-surface-400 hover:border-brand/50 hover:bg-surface-200 transition-all group">
|
||||
<span class="text-gray-300 group-hover:text-brand transition-colors">GitHub</span>
|
||||
<svg class="w-4 h-4 ml-auto text-gray-500 group-hover:text-brand transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
@@ -124,13 +181,13 @@
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="text-center text-sm text-gray-500 dark:text-gray-500 pt-8 border-t border-gray-200 dark:border-slate-800">
|
||||
<p>Powered by <a href="https://github.com/kossakovsky/n8n-install" target="_blank" rel="noopener" class="text-blue-500 hover:underline">n8n-install</a></p>
|
||||
<footer class="text-center text-sm text-gray-500 pt-8 border-t border-surface-400">
|
||||
<p>Powered by <a href="https://github.com/kossakovsky" target="_blank" rel="noopener" class="text-brand hover:text-brand-400 transition-colors">Yury Kossakovsky</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Error Toast (hidden by default) -->
|
||||
<div id="error-toast" class="fixed bottom-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 hidden">
|
||||
<div id="error-toast" class="fixed bottom-4 right-4 bg-red-500/90 backdrop-blur-sm text-white px-6 py-3 rounded-xl shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 hidden border border-red-400/50">
|
||||
<p id="error-message">Error loading data</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user