feat: full de-Angularization with Bootstrap 5 migration

- Removed AngularJS 1.4.8 completely — no more framework dependency
- Extracted core RCON logic into pure vanilla JS (js/rcon.js)
- Added lightweight hash-based SPA router (js/router.js)
- Rewrote all 6 pages as vanilla JS modules in js/pages/
- Migrated Bootstrap 3 → Bootstrap 5 (cards instead of panels, bootstrap-icons instead of glyphicons)
- Removed jQuery dependency (Bootstrap 5 is vanilla JS)
- Purged all Angular templates from html/
- Removed npm artifacts (package.json, node_modules)
- Added nginx.example.conf for static hosting
- Updated rcon.css dark theme for BS5 variables
- Static file only — no Node.js backend required

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-13 17:51:50 +02:00
co-authored by Copilot
parent 0fd94044a8
commit 0a178d17b9
32 changed files with 1242 additions and 1702 deletions
+142
View File
@@ -0,0 +1,142 @@
/**
* Connection page — connect form with localStorage persistence.
*/
function ConnectPage(params, container) {
var html =
'<div class="row">' +
'<div class="offset-md-4 col-md-6" style="margin-top: 128px">' +
'<div class="card p-4 mb-4">' +
'<h2>Connect</h2>' +
'<div class="alert alert-danger" id="connect-error" style="display:none"></div>' +
'<p>Enter the address (including rcon port) and the rcon password below to connect.</p>' +
'<form id="connect-form">' +
'<div class="mb-3">' +
'<label for="connection-address" class="form-label">Address</label>' +
'<input required minlength="5" id="connection-address" class="form-control" placeholder="xxx.xxx.xxx.xxx:28016">' +
'</div>' +
'<div class="mb-3">' +
'<label for="connection-password" class="form-label">Password</label>' +
'<input required minlength="2" type="password" id="connection-password" class="form-control">' +
'</div>' +
'<div class="form-check float-end">' +
'<input type="checkbox" class="form-check-input" id="connection-save" checked>' +
'<label class="form-check-label" for="connection-save">Save Connection</label>' +
'</div>' +
'<button type="submit" class="btn btn-primary w-100">Connect</button>' +
'</form>' +
'</div>' +
'<div class="card p-4">' +
'<div id="saved-connections"></div>' +
'</div>' +
'</div>' +
'</div>';
container.innerHTML = html;
var addrInput = document.getElementById('connection-address');
var passInput = document.getElementById('connection-password');
var saveCheck = document.getElementById('connection-save');
var errorDiv = document.getElementById('connect-error');
var savedDiv = document.getElementById('saved-connections');
var connectionsLimit = 5;
function loadConnections() {
try { return JSON.parse(localStorage.previousConnections || '[]'); }
catch (e) { return []; }
}
function saveConnections(conns) {
localStorage.previousConnections = JSON.stringify(conns);
}
function showError(msg) {
errorDiv.textContent = msg;
errorDiv.style.display = 'block';
}
function hideError() { errorDiv.style.display = 'none'; }
function addConnection(addr, pass) {
var conns = loadConnections();
conns = conns.filter(function (c) { return c.Address !== addr; });
conns.push({ Address: addr, Password: pass, date: new Date().toISOString() });
saveConnections(conns);
}
function doConnect(addr, pass) {
hideError();
Rcon.connect(addr, pass);
}
function renderSaved() {
var conns = loadConnections();
if (conns.length === 0) { savedDiv.innerHTML = ''; return; }
var display = conns.slice().reverse();
var shown = connectionsLimit || display.length;
var h = '<h2>Saved Connections</h2><ul>';
for (var i = 0; i < Math.min(shown, display.length); i++) {
var c = display[i];
h += '<li><a href="#" class="saved-conn" data-addr="' + c.Address + '" data-pass="' + c.Password + '">' + c.Address + '</a> — ' + new Date(c.date).toLocaleString() + '</li>';
}
h += '</ul>';
if (conns.length > 5) {
h += '<button type="button" class="btn btn-primary btn-block" id="toggle-conn-limit">Show ' + (connectionsLimit ? 'more' : 'less') + '</button>';
}
savedDiv.innerHTML = h;
var links = savedDiv.querySelectorAll('.saved-conn');
for (var j = 0; j < links.length; j++) {
links[j].addEventListener('click', function (e) {
e.preventDefault();
doConnect(this.dataset.addr, this.dataset.pass);
});
}
var toggle = document.getElementById('toggle-conn-limit');
if (toggle) {
toggle.addEventListener('click', function () {
connectionsLimit = connectionsLimit ? undefined : 5;
renderSaved();
});
}
}
document.getElementById('connect-form').addEventListener('submit', function (e) {
e.preventDefault();
var addr = addrInput.value.trim();
var pass = passInput.value.trim();
if (!addr || !pass) return;
if (saveCheck.checked) addConnection(addr, pass);
doConnect(addr, pass);
});
function onConnected() {
window.location.hash = '#/' + Rcon.getAddress() + '/info';
}
function onDisconnected(ev) {
showError('Connection was closed — Error ' + (ev ? ev.code : 'unknown'));
}
Rcon.on('connected', onConnected);
Rcon.on('disconnected', onDisconnected);
renderSaved();
// Auto-connect from URL — check hash for address and password
var hash = window.location.hash;
var pwMatch = hash.match(/[?&]password=([^&]+)/);
if (pwMatch) {
passInput.value = decodeURIComponent(pwMatch[1]);
window.location.hash = hash.replace(/[?&]password=[^&]+/, '');
}
var addrMatch = hash.match(/^#\/([^/]+)\//);
if (addrMatch) addrInput.value = addrMatch[1];
if (addrInput.value && passInput.value) {
if (saveCheck.checked) addConnection(addrInput.value.trim(), passInput.value.trim());
doConnect(addrInput.value.trim(), passInput.value.trim());
}
return function cleanup() {
Rcon.off('connected', onConnected);
Rcon.off('disconnected', onDisconnected);
};
}