Add auto-start targets feature with dashboard section

- Add auto_start boolean field to PictureTarget model (persisted per-target)
- Wire auto_start through API schemas, routes, and store
- Auto-start targets on server boot in main.py lifespan
- Add star toggle button on target cards (next to delete button)
- Add auto-start section on dashboard between performance and profiles
- Remove auto-start section from profiles tab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 15:08:01 +03:00
parent 701eac19e5
commit d05b4b78f4
14 changed files with 131 additions and 8 deletions

View File

@@ -363,6 +363,38 @@ export async function loadDashboard(forceFullRender = false) {
return;
}
const autoStartTargets = enriched.filter(t => t.auto_start);
if (autoStartTargets.length > 0) {
const autoStartItems = autoStartTargets.map(target => {
const isRunning = !!(target.state && target.state.processing);
const device = devicesMap[target.device_id];
const deviceName = device ? device.name : '';
const typeIcon = target.target_type === 'key_colors' ? '🎨' : '💡';
const statusBadge = isRunning
? `<span class="dashboard-badge-active">${t('profiles.status.active')}</span>`
: `<span class="dashboard-badge-stopped">${t('profiles.status.inactive')}</span>`;
return `<div class="dashboard-target dashboard-autostart" data-target-id="${target.id}">
<div class="dashboard-target-info">
<span class="dashboard-target-icon">⭐</span>
<div>
<div class="dashboard-target-name">${escapeHtml(target.name)} ${statusBadge}</div>
${deviceName ? `<div class="dashboard-target-subtitle">${typeIcon} ${escapeHtml(deviceName)}</div>` : `<div class="dashboard-target-subtitle">${typeIcon}</div>`}
</div>
</div>
<div class="dashboard-target-actions">
<button class="btn btn-icon ${isRunning ? 'btn-warning' : 'btn-success'}" onclick="${isRunning ? `dashboardStopTarget('${target.id}')` : `dashboardStartTarget('${target.id}')`}" title="${isRunning ? t('device.stop') : t('device.start')}">
${isRunning ? '⏹' : '▶'}
</button>
</div>
</div>`;
}).join('');
dynamicHtml += `<div class="dashboard-section">
${_sectionHeader('autostart', t('autostart.title'), autoStartTargets.length)}
${_sectionContent('autostart', autoStartItems)}
</div>`;
}
if (profiles.length > 0) {
const activeProfiles = profiles.filter(p => p.is_active);
const inactiveProfiles = profiles.filter(p => !p.is_active);

View File

@@ -70,7 +70,10 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap, valueS
return `
<div class="card" data-kc-target-id="${target.id}">
<button class="card-remove-btn" onclick="deleteKCTarget('${target.id}')" title="${t('common.delete')}">&#x2715;</button>
<div class="card-top-actions">
<button class="card-autostart-btn${target.auto_start ? ' active' : ''}" onclick="toggleTargetAutoStart('${target.id}', ${!target.auto_start})" title="${target.auto_start ? t('autostart.toggle.enabled') : t('autostart.toggle.disabled')}">&#x2605;</button>
<button class="card-remove-btn" onclick="deleteKCTarget('${target.id}')" title="${t('common.delete')}">&#x2715;</button>
</div>
<div class="card-header">
<div class="card-title">
${escapeHtml(target.name)}

View File

@@ -689,7 +689,10 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
return `
<div class="card" data-target-id="${target.id}">
<button class="card-remove-btn" onclick="deleteTarget('${target.id}')" title="${t('common.delete')}">&#x2715;</button>
<div class="card-top-actions">
<button class="card-autostart-btn${target.auto_start ? ' active' : ''}" onclick="toggleTargetAutoStart('${target.id}', ${!target.auto_start})" title="${target.auto_start ? t('autostart.toggle.enabled') : t('autostart.toggle.disabled')}">&#x2605;</button>
<button class="card-remove-btn" onclick="deleteTarget('${target.id}')" title="${t('common.delete')}">&#x2715;</button>
</div>
<div class="card-header">
<div class="card-title">
<span class="health-dot ${healthClass}" title="${healthTitle}"></span>
@@ -884,6 +887,25 @@ export async function cloneTarget(targetId) {
}
}
export async function toggleTargetAutoStart(targetId, enable) {
try {
const response = await fetchWithAuth(`/picture-targets/${targetId}`, {
method: 'PUT',
body: JSON.stringify({ auto_start: enable }),
});
if (response.ok) {
showToast(t(enable ? 'autostart.toggle.enabled' : 'autostart.toggle.disabled'), 'success');
loadTargetsTab();
} else {
const error = await response.json();
showToast(`Failed: ${error.detail}`, 'error');
}
} catch (error) {
console.error('Failed to toggle auto-start:', error);
showToast('Failed to toggle auto-start', 'error');
}
}
export async function deleteTarget(targetId) {
const confirmed = await showConfirm(t('targets.delete.confirm'));
if (!confirmed) return;