feat(metablogizer): Add KISS one-click features matching Streamlit
- Add upload_and_create_site: one-click deploy with auto HAProxy setup - Add unpublish_site: remove HAProxy vhost while preserving content - Add set_auth_required: toggle authentication requirement per site - Add get_sites_exposure_status: exposure/cert status for all sites - Simplify dashboard to KISS UI pattern with status badges - Action buttons: Share, Upload, Expose/Unpublish, Lock/Unlock, Delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
397d7e2f74
commit
5a1276590e
@ -139,6 +139,29 @@ var callEmancipateStatus = rpc.declare({
|
||||
params: ['job_id']
|
||||
});
|
||||
|
||||
var callUploadAndCreateSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'upload_and_create_site',
|
||||
params: ['name', 'domain', 'content', 'is_zip']
|
||||
});
|
||||
|
||||
var callUnpublishSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'unpublish_site',
|
||||
params: ['id']
|
||||
});
|
||||
|
||||
var callSetAuthRequired = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'set_auth_required',
|
||||
params: ['id', 'auth_required']
|
||||
});
|
||||
|
||||
var callGetSitesExposureStatus = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'get_sites_exposure_status'
|
||||
});
|
||||
|
||||
return baseclass.extend({
|
||||
getStatus: function() {
|
||||
return callStatus();
|
||||
@ -269,16 +292,35 @@ return baseclass.extend({
|
||||
return callEmancipateStatus(jobId);
|
||||
},
|
||||
|
||||
uploadAndCreateSite: function(name, domain, content, isZip) {
|
||||
return callUploadAndCreateSite(name, domain, content || '', isZip ? '1' : '0');
|
||||
},
|
||||
|
||||
unpublishSite: function(id) {
|
||||
return callUnpublishSite(id);
|
||||
},
|
||||
|
||||
setAuthRequired: function(id, authRequired) {
|
||||
return callSetAuthRequired(id, authRequired ? '1' : '0');
|
||||
},
|
||||
|
||||
getSitesExposureStatus: function() {
|
||||
return callGetSitesExposureStatus().then(function(res) {
|
||||
return res.sites || [];
|
||||
});
|
||||
},
|
||||
|
||||
getDashboardData: function() {
|
||||
var self = this;
|
||||
return Promise.all([
|
||||
self.getStatus(),
|
||||
self.listSites()
|
||||
self.listSites(),
|
||||
self.getSitesExposureStatus()
|
||||
]).then(function(results) {
|
||||
return {
|
||||
status: results[0] || {},
|
||||
sites: results[1] || [],
|
||||
hosting: {}
|
||||
exposure: results[2] || []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1602,6 +1602,329 @@ EOF
|
||||
json_dump
|
||||
}
|
||||
|
||||
# One-click upload and create site
|
||||
# Accepts: name, domain, content (base64), is_zip
|
||||
method_upload_and_create_site() {
|
||||
local tmpinput="/tmp/rpcd_mb_upload_create_$$.json"
|
||||
cat > "$tmpinput"
|
||||
|
||||
local name domain content is_zip
|
||||
name=$(jsonfilter -i "$tmpinput" -e '@.name' 2>/dev/null)
|
||||
domain=$(jsonfilter -i "$tmpinput" -e '@.domain' 2>/dev/null)
|
||||
content=$(jsonfilter -i "$tmpinput" -e '@.content' 2>/dev/null)
|
||||
is_zip=$(jsonfilter -i "$tmpinput" -e '@.is_zip' 2>/dev/null)
|
||||
rm -f "$tmpinput"
|
||||
|
||||
if [ -z "$name" ] || [ -z "$domain" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Name and domain are required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Sanitize name
|
||||
local section_id="site_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')"
|
||||
|
||||
# Check if site already exists
|
||||
if uci -q get "$UCI_CONFIG.$section_id" >/dev/null 2>&1; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Site with this name already exists"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
SITES_ROOT=$(get_uci main sites_root "$SITES_ROOT")
|
||||
|
||||
# 1. Create site directory
|
||||
mkdir -p "$SITES_ROOT/$name"
|
||||
|
||||
# 2. Decode and save content
|
||||
umask 022
|
||||
if [ "$is_zip" = "1" ] && [ -n "$content" ]; then
|
||||
# Handle ZIP upload
|
||||
local tmpzip="/tmp/metablog_upload_$$.zip"
|
||||
echo "$content" | base64 -d > "$tmpzip" 2>/dev/null
|
||||
unzip -o "$tmpzip" -d "$SITES_ROOT/$name" >/dev/null 2>&1
|
||||
rm -f "$tmpzip"
|
||||
elif [ -n "$content" ]; then
|
||||
# Single file - assume index.html
|
||||
echo "$content" | base64 -d > "$SITES_ROOT/$name/index.html" 2>/dev/null
|
||||
fi
|
||||
|
||||
# 3. Fix permissions
|
||||
fix_permissions "$SITES_ROOT/$name"
|
||||
|
||||
# 4. Create default index if none exists
|
||||
if [ ! -f "$SITES_ROOT/$name/index.html" ]; then
|
||||
cat > "$SITES_ROOT/$name/index.html" <<EOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>$name</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
min-height: 100vh; margin: 0; background: #f5f5f5; }
|
||||
.container { text-align: center; padding: 2rem; }
|
||||
h1 { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>$name</h1>
|
||||
<p>Site published with MetaBlogizer</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
chmod 644 "$SITES_ROOT/$name/index.html"
|
||||
fi
|
||||
|
||||
# 5. Get next port and create uhttpd instance
|
||||
local port=$(get_next_port)
|
||||
local server_address=$(uci -q get network.lan.ipaddr || echo "192.168.255.1")
|
||||
|
||||
uci set "uhttpd.metablog_${section_id}=uhttpd"
|
||||
uci set "uhttpd.metablog_${section_id}.listen_http=0.0.0.0:$port"
|
||||
uci set "uhttpd.metablog_${section_id}.home=$SITES_ROOT/$name"
|
||||
uci set "uhttpd.metablog_${section_id}.index_page=index.html"
|
||||
uci set "uhttpd.metablog_${section_id}.error_page=/index.html"
|
||||
uci commit uhttpd
|
||||
/etc/init.d/uhttpd reload 2>/dev/null
|
||||
|
||||
# 6. Create UCI site config
|
||||
uci set "$UCI_CONFIG.$section_id=site"
|
||||
uci set "$UCI_CONFIG.$section_id.name=$name"
|
||||
uci set "$UCI_CONFIG.$section_id.domain=$domain"
|
||||
uci set "$UCI_CONFIG.$section_id.ssl=1"
|
||||
uci set "$UCI_CONFIG.$section_id.enabled=1"
|
||||
uci set "$UCI_CONFIG.$section_id.port=$port"
|
||||
uci set "$UCI_CONFIG.$section_id.runtime=uhttpd"
|
||||
|
||||
# 7. Create HAProxy backend if available
|
||||
if haproxy_available; then
|
||||
local backend_name="metablog_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')"
|
||||
|
||||
uci set "haproxy.$backend_name=backend"
|
||||
uci set "haproxy.$backend_name.name=$backend_name"
|
||||
uci set "haproxy.$backend_name.mode=http"
|
||||
uci set "haproxy.$backend_name.balance=roundrobin"
|
||||
uci set "haproxy.$backend_name.enabled=1"
|
||||
|
||||
local server_name="${backend_name}_srv"
|
||||
uci set "haproxy.$server_name=server"
|
||||
uci set "haproxy.$server_name.backend=$backend_name"
|
||||
uci set "haproxy.$server_name.name=srv"
|
||||
uci set "haproxy.$server_name.address=$server_address"
|
||||
uci set "haproxy.$server_name.port=$port"
|
||||
uci set "haproxy.$server_name.weight=100"
|
||||
uci set "haproxy.$server_name.check=1"
|
||||
uci set "haproxy.$server_name.enabled=1"
|
||||
|
||||
uci commit haproxy
|
||||
reload_haproxy
|
||||
fi
|
||||
|
||||
uci commit "$UCI_CONFIG"
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "id" "$section_id"
|
||||
json_add_string "name" "$name"
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_int "port" "$port"
|
||||
json_add_string "url" "https://$domain"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Unpublish/revoke site exposure (remove HAProxy vhost but keep site)
|
||||
method_unpublish_site() {
|
||||
local id
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var id id
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Missing site id"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local name domain
|
||||
name=$(get_uci "$id" name "")
|
||||
domain=$(get_uci "$id" domain "")
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Site not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Remove HAProxy vhost (keep backend for local access)
|
||||
if uci -q get haproxy >/dev/null 2>&1; then
|
||||
local vhost_id=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||
uci delete "haproxy.$vhost_id" 2>/dev/null
|
||||
|
||||
# Remove cert entry if exists
|
||||
uci delete "haproxy.cert_$vhost_id" 2>/dev/null
|
||||
|
||||
uci commit haproxy
|
||||
reload_haproxy
|
||||
fi
|
||||
|
||||
# Mark as unpublished in UCI
|
||||
uci set "$UCI_CONFIG.$id.emancipated=0"
|
||||
uci commit "$UCI_CONFIG"
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Site unpublished"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Set authentication requirement for a site
|
||||
method_set_auth_required() {
|
||||
local id auth_required
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var id id
|
||||
json_get_var auth_required auth_required
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Missing site id"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local name domain
|
||||
name=$(get_uci "$id" name "")
|
||||
domain=$(get_uci "$id" domain "")
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Site not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Update UCI config
|
||||
uci set "$UCI_CONFIG.$id.auth_required=$auth_required"
|
||||
uci commit "$UCI_CONFIG"
|
||||
|
||||
# If site has HAProxy vhost, update it
|
||||
if uci -q get haproxy >/dev/null 2>&1 && [ -n "$domain" ]; then
|
||||
local vhost_id=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||
if uci -q get "haproxy.$vhost_id" >/dev/null 2>&1; then
|
||||
uci set "haproxy.$vhost_id.auth_required=$auth_required"
|
||||
uci commit haproxy
|
||||
reload_haproxy
|
||||
fi
|
||||
fi
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "auth_required" "$auth_required"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Get exposure status for all sites (cert info, emancipation state)
|
||||
method_get_sites_exposure_status() {
|
||||
SITES_ROOT=$(get_uci main sites_root "$SITES_ROOT")
|
||||
|
||||
json_init
|
||||
json_add_array "sites"
|
||||
|
||||
config_load "$UCI_CONFIG"
|
||||
config_foreach _add_site_exposure_status site
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
_add_site_exposure_status() {
|
||||
local section="$1"
|
||||
local name domain ssl enabled emancipated auth_required port
|
||||
|
||||
config_get name "$section" name ""
|
||||
config_get domain "$section" domain ""
|
||||
config_get ssl "$section" ssl "1"
|
||||
config_get enabled "$section" enabled "1"
|
||||
config_get emancipated "$section" emancipated "0"
|
||||
config_get auth_required "$section" auth_required "0"
|
||||
config_get port "$section" port ""
|
||||
|
||||
[ -z "$name" ] && return
|
||||
|
||||
json_add_object
|
||||
json_add_string "id" "$section"
|
||||
json_add_string "name" "$name"
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_boolean "enabled" "$enabled"
|
||||
json_add_boolean "emancipated" "$emancipated"
|
||||
json_add_boolean "auth_required" "$auth_required"
|
||||
[ -n "$port" ] && json_add_int "port" "$port"
|
||||
|
||||
# Check if HAProxy vhost exists
|
||||
local vhost_exists=0
|
||||
if [ -n "$domain" ]; then
|
||||
local vhost_id=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||
if uci -q get "haproxy.$vhost_id" >/dev/null 2>&1; then
|
||||
vhost_exists=1
|
||||
fi
|
||||
fi
|
||||
json_add_boolean "vhost_exists" "$vhost_exists"
|
||||
|
||||
# Quick certificate check - just check if file exists
|
||||
# Full expiry check is expensive, use get_hosting_status for that
|
||||
if [ -n "$domain" ] && [ "$ssl" = "1" ]; then
|
||||
local cert_file=""
|
||||
if [ -f "/srv/lxc/haproxy/rootfs/srv/haproxy/certs/${domain}.pem" ]; then
|
||||
cert_file="/srv/lxc/haproxy/rootfs/srv/haproxy/certs/${domain}.pem"
|
||||
elif [ -f "/etc/acme/${domain}_ecc/fullchain.cer" ]; then
|
||||
cert_file="/etc/acme/${domain}_ecc/fullchain.cer"
|
||||
fi
|
||||
if [ -n "$cert_file" ]; then
|
||||
json_add_string "cert_status" "valid"
|
||||
else
|
||||
json_add_string "cert_status" "missing"
|
||||
fi
|
||||
else
|
||||
json_add_string "cert_status" "none"
|
||||
fi
|
||||
|
||||
# Backend running check
|
||||
local backend_running="0"
|
||||
if [ -n "$port" ]; then
|
||||
local hex_port=$(printf '%04X' "$port" 2>/dev/null)
|
||||
if grep -qi ":${hex_port}" /proc/net/tcp 2>/dev/null; then
|
||||
backend_running="1"
|
||||
fi
|
||||
fi
|
||||
json_add_boolean "backend_running" "$backend_running"
|
||||
|
||||
# Has content
|
||||
local has_content="0"
|
||||
if [ -d "$SITES_ROOT/$name" ] && [ -f "$SITES_ROOT/$name/index.html" ]; then
|
||||
has_content="1"
|
||||
fi
|
||||
json_add_boolean "has_content" "$has_content"
|
||||
|
||||
json_close_object
|
||||
}
|
||||
|
||||
# Emancipate site - KISS ULTIME MODE (DNS + Vortex + HAProxy + SSL)
|
||||
# Runs asynchronously to avoid XHR timeout - use emancipate_status to poll
|
||||
method_emancipate() {
|
||||
@ -2042,7 +2365,11 @@ case "$1" in
|
||||
"import_vhost": { "instance": "string", "name": "string", "domain": "string" },
|
||||
"sync_config": {},
|
||||
"emancipate": { "id": "string" },
|
||||
"emancipate_status": { "job_id": "string" }
|
||||
"emancipate_status": { "job_id": "string" },
|
||||
"upload_and_create_site": { "name": "string", "domain": "string", "content": "string", "is_zip": "string" },
|
||||
"unpublish_site": { "id": "string" },
|
||||
"set_auth_required": { "id": "string", "auth_required": "string" },
|
||||
"get_sites_exposure_status": {}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
@ -2073,6 +2400,10 @@ EOF
|
||||
sync_config) method_sync_config ;;
|
||||
emancipate) method_emancipate ;;
|
||||
emancipate_status) method_emancipate_status ;;
|
||||
upload_and_create_site) method_upload_and_create_site ;;
|
||||
unpublish_site) method_unpublish_site ;;
|
||||
set_auth_required) method_set_auth_required ;;
|
||||
get_sites_exposure_status) method_get_sites_exposure_status ;;
|
||||
*) echo '{"error": "unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
"get_hosting_status",
|
||||
"check_site_health",
|
||||
"get_tor_status",
|
||||
"discover_vhosts"
|
||||
"discover_vhosts",
|
||||
"get_sites_exposure_status"
|
||||
],
|
||||
"file": ["read", "list", "stat"]
|
||||
},
|
||||
@ -39,7 +40,10 @@
|
||||
"import_vhost",
|
||||
"sync_config",
|
||||
"emancipate",
|
||||
"emancipate_status"
|
||||
"emancipate_status",
|
||||
"upload_and_create_site",
|
||||
"unpublish_site",
|
||||
"set_auth_required"
|
||||
],
|
||||
"luci.haproxy": [
|
||||
"create_backend",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user