diff --git a/server/src/wled_controller/api/routes.py b/server/src/wled_controller/api/routes.py
index ffa5d5a..9fc93f3 100644
--- a/server/src/wled_controller/api/routes.py
+++ b/server/src/wled_controller/api/routes.py
@@ -794,8 +794,9 @@ async def get_template(
template_store: TemplateStore = Depends(get_template_store),
):
"""Get template by ID."""
- template = template_store.get_template(template_id)
- if not template:
+ try:
+ template = template_store.get_template(template_id)
+ except ValueError:
raise HTTPException(status_code=404, detail=f"Template {template_id} not found")
return TemplateResponse(
@@ -822,6 +823,7 @@ async def update_template(
template = template_store.update_template(
template_id=template_id,
name=update_data.name,
+ engine_type=update_data.engine_type,
engine_config=update_data.engine_config,
description=update_data.description,
)
diff --git a/server/src/wled_controller/api/schemas.py b/server/src/wled_controller/api/schemas.py
index 98bf3c6..030aca6 100644
--- a/server/src/wled_controller/api/schemas.py
+++ b/server/src/wled_controller/api/schemas.py
@@ -230,6 +230,7 @@ class TemplateUpdate(BaseModel):
"""Request to update a template."""
name: Optional[str] = Field(None, description="Template name", min_length=1, max_length=100)
+ engine_type: Optional[str] = Field(None, description="Capture engine type (mss, dxcam, wgc)")
engine_config: Optional[Dict] = Field(None, description="Engine-specific configuration")
description: Optional[str] = Field(None, description="Template description", max_length=500)
diff --git a/server/src/wled_controller/static/app.js b/server/src/wled_controller/static/app.js
index 6a3167d..461f3be 100644
--- a/server/src/wled_controller/static/app.js
+++ b/server/src/wled_controller/static/app.js
@@ -563,7 +563,6 @@ function createDeviceCard(device) {
healthLabel = `${t('device.health.offline')}`;
}
- const displayIndex = settings.display_index !== undefined ? settings.display_index : 0;
const ledCount = state.wled_led_count || device.led_count;
return `
@@ -573,22 +572,18 @@ function createDeviceCard(device) {
${device.name || device.id}
+ ${device.url ? `${escapeHtml(device.url.replace(/^https?:\/\//, ''))}` : ''}
${healthLabel}
${isProcessing ? `${t('device.status.processing')}` : ''}
${wledVersion ? `v${wledVersion}` : ''}
- 🖥️ ${displayIndex}
${ledCount ? `💡 ${ledCount}` : ''}
${state.wled_led_type ? `🔌 ${state.wled_led_type.replace(/ RGBW$/, '')}` : ''}
${state.wled_rgbw ? '' : ''}
-
- ${t('device.url')}
- ${device.url || 'N/A'}
-
${isProcessing ? `
@@ -2357,6 +2352,9 @@ function renderTemplatesList(templates) {
return `
+ ${!template.is_default ? `
+
+ ` : ''}
@@ -2463,7 +2465,8 @@ async function editTemplate(templateId) {
// Load displays for test
await loadDisplaysForTest();
- document.getElementById('template-test-results').style.display = 'none';
+ const testResults = document.getElementById('template-test-results');
+ if (testResults) testResults.style.display = 'none';
document.getElementById('template-error').style.display = 'none';
// Show modal
@@ -2789,17 +2792,27 @@ async function loadDisplaysForTest() {
const displaysData = await response.json();
const select = document.getElementById('test-template-display');
- select.innerHTML = ``;
+ select.innerHTML = '';
+ let primaryIndex = null;
(displaysData.displays || []).forEach(display => {
const option = document.createElement('option');
option.value = display.index;
option.textContent = `Display ${display.index} (${display.width}x${display.height})`;
if (display.is_primary) {
option.textContent += ' ★';
+ primaryIndex = display.index;
}
select.appendChild(option);
});
+
+ // Auto-select: last used display, or primary as fallback
+ const lastDisplay = localStorage.getItem('lastTestDisplayIndex');
+ if (lastDisplay !== null && select.querySelector(`option[value="${lastDisplay}"]`)) {
+ select.value = lastDisplay;
+ } else if (primaryIndex !== null) {
+ select.value = String(primaryIndex);
+ }
} catch (error) {
console.error('Error loading displays:', error);
}
@@ -2843,6 +2856,7 @@ async function runTemplateTest() {
}
const result = await response.json();
+ localStorage.setItem('lastTestDisplayIndex', displayIndex);
displayTestResults(result);
} catch (error) {
console.error('Error running test:', error);
diff --git a/server/src/wled_controller/static/style.css b/server/src/wled_controller/static/style.css
index b10a0f5..62bd399 100644
--- a/server/src/wled_controller/static/style.css
+++ b/server/src/wled_controller/static/style.css
@@ -282,6 +282,17 @@ section {
flex-wrap: wrap;
}
+.device-url-badge {
+ font-size: 0.7rem;
+ font-weight: 400;
+ color: var(--text-secondary);
+ background: var(--border-color);
+ padding: 2px 8px;
+ border-radius: 10px;
+ letter-spacing: 0.03em;
+ font-family: monospace;
+}
+
.card-subtitle {
display: flex;
align-items: center;
@@ -1737,6 +1748,7 @@ input:-webkit-autofill:focus {
transition: box-shadow 0.2s;
display: flex;
flex-direction: column;
+ position: relative;
}
.template-card:hover {
@@ -1785,6 +1797,7 @@ input:-webkit-autofill:focus {
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
+ padding-right: 24px;
}
.template-name {
@@ -1843,15 +1856,31 @@ input:-webkit-autofill:focus {
padding: 4px 0;
}
-.template-config-details pre {
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 4px;
- padding: 12px;
+.config-table {
+ width: 100%;
margin-top: 8px;
- overflow-x: auto;
- font-size: 12px;
- line-height: 1.5;
+ border-collapse: collapse;
+ font-size: 13px;
+}
+
+.config-table td {
+ padding: 4px 8px;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.config-table tr:last-child td {
+ border-bottom: none;
+}
+
+.config-key {
+ color: var(--text-secondary);
+ white-space: nowrap;
+ width: 1%;
+}
+
+.config-value {
+ color: var(--text-primary);
+ font-family: monospace;
}
.template-card-actions {
diff --git a/server/src/wled_controller/storage/template_store.py b/server/src/wled_controller/storage/template_store.py
index 6f6b0e7..9f8710b 100644
--- a/server/src/wled_controller/storage/template_store.py
+++ b/server/src/wled_controller/storage/template_store.py
@@ -192,6 +192,7 @@ class TemplateStore:
self,
template_id: str,
name: Optional[str] = None,
+ engine_type: Optional[str] = None,
engine_config: Optional[Dict[str, any]] = None,
description: Optional[str] = None,
) -> CaptureTemplate:
@@ -200,6 +201,7 @@ class TemplateStore:
Args:
template_id: Template ID
name: New name (optional)
+ engine_type: New engine type (optional)
engine_config: New engine config (optional)
description: New description (optional)
@@ -220,6 +222,8 @@ class TemplateStore:
# Update fields
if name is not None:
template.name = name
+ if engine_type is not None:
+ template.engine_type = engine_type
if engine_config is not None:
template.engine_config = engine_config
if description is not None: