fix(sync): persist and cache device names so they are never lost
CI / Build & Test (push) Successful in 4m18s
CI / Build & Test (push) Successful in 4m18s
- state.js: add lastLoadedDeviceNames cache, getDeviceNames(),
setDeviceNames(); saveStateSync() always merges and writes
deviceNames so saves never strip them.
- holesail-manager: include deviceNames in saveState() and
getStateSnapshot(); applySnapshotData() updates cache from
synced snapshot. buildDefaultState and legacy migration
include deviceNames: {}.
Prevents hostnames reverting to "Device 1" etc. after saves or sync.
This commit is contained in:
@@ -601,7 +601,7 @@
|
||||
<button class="btn btn-secondary" id="btnCopyInvite" style="display:none;">
|
||||
Copy invite
|
||||
</button>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex:1;min-width:200px;">
|
||||
<div id="syncLinkDeviceForm" style="display:flex;align-items:center;gap:8px;flex:1;min-width:200px;">
|
||||
<input type="text" id="syncInviteInput" class="input" placeholder="Paste invite from other device" style="flex:1;" />
|
||||
<button class="btn btn-primary" id="btnPairWithInvite">Link device</button>
|
||||
</div>
|
||||
@@ -611,7 +611,7 @@
|
||||
</div>
|
||||
<div class="card" id="syncLinkedDevicesCard" style="display:none;margin-bottom:16px;">
|
||||
<div class="card-body flush">
|
||||
<div class="section-heading" style="padding:0 0 10px 0;">Linked devices</div>
|
||||
<div class="section-heading" style="padding:0 16px 10px 16px;">Linked devices</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -17,15 +17,18 @@ function updateSyncStatus() {
|
||||
const btnCopy = $('btnCopyInvite');
|
||||
const inviteDisplay = $('syncInviteDisplay');
|
||||
if (!text) return;
|
||||
const linkDeviceForm = $('syncLinkDeviceForm');
|
||||
if (!response || response.error) {
|
||||
if (dot) dot.style.background = 'var(--text4)';
|
||||
text.textContent = 'Not linked';
|
||||
if (lastSynced) lastSynced.textContent = '';
|
||||
if (btnCopy) btnCopy.style.display = 'none';
|
||||
if (inviteDisplay) inviteDisplay.style.display = 'none';
|
||||
if (linkDeviceForm) linkDeviceForm.style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
if (response.linked) {
|
||||
if (linkDeviceForm) linkDeviceForm.style.display = 'none';
|
||||
if (dot) dot.style.background = 'var(--green)';
|
||||
text.textContent = 'Linked';
|
||||
if (response.lastSyncedAt) {
|
||||
@@ -69,6 +72,7 @@ function updateSyncStatus() {
|
||||
if (lastSynced) lastSynced.textContent = '';
|
||||
if (btnCopy) btnCopy.style.display = 'none';
|
||||
if (inviteDisplay) inviteDisplay.style.display = 'none';
|
||||
if (linkDeviceForm) linkDeviceForm.style.display = 'flex';
|
||||
const card = $('syncLinkedDevicesCard');
|
||||
const tbody = $('syncLinkedDevicesTable');
|
||||
if (card) card.style.display = 'none';
|
||||
|
||||
@@ -65,7 +65,8 @@ function saveState() {
|
||||
virtualHosts: virtualHostsList,
|
||||
serviceTunnels: serviceTunnelsList,
|
||||
sshConnections: connectionsModule.getSshConnections(),
|
||||
rdpConnections: connectionsModule.getRdpConnections()
|
||||
rdpConnections: connectionsModule.getRdpConnections(),
|
||||
deviceNames: stateModule.getDeviceNames()
|
||||
};
|
||||
stateModule.saveStateSync(snapshot);
|
||||
if (onStateSaved) onStateSaved(snapshot);
|
||||
@@ -94,7 +95,8 @@ function getStateSnapshot() {
|
||||
virtualHosts: virtualHostsList,
|
||||
serviceTunnels: serviceTunnelsList,
|
||||
sshConnections: connectionsModule.getSshConnections(),
|
||||
rdpConnections: connectionsModule.getRdpConnections()
|
||||
rdpConnections: connectionsModule.getRdpConnections(),
|
||||
deviceNames: stateModule.getDeviceNames()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,6 +113,7 @@ function applySnapshotData(snapshot) {
|
||||
connectionsModule.applyLoaded(ssh, rdp);
|
||||
if (typeof snapshot.nextServerId === 'number') serversModule.applyLoaded(snapshot.nextServerId);
|
||||
if (typeof snapshot.nextServiceTunnelId === 'number') svcModule.applyLoaded(snapshot.nextServiceTunnelId);
|
||||
if (snapshot.deviceNames != null && typeof snapshot.deviceNames === 'object') stateModule.setDeviceNames(snapshot.deviceNames);
|
||||
}
|
||||
|
||||
// Inject the shared saveState and emit callbacks into each sub-module
|
||||
|
||||
@@ -34,6 +34,8 @@ function debugLog(...args) {
|
||||
}
|
||||
|
||||
let stateFilePath = null;
|
||||
/** In-memory cache of device names (deviceId -> hostname) so they are never lost when saving. */
|
||||
let lastLoadedDeviceNames = {};
|
||||
|
||||
function setStoragePath(baseDir) {
|
||||
if (baseDir && typeof baseDir === 'string') {
|
||||
@@ -72,13 +74,27 @@ function buildDefaultState() {
|
||||
sshConnections: [],
|
||||
rdpConnections: [],
|
||||
nextServerId: 0,
|
||||
nextServiceTunnelId: 0
|
||||
nextServiceTunnelId: 0,
|
||||
deviceNames: {}
|
||||
};
|
||||
}
|
||||
|
||||
function getDeviceNames() {
|
||||
return { ...lastLoadedDeviceNames };
|
||||
}
|
||||
|
||||
function setDeviceNames(obj) {
|
||||
if (obj != null && typeof obj === 'object') {
|
||||
lastLoadedDeviceNames = { ...obj };
|
||||
}
|
||||
}
|
||||
|
||||
function saveStateSync(data) {
|
||||
const file = getStatePath();
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
const deviceNames = (data.deviceNames && typeof data.deviceNames === 'object') ? data.deviceNames : lastLoadedDeviceNames;
|
||||
lastLoadedDeviceNames = { ...deviceNames };
|
||||
const toWrite = { ...data, deviceNames: lastLoadedDeviceNames };
|
||||
const json = JSON.stringify(toWrite, null, 2);
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
fs.writeFileSync(file, json, 'utf8');
|
||||
@@ -97,6 +113,7 @@ function loadState() {
|
||||
if (!trimmed) return buildDefaultState();
|
||||
const data = JSON.parse(raw);
|
||||
if (!data || typeof data !== 'object') return buildDefaultState();
|
||||
lastLoadedDeviceNames = (data.deviceNames && typeof data.deviceNames === 'object') ? { ...data.deviceNames } : {};
|
||||
const out = {
|
||||
settings: (data.settings && typeof data.settings === 'object') ? { ...SETTINGS_DEFAULTS, ...data.settings } : { ...SETTINGS_DEFAULTS },
|
||||
servers: Array.isArray(data.servers) ? data.servers : [],
|
||||
@@ -106,7 +123,7 @@ function loadState() {
|
||||
rdpConnections: Array.isArray(data.rdpConnections) ? data.rdpConnections : [],
|
||||
nextServerId: typeof data.nextServerId === 'number' ? data.nextServerId : 0,
|
||||
nextServiceTunnelId: typeof data.nextServiceTunnelId === 'number' ? data.nextServiceTunnelId : 0,
|
||||
deviceNames: (data.deviceNames && typeof data.deviceNames === 'object') ? { ...data.deviceNames } : {}
|
||||
deviceNames: { ...lastLoadedDeviceNames }
|
||||
};
|
||||
debugLog('state loaded path=', file, 'servers=', out.servers.length, 'vhosts=', out.virtualHosts.length);
|
||||
if (process.stderr && (out.servers.length || out.virtualHosts.length)) {
|
||||
@@ -128,6 +145,7 @@ function loadState() {
|
||||
if (!trimmed) return buildDefaultState();
|
||||
const data = JSON.parse(raw);
|
||||
if (process.stderr) process.stderr.write('[holesail-manager] migrating from ' + legacyFile + ' to ' + file + '\n');
|
||||
lastLoadedDeviceNames = {};
|
||||
const out = {
|
||||
settings: { ...SETTINGS_DEFAULTS },
|
||||
servers: Array.isArray(data.servers) ? data.servers : [],
|
||||
@@ -136,7 +154,8 @@ function loadState() {
|
||||
sshConnections: Array.isArray(data.sshConnections) ? data.sshConnections : [],
|
||||
rdpConnections: Array.isArray(data.rdpConnections) ? data.rdpConnections : [],
|
||||
nextServerId: typeof data.nextServerId === 'number' ? data.nextServerId : 0,
|
||||
nextServiceTunnelId: typeof data.nextServiceTunnelId === 'number' ? data.nextServiceTunnelId : 0
|
||||
nextServiceTunnelId: typeof data.nextServiceTunnelId === 'number' ? data.nextServiceTunnelId : 0,
|
||||
deviceNames: {}
|
||||
};
|
||||
try { fs.unlinkSync(legacyFile); } catch (_) {}
|
||||
if (process.stderr) process.stderr.write('[holesail-manager] migration complete, legacy file removed\n');
|
||||
@@ -150,4 +169,4 @@ function loadState() {
|
||||
return buildDefaultState();
|
||||
}
|
||||
|
||||
module.exports = { SETTINGS_DEFAULTS, setStoragePath, loadState, saveStateSync, buildDefaultState, ensureStorageDir };
|
||||
module.exports = { SETTINGS_DEFAULTS, setStoragePath, loadState, saveStateSync, buildDefaultState, ensureStorageDir, getDeviceNames, setDeviceNames };
|
||||
|
||||
Reference in New Issue
Block a user