cleanup
This commit is contained in:
174
adminapi/www/adminapx/static/js/apxapp.js
Normal file
174
adminapi/www/adminapx/static/js/apxapp.js
Normal file
@@ -0,0 +1,174 @@
|
||||
/*eslint no-undef:0*/
|
||||
/*eslint-env browser*/
|
||||
|
||||
"use strict";
|
||||
var app = app || {};
|
||||
|
||||
app.runapirequest = (destmodalid, axiosreq, modalcontent) => {
|
||||
/**
|
||||
* Axios option
|
||||
* {method: GET POST,...,
|
||||
* url:'/relativepath'
|
||||
* data:{anydata to pass}
|
||||
* responseType:'json'}
|
||||
*/
|
||||
if (!modalcontent) {
|
||||
modalcontent = {
|
||||
title: "An axios request",
|
||||
body: "",
|
||||
actions: [],
|
||||
};
|
||||
}
|
||||
modalcontent.body += JSON.stringify(axiosreq);
|
||||
console.log(apx.data.headers);
|
||||
axiosreq.headers = apx.data.headers;
|
||||
axios(axiosreq)
|
||||
.then((rep) => {
|
||||
console.log(rep);
|
||||
modalcontent.body += "<br>" + JSON.stringify(rep.data);
|
||||
app.modalmanagement("reqaxios", apx.data.tpl.apxmodal, modalcontent);
|
||||
})
|
||||
.catch((err, rep) => {
|
||||
console.log(err);
|
||||
modalcontent.title += " - Status :" + err.response.status;
|
||||
modalcontent.body += JSON.stringify(err.response.data);
|
||||
app.modalmanagement("reqaxios", apx.data.tpl.apxmodal, modalcontent);
|
||||
});
|
||||
};
|
||||
app.modalmanagement = (modalid, tpl, data, optionmodal) => {
|
||||
/**
|
||||
* Pre-request in hatml
|
||||
* <div id="apxmodal" add2data tpl="static/tpl/apxmodal_en.mustache" ></div>
|
||||
* data must be online with tpl
|
||||
* optionmodal see https://getbootstrap.com/docs/5.0/components/modal/
|
||||
*/
|
||||
if (!optionmodal)
|
||||
optionmodal = { backdrop: true, keyboard: true, focus: true };
|
||||
if (document.getElementById(modalid)) {
|
||||
document.getElementById(modalid).remove();
|
||||
}
|
||||
data.modalid = modalid;
|
||||
document.getElementById("apxmodal").innerHTML += Mustache.render(tpl, data);
|
||||
var currentmodal = new bootstrap.Modal(
|
||||
document.getElementById(modalid),
|
||||
optionmodal
|
||||
);
|
||||
//var currentmodal = bootstrap.Modal.getOrCreateInstance(modalid);
|
||||
|
||||
currentmodal.show();
|
||||
};
|
||||
app.search = (elt) => {
|
||||
//@todo get search string from input then return tpldata.articleslist={"search":[]}
|
||||
console.log("A FAIRE");
|
||||
};
|
||||
app.downloadlink = (keys, object, prefix) => {
|
||||
/**
|
||||
*
|
||||
* @param {string} split(.) to naviagte in object until a subobject
|
||||
* @param {object} a json object
|
||||
*
|
||||
* Create a link that download subobject as a textfile
|
||||
* example:
|
||||
* const obj={pagans:{privateKey:"123", publicKey:"AZE"}}
|
||||
* const prefix = "appname"
|
||||
* const keys="pagans.privateKey"
|
||||
*
|
||||
* in <html>
|
||||
* <button class="btn btn-outline-primary" onclick="app.downloadlink('pagans.privateKey',obj,prefix);" >Download PrivateKey</button>
|
||||
*
|
||||
*
|
||||
*/
|
||||
const dwn = (fn, t) => {
|
||||
var element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + encodeURIComponent(t)
|
||||
);
|
||||
element.setAttribute("download", fn);
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
const subobj = keys.split(".").reduce((val, k) => (val = val[k]), object);
|
||||
const txt = typeof subobj == "string" ? subobj : JSON.stringify(subobj);
|
||||
dwn(`${prefix}_${keys.split(".").pop()}.txt`, txt);
|
||||
};
|
||||
|
||||
app.setupdata = () => {
|
||||
//todo generate tpldata.articleslist get html from /static/html
|
||||
apx.data.tpldata.articleslist = {
|
||||
mostread: [{}],
|
||||
news: [{}],
|
||||
search: [],
|
||||
articles: "html",
|
||||
};
|
||||
const list = {};
|
||||
document.querySelectorAll("[add2data]").forEach((e) => {
|
||||
/** Collect from any tag with add2data url attribute (tpl tpldata object)
|
||||
* name is the filename :
|
||||
* for tpl and tpldata (relativepath/filename_xx.ext) name=filename
|
||||
* For public object (pagans, towns, nations, block) not tribes object are gettable (only)
|
||||
* with /nationchains/objectName/idx/existingindex.json
|
||||
* or for an item /itm//primarykey_value_of_item.json
|
||||
*
|
||||
* For tribe object get or for modification you need to use api and to have accessright setup properly:
|
||||
* for object index (/api/odmdb/objectname/idx/filename.json) name =objectname_filename
|
||||
* for object item (/api/odmdb/objectname/itm/primarykey.json) name =objectname_primarykey
|
||||
*/
|
||||
|
||||
if (e.getAttribute("object")) {
|
||||
const url = e.getAttribute("object");
|
||||
const spliturlobj = url.split("/").slice(-3);
|
||||
const objectname = spliturlobj[0];
|
||||
const objecttype = spliturlobj[1];
|
||||
const objectkey = spliturlobj[2].substring(0, spliturlobj[2].length - 5);
|
||||
if (!list[objectname]) list[objectname] = {};
|
||||
list[objectname][`${objecttype}${objectkey}`] = url;
|
||||
}
|
||||
for (let k of ["tpl", "tpldata"]) {
|
||||
if (e.getAttribute(k)) {
|
||||
const url = e.getAttribute(k);
|
||||
let localname = url.split("/").slice(-1)[0];
|
||||
if (url.includes(".mustache"))
|
||||
localname = localname.substring(0, localname.length - 12);
|
||||
if (url.includes(".html") || url.includes(".json"))
|
||||
localname = localname.substring(0, localname.length - 8);
|
||||
if (!list[k]) list[k] = {};
|
||||
list[k][localname] = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
// load external template and data
|
||||
|
||||
for (let k of Object.keys(list)) {
|
||||
console.log(k, list[k]);
|
||||
apx.loadfile(list[k], k);
|
||||
}
|
||||
//alert(apx.data.firsttimeload)
|
||||
if (apx.data.firsttimeload) {
|
||||
// Need to wait the first time apx.data fully load
|
||||
setTimeout(() => {
|
||||
app.setuppage();
|
||||
}, 500);
|
||||
delete apx.data.firsttimeload;
|
||||
apx.save();
|
||||
} else {
|
||||
app.setuppage();
|
||||
}
|
||||
};
|
||||
app.load = (idtoload, tplname, tpldata) => {
|
||||
const tpl = apx.data.tpl[tplname]
|
||||
? apx.data.tpl[tplname]
|
||||
: `missing template ${tplname}`;
|
||||
const data = typeof tpldata == "string" ? apx.data.tpldata[tpldata] : tpldata;
|
||||
document.getElementById(idtoload).innerHTML = Mustache.render(tpl, data);
|
||||
};
|
||||
app.setuppage = () => {
|
||||
// load partial template
|
||||
document.querySelectorAll("[apptoload]").forEach((e) => {
|
||||
console.log(e.getAttribute("apptoload"));
|
||||
eval(e.getAttribute("apptoload"));
|
||||
});
|
||||
};
|
||||
apx.ready(app.setupdata);
|
233
adminapi/www/adminapx/static/js/apxpagans.js
Normal file
233
adminapi/www/adminapx/static/js/apxpagans.js
Normal file
@@ -0,0 +1,233 @@
|
||||
/*eslint no-undef:0*/
|
||||
/*eslint-env browser*/
|
||||
|
||||
"use strict";
|
||||
var pagans = pagans || {};
|
||||
/**
|
||||
* pagans.generateKey(params) create a public/private key compatible with apXtrib backend and store it in apx.data
|
||||
* pagans.detachedSignature(params) generate a detachedSignature for a Message that backend can check with the publicKey of the alias
|
||||
* pagans.checkdetachedSignature(params) check a detachedSignature for a Message (used on the backend as well)
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
pagans.loadtpldata = () => {
|
||||
// adapte tpldata to template tpl
|
||||
// get list of tribes from nationchains/towns/idx/townId_all.json
|
||||
const datacreatepagan = { tribes: [] };
|
||||
|
||||
apx.data.towns.idxtownId_all[apx.data.tpldata.setup.townId].tribes.forEach(
|
||||
(t) => {
|
||||
if (t == apx.data.tpldata.setup.tribeId) {
|
||||
datacreatepagan.tribes.push({ selected: true, tribeId: t });
|
||||
} else {
|
||||
datacreatepagan.tribes.push({ tribeId: t });
|
||||
}
|
||||
}
|
||||
);
|
||||
return datacreatepagan;
|
||||
};
|
||||
|
||||
pagans.generateKey = async (alias, passphrase) => {
|
||||
/**
|
||||
* @param {string} alias a unique alias that identify an identity
|
||||
* @param {string} passphrase a string to cipher the publicKey (can be empty, less secure but simpler)
|
||||
* @return {publicKey,privateKey} with userIds = [{alias}]
|
||||
*/
|
||||
const pgpparam = {
|
||||
type: "ecc", // Type of the key, defaults to ECC
|
||||
curve: "curve25519", // ECC curve name, defaults to curve25519
|
||||
userIDs: [{ alias: alias }], // you can pass multiple user IDs
|
||||
passphrase: passphrase, // protects the private key
|
||||
format: "armored", // output key format, defaults to 'armored' (other options: 'binary' or 'object')
|
||||
};
|
||||
const { privateKey, publicKey } = await openpgp.generateKey(pgpparam);
|
||||
// key start by '-----BEGIN PGP PRIVATE KEY BLOCK ... '
|
||||
// get liste of alias:pubklickey await axios.get('api/v0/pagans')
|
||||
// check alias does not exist
|
||||
return { alias, privateKey, publicKey };
|
||||
};
|
||||
pagans.detachedSignature = async (pubK, privK, passphrase, message) => {
|
||||
/**
|
||||
* @pubK {string} a text public key
|
||||
* @privK {string} a test priv key
|
||||
* @passphrase {string} used to read privK
|
||||
* @message {string} message to sign
|
||||
* @Return a detached Signature of the message
|
||||
*/
|
||||
const publicKey = await openpgp.readKey({ armoredKey: pubK });
|
||||
let privateKey;
|
||||
if (passphrase == "") {
|
||||
privateKey = await openpgp.readPrivateKey({ armoredKey: privK });
|
||||
} else {
|
||||
privateKey = await openpgp.decryptKey({
|
||||
privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
|
||||
passphrase,
|
||||
});
|
||||
}
|
||||
console.log(message);
|
||||
const msg = await openpgp.createMessage({ text: message });
|
||||
console.log(msg);
|
||||
const sig = await openpgp.sign({
|
||||
message: msg,
|
||||
signingKeys: privateKey,
|
||||
detached: true,
|
||||
});
|
||||
return btoa(sig);
|
||||
};
|
||||
pagans.authenticatedetachedSignature = async (
|
||||
alias,
|
||||
pubK,
|
||||
detachedSignature,
|
||||
message
|
||||
) => {
|
||||
/**
|
||||
* Check that alias (pubkey) signe a message
|
||||
* @alias {string} alias link to the publicKey
|
||||
* @pubK {string} publiKey text format
|
||||
* @detachedSignature {string} a detachedsignatured get from apx.detachedSignature
|
||||
* @message {string} the message signed
|
||||
* @return {boolean} true the message was signed by alias
|
||||
* false the message was not signed by alias
|
||||
*/
|
||||
const publicKey = await openpgp.readKey({ armoredKey: pubK });
|
||||
const msg = await openpgp.createMessage({ text: message });
|
||||
const signature = await openpgp.readSignature({
|
||||
armoredSignature: atob(detachedSignature), // parse detached signature
|
||||
});
|
||||
const verificationResult = await openpgp.verify({
|
||||
msg, // Message object
|
||||
signature,
|
||||
verificationKeys: publicKey,
|
||||
});
|
||||
const { verified, keyID } = verificationResult.signatures[0];
|
||||
try {
|
||||
await verified; // throws on invalid signature
|
||||
console.log("Signed by key id " + keyID.toHex());
|
||||
return KeyId.toHex().alias == alias;
|
||||
} catch (e) {
|
||||
console.log("Signature could not be verified: " + e.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
pagans.createIdentity = async (alias, passphrase = "") => {
|
||||
console.log(Object.keys(apx.data.pagans["idxalias_all"]));
|
||||
if (
|
||||
alias.length < 3 ||
|
||||
Object.keys(apx.data.pagans["idxalias_all"]).includes(alias)
|
||||
) {
|
||||
alert("Please chose another alias");
|
||||
return;
|
||||
}
|
||||
const keys = await pagans.generateKey(alias, passphrase);
|
||||
for (let tag of ["inputalias", "inputpassphrase"]) {
|
||||
document.getElementById(tag).classList.add("disabled");
|
||||
}
|
||||
console.log(keys);
|
||||
document.getElementById("privatekey").setAttribute("key", keys.privateKey);
|
||||
document.getElementById("publickey").setAttribute("key", keys.publicKey);
|
||||
apx.data.tmp = keys; // to make it available for btn download
|
||||
document.getElementById("generatekeys").classList.add("d-none");
|
||||
document.getElementById("trustintribe").classList.remove("d-none");
|
||||
document.getElementById("downloadkeys").classList.remove("d-none");
|
||||
document.getElementById("createId").classList.remove("d-none");
|
||||
};
|
||||
|
||||
pagans.registerIdentity = async () => {
|
||||
const emailregex =
|
||||
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const data = {};
|
||||
data.alias = document.getElementById("inputalias").value;
|
||||
data.publickey = document.getElementById("publickey").getAttribute("key");
|
||||
|
||||
if (document.getElementById("inputemailrecovery").value != "") {
|
||||
data.email = document.getElementById("inputemailrecovery").value;
|
||||
}
|
||||
if (data.email && !emailregex.test(data.email)) {
|
||||
alert("Check your email");
|
||||
return false;
|
||||
}
|
||||
var select = document.getElementById("trustedtribe");
|
||||
if (document.getElementById("trustedcheck").checked) {
|
||||
data.trustedtribe = select.options[select.selectedIndex].value;
|
||||
data.privatekey = document.getElementById("privatekey").getAttribute("key");
|
||||
data.passphrase = document.getElementById("inputpassphrase").value;
|
||||
}
|
||||
if (document.getElementById("trustedcheck").checked && !data.email) {
|
||||
alert("Please provide a valid email");
|
||||
return false;
|
||||
}
|
||||
console.log(data);
|
||||
//store and create authentification by signing 'alias_timestamp'
|
||||
/* const passp = data.passphrase ? data.passphrase : "";
|
||||
apx.data.auth = {
|
||||
alias: data.alias,
|
||||
publickey: data.publickey,
|
||||
privatekey: document.getElementById("privatekey").getAttribute("key"),
|
||||
passphrase: passp,
|
||||
};
|
||||
apx.data.headers.xalias = data.alias;
|
||||
apx.data.headers.xdays = dayjs().valueOf();
|
||||
apx.save();
|
||||
const msg = `${data.alias}_${apx.data.headers.xdays}`;
|
||||
apx.data.headers.xhash = await pagans.detachedSignature(
|
||||
apx.data.auth.publickey,
|
||||
apx.data.auth.privatekey,
|
||||
passp,
|
||||
msg
|
||||
);
|
||||
apx.save();*/
|
||||
await pagans.authentifyme(
|
||||
data.alias,
|
||||
data.passphrase,
|
||||
document.getElementById("privatekey").getAttribute("key"),
|
||||
document.getElementById("publickey").getAttribute("key")
|
||||
);
|
||||
console.log("header", apx.data.headers);
|
||||
axios
|
||||
.post("api/pagans", data, { headers: apx.data.headers })
|
||||
.then((reppagan) => {
|
||||
console.log(reppagan);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("sorry", err);
|
||||
});
|
||||
};
|
||||
|
||||
pagans.authentifyme = async (
|
||||
alias,
|
||||
passphrase,
|
||||
privatekey,
|
||||
publickeycreate
|
||||
) => {
|
||||
/**
|
||||
* Set apx.data.auth with pub, priv, passphrase alias that allow authentification
|
||||
* set headers with xdays (timestamp) and xhash of message: {alias}_{timestamp} generate with pub & priv key
|
||||
*
|
||||
* @Param {key} publickeycreate optional when alias does not exist
|
||||
*/
|
||||
console.log(alias, passphrase);
|
||||
console.log(privatekey);
|
||||
const passp = passphrase ? passphrase : "";
|
||||
const publickey = publickeycreate
|
||||
? publickeycreate
|
||||
: apx.data.pagans.idxalias_all[alias].publicKey;
|
||||
apx.data.auth = {
|
||||
alias: alias,
|
||||
publickey: publickey,
|
||||
privatekey: privatekey,
|
||||
passphrase: passp,
|
||||
};
|
||||
apx.data.headers.xalias = alias;
|
||||
apx.data.headers.xdays = dayjs().valueOf();
|
||||
apx.save();
|
||||
const msg = `${alias}_${apx.data.headers.xdays}`;
|
||||
|
||||
apx.data.headers.xhash = await pagans.detachedSignature(
|
||||
apx.data.auth.publickey,
|
||||
apx.data.auth.privatekey,
|
||||
passp,
|
||||
msg
|
||||
);
|
||||
apx.save();
|
||||
};
|
9
adminapi/www/adminapx/static/js/apxtowns.js
Normal file
9
adminapi/www/adminapx/static/js/apxtowns.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/*eslint no-undef:0*/
|
||||
/*eslint-env browser*/
|
||||
|
||||
"use strict";
|
||||
var towns = towns || {};
|
||||
|
||||
towns.lauch =()=>{
|
||||
|
||||
}
|
134
adminapi/www/adminapx/static/js/apxtribcli.js
Normal file
134
adminapi/www/adminapx/static/js/apxtribcli.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/*eslint no-undef:0*/
|
||||
/*eslint-env browser*/
|
||||
|
||||
"use strict";
|
||||
var apx = apx || {};
|
||||
|
||||
/**
|
||||
* Js lib to interact with apxtrib backend
|
||||
*
|
||||
* html page must have apxlocal with at least headers key
|
||||
* <script>
|
||||
const apxlocal={
|
||||
headers:{xalias:"",xhash:"",xtribe:"smatchit", xapp:"smatchapp", xlang:"en" },
|
||||
firstimeload:true,
|
||||
forceload:true,
|
||||
tpl:{
|
||||
footer:"{{{msg}}}"
|
||||
},
|
||||
tpldata:{
|
||||
footer:{msg:"<p>Made with love for people freedom, enjoy!</p>"}
|
||||
},
|
||||
objects:{}
|
||||
};
|
||||
* apx.ready(callback) equivalent of jquery Document.ready()
|
||||
* apx.save() save apx.data as xapp value in localStorage
|
||||
* apx.update() get localStorage up to date
|
||||
* apx.loadfile({url:name},dest,axiosoptions) async that wait to load a list of relativ url and store it in a apx.data[dest]
|
||||
*/
|
||||
|
||||
apx.ready = (callback) => {
|
||||
/**
|
||||
* Wait loading DOM, when ready run callback function
|
||||
*/
|
||||
if (!callback) {
|
||||
alert(
|
||||
"You have an unknown callback pwa.state.ready(callback), you need to order your code callback = ()=>{} then pwa.state.ready(callback), boring but js rules ;-)"
|
||||
);
|
||||
}
|
||||
if (document.readyState != "loading") callback();
|
||||
// modern browsers
|
||||
else if (document.addEventListener)
|
||||
document.addEventListener("DOMContentLoaded", callback);
|
||||
// IE <= 8
|
||||
else
|
||||
document.attachEvent("onreadystatechange", function () {
|
||||
if (document.readyState == "complete") callback();
|
||||
});
|
||||
};
|
||||
|
||||
apx.loadfile = async (list, dest, axiosoptions) => {
|
||||
/**
|
||||
* Load external file as tpl, tpldata (temmplate and data), objects index of object
|
||||
* @param {object} list {name:url} it will run as promise all the requested url
|
||||
* @param {string} dest where to store it in apx.data (tpl, tpldata, objectname,...)
|
||||
* @param {object} axiosoptions alow to add headers any option to axios
|
||||
* @return {object} load into apx.data[dest]={name:{object}} and localStorage.xapp[dest]={name:{object}}
|
||||
*
|
||||
* @example <caption>Load into apx.data.tpl.blocncol ="string"</caption>
|
||||
* apx.loadfile({blocncol:"static/tpl/blocncol.mustache"},"tpl",{headers:apx.data.header})
|
||||
*
|
||||
*/
|
||||
const invertlist = {};
|
||||
Object.keys(list).forEach((n) => {
|
||||
invertlist[list[n]] = n;
|
||||
});
|
||||
//console.log( 'invertlist', invertlist )
|
||||
if (
|
||||
apx.data.forcereload ||
|
||||
!apx.data[dest] ||
|
||||
Object.keys(apx.data[dest]).length == 0
|
||||
) {
|
||||
if (!apx.data[dest]) apx.data[dest] = {};
|
||||
const fetchURL = (r, options) => {
|
||||
return axios.get(r, options);
|
||||
};
|
||||
const urls = Object.keys(invertlist);
|
||||
if (!axiosoptions) {
|
||||
// force no cache browser
|
||||
apx.data.headers["Cache-Control"] = "no-cache";
|
||||
apx.data.headers["Pragma"] = "no-cache";
|
||||
apx.data.headers["Expires"] = 0;
|
||||
axiosoptions = { headers: apx.data.headers };
|
||||
}
|
||||
console.log("axiosiptions", axiosoptions);
|
||||
const promiseArray = urls.map((r) => fetchURL(r, axiosoptions));
|
||||
await Promise.all(promiseArray)
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
for (let pos = 0; pos < urls.length; pos++) {
|
||||
//console.log( 'url', urls[ pos ] )
|
||||
//console.log( data[ pos ] );
|
||||
apx.data[dest][invertlist[urls[pos]]] = data[pos].data;
|
||||
console.log(
|
||||
`store apx.data[ ${dest} ][ invertlist[ ${urls[pos]} ] ]`
|
||||
);
|
||||
apx.save();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("erreur de loading", err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
apx.save = () => {
|
||||
localStorage.setItem(apx.data.headers.xapp, JSON.stringify(apx.data));
|
||||
};
|
||||
apx.update = async () => {
|
||||
if (!apxlocal) {
|
||||
console.log(
|
||||
'Please add to the html page header, this line <script> const apxlocal={headers:{xalias:"",xhash:"",xtribe:"smatchit", xapp:"smatchapp", xlang:"en" }};</script> '
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (document.querySelector("html").getAttribute("lang")) {
|
||||
apxlocal.headers.xlang = document
|
||||
.querySelector("html")
|
||||
.getAttribute("lang");
|
||||
}
|
||||
if (localStorage.getItem(apxlocal.headers.xapp)) {
|
||||
apx.data = JSON.parse(localStorage.getItem(apxlocal.headers.xapp));
|
||||
if (apx.data.headers.xtribe != apxlocal.headers.xtribe) {
|
||||
// if an app change of tribe
|
||||
localStorage.removeItem(apxlocal.headers.xapp);
|
||||
delete apx.data;
|
||||
}
|
||||
}
|
||||
if (!apx.data) {
|
||||
apx.data = apxlocal;
|
||||
}
|
||||
console.log("apx.data", apx.data);
|
||||
apx.save();
|
||||
};
|
||||
apx.ready(apx.update);
|
0
adminapi/www/adminapx/static/js/apxtribes.js
Normal file
0
adminapi/www/adminapx/static/js/apxtribes.js
Normal file
Reference in New Issue
Block a user