ui: improve mutation feedback and increase timeout for long-running docker ops
This commit is contained in:
@@ -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); }
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user