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
+80
View File
@@ -0,0 +1,80 @@
/**
* Minimal hash-based SPA router.
*
* Route format: { pattern: RegExp, title: string, template: string, onEnter: function(params, container) }
*
* The first matching route's onEnter is called with parsed params and a container element.
* The container is cleared before each navigation.
*
* Usage:
* Router.add(/^\/(.+)\/info$/, 'Server', 'html/serverInfo.html', ServerInfoPage);
* Router.add(/^\/(.+)\/player\/(.+)$/, 'Player Info', 'html/playerInfo.html', PlayerInfoPage);
* Router.start('#app-view');
*/
var Router = (function () {
'use strict';
var routes = [];
var viewSelector = null;
var currentCleanup = null;
function add(pattern, title, template, onEnter) {
routes.push({ pattern: pattern, title: title, template: template, onEnter: onEnter });
}
function navigate(hash) {
// strip leading # and optional /
var path = (hash || '').replace(/^#\/?/, '/');
if (!path || path === '/') path = '/home';
for (var i = 0; i < routes.length; i++) {
var r = routes[i];
var match = path.match(r.pattern);
if (match) {
// Store params for later use
var container = document.querySelector(viewSelector);
if (!container) return;
// Cleanup previous page
if (currentCleanup && typeof currentCleanup === 'function') {
currentCleanup();
currentCleanup = null;
}
// Clear container
container.innerHTML = '';
// Call onEnter with match groups + container
if (typeof r.onEnter === 'function') {
var result = r.onEnter(match, container);
if (typeof result === 'function') {
currentCleanup = result;
}
}
return;
}
}
}
function start(selector) {
viewSelector = selector;
window.addEventListener('hashchange', function () {
navigate(window.location.hash);
});
// Initial navigation
if (window.location.hash) {
navigate(window.location.hash);
}
}
function go(hash) {
window.location.hash = hash;
}
return {
add: add,
start: start,
go: go,
navigate: navigate
};
})();