feat(secubox-app-reporter): Add report generator for dev and services status
Two-report system for SecuBox status reporting: - Development Status Report: health score, HISTORY.md completions, WIP items, roadmap progress - Services Distribution Report: Tor hidden services, DNS/SSL vhosts, mesh services Features: - CLI: secubox-reportctl generate|send|schedule|status|preview|list|clean - HTML output with KissTheme dark styling - Email delivery via msmtp/sendmail with MIME multipart - UCI configuration for SMTP and scheduling - Cron integration for automated reports Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
019e181890
commit
246f2acc18
51
package/secubox/secubox-app-reporter/Makefile
Normal file
51
package/secubox/secubox-app-reporter/Makefile
Normal file
@ -0,0 +1,51 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-app-reporter
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/secubox-app-reporter
|
||||
SECTION:=secubox
|
||||
CATEGORY:=SecuBox
|
||||
TITLE:=SecuBox Report Generator
|
||||
DEPENDS:=+secubox-core +curl +msmtp
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/secubox-app-reporter/description
|
||||
Generates and distributes SecuBox status reports via HTML and email.
|
||||
Includes development progress tracking and services distribution status.
|
||||
endef
|
||||
|
||||
define Package/secubox-app-reporter/conffiles
|
||||
/etc/config/secubox-reporter
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/secubox-app-reporter/install
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/secubox-reporter $(1)/etc/config/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/cron.d
|
||||
$(INSTALL_DATA) ./files/etc/cron.d/secubox-reporter $(1)/etc/cron.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/secubox-reportctl $(1)/usr/sbin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/secubox-reporter/lib
|
||||
$(INSTALL_DATA) ./files/usr/share/secubox-reporter/lib/*.sh $(1)/usr/share/secubox-reporter/lib/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/secubox-reporter/templates
|
||||
$(INSTALL_DATA) ./files/usr/share/secubox-reporter/templates/*.tpl $(1)/usr/share/secubox-reporter/templates/
|
||||
|
||||
$(INSTALL_DIR) $(1)/www/reports
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,secubox-app-reporter))
|
||||
@ -0,0 +1,26 @@
|
||||
config global 'global'
|
||||
option enabled '1'
|
||||
option theme 'dark'
|
||||
option output_dir '/www/reports'
|
||||
option retention_days '30'
|
||||
|
||||
config email 'email'
|
||||
option enabled '0'
|
||||
option recipient ''
|
||||
option smtp_server ''
|
||||
option smtp_port '587'
|
||||
option smtp_user ''
|
||||
option smtp_password ''
|
||||
option smtp_tls '1'
|
||||
|
||||
config schedule 'schedule'
|
||||
option dev 'off'
|
||||
option services 'off'
|
||||
|
||||
config sources 'sources'
|
||||
option repo_path '/root/secubox-openwrt'
|
||||
option history_file '.claude/HISTORY.md'
|
||||
option wip_file '.claude/WIP.md'
|
||||
option todo_file '.claude/TODO.md'
|
||||
option roadmap_file '.claude/ROADMAP.md'
|
||||
option devstatus_file '.claude/DEV-STATUS.md'
|
||||
@ -0,0 +1,11 @@
|
||||
# SecuBox Report Generator - Scheduled Reports
|
||||
# Managed by: secubox-reportctl schedule
|
||||
|
||||
# Daily development report at 6 AM (disabled by default)
|
||||
#DAILY_DEV#0 6 * * * root /usr/sbin/secubox-reportctl send dev >/dev/null 2>&1
|
||||
|
||||
# Weekly services report on Monday at 7 AM (disabled by default)
|
||||
#WEEKLY_SERVICES#0 7 * * 1 root /usr/sbin/secubox-reportctl send services >/dev/null 2>&1
|
||||
|
||||
# Monthly full report on 1st at 8 AM (disabled by default)
|
||||
#MONTHLY_ALL#0 8 1 * * root /usr/sbin/secubox-reportctl send all >/dev/null 2>&1
|
||||
@ -0,0 +1,434 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Report Generator
|
||||
# Generates and distributes status reports via HTML and email
|
||||
# Copyright (C) 2026 CyberMind.fr
|
||||
|
||||
VERSION="1.0.0"
|
||||
SCRIPT_DIR="/usr/share/secubox-reporter"
|
||||
LIB_DIR="$SCRIPT_DIR/lib"
|
||||
TPL_DIR="$SCRIPT_DIR/templates"
|
||||
OUTPUT_DIR="/www/reports"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Load libraries
|
||||
. /lib/functions.sh
|
||||
[ -f "$LIB_DIR/collectors.sh" ] && . "$LIB_DIR/collectors.sh"
|
||||
[ -f "$LIB_DIR/formatters.sh" ] && . "$LIB_DIR/formatters.sh"
|
||||
[ -f "$LIB_DIR/mailer.sh" ] && . "$LIB_DIR/mailer.sh"
|
||||
|
||||
# Load config
|
||||
config_load secubox-reporter
|
||||
config_get OUTPUT_DIR global output_dir "/www/reports"
|
||||
config_get THEME global theme "dark"
|
||||
config_get REPO_PATH sources repo_path "/root/secubox-openwrt"
|
||||
|
||||
log_info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
||||
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
log_err() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
SecuBox Report Generator v$VERSION
|
||||
|
||||
Usage: secubox-reportctl <command> [options]
|
||||
|
||||
COMMANDS:
|
||||
generate <type> Generate report (dev|services|all)
|
||||
send <type> Generate and email report
|
||||
schedule <type> Set cron schedule (daily|weekly|off)
|
||||
status Show generator status
|
||||
preview <type> Generate and output to stdout
|
||||
list List generated reports
|
||||
clean [days] Remove reports older than N days (default: 30)
|
||||
help Show this help
|
||||
|
||||
REPORT TYPES:
|
||||
dev Development Status Report (progress, roadmap, health)
|
||||
services Distribution/Services Status Report (exposures, channels)
|
||||
all Both reports
|
||||
|
||||
OPTIONS:
|
||||
--email Also send via email
|
||||
--theme <name> Theme: dark, light, cyberpunk (default: dark)
|
||||
--output <path> Custom output path
|
||||
--json Output status as JSON
|
||||
|
||||
EXAMPLES:
|
||||
secubox-reportctl generate dev
|
||||
secubox-reportctl send services
|
||||
secubox-reportctl schedule dev daily
|
||||
secubox-reportctl generate all --theme cyberpunk
|
||||
EOF
|
||||
}
|
||||
|
||||
# Get hostname
|
||||
get_hostname() {
|
||||
uci -q get system.@system[0].hostname 2>/dev/null || hostname
|
||||
}
|
||||
|
||||
# Get SecuBox version
|
||||
get_version() {
|
||||
cat /etc/secubox_version 2>/dev/null || echo "0.19.x"
|
||||
}
|
||||
|
||||
# Generate development status report
|
||||
generate_dev_report() {
|
||||
local output_file="$1"
|
||||
local theme="${2:-dark}"
|
||||
|
||||
log_info "Generating Development Status Report..."
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
|
||||
local hostname=$(get_hostname)
|
||||
local version=$(get_version)
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local health_score=0
|
||||
|
||||
# Get health score from RPCD if available
|
||||
if command -v ubus >/dev/null 2>&1; then
|
||||
health_score=$(ubus call luci.secubox-core get_full_health_report 2>/dev/null | \
|
||||
jsonfilter -e '@.health_score' 2>/dev/null || echo "0")
|
||||
fi
|
||||
[ -z "$health_score" ] && health_score=0
|
||||
|
||||
# Collect data from .claude files
|
||||
local history_entries=""
|
||||
local wip_entries=""
|
||||
local roadmap_data=""
|
||||
|
||||
if [ -d "$REPO_PATH/.claude" ]; then
|
||||
# Recent history (last 10 dated sections)
|
||||
history_entries=$(collect_history "$REPO_PATH/.claude/HISTORY.md")
|
||||
|
||||
# WIP items
|
||||
wip_entries=$(collect_wip "$REPO_PATH/.claude/WIP.md")
|
||||
|
||||
# Roadmap progress
|
||||
roadmap_data=$(collect_roadmap "$REPO_PATH/.claude/ROADMAP.md")
|
||||
else
|
||||
history_entries="<p class='muted'>Repository not found at $REPO_PATH</p>"
|
||||
wip_entries="<p class='muted'>Repository not found</p>"
|
||||
roadmap_data="<p class='muted'>Repository not found</p>"
|
||||
fi
|
||||
|
||||
# Generate HTML from template
|
||||
if [ -f "$TPL_DIR/dev-status.html.tpl" ]; then
|
||||
sed -e "s|{{HOSTNAME}}|$hostname|g" \
|
||||
-e "s|{{VERSION}}|$version|g" \
|
||||
-e "s|{{TIMESTAMP}}|$timestamp|g" \
|
||||
-e "s|{{HEALTH_SCORE}}|$health_score|g" \
|
||||
-e "s|{{THEME}}|$theme|g" \
|
||||
-e "s|{{HISTORY_ENTRIES}}|$history_entries|g" \
|
||||
-e "s|{{WIP_ENTRIES}}|$wip_entries|g" \
|
||||
-e "s|{{ROADMAP_DATA}}|$roadmap_data|g" \
|
||||
"$TPL_DIR/dev-status.html.tpl" > "$output_file"
|
||||
else
|
||||
# Fallback inline template
|
||||
generate_dev_html_inline "$output_file" "$hostname" "$version" "$timestamp" \
|
||||
"$health_score" "$history_entries" "$wip_entries" "$roadmap_data"
|
||||
fi
|
||||
|
||||
log_ok "Generated: $output_file"
|
||||
echo "$output_file"
|
||||
}
|
||||
|
||||
# Generate services status report
|
||||
generate_services_report() {
|
||||
local output_file="$1"
|
||||
local theme="${2:-dark}"
|
||||
|
||||
log_info "Generating Services Status Report..."
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
|
||||
local hostname=$(get_hostname)
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Count services by channel
|
||||
local tor_count=0
|
||||
local dns_count=0
|
||||
local mesh_count=0
|
||||
|
||||
# Tor hidden services
|
||||
if [ -d /var/lib/tor/hidden_services ]; then
|
||||
tor_count=$(ls -1 /var/lib/tor/hidden_services 2>/dev/null | wc -l)
|
||||
fi
|
||||
|
||||
# HAProxy vhosts
|
||||
dns_count=$(uci show haproxy 2>/dev/null | grep '=vhost$' | wc -l)
|
||||
|
||||
# Mesh services (from P2P if available)
|
||||
if command -v secubox-p2p >/dev/null 2>&1; then
|
||||
mesh_count=$(secubox-p2p shared-services 2>/dev/null | wc -l)
|
||||
fi
|
||||
|
||||
# Collect service details
|
||||
local tor_services=$(collect_tor_services)
|
||||
local dns_services=$(collect_dns_services)
|
||||
local mesh_services=$(collect_mesh_services)
|
||||
|
||||
# Health check
|
||||
local health_up=0
|
||||
local health_total=$((tor_count + dns_count))
|
||||
|
||||
# Generate HTML
|
||||
if [ -f "$TPL_DIR/services-status.html.tpl" ]; then
|
||||
sed -e "s|{{HOSTNAME}}|$hostname|g" \
|
||||
-e "s|{{TIMESTAMP}}|$timestamp|g" \
|
||||
-e "s|{{TOR_COUNT}}|$tor_count|g" \
|
||||
-e "s|{{DNS_COUNT}}|$dns_count|g" \
|
||||
-e "s|{{MESH_COUNT}}|$mesh_count|g" \
|
||||
-e "s|{{HEALTH_UP}}|$health_up|g" \
|
||||
-e "s|{{HEALTH_TOTAL}}|$health_total|g" \
|
||||
-e "s|{{TOR_SERVICES}}|$tor_services|g" \
|
||||
-e "s|{{DNS_SERVICES}}|$dns_services|g" \
|
||||
-e "s|{{MESH_SERVICES}}|$mesh_services|g" \
|
||||
-e "s|{{THEME}}|$theme|g" \
|
||||
"$TPL_DIR/services-status.html.tpl" > "$output_file"
|
||||
else
|
||||
generate_services_html_inline "$output_file" "$hostname" "$timestamp" \
|
||||
"$tor_count" "$dns_count" "$mesh_count" \
|
||||
"$tor_services" "$dns_services" "$mesh_services"
|
||||
fi
|
||||
|
||||
log_ok "Generated: $output_file"
|
||||
echo "$output_file"
|
||||
}
|
||||
|
||||
# Command: generate
|
||||
cmd_generate() {
|
||||
local report_type="$1"
|
||||
local theme="$THEME"
|
||||
local output_path="$OUTPUT_DIR"
|
||||
|
||||
shift
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--theme) theme="$2"; shift 2 ;;
|
||||
--output) output_path="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local timestamp=$(date '+%Y%m%d-%H%M%S')
|
||||
|
||||
case "$report_type" in
|
||||
dev)
|
||||
generate_dev_report "$output_path/dev-status-$timestamp.html" "$theme"
|
||||
;;
|
||||
services)
|
||||
generate_services_report "$output_path/services-status-$timestamp.html" "$theme"
|
||||
;;
|
||||
all)
|
||||
generate_dev_report "$output_path/dev-status-$timestamp.html" "$theme"
|
||||
generate_services_report "$output_path/services-status-$timestamp.html" "$theme"
|
||||
;;
|
||||
*)
|
||||
log_err "Unknown report type: $report_type"
|
||||
echo "Valid types: dev, services, all"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Command: send
|
||||
cmd_send() {
|
||||
local report_type="$1"
|
||||
shift
|
||||
|
||||
# Generate report first
|
||||
local report_file=$(cmd_generate "$report_type" "$@")
|
||||
|
||||
if [ -z "$report_file" ] || [ ! -f "$report_file" ]; then
|
||||
log_err "Failed to generate report"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Send via email
|
||||
local recipient=""
|
||||
config_get recipient email recipient ""
|
||||
|
||||
if [ -z "$recipient" ]; then
|
||||
log_warn "No email recipient configured. Report saved to: $report_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Sending report to $recipient..."
|
||||
|
||||
if send_report_email "$report_type" "$(cat "$report_file")" "$recipient"; then
|
||||
log_ok "Report sent to $recipient"
|
||||
else
|
||||
log_err "Failed to send email"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Command: schedule
|
||||
cmd_schedule() {
|
||||
local report_type="$1"
|
||||
local frequency="$2"
|
||||
local cron_file="/etc/cron.d/secubox-reporter"
|
||||
|
||||
case "$frequency" in
|
||||
daily)
|
||||
sed -i "s/^#DAILY_${report_type^^}#//" "$cron_file" 2>/dev/null
|
||||
uci set secubox-reporter.schedule.$report_type='daily'
|
||||
;;
|
||||
weekly)
|
||||
sed -i "s/^#WEEKLY_${report_type^^}#//" "$cron_file" 2>/dev/null
|
||||
uci set secubox-reporter.schedule.$report_type='weekly'
|
||||
;;
|
||||
off)
|
||||
# Re-comment the lines
|
||||
sed -i "/secubox-reportctl.*$report_type/s/^[^#]/#DISABLED#/" "$cron_file" 2>/dev/null
|
||||
uci set secubox-reporter.schedule.$report_type='off'
|
||||
;;
|
||||
*)
|
||||
log_err "Invalid frequency: $frequency"
|
||||
echo "Valid: daily, weekly, off"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
uci commit secubox-reporter
|
||||
/etc/init.d/cron restart 2>/dev/null || true
|
||||
log_ok "Schedule set: $report_type = $frequency"
|
||||
}
|
||||
|
||||
# Command: status
|
||||
cmd_status() {
|
||||
local json_output=0
|
||||
[ "$1" = "--json" ] && json_output=1
|
||||
|
||||
local hostname=$(get_hostname)
|
||||
local version=$(get_version)
|
||||
local dev_schedule=$(uci -q get secubox-reporter.schedule.dev || echo "off")
|
||||
local services_schedule=$(uci -q get secubox-reporter.schedule.services || echo "off")
|
||||
local recipient=$(uci -q get secubox-reporter.email.recipient || echo "")
|
||||
local report_count=$(ls -1 "$OUTPUT_DIR"/*.html 2>/dev/null | wc -l)
|
||||
|
||||
if [ $json_output -eq 1 ]; then
|
||||
cat << EOF
|
||||
{"hostname":"$hostname","version":"$version","schedules":{"dev":"$dev_schedule","services":"$services_schedule"},"email_recipient":"$recipient","report_count":$report_count}
|
||||
EOF
|
||||
else
|
||||
echo "SecuBox Report Generator v$VERSION"
|
||||
echo "=================================="
|
||||
echo "Hostname: $hostname"
|
||||
echo "Version: $version"
|
||||
echo "Output Dir: $OUTPUT_DIR"
|
||||
echo "Reports: $report_count files"
|
||||
echo ""
|
||||
echo "Schedules:"
|
||||
echo " Dev: $dev_schedule"
|
||||
echo " Services: $services_schedule"
|
||||
echo ""
|
||||
echo "Email: ${recipient:-Not configured}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Command: list
|
||||
cmd_list() {
|
||||
local json_output=0
|
||||
[ "$1" = "--json" ] && json_output=1
|
||||
|
||||
if [ $json_output -eq 1 ]; then
|
||||
echo '{"reports":['
|
||||
local first=1
|
||||
for f in $(ls -1t "$OUTPUT_DIR"/*.html 2>/dev/null); do
|
||||
[ $first -eq 0 ] && echo ","
|
||||
first=0
|
||||
local name=$(basename "$f")
|
||||
local size=$(stat -c%s "$f" 2>/dev/null || echo 0)
|
||||
local mtime=$(stat -c%Y "$f" 2>/dev/null || echo 0)
|
||||
echo "{\"name\":\"$name\",\"size\":$size,\"mtime\":$mtime}"
|
||||
done
|
||||
echo ']}'
|
||||
else
|
||||
echo "Generated Reports:"
|
||||
echo "=================="
|
||||
ls -lh "$OUTPUT_DIR"/*.html 2>/dev/null || echo "No reports found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Command: clean
|
||||
cmd_clean() {
|
||||
local days="${1:-30}"
|
||||
|
||||
log_info "Removing reports older than $days days..."
|
||||
|
||||
local count=$(find "$OUTPUT_DIR" -name "*.html" -mtime +$days 2>/dev/null | wc -l)
|
||||
find "$OUTPUT_DIR" -name "*.html" -mtime +$days -delete 2>/dev/null
|
||||
|
||||
log_ok "Removed $count old reports"
|
||||
}
|
||||
|
||||
# Command: preview
|
||||
cmd_preview() {
|
||||
local report_type="$1"
|
||||
local tmpfile="/tmp/secubox-report-preview-$$.html"
|
||||
|
||||
case "$report_type" in
|
||||
dev)
|
||||
generate_dev_report "$tmpfile" "$THEME" >/dev/null
|
||||
;;
|
||||
services)
|
||||
generate_services_report "$tmpfile" "$THEME" >/dev/null
|
||||
;;
|
||||
*)
|
||||
log_err "Unknown report type: $report_type"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
cat "$tmpfile"
|
||||
rm -f "$tmpfile"
|
||||
}
|
||||
|
||||
# Main
|
||||
case "$1" in
|
||||
generate)
|
||||
shift
|
||||
cmd_generate "$@"
|
||||
;;
|
||||
send)
|
||||
shift
|
||||
cmd_send "$@"
|
||||
;;
|
||||
schedule)
|
||||
shift
|
||||
cmd_schedule "$@"
|
||||
;;
|
||||
status)
|
||||
shift
|
||||
cmd_status "$@"
|
||||
;;
|
||||
list)
|
||||
shift
|
||||
cmd_list "$@"
|
||||
;;
|
||||
clean)
|
||||
shift
|
||||
cmd_clean "$@"
|
||||
;;
|
||||
preview)
|
||||
shift
|
||||
cmd_preview "$@"
|
||||
;;
|
||||
help|--help|-h|"")
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
log_err "Unknown command: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@ -0,0 +1,185 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Reporter - Data Collectors
|
||||
# Extracts data from .claude files and system services
|
||||
|
||||
# Collect recent history entries
|
||||
collect_history() {
|
||||
local history_file="$1"
|
||||
[ ! -f "$history_file" ] && return
|
||||
|
||||
local output=""
|
||||
local count=0
|
||||
local max_entries=10
|
||||
local current_date=""
|
||||
local in_section=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Match date headers like "### 2026-03-12"
|
||||
if echo "$line" | grep -qE '^### [0-9]{4}-[0-9]{2}-[0-9]{2}'; then
|
||||
[ $count -ge $max_entries ] && break
|
||||
|
||||
current_date=$(echo "$line" | sed 's/### //')
|
||||
output="$output<div class='history-entry'><div class='history-date'>$current_date</div><ul>"
|
||||
in_section=1
|
||||
count=$((count + 1))
|
||||
elif [ $in_section -eq 1 ]; then
|
||||
# Match bullet points
|
||||
if echo "$line" | grep -qE '^- '; then
|
||||
local item=$(echo "$line" | sed 's/^- //' | sed 's/\*\*\([^*]*\)\*\*/<strong>\1<\/strong>/g')
|
||||
output="$output<li>$item</li>"
|
||||
elif echo "$line" | grep -qE '^$'; then
|
||||
output="$output</ul></div>"
|
||||
in_section=0
|
||||
fi
|
||||
fi
|
||||
done < "$history_file"
|
||||
|
||||
# Close any open section
|
||||
[ $in_section -eq 1 ] && output="$output</ul></div>"
|
||||
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
# Collect WIP items
|
||||
collect_wip() {
|
||||
local wip_file="$1"
|
||||
[ ! -f "$wip_file" ] && return
|
||||
|
||||
local output=""
|
||||
local in_nextup=0
|
||||
local in_inprogress=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Find "Next Up" or "In Progress" sections
|
||||
if echo "$line" | grep -qiE '^## Next Up|^### Next Up'; then
|
||||
output="$output<div class='wip-section'><h4>Next Up</h4><ul>"
|
||||
in_nextup=1
|
||||
in_inprogress=0
|
||||
elif echo "$line" | grep -qiE '^## In Progress|^### In Progress'; then
|
||||
[ $in_nextup -eq 1 ] && output="$output</ul></div>"
|
||||
output="$output<div class='wip-section'><h4>In Progress</h4><ul>"
|
||||
in_inprogress=1
|
||||
in_nextup=0
|
||||
elif echo "$line" | grep -qE '^## |^### '; then
|
||||
# End of section
|
||||
[ $in_nextup -eq 1 ] && output="$output</ul></div>"
|
||||
[ $in_inprogress -eq 1 ] && output="$output</ul></div>"
|
||||
in_nextup=0
|
||||
in_inprogress=0
|
||||
elif [ $in_nextup -eq 1 ] || [ $in_inprogress -eq 1 ]; then
|
||||
if echo "$line" | grep -qE '^[0-9]+\. |^- '; then
|
||||
local item=$(echo "$line" | sed 's/^[0-9]*\. //' | sed 's/^- //' | sed 's/\*\*\([^*]*\)\*\*/<strong>\1<\/strong>/g')
|
||||
[ -n "$item" ] && output="$output<li>$item</li>"
|
||||
fi
|
||||
fi
|
||||
done < "$wip_file"
|
||||
|
||||
# Close any open sections
|
||||
[ $in_nextup -eq 1 ] && output="$output</ul></div>"
|
||||
[ $in_inprogress -eq 1 ] && output="$output</ul></div>"
|
||||
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
# Collect roadmap progress
|
||||
collect_roadmap() {
|
||||
local roadmap_file="$1"
|
||||
[ ! -f "$roadmap_file" ] && return
|
||||
|
||||
local output=""
|
||||
local current_version=""
|
||||
|
||||
# Extract version sections with status
|
||||
while IFS= read -r line; do
|
||||
if echo "$line" | grep -qE '^### v[0-9]+\.[0-9]+'; then
|
||||
current_version=$(echo "$line" | grep -oE 'v[0-9]+\.[0-9]+')
|
||||
local title=$(echo "$line" | sed 's/^### //')
|
||||
output="$output<div class='roadmap-version'><h4>$title</h4>"
|
||||
elif echo "$line" | grep -qiE '^\*\*Status:'; then
|
||||
local status=$(echo "$line" | sed 's/.*Status: *//' | sed 's/\*//g')
|
||||
local status_class="pending"
|
||||
echo "$status" | grep -qi "progress" && status_class="inprogress"
|
||||
echo "$status" | grep -qi "complete\|done" && status_class="complete"
|
||||
output="$output<span class='status-badge $status_class'>$status</span></div>"
|
||||
fi
|
||||
done < "$roadmap_file"
|
||||
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
# Collect Tor hidden services
|
||||
collect_tor_services() {
|
||||
local output=""
|
||||
local tor_dir="/var/lib/tor/hidden_services"
|
||||
|
||||
[ ! -d "$tor_dir" ] && echo "<p class='muted'>No Tor services configured</p>" && return
|
||||
|
||||
output="<table class='services-table'><thead><tr><th>Service</th><th>Onion Address</th><th>Port</th></tr></thead><tbody>"
|
||||
|
||||
for service_dir in "$tor_dir"/*/; do
|
||||
[ ! -d "$service_dir" ] && continue
|
||||
local name=$(basename "$service_dir")
|
||||
local hostname_file="$service_dir/hostname"
|
||||
|
||||
if [ -f "$hostname_file" ]; then
|
||||
local onion=$(cat "$hostname_file" | head -1)
|
||||
local port=$(grep -oE '[0-9]+$' "$service_dir/../torrc" 2>/dev/null | head -1)
|
||||
[ -z "$port" ] && port="80"
|
||||
output="$output<tr><td>$name</td><td><code>$onion</code></td><td>$port</td></tr>"
|
||||
fi
|
||||
done
|
||||
|
||||
output="$output</tbody></table>"
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
# Collect DNS/SSL services (HAProxy vhosts)
|
||||
collect_dns_services() {
|
||||
local output=""
|
||||
|
||||
output="<table class='services-table'><thead><tr><th>Domain</th><th>Backend</th><th>SSL</th><th>Status</th></tr></thead><tbody>"
|
||||
|
||||
# Parse HAProxy vhosts from UCI
|
||||
for vhost in $(uci show haproxy 2>/dev/null | grep '=vhost$' | cut -d'.' -f2 | cut -d'=' -f1); do
|
||||
local enabled=$(uci -q get haproxy.$vhost.enabled)
|
||||
[ "$enabled" != "1" ] && continue
|
||||
|
||||
local domain=$(uci -q get haproxy.$vhost.domain)
|
||||
local backend=$(uci -q get haproxy.$vhost.backend)
|
||||
local ssl=$(uci -q get haproxy.$vhost.ssl)
|
||||
|
||||
local ssl_icon="❌"
|
||||
[ "$ssl" = "1" ] && ssl_icon="✅"
|
||||
|
||||
local status_class="running"
|
||||
local status_text="Active"
|
||||
|
||||
output="$output<tr><td><a href='https://$domain' target='_blank'>$domain</a></td><td>$backend</td><td>$ssl_icon</td><td><span class='status-badge $status_class'>$status_text</span></td></tr>"
|
||||
done
|
||||
|
||||
output="$output</tbody></table>"
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
# Collect Mesh services
|
||||
collect_mesh_services() {
|
||||
local output=""
|
||||
|
||||
if ! command -v secubox-p2p >/dev/null 2>&1; then
|
||||
echo "<p class='muted'>P2P mesh not installed</p>"
|
||||
return
|
||||
fi
|
||||
|
||||
output="<table class='services-table'><thead><tr><th>Service</th><th>Node</th><th>Port</th></tr></thead><tbody>"
|
||||
|
||||
# Get shared services from P2P
|
||||
secubox-p2p shared-services 2>/dev/null | while read line; do
|
||||
local name=$(echo "$line" | cut -d':' -f1)
|
||||
local node=$(echo "$line" | cut -d':' -f2)
|
||||
local port=$(echo "$line" | cut -d':' -f3)
|
||||
output="$output<tr><td>$name</td><td>$node</td><td>$port</td></tr>"
|
||||
done
|
||||
|
||||
output="$output</tbody></table>"
|
||||
echo "$output"
|
||||
}
|
||||
@ -0,0 +1,228 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Reporter - HTML Formatters
|
||||
# Inline HTML generation for reports
|
||||
|
||||
# KissTheme CSS (embedded)
|
||||
get_kisstheme_css() {
|
||||
cat << 'CSS'
|
||||
:root{
|
||||
--bg:#0a0a0f;--surface:#12121a;--card:#1a1a2e;
|
||||
--ink:#f0f2ff;--dim:rgba(240,242,255,.5);--muted:#666;
|
||||
--primary:#6366f1;--primary-end:#8b5cf6;
|
||||
--cyan:#06b6d4;--green:#22c55e;--red:#ef4444;--yellow:#f59e0b;
|
||||
--glass:rgba(255,255,255,.04);--border:rgba(255,255,255,.08);
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:var(--bg);color:var(--ink);font-family:"Inter","Segoe UI",system-ui,sans-serif;padding:2rem;line-height:1.6}
|
||||
.container{max-width:1200px;margin:0 auto}
|
||||
h1{font-size:2rem;margin-bottom:.5rem;background:linear-gradient(90deg,var(--primary),var(--primary-end),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
||||
h2{font-size:1.25rem;color:var(--cyan);margin:1.5rem 0 1rem;padding-bottom:.5rem;border-bottom:1px solid var(--border)}
|
||||
h3{font-size:1rem;color:var(--ink);margin-bottom:.5rem}
|
||||
h4{font-size:.9rem;color:var(--dim);margin-bottom:.5rem}
|
||||
.header{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;margin-bottom:2rem;padding:1.5rem;background:var(--card);border:1px solid var(--border);border-radius:12px}
|
||||
.header-info{flex:1}
|
||||
.header-meta{display:flex;gap:1rem;flex-wrap:wrap}
|
||||
.meta-item{font-size:.75rem;color:var(--muted);padding:.25rem .75rem;background:var(--glass);border-radius:4px}
|
||||
.score-badge{font-size:2rem;font-weight:700;padding:1rem 1.5rem;background:linear-gradient(135deg,var(--primary),var(--primary-end));border-radius:12px;text-align:center;min-width:100px}
|
||||
.score-label{font-size:.6rem;text-transform:uppercase;letter-spacing:.1em;opacity:.7;display:block;margin-top:.25rem}
|
||||
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1rem;margin-bottom:2rem}
|
||||
.stat-badge{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1rem;text-align:center}
|
||||
.stat-value{font-size:1.5rem;font-weight:700;background:linear-gradient(90deg,var(--primary),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
||||
.stat-label{font-size:.7rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-top:.25rem}
|
||||
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;margin-bottom:1.5rem}
|
||||
.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--border)}
|
||||
.card-title{font-size:1rem;font-weight:600;display:flex;align-items:center;gap:.5rem}
|
||||
.card-title::before{content:"";width:3px;height:1em;background:var(--primary);border-radius:2px}
|
||||
ul{list-style:none;padding-left:0}
|
||||
li{padding:.5rem 0;border-bottom:1px solid var(--border);font-size:.9rem}
|
||||
li:last-child{border-bottom:none}
|
||||
li strong{color:var(--cyan)}
|
||||
.history-entry{margin-bottom:1.5rem}
|
||||
.history-date{font-size:.8rem;color:var(--primary);font-weight:600;margin-bottom:.5rem;padding:.25rem .5rem;background:rgba(99,102,241,.1);border-radius:4px;display:inline-block}
|
||||
.wip-section{margin-bottom:1rem}
|
||||
.wip-section h4{color:var(--cyan);font-size:.85rem;margin-bottom:.5rem}
|
||||
.roadmap-version{display:flex;align-items:center;gap:1rem;padding:.75rem;background:var(--glass);border-radius:8px;margin-bottom:.5rem}
|
||||
.roadmap-version h4{margin:0;flex:1}
|
||||
.status-badge{font-size:.65rem;padding:.25rem .5rem;border-radius:4px;text-transform:uppercase;letter-spacing:.05em;font-weight:600}
|
||||
.status-badge.complete,.status-badge.running{background:rgba(34,197,94,.15);color:var(--green)}
|
||||
.status-badge.inprogress{background:rgba(99,102,241,.15);color:var(--primary)}
|
||||
.status-badge.pending{background:rgba(102,102,102,.15);color:var(--muted)}
|
||||
.services-table{width:100%;border-collapse:collapse;font-size:.85rem}
|
||||
.services-table th,.services-table td{padding:.75rem;text-align:left;border-bottom:1px solid var(--border)}
|
||||
.services-table th{color:var(--dim);font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;font-weight:600}
|
||||
.services-table a{color:var(--cyan);text-decoration:none}
|
||||
.services-table a:hover{text-decoration:underline}
|
||||
.services-table code{font-size:.75rem;padding:.15rem .35rem;background:var(--glass);border-radius:3px;font-family:"JetBrains Mono",monospace}
|
||||
.muted{color:var(--muted);font-style:italic}
|
||||
footer{margin-top:3rem;text-align:center;color:var(--muted);font-size:.75rem;padding-top:1.5rem;border-top:1px solid var(--border)}
|
||||
footer a{color:var(--primary)}
|
||||
@media(max-width:768px){body{padding:1rem}.header{flex-direction:column}.stats-grid{grid-template-columns:repeat(2,1fr)}}
|
||||
CSS
|
||||
}
|
||||
|
||||
# Generate development report HTML inline
|
||||
generate_dev_html_inline() {
|
||||
local output_file="$1"
|
||||
local hostname="$2"
|
||||
local version="$3"
|
||||
local timestamp="$4"
|
||||
local health_score="$5"
|
||||
local history_entries="$6"
|
||||
local wip_entries="$7"
|
||||
local roadmap_data="$8"
|
||||
|
||||
cat > "$output_file" << EOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Development Status - $hostname</title>
|
||||
<style>
|
||||
$(get_kisstheme_css)
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header-info">
|
||||
<h1>Development Status Report</h1>
|
||||
<div class="header-meta">
|
||||
<span class="meta-item">$hostname</span>
|
||||
<span class="meta-item">v$version</span>
|
||||
<span class="meta-item">$timestamp</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-badge">
|
||||
$health_score%
|
||||
<span class="score-label">Health Score</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Recent Completions</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
$history_entries
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Work In Progress</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
$wip_entries
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Roadmap Progress</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
$roadmap_data
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Generated by SecuBox Report Generator v1.0 | <a href="/cgi-bin/luci/admin/secubox/reporter">LuCI Dashboard</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
}
|
||||
|
||||
# Generate services report HTML inline
|
||||
generate_services_html_inline() {
|
||||
local output_file="$1"
|
||||
local hostname="$2"
|
||||
local timestamp="$3"
|
||||
local tor_count="$4"
|
||||
local dns_count="$5"
|
||||
local mesh_count="$6"
|
||||
local tor_services="$7"
|
||||
local dns_services="$8"
|
||||
local mesh_services="$9"
|
||||
|
||||
local total=$((tor_count + dns_count + mesh_count))
|
||||
|
||||
cat > "$output_file" << EOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Services Status - $hostname</title>
|
||||
<style>
|
||||
$(get_kisstheme_css)
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header-info">
|
||||
<h1>Distribution Status Report</h1>
|
||||
<div class="header-meta">
|
||||
<span class="meta-item">$hostname</span>
|
||||
<span class="meta-item">$timestamp</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-badge">
|
||||
$total
|
||||
<span class="score-label">Published Spaces</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-badge">
|
||||
<div class="stat-value">$tor_count</div>
|
||||
<div class="stat-label">Tor Services</div>
|
||||
</div>
|
||||
<div class="stat-badge">
|
||||
<div class="stat-value">$dns_count</div>
|
||||
<div class="stat-label">DNS/SSL Vhosts</div>
|
||||
</div>
|
||||
<div class="stat-badge">
|
||||
<div class="stat-value">$mesh_count</div>
|
||||
<div class="stat-label">Mesh Services</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Tor Hidden Services</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
$tor_services
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">DNS/SSL Services</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
$dns_services
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Mesh Services</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
$mesh_services
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Generated by SecuBox Report Generator v1.0 | <a href="/cgi-bin/luci/admin/secubox/reporter">LuCI Dashboard</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Reporter - Email Integration
|
||||
# Sends reports via msmtp or sendmail
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
# Send report via email
|
||||
send_report_email() {
|
||||
local report_type="$1"
|
||||
local html_content="$2"
|
||||
local recipient="$3"
|
||||
|
||||
local smtp_server=""
|
||||
local smtp_port=""
|
||||
local smtp_user=""
|
||||
local smtp_password=""
|
||||
local smtp_tls=""
|
||||
|
||||
config_load secubox-reporter
|
||||
config_get smtp_server email smtp_server ""
|
||||
config_get smtp_port email smtp_port "587"
|
||||
config_get smtp_user email smtp_user ""
|
||||
config_get smtp_password email smtp_password ""
|
||||
config_get smtp_tls email smtp_tls "1"
|
||||
|
||||
[ -z "$recipient" ] && {
|
||||
echo "ERROR: No email recipient specified" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
local hostname=$(uci -q get system.@system[0].hostname || hostname)
|
||||
local date_str=$(date '+%Y-%m-%d')
|
||||
|
||||
# Format report type for subject
|
||||
local report_name="Status"
|
||||
case "$report_type" in
|
||||
dev) report_name="Development Status" ;;
|
||||
services) report_name="Services Distribution" ;;
|
||||
all) report_name="Full Status" ;;
|
||||
esac
|
||||
|
||||
local subject="[SecuBox] $report_name Report - $hostname - $date_str"
|
||||
|
||||
# Build MIME multipart email
|
||||
local boundary="SecuBox_Report_$(date +%s)_$$"
|
||||
|
||||
local email_body="MIME-Version: 1.0
|
||||
From: SecuBox Reporter <secubox@$hostname>
|
||||
To: $recipient
|
||||
Subject: $subject
|
||||
Content-Type: multipart/alternative; boundary=\"$boundary\"
|
||||
|
||||
--$boundary
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
SecuBox $report_name Report
|
||||
===========================
|
||||
|
||||
Generated: $(date)
|
||||
Hostname: $hostname
|
||||
|
||||
This report contains HTML content. Please view in an HTML-capable email client.
|
||||
|
||||
--$boundary
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
$html_content
|
||||
|
||||
--$boundary--"
|
||||
|
||||
# Try msmtp first, then sendmail
|
||||
if command -v msmtp >/dev/null 2>&1 && [ -n "$smtp_server" ]; then
|
||||
# Create temporary msmtp config
|
||||
local msmtp_conf="/tmp/msmtp-report-$$.conf"
|
||||
cat > "$msmtp_conf" << EOF
|
||||
account default
|
||||
host $smtp_server
|
||||
port $smtp_port
|
||||
auth on
|
||||
user $smtp_user
|
||||
password $smtp_password
|
||||
tls $([ "$smtp_tls" = "1" ] && echo "on" || echo "off")
|
||||
tls_starttls on
|
||||
tls_certcheck off
|
||||
from secubox@$hostname
|
||||
EOF
|
||||
chmod 600 "$msmtp_conf"
|
||||
|
||||
echo "$email_body" | msmtp -C "$msmtp_conf" "$recipient" 2>/dev/null
|
||||
local result=$?
|
||||
|
||||
rm -f "$msmtp_conf"
|
||||
return $result
|
||||
|
||||
elif command -v sendmail >/dev/null 2>&1; then
|
||||
echo "$email_body" | sendmail -t 2>/dev/null
|
||||
return $?
|
||||
|
||||
else
|
||||
echo "ERROR: No mail transport available (msmtp or sendmail required)" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test email configuration
|
||||
test_email() {
|
||||
local recipient="$1"
|
||||
[ -z "$recipient" ] && {
|
||||
config_load secubox-reporter
|
||||
config_get recipient email recipient ""
|
||||
}
|
||||
|
||||
[ -z "$recipient" ] && {
|
||||
echo "ERROR: No recipient configured" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
local hostname=$(uci -q get system.@system[0].hostname || hostname)
|
||||
local test_body="<html><body><h1>SecuBox Email Test</h1><p>This is a test email from <strong>$hostname</strong>.</p><p>Generated: $(date)</p></body></html>"
|
||||
|
||||
if send_report_email "test" "$test_body" "$recipient"; then
|
||||
echo "Test email sent to $recipient"
|
||||
return 0
|
||||
else
|
||||
echo "Failed to send test email" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Development Status - {{HOSTNAME}}</title>
|
||||
<style>
|
||||
:root{--bg:#0a0a0f;--surface:#12121a;--card:#1a1a2e;--ink:#f0f2ff;--dim:rgba(240,242,255,.5);--muted:#666;--primary:#6366f1;--primary-end:#8b5cf6;--cyan:#06b6d4;--green:#22c55e;--red:#ef4444;--yellow:#f59e0b;--glass:rgba(255,255,255,.04);--border:rgba(255,255,255,.08)}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:var(--bg);color:var(--ink);font-family:"Inter","Segoe UI",system-ui,sans-serif;padding:2rem;line-height:1.6}
|
||||
.container{max-width:1200px;margin:0 auto}
|
||||
h1{font-size:2rem;margin-bottom:.5rem;background:linear-gradient(90deg,var(--primary),var(--primary-end),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;margin-bottom:2rem;padding:1.5rem;background:var(--card);border:1px solid var(--border);border-radius:12px}
|
||||
.header-meta{display:flex;gap:1rem;flex-wrap:wrap}
|
||||
.meta-item{font-size:.75rem;color:var(--muted);padding:.25rem .75rem;background:var(--glass);border-radius:4px}
|
||||
.score-badge{font-size:2rem;font-weight:700;padding:1rem 1.5rem;background:linear-gradient(135deg,var(--primary),var(--primary-end));border-radius:12px;text-align:center}
|
||||
.score-label{font-size:.6rem;text-transform:uppercase;letter-spacing:.1em;opacity:.7;display:block;margin-top:.25rem}
|
||||
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;margin-bottom:1.5rem}
|
||||
.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--border)}
|
||||
.card-title{font-size:1rem;font-weight:600;display:flex;align-items:center;gap:.5rem}
|
||||
.card-title::before{content:"";width:3px;height:1em;background:var(--primary);border-radius:2px}
|
||||
ul{list-style:none;padding-left:0}
|
||||
li{padding:.5rem 0;border-bottom:1px solid var(--border);font-size:.9rem}
|
||||
li:last-child{border-bottom:none}
|
||||
li strong{color:var(--cyan)}
|
||||
.history-entry{margin-bottom:1.5rem}
|
||||
.history-date{font-size:.8rem;color:var(--primary);font-weight:600;margin-bottom:.5rem;padding:.25rem .5rem;background:rgba(99,102,241,.1);border-radius:4px;display:inline-block}
|
||||
.wip-section{margin-bottom:1rem}
|
||||
.wip-section h4{color:var(--cyan);font-size:.85rem;margin-bottom:.5rem}
|
||||
.roadmap-version{display:flex;align-items:center;gap:1rem;padding:.75rem;background:var(--glass);border-radius:8px;margin-bottom:.5rem}
|
||||
.roadmap-version h4{margin:0;flex:1}
|
||||
.status-badge{font-size:.65rem;padding:.25rem .5rem;border-radius:4px;text-transform:uppercase;font-weight:600}
|
||||
.status-badge.complete{background:rgba(34,197,94,.15);color:var(--green)}
|
||||
.status-badge.inprogress{background:rgba(99,102,241,.15);color:var(--primary)}
|
||||
.status-badge.pending{background:rgba(102,102,102,.15);color:var(--muted)}
|
||||
.muted{color:var(--muted);font-style:italic}
|
||||
footer{margin-top:3rem;text-align:center;color:var(--muted);font-size:.75rem;padding-top:1.5rem;border-top:1px solid var(--border)}
|
||||
footer a{color:var(--primary)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header-info">
|
||||
<h1>Development Status Report</h1>
|
||||
<div class="header-meta">
|
||||
<span class="meta-item">{{HOSTNAME}}</span>
|
||||
<span class="meta-item">{{TIMESTAMP}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-badge">
|
||||
{{HEALTH_SCORE}}%
|
||||
<span class="score-label">Health Score</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Recent Completions</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{HISTORY_ENTRIES}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Work In Progress</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{WIP_ENTRIES}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Roadmap Progress</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ROADMAP_DATA}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Generated by SecuBox Report Generator v1.0
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{{SUBJECT}}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:#0a0a0f;font-family:'Segoe UI',system-ui,sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a0a0f;padding:20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background:#1a1a2e;border-radius:12px;overflow:hidden;">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background:linear-gradient(135deg,#6366f1,#8b5cf6);padding:30px;text-align:center;">
|
||||
<h1 style="margin:0;color:#fff;font-size:24px;">SecuBox Report</h1>
|
||||
<p style="margin:10px 0 0;color:rgba(255,255,255,0.8);font-size:14px;">{{HOSTNAME}} | {{TIMESTAMP}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding:30px;color:#f0f2ff;">
|
||||
{{CONTENT}}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="padding:20px;text-align:center;border-top:1px solid rgba(255,255,255,0.08);color:#666;font-size:12px;">
|
||||
Generated by SecuBox Report Generator<br>
|
||||
<a href="{{DASHBOARD_URL}}" style="color:#6366f1;text-decoration:none;">View Dashboard</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Services Status - {{HOSTNAME}}</title>
|
||||
<style>
|
||||
:root{--bg:#0a0a0f;--surface:#12121a;--card:#1a1a2e;--ink:#f0f2ff;--dim:rgba(240,242,255,.5);--muted:#666;--primary:#6366f1;--primary-end:#8b5cf6;--cyan:#06b6d4;--green:#22c55e;--red:#ef4444;--yellow:#f59e0b;--glass:rgba(255,255,255,.04);--border:rgba(255,255,255,.08)}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:var(--bg);color:var(--ink);font-family:"Inter","Segoe UI",system-ui,sans-serif;padding:2rem;line-height:1.6}
|
||||
.container{max-width:1200px;margin:0 auto}
|
||||
h1{font-size:2rem;margin-bottom:.5rem;background:linear-gradient(90deg,var(--primary),var(--primary-end),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;margin-bottom:2rem;padding:1.5rem;background:var(--card);border:1px solid var(--border);border-radius:12px}
|
||||
.header-meta{display:flex;gap:1rem;flex-wrap:wrap}
|
||||
.meta-item{font-size:.75rem;color:var(--muted);padding:.25rem .75rem;background:var(--glass);border-radius:4px}
|
||||
.score-badge{font-size:2rem;font-weight:700;padding:1rem 1.5rem;background:linear-gradient(135deg,var(--primary),var(--primary-end));border-radius:12px;text-align:center}
|
||||
.score-label{font-size:.6rem;text-transform:uppercase;letter-spacing:.1em;opacity:.7;display:block;margin-top:.25rem}
|
||||
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1rem;margin-bottom:2rem}
|
||||
.stat-badge{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1rem;text-align:center}
|
||||
.stat-value{font-size:1.5rem;font-weight:700;background:linear-gradient(90deg,var(--primary),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.stat-label{font-size:.7rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-top:.25rem}
|
||||
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;margin-bottom:1.5rem}
|
||||
.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--border)}
|
||||
.card-title{font-size:1rem;font-weight:600;display:flex;align-items:center;gap:.5rem}
|
||||
.card-title::before{content:"";width:3px;height:1em;background:var(--primary);border-radius:2px}
|
||||
.services-table{width:100%;border-collapse:collapse;font-size:.85rem}
|
||||
.services-table th,.services-table td{padding:.75rem;text-align:left;border-bottom:1px solid var(--border)}
|
||||
.services-table th{color:var(--dim);font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;font-weight:600}
|
||||
.services-table a{color:var(--cyan);text-decoration:none}
|
||||
.services-table a:hover{text-decoration:underline}
|
||||
.services-table code{font-size:.75rem;padding:.15rem .35rem;background:var(--glass);border-radius:3px;font-family:"JetBrains Mono",monospace}
|
||||
.status-badge{font-size:.65rem;padding:.25rem .5rem;border-radius:4px;text-transform:uppercase;font-weight:600}
|
||||
.status-badge.running{background:rgba(34,197,94,.15);color:var(--green)}
|
||||
.status-badge.stopped{background:rgba(239,68,68,.15);color:var(--red)}
|
||||
.muted{color:var(--muted);font-style:italic}
|
||||
footer{margin-top:3rem;text-align:center;color:var(--muted);font-size:.75rem;padding-top:1.5rem;border-top:1px solid var(--border)}
|
||||
footer a{color:var(--primary)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header-info">
|
||||
<h1>Distribution Status Report</h1>
|
||||
<div class="header-meta">
|
||||
<span class="meta-item">{{HOSTNAME}}</span>
|
||||
<span class="meta-item">{{TIMESTAMP}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-badge">
|
||||
{{HEALTH_UP}}/{{HEALTH_TOTAL}}
|
||||
<span class="score-label">Services Up</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-badge">
|
||||
<div class="stat-value">{{TOR_COUNT}}</div>
|
||||
<div class="stat-label">Tor Services</div>
|
||||
</div>
|
||||
<div class="stat-badge">
|
||||
<div class="stat-value">{{DNS_COUNT}}</div>
|
||||
<div class="stat-label">DNS/SSL Vhosts</div>
|
||||
</div>
|
||||
<div class="stat-badge">
|
||||
<div class="stat-value">{{MESH_COUNT}}</div>
|
||||
<div class="stat-label">Mesh Services</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Tor Hidden Services</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{TOR_SERVICES}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">DNS/SSL Services</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{DNS_SERVICES}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Mesh Services</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{MESH_SERVICES}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Generated by SecuBox Report Generator v1.0
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user