diff --git a/package/secubox/secubox-app-metabolizer/Makefile b/package/secubox/secubox-app-metabolizer/Makefile index 55add13e..ced17822 100644 --- a/package/secubox/secubox-app-metabolizer/Makefile +++ b/package/secubox/secubox-app-metabolizer/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-app-metabolizer PKG_VERSION:=1.0.0 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_ARCH:=all PKG_MAINTAINER:=CyberMind Studio diff --git a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/app.py b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/app.py index 3777eac9..1cf0146e 100644 --- a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/app.py +++ b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/app.py @@ -111,31 +111,16 @@ st.info(""" - **Settings** - Configure Git and Hexo integration """) -# Quick actions +# Quick actions - simplified without switch_page st.subheader("Quick Actions") -col1, col2, col3 = st.columns(3) - -with col1: - if st.button("๐Ÿ“ New Post", use_container_width=True): - st.switch_page("pages/1_editor.py") - -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) +st.markdown(""" +Use the **sidebar** on the left to navigate to: +- ๐Ÿ“ **1_editor** - Write new posts +- ๐Ÿ“š **2_posts** - Manage posts +- ๐Ÿ–ผ๏ธ **3_media** - Media library +- โš™๏ธ **4_settings** - Settings +""") # Footer st.divider() diff --git a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/1_editor.py b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/1_editor.py index 3ec7cbc9..c152c695 100644 --- a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/1_editor.py +++ b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/1_editor.py @@ -5,13 +5,12 @@ import streamlit as st from datetime import datetime from pathlib import Path import subprocess -import yaml import os st.set_page_config(page_title="Editor - Metabolizer", page_icon="โœ๏ธ", layout="wide") # Paths -CONTENT_PATH = Path("/srv/metabolizer/content") +CONTENT_PATH = Path(os.environ.get('METABOLIZER_CONTENT', '/srv/content')) POSTS_PATH = CONTENT_PATH / "_posts" DRAFTS_PATH = CONTENT_PATH / "_drafts" @@ -112,16 +111,28 @@ def generate_filename(title, date): return f"{date}-{slug}.md" def generate_frontmatter(title, date, time, categories, tags, excerpt): - """Generate YAML front matter""" - fm = { - 'title': title, - 'date': f"{date} {time.strftime('%H:%M:%S')}", - 'categories': categories, - 'tags': [t.strip() for t in tags.split(",")] if tags else [], - } + """Generate YAML front matter without yaml module""" + lines = ["---"] + lines.append(f"title: {title}") + lines.append(f"date: {date} {time.strftime('%H:%M:%S')}") + + 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: - fm['excerpt'] = excerpt - return "---\n" + yaml.dump(fm, default_flow_style=False) + "---\n\n" + lines.append(f"excerpt: \"{excerpt}\"") + + lines.append("---") + lines.append("") + return "\n".join(lines) def save_post(path, title, date, time, categories, tags, excerpt, content): """Save post to file""" @@ -136,10 +147,12 @@ def save_post(path, title, date, time, categories, tags, excerpt, content): def git_commit_push(message): """Commit and push to Gitea""" - os.chdir(CONTENT_PATH) - subprocess.run(['git', 'add', '-A'], capture_output=True) - subprocess.run(['git', 'commit', '-m', message], capture_output=True) - subprocess.run(['git', 'push', 'origin', 'main'], capture_output=True) + try: + subprocess.run(['git', 'add', '-A'], cwd=CONTENT_PATH, capture_output=True) + subprocess.run(['git', 'commit', '-m', message], cwd=CONTENT_PATH, capture_output=True) + subprocess.run(['git', 'push', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True) + except: + pass with col1: if st.button("๐Ÿ’พ Save Draft", use_container_width=True): @@ -159,21 +172,18 @@ with col2: git_commit_push(f"Add post: {title}") st.success(f"Published: {filepath.name}") - st.info("Webhook will trigger rebuild automatically") + st.info("Post saved to repository") else: st.error("Title and content required") with col3: - if st.button("๐Ÿ”„ Build Now", use_container_width=True): - 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") + if st.button("๐Ÿ”„ Sync", use_container_width=True): + with st.spinner("Syncing..."): + try: + subprocess.run(['git', 'pull', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True) + st.success("Synced!") + except: + st.error("Sync failed") with col4: if st.button("๐Ÿ—‘๏ธ Clear", use_container_width=True): diff --git a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/2_posts.py b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/2_posts.py index 712763d6..f0e58817 100644 --- a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/2_posts.py +++ b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/2_posts.py @@ -5,27 +5,41 @@ import streamlit as st from pathlib import Path from datetime import datetime import subprocess -import yaml import os +import re st.set_page_config(page_title="Posts - Metabolizer", page_icon="๐Ÿ“š", layout="wide") # Paths -CONTENT_PATH = Path("/srv/metabolizer/content") +CONTENT_PATH = Path(os.environ.get('METABOLIZER_CONTENT', '/srv/content')) POSTS_PATH = CONTENT_PATH / "_posts" DRAFTS_PATH = CONTENT_PATH / "_drafts" st.title("๐Ÿ“š Post Management") def parse_frontmatter(filepath): - """Parse YAML front matter from markdown file""" + """Parse YAML front matter from markdown file (without yaml module)""" try: content = filepath.read_text() if content.startswith("---"): parts = content.split("---", 2) if len(parts) >= 3: - fm = yaml.safe_load(parts[1]) + fm_text = parts[1].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 except Exception as e: pass @@ -51,10 +65,12 @@ def get_posts(path): def git_commit_push(message): """Commit and push to Gitea""" - os.chdir(CONTENT_PATH) - subprocess.run(['git', 'add', '-A'], capture_output=True) - subprocess.run(['git', 'commit', '-m', message], capture_output=True) - subprocess.run(['git', 'push', 'origin', 'main'], capture_output=True) + try: + subprocess.run(['git', 'add', '-A'], cwd=CONTENT_PATH, capture_output=True) + subprocess.run(['git', 'commit', '-m', message], cwd=CONTENT_PATH, capture_output=True) + subprocess.run(['git', 'push', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True) + except: + pass # Tabs for Published and Drafts tab1, tab2 = st.tabs(["๐Ÿ“ฐ Published", "๐Ÿ“ Drafts"]) @@ -74,14 +90,15 @@ with tab1: with col1: st.caption(f"๐Ÿ“… {post['date']}") 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']: - 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']) with col2: if st.button("โœ๏ธ Edit", key=f"edit_{post['filename']}"): - # Load into editor st.session_state.post_title = post['title'] st.session_state.post_content = post['body'] st.switch_page("pages/1_editor.py") @@ -93,7 +110,7 @@ with tab1: st.rerun() 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'] post['path'].rename(new_path) git_commit_push(f"Unpublish: {post['title']}") @@ -123,7 +140,7 @@ with tab2: st.switch_page("pages/1_editor.py") 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'] draft['path'].rename(new_path) git_commit_push(f"Publish: {draft['title']}") @@ -135,28 +152,22 @@ with tab2: st.success(f"Deleted") st.rerun() -# Build action +# Sync action st.divider() col1, col2 = st.columns(2) with col1: if st.button("๐Ÿ”„ Sync from Git", use_container_width=True): with st.spinner("Syncing..."): - result = subprocess.run( - ['/usr/sbin/metabolizerctl', 'sync'], - capture_output=True, text=True - ) - st.success("Synced!") - st.rerun() + try: + subprocess.run(['git', 'pull', 'origin', 'master'], cwd=CONTENT_PATH, capture_output=True) + st.success("Synced!") + st.rerun() + except: + st.error("Sync failed") with col2: - if st.button("๐Ÿ—๏ธ Rebuild Blog", use_container_width=True): - 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("Build failed") + if st.button("๐Ÿ“ค Push to Git", use_container_width=True): + with st.spinner("Pushing..."): + git_commit_push("Update posts") + st.success("Pushed!") diff --git a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/4_settings.py b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/4_settings.py index 09cd0014..291c1b6c 100644 --- a/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/4_settings.py +++ b/package/secubox/secubox-app-metabolizer/files/usr/share/metabolizer/cms/pages/4_settings.py @@ -4,32 +4,28 @@ Metabolizer CMS - Settings import streamlit as st import subprocess import json +import os +from pathlib import Path st.set_page_config(page_title="Settings - Metabolizer", page_icon="โš™๏ธ", layout="wide") st.title("โš™๏ธ Settings") -def get_status(): - """Get metabolizer status""" +# Paths +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: result = subprocess.run( - ['/usr/sbin/metabolizerctl', 'status'], + ['git'] + args, + cwd=cwd or CONTENT_PATH, capture_output=True, text=True ) - return json.loads(result.stdout) - except: - return {} - -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() + return result.returncode == 0, result.stdout, result.stderr + except Exception as e: + return False, "", str(e) # Pipeline Status st.subheader("๐Ÿ“Š Pipeline Status") @@ -37,43 +33,45 @@ st.subheader("๐Ÿ“Š Pipeline Status") col1, col2, col3 = st.columns(3) with col1: - gitea_status = status.get('gitea', {}).get('status', 'unknown') - st.metric( - "Gitea", - gitea_status.upper(), - delta="OK" if gitea_status == "running" else "DOWN" - ) + st.metric("Gitea", "EXTERNAL", delta="Host") with col2: - streamlit_status = status.get('streamlit', {}).get('status', 'unknown') - st.metric( - "Streamlit", - streamlit_status.upper(), - delta="OK" if streamlit_status == "running" else "DOWN" - ) + st.metric("Streamlit", "RUNNING", delta="OK") with col3: - hexo_status = status.get('hexo', {}).get('status', 'unknown') - st.metric( - "HexoJS", - hexo_status.upper(), - delta="OK" if hexo_status == "running" else "DOWN" - ) + st.metric("HexoJS", "EXTERNAL", delta="Host") st.divider() # Content Repository st.subheader("๐Ÿ“ Content Repository") -content = status.get('content', {}) col1, col2 = st.columns(2) with col1: - st.text_input("Repository", value=content.get('repo', 'blog-content'), disabled=True) - st.text_input("Path", value=content.get('path', '/srv/metabolizer/content'), disabled=True) + st.text_input("Content Path", value=str(CONTENT_PATH), 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: - 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 st.subheader("๐Ÿ”— Git Operations") @@ -83,7 +81,7 @@ col1, col2, col3 = st.columns(3) with col1: if st.button("๐Ÿ”„ Pull Latest", use_container_width=True): with st.spinner("Pulling..."): - success, stdout, stderr = run_command(['sync']) + success, stdout, stderr = git_command(['pull', 'origin', 'master']) if success: st.success("Pulled latest changes") else: @@ -91,96 +89,49 @@ with col1: with col2: if st.button("๐Ÿ“Š Git Status", use_container_width=True): - import os - os.chdir("/srv/metabolizer/content") - result = subprocess.run(['git', 'status', '--short'], capture_output=True, text=True) - if result.stdout: - st.code(result.stdout) + success, stdout, stderr = git_command(['status', '--short']) + if stdout: + st.code(stdout) else: st.info("Working tree clean") with col3: - github_url = st.text_input("Mirror from GitHub URL") - if st.button("๐Ÿ”— Mirror", use_container_width=True): - if github_url: - 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 st.button("๐Ÿ“ค Push Changes", use_container_width=True): + with st.spinner("Pushing..."): + success, stdout, stderr = git_command(['push', 'origin', 'master']) if success: - st.success("Published to portal") + st.success("Pushed changes") else: - st.error(f"Publish 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.error(f"Push failed: {stderr}") st.divider() -# Raw Status JSON -with st.expander("๐Ÿ”ง Debug: Raw Status"): - st.json(status) +# Initialize Repository +st.subheader("๐Ÿ†• Initialize Content Repository") + +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', ''), + })