move instance management and firewall help into modal windows

This commit is contained in:
CPTN Cosmo 2026-04-18 17:10:26 +02:00
parent 76565477e7
commit 77c1c3114f
3 changed files with 185 additions and 64 deletions

View file

@ -15,10 +15,30 @@ document.addEventListener('DOMContentLoaded', () => {
const createUserForm = document.getElementById('create-user-form'); const createUserForm = document.getElementById('create-user-form');
const changePasswordForm = document.getElementById('change-password-form'); const changePasswordForm = document.getElementById('change-password-form');
const profileSuccess = document.getElementById('profile-success'); const profileSuccess = document.getElementById('profile-success');
const helpBtn = document.getElementById('help-btn');
const helpModal = document.getElementById('help-modal');
const addInstanceModal = document.getElementById('add-instance-modal');
const showAddBtn = document.getElementById('show-add-btn');
let config = { firewall_host_ip: null }; let config = { firewall_host_ip: null };
let currentUser = null; let currentUser = null;
const openModal = (modal) => modal.classList.add('active');
const closeModal = (modal) => modal.classList.remove('active');
document.querySelectorAll('.btn-close').forEach(btn => {
btn.addEventListener('click', () => {
closeModal(btn.closest('.modal'));
});
});
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) closeModal(e.target);
});
helpBtn.addEventListener('click', () => openModal(helpModal));
showAddBtn.addEventListener('click', () => openModal(addInstanceModal));
const showLogin = () => { const showLogin = () => {
loginOverlay.style.display = 'flex'; loginOverlay.style.display = 'flex';
logoutBtn.style.display = 'none'; logoutBtn.style.display = 'none';
@ -158,6 +178,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (res.ok) { if (res.ok) {
addForm.reset(); addForm.reset();
document.getElementById('port').value = "4646"; document.getElementById('port').value = "4646";
closeModal(addInstanceModal);
fetchInstances(); fetchInstances();
} else if (res.status === 401) { } else if (res.status === 401) {
showLogin(); showLogin();

View file

@ -18,6 +18,14 @@
</svg> </svg>
<h1>XIVLauncher Remote OTP</h1> <h1>XIVLauncher Remote OTP</h1>
</div> </div>
<div class="header-actions">
<button id="help-btn" class="btn btn-icon" title="Firewall Help">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
</button>
<button id="logout-btn" class="btn btn-icon btn-logout" title="Logout" style="display: none;"> <button id="logout-btn" class="btn btn-icon btn-logout" title="Logout" style="display: none;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path> <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
@ -25,6 +33,7 @@
<line x1="21" y1="12" x2="9" y2="12"></line> <line x1="21" y1="12" x2="9" y2="12"></line>
</svg> </svg>
</button> </button>
</div>
</header> </header>
<nav id="main-nav" class="main-nav" style="display: none;"> <nav id="main-nav" class="main-nav" style="display: none;">
@ -35,57 +44,14 @@
<main> <main>
<div id="instances-section" class="tab-content active"> <div id="instances-section" class="tab-content active">
<div class="card add-instance-card">
<h2>Add Instance</h2>
<form id="add-form">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" placeholder="e.g. Steam Deck" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="ip">IP Address</label>
<input type="text" id="ip" placeholder="192.168.1.100" required>
</div>
<div class="form-group">
<label for="port">Port</label>
<input type="number" id="port" value="4646" required>
</div>
</div>
<div class="form-group">
<label for="secret">OTP Secret</label>
<input type="password" id="secret" placeholder="Base32 Secret or otpauth:// URL" required>
</div>
<button type="submit" class="btn btn-primary">Add Instance</button>
</form>
</div>
<div class="card firewall-helper-card">
<h2>Firewall Helper</h2>
<p class="helper-text">Run these commands on the machine running XIVLauncher to allow incoming connections on the specified port.</p>
<div class="code-block">
<span class="code-label">UFW</span>
<div class="code-wrapper">
<code id="ufw-cmd">sudo ufw allow from localhost to any port 4646 proto tcp</code>
<button class="btn-copy" onclick="copyToClipboard('ufw-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
<div class="code-block">
<span class="code-label">iptables</span>
<div class="code-wrapper">
<code id="iptables-cmd">sudo iptables -I INPUT -p tcp -s localhost --dport 4646 -j ACCEPT</code>
<button class="btn-copy" onclick="copyToClipboard('iptables-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
</div>
<div class="card instances-card"> <div class="card instances-card">
<div class="instances-header"> <div class="instances-header">
<h2>Managed Instances</h2> <h2>Managed Instances</h2>
<div class="actions">
<button class="btn btn-primary btn-sm" id="show-add-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add
</button>
<button class="btn btn-icon" id="refresh-btn" title="Refresh"> <button class="btn btn-icon" id="refresh-btn" title="Refresh">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline> <polyline points="23 4 23 10 17 10"></polyline>
@ -93,10 +59,12 @@
</svg> </svg>
</button> </button>
</div> </div>
</div>
<div id="instances-list" class="instances-list"> <div id="instances-list" class="instances-list">
<!-- Instances injected via JS --> <!-- Instances injected via JS -->
</div> </div>
</div> </div>
</div>
<div id="admin-section" class="tab-content"> <div id="admin-section" class="tab-content">
<div class="card user-management-card"> <div class="card user-management-card">
@ -136,6 +104,65 @@
</main> </main>
</div> </div>
<!-- Modals -->
<div id="add-instance-modal" class="modal">
<div class="modal-content card">
<div class="modal-header">
<h2>Add Instance</h2>
<button class="btn-close">&times;</button>
</div>
<form id="add-form">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" placeholder="e.g. Steam Deck" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="ip">IP Address</label>
<input type="text" id="ip" placeholder="192.168.1.100" required>
</div>
<div class="form-group">
<label for="port">Port</label>
<input type="number" id="port" value="4646" required>
</div>
</div>
<div class="form-group">
<label for="secret">OTP Secret</label>
<input type="password" id="secret" placeholder="Base32 Secret or otpauth:// URL" required>
</div>
<button type="submit" class="btn btn-primary">Add Instance</button>
</form>
</div>
</div>
<div id="help-modal" class="modal">
<div class="modal-content card">
<div class="modal-header">
<h2>Firewall Help</h2>
<button class="btn-close">&times;</button>
</div>
<p class="helper-text">Run these commands on the machine running XIVLauncher to allow incoming connections on the specified port.</p>
<div class="code-block">
<span class="code-label">UFW</span>
<div class="code-wrapper">
<code id="ufw-cmd">sudo ufw allow from localhost to any port 4646 proto tcp</code>
<button class="btn-copy" onclick="copyToClipboard('ufw-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
<div class="code-block">
<span class="code-label">iptables</span>
<div class="code-wrapper">
<code id="iptables-cmd">sudo iptables -I INPUT -p tcp -s localhost --dport 4646 -j ACCEPT</code>
<button class="btn-copy" onclick="copyToClipboard('iptables-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
</div>
</div>
<div id="login-overlay" class="login-overlay"> <div id="login-overlay" class="login-overlay">
<div class="login-card"> <div class="login-card">
<div class="logo"> <div class="logo">

View file

@ -419,3 +419,76 @@ input:focus {
margin-top: 1rem; margin-top: 1rem;
font-size: 0.875rem; font-size: 0.875rem;
} }
/* Modals */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
z-index: 2000;
justify-content: center;
align-items: center;
padding: 1rem;
}
.modal.active {
display: flex;
}
.modal-content {
width: 100%;
max-width: 500px;
margin: 0;
position: relative;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.modal-header h2 {
margin: 0;
}
.btn-close {
background: transparent;
border: none;
color: var(--text-muted);
font-size: 1.5rem;
cursor: pointer;
line-height: 1;
}
.btn-close:hover {
color: var(--text-main);
}
.header-actions {
display: flex;
gap: 0.5rem;
}
.instances-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.instances-header .actions {
display: flex;
gap: 0.5rem;
}
.btn-sm {
padding: 0.4rem 0.8rem;
font-size: 0.75rem;
}