fix(metabolizer): Remove yaml dependency, make CMS container-friendly
- Remove yaml import, use simple string parsing for front matter - Remove dependency on host metabolizerctl - Use environment variables for paths (METABOLIZER_CONTENT) - Remove switch_page calls that fail in container - CMS now works standalone inside Streamlit container - Bump to r2 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5a2ef2d6ff
commit
35957e34ab
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=secubox-app-metabolizer
|
PKG_NAME:=secubox-app-metabolizer
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
|
|
||||||
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
||||||
|
|||||||
@ -111,31 +111,16 @@ st.info("""
|
|||||||
- **Settings** - Configure Git and Hexo integration
|
- **Settings** - Configure Git and Hexo integration
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Quick actions
|
# Quick actions - simplified without switch_page
|
||||||
st.subheader("Quick Actions")
|
st.subheader("Quick Actions")
|
||||||
|
|
||||||
col1, col2, col3 = st.columns(3)
|
st.markdown("""
|
||||||
|
Use the **sidebar** on the left to navigate to:
|
||||||
with col1:
|
- 📝 **1_editor** - Write new posts
|
||||||
if st.button("📝 New Post", use_container_width=True):
|
- 📚 **2_posts** - Manage posts
|
||||||
st.switch_page("pages/1_editor.py")
|
- 🖼️ **3_media** - Media library
|
||||||
|
- ⚙️ **4_settings** - Settings
|
||||||
with col2:
|
""")
|
||||||
if st.button("🔄 Sync & Build", use_container_width=True):
|
|
||||||
import subprocess
|
|
||||||
with st.spinner("Building..."):
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/sbin/metabolizerctl', 'build'],
|
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
st.success("Build complete!")
|
|
||||||
else:
|
|
||||||
st.error(f"Build failed: {result.stderr}")
|
|
||||||
|
|
||||||
with col3:
|
|
||||||
if st.button("🌐 View Blog", use_container_width=True):
|
|
||||||
st.markdown("[Open Blog](/blog/)", unsafe_allow_html=True)
|
|
||||||
|
|
||||||
# Footer
|
# Footer
|
||||||
st.divider()
|
st.divider()
|
||||||
|
|||||||
@ -5,13 +5,12 @@ import streamlit as st
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import yaml
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
st.set_page_config(page_title="Editor - Metabolizer", page_icon="✏️", layout="wide")
|
st.set_page_config(page_title="Editor - Metabolizer", page_icon="✏️", layout="wide")
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
CONTENT_PATH = Path("/srv/metabolizer/content")
|
CONTENT_PATH = Path(os.environ.get('METABOLIZER_CONTENT', '/srv/content'))
|
||||||
POSTS_PATH = CONTENT_PATH / "_posts"
|
POSTS_PATH = CONTENT_PATH / "_posts"
|
||||||
DRAFTS_PATH = CONTENT_PATH / "_drafts"
|
DRAFTS_PATH = CONTENT_PATH / "_drafts"
|
||||||
|
|
||||||
@ -112,16 +111,28 @@ def generate_filename(title, date):
|
|||||||
return f"{date}-{slug}.md"
|
return f"{date}-{slug}.md"
|
||||||
|
|
||||||
def generate_frontmatter(title, date, time, categories, tags, excerpt):
|
def generate_frontmatter(title, date, time, categories, tags, excerpt):
|
||||||
"""Generate YAML front matter"""
|
"""Generate YAML front matter without yaml module"""
|
||||||
fm = {
|
lines = ["---"]
|
||||||
'title': title,
|
lines.append(f"title: {title}")
|
||||||
'date': f"{date} {time.strftime('%H:%M:%S')}",
|
lines.append(f"date: {date} {time.strftime('%H:%M:%S')}")
|
||||||
'categories': categories,
|
|
||||||
'tags': [t.strip() for t in tags.split(",")] if tags else [],
|
if categories:
|
||||||
}
|
lines.append(f"categories: [{', '.join(categories)}]")
|
||||||
|
else:
|
||||||
|
lines.append("categories: []")
|
||||||
|
|
||||||
|
if tags:
|
||||||
|
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
|
||||||
|
lines.append(f"tags: [{', '.join(tag_list)}]")
|
||||||
|
else:
|
||||||
|
lines.append("tags: []")
|
||||||
|
|
||||||
if excerpt:
|
if excerpt:
|
||||||
fm['excerpt'] = excerpt
|
lines.append(f"excerpt: \"{excerpt}\"")
|
||||||
return "---\n" + yaml.dump(fm, default_flow_style=False) + "---\n\n"
|
|
||||||
|
lines.append("---")
|
||||||
|
lines.append("")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
def save_post(path, title, date, time, categories, tags, excerpt, content):
|
def save_post(path, title, date, time, categories, tags, excerpt, content):
|
||||||
"""Save post to file"""
|
"""Save post to file"""
|
||||||
@ -136,10 +147,12 @@ def save_post(path, title, date, time, categories, tags, excerpt, content):
|
|||||||
|
|
||||||
def git_commit_push(message):
|
def git_commit_push(message):
|
||||||
"""Commit and push to Gitea"""
|
"""Commit and push to Gitea"""
|
||||||
os.chdir(CONTENT_PATH)
|
try:
|
||||||
subprocess.run(['git', 'add', '-A'], capture_output=True)
|
subprocess.run(['git', 'add', '-A'], cwd=CONTENT_PATH, capture_output=True)
|
||||||
subprocess.run(['git', 'commit', '-m', message], capture_output=True)
|
subprocess.run(['git', 'commit', '-m', message], cwd=CONTENT_PATH, capture_output=True)
|
||||||
subprocess.run(['git', 'push', 'origin', 'main'], capture_output=True)
|
subprocess.run(['git', 'push', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
if st.button("💾 Save Draft", use_container_width=True):
|
if st.button("💾 Save Draft", use_container_width=True):
|
||||||
@ -159,21 +172,18 @@ with col2:
|
|||||||
git_commit_push(f"Add post: {title}")
|
git_commit_push(f"Add post: {title}")
|
||||||
|
|
||||||
st.success(f"Published: {filepath.name}")
|
st.success(f"Published: {filepath.name}")
|
||||||
st.info("Webhook will trigger rebuild automatically")
|
st.info("Post saved to repository")
|
||||||
else:
|
else:
|
||||||
st.error("Title and content required")
|
st.error("Title and content required")
|
||||||
|
|
||||||
with col3:
|
with col3:
|
||||||
if st.button("🔄 Build Now", use_container_width=True):
|
if st.button("🔄 Sync", use_container_width=True):
|
||||||
with st.spinner("Building..."):
|
with st.spinner("Syncing..."):
|
||||||
result = subprocess.run(
|
try:
|
||||||
['/usr/sbin/metabolizerctl', 'build'],
|
subprocess.run(['git', 'pull', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True)
|
||||||
capture_output=True, text=True
|
st.success("Synced!")
|
||||||
)
|
except:
|
||||||
if result.returncode == 0:
|
st.error("Sync failed")
|
||||||
st.success("Build complete!")
|
|
||||||
else:
|
|
||||||
st.error(f"Build failed")
|
|
||||||
|
|
||||||
with col4:
|
with col4:
|
||||||
if st.button("🗑️ Clear", use_container_width=True):
|
if st.button("🗑️ Clear", use_container_width=True):
|
||||||
|
|||||||
@ -5,27 +5,41 @@ import streamlit as st
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import subprocess
|
import subprocess
|
||||||
import yaml
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
st.set_page_config(page_title="Posts - Metabolizer", page_icon="📚", layout="wide")
|
st.set_page_config(page_title="Posts - Metabolizer", page_icon="📚", layout="wide")
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
CONTENT_PATH = Path("/srv/metabolizer/content")
|
CONTENT_PATH = Path(os.environ.get('METABOLIZER_CONTENT', '/srv/content'))
|
||||||
POSTS_PATH = CONTENT_PATH / "_posts"
|
POSTS_PATH = CONTENT_PATH / "_posts"
|
||||||
DRAFTS_PATH = CONTENT_PATH / "_drafts"
|
DRAFTS_PATH = CONTENT_PATH / "_drafts"
|
||||||
|
|
||||||
st.title("📚 Post Management")
|
st.title("📚 Post Management")
|
||||||
|
|
||||||
def parse_frontmatter(filepath):
|
def parse_frontmatter(filepath):
|
||||||
"""Parse YAML front matter from markdown file"""
|
"""Parse YAML front matter from markdown file (without yaml module)"""
|
||||||
try:
|
try:
|
||||||
content = filepath.read_text()
|
content = filepath.read_text()
|
||||||
if content.startswith("---"):
|
if content.startswith("---"):
|
||||||
parts = content.split("---", 2)
|
parts = content.split("---", 2)
|
||||||
if len(parts) >= 3:
|
if len(parts) >= 3:
|
||||||
fm = yaml.safe_load(parts[1])
|
fm_text = parts[1].strip()
|
||||||
body = parts[2].strip()
|
body = parts[2].strip()
|
||||||
|
|
||||||
|
# Simple parsing
|
||||||
|
fm = {}
|
||||||
|
for line in fm_text.split('\n'):
|
||||||
|
if ':' in line:
|
||||||
|
key, value = line.split(':', 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
# Handle arrays
|
||||||
|
if value.startswith('[') and value.endswith(']'):
|
||||||
|
value = [v.strip().strip('"\'') for v in value[1:-1].split(',') if v.strip()]
|
||||||
|
elif value.startswith('"') and value.endswith('"'):
|
||||||
|
value = value[1:-1]
|
||||||
|
fm[key] = value
|
||||||
return fm, body
|
return fm, body
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
@ -51,10 +65,12 @@ def get_posts(path):
|
|||||||
|
|
||||||
def git_commit_push(message):
|
def git_commit_push(message):
|
||||||
"""Commit and push to Gitea"""
|
"""Commit and push to Gitea"""
|
||||||
os.chdir(CONTENT_PATH)
|
try:
|
||||||
subprocess.run(['git', 'add', '-A'], capture_output=True)
|
subprocess.run(['git', 'add', '-A'], cwd=CONTENT_PATH, capture_output=True)
|
||||||
subprocess.run(['git', 'commit', '-m', message], capture_output=True)
|
subprocess.run(['git', 'commit', '-m', message], cwd=CONTENT_PATH, capture_output=True)
|
||||||
subprocess.run(['git', 'push', 'origin', 'main'], capture_output=True)
|
subprocess.run(['git', 'push', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Tabs for Published and Drafts
|
# Tabs for Published and Drafts
|
||||||
tab1, tab2 = st.tabs(["📰 Published", "📝 Drafts"])
|
tab1, tab2 = st.tabs(["📰 Published", "📝 Drafts"])
|
||||||
@ -74,14 +90,15 @@ with tab1:
|
|||||||
with col1:
|
with col1:
|
||||||
st.caption(f"📅 {post['date']}")
|
st.caption(f"📅 {post['date']}")
|
||||||
if post['categories']:
|
if post['categories']:
|
||||||
st.caption(f"📁 {', '.join(post['categories'])}")
|
cats = post['categories'] if isinstance(post['categories'], list) else [post['categories']]
|
||||||
|
st.caption(f"📁 {', '.join(cats)}")
|
||||||
if post['tags']:
|
if post['tags']:
|
||||||
st.caption(f"🏷️ {', '.join(post['tags'])}")
|
tags = post['tags'] if isinstance(post['tags'], list) else [post['tags']]
|
||||||
|
st.caption(f"🏷️ {', '.join(tags)}")
|
||||||
st.markdown(post['excerpt'])
|
st.markdown(post['excerpt'])
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
if st.button("✏️ Edit", key=f"edit_{post['filename']}"):
|
if st.button("✏️ Edit", key=f"edit_{post['filename']}"):
|
||||||
# Load into editor
|
|
||||||
st.session_state.post_title = post['title']
|
st.session_state.post_title = post['title']
|
||||||
st.session_state.post_content = post['body']
|
st.session_state.post_content = post['body']
|
||||||
st.switch_page("pages/1_editor.py")
|
st.switch_page("pages/1_editor.py")
|
||||||
@ -93,7 +110,7 @@ with tab1:
|
|||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
if st.button("📥 Unpublish", key=f"unpub_{post['filename']}"):
|
if st.button("📥 Unpublish", key=f"unpub_{post['filename']}"):
|
||||||
# Move to drafts
|
DRAFTS_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
new_path = DRAFTS_PATH / post['filename']
|
new_path = DRAFTS_PATH / post['filename']
|
||||||
post['path'].rename(new_path)
|
post['path'].rename(new_path)
|
||||||
git_commit_push(f"Unpublish: {post['title']}")
|
git_commit_push(f"Unpublish: {post['title']}")
|
||||||
@ -123,7 +140,7 @@ with tab2:
|
|||||||
st.switch_page("pages/1_editor.py")
|
st.switch_page("pages/1_editor.py")
|
||||||
|
|
||||||
if st.button("📤 Publish", key=f"pub_{draft['filename']}"):
|
if st.button("📤 Publish", key=f"pub_{draft['filename']}"):
|
||||||
# Move to posts
|
POSTS_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
new_path = POSTS_PATH / draft['filename']
|
new_path = POSTS_PATH / draft['filename']
|
||||||
draft['path'].rename(new_path)
|
draft['path'].rename(new_path)
|
||||||
git_commit_push(f"Publish: {draft['title']}")
|
git_commit_push(f"Publish: {draft['title']}")
|
||||||
@ -135,28 +152,22 @@ with tab2:
|
|||||||
st.success(f"Deleted")
|
st.success(f"Deleted")
|
||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
# Build action
|
# Sync action
|
||||||
st.divider()
|
st.divider()
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
if st.button("🔄 Sync from Git", use_container_width=True):
|
if st.button("🔄 Sync from Git", use_container_width=True):
|
||||||
with st.spinner("Syncing..."):
|
with st.spinner("Syncing..."):
|
||||||
result = subprocess.run(
|
try:
|
||||||
['/usr/sbin/metabolizerctl', 'sync'],
|
subprocess.run(['git', 'pull', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True)
|
||||||
capture_output=True, text=True
|
st.success("Synced!")
|
||||||
)
|
st.rerun()
|
||||||
st.success("Synced!")
|
except:
|
||||||
st.rerun()
|
st.error("Sync failed")
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
if st.button("🏗️ Rebuild Blog", use_container_width=True):
|
if st.button("📤 Push to Git", use_container_width=True):
|
||||||
with st.spinner("Building..."):
|
with st.spinner("Pushing..."):
|
||||||
result = subprocess.run(
|
git_commit_push("Update posts")
|
||||||
['/usr/sbin/metabolizerctl', 'build'],
|
st.success("Pushed!")
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
st.success("Build complete!")
|
|
||||||
else:
|
|
||||||
st.error("Build failed")
|
|
||||||
|
|||||||
@ -4,32 +4,28 @@ Metabolizer CMS - Settings
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
st.set_page_config(page_title="Settings - Metabolizer", page_icon="⚙️", layout="wide")
|
st.set_page_config(page_title="Settings - Metabolizer", page_icon="⚙️", layout="wide")
|
||||||
|
|
||||||
st.title("⚙️ Settings")
|
st.title("⚙️ Settings")
|
||||||
|
|
||||||
def get_status():
|
# Paths
|
||||||
"""Get metabolizer status"""
|
CONTENT_PATH = Path(os.environ.get('METABOLIZER_CONTENT', '/srv/content'))
|
||||||
|
GITEA_URL = os.environ.get('GITEA_URL', 'http://host.containers.internal:3000')
|
||||||
|
|
||||||
|
def git_command(args, cwd=None):
|
||||||
|
"""Run git command"""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['/usr/sbin/metabolizerctl', 'status'],
|
['git'] + args,
|
||||||
|
cwd=cwd or CONTENT_PATH,
|
||||||
capture_output=True, text=True
|
capture_output=True, text=True
|
||||||
)
|
)
|
||||||
return json.loads(result.stdout)
|
return result.returncode == 0, result.stdout, result.stderr
|
||||||
except:
|
except Exception as e:
|
||||||
return {}
|
return False, "", str(e)
|
||||||
|
|
||||||
def run_command(cmd):
|
|
||||||
"""Run metabolizerctl command"""
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/sbin/metabolizerctl'] + cmd,
|
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
return result.returncode == 0, result.stdout, result.stderr
|
|
||||||
|
|
||||||
# Get current status
|
|
||||||
status = get_status()
|
|
||||||
|
|
||||||
# Pipeline Status
|
# Pipeline Status
|
||||||
st.subheader("📊 Pipeline Status")
|
st.subheader("📊 Pipeline Status")
|
||||||
@ -37,43 +33,45 @@ st.subheader("📊 Pipeline Status")
|
|||||||
col1, col2, col3 = st.columns(3)
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
gitea_status = status.get('gitea', {}).get('status', 'unknown')
|
st.metric("Gitea", "EXTERNAL", delta="Host")
|
||||||
st.metric(
|
|
||||||
"Gitea",
|
|
||||||
gitea_status.upper(),
|
|
||||||
delta="OK" if gitea_status == "running" else "DOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
streamlit_status = status.get('streamlit', {}).get('status', 'unknown')
|
st.metric("Streamlit", "RUNNING", delta="OK")
|
||||||
st.metric(
|
|
||||||
"Streamlit",
|
|
||||||
streamlit_status.upper(),
|
|
||||||
delta="OK" if streamlit_status == "running" else "DOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
with col3:
|
with col3:
|
||||||
hexo_status = status.get('hexo', {}).get('status', 'unknown')
|
st.metric("HexoJS", "EXTERNAL", delta="Host")
|
||||||
st.metric(
|
|
||||||
"HexoJS",
|
|
||||||
hexo_status.upper(),
|
|
||||||
delta="OK" if hexo_status == "running" else "DOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
|
|
||||||
# Content Repository
|
# Content Repository
|
||||||
st.subheader("📁 Content Repository")
|
st.subheader("📁 Content Repository")
|
||||||
|
|
||||||
content = status.get('content', {})
|
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
st.text_input("Repository", value=content.get('repo', 'blog-content'), disabled=True)
|
st.text_input("Content Path", value=str(CONTENT_PATH), disabled=True)
|
||||||
st.text_input("Path", value=content.get('path', '/srv/metabolizer/content'), disabled=True)
|
|
||||||
|
# Check if git repo exists
|
||||||
|
if (CONTENT_PATH / '.git').exists():
|
||||||
|
success, stdout, _ = git_command(['remote', '-v'])
|
||||||
|
if success and stdout:
|
||||||
|
remote = stdout.split('\n')[0] if stdout else "No remote"
|
||||||
|
st.text_input("Remote", value=remote.split()[1] if '\t' in remote or ' ' in remote else remote, disabled=True)
|
||||||
|
|
||||||
|
success, stdout, _ = git_command(['rev-parse', '--abbrev-ref', 'HEAD'])
|
||||||
|
st.text_input("Branch", value=stdout.strip() if success else "unknown", disabled=True)
|
||||||
|
else:
|
||||||
|
st.warning("Content directory is not a git repository")
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
st.metric("Posts", content.get('post_count', 0))
|
# Count posts
|
||||||
|
posts_path = CONTENT_PATH / '_posts'
|
||||||
|
post_count = len(list(posts_path.glob('*.md'))) if posts_path.exists() else 0
|
||||||
|
st.metric("Posts", post_count)
|
||||||
|
|
||||||
|
drafts_path = CONTENT_PATH / '_drafts'
|
||||||
|
draft_count = len(list(drafts_path.glob('*.md'))) if drafts_path.exists() else 0
|
||||||
|
st.metric("Drafts", draft_count)
|
||||||
|
|
||||||
# Git Operations
|
# Git Operations
|
||||||
st.subheader("🔗 Git Operations")
|
st.subheader("🔗 Git Operations")
|
||||||
@ -83,7 +81,7 @@ col1, col2, col3 = st.columns(3)
|
|||||||
with col1:
|
with col1:
|
||||||
if st.button("🔄 Pull Latest", use_container_width=True):
|
if st.button("🔄 Pull Latest", use_container_width=True):
|
||||||
with st.spinner("Pulling..."):
|
with st.spinner("Pulling..."):
|
||||||
success, stdout, stderr = run_command(['sync'])
|
success, stdout, stderr = git_command(['pull', 'origin', 'master'])
|
||||||
if success:
|
if success:
|
||||||
st.success("Pulled latest changes")
|
st.success("Pulled latest changes")
|
||||||
else:
|
else:
|
||||||
@ -91,96 +89,49 @@ with col1:
|
|||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
if st.button("📊 Git Status", use_container_width=True):
|
if st.button("📊 Git Status", use_container_width=True):
|
||||||
import os
|
success, stdout, stderr = git_command(['status', '--short'])
|
||||||
os.chdir("/srv/metabolizer/content")
|
if stdout:
|
||||||
result = subprocess.run(['git', 'status', '--short'], capture_output=True, text=True)
|
st.code(stdout)
|
||||||
if result.stdout:
|
|
||||||
st.code(result.stdout)
|
|
||||||
else:
|
else:
|
||||||
st.info("Working tree clean")
|
st.info("Working tree clean")
|
||||||
|
|
||||||
with col3:
|
with col3:
|
||||||
github_url = st.text_input("Mirror from GitHub URL")
|
if st.button("📤 Push Changes", use_container_width=True):
|
||||||
if st.button("🔗 Mirror", use_container_width=True):
|
with st.spinner("Pushing..."):
|
||||||
if github_url:
|
success, stdout, stderr = git_command(['push', 'origin', 'master'])
|
||||||
with st.spinner("Mirroring..."):
|
|
||||||
success, stdout, stderr = run_command(['mirror', github_url])
|
|
||||||
if success:
|
|
||||||
st.success(f"Mirrored: {stdout}")
|
|
||||||
else:
|
|
||||||
st.error(f"Mirror failed: {stderr}")
|
|
||||||
else:
|
|
||||||
st.warning("Enter a GitHub URL")
|
|
||||||
|
|
||||||
st.divider()
|
|
||||||
|
|
||||||
# Portal Settings
|
|
||||||
st.subheader("🌐 Portal")
|
|
||||||
|
|
||||||
portal = status.get('portal', {})
|
|
||||||
col1, col2 = st.columns(2)
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
st.text_input("Blog URL", value=portal.get('url', 'http://router/blog/'), disabled=True)
|
|
||||||
st.text_input("Static Path", value=portal.get('path', '/www/blog'), disabled=True)
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
if portal.get('enabled'):
|
|
||||||
st.success("Portal Enabled")
|
|
||||||
if st.button("🌐 View Blog", use_container_width=True):
|
|
||||||
st.markdown(f"[Open Blog]({portal.get('url', '/blog/')})")
|
|
||||||
else:
|
|
||||||
st.warning("Portal Disabled")
|
|
||||||
|
|
||||||
st.divider()
|
|
||||||
|
|
||||||
# Build Actions
|
|
||||||
st.subheader("🏗️ Build Pipeline")
|
|
||||||
|
|
||||||
col1, col2, col3 = st.columns(3)
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
if st.button("🧹 Clean", use_container_width=True):
|
|
||||||
with st.spinner("Cleaning..."):
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/sbin/hexoctl', 'clean'],
|
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
st.success("Cleaned build cache")
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
if st.button("🔨 Generate", use_container_width=True):
|
|
||||||
with st.spinner("Generating..."):
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/sbin/hexoctl', 'generate'],
|
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
st.success("Generated static site")
|
|
||||||
else:
|
|
||||||
st.error("Generation failed")
|
|
||||||
|
|
||||||
with col3:
|
|
||||||
if st.button("📤 Publish", use_container_width=True):
|
|
||||||
with st.spinner("Publishing..."):
|
|
||||||
success, stdout, stderr = run_command(['publish'])
|
|
||||||
if success:
|
if success:
|
||||||
st.success("Published to portal")
|
st.success("Pushed changes")
|
||||||
else:
|
else:
|
||||||
st.error(f"Publish failed: {stderr}")
|
st.error(f"Push failed: {stderr}")
|
||||||
|
|
||||||
# Full pipeline
|
|
||||||
if st.button("🚀 Full Pipeline (Clean → Generate → Publish)", use_container_width=True, type="primary"):
|
|
||||||
with st.spinner("Running full pipeline..."):
|
|
||||||
success, stdout, stderr = run_command(['build'])
|
|
||||||
if success:
|
|
||||||
st.success("Full pipeline complete!")
|
|
||||||
st.balloons()
|
|
||||||
else:
|
|
||||||
st.error(f"Pipeline failed: {stderr}")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
|
|
||||||
# Raw Status JSON
|
# Initialize Repository
|
||||||
with st.expander("🔧 Debug: Raw Status"):
|
st.subheader("🆕 Initialize Content Repository")
|
||||||
st.json(status)
|
|
||||||
|
with st.expander("Setup New Repository"):
|
||||||
|
repo_url = st.text_input("Gitea Repository URL", placeholder="http://host:3000/user/blog-content.git")
|
||||||
|
|
||||||
|
if st.button("Clone Repository", use_container_width=True):
|
||||||
|
if repo_url:
|
||||||
|
with st.spinner("Cloning..."):
|
||||||
|
CONTENT_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
|
success, stdout, stderr = git_command(['clone', repo_url, str(CONTENT_PATH)], cwd='/srv')
|
||||||
|
if success:
|
||||||
|
st.success("Repository cloned!")
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.error(f"Clone failed: {stderr}")
|
||||||
|
else:
|
||||||
|
st.warning("Enter a repository URL")
|
||||||
|
|
||||||
|
st.divider()
|
||||||
|
|
||||||
|
# Environment Info
|
||||||
|
with st.expander("🔧 Debug: Environment"):
|
||||||
|
st.json({
|
||||||
|
"CONTENT_PATH": str(CONTENT_PATH),
|
||||||
|
"GITEA_URL": GITEA_URL,
|
||||||
|
"CWD": os.getcwd(),
|
||||||
|
"PATH": os.environ.get('PATH', ''),
|
||||||
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user