feat: add Remote Instances management UI to Settings (task-15)

This commit is contained in:
Brian Krabach
2026-03-31 00:55:26 -07:00
parent 200137a995
commit c8a963f28e
6 changed files with 388 additions and 0 deletions
+112
View File
@@ -1184,6 +1184,57 @@ function buildSources(settings) {
return sources;
}
/**
* Build a single remote instance row element with URL input, name input, and remove button.
* @param {string} url - remote instance URL
* @param {string} name - remote instance display name
* @returns {HTMLDivElement}
*/
function _buildRemoteInstanceRow(url, name) {
var row = document.createElement('div');
row.className = 'settings-remote-row';
var urlInput = document.createElement('input');
urlInput.type = 'text';
urlInput.className = 'settings-remote-url';
urlInput.placeholder = 'http://192.168.1.x:8000';
urlInput.value = url || '';
var nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.className = 'settings-remote-name';
nameInput.placeholder = 'Device name';
nameInput.value = name || '';
var removeBtn = document.createElement('button');
removeBtn.className = 'settings-remote-remove';
removeBtn.textContent = '\u00d7';
removeBtn.setAttribute('aria-label', 'Remove remote instance');
row.appendChild(urlInput);
row.appendChild(nameInput);
row.appendChild(removeBtn);
return row;
}
/**
* Read remote instance rows from the DOM and save to server settings.
* Rebuilds _sources after saving so polling updates immediately.
*/
function _saveRemoteInstances() {
var container = $('setting-remote-instances');
if (!container) return;
var instances = [];
container.querySelectorAll('.settings-remote-row').forEach(function(row) {
var urlEl = row.querySelector('.settings-remote-url');
var nameEl = row.querySelector('.settings-remote-name');
var url = (urlEl && urlEl.value) ? urlEl.value.trim() : '';
var name = (nameEl && nameEl.value) ? nameEl.value.trim() : '';
if (url) {
instances.push({ url: url, name: name });
}
});
patchServerSetting('remote_instances', instances).then(function() {
_sources = buildSources(_serverSettings);
});
}
// ─── Settings dialog ──────────────────────────────────────────────────────────
/**
@@ -1403,6 +1454,22 @@ function openSettings() {
autoOpenEl.checked = ss && ss.auto_open !== undefined ? !!ss.auto_open : true;
}
// Device name
const deviceNameEl = $('setting-device-name');
if (deviceNameEl) {
deviceNameEl.value = (ss && ss.device_name) || '';
}
// Remote instances
const remoteInstancesEl = $('setting-remote-instances');
if (remoteInstancesEl) {
remoteInstancesEl.innerHTML = '';
var remotes = (ss && ss.remote_instances) || [];
remotes.forEach(function(r) {
remoteInstancesEl.appendChild(_buildRemoteInstanceRow(r.url || '', r.name || ''));
});
}
// New Session tab - populate template textarea
const templateEl = $('setting-template');
if (templateEl) {
@@ -1882,6 +1949,51 @@ function bindStaticEventListeners() {
patchServerSetting('new_session_template', NEW_SESSION_DEFAULT_TEMPLATE);
});
// Sessions tab — device name with 500ms debounce
var _deviceNameDebounceTimer;
on($('setting-device-name'), 'input', function() {
clearTimeout(_deviceNameDebounceTimer);
var val = this.value;
_deviceNameDebounceTimer = setTimeout(function() {
patchServerSetting('device_name', val).then(function() {
_sources = buildSources(_serverSettings);
});
}, 500);
});
// Sessions tab — add remote instance button
on($('add-remote-instance-btn'), 'click', function() {
var container = $('setting-remote-instances');
if (container) {
container.appendChild(_buildRemoteInstanceRow('', ''));
}
});
// Sessions tab — delegated remove handler on remote instances container
var remoteInstancesContainer = $('setting-remote-instances');
if (remoteInstancesContainer) {
remoteInstancesContainer.addEventListener('click', function(e) {
var removeBtn = e.target.closest && e.target.closest('.settings-remote-remove');
if (!removeBtn) return;
var row = removeBtn.closest('.settings-remote-row');
if (row) {
row.remove();
_saveRemoteInstances();
}
});
// Delegated input save with debounce for remote instance URL/name fields
var _remoteDebounceTimer;
remoteInstancesContainer.addEventListener('input', function(e) {
var input = e.target.closest && e.target.closest('.settings-remote-url, .settings-remote-name');
if (!input) return;
clearTimeout(_remoteDebounceTimer);
_remoteDebounceTimer = setTimeout(function() {
_saveRemoteInstances();
}, 500);
});
}
// Filter bar — delegated click handler (pills are re-rendered each poll)
var filterBarEl = $('filter-bar');
if (filterBarEl) {
+9
View File
@@ -164,6 +164,15 @@
<label class="settings-label" for="setting-auto-open">Auto-open created sessions</label>
<input type="checkbox" id="setting-auto-open" class="settings-checkbox" checked />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-device-name">Device Name</label>
<input type="text" id="setting-device-name" class="settings-input" placeholder="This device" />
</div>
<div class="settings-field settings-field--column">
<label class="settings-label">Remote Instances</label>
<div id="setting-remote-instances" class="settings-remote-list"></div>
<button id="add-remote-instance-btn" class="settings-action-btn">+ Add Remote</button>
</div>
</div>
<div class="settings-panel hidden" data-tab="notifications">
<div class="settings-field">
+83
View File
@@ -1451,3 +1451,86 @@ body {
display: none;
}
}
/* ============================================================
Remote Instances UI (task-15)
============================================================ */
.settings-input {
flex: 1;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 13px;
padding: 5px 8px;
outline: none;
transition: border-color 0.15s;
}
.settings-input:focus {
border-color: var(--accent);
}
.settings-remote-list {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.settings-remote-row {
display: flex;
gap: 6px;
align-items: center;
}
.settings-remote-url {
flex: 2;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 13px;
padding: 5px 8px;
outline: none;
transition: border-color 0.15s;
}
.settings-remote-url:focus {
border-color: var(--accent);
}
.settings-remote-name {
flex: 1;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 13px;
padding: 5px 8px;
outline: none;
transition: border-color 0.15s;
}
.settings-remote-name:focus {
border-color: var(--accent);
}
.settings-remote-remove {
flex-shrink: 0;
background: transparent;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-muted);
cursor: pointer;
font-size: 16px;
line-height: 1;
padding: 4px 8px;
transition: color 0.15s, border-color 0.15s;
}
.settings-remote-remove:hover {
border-color: var(--accent);
color: var(--text);
}