diff --git a/package/secubox/luci-app-streamlit-forge/htdocs/luci-static/resources/view/streamlit-forge/overview.js b/package/secubox/luci-app-streamlit-forge/htdocs/luci-static/resources/view/streamlit-forge/overview.js index 694b7ad8..5fee1a4c 100644 --- a/package/secubox/luci-app-streamlit-forge/htdocs/luci-static/resources/view/streamlit-forge/overview.js +++ b/package/secubox/luci-app-streamlit-forge/htdocs/luci-static/resources/view/streamlit-forge/overview.js @@ -65,16 +65,52 @@ var callPublish = rpc.declare({ expect: {} }); +var callGiteaStatus = rpc.declare({ + object: 'luci.streamlit-forge', + method: 'gitea_status', + expect: {} +}); + +var callEdit = rpc.declare({ + object: 'luci.streamlit-forge', + method: 'edit', + params: ['name'], + expect: {} +}); + +var callPull = rpc.declare({ + object: 'luci.streamlit-forge', + method: 'pull', + params: ['name'], + expect: {} +}); + +var callPush = rpc.declare({ + object: 'luci.streamlit-forge', + method: 'push', + params: ['name', 'message'], + expect: {} +}); + +var callPreview = rpc.declare({ + object: 'luci.streamlit-forge', + method: 'preview', + params: ['name'], + expect: {} +}); + return view.extend({ apps: [], templates: [], status: {}, + giteaStatus: {}, load: function() { return Promise.all([ callList(), callStatus(), - callTemplates() + callTemplates(), + callGiteaStatus().catch(function() { return {}; }) ]); }, @@ -83,6 +119,7 @@ return view.extend({ this.apps = (data[0] && data[0].apps) || []; this.status = data[1] || {}; this.templates = (data[2] && data[2].templates) || []; + this.giteaStatus = data[3] || {}; var view = E('div', { 'class': 'cbi-map' }, [ E('h2', {}, 'Streamlit Forge'), @@ -94,7 +131,10 @@ return view.extend({ this.renderStatCard('Running', this.status.running || 0, '#4caf50'), this.renderStatCard('Total Apps', this.status.total || 0, '#2196f3'), this.renderStatCard('LXC Container', this.status.lxc_status || 'unknown', - this.status.lxc_status === 'running' ? '#4caf50' : '#ff9800') + this.status.lxc_status === 'running' ? '#4caf50' : '#ff9800'), + this.renderStatCard('Gitea', + this.giteaStatus.gitea_available === 'true' ? 'v' + (this.giteaStatus.gitea_version || '?') : 'offline', + this.giteaStatus.gitea_available === 'true' ? '#9c27b0' : '#666') ]), // Actions @@ -165,17 +205,22 @@ return view.extend({ 'href': 'http://' + window.location.hostname + ':' + app.port, 'target': '_blank' }, 'Open') : '', + E('button', { + 'class': 'cbi-button', + 'style': 'padding:4px 8px;font-size:0.8rem;background:#9c27b0;color:#fff;border-color:#9c27b0;', + 'click': ui.createHandlerFn(this, 'handleEdit', app.name) + }, 'Edit'), + E('button', { + 'class': 'cbi-button', + 'style': 'padding:4px 8px;font-size:0.8rem;', + 'click': ui.createHandlerFn(this, 'handlePull', app.name) + }, 'Pull'), !app.domain ? E('button', { 'class': 'cbi-button', 'style': 'padding:4px 8px;font-size:0.8rem;', 'click': ui.createHandlerFn(this, 'handleExpose', app.name) }, 'Expose') : '', - E('button', { - 'class': 'cbi-button', - 'style': 'padding:4px 8px;font-size:0.8rem;', - 'click': ui.createHandlerFn(this, 'handlePublish', app.name) - }, 'Publish'), E('button', { 'class': 'cbi-button cbi-button-remove', 'style': 'padding:4px 8px;font-size:0.8rem;', @@ -379,5 +424,81 @@ return view.extend({ } return self.pollStatus(); }); + }, + + handleEdit: function(name) { + var self = this; + ui.showModal('Opening Editor...', [ + E('p', { 'class': 'spinning' }, 'Setting up Gitea repository...') + ]); + + return callEdit(name).then(function(res) { + ui.hideModal(); + if (res.code === 0 && res.edit_url) { + ui.showModal('Edit in Gitea', [ + E('p', {}, 'Your app is ready for editing in Gitea:'), + E('p', {}, [ + E('a', { + 'href': res.edit_url, + 'target': '_blank', + 'style': 'color:#9c27b0;font-weight:bold;' + }, res.edit_url) + ]), + E('p', { 'style': 'margin-top:1rem;color:#888;' }, + 'After editing, click "Pull" to sync changes to this device.'), + E('div', { 'class': 'right', 'style': 'margin-top:1rem;' }, [ + E('button', { + 'class': 'cbi-button', + 'click': ui.hideModal + }, 'Close'), + E('a', { + 'class': 'cbi-button cbi-button-positive', + 'href': res.edit_url, + 'target': '_blank', + 'style': 'margin-left:0.5rem;text-decoration:none;' + }, 'Open Editor') + ]) + ]); + } else { + ui.addNotification(null, E('p', 'Error: ' + (res.output || 'Failed to open editor'))); + } + }); + }, + + handlePull: function(name) { + var self = this; + ui.showModal('Pulling Changes...', [ + E('p', { 'class': 'spinning' }, 'Pulling latest from Gitea...') + ]); + + return callPull(name).then(function(res) { + ui.hideModal(); + if (res.code === 0) { + ui.addNotification(null, E('p', 'Changes pulled successfully')); + } else { + ui.addNotification(null, E('p', 'Error: ' + (res.output || 'Failed to pull'))); + } + return self.pollStatus(); + }); + }, + + handlePush: function(name) { + var self = this; + var message = prompt('Commit message:', 'Update from LuCI'); + if (message === null) return; + + ui.showModal('Pushing Changes...', [ + E('p', { 'class': 'spinning' }, 'Pushing to Gitea...') + ]); + + return callPush(name, message).then(function(res) { + ui.hideModal(); + if (res.code === 0) { + ui.addNotification(null, E('p', 'Changes pushed to Gitea')); + } else { + ui.addNotification(null, E('p', 'Error: ' + (res.output || 'Failed to push'))); + } + return self.pollStatus(); + }); } }); diff --git a/package/secubox/luci-app-streamlit-forge/root/usr/libexec/rpcd/luci.streamlit-forge b/package/secubox/luci-app-streamlit-forge/root/usr/libexec/rpcd/luci.streamlit-forge index 6e015778..f70c487c 100644 --- a/package/secubox/luci-app-streamlit-forge/root/usr/libexec/rpcd/luci.streamlit-forge +++ b/package/secubox/luci-app-streamlit-forge/root/usr/libexec/rpcd/luci.streamlit-forge @@ -324,42 +324,156 @@ get_config() { json_init config_load streamlit-forge - local enabled port domain entrypoint memory + local enabled port domain entrypoint memory gitea_repo last_pull last_push config_get enabled "$name" enabled '0' config_get port "$name" port '' config_get domain "$name" domain '' config_get entrypoint "$name" entrypoint 'app.py' config_get memory "$name" memory '512M' + config_get gitea_repo "$name" gitea_repo '' + config_get last_pull "$name" last_pull '' + config_get last_push "$name" last_push '' json_add_string "enabled" "$enabled" json_add_string "port" "$port" json_add_string "domain" "$domain" json_add_string "entrypoint" "$entrypoint" json_add_string "memory" "$memory" + json_add_string "gitea_repo" "$gitea_repo" + json_add_string "last_pull" "$last_pull" + json_add_string "last_push" "$last_push" + json_dump +} + +# ============================================ +# Phase 2: Gitea Integration +# ============================================ + +# Get edit URL for Gitea +do_edit() { + local name="$1" + [ -z "$name" ] && { echo '{"error":"App name required"}'; return; } + + local output + output=$(slforge edit "$name" 2>&1) + local rc=$? + + # Extract URL from output + local edit_url=$(echo "$output" | grep -oE 'http[s]?://[^ ]+') + + json_init + json_add_int "code" "$rc" + json_add_string "output" "$output" + json_add_string "edit_url" "$edit_url" + json_dump +} + +# Pull from Gitea +do_pull() { + local name="$1" + [ -z "$name" ] && { echo '{"error":"App name required"}'; return; } + + local output + output=$(slforge pull "$name" 2>&1) + local rc=$? + + json_init + json_add_int "code" "$rc" + json_add_string "output" "$output" + json_dump +} + +# Push to Gitea +do_push() { + local name="$1" + local message="$2" + [ -z "$name" ] && { echo '{"error":"App name required"}'; return; } + + local output + if [ -n "$message" ]; then + output=$(slforge push "$name" -m "$message" 2>&1) + else + output=$(slforge push "$name" 2>&1) + fi + local rc=$? + + json_init + json_add_int "code" "$rc" + json_add_string "output" "$output" + json_dump +} + +# Generate preview +do_preview() { + local name="$1" + [ -z "$name" ] && { echo '{"error":"App name required"}'; return; } + + local output + output=$(slforge preview "$name" 2>&1) + local rc=$? + + # Get preview path + config_load streamlit-forge + local preview_path + config_get preview_path "$name" preview '' + + json_init + json_add_int "code" "$rc" + json_add_string "output" "$output" + json_add_string "preview_path" "$preview_path" + json_dump +} + +# Get Gitea status +get_gitea_status() { + json_init + + # Load Gitea config + config_load gitea + local gitea_url gitea_token + config_get gitea_url main url 'http://127.0.0.1:3001' + config_get gitea_token main api_token '' + + local gitea_available="false" + if [ -n "$gitea_token" ]; then + local version=$(curl -s -m 3 -H "Authorization: token $gitea_token" "$gitea_url/api/v1/version" 2>/dev/null | jsonfilter -e '@.version' 2>/dev/null) + if [ -n "$version" ]; then + gitea_available="true" + json_add_string "gitea_version" "$version" + fi + fi + + json_add_string "gitea_available" "$gitea_available" + json_add_string "gitea_url" "$gitea_url" json_dump } case "$1" in list) - echo '{"list":{},"status":{},"info":{"name":"String"},"templates":{},"config":{"name":"String"},"create":{"name":"String","template":"String"},"delete":{"name":"String"},"start":{"name":"String"},"stop":{"name":"String"},"restart":{"name":"String"},"expose":{"name":"String","domain":"String"},"hide":{"name":"String"},"publish":{"name":"String"},"unpublish":{"name":"String"},"set_config":{"name":"String","key":"String","value":"String"}}' + echo '{"list":{},"status":{},"gitea_status":{},"info":{"name":"String"},"templates":{},"config":{"name":"String"},"create":{"name":"String","template":"String"},"delete":{"name":"String"},"start":{"name":"String"},"stop":{"name":"String"},"restart":{"name":"String"},"expose":{"name":"String","domain":"String"},"hide":{"name":"String"},"publish":{"name":"String"},"unpublish":{"name":"String"},"set_config":{"name":"String","key":"String","value":"String"},"edit":{"name":"String"},"pull":{"name":"String"},"push":{"name":"String","message":"String"},"preview":{"name":"String"}}' ;; call) case "$2" in - list) get_apps ;; - status) get_status ;; - info) read input; json_load "$input"; json_get_var name name; get_info "$name" ;; - templates) get_templates ;; - config) read input; json_load "$input"; json_get_var name name; get_config "$name" ;; - create) read input; json_load "$input"; json_get_var name name; json_get_var template template; do_create "$name" "$template" ;; - delete) read input; json_load "$input"; json_get_var name name; do_delete "$name" ;; - start) read input; json_load "$input"; json_get_var name name; do_start "$name" ;; - stop) read input; json_load "$input"; json_get_var name name; do_stop "$name" ;; - restart) read input; json_load "$input"; json_get_var name name; do_restart "$name" ;; - expose) read input; json_load "$input"; json_get_var name name; json_get_var domain domain; do_expose "$name" "$domain" ;; - hide) read input; json_load "$input"; json_get_var name name; do_hide "$name" ;; - publish) read input; json_load "$input"; json_get_var name name; do_publish "$name" ;; - unpublish) read input; json_load "$input"; json_get_var name name; do_unpublish "$name" ;; - set_config) read input; json_load "$input"; json_get_var name name; json_get_var key key; json_get_var value value; do_set_config "$name" "$key" "$value" ;; + list) get_apps ;; + status) get_status ;; + gitea_status) get_gitea_status ;; + info) read input; json_load "$input"; json_get_var name name; get_info "$name" ;; + templates) get_templates ;; + config) read input; json_load "$input"; json_get_var name name; get_config "$name" ;; + create) read input; json_load "$input"; json_get_var name name; json_get_var template template; do_create "$name" "$template" ;; + delete) read input; json_load "$input"; json_get_var name name; do_delete "$name" ;; + start) read input; json_load "$input"; json_get_var name name; do_start "$name" ;; + stop) read input; json_load "$input"; json_get_var name name; do_stop "$name" ;; + restart) read input; json_load "$input"; json_get_var name name; do_restart "$name" ;; + expose) read input; json_load "$input"; json_get_var name name; json_get_var domain domain; do_expose "$name" "$domain" ;; + hide) read input; json_load "$input"; json_get_var name name; do_hide "$name" ;; + publish) read input; json_load "$input"; json_get_var name name; do_publish "$name" ;; + unpublish) read input; json_load "$input"; json_get_var name name; do_unpublish "$name" ;; + set_config) read input; json_load "$input"; json_get_var name name; json_get_var key key; json_get_var value value; do_set_config "$name" "$key" "$value" ;; + edit) read input; json_load "$input"; json_get_var name name; do_edit "$name" ;; + pull) read input; json_load "$input"; json_get_var name name; do_pull "$name" ;; + push) read input; json_load "$input"; json_get_var name name; json_get_var message message; do_push "$name" "$message" ;; + preview) read input; json_load "$input"; json_get_var name name; do_preview "$name" ;; esac ;; esac diff --git a/package/secubox/luci-app-streamlit-forge/root/usr/share/rpcd/acl.d/luci-app-streamlit-forge.json b/package/secubox/luci-app-streamlit-forge/root/usr/share/rpcd/acl.d/luci-app-streamlit-forge.json index b65d29b1..89b3529f 100644 --- a/package/secubox/luci-app-streamlit-forge/root/usr/share/rpcd/acl.d/luci-app-streamlit-forge.json +++ b/package/secubox/luci-app-streamlit-forge/root/usr/share/rpcd/acl.d/luci-app-streamlit-forge.json @@ -3,13 +3,13 @@ "description": "Grant access to Streamlit Forge", "read": { "ubus": { - "luci.streamlit-forge": ["list", "status", "info", "templates", "config"] + "luci.streamlit-forge": ["list", "status", "gitea_status", "info", "templates", "config"] }, - "uci": ["streamlit-forge"] + "uci": ["streamlit-forge", "gitea"] }, "write": { "ubus": { - "luci.streamlit-forge": ["create", "delete", "start", "stop", "restart", "expose", "hide", "publish", "unpublish", "set_config"] + "luci.streamlit-forge": ["create", "delete", "start", "stop", "restart", "expose", "hide", "publish", "unpublish", "set_config", "edit", "pull", "push", "preview"] }, "uci": ["streamlit-forge"] } diff --git a/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge b/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge index 66bfd0e8..3da396fa 100644 --- a/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge +++ b/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge @@ -6,7 +6,7 @@ # Load config config_load streamlit-forge -config_get GITEA_URL main gitea_url 'http://127.0.0.1:3000' +config_get GITEA_URL main gitea_url 'http://127.0.0.1:3001' config_get GITEA_ORG main gitea_org 'streamlit-apps' config_get APPS_DIR main apps_dir '/srv/streamlit/apps' config_get PREVIEWS_DIR main previews_dir '/srv/streamlit/previews' @@ -15,6 +15,11 @@ config_get BASE_DOMAIN main base_domain 'apps.secubox.in' config_get DEFAULT_PORT main default_port_start '8501' config_get DEFAULT_MEMORY main default_memory '512M' +# Load Gitea token from gitea config if available +config_load gitea +config_get GITEA_TOKEN main api_token '' +config_get GITEA_URL main url "$GITEA_URL" + # Colors RED='\033[0;31m' GREEN='\033[0;32m' @@ -115,6 +120,290 @@ set_app_config() { uci commit streamlit-forge } +# ============================================ +# Gitea Integration Functions (Phase 2) +# ============================================ + +# Call Gitea API +gitea_api() { + local method="$1" + local endpoint="$2" + local data="$3" + + if [ -z "$GITEA_TOKEN" ]; then + log_err "Gitea API token not configured" + return 1 + fi + + local url="${GITEA_URL}/api/v1${endpoint}" + + if [ "$method" = "GET" ]; then + curl -s -H "Authorization: token $GITEA_TOKEN" "$url" + elif [ "$method" = "POST" ]; then + curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$data" "$url" + elif [ "$method" = "DELETE" ]; then + curl -s -X DELETE -H "Authorization: token $GITEA_TOKEN" "$url" + fi +} + +# Check if Gitea org exists, create if not +gitea_ensure_org() { + local org="$GITEA_ORG" + + # Check if org exists + local result=$(gitea_api GET "/orgs/$org" 2>/dev/null) + if echo "$result" | grep -q '"id"'; then + return 0 + fi + + # Create org + log_info "Creating Gitea organization: $org" + gitea_api POST "/orgs" "{\"username\":\"$org\",\"description\":\"Streamlit Forge Apps\"}" >/dev/null +} + +# Create Gitea repo for app +gitea_create_repo() { + local name="$1" + local app_dir="$APPS_DIR/$name" + + gitea_ensure_org + + # Check if repo exists + local result=$(gitea_api GET "/repos/$GITEA_ORG/$name" 2>/dev/null) + if echo "$result" | grep -q '"id"'; then + log_info "Repository already exists: $GITEA_ORG/$name" + return 0 + fi + + # Create repo + log_info "Creating repository: $GITEA_ORG/$name" + gitea_api POST "/orgs/$GITEA_ORG/repos" \ + "{\"name\":\"$name\",\"description\":\"Streamlit app: $name\",\"private\":false,\"auto_init\":false}" >/dev/null + + # Initialize git in app directory + cd "$app_dir/src" + if [ ! -d ".git" ]; then + git init -q + git add -A + git commit -q -m "Initial commit from Streamlit Forge" + fi + + # Add remote + local remote_url="${GITEA_URL}/${GITEA_ORG}/${name}.git" + git remote remove origin 2>/dev/null + git remote add origin "$remote_url" + + # Push + git push -u origin main 2>/dev/null || git push -u origin master 2>/dev/null + + # Save repo info to UCI + set_app_config "$name" gitea_repo "$name" + set_app_config "$name" gitea_branch "$(git rev-parse --abbrev-ref HEAD)" + + log_ok "Created and pushed to: $remote_url" +} + +# Open Gitea editor +cmd_edit() { + local name="$1" + [ -z "$name" ] && { log_err "App name required"; return 1; } + + if ! app_exists "$name"; then + log_err "App not found: $name" + return 1 + fi + + local gitea_repo=$(get_app_config "$name" gitea_repo) + + # Create repo if doesn't exist + if [ -z "$gitea_repo" ]; then + log_info "Setting up Gitea repository..." + gitea_create_repo "$name" + gitea_repo="$name" + fi + + local branch=$(get_app_config "$name" gitea_branch) + [ -z "$branch" ] && branch="main" + + local edit_url="${GITEA_URL}/${GITEA_ORG}/${gitea_repo}/_edit/${branch}/app.py" + + echo "" + log_ok "Edit your app at:" + echo " $edit_url" + echo "" + echo "After editing, run: slforge pull $name" + echo "" +} + +# Pull from Gitea +cmd_pull() { + local name="$1" + [ -z "$name" ] && { log_err "App name required"; return 1; } + + if ! app_exists "$name"; then + log_err "App not found: $name" + return 1 + fi + + local app_dir="$APPS_DIR/$name" + local gitea_repo=$(get_app_config "$name" gitea_repo) + + if [ -z "$gitea_repo" ]; then + log_err "No Gitea repository configured for $name" + log_info "Run: slforge edit $name to set up the repository" + return 1 + fi + + cd "$app_dir/src" + + if [ ! -d ".git" ]; then + log_err "Not a git repository. Setting up..." + git init -q + git remote add origin "${GITEA_URL}/${GITEA_ORG}/${gitea_repo}.git" + fi + + log_info "Pulling latest changes..." + git fetch origin 2>/dev/null + git pull origin "$(git rev-parse --abbrev-ref HEAD)" 2>/dev/null || { + # Try with reset for forced sync + git reset --hard origin/"$(git rev-parse --abbrev-ref HEAD)" 2>/dev/null + } + + # Update timestamp + set_app_config "$name" last_pull "$(date -Iseconds)" + + # Check if app is running and restart + local port=$(get_app_config "$name" port) + if [ -n "$port" ] && netstat -tln 2>/dev/null | grep -q ":$port "; then + log_info "App is running, restarting to apply changes..." + cmd_restart "$name" + fi + + log_ok "Pulled latest for $name" +} + +# Push to Gitea +cmd_push() { + local name="$1" + shift + + [ -z "$name" ] && { log_err "App name required"; return 1; } + + if ! app_exists "$name"; then + log_err "App not found: $name" + return 1 + fi + + local message="Update from Streamlit Forge" + while [ $# -gt 0 ]; do + case "$1" in + -m|--message) message="$2"; shift 2 ;; + *) shift ;; + esac + done + + local app_dir="$APPS_DIR/$name" + local gitea_repo=$(get_app_config "$name" gitea_repo) + + if [ -z "$gitea_repo" ]; then + log_info "Setting up Gitea repository..." + gitea_create_repo "$name" + return 0 + fi + + cd "$app_dir/src" + + if [ ! -d ".git" ]; then + log_err "Not a git repository" + return 1 + fi + + # Check for changes + if git diff --quiet && git diff --cached --quiet; then + log_info "No changes to push" + return 0 + fi + + log_info "Pushing changes..." + git add -A + git commit -m "$message" 2>/dev/null + git push origin "$(git rev-parse --abbrev-ref HEAD)" 2>/dev/null || { + log_err "Failed to push. Check remote access." + return 1 + } + + # Update timestamp + set_app_config "$name" last_push "$(date -Iseconds)" + + log_ok "Pushed changes for $name" +} + +# Generate preview screenshot +cmd_preview() { + local name="$1" + [ -z "$name" ] && { log_err "App name required"; return 1; } + + if ! app_exists "$name"; then + log_err "App not found: $name" + return 1 + fi + + local port=$(get_app_config "$name" port) + + # Check if running + if ! netstat -tln 2>/dev/null | grep -q ":$port "; then + log_warn "App not running. Starting temporarily..." + cmd_start "$name" --quiet + sleep 5 + fi + + mkdir -p "$PREVIEWS_DIR" + local preview_file="$PREVIEWS_DIR/${name}.png" + local preview_html="$PREVIEWS_DIR/${name}.html" + + # Method 1: Try wkhtmltoimage if available (best quality) + if command -v wkhtmltoimage >/dev/null 2>&1; then + log_info "Generating preview with wkhtmltoimage..." + wkhtmltoimage --width 1280 --height 800 --quality 85 \ + "http://127.0.0.1:$port" "$preview_file" 2>/dev/null + if [ -f "$preview_file" ]; then + log_ok "Preview saved: $preview_file" + set_app_config "$name" preview "$preview_file" + return 0 + fi + fi + + # Method 2: Capture HTML content for later rendering + log_info "Capturing page content..." + curl -s -m 10 "http://127.0.0.1:$port" > "$preview_html" 2>/dev/null + + if [ -s "$preview_html" ]; then + log_ok "HTML preview saved: $preview_html" + set_app_config "$name" preview "$preview_html" + + # Generate a simple placeholder PNG using SVG + cat > "${preview_file%.*}.svg" <<-SVGEOF + + + 📊 $name + Streamlit App + Port: $port + +SVGEOF + log_info "SVG placeholder: ${preview_file%.*}.svg" + return 0 + fi + + log_err "Failed to generate preview" + return 1 +} + +# ============================================ +# End Gitea Integration Functions +# ============================================ + # Create app from template cmd_create() { local name="$1" @@ -216,10 +505,9 @@ REQEOF uci set streamlit-forge."$name".created="$(date +%Y-%m-%d)" uci commit streamlit-forge - # Create Gitea repo if available - if curl -s "$GITEA_URL/api/v1/version" >/dev/null 2>&1; then - log_info "Creating Gitea repository..." - # TODO: Create repo via Gitea API + # Create Gitea repo if token available + if [ -n "$GITEA_TOKEN" ]; then + gitea_create_repo "$name" 2>/dev/null || log_warn "Gitea repo creation skipped" fi log_ok "Created app: $name" @@ -654,19 +942,23 @@ cmd_unpublish() { # Main command router case "$1" in - create) shift; cmd_create "$@" ;; - list) cmd_list ;; - info) shift; cmd_info "$@" ;; - delete) shift; cmd_delete "$@" ;; - start) shift; cmd_start "$@" ;; - stop) shift; cmd_stop "$@" ;; - restart) shift; cmd_restart "$@" ;; - status) shift; cmd_status "$@" ;; - logs) shift; cmd_logs "$@" ;; - config) shift; cmd_config "$@" ;; - expose) shift; cmd_expose "$@" ;; - hide) shift; cmd_hide "$@" ;; - publish) shift; cmd_publish "$@" ;; + create) shift; cmd_create "$@" ;; + list) cmd_list ;; + info) shift; cmd_info "$@" ;; + delete) shift; cmd_delete "$@" ;; + start) shift; cmd_start "$@" ;; + stop) shift; cmd_stop "$@" ;; + restart) shift; cmd_restart "$@" ;; + status) shift; cmd_status "$@" ;; + logs) shift; cmd_logs "$@" ;; + config) shift; cmd_config "$@" ;; + edit) shift; cmd_edit "$@" ;; + pull) shift; cmd_pull "$@" ;; + push) shift; cmd_push "$@" ;; + preview) shift; cmd_preview "$@" ;; + expose) shift; cmd_expose "$@" ;; + hide) shift; cmd_hide "$@" ;; + publish) shift; cmd_publish "$@" ;; unpublish) shift; cmd_unpublish "$@" ;; templates) cmd_templates ;; help|--help|-h|"")