Merge claude/2
This commit is contained in:
commit
0a25bd5a28
@ -1,66 +1,51 @@
|
||||
<div class="container peers-layout">
|
||||
<div class="main">
|
||||
<div class="peers-page" id="peers-page">
|
||||
<div class="config-section">
|
||||
<div class="page-header">
|
||||
<div class="page-header-icon-slot">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="6" cy="7" r="3"></circle>
|
||||
<circle cx="18" cy="7" r="3"></circle>
|
||||
<path d="M9 17l3-3 3 3"></path>
|
||||
<path d="M12 14v7"></path>
|
||||
<path d="M6 10v3a3 3 0 003 3"></path>
|
||||
<path d="M18 10v3a3 3 0 01-3 3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="page-header-title">
|
||||
<h1>Peers</h1>
|
||||
<p>Named references to other LibrePortal instances. Use them in the Migrate tab to pull apps across without typing hostnames.</p>
|
||||
</div>
|
||||
<div class="page-header-actions">
|
||||
<button class="backup-refresh-btn" id="peers-refresh-btn" title="Refresh">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 4 23 10 17 10"></polyline>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||
</svg>
|
||||
Refresh
|
||||
</button>
|
||||
<button class="backup-secondary-btn" id="peers-token-btn" title="Show this host's pairing token">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0110 0v4"></path>
|
||||
</svg>
|
||||
Show my token
|
||||
</button>
|
||||
<button class="backup-secondary-btn" id="peers-pair-btn" title="Paste a token from another LibrePortal">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07L11.5 5.45"></path>
|
||||
<path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07L12.5 18.55"></path>
|
||||
</svg>
|
||||
Pair with token
|
||||
</button>
|
||||
<button class="backup-primary-btn" id="peers-add-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
Add backup-channel peer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="peers-empty" id="peers-empty" hidden>
|
||||
<p>No peers yet.</p>
|
||||
<p class="backup-card-hint">
|
||||
Add one to give a memorable name to another LibrePortal you share a backup location with.
|
||||
Direct-SSH peers (no shared backup repo needed) ship with Phase 3.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="peers-list" id="peers-list"></div>
|
||||
</div>
|
||||
<div class="peers-page">
|
||||
<div class="page-header config-page-header">
|
||||
<div class="page-header-title">
|
||||
<div class="admin-breadcrumb">Tools</div>
|
||||
<h1>Peers</h1>
|
||||
<p>Named references to other LibrePortal instances. Use them in the Migrate tab to pull apps across without typing hostnames.</p>
|
||||
</div>
|
||||
<div class="page-header-actions">
|
||||
<button class="backup-refresh-btn" id="peers-refresh-btn" title="Refresh">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 4 23 10 17 10"></polyline>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||
</svg>
|
||||
Refresh
|
||||
</button>
|
||||
<button class="backup-secondary-btn" id="peers-token-btn" title="Show this host's pairing token">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0110 0v4"></path>
|
||||
</svg>
|
||||
Show my token
|
||||
</button>
|
||||
<button class="backup-secondary-btn" id="peers-pair-btn" title="Paste a token from another LibrePortal">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07L11.5 5.45"></path>
|
||||
<path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07L12.5 18.55"></path>
|
||||
</svg>
|
||||
Pair with token
|
||||
</button>
|
||||
<button class="backup-primary-btn" id="peers-add-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
Add backup-channel peer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="peers-empty" id="peers-empty" hidden style="padding:24px; text-align:center; opacity:.75">
|
||||
<p>No peers yet.</p>
|
||||
<p class="backup-card-hint">
|
||||
Add one to give a memorable name to another LibrePortal you share a backup location with.
|
||||
Or use <strong>Show my token</strong> / <strong>Pair with token</strong> for direct-SSH peers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="peers-list" id="peers-list" style="padding-top:8px"></div>
|
||||
</div>
|
||||
|
||||
<div class="backup-modal" id="peers-add-modal">
|
||||
|
||||
@ -51,17 +51,6 @@
|
||||
</svg>
|
||||
Backups
|
||||
</a>
|
||||
<a href="/peers" class="nav-item" id="nav-peers">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="6" cy="7" r="3"></circle>
|
||||
<circle cx="18" cy="7" r="3"></circle>
|
||||
<path d="M9 17l3-3 3 3"></path>
|
||||
<path d="M12 14v7"></path>
|
||||
<path d="M6 10v3a3 3 0 003 3"></path>
|
||||
<path d="M18 10v3a3 3 0 01-3 3"></path>
|
||||
</svg>
|
||||
Peers
|
||||
</a>
|
||||
</nav>
|
||||
<div class="mobile-drawer-page-section" id="mobile-drawer-page-section"></div>
|
||||
<div class="topbar-controls">
|
||||
|
||||
@ -57,6 +57,28 @@ if (typeof window.ConfigManager === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Peers is an admin tool page — named references to other LibrePortal
|
||||
// instances (kind=backup-channel for shared-backup migrate, or
|
||||
// kind=direct-ssh-direct for live SSH pulls). Same shape as SSH Access:
|
||||
// we inject its content template, then init PeersPage.
|
||||
if (category === 'peers') {
|
||||
try { this.sidebar.populateSidebar(); } catch (e) {}
|
||||
try {
|
||||
const html = await fetch('/html/peers-content.html').then(r => r.text());
|
||||
configSection.innerHTML = html;
|
||||
} catch (e) {
|
||||
configSection.innerHTML = '<div class="error">Peers page template failed to load.</div>';
|
||||
return;
|
||||
}
|
||||
if (typeof PeersPage !== 'undefined') {
|
||||
window.peersPage = new PeersPage('config-section');
|
||||
await window.peersPage.init();
|
||||
} else {
|
||||
configSection.innerHTML = '<div class="error">PeersPage controller not loaded.</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// System is an admin tool page (live host + per-app statistics) with its
|
||||
// own controller, like SSH Access above.
|
||||
if (category === 'system') {
|
||||
|
||||
@ -36,6 +36,24 @@ class ConfigSidebar {
|
||||
});
|
||||
this.categoriesList.appendChild(overviewItem);
|
||||
|
||||
// System sits right under Overview — both are admin-landing surfaces
|
||||
// (Overview = ops/health summary, System = live host + per-app stats),
|
||||
// distinct from the config form pages or the Tools utilities below.
|
||||
const systemItem = document.createElement('div');
|
||||
systemItem.className = 'category';
|
||||
systemItem.setAttribute('data-category', 'system');
|
||||
systemItem.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:8px;vertical-align:middle"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg> System';
|
||||
systemItem.addEventListener('click', function () {
|
||||
window.history.pushState({}, '', window.adminPath('system'));
|
||||
document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); });
|
||||
this.classList.add('active');
|
||||
window.configCategory = 'system';
|
||||
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
|
||||
window.configManager.renderConfig('system');
|
||||
}
|
||||
});
|
||||
this.categoriesList.appendChild(systemItem);
|
||||
|
||||
// "Config" group heading above the configuration categories (mirrors the
|
||||
// "Tools" heading below).
|
||||
const configLabel = document.createElement('div');
|
||||
@ -118,20 +136,20 @@ class ConfigSidebar {
|
||||
});
|
||||
self.categoriesList.appendChild(sshItem);
|
||||
|
||||
const systemItem = document.createElement('div');
|
||||
systemItem.className = 'category';
|
||||
systemItem.setAttribute('data-category', 'system');
|
||||
systemItem.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:8px;vertical-align:middle"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg> System';
|
||||
systemItem.addEventListener('click', function () {
|
||||
window.history.pushState({}, '', window.adminPath('system'));
|
||||
const peersItem = document.createElement('div');
|
||||
peersItem.className = 'category';
|
||||
peersItem.setAttribute('data-category', 'peers');
|
||||
peersItem.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:8px;vertical-align:middle"><circle cx="6" cy="7" r="3"></circle><circle cx="18" cy="7" r="3"></circle><path d="M9 17l3-3 3 3"></path><path d="M12 14v7"></path><path d="M6 10v3a3 3 0 003 3"></path><path d="M18 10v3a3 3 0 01-3 3"></path></svg> Peers';
|
||||
peersItem.addEventListener('click', function () {
|
||||
window.history.pushState({}, '', window.adminPath('peers'));
|
||||
document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); });
|
||||
this.classList.add('active');
|
||||
window.configCategory = 'system';
|
||||
window.configCategory = 'peers';
|
||||
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
|
||||
window.configManager.renderConfig('system');
|
||||
window.configManager.renderConfig('peers');
|
||||
}
|
||||
});
|
||||
self.categoriesList.appendChild(systemItem);
|
||||
self.categoriesList.appendChild(peersItem);
|
||||
|
||||
// Set initial active category
|
||||
this.setActiveCategory(window.configCategory || 'overview');
|
||||
|
||||
@ -4,7 +4,12 @@
|
||||
// kind=backup-channel; the other kinds light up in Phase 3.
|
||||
|
||||
class PeersPage {
|
||||
constructor() {
|
||||
constructor(rootId = 'config-section') {
|
||||
// rootId is the container the page renders into when embedded under
|
||||
// /admin/tools/peers. The peers-* element IDs are unique enough that
|
||||
// global document.getElementById queries still find them inside the
|
||||
// injected template — but keeping the ref for future scoped queries.
|
||||
this.rootId = rootId;
|
||||
this.peers = [];
|
||||
this.backupLocations = []; // populated for the loc_idx dropdown
|
||||
this.taskManager = (typeof TaskManager !== 'undefined') ? new TaskManager() : null;
|
||||
|
||||
@ -77,7 +77,7 @@ class LibrePortalSPAClean {
|
||||
this.routes.set('/tasks*', () => this.handleTasks()); // Handle /tasks with query
|
||||
this.routes.set('/backup', () => this.handleBackup());
|
||||
this.routes.set('/backup*', () => this.handleBackup());
|
||||
this.routes.set('/peers', () => this.handlePeers());
|
||||
this.routes.set('/peers', () => this.handlePeers()); // legacy → /admin/tools/peers
|
||||
this.routes.set('/peers*', () => this.handlePeers());
|
||||
this.routes.set('/ssh', () => this.handleSsh()); // legacy → /admin/tools/ssh-access
|
||||
this.routes.set('/ssh*', () => this.handleSsh());
|
||||
@ -275,19 +275,8 @@ class LibrePortalSPAClean {
|
||||
}
|
||||
|
||||
async handlePeers() {
|
||||
try {
|
||||
const html = await this.fetchContent('/html/peers-content.html');
|
||||
this.loadContent(html, 'Peers');
|
||||
if (typeof PeersPage !== 'undefined') {
|
||||
window.peersPage = new PeersPage();
|
||||
await window.peersPage.init();
|
||||
} else {
|
||||
console.error('PeersPage class not loaded');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Peers page load error:', error);
|
||||
this.showError('Failed to load peers page');
|
||||
}
|
||||
// Legacy /peers → Peers under the Admin area.
|
||||
this.navigate('/admin/tools/peers', true);
|
||||
}
|
||||
|
||||
async handleSsh() {
|
||||
@ -533,6 +522,7 @@ class LibrePortalSPAClean {
|
||||
window.adminPath = function (category) {
|
||||
if (!category || category === 'overview') return '/admin';
|
||||
if (category === 'ssh-access') return '/admin/tools/ssh-access';
|
||||
if (category === 'peers') return '/admin/tools/peers';
|
||||
return '/admin/config/' + category;
|
||||
};
|
||||
window.adminCategoryFromPath = function (pathname) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user