feat: Fix product tables and responsive fonts

- Add product detail page ([slug].astro) with table rendering
- Display productTables from site-config.ts on product pages
- Add responsive font scaling for large screens (1280px+)
- Base font scales from 16px to 24px on 4K displays
- All text elements use responsive sizing (md/lg/xl breakpoints)
- Tables styled with green headers and alternating rows
- Add comprehensive documentation (FIXES_SUMMARY.md)

Fixes:
- Product specification tables now visible on product pages
- Font too small on large screens - now responsive
This commit is contained in:
Kunthawat Greethong
2026-03-02 12:22:13 +07:00
parent 6b453a8b86
commit ede8e32591
179 changed files with 35057 additions and 0 deletions

View File

@@ -0,0 +1,280 @@
#!/bin/bash
# Easypanel Deployment Skill v2.2
# Production-ready with error handling
set -e
CREDENTIALS_FILE="$HOME/.easypanel/credentials"
STATE_FILE="$HOME/.easypanel/state.json"
CONFIG_FILE="easypanel.config.json"
# Load credentials
if [ -f "$CREDENTIALS_FILE" ]; then
export $(grep -v '^#' "$CREDENTIALS_FILE" | xargs) 2>/dev/null || true
fi
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE} $1${NC}"; }
log_success() { echo -e "${GREEN}$1${NC}"; }
log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
log_error() { echo -e "${RED}$1${NC}"; }
# Check token
if [ -z "$EASYPANEL_API_TOKEN" ] || [ "$EASYPANEL_API_TOKEN" = "YOUR_API_TOKEN_HERE" ]; then
log_error "API token not set!"
echo ""
echo "Edit ~/.easypanel/credentials and add your token"
exit 1
fi
# Load config
load_config() {
if [ -f "$CONFIG_FILE" ]; then
APP_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['name'])" 2>/dev/null || echo "")
PORT=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['port'])" 2>/dev/null || echo "4321")
DOCKER_IMAGE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['image'])" 2>/dev/null || echo "")
PROJECT_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['project'])" 2>/dev/null || echo "default")
fi
APP_NAME="${APP_NAME:-$(basename "$(pwd)")}"
PORT="${PORT:-4321}"
DOCKER_IMAGE="${DOCKER_IMAGE:-$APP_NAME:latest}"
PROJECT_NAME="${PROJECT_NAME:-default}"
}
# Save state
save_state() {
python3 << PYEOF
import json, os
from datetime import datetime
state_file = "$STATE_FILE"
os.makedirs(os.path.dirname(state_file), exist_ok=True)
if os.path.exists(state_file):
with open(state_file, 'r') as f:
state = json.load(f)
else:
state = {"version":"2.0","projects":{},"services":{},"deployments":[]}
if "$1" == "service":
state['services']['$2'] = {'id':'$3','project_id':'$PROJECT_ID','name':'$APP_NAME','port':$PORT,'updated':datetime.utcnow().isoformat()+"Z"}
elif "$1" == "project":
state['projects']['$2'] = {'id':'$3','name':'$2','updated':datetime.utcnow().isoformat()+"Z"}
with open(state_file, 'w') as f:
json.dump(state, f, indent=2)
print(f"Saved: {$1}.$2 = $3")
PYEOF
}
# Get state
get_state() {
python3 << PYEOF 2>/dev/null || echo ""
import json
try:
with open('$STATE_FILE', 'r') as f:
state = json.load(f)
key, type = '$1', '$2'
if type == 'service' and key in state.get('services', {}):
print(state['services'][key].get('id', ''))
elif type == 'project' and key in state.get('projects', {}):
print(state['projects'][key].get('id', ''))
except:
pass
PYEOF
}
# List projects
list_projects() {
log_info "Fetching projects from Easypanel..."
local response=$(curl -s -w "\n%{http_code}" \
"http://110.164.146.46:3000/api/trpc/projects.listProjects" \
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
--insecure --compressed)
local http_code=$(echo "$response" | tail -1)
local body=$(echo "$response" | sed '$d')
if [ "$http_code" != "200" ]; then
log_error "Failed to fetch projects (HTTP $http_code)"
return 1
fi
echo "$body" | python3 << 'PYEOF'
import json, sys
try:
data = json.load(sys.stdin)
result = data.get('result', {})
data_content = result.get('data', {}) if isinstance(result, dict) else {}
items = data_content.get('items', [])
if not items:
print("No projects found")
else:
print(f"\n{'ID':<25} {'Name':<30}")
print("-" * 60)
for proj in items:
pid = str(proj.get('id', ''))[:23]
name = str(proj.get('name', ''))[:28]
print(f"{pid:<25} {name:<30}")
print("-" * 60)
print(f"Total: {len(items)} project(s)\n")
except Exception as e:
print(f"Error parsing response: {e}")
print("Raw response:", sys.stdin.read()[:200])
PYEOF
}
# Deploy command
cmd_deploy() {
load_config
log_info "========================================"
log_info "Deploying $APP_NAME"
log_info "========================================"
log_info "Project: $PROJECT_NAME"
log_info "Image: $DOCKER_IMAGE"
log_info "Port: $PORT"
log_info ""
# Build Docker
log_info "Building Docker image..."
if [ ! "$SKIP_BUILD" = "true" ]; then
docker build -t "$DOCKER_IMAGE" . || {
log_error "Docker build failed"
exit 1
}
log_success "Built: $DOCKER_IMAGE"
fi
# List projects and find/create
log_info "Looking for project: $PROJECT_NAME"
list_projects
log_warning ""
log_warning "⚠️ Manual Step Required"
log_warning ""
log_info "Easypanel API requires service creation via dashboard"
log_info ""
log_info "Steps:"
log_info "1. Open: $EASYPANEL_URL"
log_info "2. Select project: $PROJECT_NAME (or create it)"
log_info "3. Click 'New Service' → 'Docker image'"
log_info "4. Enter:"
log_info " Name: $APP_NAME"
log_info " Image: $DOCKER_IMAGE"
log_info " Port: $PORT"
log_info "5. Deploy"
log_info ""
log_info "After deployment, save the service ID:"
log_info "./deploy.sh register SERVICE_ID"
log_info ""
# Save deployment attempt
save_state "last_attempt" "$APP_NAME" "deployment"
}
# Register service
cmd_register() {
local service_id="$1"
if [ -z "$service_id" ]; then
log_error "Service ID required"
log_info "Usage: ./deploy.sh register SERVICE_ID"
exit 1
fi
load_config
save_state "service" "$APP_NAME" "$service_id"
log_success "✅ Service registered!"
log_info "Service ID: $service_id"
log_info "Next update: ./deploy.sh update"
}
# Update service
cmd_update() {
load_config
SERVICE_ID=$(get_state "$APP_NAME" "service")
if [ -z "$SERVICE_ID" ]; then
log_error "Service not registered"
log_info "Run: ./deploy.sh deploy"
exit 1
fi
log_info "Updating: $APP_NAME ($SERVICE_ID)"
# Rebuild
if [ ! "$SKIP_BUILD" = "true" ]; then
docker build -t "$DOCKER_IMAGE" . || exit 1
log_success "Rebuilt: $DOCKER_IMAGE"
fi
log_info "✅ Image ready"
log_info ""
log_info "To deploy update:"
log_info "1. Go to $EASYPANEL_URL"
log_info "2. Select service: $APP_NAME"
log_info "3. Click 'Deploy' to pull new image"
}
# Status
cmd_status() {
load_config
SERVICE_ID=$(get_state "$APP_NAME" "service")
if [ -z "$SERVICE_ID" ]; then
log_error "Not deployed"
exit 1
fi
log_info "Service: $APP_NAME"
log_info "ID: $SERVICE_ID"
log_info "Image: $DOCKER_IMAGE"
log_info "Port: $PORT"
}
# Help
cmd_help() {
cat << 'EOF'
Easypanel Deployment Skill v2.2
Usage: ./deploy.sh [command]
Commands:
deploy Build and prepare deployment
register ID Register service with ID
update Update existing service
status Show status
list List projects
help This help
Workflow:
1. ./deploy.sh deploy
2. Create service in Easypanel dashboard
3. ./deploy.sh register SERVICE_ID
4. ./deploy.sh update (for future updates)
EOF
}
SKIP_BUILD="false"
[ "$1" = "-b" ] || [ "$1" = "--skip-build" ] && SKIP_BUILD="true"
case "${1:-deploy}" in
deploy) cmd_deploy ;;
register) cmd_register "$2" ;;
update) cmd_update ;;
status) cmd_status ;;
list) list_projects ;;
help|--help|-h) cmd_help ;;
*) cmd_help; exit 1 ;;
esac