mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
● Compact SpoolBuddy updates tab layout to fit without scrolling
Merged version, status, and update check into a single card. Buttons are side by side when up to date. Reduced padding and font sizes. Removed separate "complete" banner since status is now cleared on re-registration. SSH Setup section is smaller and more compact.
This commit is contained in:
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
# Application version - single source of truth
|
||||
APP_VERSION = "0.2.3"
|
||||
APP_VERSION = "0.2.3.b1"
|
||||
GITHUB_REPO = "maziggy/bambuddy"
|
||||
BUG_REPORT_RELAY_URL = os.environ.get("BUG_REPORT_RELAY_URL", "https://bambuddy.cool/api/bug-report")
|
||||
|
||||
|
||||
@@ -546,153 +546,115 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
|
||||
|| (updateResult?.current_version && updateResult.current_version !== '0.0.0' ? updateResult.current_version : null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Current version */}
|
||||
<div className="bg-zinc-800 rounded-lg p-4">
|
||||
<h3 className="text-sm font-semibold text-zinc-300 mb-3">
|
||||
{t('spoolbuddy.settings.daemonVersion', 'Daemon Version')}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{/* Version + Update status + Check — single card */}
|
||||
<div className="bg-zinc-800 rounded-lg p-3 space-y-3">
|
||||
{/* Version row */}
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-zinc-500">{t('spoolbuddy.settings.currentVersion', 'Current')}</span>
|
||||
<span className="text-zinc-500">{t('spoolbuddy.settings.currentVersion', 'Current Version')}</span>
|
||||
<span className="text-zinc-200 font-mono">
|
||||
{displayVersion || (
|
||||
<span className="text-zinc-500 italic">{t('spoolbuddy.settings.versionPending', 'Waiting for daemon...')}</span>
|
||||
<span className="text-zinc-500 italic text-xs">{t('spoolbuddy.settings.versionPending', 'Waiting for daemon...')}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Update progress (shown when update is in progress) */}
|
||||
{isUpdating && (
|
||||
<div className="bg-zinc-800 rounded-lg p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<svg className="w-5 h-5 animate-spin text-green-400 flex-shrink-0" viewBox="0 0 24 24" fill="none">
|
||||
{/* Update progress */}
|
||||
{isUpdating && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<svg className="w-4 h-4 animate-spin text-green-400 flex-shrink-0" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-green-300">
|
||||
{t('spoolbuddy.settings.updating', 'Updating...')}
|
||||
</p>
|
||||
<p className="text-xs text-zinc-400 mt-0.5">
|
||||
{device.update_message || t('spoolbuddy.settings.updateWaiting', 'Waiting for device...')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Update complete */}
|
||||
{device.update_status === 'complete' && (
|
||||
<div className="rounded-lg p-3 text-sm bg-green-900/30 border border-green-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-green-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<p className="text-green-300">{device.update_message || t('spoolbuddy.settings.updateComplete', 'Update complete!')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Update error */}
|
||||
{device.update_status === 'error' && (
|
||||
<div className="rounded-lg p-3 text-sm bg-red-900/30 border border-red-800">
|
||||
<p className="text-red-300">{device.update_message || t('spoolbuddy.settings.updateFailed', 'Update failed')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Check for updates */}
|
||||
<div className="bg-zinc-800 rounded-lg p-4 space-y-3">
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
disabled={checking || isUpdating}
|
||||
className="w-full px-4 py-2.5 rounded-lg text-sm font-medium bg-zinc-700 text-zinc-200 hover:bg-zinc-600 disabled:opacity-40 transition-colors min-h-[44px] flex items-center justify-center gap-2"
|
||||
>
|
||||
{checking && (
|
||||
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
)}
|
||||
{checking ? t('spoolbuddy.settings.checking', 'Checking...') : t('spoolbuddy.settings.checkUpdates', 'Check for Updates')}
|
||||
</button>
|
||||
|
||||
{/* Error feedback */}
|
||||
{error && (
|
||||
<div className="rounded-lg p-3 text-sm bg-red-900/30 border border-red-800">
|
||||
<p className="text-red-300">{error}</p>
|
||||
<span className="text-green-300 text-xs">
|
||||
{device.update_message || t('spoolbuddy.settings.updateWaiting', 'Updating...')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Result feedback */}
|
||||
{updateResult && (
|
||||
<div className={`rounded-lg p-3 text-sm ${
|
||||
updateResult.update_available
|
||||
? 'bg-green-900/30 border border-green-800'
|
||||
: 'bg-zinc-700/50'
|
||||
}`}>
|
||||
{updateResult.update_available ? (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<p className="text-green-300 font-medium">
|
||||
{t('spoolbuddy.settings.updateAvailable', 'Update available')}: v{updateResult.latest_version}
|
||||
</p>
|
||||
<p className="text-xs text-zinc-400">
|
||||
{displayVersion ? `${displayVersion} → ${updateResult.latest_version}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={applyUpdate}
|
||||
disabled={applying || isUpdating || !device.online}
|
||||
className="w-full px-4 py-2.5 rounded-lg text-sm font-medium bg-green-600 text-white hover:bg-green-700 disabled:opacity-40 transition-colors min-h-[44px] flex items-center justify-center gap-2"
|
||||
>
|
||||
{applying && (
|
||||
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
)}
|
||||
{!device.online
|
||||
? t('spoolbuddy.settings.deviceOffline', 'Device Offline')
|
||||
: t('spoolbuddy.settings.applyUpdate', 'Apply Update')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-green-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<p className="text-zinc-300">{t('spoolbuddy.settings.upToDate', 'Up to date')}</p>
|
||||
</div>
|
||||
{/* Force update button — re-deploy even when versions match */}
|
||||
<button
|
||||
onClick={applyUpdate}
|
||||
disabled={applying || isUpdating || !device.online}
|
||||
className="w-full px-4 py-2 rounded-lg text-xs font-medium bg-zinc-700 text-zinc-400 hover:bg-zinc-600 hover:text-zinc-200 disabled:opacity-40 transition-colors min-h-[36px]"
|
||||
>
|
||||
{!device.online
|
||||
? t('spoolbuddy.settings.deviceOffline', 'Device Offline')
|
||||
: t('spoolbuddy.settings.forceUpdate', 'Force Update')}
|
||||
</button>
|
||||
</div>
|
||||
{/* Update error */}
|
||||
{device.update_status === 'error' && (
|
||||
<p className="text-xs text-red-300">{device.update_message || t('spoolbuddy.settings.updateFailed', 'Update failed')}</p>
|
||||
)}
|
||||
|
||||
{/* Error from trigger */}
|
||||
{error && <p className="text-xs text-red-300">{error}</p>}
|
||||
|
||||
{/* Update available → Apply button */}
|
||||
{updateResult?.update_available ? (
|
||||
<>
|
||||
<p className="text-xs text-green-300">
|
||||
{t('spoolbuddy.settings.updateAvailable', 'Update available')}: {displayVersion} → {updateResult.latest_version}
|
||||
</p>
|
||||
<button
|
||||
onClick={applyUpdate}
|
||||
disabled={applying || isUpdating || !device.online}
|
||||
className="w-full px-3 py-2 rounded-lg text-sm font-medium bg-green-600 text-white hover:bg-green-700 disabled:opacity-40 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
{applying && (
|
||||
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
)}
|
||||
{!device.online
|
||||
? t('spoolbuddy.settings.deviceOffline', 'Device Offline')
|
||||
: t('spoolbuddy.settings.applyUpdate', 'Apply Update')}
|
||||
</button>
|
||||
</>
|
||||
) : updateResult && !isUpdating ? (
|
||||
/* Up to date → Check + Force buttons side by side */
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
disabled={checking}
|
||||
className="flex-1 px-3 py-2 rounded-lg text-xs font-medium bg-zinc-700 text-zinc-300 hover:bg-zinc-600 disabled:opacity-40 transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
{checking && (
|
||||
<svg className="w-3 h-3 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
)}
|
||||
{t('spoolbuddy.settings.checkUpdates', 'Check for Updates')}
|
||||
</button>
|
||||
<button
|
||||
onClick={applyUpdate}
|
||||
disabled={applying || isUpdating || !device.online}
|
||||
className="px-3 py-2 rounded-lg text-xs font-medium bg-zinc-700 text-zinc-400 hover:bg-zinc-600 hover:text-zinc-200 disabled:opacity-40 transition-colors"
|
||||
>
|
||||
{t('spoolbuddy.settings.forceUpdate', 'Force Update')}
|
||||
</button>
|
||||
</div>
|
||||
) : !isUpdating ? (
|
||||
/* No result yet → Check button */
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
disabled={checking}
|
||||
className="w-full px-3 py-2 rounded-lg text-sm font-medium bg-zinc-700 text-zinc-200 hover:bg-zinc-600 disabled:opacity-40 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
{checking && (
|
||||
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{t('spoolbuddy.settings.checkUpdates', 'Check for Updates')}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* SSH Setup */}
|
||||
<div className="bg-zinc-800 rounded-lg p-4">
|
||||
{/* SSH Setup — collapsible */}
|
||||
<div className="bg-zinc-800 rounded-lg p-3">
|
||||
<button
|
||||
onClick={() => setSSHExpanded(!sshExpanded)}
|
||||
className="w-full flex justify-between items-center text-sm"
|
||||
className="w-full flex justify-between items-center text-xs"
|
||||
>
|
||||
<span className="font-semibold text-zinc-300">
|
||||
<span className="font-medium text-zinc-400">
|
||||
{t('spoolbuddy.settings.sshSetup', 'SSH Setup')}
|
||||
</span>
|
||||
<svg
|
||||
className={`w-4 h-4 text-zinc-400 transition-transform ${sshExpanded ? 'rotate-180' : ''}`}
|
||||
className={`w-3 h-3 text-zinc-500 transition-transform ${sshExpanded ? 'rotate-180' : ''}`}
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
@@ -700,26 +662,26 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
|
||||
</button>
|
||||
|
||||
{sshExpanded && (
|
||||
<div className="mt-3 space-y-3">
|
||||
<p className="text-xs text-zinc-400">
|
||||
{t('spoolbuddy.settings.sshDescription', 'Add this public key to your SpoolBuddy device to enable remote updates. Run the install script with --ssh-pubkey or add it to ~/.ssh/authorized_keys on the device.')}
|
||||
<div className="mt-2 space-y-2">
|
||||
<p className="text-xs text-zinc-500">
|
||||
{t('spoolbuddy.settings.sshDescription', 'SSH key is deployed automatically. For manual setup, add this key to ~/.ssh/authorized_keys on the device.')}
|
||||
</p>
|
||||
{sshKeyData?.public_key ? (
|
||||
<div className="relative">
|
||||
<pre className="bg-zinc-900 rounded p-3 text-xs text-zinc-300 font-mono break-all whitespace-pre-wrap">
|
||||
<pre className="bg-zinc-900 rounded p-2 text-[10px] text-zinc-400 font-mono break-all whitespace-pre-wrap">
|
||||
{sshKeyData.public_key}
|
||||
</pre>
|
||||
<button
|
||||
onClick={copyKey}
|
||||
className="absolute top-2 right-2 px-2 py-1 rounded text-xs bg-zinc-700 text-zinc-300 hover:bg-zinc-600 transition-colors"
|
||||
className="absolute top-1 right-1 px-1.5 py-0.5 rounded text-[10px] bg-zinc-700 text-zinc-300 hover:bg-zinc-600 transition-colors"
|
||||
>
|
||||
{copied ? t('common.copied', 'Copied!') : t('common.copy', 'Copy')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-zinc-500 italic">
|
||||
{t('spoolbuddy.settings.sshKeyLoading', 'Loading SSH key...')}
|
||||
</div>
|
||||
<span className="text-[10px] text-zinc-500 italic">
|
||||
{t('spoolbuddy.settings.sshKeyLoading', 'Loading...')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -23,8 +23,8 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-BSgCAUZw.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CJ-drcFM.css">
|
||||
<script type="module" crossorigin src="/assets/index-DrqpB7p-.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Dzv7xI7m.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user