mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
fix(cookbook): preserve same-host ssh profile selection (#3373)
* fix(cookbook): preserve same-host ssh profile selection * fix(cookbook): resolve same-host ssh profiles in running tab and port lookups
This commit is contained in:
+20
-12
@@ -18,6 +18,8 @@ import {
|
|||||||
_lastCacheHost,
|
_lastCacheHost,
|
||||||
_setLastCacheHost,
|
_setLastCacheHost,
|
||||||
_serverByVal,
|
_serverByVal,
|
||||||
|
_serverKey,
|
||||||
|
_currentServerValue,
|
||||||
_shellQuote,
|
_shellQuote,
|
||||||
_MODELDIR_CHECK_ON,
|
_MODELDIR_CHECK_ON,
|
||||||
_MODELDIR_CHECK_OFF,
|
_MODELDIR_CHECK_OFF,
|
||||||
@@ -358,6 +360,7 @@ function _scanSig() {
|
|||||||
const tc = document.getElementById('hwfit-gpu-toggles');
|
const tc = document.getElementById('hwfit-gpu-toggles');
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
h: _envState.remoteHost || '',
|
h: _envState.remoteHost || '',
|
||||||
|
hk: _currentServerValue(),
|
||||||
u: document.getElementById('hwfit-usecase')?.value || '',
|
u: document.getElementById('hwfit-usecase')?.value || '',
|
||||||
s: document.getElementById('hwfit-search')?.value?.trim() || '',
|
s: document.getElementById('hwfit-search')?.value?.trim() || '',
|
||||||
o: sortEl?.value || 'score',
|
o: sortEl?.value || 'score',
|
||||||
@@ -467,9 +470,10 @@ export async function _hwfitFetch(fresh = false) {
|
|||||||
_hwfitCache = null; // no instant paint — clear until the fetch returns
|
_hwfitCache = null; // no instant paint — clear until the fetch returns
|
||||||
}
|
}
|
||||||
// Only fetch cached model IDs when server changes, not on every search/sort
|
// Only fetch cached model IDs when server changes, not on every search/sort
|
||||||
if (!_cachedModelIds || _lastCacheHost() !== remoteHost) {
|
const remoteKey = _currentServerValue();
|
||||||
_setLastCacheHost(remoteHost);
|
if (!_cachedModelIds || _lastCacheHost() !== remoteKey) {
|
||||||
const _cacheSrv = _envState.servers.find(s => s.host === remoteHost);
|
_setLastCacheHost(remoteKey);
|
||||||
|
const _cacheSrv = _serverByVal(_envState.remoteServerKey || remoteHost);
|
||||||
const _cachePort = _cacheSrv?.port || '';
|
const _cachePort = _cacheSrv?.port || '';
|
||||||
const _cacheParams = new URLSearchParams({ host: remoteHost }); if (_cachePort) _cacheParams.set('ssh_port', _cachePort); if (_cacheSrv?.platform) _cacheParams.set('platform', _cacheSrv.platform);
|
const _cacheParams = new URLSearchParams({ host: remoteHost }); if (_cachePort) _cacheParams.set('ssh_port', _cachePort); if (_cacheSrv?.platform) _cacheParams.set('platform', _cacheSrv.platform);
|
||||||
fetch(`/api/model/cached?${_cacheParams}`, { credentials: 'same-origin' })
|
fetch(`/api/model/cached?${_cacheParams}`, { credentials: 'same-origin' })
|
||||||
@@ -510,7 +514,7 @@ export async function _hwfitFetch(fresh = false) {
|
|||||||
if (search) params.set('search', search);
|
if (search) params.set('search', search);
|
||||||
if (remoteHost) {
|
if (remoteHost) {
|
||||||
params.set('host', remoteHost);
|
params.set('host', remoteHost);
|
||||||
const _srv = _envState.servers.find(s => s.host === remoteHost);
|
const _srv = _serverByVal(_envState.remoteServerKey || remoteHost);
|
||||||
const _hp = _srv?.port || '';
|
const _hp = _srv?.port || '';
|
||||||
if (_hp) params.set('ssh_port', _hp);
|
if (_hp) params.set('ssh_port', _hp);
|
||||||
if (_srv?.platform) params.set('platform', _srv.platform);
|
if (_srv?.platform) params.set('platform', _srv.platform);
|
||||||
@@ -1024,11 +1028,13 @@ function _syncHostFromScanDropdown() {
|
|||||||
let host = '';
|
let host = '';
|
||||||
if (ss.value === 'local') {
|
if (ss.value === 'local') {
|
||||||
_envState.remoteHost = '';
|
_envState.remoteHost = '';
|
||||||
|
_envState.remoteServerKey = '';
|
||||||
} else {
|
} else {
|
||||||
const s = _serverByVal(ss.value);
|
const s = _serverByVal(ss.value);
|
||||||
if (s) {
|
if (s) {
|
||||||
host = s.host;
|
host = s.host;
|
||||||
_envState.remoteHost = s.host;
|
_envState.remoteHost = s.host;
|
||||||
|
_envState.remoteServerKey = _serverKey(s);
|
||||||
_envState.env = s.env;
|
_envState.env = s.env;
|
||||||
_envState.envPath = s.envPath;
|
_envState.envPath = s.envPath;
|
||||||
_envState.platform = s.platform || '';
|
_envState.platform = s.platform || '';
|
||||||
@@ -1209,7 +1215,7 @@ export function _expandModelRow(row, modelData) {
|
|||||||
// Launch via serve API. Field names must match the backend ServeRequest
|
// Launch via serve API. Field names must match the backend ServeRequest
|
||||||
// schema (repo_id + cmd) — sending `command`/`model` failed Pydantic
|
// schema (repo_id + cmd) — sending `command`/`model` failed Pydantic
|
||||||
// validation (422), which is why Run silently did nothing.
|
// validation (422), which is why Run silently did nothing.
|
||||||
const _srv = (_envState.servers || []).find(s => s.host === host);
|
const _srv = _serverByVal(_envState.remoteServerKey || host);
|
||||||
const payload = {
|
const payload = {
|
||||||
repo_id: modelData.name,
|
repo_id: modelData.name,
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
@@ -1428,7 +1434,7 @@ export function _hwfitInit() {
|
|||||||
// dropdown still showed odysseus. The user's selection must only change via
|
// dropdown still showed odysseus. The user's selection must only change via
|
||||||
// an explicit dropdown pick. Here we just refresh env/path if we can match
|
// an explicit dropdown pick. Here we just refresh env/path if we can match
|
||||||
// the current host; otherwise leave remoteHost untouched.
|
// the current host; otherwise leave remoteHost untouched.
|
||||||
const sel = _envState.servers.find(s => s.host === _envState.remoteHost);
|
const sel = _serverByVal(_envState.remoteServerKey || _envState.remoteHost);
|
||||||
if (sel) { _envState.env = sel.env; _envState.envPath = sel.envPath; }
|
if (sel) { _envState.env = sel.env; _envState.envPath = sel.envPath; }
|
||||||
_persistEnvState();
|
_persistEnvState();
|
||||||
}
|
}
|
||||||
@@ -1604,15 +1610,16 @@ export function _hwfitInit() {
|
|||||||
// (inline — _applyServerSelection lives in cookbook.js and isn't imported here).
|
// (inline — _applyServerSelection lives in cookbook.js and isn't imported here).
|
||||||
const _dk = _envState.defaultServer;
|
const _dk = _envState.defaultServer;
|
||||||
if (_dk) {
|
if (_dk) {
|
||||||
if (_dk === 'local') { _envState.remoteHost = ''; _envState.env = 'none'; _envState.envPath = ''; _envState.platform = ''; }
|
if (_dk === 'local') { _envState.remoteHost = ''; _envState.remoteServerKey = ''; _envState.env = 'none'; _envState.envPath = ''; _envState.platform = ''; }
|
||||||
else { const _s = (_envState.servers || []).find(x => x.host === _dk); if (_s) { _envState.remoteHost = _s.host; _envState.env = _s.env || 'none'; _envState.envPath = _s.envPath || ''; _envState.platform = _s.platform || ''; } }
|
else { const _s = _serverByVal(_dk); if (_s) { _envState.remoteHost = _s.host; _envState.remoteServerKey = _serverKey(_s); _envState.env = _s.env || 'none'; _envState.envPath = _s.envPath || ''; _envState.platform = _s.platform || ''; } }
|
||||||
_persistEnvState();
|
_persistEnvState();
|
||||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||||
if (sel && sel.tagName === 'SELECT') sel.value = _envState.remoteHost || 'local';
|
if (sel && sel.tagName === 'SELECT') sel.value = _currentServerValue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const defaultSrv = _serverByVal(_envState.defaultServer);
|
||||||
uiModule.showToast(_envState.defaultServer
|
uiModule.showToast(_envState.defaultServer
|
||||||
? 'Default server: ' + (_envState.defaultServer === 'local' ? 'Local' : _envState.defaultServer)
|
? 'Default server: ' + (_envState.defaultServer === 'local' ? 'Local' : (defaultSrv?.name || defaultSrv?.host || 'selected server'))
|
||||||
: 'Default server cleared');
|
: 'Default server cleared');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1866,12 +1873,14 @@ export function _hwfitInit() {
|
|||||||
const val = serverSelect.value;
|
const val = serverSelect.value;
|
||||||
if (val === 'local') {
|
if (val === 'local') {
|
||||||
_envState.remoteHost = '';
|
_envState.remoteHost = '';
|
||||||
|
_envState.remoteServerKey = '';
|
||||||
_envState.env = 'none';
|
_envState.env = 'none';
|
||||||
_envState.envPath = '';
|
_envState.envPath = '';
|
||||||
} else {
|
} else {
|
||||||
const s = _serverByVal(val);
|
const s = _serverByVal(val);
|
||||||
if (s) {
|
if (s) {
|
||||||
_envState.remoteHost = s.host;
|
_envState.remoteHost = s.host;
|
||||||
|
_envState.remoteServerKey = _serverKey(s);
|
||||||
_envState.env = s.env;
|
_envState.env = s.env;
|
||||||
_envState.envPath = s.envPath;
|
_envState.envPath = s.envPath;
|
||||||
}
|
}
|
||||||
@@ -1881,10 +1890,9 @@ export function _hwfitInit() {
|
|||||||
// download-input button reads #hwfit-dl-server *directly*, so without this
|
// download-input button reads #hwfit-dl-server *directly*, so without this
|
||||||
// it kept its old value and downloads went to the wrong host even
|
// it kept its old value and downloads went to the wrong host even
|
||||||
// though the scan here correctly switched to the selected server.
|
// though the scan here correctly switched to the selected server.
|
||||||
// Option values are host strings now ('local' for the local box).
|
|
||||||
document.querySelectorAll('#hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
document.querySelectorAll('#hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||||
if (!sel || sel.tagName !== 'SELECT') return;
|
if (!sel || sel.tagName !== 'SELECT') return;
|
||||||
sel.value = _envState.remoteHost || 'local';
|
sel.value = _currentServerValue();
|
||||||
});
|
});
|
||||||
_hwfitCache = null;
|
_hwfitCache = null;
|
||||||
// Reset GPU-toggle state (no flicker) so the new server's hardware re-renders.
|
// Reset GPU-toggle state (no flicker) so the new server's hardware re-renders.
|
||||||
|
|||||||
+67
-25
@@ -72,7 +72,7 @@ function _platformIcon(platform) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export let _envState = { env: 'none', envPath: '', hfToken: '', hfTokenConfigured: false, hfTokenMasked: '', gpus: '', remoteHost: '', servers: [], modelPaths: [], platform: '', defaultServer: '' };
|
export let _envState = { env: 'none', envPath: '', hfToken: '', hfTokenConfigured: false, hfTokenMasked: '', gpus: '', remoteHost: '', remoteServerKey: '', servers: [], modelPaths: [], platform: '', defaultServer: '' };
|
||||||
let _lastCacheHostVal = null;
|
let _lastCacheHostVal = null;
|
||||||
let _cookbookOpeningSpinners = [];
|
let _cookbookOpeningSpinners = [];
|
||||||
export function _lastCacheHost() { return _lastCacheHostVal; }
|
export function _lastCacheHost() { return _lastCacheHostVal; }
|
||||||
@@ -114,18 +114,44 @@ function _setCookbookOpening(on) {
|
|||||||
// True for the local server entry (empty / "local" / "localhost" host).
|
// True for the local server entry (empty / "local" / "localhost" host).
|
||||||
function _isLocalEntry(s) { return !s || !s.host || s.host === 'local' || s.host.toLowerCase() === 'localhost'; }
|
function _isLocalEntry(s) { return !s || !s.host || s.host === 'local' || s.host.toLowerCase() === 'localhost'; }
|
||||||
|
|
||||||
// Resolve a dropdown option value to a server entry. Option values are the
|
// Resolve a dropdown option value to a server entry. New option values are
|
||||||
// stable HOST string ('local' for the local box) — NOT array indices — because
|
// stable per-profile keys, so same-host SSH profiles stay distinguishable.
|
||||||
// `_envState.servers` gets deduped/reordered, which made index-based selection
|
// Host strings and numeric indices remain accepted for stale saved state.
|
||||||
// silently resolve to the wrong (or local) server. Accepts a numeric index too
|
export function _serverKey(s) {
|
||||||
// for backwards-compat with any stale value.
|
if (_isLocalEntry(s)) return 'local';
|
||||||
|
return 'srv:' + [
|
||||||
|
s?.name || '',
|
||||||
|
s?.host || '',
|
||||||
|
s?.port || '',
|
||||||
|
s?.envPath || '',
|
||||||
|
s?.platform || '',
|
||||||
|
].map(v => encodeURIComponent(String(v).trim())).join('|');
|
||||||
|
}
|
||||||
|
|
||||||
function _serverByVal(val) {
|
function _serverByVal(val) {
|
||||||
if (val == null || val === 'local' || val === '') return null;
|
if (val == null || val === 'local' || val === '') return null;
|
||||||
let s = _envState.servers.find(x => x.host === val);
|
const raw = String(val);
|
||||||
|
let s = _envState.servers.find(x => _serverKey(x) === raw);
|
||||||
|
if (!s) s = _envState.servers.find(x => x.host === raw);
|
||||||
if (!s && /^\d+$/.test(String(val))) s = _envState.servers[parseInt(val)];
|
if (!s && /^\d+$/.test(String(val))) s = _envState.servers[parseInt(val)];
|
||||||
return s || null;
|
return s || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function _selectedServer() {
|
||||||
|
if (_envState.remoteServerKey) {
|
||||||
|
const keyed = _serverByVal(_envState.remoteServerKey);
|
||||||
|
if (keyed) return keyed;
|
||||||
|
}
|
||||||
|
if (_envState.remoteHost) return _envState.servers.find(s => s.host === _envState.remoteHost) || null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _currentServerValue() {
|
||||||
|
const selected = _selectedServer();
|
||||||
|
if (selected) return _serverKey(selected);
|
||||||
|
return _envState.remoteHost || 'local';
|
||||||
|
}
|
||||||
|
|
||||||
function _buildServerOpts(excludeLocal = false) {
|
function _buildServerOpts(excludeLocal = false) {
|
||||||
// The local server is ALWAYS represented by the synthetic value="local" option
|
// The local server is ALWAYS represented by the synthetic value="local" option
|
||||||
// (showing its custom name from the "server name" feature). We must therefore
|
// (showing its custom name from the "server name" feature). We must therefore
|
||||||
@@ -134,13 +160,20 @@ function _buildServerOpts(excludeLocal = false) {
|
|||||||
const _localSrv = _localIdx >= 0 ? _envState.servers[_localIdx] : null;
|
const _localSrv = _localIdx >= 0 ? _envState.servers[_localIdx] : null;
|
||||||
const _localLabel = (_localSrv && _localSrv.name) ? _localSrv.name : 'Local';
|
const _localLabel = (_localSrv && _localSrv.name) ? _localSrv.name : 'Local';
|
||||||
let html = `<option value="local"${!_envState.remoteHost ? ' selected' : ''}>${esc(_localLabel)}</option>`;
|
let html = `<option value="local"${!_envState.remoteHost ? ' selected' : ''}>${esc(_localLabel)}</option>`;
|
||||||
|
const selectedKey = _envState.remoteServerKey || '';
|
||||||
|
let legacyHostSelected = false;
|
||||||
for (let i = 0; i < _envState.servers.length; i++) {
|
for (let i = 0; i < _envState.servers.length; i++) {
|
||||||
const s = _envState.servers[i];
|
const s = _envState.servers[i];
|
||||||
if (i === _localIdx) continue; // already the synthetic "local" option
|
if (i === _localIdx) continue; // already the synthetic "local" option
|
||||||
if (excludeLocal && _isLocalEntry(s)) continue;
|
if (excludeLocal && _isLocalEntry(s)) continue;
|
||||||
const label = s.name || s.host || `Server ${i + 1}`;
|
const label = s.name || s.host || `Server ${i + 1}`;
|
||||||
const selected = _envState.remoteHost === s.host ? ' selected' : '';
|
const value = _serverKey(s);
|
||||||
html += `<option value="${esc(s.host)}"${selected}>${esc(label)}</option>`;
|
let selected = selectedKey ? value === selectedKey : false;
|
||||||
|
if (!selectedKey && _envState.remoteHost === s.host && !legacyHostSelected) {
|
||||||
|
selected = true;
|
||||||
|
legacyHostSelected = true;
|
||||||
|
}
|
||||||
|
html += `<option value="${esc(value)}"${selected ? ' selected' : ''}>${esc(label)}</option>`;
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -154,8 +187,9 @@ export function _sshCmd(host, cmd, port) {
|
|||||||
/** Get SSH port for a given host (or task object) */
|
/** Get SSH port for a given host (or task object) */
|
||||||
function _getPort(hostOrTask) {
|
function _getPort(hostOrTask) {
|
||||||
if (!hostOrTask) return '';
|
if (!hostOrTask) return '';
|
||||||
if (typeof hostOrTask === 'object') return hostOrTask.sshPort || _getPort(hostOrTask.remoteHost);
|
if (typeof hostOrTask === 'object') return hostOrTask.sshPort || _getPort(hostOrTask.remoteServerKey || hostOrTask.remoteHost);
|
||||||
const srv = _envState.servers.find(s => s.host === hostOrTask);
|
const selected = hostOrTask === _envState.remoteHost ? _selectedServer() : null;
|
||||||
|
const srv = selected || _serverByVal(hostOrTask);
|
||||||
return srv?.port || '';
|
return srv?.port || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +218,10 @@ export function _getPlatform(hostOrTask) {
|
|||||||
if (!h || h === 'local') {
|
if (!h || h === 'local') {
|
||||||
return hostOrTask.platform || localPlatform();
|
return hostOrTask.platform || localPlatform();
|
||||||
}
|
}
|
||||||
return hostOrTask.platform || _getPlatform(h);
|
return hostOrTask.platform || _getPlatform(hostOrTask.remoteServerKey || h);
|
||||||
}
|
}
|
||||||
const srv = _envState.servers.find(s => s.host === hostOrTask);
|
const selected = hostOrTask === _envState.remoteHost ? _selectedServer() : null;
|
||||||
|
const srv = selected || _serverByVal(hostOrTask);
|
||||||
return srv?.platform || '';
|
return srv?.platform || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,6 +964,7 @@ async function _fetchDependencies() {
|
|||||||
function _applyServerSelection(val) {
|
function _applyServerSelection(val) {
|
||||||
if (val === 'local') {
|
if (val === 'local') {
|
||||||
_envState.remoteHost = '';
|
_envState.remoteHost = '';
|
||||||
|
_envState.remoteServerKey = '';
|
||||||
_envState.env = 'none';
|
_envState.env = 'none';
|
||||||
_envState.envPath = '';
|
_envState.envPath = '';
|
||||||
_envState.platform = '';
|
_envState.platform = '';
|
||||||
@@ -936,6 +972,7 @@ function _applyServerSelection(val) {
|
|||||||
const s = _serverByVal(val);
|
const s = _serverByVal(val);
|
||||||
if (s) {
|
if (s) {
|
||||||
_envState.remoteHost = s.host;
|
_envState.remoteHost = s.host;
|
||||||
|
_envState.remoteServerKey = _serverKey(s);
|
||||||
_envState.env = s.env || 'none';
|
_envState.env = s.env || 'none';
|
||||||
_envState.envPath = s.envPath || '';
|
_envState.envPath = s.envPath || '';
|
||||||
_envState.platform = s.platform || '';
|
_envState.platform = s.platform || '';
|
||||||
@@ -946,10 +983,9 @@ function _applyServerSelection(val) {
|
|||||||
// bug: the Download/Cache/Deps dropdowns set the host but never saved it, so
|
// bug: the Download/Cache/Deps dropdowns set the host but never saved it, so
|
||||||
// it silently reverted and downloads/scans hit the wrong server).
|
// it silently reverted and downloads/scans hit the wrong server).
|
||||||
_persistEnvState();
|
_persistEnvState();
|
||||||
const _want = _envState.remoteHost || 'local';
|
const _want = _currentServerValue();
|
||||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||||
if (!sel || sel.tagName !== 'SELECT') return;
|
if (!sel || sel.tagName !== 'SELECT') return;
|
||||||
// Option values are host strings now ('local' for the local box).
|
|
||||||
sel.value = _want;
|
sel.value = _want;
|
||||||
// If the host isn't among this select's current options (stale options after
|
// If the host isn't among this select's current options (stale options after
|
||||||
// the server list changed), the browser leaves the box BLANK/grey even though
|
// the server list changed), the browser leaves the box BLANK/grey even though
|
||||||
@@ -957,7 +993,7 @@ function _applyServerSelection(val) {
|
|||||||
// re-apply; fall back to 'local' only if it's genuinely gone.
|
// re-apply; fall back to 'local' only if it's genuinely gone.
|
||||||
if (sel.selectedIndex < 0) {
|
if (sel.selectedIndex < 0) {
|
||||||
sel.innerHTML = _buildServerOpts(sel.id === 'hwfit-dl-server');
|
sel.innerHTML = _buildServerOpts(sel.id === 'hwfit-dl-server');
|
||||||
sel.value = _want;
|
sel.value = _currentServerValue();
|
||||||
if (sel.selectedIndex < 0) sel.value = 'local';
|
if (sel.selectedIndex < 0) sel.value = 'local';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1045,11 +1081,13 @@ function _wireTabEvents(body) {
|
|||||||
const remotes = servers.filter(s => !_isLocalEntry(s));
|
const remotes = servers.filter(s => !_isLocalEntry(s));
|
||||||
if (remotes.length === 1) {
|
if (remotes.length === 1) {
|
||||||
_envState.remoteHost = remotes[0].host;
|
_envState.remoteHost = remotes[0].host;
|
||||||
|
_envState.remoteServerKey = _serverKey(remotes[0]);
|
||||||
_envState.env = remotes[0].env || 'none';
|
_envState.env = remotes[0].env || 'none';
|
||||||
_envState.envPath = remotes[0].envPath || '';
|
_envState.envPath = remotes[0].envPath || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const activeSrv = servers.find(s => s.host === _envState.remoteHost);
|
const activeSrv = _selectedServer();
|
||||||
|
if (activeSrv) _envState.remoteServerKey = _serverKey(activeSrv);
|
||||||
_envState.platform = activeSrv?.platform || '';
|
_envState.platform = activeSrv?.platform || '';
|
||||||
localStorage.setItem('cookbook-last-state', JSON.stringify(_envStateForStorage()));
|
localStorage.setItem('cookbook-last-state', JSON.stringify(_envStateForStorage()));
|
||||||
_saveTasks(_loadTasks());
|
_saveTasks(_loadTasks());
|
||||||
@@ -1057,7 +1095,7 @@ function _wireTabEvents(body) {
|
|||||||
// UI matches the resolved host. Done in a microtask so the dropdowns
|
// UI matches the resolved host. Done in a microtask so the dropdowns
|
||||||
// exist by the time we set their .value.
|
// exist by the time we set their .value.
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const _want = _envState.remoteHost || 'local';
|
const _want = _currentServerValue();
|
||||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||||
if (sel && sel.tagName === 'SELECT') sel.value = _want;
|
if (sel && sel.tagName === 'SELECT') sel.value = _want;
|
||||||
});
|
});
|
||||||
@@ -1345,7 +1383,7 @@ function _wireTabEvents(body) {
|
|||||||
if (srvVal !== 'local') {
|
if (srvVal !== 'local') {
|
||||||
host = _serverByVal(srvVal)?.host || '';
|
host = _serverByVal(srvVal)?.host || '';
|
||||||
}
|
}
|
||||||
const _hsrv = _envState.servers.find(sv => sv.host === host) || {};
|
const _hsrv = srvVal !== 'local' ? (_serverByVal(srvVal) || {}) : {};
|
||||||
let env = host ? (_hsrv.env || 'none') : _envState.env;
|
let env = host ? (_hsrv.env || 'none') : _envState.env;
|
||||||
let envPath = host ? (_hsrv.envPath || '') : _envState.envPath;
|
let envPath = host ? (_hsrv.envPath || '') : _envState.envPath;
|
||||||
const payload = { repo_id: repo };
|
const payload = { repo_id: repo };
|
||||||
@@ -1605,8 +1643,9 @@ export function _serverEntryHtml(s, i, defaultServer, forceRemote, isNew) {
|
|||||||
let html = '';
|
let html = '';
|
||||||
html += `<div class="cookbook-server-entry" data-idx="${i}" data-platform="${esc(s.platform || '')}">`;
|
html += `<div class="cookbook-server-entry" data-idx="${i}" data-platform="${esc(s.platform || '')}">`;
|
||||||
const _srvTitle = s.name || (isLocal ? 'Local' : (s.host || `Server ${i + 1}`));
|
const _srvTitle = s.name || (isLocal ? 'Local' : (s.host || `Server ${i + 1}`));
|
||||||
const _srvKey = isLocal ? 'local' : (s.host || '');
|
const _srvKey = isLocal ? 'local' : _serverKey(s);
|
||||||
const _isDefaultSrv = (defaultServer || '') === _srvKey;
|
const _legacyDefault = !String(defaultServer || '').startsWith('srv:') && !isLocal && (defaultServer || '') === (s.host || '');
|
||||||
|
const _isDefaultSrv = (defaultServer || '') === _srvKey || _legacyDefault;
|
||||||
const _pIco = _platformIcon(s.platform);
|
const _pIco = _platformIcon(s.platform);
|
||||||
const _keyBtn = `<button class="cookbook-server-key-btn" title="Set up SSH key for this server" style="height:22px;box-sizing:border-box;display:inline-flex;align-items:center;position:relative;top:-2px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px;flex-shrink:0;"><circle cx="7.5" cy="15.5" r="5.5"/><path d="M12 11l8-8"/><path d="M17 6l3 3"/></svg>Key</button>`;
|
const _keyBtn = `<button class="cookbook-server-key-btn" title="Set up SSH key for this server" style="height:22px;box-sizing:border-box;display:inline-flex;align-items:center;position:relative;top:-2px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px;flex-shrink:0;"><circle cx="7.5" cy="15.5" r="5.5"/><path d="M12 11l8-8"/><path d="M17 6l3 3"/></svg>Key</button>`;
|
||||||
const _checkBtn = `<button class="cookbook-server-check-btn" title="Check SSH connection" style="height:22px;box-sizing:border-box;display:inline-flex;align-items:center;position:relative;top:-2px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px;flex-shrink:0;"><polyline points="20 6 9 17 4 12"/></svg>Check</button>`;
|
const _checkBtn = `<button class="cookbook-server-check-btn" title="Check SSH connection" style="height:22px;box-sizing:border-box;display:inline-flex;align-items:center;position:relative;top:-2px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px;flex-shrink:0;"><polyline points="20 6 9 17 4 12"/></svg>Check</button>`;
|
||||||
@@ -1846,7 +1885,7 @@ function _renderRecipes() {
|
|||||||
html += '<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:2px;">';
|
html += '<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:2px;">';
|
||||||
html += '<h2 style="margin:0;padding:0;line-height:1;">Serve <span id="serve-stats" class="memory-count" style="font-size:0.6em;opacity:0.6;font-weight:normal"></span></h2>';
|
html += '<h2 style="margin:0;padding:0;line-height:1;">Serve <span id="serve-stats" class="memory-count" style="font-size:0.6em;opacity:0.6;font-weight:normal"></span></h2>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
const _selSrv = _es.servers.find(s => s.host === _es.remoteHost) || _es.servers[0] || {};
|
const _selSrv = _selectedServer() || _es.servers[0] || {};
|
||||||
const _srvDirs = (Array.isArray(_selSrv.modelDirs) ? _selSrv.modelDirs : [_selSrv.modelDir || '~/.cache/huggingface/hub']).map(d => d.replaceAll('✕', '').replaceAll('✖', '').trim()).filter(Boolean);
|
const _srvDirs = (Array.isArray(_selSrv.modelDirs) ? _selSrv.modelDirs : [_selSrv.modelDir || '~/.cache/huggingface/hub']).map(d => d.replaceAll('✕', '').replaceAll('✖', '').trim()).filter(Boolean);
|
||||||
html += '<div class="cookbook-serve-dirs" style="margin-top:6px;">';
|
html += '<div class="cookbook-serve-dirs" style="margin-top:6px;">';
|
||||||
html += _srvDirs.map(d => `<span class="cookbook-serve-dir-pill">${esc(d)}</span>`).join('');
|
html += _srvDirs.map(d => `<span class="cookbook-serve-dir-pill">${esc(d)}</span>`).join('');
|
||||||
@@ -2052,10 +2091,10 @@ export async function open(opts) {
|
|||||||
if (_envState.defaultServer) {
|
if (_envState.defaultServer) {
|
||||||
const _dk = _envState.defaultServer;
|
const _dk = _envState.defaultServer;
|
||||||
if (_dk === 'local') {
|
if (_dk === 'local') {
|
||||||
_envState.remoteHost = ''; _envState.env = 'none'; _envState.envPath = ''; _envState.platform = '';
|
_envState.remoteHost = ''; _envState.remoteServerKey = ''; _envState.env = 'none'; _envState.envPath = ''; _envState.platform = '';
|
||||||
} else {
|
} else {
|
||||||
const _ds = (_envState.servers || []).find(s => s.host === _dk);
|
const _ds = _serverByVal(_dk);
|
||||||
if (_ds) { _envState.remoteHost = _ds.host; _envState.env = _ds.env || 'none'; _envState.envPath = _ds.envPath || ''; _envState.platform = _ds.platform || ''; }
|
if (_ds) { _envState.remoteHost = _ds.host; _envState.remoteServerKey = _serverKey(_ds); _envState.env = _ds.env || 'none'; _envState.envPath = _ds.envPath || ''; _envState.platform = _ds.platform || ''; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Re-render on every open AFTER sync so the freshly-fetched state (servers,
|
// Re-render on every open AFTER sync so the freshly-fetched state (servers,
|
||||||
@@ -2178,6 +2217,9 @@ const shared = {
|
|||||||
_getPort,
|
_getPort,
|
||||||
_sshPrefix,
|
_sshPrefix,
|
||||||
_getPlatform,
|
_getPlatform,
|
||||||
|
_serverByVal,
|
||||||
|
_selectedServer,
|
||||||
|
_currentServerValue,
|
||||||
_isWindows,
|
_isWindows,
|
||||||
_isMetal,
|
_isMetal,
|
||||||
_buildEnvPrefix,
|
_buildEnvPrefix,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ let _envState;
|
|||||||
let _sshCmd;
|
let _sshCmd;
|
||||||
let _getPort;
|
let _getPort;
|
||||||
let _getPlatform;
|
let _getPlatform;
|
||||||
|
let _serverByVal;
|
||||||
let _isWindows;
|
let _isWindows;
|
||||||
let _buildEnvPrefix;
|
let _buildEnvPrefix;
|
||||||
let _buildServeCmd;
|
let _buildServeCmd;
|
||||||
@@ -118,7 +119,7 @@ export function _buildDownloadCmd(model, backend) {
|
|||||||
const includeArg = includePattern ? `, allow_patterns=["${includePattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]` : '';
|
const includeArg = includePattern ? `, allow_patterns=["${includePattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]` : '';
|
||||||
// Reflect the server's download target in the preview (matches the real
|
// Reflect the server's download target in the preview (matches the real
|
||||||
// download path built server-side). '' = default HF cache.
|
// download path built server-side). '' = default HF cache.
|
||||||
const _dlDir = (_envState.servers.find(s => s.host === (_envState.remoteHost || '')) || {}).downloadDir || '';
|
const _dlDir = (_serverByVal?.(_envState.remoteServerKey || _envState.remoteHost || '') || {}).downloadDir || '';
|
||||||
const _localDirArg = _dlDir ? `, local_dir=os.path.expanduser('${_dlDir.replace(/\/$/, '')}/${repo.split('/').pop()}')` : '';
|
const _localDirArg = _dlDir ? `, local_dir=os.path.expanduser('${_dlDir.replace(/\/$/, '')}/${repo.split('/').pop()}')` : '';
|
||||||
const _py = _isWindows() ? 'python' : 'python3';
|
const _py = _isWindows() ? 'python' : 'python3';
|
||||||
cmd = `${_py} -u -c "
|
cmd = `${_py} -u -c "
|
||||||
@@ -475,10 +476,10 @@ export async function _runModelDownload(panel, model, backend, hostOverride) {
|
|||||||
// No explicit host passed: resolve from the visible server dropdown rather
|
// No explicit host passed: resolve from the visible server dropdown rather
|
||||||
// than _envState.remoteHost (unreliable — multiple state copies disagree).
|
// than _envState.remoteHost (unreliable — multiple state copies disagree).
|
||||||
const ssEl = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
|
const ssEl = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
|
||||||
// Dropdown values are host strings now ('local' for local); resolve by host
|
// Dropdown values are profile keys now ('local' for local); stale host
|
||||||
// (numeric fallback for any stale value).
|
// strings and numeric indices still resolve for backwards compatibility.
|
||||||
const _ssv = ssEl ? ssEl.value : null;
|
const _ssv = ssEl ? ssEl.value : null;
|
||||||
const _dsrv = (_ssv && _ssv !== 'local') ? (_envState.servers.find(s => s.host === _ssv) || _envState.servers[parseInt(_ssv)]) : null;
|
const _dsrv = (_ssv && _ssv !== 'local') ? (_serverByVal?.(_ssv) || _envState.servers[parseInt(_ssv)]) : null;
|
||||||
if (_dsrv) {
|
if (_dsrv) {
|
||||||
host = _dsrv.host;
|
host = _dsrv.host;
|
||||||
} else if (ssEl && ssEl.value === 'local') {
|
} else if (ssEl && ssEl.value === 'local') {
|
||||||
@@ -487,7 +488,7 @@ export async function _runModelDownload(panel, model, backend, hostOverride) {
|
|||||||
host = _envState.remoteHost || '';
|
host = _envState.remoteHost || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const srv = _envState.servers.find(s => s.host === host) || {};
|
const srv = _serverByVal?.(_envState.remoteServerKey || host) || {};
|
||||||
const env = host ? (srv.env || 'none') : (_envState.env || 'none');
|
const env = host ? (srv.env || 'none') : (_envState.env || 'none');
|
||||||
const envPath = host ? (srv.envPath || '') : (_envState.envPath || '');
|
const envPath = host ? (srv.envPath || '') : (_envState.envPath || '');
|
||||||
const platform = host ? (srv.platform || '') : (_envState.platform || '');
|
const platform = host ? (srv.platform || '') : (_envState.platform || '');
|
||||||
@@ -546,7 +547,8 @@ export async function _runModelDownload(panel, model, backend, hostOverride) {
|
|||||||
if (zombieCandidate) {
|
if (zombieCandidate) {
|
||||||
try {
|
try {
|
||||||
const _zh = zombieCandidate.remoteHost || '';
|
const _zh = zombieCandidate.remoteHost || '';
|
||||||
const _zPort = (_envState.servers || []).find(s => s.host === _zh)?.port;
|
const _zPort = (_serverByVal?.(_envState.remoteServerKey || _zh)
|
||||||
|
|| (_envState.servers || []).find(s => s.host === _zh) || {}).port;
|
||||||
const _sshPf = _zh ? `ssh ${_zPort && _zPort !== '22' ? `-p ${_zPort} ` : ''}${_zh} '` : '';
|
const _sshPf = _zh ? `ssh ${_zPort && _zPort !== '22' ? `-p ${_zPort} ` : ''}${_zh} '` : '';
|
||||||
const _sshSf = _zh ? `'` : '';
|
const _sshSf = _zh ? `'` : '';
|
||||||
const _probeCmd = `${_sshPf}tmux has-session -t ${zombieCandidate.sessionId} 2>/dev/null${_sshSf}`;
|
const _probeCmd = `${_sshPf}tmux has-session -t ${zombieCandidate.sessionId} 2>/dev/null${_sshSf}`;
|
||||||
@@ -615,6 +617,7 @@ export function initDownload(shared) {
|
|||||||
_sshCmd = shared._sshCmd;
|
_sshCmd = shared._sshCmd;
|
||||||
_getPort = shared._getPort;
|
_getPort = shared._getPort;
|
||||||
_getPlatform = shared._getPlatform;
|
_getPlatform = shared._getPlatform;
|
||||||
|
_serverByVal = shared._serverByVal;
|
||||||
_isWindows = shared._isWindows;
|
_isWindows = shared._isWindows;
|
||||||
_buildEnvPrefix = shared._buildEnvPrefix;
|
_buildEnvPrefix = shared._buildEnvPrefix;
|
||||||
_buildServeCmd = shared._buildServeCmd;
|
_buildServeCmd = shared._buildServeCmd;
|
||||||
|
|||||||
@@ -255,6 +255,8 @@ let _savePresets;
|
|||||||
let _copyText;
|
let _copyText;
|
||||||
let _persistEnvState;
|
let _persistEnvState;
|
||||||
let _refreshDependencies;
|
let _refreshDependencies;
|
||||||
|
let _serverByVal;
|
||||||
|
let _selectedServer;
|
||||||
let modelLogo;
|
let modelLogo;
|
||||||
let esc;
|
let esc;
|
||||||
let _detectBackend;
|
let _detectBackend;
|
||||||
@@ -1263,7 +1265,8 @@ async function _openServeEditForTask(task, cmdOverride, fieldOverrides = null) {
|
|||||||
// Switch the active server to the one this serve ran on (mirrors _openEdit).
|
// Switch the active server to the one this serve ran on (mirrors _openEdit).
|
||||||
const _tHost = task.remoteHost || '';
|
const _tHost = task.remoteHost || '';
|
||||||
_envState.remoteHost = _tHost;
|
_envState.remoteHost = _tHost;
|
||||||
const _tSrv = _envState.servers.find(s => s.host === _tHost);
|
const _tSrv = _serverByVal(_envState.remoteServerKey || _tHost)
|
||||||
|
|| _envState.servers.find(s => s.host === _tHost);
|
||||||
if (_tSrv) { _envState.env = _tSrv.env || 'none'; _envState.envPath = _tSrv.envPath || ''; _envState.platform = _tSrv.platform || ''; }
|
if (_tSrv) { _envState.env = _tSrv.env || 'none'; _envState.envPath = _tSrv.envPath || ''; _envState.platform = _tSrv.platform || ''; }
|
||||||
else if (!_tHost) { _envState.env = 'none'; _envState.envPath = ''; _envState.platform = ''; }
|
else if (!_tHost) { _envState.env = 'none'; _envState.envPath = ''; _envState.platform = ''; }
|
||||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||||
@@ -1473,7 +1476,8 @@ export async function _launchServeTask(shortName, repo, cmd, fields, hostOverrid
|
|||||||
// up that server's port/platform from the shared servers list. Only fall back
|
// up that server's port/platform from the shared servers list. Only fall back
|
||||||
// to _envState.remoteHost for legacy callers (diagnosis/pip-update).
|
// to _envState.remoteHost for legacy callers (diagnosis/pip-update).
|
||||||
const _host = (hostOverride !== undefined) ? (hostOverride || '') : (_envState.remoteHost || '');
|
const _host = (hostOverride !== undefined) ? (hostOverride || '') : (_envState.remoteHost || '');
|
||||||
const _hsrv = _envState.servers.find(s => s.host === _host) || {};
|
const _hsrv = _serverByVal(_envState.remoteServerKey || _host)
|
||||||
|
|| _envState.servers.find(s => s.host === _host) || {};
|
||||||
const _hplatform = _host ? (_hsrv.platform || '') : (_envState.platform || '');
|
const _hplatform = _host ? (_hsrv.platform || '') : (_envState.platform || '');
|
||||||
|
|
||||||
// Replace any serve already targeting this same host:port — you can't run two
|
// Replace any serve already targeting this same host:port — you can't run two
|
||||||
@@ -1700,7 +1704,8 @@ export function _renderRunningTab() {
|
|||||||
// Group tasks by server
|
// Group tasks by server
|
||||||
const _serverName = (host) => {
|
const _serverName = (host) => {
|
||||||
if (!host) return 'Local';
|
if (!host) return 'Local';
|
||||||
const srv = _envState.servers.find(s => s.host === host);
|
const srv = _serverByVal(_envState.remoteServerKey || host)
|
||||||
|
|| _envState.servers.find(s => s.host === host);
|
||||||
return srv?.name || host;
|
return srv?.name || host;
|
||||||
};
|
};
|
||||||
const serverGroups = {};
|
const serverGroups = {};
|
||||||
@@ -1971,7 +1976,8 @@ export function _renderRunningTab() {
|
|||||||
// Point the active server at the one it downloaded to.
|
// Point the active server at the one it downloaded to.
|
||||||
const _tHost = task.remoteHost || '';
|
const _tHost = task.remoteHost || '';
|
||||||
_envState.remoteHost = _tHost;
|
_envState.remoteHost = _tHost;
|
||||||
const _tSrv = _envState.servers.find(s => s.host === _tHost);
|
const _tSrv = _serverByVal(_envState.remoteServerKey || _tHost)
|
||||||
|
|| _envState.servers.find(s => s.host === _tHost);
|
||||||
if (_tSrv) { _envState.env = _tSrv.env || 'none'; _envState.envPath = _tSrv.envPath || ''; _envState.platform = _tSrv.platform || ''; }
|
if (_tSrv) { _envState.env = _tSrv.env || 'none'; _envState.envPath = _tSrv.envPath || ''; _envState.platform = _tSrv.platform || ''; }
|
||||||
else if (!_tHost) { _envState.env = 'none'; _envState.envPath = ''; _envState.platform = ''; }
|
else if (!_tHost) { _envState.env = 'none'; _envState.envPath = ''; _envState.platform = ''; }
|
||||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||||
@@ -3707,6 +3713,8 @@ export function initRunning(shared) {
|
|||||||
_copyText = shared._copyText;
|
_copyText = shared._copyText;
|
||||||
_persistEnvState = shared._persistEnvState;
|
_persistEnvState = shared._persistEnvState;
|
||||||
_refreshDependencies = shared._refreshDependencies;
|
_refreshDependencies = shared._refreshDependencies;
|
||||||
|
_serverByVal = shared._serverByVal;
|
||||||
|
_selectedServer = shared._selectedServer;
|
||||||
modelLogo = shared.modelLogo;
|
modelLogo = shared.modelLogo;
|
||||||
esc = shared.esc;
|
esc = shared.esc;
|
||||||
_detectBackend = shared._detectBackend;
|
_detectBackend = shared._detectBackend;
|
||||||
|
|||||||
+15
-12
@@ -14,6 +14,7 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
|||||||
let _envState;
|
let _envState;
|
||||||
let _sshCmd;
|
let _sshCmd;
|
||||||
let _getPort;
|
let _getPort;
|
||||||
|
let _serverByVal;
|
||||||
let _sshPrefix;
|
let _sshPrefix;
|
||||||
let _getPlatform;
|
let _getPlatform;
|
||||||
let _isWindows;
|
let _isWindows;
|
||||||
@@ -97,14 +98,14 @@ function _selectedServeTarget(panel) {
|
|||||||
const select = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
|
const select = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
|
||||||
const servers = Array.isArray(_envState.servers) ? _envState.servers : [];
|
const servers = Array.isArray(_envState.servers) ? _envState.servers : [];
|
||||||
let host = _envState.remoteHost || '';
|
let host = _envState.remoteHost || '';
|
||||||
let server = host ? servers.find(s => s.host === host) : null;
|
let server = host ? (_serverByVal?.(_envState.remoteServerKey || host) || servers.find(s => s.host === host)) : null;
|
||||||
if (select && select.value != null) {
|
if (select && select.value != null) {
|
||||||
if (select.value === 'local') {
|
if (select.value === 'local') {
|
||||||
host = '';
|
host = '';
|
||||||
server = servers.find(s => !s.host || s.host === 'local') || null;
|
server = servers.find(s => !s.host || s.host === 'local') || null;
|
||||||
} else {
|
} else {
|
||||||
const idx = /^\d+$/.test(String(select.value)) ? parseInt(select.value, 10) : -1;
|
const idx = /^\d+$/.test(String(select.value)) ? parseInt(select.value, 10) : -1;
|
||||||
server = servers.find(s => s.host === select.value) || (idx >= 0 ? servers[idx] : null) || null;
|
server = _serverByVal?.(select.value) || (idx >= 0 ? servers[idx] : null) || null;
|
||||||
host = server?.host || '';
|
host = server?.host || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,7 @@ function _selectedServeTarget(panel) {
|
|||||||
: (server?.name || 'local server');
|
: (server?.name || 'local server');
|
||||||
return {
|
return {
|
||||||
host,
|
host,
|
||||||
port: host ? (_getPort(host) || server?.port || '') : '',
|
port: host ? (server?.port || _getPort(host) || '') : '',
|
||||||
venv,
|
venv,
|
||||||
label,
|
label,
|
||||||
};
|
};
|
||||||
@@ -536,7 +537,7 @@ function _rerenderCachedModels() {
|
|||||||
// The venv set per-server in Settings (server.envPath). Used as the venv
|
// The venv set per-server in Settings (server.envPath). Used as the venv
|
||||||
// field default when the global active env path isn't carrying it, so a
|
// field default when the global active env path isn't carrying it, so a
|
||||||
// configured server venv shows up without re-typing it.
|
// configured server venv shows up without re-typing it.
|
||||||
const _selSrv = (_es.servers || []).find(s => s.host === (_es.remoteHost || '')) || {};
|
const _selSrv = _serverByVal?.(_es.remoteServerKey || _es.remoteHost || '') || {};
|
||||||
const _srvVenv = _selSrv.envPath || '';
|
const _srvVenv = _selSrv.envPath || '';
|
||||||
// Serve state schema: { _byRepo: { <repo>: {...} }, _lastUsed: {...} }.
|
// Serve state schema: { _byRepo: { <repo>: {...} }, _lastUsed: {...} }.
|
||||||
// Loading priority: this-repo's saved settings → last-used (from any
|
// Loading priority: this-repo's saved settings → last-used (from any
|
||||||
@@ -894,10 +895,11 @@ function _rerenderCachedModels() {
|
|||||||
if (!wrap) return;
|
if (!wrap) return;
|
||||||
try {
|
try {
|
||||||
const host = (_es.remoteHost || '').trim();
|
const host = (_es.remoteHost || '').trim();
|
||||||
|
const selected = _serverByVal?.(_es.remoteServerKey || host);
|
||||||
const params = new URLSearchParams({ model: repo });
|
const params = new URLSearchParams({ model: repo });
|
||||||
if (host) {
|
if (host) {
|
||||||
params.set('host', host);
|
params.set('host', host);
|
||||||
const _sp = (_es.servers || []).find(s => s.host === host)?.port;
|
const _sp = selected?.port;
|
||||||
if (_sp) params.set('ssh_port', _sp);
|
if (_sp) params.set('ssh_port', _sp);
|
||||||
}
|
}
|
||||||
// SERVE mode: this is a specific GGUF file already on disk, so its quant
|
// SERVE mode: this is a specific GGUF file already on disk, so its quant
|
||||||
@@ -960,10 +962,11 @@ function _rerenderCachedModels() {
|
|||||||
if (!el || !document.body.contains(el)) return false; // panel closed → stop
|
if (!el || !document.body.contains(el)) return false; // panel closed → stop
|
||||||
try {
|
try {
|
||||||
const host = (_es.remoteHost || '').trim();
|
const host = (_es.remoteHost || '').trim();
|
||||||
|
const selected = _serverByVal?.(_es.remoteServerKey || host);
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (host) {
|
if (host) {
|
||||||
params.set('host', host);
|
params.set('host', host);
|
||||||
const _sp = (_es.servers || []).find(s => s.host === host)?.port;
|
const _sp = selected?.port;
|
||||||
if (_sp) params.set('ssh_port', _sp);
|
if (_sp) params.set('ssh_port', _sp);
|
||||||
}
|
}
|
||||||
const res = await fetch('/api/cookbook/gpus' + (params.toString() ? '?' + params : ''));
|
const res = await fetch('/api/cookbook/gpus' + (params.toString() ? '?' + params : ''));
|
||||||
@@ -1787,7 +1790,7 @@ function _rerenderCachedModels() {
|
|||||||
const _probeParams = new URLSearchParams();
|
const _probeParams = new URLSearchParams();
|
||||||
if (_probeHost) {
|
if (_probeHost) {
|
||||||
_probeParams.set('host', _probeHost);
|
_probeParams.set('host', _probeHost);
|
||||||
const _sp = (_envState.servers || []).find(s => s.host === _probeHost)?.port;
|
const _sp = (_serverByVal?.(_envState.remoteServerKey || _probeHost) || {}).port;
|
||||||
if (_sp) _probeParams.set('ssh_port', _sp);
|
if (_sp) _probeParams.set('ssh_port', _sp);
|
||||||
}
|
}
|
||||||
const _probeRes = await fetch('/api/cookbook/gpus' + (_probeParams.toString() ? '?' + _probeParams : ''), { credentials: 'same-origin' });
|
const _probeRes = await fetch('/api/cookbook/gpus' + (_probeParams.toString() ? '?' + _probeParams : ''), { credentials: 'same-origin' });
|
||||||
@@ -1879,8 +1882,7 @@ function _rerenderCachedModels() {
|
|||||||
if (_ssEl && _ssEl.value != null) {
|
if (_ssEl && _ssEl.value != null) {
|
||||||
if (_ssEl.value === 'local') serveHost = '';
|
if (_ssEl.value === 'local') serveHost = '';
|
||||||
else {
|
else {
|
||||||
// Values are host strings now; resolve by host (numeric fallback).
|
const _srv = _serverByVal?.(_ssEl.value) || _envState.servers[parseInt(_ssEl.value)];
|
||||||
const _srv = _envState.servers.find(s => s.host === _ssEl.value) || _envState.servers[parseInt(_ssEl.value)];
|
|
||||||
if (_srv) {
|
if (_srv) {
|
||||||
serveHost = _srv.host;
|
serveHost = _srv.host;
|
||||||
_srvEnv = _srv.env || '';
|
_srvEnv = _srv.env || '';
|
||||||
@@ -1939,7 +1941,7 @@ function _resolveCacheHost() {
|
|||||||
if (cacheSrv) {
|
if (cacheSrv) {
|
||||||
const val = cacheSrv.value;
|
const val = cacheSrv.value;
|
||||||
if (val === 'local') host = '';
|
if (val === 'local') host = '';
|
||||||
else { const s = _envState.servers.find(x => x.host === val) || _envState.servers[parseInt(val)]; if (s) host = s.host; }
|
else { const s = _serverByVal?.(val) || _envState.servers[parseInt(val)]; if (s) host = s.host; }
|
||||||
}
|
}
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
@@ -2135,11 +2137,11 @@ export async function _fetchCachedModels() {
|
|||||||
host = '';
|
host = '';
|
||||||
selectedServer = _envState.servers.find(s => !s.host || s.host === 'local') || _envState.servers[0];
|
selectedServer = _envState.servers.find(s => !s.host || s.host === 'local') || _envState.servers[0];
|
||||||
} else {
|
} else {
|
||||||
const s = _envState.servers.find(x => x.host === val) || _envState.servers[parseInt(val)];
|
const s = _serverByVal?.(val) || _envState.servers[parseInt(val)];
|
||||||
if (s) { host = s.host; selectedServer = s; }
|
if (s) { host = s.host; selectedServer = s; }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedServer = _envState.servers.find(s => s.host === host) || _envState.servers[0];
|
selectedServer = _serverByVal?.(_envState.remoteServerKey || host) || _envState.servers[0];
|
||||||
}
|
}
|
||||||
// Read extra model dirs from the SELECTED server's modelDirs (canonical source)
|
// Read extra model dirs from the SELECTED server's modelDirs (canonical source)
|
||||||
const modelDirs = [];
|
const modelDirs = [];
|
||||||
@@ -2266,6 +2268,7 @@ export function initServe(shared) {
|
|||||||
_envState = shared._envState;
|
_envState = shared._envState;
|
||||||
_sshCmd = shared._sshCmd;
|
_sshCmd = shared._sshCmd;
|
||||||
_getPort = shared._getPort;
|
_getPort = shared._getPort;
|
||||||
|
_serverByVal = shared._serverByVal;
|
||||||
_sshPrefix = shared._sshPrefix;
|
_sshPrefix = shared._sshPrefix;
|
||||||
_getPlatform = shared._getPlatform;
|
_getPlatform = shared._getPlatform;
|
||||||
_isWindows = shared._isWindows;
|
_isWindows = shared._isWindows;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""Regression guards for same-host Cookbook SSH server profiles (#3337)."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
COOKBOOK = (ROOT / "static/js/cookbook.js").read_text(encoding="utf-8")
|
||||||
|
HWFIT = (ROOT / "static/js/cookbook-hwfit.js").read_text(encoding="utf-8")
|
||||||
|
DOWNLOAD = (ROOT / "static/js/cookbookDownload.js").read_text(encoding="utf-8")
|
||||||
|
SERVE = (ROOT / "static/js/cookbookServe.js").read_text(encoding="utf-8")
|
||||||
|
RUNNING = (ROOT / "static/js/cookbookRunning.js").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_dropdown_options_use_profile_keys_not_hosts():
|
||||||
|
assert "remoteServerKey" in COOKBOOK
|
||||||
|
assert "export function _serverKey(s)" in COOKBOOK
|
||||||
|
assert "s?.name || ''" in COOKBOOK
|
||||||
|
assert "s?.host || ''" in COOKBOOK
|
||||||
|
assert "s?.port || ''" in COOKBOOK
|
||||||
|
assert "s?.envPath || ''" in COOKBOOK
|
||||||
|
assert 'const value = _serverKey(s);' in COOKBOOK
|
||||||
|
assert 'option value="${esc(s.host)}"' not in COOKBOOK
|
||||||
|
|
||||||
|
|
||||||
|
def test_selected_server_helpers_prefer_profile_key_before_host_fallback():
|
||||||
|
assert "_envState.remoteServerKey = _serverKey(s);" in COOKBOOK
|
||||||
|
assert "const selected = hostOrTask === _envState.remoteHost ? _selectedServer() : null;" in COOKBOOK
|
||||||
|
assert "const srv = selected || _serverByVal(hostOrTask);" in COOKBOOK
|
||||||
|
assert "const _want = _currentServerValue();" in COOKBOOK
|
||||||
|
|
||||||
|
|
||||||
|
def test_cookbook_submodules_resolve_visible_profile_selection():
|
||||||
|
assert "_serverByVal?.(_ssv)" in DOWNLOAD
|
||||||
|
assert "_serverByVal?.(_envState.remoteServerKey || host)" in DOWNLOAD
|
||||||
|
assert "_serverByVal?.(_envState.remoteServerKey || _zh)" in DOWNLOAD
|
||||||
|
assert "_serverByVal(_envState.remoteServerKey || remoteHost)" in HWFIT
|
||||||
|
assert "hk: _currentServerValue()" in HWFIT
|
||||||
|
assert "sel.value = _currentServerValue();" in HWFIT
|
||||||
|
assert "_serverByVal?.(_ssEl.value)" in SERVE
|
||||||
|
assert "_serverByVal?.(val)" in SERVE
|
||||||
|
assert "_serverByVal?.(_es.remoteServerKey || _es.remoteHost || '')" in SERVE
|
||||||
|
assert "_serverByVal?.(_envState.remoteServerKey || _probeHost)" in SERVE
|
||||||
|
|
||||||
|
|
||||||
|
def test_running_tab_resolves_profile_key_not_first_host():
|
||||||
|
assert "_serverByVal(_envState.remoteServerKey || _tHost)" in RUNNING
|
||||||
|
assert "_serverByVal(_envState.remoteServerKey || _host)" in RUNNING
|
||||||
|
assert "_serverByVal(_envState.remoteServerKey || host)" in RUNNING
|
||||||
|
assert "_serverByVal = shared._serverByVal;" in RUNNING
|
||||||
|
assert "_selectedServer = shared._selectedServer;" in RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_same_host_selector_paths_resolve_by_first_matching_host():
|
||||||
|
forbidden = [
|
||||||
|
"servers.find(s => s.host === select.value)",
|
||||||
|
"servers.find(s => s.host === _ssEl.value)",
|
||||||
|
"servers.find(x => x.host === val)",
|
||||||
|
"servers.find(s => s.host === _ssv)",
|
||||||
|
]
|
||||||
|
combined = "\n".join([DOWNLOAD, HWFIT, SERVE])
|
||||||
|
for needle in forbidden:
|
||||||
|
assert needle not in combined
|
||||||
Reference in New Issue
Block a user