Fix Toggle All button state, stop icons, and Disable tooltip

- Fix Toggle All button always showing Start: /picture-targets list
  endpoint does not include processing state; now fetches
  /picture-targets/{id}/state per-target in parallel in both
  loadProfiles() and toggleProfileTargets()
- Replace pause icons (⏸) with stop icons (⏹) in dashboard
- Change profile automation toggle tooltip from 'Disabled' (status)
  to 'Disable' (action); add profiles.action.disable i18n key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 22:35:24 +03:00
parent 8cf7678e2b
commit 0a000cc44c
5 changed files with 71 additions and 13 deletions

View File

@@ -214,7 +214,7 @@ function _updateProfilesInPlace(profiles) {
if (btn) {
btn.className = `btn btn-icon ${p.enabled ? 'btn-warning' : 'btn-success'}`;
btn.setAttribute('onclick', `dashboardToggleProfile('${p.id}', ${!p.enabled})`);
btn.textContent = p.enabled ? '' : '▶';
btn.textContent = p.enabled ? '' : '▶';
}
}
}
@@ -491,7 +491,7 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap
</div>
</div>
<div class="dashboard-target-actions">
<button class="btn btn-icon btn-warning" onclick="dashboardStopTarget('${target.id}')" title="${t('device.button.stop')}"></button>
<button class="btn btn-icon btn-warning" onclick="dashboardStopTarget('${target.id}')" title="${t('device.button.stop')}"></button>
</div>
</div>`;
} else {
@@ -555,8 +555,8 @@ function renderDashboardProfile(profile) {
</div>
</div>
<div class="dashboard-target-actions">
<button class="btn btn-icon ${profile.enabled ? 'btn-warning' : 'btn-success'}" onclick="dashboardToggleProfile('${profile.id}', ${!profile.enabled})" title="${profile.enabled ? t('profiles.status.disabled') : t('profiles.status.active')}">
${profile.enabled ? '' : '▶'}
<button class="btn btn-icon ${profile.enabled ? 'btn-warning' : 'btn-success'}" onclick="dashboardToggleProfile('${profile.id}', ${!profile.enabled})" title="${profile.enabled ? t('profiles.action.disable') : t('profiles.status.active')}">
${profile.enabled ? '' : '▶'}
</button>
</div>
</div>`;

View File

@@ -25,11 +25,27 @@ export async function loadProfiles() {
if (!container) return;
try {
const resp = await fetchWithAuth('/profiles');
if (!resp.ok) throw new Error('Failed to load profiles');
const data = await resp.json();
const [profilesResp, targetsResp] = await Promise.all([
fetchWithAuth('/profiles'),
fetchWithAuth('/picture-targets'),
]);
if (!profilesResp.ok) throw new Error('Failed to load profiles');
const data = await profilesResp.json();
const targetsData = targetsResp.ok ? await targetsResp.json() : { targets: [] };
const allTargets = targetsData.targets || [];
// State is not included in the list response — fetch per-target in parallel
const stateResults = await Promise.all(
allTargets.map(tgt =>
fetchWithAuth(`/picture-targets/${tgt.id}/state`)
.then(r => r.ok ? r.json() : null)
.catch(() => null)
)
);
const runningTargetIds = new Set(
allTargets.filter((_, i) => stateResults[i]?.processing).map(tgt => tgt.id)
);
set_profilesCache(data.profiles);
renderProfiles(data.profiles);
renderProfiles(data.profiles, runningTargetIds);
} catch (error) {
if (error.isAuth) return;
console.error('Failed to load profiles:', error);
@@ -37,12 +53,12 @@ export async function loadProfiles() {
}
}
function renderProfiles(profiles) {
function renderProfiles(profiles, runningTargetIds = new Set()) {
const container = document.getElementById('profiles-content');
let html = '<div class="devices-grid">';
for (const p of profiles) {
html += createProfileCard(p);
html += createProfileCard(p, runningTargetIds);
}
html += `<div class="template-card add-template-card" onclick="openProfileEditor()">
<div class="add-template-icon">+</div>
@@ -57,7 +73,7 @@ function renderProfiles(profiles) {
});
}
function createProfileCard(profile) {
function createProfileCard(profile, runningTargetIds = new Set()) {
const statusClass = !profile.enabled ? 'disabled' : profile.is_active ? 'active' : 'inactive';
const statusText = !profile.enabled ? t('profiles.status.disabled') : profile.is_active ? t('profiles.status.active') : t('profiles.status.inactive');
@@ -104,7 +120,15 @@ function createProfileCard(profile) {
<div class="stream-card-props">${condPills}</div>
<div class="card-actions">
<button class="btn btn-icon btn-secondary" onclick="openProfileEditor('${profile.id}')" title="${t('profiles.edit')}">⚙️</button>
<button class="btn btn-icon ${profile.enabled ? 'btn-warning' : 'btn-success'}" onclick="toggleProfileEnabled('${profile.id}', ${!profile.enabled})" title="${profile.enabled ? t('profiles.status.disabled') : t('profiles.status.active')}">
${profile.target_ids.length > 0 ? (() => {
const anyRunning = profile.target_ids.some(id => runningTargetIds.has(id));
return `<button class="btn btn-icon ${anyRunning ? 'btn-warning' : 'btn-success'}"
onclick="toggleProfileTargets('${profile.id}')"
title="${anyRunning ? t('profiles.toggle_all.stop') : t('profiles.toggle_all.start')}">
${anyRunning ? '⏹' : '▶️'}
</button>`;
})() : ''}
<button class="btn btn-icon ${profile.enabled ? 'btn-warning' : 'btn-success'}" onclick="toggleProfileEnabled('${profile.id}', ${!profile.enabled})" title="${profile.enabled ? t('profiles.action.disable') : t('profiles.status.active')}">
${profile.enabled ? '⏸' : '▶'}
</button>
</div>
@@ -361,6 +385,33 @@ export async function saveProfileEditor() {
}
}
export async function toggleProfileTargets(profileId) {
try {
const profileResp = await fetchWithAuth(`/profiles/${profileId}`);
if (!profileResp.ok) throw new Error('Failed to load profile');
const profile = await profileResp.json();
// Fetch actual processing state for each target in this profile
const stateResults = await Promise.all(
profile.target_ids.map(id =>
fetchWithAuth(`/picture-targets/${id}/state`)
.then(r => r.ok ? r.json() : null)
.catch(() => null)
)
);
const runningSet = new Set(
profile.target_ids.filter((_, i) => stateResults[i]?.processing)
);
const shouldStop = profile.target_ids.some(id => runningSet.has(id));
await Promise.all(profile.target_ids.map(id =>
fetchWithAuth(`/picture-targets/${id}/${shouldStop ? 'stop' : 'start'}`, { method: 'POST' }).catch(() => {})
));
loadProfiles();
} catch (e) {
if (e.isAuth) return;
showToast(e.message, 'error');
}
}
export async function toggleProfileEnabled(profileId, enable) {
try {
const action = enable ? 'enable' : 'disable';