mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-03 04:31:28 +02:00
Fixed os.path issue in update module
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -27,6 +28,30 @@ _update_status = {
|
||||
}
|
||||
|
||||
|
||||
def _find_executable(name: str) -> str | None:
|
||||
"""Find an executable in PATH or common locations."""
|
||||
# Try standard PATH first
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return path
|
||||
|
||||
# Common locations for executables (useful when running as systemd service)
|
||||
common_paths = [
|
||||
f"/usr/bin/{name}",
|
||||
f"/usr/local/bin/{name}",
|
||||
f"/opt/homebrew/bin/{name}",
|
||||
f"/home/linuxbrew/.linuxbrew/bin/{name}",
|
||||
f"{os.path.expanduser('~')}/.nvm/current/bin/{name}",
|
||||
f"{os.path.expanduser('~')}/.local/bin/{name}",
|
||||
]
|
||||
|
||||
for p in common_paths:
|
||||
if os.path.isfile(p) and os.access(p, os.X_OK):
|
||||
return p
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_version(version: str) -> tuple[int, ...]:
|
||||
"""Parse version string into tuple for comparison."""
|
||||
# Remove 'v' prefix if present
|
||||
@@ -140,21 +165,55 @@ async def check_for_updates(db: AsyncSession = Depends(get_db)):
|
||||
|
||||
|
||||
async def _perform_update():
|
||||
"""Perform the actual update using git pull."""
|
||||
"""Perform the actual update using git fetch and reset."""
|
||||
global _update_status
|
||||
|
||||
try:
|
||||
base_dir = settings.base_dir
|
||||
|
||||
# Find git executable (may not be in PATH when running as systemd service)
|
||||
git_path = _find_executable("git")
|
||||
if not git_path:
|
||||
_update_status = {
|
||||
"status": "error",
|
||||
"progress": 0,
|
||||
"message": "Git not found",
|
||||
"error": "Could not find git executable. Please ensure git is installed.",
|
||||
}
|
||||
return
|
||||
|
||||
logger.info(f"Using git at: {git_path}")
|
||||
|
||||
# Git config to avoid safe.directory issues
|
||||
git_config = ["-c", f"safe.directory={base_dir}"]
|
||||
|
||||
_update_status = {
|
||||
"status": "downloading",
|
||||
"progress": 20,
|
||||
"message": "Pulling latest changes...",
|
||||
"progress": 10,
|
||||
"message": "Configuring git...",
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# Run git pull in the project directory
|
||||
base_dir = settings.base_dir
|
||||
# Ensure remote uses HTTPS (SSH may not be available)
|
||||
https_url = f"https://github.com/{GITHUB_REPO}.git"
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"git", "pull", "--rebase",
|
||||
git_path, *git_config, "remote", "set-url", "origin", https_url,
|
||||
cwd=str(base_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
await process.communicate()
|
||||
|
||||
_update_status = {
|
||||
"status": "downloading",
|
||||
"progress": 20,
|
||||
"message": "Fetching latest changes...",
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# Fetch from origin
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
git_path, *git_config, "fetch", "origin", "main",
|
||||
cwd=str(base_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
@@ -162,12 +221,39 @@ async def _perform_update():
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
error_msg = stderr.decode() if stderr else "Git pull failed"
|
||||
logger.error(f"Git pull failed: {error_msg}")
|
||||
error_msg = stderr.decode() if stderr else "Git fetch failed"
|
||||
logger.error(f"Git fetch failed: {error_msg}")
|
||||
_update_status = {
|
||||
"status": "error",
|
||||
"progress": 0,
|
||||
"message": "Failed to pull updates",
|
||||
"message": "Failed to fetch updates",
|
||||
"error": error_msg,
|
||||
}
|
||||
return
|
||||
|
||||
_update_status = {
|
||||
"status": "downloading",
|
||||
"progress": 40,
|
||||
"message": "Applying updates...",
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# Hard reset to origin/main (clean update, no merge conflicts)
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
git_path, *git_config, "reset", "--hard", "origin/main",
|
||||
cwd=str(base_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
error_msg = stderr.decode() if stderr else "Git reset failed"
|
||||
logger.error(f"Git reset failed: {error_msg}")
|
||||
_update_status = {
|
||||
"status": "error",
|
||||
"progress": 0,
|
||||
"message": "Failed to apply updates",
|
||||
"error": error_msg,
|
||||
}
|
||||
return
|
||||
@@ -191,19 +277,21 @@ async def _perform_update():
|
||||
if process.returncode != 0:
|
||||
logger.warning(f"pip install warning: {stderr.decode() if stderr else 'unknown'}")
|
||||
|
||||
_update_status = {
|
||||
"status": "installing",
|
||||
"progress": 70,
|
||||
"message": "Building frontend...",
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# Build frontend
|
||||
# Try to build frontend if npm is available (optional - static files are pre-built)
|
||||
npm_path = _find_executable("npm")
|
||||
frontend_dir = base_dir / "frontend"
|
||||
if frontend_dir.exists():
|
||||
|
||||
if npm_path and frontend_dir.exists():
|
||||
_update_status = {
|
||||
"status": "installing",
|
||||
"progress": 70,
|
||||
"message": "Building frontend...",
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# npm install
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"npm", "install",
|
||||
npm_path, "install",
|
||||
cwd=str(frontend_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
@@ -212,7 +300,7 @@ async def _perform_update():
|
||||
|
||||
# npm run build
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"npm", "run", "build",
|
||||
npm_path, "run", "build",
|
||||
cwd=str(frontend_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
@@ -221,6 +309,8 @@ async def _perform_update():
|
||||
|
||||
if process.returncode != 0:
|
||||
logger.warning(f"Frontend build warning: {stderr.decode() if stderr else 'unknown'}")
|
||||
else:
|
||||
logger.info("npm not found or frontend dir missing - using pre-built static files")
|
||||
|
||||
_update_status = {
|
||||
"status": "complete",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, type LucideIcon } from 'lucide-react';
|
||||
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, X, type LucideIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
|
||||
@@ -80,6 +80,9 @@ export function Layout() {
|
||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||
const hasRedirected = useRef(false);
|
||||
const [dismissedUpdateVersion, setDismissedUpdateVersion] = useState<string | null>(() =>
|
||||
sessionStorage.getItem('dismissedUpdateVersion')
|
||||
);
|
||||
|
||||
// Check for updates
|
||||
const { data: versionInfo } = useQuery({
|
||||
@@ -102,6 +105,18 @@ export function Layout() {
|
||||
refetchInterval: 60 * 60 * 1000, // Check every hour
|
||||
});
|
||||
|
||||
// Show update banner if update available and not dismissed for this version
|
||||
const showUpdateBanner = updateCheck?.update_available &&
|
||||
updateCheck.latest_version &&
|
||||
updateCheck.latest_version !== dismissedUpdateVersion;
|
||||
|
||||
const dismissUpdateBanner = () => {
|
||||
if (updateCheck?.latest_version) {
|
||||
sessionStorage.setItem('dismissedUpdateVersion', updateCheck.latest_version);
|
||||
setDismissedUpdateVersion(updateCheck.latest_version);
|
||||
}
|
||||
};
|
||||
|
||||
// Redirect to default view on initial load
|
||||
useEffect(() => {
|
||||
if (!hasRedirected.current && location.pathname === '/') {
|
||||
@@ -345,6 +360,33 @@ export function Layout() {
|
||||
|
||||
{/* Main content */}
|
||||
<main className={`flex-1 bg-bambu-dark overflow-auto ${sidebarExpanded ? 'ml-64' : 'ml-16'} transition-all duration-300`}>
|
||||
{/* Persistent update banner */}
|
||||
{showUpdateBanner && (
|
||||
<div className="bg-bambu-green/20 border-b border-bambu-green/30 px-4 py-2 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<ArrowUpCircle className="w-4 h-4 text-bambu-green" />
|
||||
<span>
|
||||
{t('nav.updateAvailableBanner', {
|
||||
version: updateCheck?.latest_version,
|
||||
defaultValue: `Version ${updateCheck?.latest_version} is available!`
|
||||
})}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => navigate('/settings')}
|
||||
className="text-bambu-green hover:text-bambu-green/80 font-medium underline"
|
||||
>
|
||||
{t('nav.viewUpdate', { defaultValue: 'View update' })}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={dismissUpdateBanner}
|
||||
className="p-1 hover:bg-bambu-dark-tertiary rounded transition-colors"
|
||||
title={t('common.dismiss', { defaultValue: 'Dismiss' })}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -7,7 +7,7 @@
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
|
||||
<script type="module" crossorigin src="/assets/index-E695Z6RQ.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BdUzePl9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Crbfjp9b.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user