This commit is contained in:
naielv
2025-12-24 23:30:32 +01:00
parent 2258e74960
commit 13a4367c92
21 changed files with 1113 additions and 1020 deletions

View File

@@ -1,6 +1,6 @@
# TeleSec - Secure Distributed Communication Application
TeleSec is a Spanish Progressive Web Application (PWA) built with vanilla JavaScript, HTML, and CSS that provides secure group communication using GunDB for distributed peer-to-peer networking. The application allows users to join encrypted communication groups using group codes and secret keys.
TeleSec is a Spanish Progressive Web Application (PWA) built with vanilla JavaScript, HTML, and CSS that provides secure group communication using a local-first PouchDB datastore with optional CouchDB replication for syncing. The application allows users to join encrypted communication groups using group codes and secret keys.
**ALWAYS reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.**
@@ -75,8 +75,8 @@ TeleSec is a Spanish Progressive Web Application (PWA) built with vanilla JavaSc
│ ├── index.html # Main application HTML with %%PREFETCH%% variables
│ ├── app_logic.js # Core application logic and authentication
│ ├── app_modules.js # Application modules and utilities
│ ├── config.js # Configuration and relay servers
│ ├── gun_init.js # GunDB initialization
│ ├── config.js # Configuration and CouchDB setup
│ ├── db.js # PouchDB wrapper and replication
│ ├── pwa.js # Progressive Web App functionality
│ ├── sw.js # Service Worker with cache configuration
│ └── page/ # Individual page modules
@@ -94,8 +94,7 @@ TeleSec is a Spanish Progressive Web Application (PWA) built with vanilla JavaSc
├── manifest.json # PWA manifest
├── *.png, *.jpg # Icons and images
├── static/ # JavaScript libraries and CSS
│ ├── gun.js # GunDB library
│ ├── sea.js # GunDB encryption
│ ├── pouchdb (via CDN) # PouchDB is used for local storage and replication
│ ├── webrtc.js # WebRTC functionality
│ ├── euskaditech-css/ # CSS framework
│ └── ico/ # Application icons
@@ -116,18 +115,14 @@ The `build.py` script performs these operations:
### Technology Stack
- **Frontend:** Vanilla JavaScript, HTML5, CSS3
- **Data Layer:** GunDB - distributed, decentralized database
- **Networking:** WebRTC for peer-to-peer connections
- **Data Layer:** PouchDB (local-first) with optional CouchDB replication
- **Networking:** WebRTC for peer-to-peer connections (where applicable)
- **Authentication:** Group codes + secret keys (converted to uppercase)
- **Storage:** Browser LocalStorage + GunDB distributed storage
- **Storage:** Browser LocalStorage + PouchDB local datastore
- **PWA Features:** Service Worker, Web App Manifest
### GunDB Relay Servers
The application connects to multiple relay servers:
- gun-es01.tech.eus through gun-es06.tech.eus
- gun-manhattan.herokuapp.com
- peer.wallie.io
- gun.defucc.me
### Remote Sync (Optional)
The application can optionally replicate to a remote CouchDB server for cloud backup and multi-device syncing. Configure the CouchDB server, database name, and credentials in the login/setup form in the application.
### Application Modules
- **Login/Authentication:** Group-based access with secret keys
@@ -154,8 +149,8 @@ The application connects to multiple relay servers:
4. Rebuild and test complete user workflows
### Debugging Common Issues
- **Login Issues:** Check browser console for GunDB connection logs
- **Network Connectivity:** Verify relay servers are accessible
- **Login Issues:** Check browser console for replication/auth errors and DB initialization logs
- **Network Connectivity:** Verify remote CouchDB server is reachable and replication is active
- **Build Issues:** Check Python syntax in build.py
- **Performance:** Monitor browser DevTools Network tab for asset loading
@@ -177,12 +172,12 @@ After making any changes, ALWAYS test this complete scenario:
- Enter group code: "TEST"
- Enter secret key: "SECRET123"
- Click "Iniciar sesión"
- Verify header shows: "TeleSec - TEST - (X nodos)" where X > 0
- Verify header shows: "TeleSec - TEST" and that the login is accepted
3. **Network Connectivity Test:**
- Confirm green connection indicator appears (bottom right)
- Check browser console shows GunDB peer connections
- Verify heartbeat messages appear in console logs
- Check browser console shows PouchDB replication logs when a remote is configured
- Verify heartbeat/last-seen docs are being updated in the local DB
4. **PWA Functionality Test:**
- Check Service Worker registers successfully

View File

@@ -1,6 +1,6 @@
;(function(){
// assets/static/gun.js - Deprecated. Replaced by PouchDB (DB module).
console.warn('assets/static/gun.js is deprecated and unused.');
/* UNBUILD */
function USE(arg, req){
return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
arg(mod = {exports: {}});

View File

@@ -1,4 +1,6 @@
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
// assets/static/open.js - Deprecated. Part of Gun library, not used after migration to PouchDB.
console.warn('assets/static/open.js is deprecated and unused.');
var Gun = (typeof window !== "undefined")? window.Gun || {} : {};
Gun.chain.open = function(cb, opt, at, depth){ // this is a recursive function, BEWARE!
depth = depth || 1;

View File

@@ -1,6 +1,6 @@
;(function(){
// assets/static/sea.js - Deprecated. Replaced by CryptoJS AES usage in app_modules.js.
console.warn('assets/static/sea.js is deprecated and unused.');
/* UNBUILD */
function USE(arg, req){
return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
arg(mod = {exports: {}});

View File

@@ -6,49 +6,54 @@ function tableScroll(query) {
$(query).doubleScroll();
}
//var secretTokenEl = document.getElementById("secretToken");
var groupIdEl = document.getElementById("LinkAccount_group");
var container = document.getElementById("container");
function LinkAccount(LinkAccount_group, LinkAccount_secret, refresh = false) {
GROUPID = LinkAccount_group.toUpperCase();
SECRET = LinkAccount_secret.toUpperCase();
function LinkAccount(LinkAccount_secret, refresh = false) {
// Store group id for backward compatibility and keep secret
localStorage.setItem("TELESEC_AUTO", "YES");
localStorage.setItem("TELESEC_groupid", GROUPID);
SECRET = LinkAccount_secret.toUpperCase();
localStorage.setItem("TELESEC_secret", SECRET);
TABLE = GROUPID + ":telesec.tech.eus";
//secretTokenEl.innerText = SECRET;
groupIdEl.value = GROUPID;
if (refresh == true) {
location.reload();
}
// Initialize local DB and start replication if DB is available
try {
if (typeof DB !== 'undefined') {
const remoteServer = localStorage.getItem('TELESEC_COUCH_URL') || '';
const username = localStorage.getItem('TELESEC_COUCH_USER') || '';
const password = localStorage.getItem('TELESEC_COUCH_PASS') || '';
DB.init({ secret: SECRET, remoteServer, username, password, dbname: localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined });
}
} catch (e) {
console.warn('DB.init failed or not available yet', e);
}
}
if (localStorage.getItem("TELESEC_AUTO") == "YES") {
LinkAccount(
localStorage.getItem("TELESEC_groupid"),
localStorage.getItem("TELESEC_secret")
);
}
if (urlParams.get("login") != null) {
LinkAccount(
urlParams.get("login").split(":")[0],
urlParams.get("login").split(":")[1]
urlParams.get("enc_password"),
);
//location.search = "";
}
function open_page(params) {
EventListeners.GunJS.forEach(ev => ev.off());
EventListeners.GunJS = []
// Clear stored event listeners and timers
EventListeners.GunJS = [];
EventListeners.Timeout.forEach(ev => clearTimeout(ev));
EventListeners.Timeout = []
EventListeners.Timeout = [];
EventListeners.Interval.forEach(ev => clearInterval(ev));
EventListeners.Interval = []
EventListeners.Interval = [];
EventListeners.QRScanner.forEach(ev => ev.clear());
EventListeners.QRScanner = []
EventListeners.QRScanner = [];
EventListeners.Custom.forEach(ev => ev());
EventListeners.Custom = []
EventListeners.Custom = [];
if (SUB_LOGGED_IN != true && params != "login,setup") {
PAGES["login"].index();
return;
@@ -147,35 +152,13 @@ function fixGunLocalStorage() {
location.reload();
}
function betterGunPut(ref, data) {
ref.put(data, (ack) => {
if (ack.err) {
console.error("Ack failure", ack)
toastr.error(
ack.err + "<br>Pulsa aqui para reiniciar la app",
"Error al guardar", { onclick: () => fixGunLocalStorage() }
);
} else {
console.debug("Guardado correctamente");
}
});
setTimeout(() => {
ref.put(data);
}, 250);
setTimeout(() => {
ref.put(data);
}, 500);
}
// Heartbeat: store a small "last seen" doc locally and replicate to remote when available
setInterval(() => {
betterGunPut(
gun.get(TABLE).get("heartbeat"),
"heartbeat-" + CurrentISOTime()
);
gun.get(TABLE).get("heartbeat").load(console.debug);
if (typeof DB !== 'undefined') {
DB.put('heartbeat', getDBName() || 'heartbeat', 'heartbeat-' + CurrentISOTime());
}
}, 5000);
gun.get(TABLE).on((data) => {
var e = true;
});
function betterSorter(a, b) {
// 1. Fecha (ascending)

View File

@@ -538,57 +538,46 @@ const SC_actions = {
};
// Listado precargado de personas:
function TS_decrypt(input, secret, callback) {
// if input starts with "SEA{" and ends with "}", then it's encrypted with SEA
// if not, it is encrypted with RSA
// Only support AES-based encrypted payloads in the format: RSA{<ciphertext>} or plain objects/strings
if (typeof input != "string") {
callback(input);
} else if (input.startsWith("SEA{") && input.endsWith("}")) {
Gun.SEA.decrypt(input, secret).then((decrypted) => {
callback(decrypted);
});
} else if (input.startsWith("RSA{") && input.endsWith("}")) {
// ignore RSA{}
var data = input.slice(4, -1);
var decrypted = CryptoJS.AES.decrypt(data, secret).toString(
CryptoJS.enc.Utf8
);
callback(JSON.parse(decrypted));
var decrypted = CryptoJS.AES.decrypt(data, secret).toString(CryptoJS.enc.Utf8);
try {
callback(JSON.parse(decrypted));
} catch (e) {
console.error('TS_decrypt: invalid JSON', e);
callback(null);
}
} else {
// plain string (settings, etc.)
callback(input);
}
}
function TS_encrypt(input, secret, callback, mode = "RSA") {
if (mode == "SEA") {
Gun.TS_encrypt(input, secret).then((encrypted) => {
callback(encrypted);
});
} else if (mode == "RSA") {
var encrypted = CryptoJS.AES.encrypt(
JSON.stringify(input),
secret
).toString();
callback("RSA{" + encrypted + "}");
}
// Use AES symmetric encryption (RSA{} envelope for backwards compatibility)
var encrypted = CryptoJS.AES.encrypt(JSON.stringify(input), secret).toString();
callback("RSA{" + encrypted + "}");
}
gun
.get(TABLE)
.get("personas")
.map()
.on((data, key, _msg, _ev) => {
function add_row(data, key) {
if (data != null) {
data["_key"] = key;
SC_Personas[key] = data;
} else {
delete SC_Personas[key];
}
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data, key);
});
// Populate SC_Personas from DB (PouchDB)
DB.map('personas', (data, key) => {
function add_row(data, key) {
if (data != null) {
data["_key"] = key;
SC_Personas[key] = data;
} else {
add_row(data, key);
delete SC_Personas[key];
}
});
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data, key);
});
} else {
add_row(data, key);
}
});
function SC_parse(json) {
var out = "";
@@ -915,8 +904,19 @@ function TS_IndexElement(
event.stopPropagation();
data.Estado = state;
TS_encrypt(data, SECRET, (encrypted) => {
betterGunPut(ref.get(data._key), encrypted);
toastr.success("Guardado!");
if (typeof ref === 'string') {
DB.put(ref, data._key, encrypted).then(() => {
toastr.success("Guardado!");
});
} else {
try {
// legacy
ref.get(data._key).put(encrypted);
toastr.success("Guardado!");
} catch (e) {
console.warn('Could not save item', e);
}
}
});
return false;
};
@@ -1020,25 +1020,51 @@ function TS_IndexElement(
tablebody_EL.innerHTML = "";
tablebody_EL.appendChild(fragment);
}
ref.map().on((data, key, _msg, _ev) => {
EventListeners.GunJS.push(_ev);
function add_row(data, key) {
if (data != null) {
data["_key"] = key;
rows[key] = data;
} else {
delete rows[key];
// Subscribe to dataset updates using DB.map (PouchDB) when `ref` is a table name string
if (typeof ref === 'string') {
DB.map(ref, (data, key) => {
function add_row(data, key) {
if (data != null) {
data["_key"] = key;
rows[key] = data;
} else {
delete rows[key];
}
debounce(debounce_load, render, 300, [rows]);
}
debounce(debounce_load, render, 300, [rows]);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data, key);
});
} else {
add_row(data, key);
}
});
} else if (ref && typeof ref.map === 'function') {
// Legacy: try to use ref.map().on if available (for backwards compatibility)
try {
ref.map().on((data, key, _msg, _ev) => {
function add_row(data, key) {
if (data != null) {
data["_key"] = key;
rows[key] = data;
} else {
delete rows[key];
}
debounce(debounce_load, render, 300, [rows]);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data, key);
});
} else {
add_row(data, key);
}
});
} else {
add_row(data, key);
} catch (e) {
console.warn('TS_IndexElement: cannot subscribe to ref', e);
}
});
}
}
function BuildQR(mid, label) {
@@ -1103,6 +1129,50 @@ function SetPages() {
var Booted = false;
var TimeoutBoot = 6;
var BootLoops = 0;
function getPeers() {
const peerCountEl = document.getElementById("peerCount");
const peerListEl = document.getElementById("peerList");
const pidEl = document.getElementById("peerPID");
const statusImg = document.getElementById("connectStatus");
// Default status based on navigator
if (window.navigator && window.navigator.onLine === false) {
peerCountEl.innerText = "offline";
if (statusImg) statusImg.src = "static/ico/offline.svg";
} else {
peerCountEl.innerText = "local";
if (statusImg) statusImg.src = "static/ico/connect_ok.svg";
}
// Clear previous list
if (peerListEl) peerListEl.innerHTML = "";
// Show local DB stats if available
if (window.DB && DB._internal && DB._internal.local) {
DB._internal.local
.info()
.then((info) => {
if (peerListEl) {
const li = document.createElement("li");
li.innerText = `Local DB: ${info.db_name || "telesec"} (docs: ${info.doc_count || 0})`;
peerListEl.appendChild(li);
}
if (pidEl) pidEl.innerText = `DB: ${info.db_name || "telesec"}`;
})
.catch(() => {
if (peerListEl) {
const li = document.createElement("li");
li.innerText = "Local DB: unavailable";
peerListEl.appendChild(li);
}
if (pidEl) pidEl.innerText = "DB: local";
});
} else {
if (pidEl) pidEl.innerText = "DB: none";
}
}
getPeers();
setInterval(() => {
getPeers();
@@ -1111,34 +1181,73 @@ setInterval(() => {
var BootIntervalID = setInterval(() => {
BootLoops += 1;
getPeers();
if (
(BootLoops >= TimeoutBoot || window.navigator.onLine == false) &&
!Booted
) {
Booted = true;
document.getElementById("loading").style.display = "none";
toastr.error(
"Sin conexion! Los cambios se sincronizarán cuando te vuelvas a conectar."
);
if (!SUB_LOGGED_IN) {
open_page("login");
} else {
SetPages();
open_page(location.hash.replace("#", ""));
const isOnline = window.navigator ? window.navigator.onLine !== false : true;
// Check if local DB is initialized and responsive
const checkLocalDB = () => {
if (window.DB && DB._internal && DB._internal.local) {
return DB._internal.local.info().then(() => true).catch(() => false);
}
clearInterval(BootIntervalID);
}
if (ConnectionStarted && !Booted) {
Booted = true;
document.getElementById("loading").style.display = "none";
if (!SUB_LOGGED_IN) {
open_page("login");
return;
return Promise.resolve(false);
};
checkLocalDB().then((dbReady) => {
// If offline, or DB ready, or we've waited long enough, proceed to boot the UI
if ((dbReady || !isOnline || BootLoops >= TimeoutBoot) && !Booted) {
Booted = true;
document.getElementById("loading").style.display = "none";
if (!isOnline) {
toastr.error(
"Sin conexión! Los cambios se sincronizarán cuando vuelvas a estar en línea."
);
}
if (!SUB_LOGGED_IN) {
if (AC_BYPASS) {
// Auto-create or load a bypass persona and log in automatically
const bypassId = localStorage.getItem('TELESEC_BYPASS_ID') || 'bypass-admin';
if (window.DB && DB.get) {
DB.get('personas', bypassId).then((data) => {
function finish(pdata, id) {
SUB_LOGGED_IN_ID = id || bypassId;
SUB_LOGGED_IN_DETAILS = pdata || {};
SUB_LOGGED_IN = true;
localStorage.setItem('TELESEC_BYPASS_ID', SUB_LOGGED_IN_ID);
SetPages();
open_page(location.hash.replace("#", ""));
}
if (!data) {
const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' };
TS_encrypt(persona, SECRET, (encrypted) => {
DB.put('personas', bypassId, encrypted).then(() => finish(persona, bypassId)).catch((e) => { console.warn('AC_BYPASS create error', e); open_page('login'); });
});
} else {
if (typeof data === 'string') {
TS_decrypt(data, SECRET, (pdata) => finish(pdata, bypassId));
} else {
finish(data, bypassId);
}
}
}).catch((e) => {
console.warn('AC_BYPASS persona check error', e);
open_page('login');
});
} else {
// DB not ready, fallback to login page
open_page('login');
}
} else {
open_page("login");
}
} else {
SetPages();
open_page(location.hash.replace("#", ""));
}
clearInterval(BootIntervalID);
}
SetPages();
open_page(location.hash.replace("#", ""));
clearInterval(BootIntervalID);
}
});
}, 750);
const tabs = document.querySelectorAll(".ribbon-tab");
@@ -1238,20 +1347,16 @@ function GlobalSearch() {
function loadAllData() {
searchableModules.forEach((module) => {
searchData[module.key] = {};
gun
.get(TABLE)
.get(module.key)
.map()
.on((data, key) => {
if (!data) return;
DB.map(module.key, (data, key) => {
if (!data) return;
function processData(processedData) {
if (processedData && typeof processedData === "object") {
searchData[module.key][key] = {
_key: key,
_module: module.key,
_title: module.title,
_icon: module.icon,
function processData(processedData) {
if (processedData && typeof processedData === "object") {
searchData[module.key][key] = {
_key: key,
_module: module.key,
_title: module.title,
_icon: module.icon,
...processedData,
};
}

View File

@@ -5,6 +5,10 @@ var EventListeners = {
QRScanner: [],
Custom: [],
};
function safeuuid(prefix = "AXLUID_") {
return prefix + crypto.randomUUID().split("-")[4];
}
var urlParams = new URLSearchParams(location.search);
var AC_BYPASS = false;
if (urlParams.get("ac_bypass") == "yes") {
@@ -13,28 +17,17 @@ if (urlParams.get("ac_bypass") == "yes") {
if (urlParams.get("hidenav") != undefined) {
document.getElementById("header_hide_query").style.display = "none";
}
var GROUPID = "";
// getDBName: prefer explicit CouchDB dbname from settings. Single-group model: default to 'telesec'
function getDBName() {
const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || '';
if (dbname && dbname.trim() !== '') return dbname.trim();
return 'telesec';
}
// const PUBLIC_KEY = "~cppGiuA4UFUPGTDoC-4r2izVC3F7MfpaCmF3iZdESN4.vntmjgbAVUpF_zfinYY6EKVFuuTYxh5xOrL4KmtdTmc"
var TABLE = GROUPID + ":telesec.tech.eus";
const RELAYS = [
"https://gun-es01.tech.eus/gun",
// Desactivado por alto consumo electrico:
// "https://gun-es02.tech.eus/gun",
// "https://gun-es03.tech.eus/gun",
// "https://gun-es04.tech.eus/gun",
// "https://gun-es05.tech.eus/gun",
// "https://gun-es06.tech.eus/gun",
// "https://gun-es07.tech.eus/gun", // No he podido instalar el nodo.
// Desactivado por fallos:
// "https://gun-manhattan.herokuapp.com/gun",
// "https://peer.wallie.io/gun",
// "https://gundb-relay-mlccl.ondigitalocean.app/gun",
// "https://plankton-app-6qfp3.ondigitalocean.app/",
// "https://gun.defucc.me/gun",
// "https://gun.o8.is/gun",
// "https://shogun-relay.scobrudot.dev/gun",
];
// `TABLE` variable removed. The CouchDB database name should be configured via the login/setup form
// and passed to `DB.init({ dbname: '<your-db>' })` so it becomes the app's primary DB.
// Legacy relay list removed (migrated to CouchDB/PouchDB)
const RELAYS = [];
var SECRET = "";
var SUB_LOGGED_IN = false;
var SUB_LOGGED_IN_DETAILS = false;

138
src/db.js Normal file
View File

@@ -0,0 +1,138 @@
// Simple PouchDB wrapper for TeleSec
// - Uses PouchDB for local storage and optional replication to a CouchDB server
// - Stores records as docs with _id = "<table>:<id>" and field `data` containing either plain object or encrypted string
// - Exposes: init, put, get, del, map, list, replicate
var DB = (function () {
let local = null;
let secret = null;
let remote = null;
let changes = null;
let callbacks = {}; // table -> [cb]
function makeId(table, id) {
return table + ':' + id;
}
function init(opts) {
// opts: { secret, remoteServer, username, password, dbname }
secret = opts.secret || '';
const localName = opts.dbname || localStorage.getItem('TELESEC_COUCH_DBNAME') || 'telesec';
local = new PouchDB(localName);
if (opts.remoteServer) {
try {
const server = opts.remoteServer.replace(/\/$/, '');
const dbname = encodeURIComponent((opts.dbname || localName));
let authPart = '';
if (opts.username) {
authPart = opts.username + ':' + (opts.password || '') + '@';
}
const remoteUrl = server.replace(/https?:\/\//, (m) => m) + '/' + dbname;
// to keep things simple, embed credentials if provided
if (opts.username) {
remote = new PouchDB(remoteUrl.replace(/:\/\//, '://' + authPart));
} else {
remote = new PouchDB(remoteUrl);
}
replicateToRemote();
} catch (e) {
console.warn('Remote DB init error', e);
}
}
if (changes) changes.cancel();
changes = local.changes({ live: true, since: 'now', include_docs: true }).on('change', onChange);
return Promise.resolve();
}
function replicateToRemote() {
if (!local || !remote) return;
PouchDB.replicate(local, remote, { live: true, retry: true }).on('error', function (err) {
console.warn('Replication error', err);
});
PouchDB.replicate(remote, local, { live: true, retry: true }).on('error', function (err) {
console.warn('Replication error', err);
});
}
function onChange(change) {
const doc = change.doc;
if (!doc || !doc._id) return;
const [table, id] = doc._id.split(':');
if (!callbacks[table]) return;
callbacks[table].forEach((cb) => {
try { cb(doc.data, id); } catch (e) { console.error(e); }
});
}
async function put(table, id, data) {
const _id = makeId(table, id);
try {
const existing = await local.get(_id).catch(() => null);
if (data === null) {
// delete
if (existing) {
await local.remove(existing);
}
return;
}
const doc = existing || { _id: _id };
doc.data = data;
doc.table = table;
doc.ts = new Date().toISOString();
if (existing) doc._rev = existing._rev;
await local.put(doc);
} catch (e) {
console.error('DB.put error', e);
}
}
async function get(table, id) {
const _id = makeId(table, id);
try {
const doc = await local.get(_id);
return doc.data;
} catch (e) {
return null;
}
}
async function del(table, id) {
return put(table, id, null);
}
async function list(table) {
try {
const res = await local.allDocs({ include_docs: true, startkey: table + ':', endkey: table + ':\uffff' });
return res.rows.map(r => {
const id = r.id.split(':')[1];
return { id: id, data: r.doc.data };
});
} catch (e) { return []; }
}
function map(table, cb) {
callbacks[table] = callbacks[table] || [];
callbacks[table].push(cb);
// initial load
list(table).then(rows => rows.forEach(r => cb(r.data, r.id)));
// return unsubscribe
return () => {
callbacks[table] = callbacks[table].filter(x => x !== cb);
}
}
return {
init,
put,
get,
del,
list,
map,
replicateToRemote,
_internal: { local }
};
})();
window.DB = DB;

View File

@@ -1,113 +1,4 @@
window.rtcRoom = "telesec.tech.eus";
var opt = {
axe: false,
localStorage: false,
peers: RELAYS,
// radisk: true,
};
opt.store = RindexedDB(opt);
var gun = Gun(opt);
var SEA = Gun.SEA;
var user = gun.user();
function removeCache() {
caches.keys().then(function (names) {
for (let name of names) caches.delete(name);
console.log("Removing cache " + name);
console.log("OK");
location.reload(true);
});
}
var AtLeastThreePeers = false;
var ConnectionStarted = false;
function formatPeerInfo(peer) {
const wireType = peer.wire.constructor.name;
let wireHType = wireType;
let wireID = peer.id;
switch (wireType) {
case "WebSocket":
wireHType = "Web";
wireID = wireID.split("/")[2];
break;
case "RTCDataChannel":
wireHType = "Mesh";
break;
}
return { wireHType, wireID };
}
function isPeerConnected(peer) {
return (
peer.wire != undefined &&
(peer.wire.readyState == 1 || peer.wire.readyState == "open")
);
}
function createPeerListElement(wireHType, wireID) {
const el = document.createElement("li");
el.innerText = `Nodo ${wireHType}: ${wireID}`;
return el;
}
function updateConnectionStatus(peerCount) {
var statusImage = peerCount < 1 ? "connect_ko.svg" : "connect_ok.svg";
if (window.navigator.onLine == false) {
statusImage = "offline.svg";
}
document.getElementById("connectStatus").src = `static/ico/${statusImage}`;
if (peerCount < 1) {
if (!window.peerRetryCount) window.peerRetryCount = 0;
window.peerRetryCount = (window.peerRetryCount + 1) % 3;
if (window.peerRetryCount === 0) {
gun.opt({ peers: RELAYS });
// Enhanced peer connection retry
setTimeout(() => {
gun.opt({ peers: RELAYS });
}, 1000);
setTimeout(() => {
gun.opt({ peers: RELAYS });
}, 3000);
}
AtLeastThreePeers = false;
} else {
ConnectionStarted = true;
AtLeastThreePeers = true;
// When we have good connectivity, force a data refresh
if (typeof refreshAllData === "function") {
setTimeout(refreshAllData, 1000);
}
}
}
function getPeers() {
const peerCountEl = document.getElementById("peerCount");
const peerListEl = document.getElementById("peerList");
const list = document.createElement("ul");
document.getElementById("peerPID").innerText = "PID " + gun.back("opt.pid");
const connectedPeers = Object.values(gun.back("opt.peers"))
.filter(isPeerConnected)
.map((peer) => {
const { wireHType, wireID } = formatPeerInfo(peer);
return createPeerListElement(wireHType, wireID);
});
connectedPeers.forEach((el) => list.append(el));
peerListEl.innerHTML = list.innerHTML;
const peerCount = connectedPeers.length;
peerCountEl.innerText = peerCount;
updateConnectionStatus(peerCount);
}
function safeuuid(prefix = "AXLUID_") {
return prefix + crypto.randomUUID().split("-")[4];
}
// gun_init.js - Deprecated
// Gun/GunDB has been replaced by PouchDB/CouchDB in this project.
// This file is kept for reference only and should not be used.
console.warn('gun_init.js is deprecated; using PouchDB/DB module instead.');

View File

@@ -48,15 +48,12 @@
<div class="ribbon-panel">
<div>
<input type="text" id="LinkAccount_group" placeholder="Usuario..." />
<input type="password" id="LinkAccount_secret" placeholder="Contraseña / Token..." />
<br />
<input type="text" id="LinkAccount_secret" placeholder="Contraseña..." />
<br />
<button type="button" onclick='LinkAccount(document.getElementById("LinkAccount_group").value, document.getElementById("LinkAccount_secret").value, true)'>
<button type="button" onclick='LinkAccount(document.getElementById("LinkAccount_secret").value, true)'>
Iniciar sesión
</button><br>
<b>Conectado a <span id="peerCount">?</span>
nodos.</b>
<b>Estado de sincronización: <span id="peerCount">Desconocido</span></b>
</div>
</div>
</details>
@@ -67,8 +64,8 @@
<img id="loading" src="load.gif" style="display: block; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: calc(100% - 50px); max-width: 400px;" />
<details class="supermesh-indicator">
<summary>
<b>SuperMesh</b><br />
<br /><small id="peerPID" style="font-family: monospace">PID ??????????</small>
<b>Sincronización</b><br />
<br /><small id="peerPID" style="font-family: monospace">Estado: local</small>
</summary>
<ul id="peerList"></ul>
<i>Todos los datos están encriptados.</i>
@@ -93,17 +90,7 @@
<script src="static/qrcode/html5-qrcode.min.js"></script>
<script src="static/qrcode/barcode.js"></script>
<script src="static/jquery.js"></script>
<script src="static/gun.js"></script>
<script src="static/webrtc.js"></script>
<script src="static/sea.js"></script>
<script src="static/yson.js"></script>
<script src="static/radix.js"></script>
<script src="static/radisk.js"></script>
<script src="static/store.js"></script>
<script src="static/rindexed.js"></script>
<script src="static/path.js"></script>
<script src="static/open.js"></script>
<script src="static/load.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pouchdb@7.3.1/dist/pouchdb.min.js"></script>
<!--<script src="static/synchronous.js"></script>-->
<!--<script src="static/axe.js"></script>-->
<script src="static/toastr.min.js"></script>
@@ -111,7 +98,7 @@
<!--<script src="static/simplemde.min.js"></script>-->
<script src="pwa.js"></script>
<script src="config.js"></script>
<script src="gun_init.js"></script>
<script src="db.js"></script>
<script src="app_logic.js"></script>
<script src="app_modules.js"></script>
<script src="page/login.js"></script>

View File

@@ -45,96 +45,81 @@ PAGES.aulas = {
//#region Cargar Clima
// Get location from gun.get("settings").get("weather_location"), if missing ask user and save it
// Get location from DB settings.weather_location; if missing ask user and save it
// url format: https://wttr.in/<loc>?F0m
gun
.get("settings")
.get("weather_location")
.once((loc) => {
if (!loc) {
loc = prompt("Introduce tu ubicación para el clima (ciudad, país):", "Madrid, Spain");
if (loc) {
betterGunPut(gun.get("settings").get("weather_location"), loc);
}
}
DB.get('settings','weather_location').then((loc) => {
if (!loc) {
loc = prompt("Introduce tu ubicación para el clima (ciudad, país):", "Madrid, Spain");
if (loc) {
document.getElementById(data_Weather).src = "https://wttr.in/" + encodeURIComponent(loc) + "_IF0m_background=FFFFFF.png";
} else {
document.getElementById(data_Weather).src = "https://wttr.in/_IF0m_background=FFFFFF.png";
DB.put('settings','weather_location', loc);
}
});
}
if (loc) {
document.getElementById(data_Weather).src = "https://wttr.in/" + encodeURIComponent(loc) + "_IF0m_background=FFFFFF.png";
} else {
document.getElementById(data_Weather).src = "https://wttr.in/_IF0m_background=FFFFFF.png";
}
});
//#endregion Cargar Clima
//#region Cargar Comedor
gun
.get(TABLE)
.get("comedor")
.get(CurrentISODate())
.once((data, key) => {
function add_row(data) {
// Fix newlines
data.Platos = data.Platos || "No hay platos registrados para hoy.";
// Display platos
document.getElementById(data_Comedor).innerHTML = data.Platos.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
});
} else {
DB.get('comedor', CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Platos = data.Platos || "No hay platos registrados para hoy.";
// Display platos
document.getElementById(data_Comedor).innerHTML = data.Platos.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
}
});
});
} else {
add_row(data || {});
}
});
//#endregion Cargar Comedor
//#region Cargar Tareas
gun
.get(TABLE)
.get("notas")
.get("tareas")
.once((data, key) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay tareas.";
// Display platos
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
});
} else {
DB.get('notas', 'tareas').then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay tareas.";
// Display platos
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
}
});
});
} else {
add_row(data || {});
}
});
//#endregion Cargar Tareas
//#region Cargar Diario
gun
.get(TABLE)
.get("aulas_informes")
.get("diario-" + CurrentISODate())
.once((data, key) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay un diario.";
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
});
} else {
DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay un diario.";
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
}
});
});
} else {
add_row(data || {});
}
});
//#endregion Cargar Diario
},
_solicitudes: function () {
@@ -162,7 +147,7 @@ PAGES.aulas = {
label: "Asunto",
},
],
gun.get(TABLE).get("aulas_solicitudes"),
"aulas_solicitudes",
document.querySelector("#cont")
);
document.getElementById(btn_new).onclick = () => {
@@ -197,26 +182,22 @@ PAGES.aulas = {
<button class="rojo" id="${btn_borrar}">Borrar</button>
</fieldset>
`;
gun
.get(TABLE)
.get("aulas_solicitudes")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_asunto).value = data["Asunto"] || "";
document.getElementById(field_contenido).value =
data["Contenido"] || "";
document.getElementById(field_autor).value = data["Solicitante"] || SUB_LOGGED_IN_ID || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
(async () => {
const data = await DB.get('aulas_solicitudes', mid);
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_asunto).value = data["Asunto"] || "";
document.getElementById(field_contenido).value = data["Contenido"] || "";
document.getElementById(field_autor).value = data["Solicitante"] || SUB_LOGGED_IN_ID || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
})();
document.getElementById(btn_guardar).onclick = () => {
var data = {
Solicitante: document.getElementById(field_autor).value,
@@ -225,21 +206,23 @@ PAGES.aulas = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("aulas_solicitudes").get(mid), encrypted);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,solicitudes");
}, SAVE_WAIT);
DB.put('aulas_solicitudes', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,solicitudes");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta solicitud?") == true) {
betterGunPut(gun.get(TABLE).get("aulas_solicitudes").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("aulas,solicitudes");
}, SAVE_WAIT);
DB.del('aulas_solicitudes', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("aulas,solicitudes");
}, SAVE_WAIT);
});
}
};
},
@@ -281,7 +264,7 @@ PAGES.aulas = {
label: "Asunto",
},
],
gun.get(TABLE).get("aulas_informes"),
"aulas_informes",
document.querySelector("#cont")
);
document.getElementById(btn_new).onclick = () => {
@@ -331,27 +314,23 @@ PAGES.aulas = {
<button class="rojo" id="${btn_borrar}">Borrar</button>
</fieldset>
`;
gun
.get(TABLE)
.get("aulas_informes")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_asunto).value = data["Asunto"] || title || "";
document.getElementById(field_contenido).value =
data["Contenido"] || "";
document.getElementById(field_autor).value = data["Autor"] || SUB_LOGGED_IN_ID || "";
document.getElementById(field_fecha).value = data["Fecha"] || mid.startsWith("diario-") ? mid.replace("diario-", "") : CurrentISODate();
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
(async () => {
const data = await DB.get('aulas_informes', mid);
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_asunto).value = data["Asunto"] || title || "";
document.getElementById(field_contenido).value = data["Contenido"] || "";
document.getElementById(field_autor).value = data["Autor"] || SUB_LOGGED_IN_ID || "";
document.getElementById(field_fecha).value = data["Fecha"] || mid.startsWith("diario-") ? mid.replace("diario-", "") : CurrentISODate();
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
})();
document.getElementById(btn_guardar).onclick = () => {
var data = {
Autor: document.getElementById(field_autor).value,
@@ -361,21 +340,23 @@ PAGES.aulas = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("aulas_informes").get(mid), encrypted);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,informes");
}, SAVE_WAIT);
DB.put('aulas_informes', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,informes");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar este informe?") == true) {
betterGunPut(gun.get(TABLE).get("aulas_informes").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("aulas,informes");
}, SAVE_WAIT);
DB.del('aulas_informes', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("aulas,informes");
}, SAVE_WAIT);
});
}
};
},

View File

@@ -80,53 +80,47 @@ PAGES.avisos = {
},
"Destino"
);
gun
.get(TABLE)
.get("notificaciones")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_fecha).value = data["Fecha"] || CurrentISODate() || "";
document.getElementById(field_asunto).value = data["Asunto"] || "";
document.getElementById(field_mensaje).value =
data["Mensaje"] || "";
document.getElementById(field_origen).value = data["Origen"] || SUB_LOGGED_IN_ID || "";
document.getElementById(field_destino).value =
data["Destino"] || "";
document.getElementById(field_estado).value = data["Estado"] || "%%" || "";
document.getElementById(field_respuesta).value =
data["Respuesta"] || "";
(async () => {
const data = await DB.get('notificaciones', mid);
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_fecha).value = data["Fecha"] || CurrentISODate() || "";
document.getElementById(field_asunto).value = data["Asunto"] || "";
document.getElementById(field_mensaje).value = data["Mensaje"] || "";
document.getElementById(field_origen).value = data["Origen"] || SUB_LOGGED_IN_ID || "";
document.getElementById(field_destino).value = data["Destino"] || "";
document.getElementById(field_estado).value = data["Estado"] || "%%" || "";
document.getElementById(field_respuesta).value = data["Respuesta"] || "";
// Persona select
divact.innerHTML = "";
addCategory_Personas(
divact,
SC_Personas,
data["Origen"] || "",
(value) => {
document.getElementById(field_origen).value = value;
},
"Origen"
);
addCategory_Personas(
divact,
SC_Personas,
data["Destino"] || "",
(value) => {
document.getElementById(field_destino).value = value;
},
"Destino"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
// Persona select
divact.innerHTML = "";
addCategory_Personas(
divact,
SC_Personas,
data["Origen"] || "",
(value) => {
document.getElementById(field_origen).value = value;
},
"Origen"
);
addCategory_Personas(
divact,
SC_Personas,
data["Destino"] || "",
(value) => {
document.getElementById(field_destino).value = value;
},
"Destino"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
})();
document.getElementById(btn_guardar).onclick = () => {
if (document.getElementById(field_origen).value == "") {
alert("¡Hay que elegir una persona de origen!");
@@ -149,24 +143,23 @@ PAGES.avisos = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(
gun.get(TABLE).get("notificaciones").get(mid),
encrypted
);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("avisos");
}, SAVE_WAIT);
DB.put('notificaciones', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("avisos");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta notificación?") == true) {
betterGunPut(gun.get(TABLE).get("notificaciones").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("avisos");
}, SAVE_WAIT);
DB.del('notificaciones', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("avisos");
}, SAVE_WAIT);
});
}
};
},
@@ -207,7 +200,7 @@ PAGES.avisos = {
label: "Estado",
},
],
gun.get(TABLE).get("notificaciones"),
"notificaciones",
document.querySelector("#cont"),
(data, new_tr) => {
new_tr.style.backgroundColor = "#FFCCCB";

View File

@@ -28,25 +28,21 @@ PAGES.comedor = {
<button class="rojo" id="${btn_borrar}">Borrar</button>
</fieldset>
`;
gun
.get(TABLE)
.get("comedor")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_fecha).value = data["Fecha"] || mid || CurrentISODate();
document.getElementById(field_platos).value =
data["Platos"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
DB.get('comedor', mid).then((data) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_fecha).value = data["Fecha"] || mid || CurrentISODate();
document.getElementById(field_platos).value =
data["Platos"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
document.getElementById(btn_guardar).onclick = () => {
const newDate = document.getElementById(field_fecha).value;
var data = {
@@ -56,26 +52,28 @@ PAGES.comedor = {
// If the date has changed, we need to delete the old entry
if (mid !== newDate && mid !== "") {
betterGunPut(gun.get(TABLE).get("comedor").get(mid), null);
DB.del('comedor', mid);
}
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("comedor").get(newDate), encrypted);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("comedor");
}, SAVE_WAIT);
DB.put('comedor', newDate, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("comedor");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta entrada?") == true) {
betterGunPut(gun.get(TABLE).get("comedor").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("comedor");
}, SAVE_WAIT);
DB.del('comedor', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("comedor");
}, SAVE_WAIT);
});
}
};
},
@@ -104,7 +102,7 @@ PAGES.comedor = {
label: "Platos",
}
],
gun.get(TABLE).get("comedor"),
"comedor",
document.getElementById(cont),
(data, new_tr) => {
// new_tr.style.backgroundColor = "#FFCCCB";

View File

@@ -59,7 +59,7 @@ PAGES.dataman = {
<button id="${button_export_local}" type="button">Exportar sin cifrar</button>
<button id="${button_export_safe}" type="button">Exportar con cifrado</button>
<button id="${button_export_safe_cloud}" style="display: none;" type="button">Exportar a EuskadiTech - cifrado</button>
<!--<br><br><em>Para descargar envia un correo a telesec@tech.eus con el asunto "TSBK %${GROUPID}".</em>-->
<!--<br><br><em>Para descargar envia un correo a telesec@tech.eus con el asunto "TSBK %${getDBName()}".</em>-->
</fieldset>
`;
document.getElementById(button_export_local).onclick = () => {
@@ -68,47 +68,61 @@ PAGES.dataman = {
materiales: {},
personas: {},
};
var download_data = (DATA) => {
Object.keys(DATA).forEach((modul) => {
Object.entries(DATA[modul] || {}).forEach((entry) => {
var key = entry[0];
var value = entry[1];
if (value != null) {
if (typeof value == "string") {
TS_decrypt(value, SECRET, (data) => {
output[modul][key] = data;
});
} else {
output[modul][key] = value;
}
(async () => {
const materiales = await DB.list('materiales');
materiales.forEach(entry => {
const key = entry.id;
const value = entry.data;
if (value != null) {
if (typeof value == 'string') {
TS_decrypt(value, SECRET, (data) => {
output.materiales[key] = data;
});
} else {
output.materiales[key] = value;
}
});
toastr.success("Exportado todo, descargando!");
download(
`Export %%TITLE%% ${GROUPID}.json.txt`,
JSON.stringify(output)
);
//setUrlHash(sel);
}, 2500);
};
gun.get(TABLE).load(download_data);
}
});
const personas = await DB.list('personas');
personas.forEach(entry => {
const key = entry.id;
const value = entry.data;
if (value != null) {
if (typeof value == 'string') {
TS_decrypt(value, SECRET, (data) => {
output.personas[key] = data;
});
} else {
output.personas[key] = value;
}
}
});
toastr.success("Exportado todo, descargando!");
download(
`Export %%TITLE%% ${getDBName()}.json.txt`,
JSON.stringify(output)
);
})();
};
document.getElementById(button_export_safe).onclick = () => {
var download_data = (DATA) => {
(async () => {
const result = { materiales: {}, personas: {} };
const materiales = await DB.list('materiales');
materiales.forEach(entry => { result.materiales[entry.id] = entry.data; });
const personas = await DB.list('personas');
personas.forEach(entry => { result.personas[entry.id] = entry.data; });
toastr.success("Exportado todo, descargado!");
download(
`Export %%TITLE%% Encriptado ${GROUPID}.json.txt`,
JSON.stringify(DATA)
`Export %%TITLE%% Encriptado ${getDBName()}.json.txt`,
JSON.stringify(result)
);
//setUrlHash(sel);
};
gun.get(TABLE).load(download_data);
})();
};
// document.getElementById(button_export_safe_cloud).onclick = () => {
// var download_data = (DATA) => {
// toastr.info("Exportado todo, subiendo!");
// fetch(
// "https://telesec-sync.tech.eus/upload_backup.php?table=" + GROUPID,
// "https://telesec-sync.tech.eus/upload_backup.php?table=" + getDBName(),
// {
// method: "POST",
// body: JSON.stringify(DATA),
@@ -153,13 +167,12 @@ PAGES.dataman = {
var val = document.getElementById(textarea_content).value;
var sel = document.getElementById(select_type).value;
if (sel == "%telesec") {
gun.get(TABLE).put(JSON.parse(val), (ack) => {
toastr.info("Importado " + entry[0] + ".");
});
// legacy import, store entire payload as-is
DB.put('%telesec', 'export_' + Date.now(), JSON.parse(val));
} else {
Object.entries(JSON.parse(val)["data"]).forEach((entry) => {
var enc = TS_encrypt(entry[1], SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get(sel).get(entry[0]), encrypted);
DB.put(sel, entry[0], encrypted);
});
});
}
@@ -183,21 +196,17 @@ PAGES.dataman = {
<div id="${div_materiales}"></div>
<br><br>`;
div_materiales = document.getElementById(div_materiales);
gun
.get(TABLE)
.get("materiales")
.map()
.once((data, key) => {
function add_row(data, key) {
if (data != null) {
div_materiales.innerHTML += BuildQR(
"materiales," + key,
data["Nombre"] || key
);
}
DB.map('materiales', (data, key) => {
function add_row(data, key) {
if (data != null) {
div_materiales.innerHTML += BuildQR(
"materiales," + key,
data["Nombre"] || key
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data, key);
});
} else {

View File

@@ -2,12 +2,47 @@ PAGES.login = {
Esconder: true,
Title: "Login",
edit: function (mid) {
// Setup form to configure CouchDB remote and initial group/secret
var field_couch = safeuuid();
var field_couch_dbname = safeuuid();
var field_couch_user = safeuuid();
var field_couch_pass = safeuuid();
var btn_save = safeuuid();
container.innerHTML = `
<h1>Empezar desde cero - No disponible</h1>
<h2>Paso 1: Rellena los credenciales</h2>
<h2>Paso 2: Crea una cuenta administrativa</h2>
<h2>Y ya está!</h2>
`
<h1>Configuración del servidor CouchDB</h1>
<fieldset>
<label>Servidor CouchDB (ej: https://couch.example.com)
<input type="text" id="${field_couch}" value="${localStorage.getItem('TELESEC_COUCH_URL') || ''}"><br><br>
</label>
<label>Nombre de la base (opcional, por defecto usa telesec-<grupo>)
<input type="text" id="${field_couch_dbname}" value="${localStorage.getItem('TELESEC_COUCH_DBNAME') || ''}"><br><br>
</label>
<label>Usuario
<input type="text" id="${field_couch_user}" value="${localStorage.getItem('TELESEC_COUCH_USER') || ''}"><br><br>
</label>
<label>Contraseña
<input type="password" id="${field_couch_pass}" value="${localStorage.getItem('TELESEC_COUCH_PASS') || ''}"><br><br>
</label>
<button id="${btn_save}" class="btn5">Guardar y Conectar</button>
</fieldset>
<p>Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor.</p>
`;
document.getElementById(btn_save).onclick = () => {
var url = document.getElementById(field_couch).value.trim();
var dbname = document.getElementById(field_couch_dbname).value.trim();
var user = document.getElementById(field_couch_user).value.trim();
var pass = document.getElementById(field_couch_pass).value;
localStorage.setItem('TELESEC_COUCH_URL', url);
localStorage.setItem('TELESEC_COUCH_DBNAME', dbname);
localStorage.setItem('TELESEC_COUCH_USER', user);
localStorage.setItem('TELESEC_COUCH_PASS', pass);
try {
DB.init({ secret: SECRET, remoteServer: url, username: user, password: pass, dbname: dbname || undefined });
toastr.success('Iniciando sincronización con CouchDB');
} catch (e) {
toastr.error('Error al iniciar sincronización: ' + e.message);
}
};
},
index: function (mid) {
var field_persona = safeuuid();
@@ -23,7 +58,8 @@ PAGES.login = {
<button class="btn5" id="${btn_guardar}">Acceder</button>
<button class="btn1" id="${btn_reload}">Recargar lista</button>
</fieldset>
<a href="#login,setup">Empezar desde cero</a>
<a href="#login,setup">Configurar servidor CouchDB / Empezar desde cero</a>
<div style="margin-top:10px; font-size:90%">Servidor CouchDB: <b>${localStorage.getItem('TELESEC_COUCH_URL') || '(no configurado)'} </b></div>
`;
var divact = document.getElementById(div_actions);
addCategory_Personas(
@@ -57,6 +93,30 @@ PAGES.login = {
document.getElementById(btn_reload).onclick = () => {
open_page("login")
};
},
// AC_BYPASS: allow creating a local persona from the login screen
if (AC_BYPASS) {
var btn_bypass_create = safeuuid();
divact.innerHTML += `<button id="${btn_bypass_create}" class="btn2" style="margin-left:10px;">Crear persona local (bypass)</button>`;
document.getElementById(btn_bypass_create).onclick = () => {
var name = prompt("Nombre de la persona (ej: Admin):");
if (!name) return;
var id = 'bypass-' + Date.now();
var persona = { Nombre: name, Roles: 'ADMIN,' };
TS_encrypt(persona, SECRET, (encrypted) => {
DB.put('personas', id, encrypted).then(() => {
toastr.success('Persona creada: ' + id);
localStorage.setItem('TELESEC_BYPASS_ID', id);
SUB_LOGGED_IN_ID = id;
SUB_LOGGED_IN_DETAILS = persona;
SUB_LOGGED_IN = true;
SetPages();
open_page('index');
}).catch((e) => {
toastr.error('Error creando persona: ' + (e && e.message ? e.message : e));
});
});
};
}
}
}

View File

@@ -57,34 +57,30 @@ PAGES.materiales = {
<button class="rojo" id="${btn_borrar}">Borrar</button>
</fieldset>
`;
gun
.get(TABLE)
.get("materiales")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_nombre).value = data["Nombre"] || "";
document.getElementById(field_unidad).value =
data["Unidad"] || "unidad(es)";
document.getElementById(field_cantidad).value =
data["Cantidad"] || "";
document.getElementById(field_cantidad_min).value =
data["Cantidad_Minima"] || "";
document.getElementById(field_ubicacion).value =
data["Ubicacion"] || "-";
document.getElementById(field_revision).value =
data["Revision"] || "-";
document.getElementById(field_notas).value = data["Notas"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
DB.get('materiales', mid).then((data) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_nombre).value = data["Nombre"] || "";
document.getElementById(field_unidad).value =
data["Unidad"] || "unidad(es)";
document.getElementById(field_cantidad).value =
data["Cantidad"] || "";
document.getElementById(field_cantidad_min).value =
data["Cantidad_Minima"] || "";
document.getElementById(field_ubicacion).value =
data["Ubicacion"] || "-";
document.getElementById(field_revision).value =
data["Revision"] || "-";
document.getElementById(field_notas).value = data["Notas"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
document.getElementById(btn_guardar).onclick = () => {
var data = {
Nombre: document.getElementById(field_nombre).value,
@@ -97,21 +93,23 @@ PAGES.materiales = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("materiales").get(mid), encrypted);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("materiales");
}, SAVE_WAIT);
DB.put('materiales', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("materiales");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar este material?") == true) {
betterGunPut(gun.get(TABLE).get("materiales").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("materiales");
}, SAVE_WAIT);
DB.del('materiales', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("materiales");
}, SAVE_WAIT);
});
}
};
},
@@ -161,54 +159,50 @@ PAGES.materiales = {
];
// Obtener todas las ubicaciones únicas y poblar el <select>, desencriptando si es necesario
gun
.get(TABLE)
.get("materiales")
.map()
.once((data, key) => {
try {
if (!data) return;
DB.map("materiales", (data, key) => {
try {
if (!data) return;
function addUbicacion(d) {
const ubicacion = d.Ubicacion || "-";
const select = document.getElementById(select_ubicacion);
function addUbicacion(d) {
const ubicacion = d.Ubicacion || "-";
const select = document.getElementById(select_ubicacion);
if (!select) {
console.warn(`Element with ID "${select_ubicacion}" not found.`);
return;
}
const optionExists = Array.from(select.options).some(
(opt) => opt.value === ubicacion
);
if (!optionExists) {
const option = document.createElement("option");
option.value = ubicacion;
option.textContent = ubicacion;
select.appendChild(option);
}
if (!select) {
console.warn(`Element with ID "${select_ubicacion}" not found.`);
return;
}
if (typeof data === "string") {
TS_decrypt(data, SECRET, (dec) => {
if (dec && typeof dec === "object") {
addUbicacion(dec);
}
});
} else {
addUbicacion(data);
const optionExists = Array.from(select.options).some(
(opt) => opt.value === ubicacion
);
if (!optionExists) {
const option = document.createElement("option");
option.value = ubicacion;
option.textContent = ubicacion;
select.appendChild(option);
}
} catch (error) {
console.warn("Error processing ubicacion:", error);
}
});
if (typeof data === "string") {
TS_decrypt(data, SECRET, (dec) => {
if (dec && typeof dec === "object") {
addUbicacion(dec);
}
});
} else {
addUbicacion(data);
}
} catch (error) {
console.warn("Error processing ubicacion:", error);
}
});
// Función para renderizar la tabla filtrada
function renderTable(filtroUbicacion) {
TS_IndexElement(
"materiales",
config,
gun.get(TABLE).get("materiales"),
"materiales",
document.getElementById(tableContainer),
function (data, new_tr) {
if (parseFloat(data.Cantidad) < parseFloat(data.Cantidad_Minima)) {

View File

@@ -45,38 +45,34 @@ PAGES.notas = {
},
"Autor"
);
gun
.get(TABLE)
.get("notas")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_asunto).value = data["Asunto"] || "";
document.getElementById(field_contenido).value =
data["Contenido"] || "";
document.getElementById(field_autor).value = data["Autor"] || SUB_LOGGED_IN_ID || "";
DB.get('notas', mid).then((data) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_asunto).value = data["Asunto"] || "";
document.getElementById(field_contenido).value =
data["Contenido"] || "";
document.getElementById(field_autor).value = data["Autor"] || SUB_LOGGED_IN_ID || "";
// Persona select
divact.innerHTML = "";
addCategory_Personas(
divact,
SC_Personas,
data["Autor"] || SUB_LOGGED_IN_ID || "",
(value) => {
document.getElementById(field_autor).value = value;
},
"Autor"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
// Persona select
divact.innerHTML = "";
addCategory_Personas(
divact,
SC_Personas,
data["Autor"] || SUB_LOGGED_IN_ID || "",
(value) => {
document.getElementById(field_autor).value = value;
},
"Autor"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
document.getElementById(btn_guardar).onclick = () => {
var data = {
Autor: document.getElementById(field_autor).value,
@@ -85,24 +81,23 @@ PAGES.notas = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(
gun.get(TABLE).get("notas").get(mid),
encrypted
);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("notas");
}, SAVE_WAIT);
DB.put('notas', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("notas");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta nota?") == true) {
betterGunPut(gun.get(TABLE).get("notas").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("notas");
}, SAVE_WAIT);
DB.del('notas', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("notas");
}, SAVE_WAIT);
});
}
};
},
@@ -131,7 +126,7 @@ PAGES.notas = {
label: "Asunto",
},
],
gun.get(TABLE).get("notas"),
"notas",
document.querySelector("#cont"),
);
if (!checkRole("notas:edit")) {

View File

@@ -478,34 +478,35 @@ PAGES.pagos = {
persona.Monedero_Balance = fixfloat(newBalance);
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("personas").get(personaId), encrypted);
if (callback) callback();
DB.put('personas', personaId, encrypted).then(() => {
if (callback) callback();
});
});
}
function saveTransaction(ticketId, data) {
TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("pagos").get(ticketId), encrypted);
// If this is from SuperCafé, update the order
if (data.Origen === "SuperCafé" && data.OrigenID) {
handleSuperCafePayment(data);
}
// Check for promotional bonus on Ingreso transactions (Efectivo only)
if (data.Tipo === "Ingreso" && data.Metodo === "Efectivo") {
var bonusAmount = calculatePromoBonus(data.Monto);
if (bonusAmount > 0) {
createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto);
DB.put('pagos', ticketId, encrypted).then(() => {
// If this is from SuperCafé, update the order
if (data.Origen === "SuperCafé" && data.OrigenID) {
handleSuperCafePayment(data);
}
}
toastr.success("¡Transacción completada!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + ticketId);
}, SAVE_WAIT);
// Check for promotional bonus on Ingreso transactions (Efectivo only)
if (data.Tipo === "Ingreso" && data.Metodo === "Efectivo") {
var bonusAmount = calculatePromoBonus(data.Monto);
if (bonusAmount > 0) {
createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto);
}
}
toastr.success("¡Transacción completada!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + ticketId);
}, SAVE_WAIT);
});
});
}
@@ -556,16 +557,13 @@ PAGES.pagos = {
persona.Monedero_Balance = fixfloat(newBalance);
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(
gun.get(TABLE).get("personas").get(personaId),
encrypted
);
DB.put('personas', personaId, encrypted);
});
}
// Save bonus transaction
TS_encrypt(bonusData, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("pagos").get(bonusTicketId), encrypted);
DB.put('pagos', bonusTicketId, encrypted);
});
toastr.success(
@@ -575,20 +573,15 @@ PAGES.pagos = {
function handleSuperCafePayment(transactionData) {
// Mark the SuperCafé order as paid and delete it
betterGunPut(
gun.get(TABLE).get("supercafe").get(transactionData.OrigenID),
null
);
DB.del('supercafe', transactionData.OrigenID).then(() => {});
// Update persona points
var persona = SC_Personas[transactionData.Persona];
if (!persona) return;
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(
gun.get(TABLE).get("personas").get(transactionData.Persona),
encrypted
);
DB.put('personas', transactionData.Persona, encrypted);
});
}
@@ -732,39 +725,36 @@ PAGES.pagos = {
setUrlHash("supercafe");
};
gun
.get(TABLE)
.get("pagos")
.get(tid)
.once((data, key) => {
function load_data(data) {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_ticket).value = data.Ticket || key;
var fecha = data.Fecha || "";
if (fecha) {
var d = new Date(fecha);
document.getElementById(field_fecha).value =
d.toLocaleString("es-ES");
}
document.getElementById(field_tipo).value = data.Tipo || "";
document.getElementById(field_monto).value =
(data.Monto || 0).toFixed(2) + "€";
var persona = SC_Personas[data.Persona] || {};
document.getElementById(field_persona).value =
persona.Nombre || data.Persona || "";
if (data.PersonaDestino) {
var personaDestino = SC_Personas[data.PersonaDestino] || {};
document.getElementById(field_persona_destino).value =
personaDestino.Nombre || data.PersonaDestino || "";
document.getElementById(div_persona_destino).style.display =
"block";
}
document.getElementById(field_metodo).value = data.Metodo || "";
(async () => {
const data = await DB.get('pagos', tid);
function load_data(data) {
document.getElementById(nameh1).innerText = tid;
document.getElementById(field_ticket).value = data.Ticket || tid;
var fecha = data.Fecha || "";
if (fecha) {
var d = new Date(fecha);
document.getElementById(field_fecha).value =
d.toLocaleString("es-ES");
}
document.getElementById(field_tipo).value = data.Tipo || "";
document.getElementById(field_monto).value =
(data.Monto || 0).toFixed(2) + "€";
var persona = SC_Personas[data.Persona] || {};
document.getElementById(field_persona).value =
persona.Nombre || data.Persona || "";
if (data.PersonaDestino) {
var personaDestino = SC_Personas[data.PersonaDestino] || {};
document.getElementById(field_persona_destino).value =
personaDestino.Nombre || data.PersonaDestino || "";
document.getElementById(div_persona_destino).style.display =
"block";
}
document.getElementById(field_metodo).value = data.Metodo || "";
document.getElementById(field_estado).value = data.Estado || "";
document.getElementById(field_notas).value = data.Notas || "";
@@ -791,11 +781,12 @@ PAGES.pagos = {
"¿Estás seguro de que quieres ELIMINAR esta transacción?\n\nEsta acción NO se puede deshacer y los cambios en los monederos NO se revertirán automáticamente.\n\nPara revertir los cambios en los monederos, usa el botón 'Revertir Transacción' en su lugar."
)
) {
betterGunPut(gun.get(TABLE).get("pagos").get(key), null);
toastr.success("Transacción eliminada");
setTimeout(() => {
setUrlHash("pagos");
}, 1000);
DB.del('pagos', key).then(() => {
toastr.success("Transacción eliminada");
setTimeout(() => {
setUrlHash("pagos");
}, 1000);
});
}
};
@@ -858,20 +849,19 @@ PAGES.pagos = {
persona.Monedero_Balance = fixfloat(newBalance);
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(
gun.get(TABLE).get("personas").get(personaId),
encrypted
);
if (callback) callback();
DB.put('personas', personaId, encrypted).then(() => {
if (callback) callback();
});
});
}
function deleteTransaction(transactionKey) {
betterGunPut(gun.get(TABLE).get("pagos").get(transactionKey), null);
toastr.success("Transacción revertida y eliminada");
setTimeout(() => {
setUrlHash("pagos");
}, 1000);
DB.del('pagos', transactionKey).then(() => {
toastr.success("Transacción revertida y eliminada");
setTimeout(() => {
setUrlHash("pagos");
}, 1000);
});
}
}
@@ -1016,7 +1006,7 @@ PAGES.pagos = {
TS_IndexElement(
"pagos",
config,
gun.get(TABLE).get("pagos"),
'pagos',
document.getElementById("tableContainer"),
(data, new_tr) => {
var id = data._key;
@@ -1252,32 +1242,29 @@ PAGES.pagos = {
`;
// Load transaction data
gun
.get(TABLE)
.get("pagos")
.get(transactionId)
.once((data, key) => {
function loadTransactionData(data) {
originalData = data;
document.getElementById(field_tipo).value = data.Tipo || "Ingreso";
document.getElementById(field_metodo).value =
data.Metodo || "Efectivo";
document.getElementById(field_monto).value = data.Monto || 0;
document.getElementById(field_estado).value =
data.Estado || "Completado";
document.getElementById(field_notas).value = data.Notas || "";
selectedPersona = data.Persona || "";
selectedPersonaDestino = data.PersonaDestino || "";
loadPersonaSelector();
if (data.Tipo === "Transferencia") {
document.getElementById(div_persona_destino).style.display =
"block";
loadPersonaDestinoSelector();
}
(async () => {
const data = await DB.get('pagos', transactionId);
function loadTransactionData(data) {
originalData = data;
document.getElementById(field_tipo).value = data.Tipo || "Ingreso";
document.getElementById(field_metodo).value =
data.Metodo || "Efectivo";
document.getElementById(field_monto).value = data.Monto || 0;
document.getElementById(field_estado).value =
data.Estado || "Completado";
document.getElementById(field_notas).value = data.Notas || "";
selectedPersona = data.Persona || "";
selectedPersonaDestino = data.PersonaDestino || "";
loadPersonaSelector();
if (data.Tipo === "Transferencia") {
document.getElementById(div_persona_destino).style.display =
"block";
loadPersonaDestinoSelector();
}
}
if (typeof data == "string") {
@@ -1398,12 +1385,13 @@ PAGES.pagos = {
TS_encrypt(updatedData, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("pagos").get(transactionId), encrypted);
toastr.success("¡Transacción actualizada!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + transactionId);
}, SAVE_WAIT);
DB.put('pagos', transactionId, encrypted).then(() => {
toastr.success("¡Transacción actualizada!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + transactionId);
}, SAVE_WAIT);
});
});
};

View File

@@ -71,11 +71,11 @@ PAGES.personas = {
<div style="padding: 15px;">
<label>
Este servidor<br>
<input type="url" value="${location.protocol}//${location.hostname}:${location.port}${location.pathname}?login=${GROUPID}:${SECRET}&sublogin=${mid}" style="font-size: 10px; font-weight: bold; color: #000;"><br>
<input type="url" value="${location.protocol}//${location.hostname}:${location.port}${location.pathname}?login=${getDBName()}:${SECRET}&sublogin=${mid}" style="font-size: 10px; font-weight: bold; color: #000;"><br>
</label>
<label>
Cualquier Servidor<br>
<input type="url" value="https://tech.eus/ts/?login=${GROUPID}:${SECRET}&sublogin=${mid}" style="font-size: 10px; font-weight: bold; color: #000;"><br>
<input type="url" value="https://tech.eus/ts/?login=${getDBName()}:${SECRET}&sublogin=${mid}" style="font-size: 10px; font-weight: bold; color: #000;"><br>
</label>
</div>
</details>
@@ -90,47 +90,43 @@ PAGES.personas = {
`;
var resized = "";
var pdel = document.getElementById(permisosdet);
gun
.get(TABLE)
.get("personas")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
var pot = "<ul>";
Object.entries(PERMS).forEach((page) => {
var c = "";
if ((data["Roles"] || ",").split(",").includes(page[0])) {
c = "checked";
}
pot += `
<li><label>
<input name="perm" value="${page[0]}" type="checkbox" ${c}>
${page[1]}
</label></li>
`;
});
pdel.innerHTML = pot + "</ul>";
document.getElementById(field_nombre).value = data["Nombre"] || "";
document.getElementById(field_zona).value = data["Region"] || "";
document.getElementById(field_anilla).value = data["SC_Anilla"] || "";
document.getElementById(render_foto).src =
data["Foto"] || "static/ico/user_generic.png";
resized = data["Foto"] || "static/ico/user_generic.png";
document.getElementById(field_notas).value = data["markdown"] || "";
document.getElementById(field_monedero_balance).value =
data["Monedero_Balance"] || 0;
document.getElementById(field_monedero_notas).value =
data["Monedero_Notas"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
DB.get('personas', mid).then((data) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
var pot = "<ul>";
Object.entries(PERMS).forEach((page) => {
var c = "";
if ((data["Roles"] || ",").split(",").includes(page[0])) {
c = "checked";
}
pot += `
<li><label>
<input name="perm" value="${page[0]}" type="checkbox" ${c}>
${page[1]}
</label></li>
`;
});
pdel.innerHTML = pot + "</ul>";
document.getElementById(field_nombre).value = data["Nombre"] || "";
document.getElementById(field_zona).value = data["Region"] || "";
document.getElementById(field_anilla).value = data["SC_Anilla"] || "";
document.getElementById(render_foto).src =
data["Foto"] || "static/ico/user_generic.png";
resized = data["Foto"] || "static/ico/user_generic.png";
document.getElementById(field_notas).value = data["markdown"] || "";
document.getElementById(field_monedero_balance).value =
data["Monedero_Balance"] || 0;
document.getElementById(field_monedero_notas).value =
data["Monedero_Notas"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
document
.getElementById(field_foto)
.addEventListener("change", function (e) {
@@ -163,12 +159,13 @@ PAGES.personas = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("personas").get(mid), encrypted);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("personas");
}, SAVE_WAIT);
DB.put('personas', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("personas");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_ver_monedero).onclick = () => {
@@ -176,11 +173,12 @@ PAGES.personas = {
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta persona?") == true) {
betterGunPut(gun.get(TABLE).get("personas").get(mid), null);
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("personas");
}, SAVE_WAIT);
DB.del('personas', mid).then(() => {
toastr.error("Borrado!");
setTimeout(() => {
setUrlHash("personas");
}, SAVE_WAIT);
});
}
};
},
@@ -208,7 +206,7 @@ PAGES.personas = {
TS_IndexElement(
"personas",
config,
gun.get(TABLE).get("personas"),
"personas",
document.getElementById("tableContainer"),
undefined,
undefined,

View File

@@ -23,96 +23,81 @@ PAGES.resumen_diario = {
`;
//#region Cargar Clima
// Get location from gun.get("settings").get("weather_location"), if missing ask user and save it
// Get location from DB settings.weather_location; if missing ask user and save it
// url format: https://wttr.in/<loc>?F0m
gun
.get("settings")
.get("weather_location")
.once((loc) => {
if (!loc) {
loc = prompt("Introduce tu ubicación para el clima (ciudad, país):", "Madrid, Spain");
if (loc) {
betterGunPut(gun.get("settings").get("weather_location"), loc);
}
}
DB.get('settings','weather_location').then((loc) => {
if (!loc) {
loc = prompt("Introduce tu ubicación para el clima (ciudad, país):", "Madrid, Spain");
if (loc) {
document.getElementById(data_Weather).src = "https://wttr.in/" + encodeURIComponent(loc) + "_IF0m_background=FFFFFF.png";
} else {
document.getElementById(data_Weather).src = "https://wttr.in/_IF0m_background=FFFFFF.png";
DB.put('settings','weather_location', loc);
}
});
}
if (loc) {
document.getElementById(data_Weather).src = "https://wttr.in/" + encodeURIComponent(loc) + "_IF0m_background=FFFFFF.png";
} else {
document.getElementById(data_Weather).src = "https://wttr.in/_IF0m_background=FFFFFF.png";
}
});
//#endregion Cargar Clima
//#region Cargar Comedor
gun
.get(TABLE)
.get("comedor")
.get(CurrentISODate())
.once((data, key) => {
function add_row(data) {
// Fix newlines
data.Platos = data.Platos || "No hay platos registrados para hoy.";
// Display platos
document.getElementById(data_Comedor).innerHTML = data.Platos.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
});
} else {
DB.get('comedor', CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Platos = data.Platos || "No hay platos registrados para hoy.";
// Display platos
document.getElementById(data_Comedor).innerHTML = data.Platos.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
}
});
});
} else {
add_row(data || {});
}
});
//#endregion Cargar Comedor
//#region Cargar Tareas
gun
.get(TABLE)
.get("notas")
.get("tareas")
.once((data, key) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay tareas.";
// Display platos
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
});
} else {
DB.get('notas', 'tareas').then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay tareas.";
// Display platos
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
}
});
});
} else {
add_row(data || {});
}
});
//#endregion Cargar Tareas
//#region Cargar Diario
gun
.get(TABLE)
.get("aulas_informes")
.get("diario-" + CurrentISODate())
.once((data, key) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay un diario.";
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
});
} else {
DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || "No hay un diario.";
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(
/\n/g,
"<br>"
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
add_row(data || {});
}
});
});
} else {
add_row(data || {});
}
});
//#endregion Cargar Diario
},
};

View File

@@ -76,32 +76,28 @@ PAGES.supercafe = {
});
}
loadActions();
gun
.get(TABLE)
.get("supercafe")
.get(mid)
.once((data, key) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_fecha).value = data["Fecha"] || CurrentISODate();
document.getElementById(field_persona).value = data["Persona"] || "";
currentPersonaID = data["Persona"] || "";
document.getElementById(field_comanda).value =
SC_parse(JSON.parse(data["Comanda"] || "{}")) || "";
document.getElementById(field_notas).value = data["Notas"] || "";
document.getElementById(field_estado).value = data["Estado"] || "%%";
currentData = JSON.parse(data["Comanda"] || "{}");
DB.get('supercafe', mid).then((data) => {
function load_data(data, ENC = "") {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_fecha).value = data["Fecha"] || CurrentISODate();
document.getElementById(field_persona).value = data["Persona"] || "";
currentPersonaID = data["Persona"] || "";
document.getElementById(field_comanda).value =
SC_parse(JSON.parse(data["Comanda"] || "{}")) || "";
document.getElementById(field_notas).value = data["Notas"] || "";
document.getElementById(field_estado).value = data["Estado"] || "%%";
currentData = JSON.parse(data["Comanda"] || "{}");
loadActions();
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
loadActions();
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
load_data(data, "%E");
});
} else {
load_data(data || {});
}
});
document.getElementById(btn_guardar).onclick = () => {
if (document.getElementById(field_persona).value == "") {
alert("¡Hay que elegir una persona!");
@@ -118,12 +114,13 @@ PAGES.supercafe = {
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("supercafe").get(mid), encrypted);
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("supercafe");
}, SAVE_WAIT);
DB.put('supercafe', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("supercafe");
}, SAVE_WAIT);
});
});
};
document.getElementById(btn_borrar).onclick = () => {
@@ -132,10 +129,11 @@ PAGES.supercafe = {
"¿Quieres borrar esta comanda? - NO se actualizará el monedero de la persona asignada."
) == true
) {
betterGunPut(gun.get(TABLE).get("supercafe").get(mid), null);
setTimeout(() => {
setUrlHash("supercafe");
}, SAVE_WAIT);
DB.del('supercafe', mid).then(() => {
setTimeout(() => {
setUrlHash("supercafe");
}, SAVE_WAIT);
});
}
};
},
@@ -229,7 +227,7 @@ PAGES.supercafe = {
TS_IndexElement(
"supercafe",
config,
gun.get(TABLE).get("supercafe"),
"supercafe",
document.querySelector("#cont1"),
(data, new_tr) => {
// new_tr.style.backgroundColor = "#FFCCCB";
@@ -278,7 +276,7 @@ PAGES.supercafe = {
TS_IndexElement(
"supercafe",
config,
gun.get(TABLE).get("supercafe"),
"supercafe",
document.querySelector("#cont2"),
(data, new_tr) => {
// new_tr.style.backgroundColor = "#FFCCCB";