ui: improve mutation feedback and increase timeout for long-running docker ops

This commit is contained in:
2026-04-08 00:22:23 -04:00
parent e24da92e0f
commit 6e19848723
2 changed files with 24 additions and 5 deletions

View File

@@ -118,3 +118,12 @@
from { opacity: 0.5; } from { opacity: 0.5; }
to { opacity: 1; } to { opacity: 1; }
} }
.spin {
animation: spin 1.5s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

View File

@@ -18,6 +18,7 @@ interface Decky {
const DeckyFleet: React.FC = () => { const DeckyFleet: React.FC = () => {
const [deckies, setDeckies] = useState<Decky[]>([]); const [deckies, setDeckies] = useState<Decky[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [mutating, setMutating] = useState<string | null>(null);
const fetchDeckies = async () => { const fetchDeckies = async () => {
try { try {
@@ -31,12 +32,19 @@ const DeckyFleet: React.FC = () => {
}; };
const handleMutate = async (name: string) => { const handleMutate = async (name: string) => {
setMutating(name);
try { try {
await api.post(`/deckies/${name}/mutate`, {}, { timeout: 120000 }); await api.post(`/deckies/${name}/mutate`, {}, { timeout: 120000 });
fetchDeckies(); await fetchDeckies();
} catch (err) { } catch (err: any) {
console.error('Failed to mutate', err); console.error('Failed to mutate', err);
alert('Mutation failed'); if (err.code === 'ECONNABORTED') {
alert('Mutation is still running in the background but the UI timed out.');
} else {
alert('Mutation failed');
}
} finally {
setMutating(null);
} }
}; };
@@ -102,13 +110,15 @@ const DeckyFleet: React.FC = () => {
</span> </span>
<button <button
onClick={() => handleMutate(decky.name)} onClick={() => handleMutate(decky.name)}
disabled={!!mutating}
style={{ style={{
background: 'transparent', border: '1px solid var(--accent-color)', background: 'transparent', border: '1px solid var(--accent-color)',
color: 'var(--accent-color)', padding: '2px 8px', fontSize: '0.7rem', color: 'var(--accent-color)', padding: '2px 8px', fontSize: '0.7rem',
cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '4px', marginLeft: 'auto' cursor: mutating ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', gap: '4px', marginLeft: 'auto',
opacity: mutating ? 0.5 : 1
}} }}
> >
<RefreshCw size={10} /> FORCE <RefreshCw size={10} className={mutating === decky.name ? "spin" : ""} /> {mutating === decky.name ? 'MUTATING...' : 'FORCE'}
</button> </button>
</div> </div>
{decky.last_mutated > 0 && ( {decky.last_mutated > 0 && (