V1
This commit is contained in:
33
.github/copilot-instructions.md
vendored
33
.github/copilot-instructions.md
vendored
@@ -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
|
||||
|
||||
@@ -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: {}});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {}});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
138
src/db.js
Normal 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;
|
||||
117
src/gun_init.js
117
src/gun_init.js
@@ -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.');
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user