Initial commit — multi-tenant filtering, port constraints, chart bbox
This commit is contained in:
+94
-21
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const API = 'http://localhost:5503';
|
||||
const API = ''; // relative — works from any IP (localhost, Tailscale, LAN)
|
||||
const SESSION_KEY = 'ams_session';
|
||||
|
||||
// ── Estado de sesión ────────────────────────────────────────────────────────
|
||||
@@ -51,7 +51,18 @@ const Auth = {
|
||||
`;
|
||||
el.title = 'Click to logout';
|
||||
el.onclick = () => {
|
||||
if (confirm(`Logout as ${this.session.nombre}?`)) Auth.clear();
|
||||
if (confirm(`Logout as ${this.session.nombre}?`)) {
|
||||
Auth.clear();
|
||||
window._clearPortConstraints?.();
|
||||
_portCache = null;
|
||||
// Return to login screen
|
||||
document.getElementById('app').classList.add('hidden');
|
||||
document.getElementById('login-screen').classList.remove('hidden');
|
||||
document.getElementById('ls-user').value = '';
|
||||
document.getElementById('ls-pass').value = '';
|
||||
document.getElementById('ls-error').classList.add('hidden');
|
||||
setTimeout(() => document.getElementById('ls-user').focus(), 100);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -97,15 +108,11 @@ const Modal = {
|
||||
if (!res.ok) throw new Error('invalid');
|
||||
const data = await res.json();
|
||||
|
||||
if (!['ADMIN','SUPERADMIN'].includes(data.role)) {
|
||||
errEl.textContent = 'Your account does not have edit permissions.';
|
||||
errEl.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
Auth.save({ token: data.access_token, username: data.username,
|
||||
nombre: data.nombre, role: data.role });
|
||||
_hideOverlay();
|
||||
afterLogin();
|
||||
_fetchAndApplyPort(data.access_token, data.role); // update constraints if user changed
|
||||
|
||||
if (this._pendingAid) {
|
||||
this.openEdit(this._pendingAid);
|
||||
@@ -360,6 +367,61 @@ function _hideOverlay() {
|
||||
document.getElementById('modal-edit').classList.add('hidden');
|
||||
}
|
||||
|
||||
// ── Port navigation + constraints ─────────────────────────────────────────────
|
||||
// Cached port result — re-applied after the app container becomes visible.
|
||||
let _portCache = null; // { lon, lat, zoom, port }
|
||||
|
||||
// Called right after #app becomes visible.
|
||||
// OL initialized with display:none → size=0×0 → map renders half-screen and
|
||||
// ignores setCenter. Fix: updateSize() tells OL the real container size,
|
||||
// then re-apply port position so the map lands at the correct place.
|
||||
function _showMap() {
|
||||
requestAnimationFrame(() => {
|
||||
window._mapUpdateSize?.(); // fix half-screen rendering
|
||||
requestAnimationFrame(() => {
|
||||
_applyPortToMap(); // re-apply position after resize
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _applyPortToMap() {
|
||||
if (!_portCache) return;
|
||||
const { lon, lat, zoom, port } = _portCache;
|
||||
|
||||
// Step 1: clear any previous constraints so navigation is free
|
||||
window._clearPortConstraints?.();
|
||||
|
||||
// Step 2: fly to port (same as port search)
|
||||
window.flyToCoords?.(lon, lat, zoom);
|
||||
|
||||
// Step 3: after animation lands, lock zoom-out and extent
|
||||
if (port) {
|
||||
setTimeout(() => window._setPortConstraints?.(port), 1400); // 1200ms anim + margin
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch /org/me/company, cache result, apply to map.
|
||||
// Call this BEFORE showing the app, then re-apply after show (OL resize fix).
|
||||
async function _fetchAndApplyPort(token, role) {
|
||||
try {
|
||||
const res = await fetch(`${API}/org/me/company`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const me = await res.json();
|
||||
if (!me.port || me.port.center_lat == null || me.port.center_lon == null) return;
|
||||
|
||||
const isClient = (role === 'USER' || role === 'CLIENT_ADMIN');
|
||||
_portCache = {
|
||||
lon: me.port.center_lon,
|
||||
lat: me.port.center_lat,
|
||||
zoom: me.port.default_zoom || 12,
|
||||
port: isClient ? me.port : null,
|
||||
};
|
||||
_applyPortToMap();
|
||||
} catch (_) { /* non-fatal */ }
|
||||
}
|
||||
|
||||
// ── Login de inicio ───────────────────────────────────────────────────────────
|
||||
async function initStartupLogin() {
|
||||
const screen = document.getElementById('login-screen');
|
||||
@@ -367,9 +429,7 @@ async function initStartupLogin() {
|
||||
const btn = document.getElementById('ls-submit');
|
||||
const errEl = document.getElementById('ls-error');
|
||||
|
||||
// Validate the cached token against /auth/me before auto-entering. The
|
||||
// backend may have restarted / token may have expired — in that case we
|
||||
// force the user back to the login screen instead of showing a broken UI.
|
||||
// Validate cached token. If valid, configure map THEN show app.
|
||||
Auth.load();
|
||||
if (Auth.isLoggedIn()) {
|
||||
try {
|
||||
@@ -377,18 +437,22 @@ async function initStartupLogin() {
|
||||
headers: { Authorization: `Bearer ${Auth.token()}` },
|
||||
});
|
||||
if (r.ok) {
|
||||
try {
|
||||
await _fetchAndApplyPort(Auth.token(), Auth.session.role);
|
||||
app.classList.remove('hidden');
|
||||
window._mapUpdateSize?.();
|
||||
_applyPortToMap();
|
||||
} catch (e) {
|
||||
console.warn('[auth] map positioning error:', e);
|
||||
app.classList.remove('hidden');
|
||||
}
|
||||
screen.classList.add('hidden');
|
||||
app.classList.remove('hidden');
|
||||
afterLogin();
|
||||
return;
|
||||
}
|
||||
// 401 / network error → drop the stale session and show login
|
||||
Auth.clear();
|
||||
} catch {
|
||||
Auth.clear();
|
||||
}
|
||||
} catch { Auth.clear(); }
|
||||
}
|
||||
// Make sure login screen is visible if we got here
|
||||
screen.classList.remove('hidden');
|
||||
app.classList.add('hidden');
|
||||
|
||||
@@ -412,8 +476,18 @@ async function initStartupLogin() {
|
||||
const data = await res.json();
|
||||
Auth.save({ token: data.access_token, username: data.username,
|
||||
nombre: data.nombre, role: data.role });
|
||||
screen.classList.add('hidden');
|
||||
app.classList.remove('hidden');
|
||||
|
||||
// Map positioning is separate — errors here must NOT show credential message
|
||||
try {
|
||||
await _fetchAndApplyPort(data.access_token, data.role);
|
||||
app.classList.remove('hidden'); // show app BEHIND login screen (z-index:9999)
|
||||
window._mapUpdateSize?.(); // OL measures real container size
|
||||
_applyPortToMap(); // position map at correct port
|
||||
} catch (e) {
|
||||
console.warn('[auth] map positioning error:', e);
|
||||
app.classList.remove('hidden'); // show app anyway at default position
|
||||
}
|
||||
screen.classList.add('hidden'); // reveal map
|
||||
afterLogin();
|
||||
} catch {
|
||||
errEl.textContent = 'Invalid username or password.';
|
||||
@@ -425,7 +499,7 @@ async function initStartupLogin() {
|
||||
}
|
||||
|
||||
btn.addEventListener('click', doLogin);
|
||||
document.getElementById('ls-pass').addEventListener('keydown', (e) => {
|
||||
document.getElementById('ls-pass').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') doLogin();
|
||||
});
|
||||
}
|
||||
@@ -433,7 +507,6 @@ async function initStartupLogin() {
|
||||
function afterLogin() {
|
||||
if (window.PortSearch) window.PortSearch.init();
|
||||
Auth._renderBadge();
|
||||
// Sincronizar barra de estado inferior con usuario conectado
|
||||
const sbUser = document.getElementById('sb-user');
|
||||
if (sbUser && Auth.session) {
|
||||
sbUser.textContent = `${Auth.session.nombre} · ${Auth.session.role}`;
|
||||
|
||||
Reference in New Issue
Block a user