diff --git a/index.js b/index.js index 545f6b19a10731035df60137a2f012b38a36f989..2974559c290fee0f044f7111987674d96b53a9be 100755 --- a/index.js +++ b/index.js @@ -38,7 +38,7 @@ module.exports = { }, cliOptions: [ - //{ value: '--wotexURL <url>', desc: 'URL of Wotex service for UID links on willMembers page'} + //{ value: '--option-name <value_type>', desc: 'description for help command'} ], cli: [{ diff --git a/lg/membersCount_en.txt b/lg/membersCount_en.txt index a9e753c7e59f56efee2226a54c82eb7eff8cebab..ffe695c55960acfcf91692439409a16e363e4433 100755 --- a/lg/membersCount_en.txt +++ b/lg/membersCount_en.txt @@ -11,7 +11,10 @@ YEARS years SUBMIT_BUTTON submit DESCRIPTION1 *Every member automatically becomes a referring member as soon as he has emitted <b><u>and</u></b> received Y[n] certifications. (Y[n] = CEILING(N(t)^(1/5))). DESCRIPTION2 Currently, Y[n] = -CHART_TITLE Évolution du nombre de membres, de membres référents et de membres écrivains de la blockchain sur la période +CHART_TITLE Evolution count of members, referring members and of members writers of the blockchain over the period +SHOW_POW_MIN Show evolution of common difficulty network (proof of work) +Afficher l'évolution de la difficulté commune du réseau (preuve de travail) MEMBERS_COUNT members SENTRIES_COUNT referring* members -ISSUERS_COUNT Members co-writers blockchain \ No newline at end of file +ISSUERS_COUNT Members co-writers blockchain +POW_MIN Common difficulty network (proof of work) \ No newline at end of file diff --git a/lg/membersCount_fr.txt b/lg/membersCount_fr.txt index 997555b29be02166c83388dba19e204fa0cc1542..1c0a036c1e69c5327cfaf806c7243f2326cf46e1 100755 --- a/lg/membersCount_fr.txt +++ b/lg/membersCount_fr.txt @@ -12,6 +12,8 @@ SUBMIT_BUTTON recharger DESCRIPTION1 *Tout membre devient automatiquement référent dès lors qu'il a émis <b><u>et</u></b> reçu Y[n] certifications. (Y[n] = CEILING(N(t)^(1/5))). DESCRIPTION2 Actuellement, Y[n] = CHART_TITLE Évolution du nombre de membres, de membres référents et de membres écrivains de la blockchain sur la période +SHOW_POW_MIN Afficher l'évolution de la difficulté commune du réseau (preuve de travail) MEMBERS_COUNT Membres SENTRIES_COUNT Membres référents* -ISSUERS_COUNT Membres co-écrivains de la blockchain Ğ1 \ No newline at end of file +ISSUERS_COUNT Membres co-écrivains de la blockchain Ğ1 +POW_MIN Difficulté commune du réseau (preuve de travail) \ No newline at end of file diff --git a/lg/members_en.txt b/lg/members_en.txt index c7609eb4b9437bcb8edd5cc7d2dca70fbfe637ef..b29593eff33df15a945d8300e7be69bc91a3bcc6 100755 --- a/lg/members_en.txt +++ b/lg/members_en.txt @@ -4,14 +4,39 @@ SORT_BY_EXPIRE_MEMBERSHIP expire membership time SORT_LAST_RENEWAL last renewal time SORT_BY_OLDEST_SIG oldest sig time SORT_BY_LAST_SIG last sig time +SORT_BY_CENTRALITY centrality degree +SORT_BY_QUALITY member quality SORT_BY_SIG_COUNT certifications count ORDER_BY in the following order ASC ascending DESC descending SUBMIT_BUTTON submit +CHECKBOX_CENTRALITY Recalculate members centrality degree CHECKBOX_PENDING_SIGS Include pending certifications CHECKBOX_MODE_SIG Print emitted certifications (Instead of received certifications) +LEGEND Legend LEGEND_AVAILABILITY [availability : means this certification may already be written in the next block.] +WHAT_IS_CENTRALITY_TITLE What is the centrality degree ? +WHAT_IS_CENTRALITY_TEXT The centrality degree of a member is the number of pairs directed (member->member) for which the member makes left one of the shortest paths. +QUALITY_MEMBER_TITLE What is the certification quality ? +QUALITY_MEMBER_TXT It is the report enters the rate of referring members reachable by a certification of this member and the rate of referring members which needs to achieve to respect the rule of distance. +WOT_TENSION_STATE Wot tension state +CENTRALITY_NOT_CALC The degree of centrality of the members was never calculated +CENTRALITY_CALC_BUSY The centrality data are currently recalculate, to see them, refresh this page by a few minutes +MEAN_CENTRALITY Mean centrality degree +NUMBER_OF_EXIST_PATH Number of pairs directed for which there is a path of 5 steps or less +NUMBER_OF_PAIRS_MEMBER Number of pairs directed (member->member) +PROPORTION_OF_EXIST_PATH Percent of pairs directed for which there is a path of 5 steps or less +MEAN_LENGTH_PATH mean length shortest path +WOT_TENSION_FACTOR Wot tension factor* +DATA_AT Data at +meanMembersReachedByMembersInSingleExtCert Mean members/sentries reached in single member/sentry certification +SENTRIES_REACHED sentries reached +MEMBERS_REACHED members reached +SENTRY_CERT sentry certification +MEMBER_CERT member certification +MEAN_QUALITY mean quality +PROPORTION_MEMBERS_WITH_QUALITY_UPPER_1 Proportion of members with an upper quality an equal in 1 CURRENT_BLOCKCHAIN_TIME Current blockchain time TABLE_TITLE Members that will expire in less than DAYS days @@ -26,8 +51,14 @@ COL_LIST_RECEIVED_CERT list received certifications COL_LIST_EMITTED_CERT list emitted certifications LAST2OLDEST récentes -> anciennes OLDEST2LAST anciennes -> récentes +REFERRING_MEMBER referring +YES yes +NO no +QUALITY_EXT quality +CENTRALITY centrality EMITTED emitted WRITTEN written +INVALID_BLOCKSTAMP invalid blockstamp CERT_AVAILABLE available OVERALL Overall MEMBERS members \ No newline at end of file diff --git a/lg/members_fr.txt b/lg/members_fr.txt index c2db83b8c994d56642ed2e704c94e9f5afff3c8a..0d2031ee8410378de7035120bbe922323505f65d 100755 --- a/lg/members_fr.txt +++ b/lg/members_fr.txt @@ -4,14 +4,39 @@ SORT_BY_EXPIRE_MEMBERSHIP date d'expiration du membership SORT_LAST_RENEWAL date de dernier renouvellement SORT_BY_OLDEST_SIG date de plus vielle certification SORT_BY_LAST_SIG date de plus récente certification +SORT_BY_CENTRALITY degré de centralité +SORT_BY_QUALITY qualité du membre SORT_BY_SIG_COUNT nombre de certifications reçues ORDER_BY dans l'ordre ASC croissant DESC décroissant SUBMIT_BUTTON recharger +CHECKBOX_CENTRALITY Recalculer le degré de centralité des membres CHECKBOX_PENDING_SIGS Inclure les certifications en piscine CHECKBOX_MODE_SIG Afficher les certifications émises (à la place des certifications reçus) +LEGEND Légende LEGEND_AVAILABILITY [disponibilité : date à partir de laquelle cette certification pourra être écrite compte tenu de sigPeriod] +WHAT_IS_CENTRALITY_TITLE Qu'est ce que le degré de centralité ? +WHAT_IS_CENTRALITY_TEXT Le degré de centralité d'un membre est le nombre de couples orientés (membre->membre) pour lesquels il fait parti d'un des plus court chemin reliant ce couple. +QUALITY_MEMBER_TITLE Qu'est ce que la qualité d'un membre ? +QUALITY_MEMBER_TXT C'est le rapport entre le taux de membres référents rendu atteignables par une certification de ce membre et le taux de membres référents qui faut atteindre pour respecter la règle de distance. Si ce nombre est supérieur ou égal à 1 c'est qu'une certification de ce membre suffit à elle seule pour respecter la règle de distance. Si les qualités de plusieurs certifieurs ne s'additionnent pas c'est tout simplement parce qu'une grande partie des membres référents qu'ils permettent d'atteindre sont les mêmes. +WOT_TENSION_STATE État de tension de la toile +CENTRALITY_NOT_CALC le degré de centralité des membres n'a jamais été calculé +CENTRALITY_CALC_BUSY Les données de centralité sont en cours de mise à jours, pour les voir, rechargez cette page d'ici quelques minutes. +MEAN_CENTRALITY Dégré de centralité moyen +NUMBER_OF_EXIST_PATH Nombre de couples orientés pour lequels il existe un chemin de 5 pas ou moins +NUMBER_OF_PAIRS_MEMBER Nombre de couples orientés (membre->membre) +PROPORTION_OF_EXIST_PATH Proportion de couples orientés pour lequels il existe un chemin de 5 pas ou moins +MEAN_LENGTH_PATH Longueur moyenne d'un plus court chemin +WOT_TENSION_FACTOR Facteur de tension de la toile* +DATA_AT Données au +meanMembersReachedByMembersInSingleExtCert Taux moyen de membres joiniables via une seule certification +MEMBER_CERT certification d'un membre +MEMBERS_REACHED membres joiniables +SENTRY_CERT certification d'un membre référent +SENTRIES_REACHED membres référents joiniables +MEAN_QUALITY Qualité moyenne +PROPORTION_MEMBERS_WITH_QUALITY_UPPER_1 Proportion de membre avec une qualité >= 1 CURRENT_BLOCKCHAIN_TIME Temps Blockchain actuel TABLE_TITLE Membres dont le statut de membre va expirer dans moins de DAYS jours @@ -26,8 +51,14 @@ COL_LIST_RECEIVED_CERT liste des certifications reçues COL_LIST_EMITTED_CERT liste des certifications émises LAST2OLDEST récentes -> anciennes OLDEST2LAST anciennes -> récentes +REFERRING_MEMBER référent +YES oui +NO non +QUALITY_EXT qualité +CENTRALITY centralité EMITTED émise WRITTEN écrite +INVALID_BLOCKSTAMP blockstamp incorrect CERT_AVAILABLE disponible OVERALL Total MEMBERS membres \ No newline at end of file diff --git a/lg/willMembers_en.txt b/lg/willMembers_en.txt index 34f2383b3291217deff6433178e7098520ea2e9b..47d0f60971b02dd7ffdae69dddcd133c1911a52f 100755 --- a/lg/willMembers_en.txt +++ b/lg/willMembers_en.txt @@ -7,30 +7,46 @@ ORDER in the following order ORDER_ASC ascending ORDER_DESC descending SUBMIT_TXT Submit -CHECKBOX_HIDE_IDTY_WITH_ZERO_CERT Hide identities that didn't receive any certification. +CHECKBOX_SHOW_IDTY_WITH_ZERO_CERT Show identities that didn't receive any certification. CHECKBOX_SORT_SIG sort certifications by availability time. +LEGEND Legend SIG_PERIOD_LEGEND darkred : [date from which this certification can be written according to sigPeriod]. SIG_PERIOD_LEGEND2 green : [available] : means this certification may already be written in the next block. HOW_TO_BECOME_MEMBER_TITLE When does an identity become a member ? HOW_TO_BECOME_MEMBER_TEXT An identity becomes a member as soon as it has at least <b>5</b> certifications in the <font color='green'>[available]</font> state <b>at the same time <u>and</u></b> it respects the Distance Rule.<br>You can click directly on an identity to reach the according wotex page to verify if it respects the Distance Rule. DISTANCE_RULE_TITLE What is the Distance Rule ? -DISTANCE_RULE_TXT It is required that for more than 80% of the referring members, a path of at most 5 steps should exist between each of them and the identity about to be certified.<br>Every member automatically becomes a referring member as soon as he has emitted <b><u>and</u></b> received Y[n] certifications. (Y[n] = CEILING(N(t)^(1/5))). +DISTANCE_RULE_TXT It is required that for more than <font color='blue'><b>80% of the referring members</b></font>, a path of at most 5 steps should exist between each of them and the identity about to be certified.<br>Every member automatically becomes a referring member as soon as he has emitted <b><u>and</u></b> received Y[n] certifications. (Y[n] = CEILING(N(t)^(1/5))).<br>Currently, Yn = +WHAT_IS_CENTRALITY_TITLE What is the centrality degree ? +WHAT_IS_CENTRALITY_TEXT The centrality degree of a member is the number of pairs directed (member->member) for which the member makes left one of the shortest paths. +QUALITY_MEMBER_TITLE What is the certification quality ? +QUALITY_MEMBER_TXT It is the report enters the rate of referring members reachable by a certification of this member and the rate of referring members which needs to achieve to respect the rule of distance. BLOCKCHAIN_TIME Current Blockchain Time TABLE_TITLE1 Identities that will expire in less than TABLE_TITLE2 days COUNT_READY_MEMBERS Identities having received 5 valid certifications or more -COL_1 uid -COL_2 Identity created -COL_3 Identity expires +WOT_TENSION_STATE Quality averages cases of membership according to the number of received valid certifications +NUMBER_OF_RECEIVED_CERTS Number of received certs +MEAN_SENTRIES_REACHED mean sentries reached +MEAN_QUALITY_CERTS Quality averages of the set of certifications +MEAN_MEMBERS_REACHED mean members reached +IDENTITY Identity +MEMBERSHIP_CASE membership case COL_4 List of received certifications (recent -> old) COL_4_WITH_AVAIlABILITY_SORT List of received certifications (by availability time) -COL_DISTANCE distance -COL_DISTANCE_isOutdistanced KO -COL_DISTANCE_isNotOutdistanced OK -COL_MEMBERSHIP membership -COL_MEMBERSHIP_KO KO -COL_MEMBERSHIP_OK OK +EMITTED_ON emitted on +AT_BLOCK at block +EXPIRE_ON expire on +KO KO +OK OK +MEMBERSHIP_ASKED Membership asked +YES yes +NO no +QUALITY_CERTIFIERS quality certifiers +DISTANCE Distance +CERTIFIERS_COUNT certifiers count +QUALITY quality SIG_PERIOD_OK available +IDTY_REVOKED [identity revoked] INVALID_BLOCKSTAMP invalid blockstamp LAST_TR1 total LAST_TR2 identities diff --git a/lg/willMembers_fr.txt b/lg/willMembers_fr.txt index ba39fc2a5f3bfb6c83d7b4364097e3e65e521c56..589ccb14926c58e66b690a8386e429ddb250cbac 100755 --- a/lg/willMembers_fr.txt +++ b/lg/willMembers_fr.txt @@ -7,30 +7,46 @@ ORDER dans l'ordre ORDER_ASC croissant ORDER_DESC décroissant SUBMIT_TXT envoyer -CHECKBOX_HIDE_IDTY_WITH_ZERO_CERT Masquer les identités n'ayant reçu aucune certification. +CHECKBOX_SHOW_IDTY_WITH_ZERO_CERT Afficher les identités n'ayant reçu aucune certification. CHECKBOX_SORT_SIG trier les certifications par date de disponibilité. +LEGEND Légende SIG_PERIOD_LEGEND darkred : [date à partir de laquelle cette certification pourra être écrite compte tenu de sigPeriod]. SIG_PERIOD_LEGEND2 green : [disponible] : signifie que cette certification pourrait être écrite dès le prochain block. HOW_TO_BECOME_MEMBER_TITLE À quel moment une identité devient membre ? HOW_TO_BECOME_MEMBER_TEXT Une identité devient membre dès qu'elle bénéficie d'au moins <b>5</b> certifications à l'état <font color='green'>[disponible]</font> <b>au même moment <u>et</u></b> qu'elle respecte la règle de distance.<br>Vous pouvez cliquer directement sur une identité pour arriver sur la page wotex correspondante afin de vérifier si elle respecte la règle de distance. DISTANCE_RULE_TITLE C'est quoi la règle de distance ? -DISTANCE_RULE_TXT Il faut que pour plus de 80% des membres référents, il existe au moins un chemin de 5 pas ou moins entre eux et l'identité considérée.<br>Tout membre devient automatiquement référent dès lors qu'il a émis <b><u>et</u></b> reçu Y[n] certifications. (Y[n] = CEILING(N(t)^(1/5))). +DISTANCE_RULE_TXT Il faut qu'il existe un chemin de 5 pas maximum d'au moins <font color='blue'><b>80% des membres référents</b></font> vers l'identité qui souhaite devenir membre.<br>Tout membre devient automatiquement référent dès lors qu'il a émis <b><u>et</u></b> reçu Y[n] certifications. (Y[n] = CEILING(N(t)^(1/5))).<br>Actuellement, Yn = +WHAT_IS_CENTRALITY_TITLE Qu'est ce que le degré de centralité ? +WHAT_IS_CENTRALITY_TEXT Le degré de centralité d'un membre est le nombre de couples orientés (membre->membre) pour lesquels il fait parti d'un des plus court chemin reliant ce couple. +QUALITY_MEMBER_TITLE Qu'est ce que la qualité d'un membre ? +QUALITY_MEMBER_TXT C'est le rapport entre le taux de membres référents rendu atteignables par une certification de ce membre et le taux de membres référents qui faut atteindre pour respecter la règle de distance. Si ce nombre est supérieur ou égal à 1 c'est qu'une certification de ce membre suffit à elle seule pour respecter la règle de distance. Si les qualités de plusieurs certifieurs ne s'additionnent pas c'est tout simplement parce qu'une grande partie des membres référents qu'ils permettent d'atteindre sont les mêmes. BLOCKCHAIN_TIME Temps Blockchain actuel TABLE_TITLE1 Identités qui vont expirer dans moins de TABLE_TITLE2 jours COUNT_READY_MEMBERS Identités avec au moins 5 certifications valides reçues -COL_1 uid -COL_2 création identité -COL_3 expiration identité +WOT_TENSION_STATE Qualité moyenne des dossiers d'adhésion en fonction du nombre de certifications valides reçues +NUMBER_OF_RECEIVED_CERTS Nombre de certifications reçues +MEAN_SENTRIES_REACHED Taux moyen de membres référents joiniables +MEAN_QUALITY_CERTS Qualité moyenne par groupe de certifications reçues +MEAN_MEMBERS_REACHED Taux moyen de membres joiniables +IDENTITY Identité +MEMBERSHIP_CASE dossier d'adhésion COL_4 liste des certifications reçues (récentes -> anciennes) COL_4_WITH_AVAIlABILITY_SORT liste des certifications reçues (par date de disponibilité) -COL_DISTANCE distance -COL_DISTANCE_isOutdistanced KO -COL_DISTANCE_isNotOutdistanced OK -COL_MEMBERSHIP adhésion -COL_MEMBERSHIP_KO KO -COL_MEMBERSHIP_OK OK +EMITTED_ON émise le +AT_BLOCK au bloc +EXPIRE_ON expire le +KO KO +OK OK +MEMBERSHIP_ASKED Adhésion demandée +YES oui +NO non +QUALITY_CERTIFIERS qualité certificateurs +DISTANCE Distance +CERTIFIERS_COUNT Nombre de certificateurs +QUALITY qualité SIG_PERIOD_OK disponible +IDTY_REVOKED [identité revoquée] INVALID_BLOCKSTAMP blockstamp incorrect LAST_TR1 total LAST_TR2 identités diff --git a/lib/constants.js b/lib/constants.js new file mode 100755 index 0000000000000000000000000000000000000000..9f2a70fe3a6f4964f91aaa268249d394e3894801 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + USE_WOTB6: false, + MIN_WILLMEMBERS_UPDATE_FREQ: 180, + MIN_MEMBERS_UPDATE_FREQ: 180, + STEP_COUNT_MIN: 4, + STEP_COUNT_MAX: 150, + MIN_CACHE_UPDATE_FREQ: 150 // 2 min 30 (150 sec) +}; \ No newline at end of file diff --git a/lib/main.js b/lib/main.js index 2094bd6fe17013a75f2942b5b0842c53f38aea6c..80ad872dd2ee3663d114b5b016c165a3ba52da03 100755 --- a/lib/main.js +++ b/lib/main.js @@ -14,8 +14,9 @@ module.exports = (duniterServer, host, port, appParente, program) => co(function // Define cache var cache = { + // membersCount + lockMembersCount: false, lastUptime: 0, - lock : false, beginBlock: null, currentBlockNumber : 0, currentBlockTime: 0, @@ -25,10 +26,10 @@ module.exports = (duniterServer, host, port, appParente, program) => co(function stepUnit: null, stepTime: null, onlyDate: null, - Yn: 0, - pubkeys: new Array(), - pub_index: new Array(), - blockchain: new Array() + Yn: 0, + pubkeys: new Array(), + pub_index: new Array(), + blockchain: new Array() }; // Create or open monit database diff --git a/lib/updateCache.js b/lib/updateCache.js index f0535016e5bf0f88746e0f48451dc283c63b0cdb..088ec1c2091de232470b03b6fa5d35d75dc1b780 100755 --- a/lib/updateCache.js +++ b/lib/updateCache.js @@ -2,10 +2,7 @@ const co = require('co'); //const sqlite3 = require('sqlite3').verbose(); - -const STEP_COUNT_MIN = 4; -const STEP_COUNT_MAX = 150; -const MIN_CACHE_UPDATE_FREQ = 150; // 2 min 30 (150 sec) +const constants = require(__dirname + '/constants') /** * updateCache @@ -16,18 +13,28 @@ module.exports = (req, res, next) => co(function *() { var { duniterServer, sigValidity, msValidity, sigWindow, idtyWindow, sigQty, stepMax, cache, dbPath } = req.app.locals try { - // if updateCache working, wait - while (cache.lock); - - // Lock cache - cache.lock = true; + // Définition des variables + let updateCache = false; + + // Cacluler s'il faut mettre à jour le cache ou pas + updateCache = (Math.floor(Date.now() / 1000) > (cache.lastUptime + constants.MIN_CACHE_UPDATE_FREQ)); + + // Si le cache membersCount est dévérouillé, le vérouiller, sinon ne pas réinitialiser le cache + if (updateCache && !cache.lockMembersCount) + { + cache.lockMembersCount = true; + } + else if(cache.lockMembersCount) + { + updateCache = false; + } // If fork, unstack cache let reinitBdd = false; if (cache.endBlock != null) { let checkBlock = yield duniterServer.dal.peerDAL.query('SELECT `hash` FROM block WHERE `fork`=0 AND `number`='+(cache.blockchain[cache.blockchain.length-1].number)+' LIMIT 1 '); - if (cache.blockchain.length > 0 && cache.blockchain[cache.blockchain.length-1].hash != checkBlock[0].hash && Math.floor(Date.now() / 1000) > (cache.lastUptime + MIN_CACHE_UPDATE_FREQ)) + if (cache.blockchain.length > 0 && cache.blockchain[cache.blockchain.length-1].hash != checkBlock[0].hash && updateCache) { /*// unstack loop while (cache.blockchain.length > 0 && cache.blockchain[cache.blockchain.length-1].hash != checkBlock[0].hash) @@ -49,23 +56,21 @@ module.exports = (req, res, next) => co(function *() { else {*/ // reinitialize cache - cache = { - lastUptime: 0, - lock : false, - beginBlock: null, - currentBlockNumber : 0, - currentBlockTime: 0, - currentSentries: 0, - endBlock: null, - step: null, - stepUnit: null, - stepTime: null, - onlyDate: null, - Yn: 0, - pubkeys: new Array(), - pub_index: new Array(), - blockchain: new Array() - }; + cache.lastUptime = 0; + cache.lockMembersCount = false; + cache.beginBlock = null; + cache.currentBlockNumber = 0; + cache.currentBlockTime = 0; + cache.currentSentries = 0; + cache.endBlock = null; + cache.step = null; + cache.stepUnit = null; + cache.stepTime = null; + cache.onlyDate = null; + cache.Yn = 0; + cache.pubkeys = new Array(); + cache.pub_index = new Array(); + cache.blockchain = new Array(); // reinitialize bdd reinitBdd = true; @@ -121,7 +126,7 @@ module.exports = (req, res, next) => co(function *() { } // get endBlock - if ( typeof(req.query.end) == 'undefined' || req.query.end < 0) + if ( typeof(req.query.end) == 'undefined' || req.query.end <= 0) { cache.endBlock = yield duniterServer.dal.peerDAL.query('SELECT `hash`,`medianTime`,`number`,`membersCount` FROM block WHERE `fork`=0 ORDER BY `medianTime` DESC LIMIT 1 '); } @@ -140,18 +145,18 @@ module.exports = (req, res, next) => co(function *() { { cache.beginBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`number` FROM block WHERE `fork`=0 AND `number`=0 LIMIT 1 '); } else if (req.query.begin > cache.endBlock[0].number) { - let beginTime = cache.endBlock[0].medianTime-(parseInt(cache.step)*unitTime*STEP_COUNT_MIN); + let beginTime = cache.endBlock[0].medianTime-(parseInt(cache.step)*unitTime*constants.STEP_COUNT_MIN); cache.beginBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`number` FROM block WHERE `fork`=0 AND `medianTime` >= \''+beginTime+'\' ORDER BY `medianTime` ASC LIMIT 1 '); } else { cache.beginBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`number` FROM block WHERE `fork`=0 AND `number`='+req.query.begin+' LIMIT 1 '); } // Apply STEP_COUNT_MAX and calculate stepTime - if ( Math.ceil((cache.endBlock[0].medianTime-cache.beginBlock[0].medianTime)/(cache.step*unitTime)) > STEP_COUNT_MAX ) - { cache.step = Math.ceil((cache.endBlock[0].medianTime-cache.beginBlock[0].medianTime)/(STEP_COUNT_MAX*unitTime)); } + if ( Math.ceil((cache.endBlock[0].medianTime-cache.beginBlock[0].medianTime)/(cache.step*unitTime)) > constants.STEP_COUNT_MAX ) + { cache.step = Math.ceil((cache.endBlock[0].medianTime-cache.beginBlock[0].medianTime)/(constants.STEP_COUNT_MAX*unitTime)); } cache.stepTime = parseInt(cache.step)*unitTime; // if new blocks and MIN_CACHE_UPDATE_FREQ pass, update cache - if ( parseInt(cache.endBlock[0].number) >= cache.currentBlockNumber && Math.floor(Date.now() / 1000) > (cache.lastUptime + MIN_CACHE_UPDATE_FREQ)) + if ( parseInt(cache.endBlock[0].number) >= cache.currentBlockNumber && Math.floor(Date.now() / 1000) > (cache.lastUptime + constants.MIN_CACHE_UPDATE_FREQ)) { // let previousCacheTime = (cache.blockchain.length > 0) ? cache.blockchain[cache.blockchain.length-1].medianTime:0; var newBlocks = yield duniterServer.dal.peerDAL.query( @@ -517,8 +522,11 @@ module.exports = (req, res, next) => co(function *() { cache.lastUptime = Math.floor(Date.now() / 1000); } - // Unlock cache - cache.lock = false; + // Unlock Members count cache + if (updateCache) + { + cache.lockMembersCount = false; + } next() } catch (e) { diff --git a/package.json b/package.json index 913d205f787926f93bb3e58eb08d8efb5a1933f8..eb16f0ab95358fad855b23f909219b3de524f61b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "duniter-currency-monit", - "version": "0.3.2", + "version": "0.3.6", "main": "index.js", "license": "AGPLv3", "dependencies": { @@ -23,13 +23,13 @@ "ws": "1.1.1" }, "devDependencies": { - "duniter": "1.3.9", + "duniter": "1.3.14", "duniter-bma": "1.3.x", "duniter-crawler": "1.3.x", "duniter-keypair": "1.3.x", "duniter-prover": "1.3.x" }, "peerDependencies": { - "duniter": "1.3.13" + "duniter": "1.3.14" } } diff --git a/routes/blockCount.js b/routes/blockCount.js index 5272c24f2892103ba992f615a2121384a0bbc8a6..a03a86fc671f706575c17240cab58b7aeabbe329 100755 --- a/routes/blockCount.js +++ b/routes/blockCount.js @@ -29,9 +29,9 @@ module.exports = (req, res, next) => co(function *() { let newHashPreviousCurrentblock = yield duniterServer.dal.peerDAL.query('SELECT `hash` FROM block WHERE `fork`=0 AND `number` = '+(blockchain.length-1)+' LIMIT 1'); if ( hashPreviousCurrentblock != newHashPreviousCurrentblock ) { - blockchain.splice(0, blockchain.length); - hashPreviousCurrentblock = 0; - previousBlockchainTime = 0; + blockchain.splice(0, blockchain.length); + hashPreviousCurrentblock = 0; + previousBlockchainTime = 0; } } diff --git a/routes/members.js b/routes/members.js index 0e566fffd265e0854ef8790213bfe947b1529ecd..e23b09331d51c6fb62083d63d40202eba1c2008f 100755 --- a/routes/members.js +++ b/routes/members.js @@ -1,13 +1,49 @@ "use strict"; const co = require('co') -//const wotb = require('wotb') + +const constants = require(__dirname + '/../lib/constants') + +const wotb = (constants.USE_WOTB6) ? require('wotb'):null; const timestampToDatetime = require(__dirname + '/../lib/timestampToDatetime') +// Préserver les résultats en cache +var lockMembers = false; +var membersLastUptime = 0; +var previousMode = null; +var previousCentrality = null; +var membersList = []; +var membersIdentity = []; +var membersFirstCertifExpire = []; +var membersCertifsList = []; +var membersPendingCertifsList = []; +var membershipsTimeList = []; +var membershipsBlockNumberList = []; +var membershipsExpireTimeList = []; +var nbMaxCertifs = 0; +var sentries = []; +var sentriesIndex = []; +var membersQualityExt = []; +var meanSentriesReachedBySentriesInSingleExtCert = 0; +var meanMembersReachedBySentriesInSingleExtCert = 0; +var meanSentriesReachedByMembersInSingleExtCert = 0; +var meanMembersReachedByMembersInSingleExtCert = 0; +var proportionMembersWithQualityUpper1 = 0; +var proportionMembersWithQualityUpper1IfNoSentries = 0; + +// wotCentrality cache +var lockCentralityCalc = false; +var membersLastCentralityCalcTime = 0; +var membersCentrality = []; +var meanCentrality = 0; +var meanShortestsPathLength = 0; +var nbShortestsPath = 0; + + module.exports = (req, res, next) => co(function *() { - var { duniterServer, sigValidity, msValidity, sigWindow, idtyWindow, cache } = req.app.locals + var { duniterServer } = req.app.locals try { // Initaliser les constantes @@ -18,192 +54,385 @@ module.exports = (req, res, next) => co(function *() { const dSen = Math.ceil(Math.pow(membersCount, 1 / conf.stepMax)); // Initaliser les variables - var contenu = ""; - var membersIdentity = []; - var membersFirstCertifExpire = []; - var membersCertifsList = []; - var membersPendingCertifsList = []; - var membershipsTimeList = []; - var membershipsBlockNumberList = []; - var membershipsExpireTimeList = []; - + let membersListOrdered = []; + let membersCertifsListSorted = []; + let tabSort = []; + let countSentries = 0; + let membersNbSentriesUnreached = []; + // Récupéré les paramètres var days = req.query.d || 400 // Valeur par défaut var mode = req.query.mode || 'received' // Valeur par défaut var order = req.query.d && req.query.order || 'desc' // Valeur par défaut var sort_by = req.query.sort_by || "idtyWritten" // Valeur par défaut - var pendingSigs = req.query.pendingSigs || "no"; - var format = req.query.format || 'HTML'; + var pendingSigs = req.query.pendingSigs || "no"; // Valeur par défaut + var centrality = req.query.centrality || "no"; // Valeur par défaut + var format = req.query.format || 'HTML'; // Valeur par défaut // Alimenter wotb avec la toile actuelle - //const wotbInstance = wotb.newFileInstance(duniterServer.home + '/wotb.bin'); + const wotbInstance = (constants.USE_WOTB6) ? wotb.newFileInstance(duniterServer.home + '/wotb.bin'):duniterServer.dal.wotb; + + // Vérifier si le cache doit être Réinitialiser + let reinitCache = (Math.floor(Date.now() / 1000) > (membersLastUptime + constants.MIN_MEMBERS_UPDATE_FREQ)); + + // Si changement de conditions, alors forcer le rechargement du cache s'il n'est pas, vérouillé, sinon forcer les conditions à celles en mémoire + if (previousMode != mode || previousCentrality != centrality) + { + if (!lockMembers) + { + lockMembers = true; + reinitCache = true; + } + else + { + mode = previousMode; + centrality = previousCentrality; + } + } + // Sinon, si les conditions sont identiques : + // Si le cache members est dévérouillé, le vérouiller, sinon ne pas réinitialiser le cache + else if (reinitCache && !lockMembers) + { + lockMembers = true; + } + else if(lockMembers) + { + reinitCache = false; + } + + if (reinitCache) + { + // Réinitialiser le cache + membersLastUptime = Math.floor(Date.now() / 1000); + previousMode = mode; + previousCentrality = centrality; + membersList = []; + membersIdentity = []; + membersFirstCertifExpire = []; + membersCertifsList = []; + membersPendingCertifsList = []; + membershipsTimeList = []; + membershipsBlockNumberList = []; + membershipsExpireTimeList = []; + nbMaxCertifs = 0; + sentries = []; + sentriesIndex = []; + membersQualityExt = []; + meanSentriesReachedBySentriesInSingleExtCert = 0; + meanMembersReachedBySentriesInSingleExtCert = 0; + meanSentriesReachedByMembersInSingleExtCert = 0; + meanMembersReachedByMembersInSingleExtCert = 0; + proportionMembersWithQualityUpper1 = 0; + proportionMembersWithQualityUpper1IfNoSentries = 0; + + // Réinitialiser le cache des données de centralité + if (centrality=='yes') + { + membersLastCentralityCalcTime = Math.floor(Date.now() / 1000); + membersCentrality = []; + meanCentrality = 0; + meanShortestsPathLength = 0; + nbShortestsPath = 0; + } + + // Récupérer la liste des membres référents + sentries = wotbInstance.getSentries(dSen); - // Récupérer la liste des identités ayant actuellement le statut de membre - const membersList = yield duniterServer.dal.peerDAL.query('SELECT `uid`,`pub`,`member`,`written_on`,`wotb_id` FROM i_index WHERE `member`=1'); + // Récupérer la liste des identités ayant actuellement le statut de membre + membersList = yield duniterServer.dal.peerDAL.query('SELECT `uid`,`pub`,`member`,`written_on`,`wotb_id` FROM i_index WHERE `member`=1'); - // Récupérer pour chaque identité, le numéro du block d'écriture du dernier membership - // Ainsi que la première ou dernière certification - var nbMaxCertifs = 0; - for (let m=0;m<membersList.length;m++) - { - // Récupérer les blockstamp d'écriture et date d'expiration du membership courant du membre m - let tmpQueryResult = yield duniterServer.dal.peerDAL.query( - 'SELECT `written_on`,`expires_on` FROM m_index WHERE `pub`=\''+membersList[m].pub+'\' ORDER BY `expires_on` DESC LIMIT 1'); - membershipsExpireTimeList.push(tmpQueryResult[0].expires_on); - - // Extraire le numéro de bloc du blockstamp d'écriture du membership courant - let blockstampMembershipWritten = tmpQueryResult[0].written_on.split("-"); // Separate blockNumber and blockHash - membershipsBlockNumberList.push(blockstampMembershipWritten[0]); - - // Extraire le numéro de bloc du blockstamp d'écriture de l'identité du membre - let blockstampIdtyWritten = membersList[m].written_on.split("-"); // Separate blockNumber and blockHash - - // Récupérer le champ medianTime du bloc d'écriture de l'identité du membre - let resultQueryTimeWrittenIdty = yield duniterServer.dal.peerDAL.query( - 'SELECT `medianTime` FROM block WHERE `number`=\''+blockstampIdtyWritten[0]+'\' LIMIT 1') - - // Tester la distance - //let tmpWot = wotbInstance.memCopy(); - let tmpWot = duniterServer.dal.wotb.memCopy(); + // Récupérer pour chaque identité, le numéro du block d'écriture du dernier membership + // Ainsi que la première ou dernière certification + for (let m=0;m<membersList.length;m++) + { + // Récupérer les blockstamp d'écriture et date d'expiration du membership courant du membre m + let tmpQueryResult = yield duniterServer.dal.peerDAL.query( + 'SELECT `written_on`,`expires_on` FROM m_index WHERE `pub`=\''+membersList[m].pub+'\' ORDER BY `expires_on` DESC LIMIT 1'); + membershipsExpireTimeList.push(tmpQueryResult[0].expires_on); + + // Extraire le numéro de bloc du blockstamp d'écriture du membership courant + let blockstampMembershipWritten = tmpQueryResult[0].written_on.split("-"); // Separate blockNumber and blockHash + membershipsBlockNumberList.push(blockstampMembershipWritten[0]); + + // Extraire le numéro de bloc du blockstamp d'écriture de l'identité du membre + let blockstampIdtyWritten = membersList[m].written_on.split("-"); // Separate blockNumber and blockHash + + // Récupérer le champ medianTime du bloc d'écriture de l'identité du membre + let resultQueryTimeWrittenIdty = yield duniterServer.dal.peerDAL.query( + 'SELECT `medianTime` FROM block WHERE `number`=\''+blockstampIdtyWritten[0]+'\' LIMIT 1') + + // Vérifier si le membre est référent + let currentMemberIsSentry = false; + sentriesIndex[membersList[m].uid] = false; + for (let s=0;s<sentries.length;s++) + { + if (sentries[s] == membersList[m].wotb_id) + { + currentMemberIsSentry=true; + sentries.splice(s, 1); + sentriesIndex[membersList[m].uid] = true; + } + } + + // Réinitialiser le degré de centralité du membre + if (centrality=='yes') + { + membersCentrality[membersList[m].wotb_id] = 0; + } + + // Créer une wot temporaire + let tmpWot = wotbInstance.memCopy(); + + // Récupérer les informations détaillés de distance pour le membre courant + let detailedDistance = null; + if (constants.USE_WOTB6) + { + detailedDistance = tmpWot.detailedDistance(membersList[m].wotb_id, dSen, conf.stepMax, conf.xpercent); + } + else + { + detailedDistance = { + isOutdistanced: tmpWot.isOutdistanced(membersList[m].wotb_id, dSen, conf.stepMax, conf.xpercent) + }; + } + + + if (constants.USE_WOTB6) + { + // Calculate membersNbSentriesUnreached + membersNbSentriesUnreached[membersList[m].uid] = parseInt(detailedDistance.nbSentries)-parseInt(detailedDistance.nbSuccess); + + // Récupérer les informations détaillés de distance pour une nouvelle identité qui ne serait certifiée que par le membre courant (ce qui équivaut à récupérer les informations de distance pour le membre courant en décrémentant stepMax de 1) + let detailedDistanceQualityExt = tmpWot.detailedDistance(membersList[m].wotb_id, dSen, conf.stepMax-1, conf.xpercent); + + // Calculer la qualité du membre courant + membersQualityExt[membersList[m].uid] = ((detailedDistanceQualityExt.nbSuccess/detailedDistanceQualityExt.nbSentries)/conf.xpercent).toFixed(2); + if (membersQualityExt[membersList[m].uid] >= 1.0) + { + proportionMembersWithQualityUpper1++; + } + + // Calculer la qualité du membre courant s'il n'y avait pas de référents (autrement di si tout les membres était référents) + let membersQualityIfNoSentries = ((detailedDistanceQualityExt.nbReached/membersList.length)/conf.xpercent).toFixed(2); + //console.log("membersQualityIfNoSentries[%s] = %s", membersList[m].uid, membersQualityIfNoSentries); + if (membersQualityIfNoSentries >= 1.0) + { + proportionMembersWithQualityUpper1IfNoSentries++; + } + + // Calculate meanSentriesReachedBySentriesInSingleExtCert, meanMembersReachedBySentriesInSingleExtCert, meanSentriesReachedByMembersInSingleExtCert and meanMembersReachedByMembersInSingleExtCert + if (currentMemberIsSentry) + { + meanSentriesReachedBySentriesInSingleExtCert += parseFloat(((detailedDistanceQualityExt.nbSuccess/detailedDistanceQualityExt.nbSentries)*100).toFixed(2)); + meanMembersReachedBySentriesInSingleExtCert += parseFloat(((detailedDistanceQualityExt.nbReached/membersList.length)*100).toFixed(2)); + countSentries++; + } + meanSentriesReachedByMembersInSingleExtCert += parseFloat(((detailedDistanceQualityExt.nbSuccess/detailedDistanceQualityExt.nbSentries)*100).toFixed(2)); + meanMembersReachedByMembersInSingleExtCert += parseFloat(((detailedDistanceQualityExt.nbReached/membersList.length)*100).toFixed(2)); + } + + // Nettoyer la wot temporaire + tmpWot.clear(); + + // Stocker les informations de l'identité + membersIdentity.push({ + writtenBloc: blockstampIdtyWritten[0], + writtenTimestamp: resultQueryTimeWrittenIdty[0].medianTime, + detailedDistance: detailedDistance, + isSentry: currentMemberIsSentry + }); + + // récupérer toutes les certification reçus/émises par l'utilisateur + let tmpQueryCertifsList = []; + let tmpOrder = (sort_by == "lastSig") ? 'DESC' : 'ASC'; + if (mode == 'emitted') + { + tmpQueryCertifsList = yield duniterServer.dal.peerDAL.query( + 'SELECT `receiver`,`written_on`,`expires_on` FROM c_index WHERE `issuer`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); + } + else + { + tmpQueryCertifsList = yield duniterServer.dal.peerDAL.query( + 'SELECT `issuer`,`written_on`,`expires_on` FROM c_index WHERE `receiver`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); + } - //let detailedDistance = tmpWot.detailedDistance(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent); - //let isOutdistanced = detailedDistance.isOutdistanced; - let isOutdistanced = tmpWot.isOutdistanced(membersList[m].wotb_id, dSen, conf.stepMax, conf.xpercent); - - // Stocker les informations de l'identité - membersIdentity.push({ - writtenBloc: blockstampIdtyWritten[0], - writtenTimestamp: resultQueryTimeWrittenIdty[0].medianTime, - isOutdistanced: isOutdistanced - }); - - // récupérer toutes les certification reçus/émises par l'utilisateur - let tmpQueryCertifsList = []; - let tmpOrder = (sort_by == "lastSig") ? 'DESC' : 'ASC'; - if (mode == 'emitted') - { - tmpQueryCertifsList = yield duniterServer.dal.peerDAL.query( - 'SELECT `receiver`,`written_on`,`expires_on` FROM c_index WHERE `issuer`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); - } - else - { - tmpQueryCertifsList = yield duniterServer.dal.peerDAL.query( - 'SELECT `issuer`,`written_on`,`expires_on` FROM c_index WHERE `receiver`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); - } - - // Calculer le nombre de certifications reçus/émises par le membre courant - let nbWrittenCertifs = tmpQueryCertifsList.length; - - // Récupérer les uid des émetteurs/receveurs des certifications reçus/émises par l'utilisateur - // Et stocker les uid et dates d'expiration dans un tableau - membersCertifsList[m] = new Array(); - for (var i=0;i<nbWrittenCertifs;i++) - { - let tmpQueryGetUidProtagonistCert - if (mode == 'emitted') - { - tmpQueryGetUidProtagonistCert = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+tmpQueryCertifsList[i].receiver+'\' LIMIT 1'); - } - else - { - tmpQueryGetUidProtagonistCert = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+tmpQueryCertifsList[i].issuer+'\' LIMIT 1'); - } - let tmpBlockWrittenOn = tmpQueryCertifsList[i].written_on.split("-"); - - // Stoker la liste des certifications qui n'ont pas encore expirées - if (tmpQueryCertifsList[i].expires_on > currentBlockchainTimestamp) - { - if (i == 0) - { - membersFirstCertifExpire.push(tmpQueryCertifsList[0].expires_on); - } - membersCertifsList[m].push({ - issuer: (mode=='emitted') ? membersList[m].uid:tmpQueryGetUidProtagonistCert[0].uid, - receiver: (mode!='emitted') ? membersList[m].uid:tmpQueryGetUidProtagonistCert[0].uid, - writtenBloc: tmpBlockWrittenOn[0], - timestampExpire: tmpQueryCertifsList[i].expires_on - }); - } - } - - // SI LES CERTIFICATIONS EN PISCINE SONT DEMANDÉES - let nbValidPendingCertifs = 0; - - if (pendingSigs == "yes") - { - // récupérer toutes les certification en piscine - let tmpQueryPendingCertifsList = []; - if (mode == 'emitted') - { - tmpQueryPendingCertifsList = yield duniterServer.dal.peerDAL.query( - 'SELECT `from`,`to`,`block_number`,`expires_on` FROM certifications_pending WHERE `from`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); - } - else - { - tmpQueryPendingCertifsList = yield duniterServer.dal.peerDAL.query( - 'SELECT `from`,`block_number`,`expires_on` FROM certifications_pending WHERE `to`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); - } - - // Calculer le nombre de certifications en attentes destinées au membre courant - let nbPendingCertifs = tmpQueryPendingCertifsList.length; - - // Récupérer les uid des émetteurs des certifications reçus par l'utilisateur - // Et stocker les uid et dates d'expiration dans un tableau - membersPendingCertifsList[m] = new Array(); - for (var i=0;i<nbPendingCertifs;i++) - { - let tmpPub = (mode=='emitted') ? tmpQueryPendingCertifsList[i].to:tmpQueryPendingCertifsList[i].from; - let tmpQueryGetUidProtagonistPendingCert = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+tmpPub+'\' LIMIT 1'); - - // Vérifier que l'émetteur de la certification correspond à une identié connue - if ( tmpQueryGetUidProtagonistPendingCert.length > 0 ) - { - // récupérer le timestamp d'écriture de la dernière certification écrite par l'émetteur - let tmpQueryLastIssuerCert = yield duniterServer.dal.peerDAL.query('SELECT `chainable_on` FROM c_index WHERE `issuer`=\''+tmpQueryPendingCertifsList[i].from+'\' ORDER BY `expires_on` DESC LIMIT 1'); - - // Stoker la liste des certifications en piscine qui n'ont pas encore expirées - if (tmpQueryPendingCertifsList[i].expires_on > currentBlockchainTimestamp) - { - membersPendingCertifsList[m].push({ - protagonist: tmpQueryGetUidProtagonistPendingCert[0].uid, - blockNumber: tmpQueryPendingCertifsList[i].block_number, - timestampExpire: tmpQueryPendingCertifsList[i].expires_on, - timestampWritable: (typeof(tmpQueryLastIssuerCert[0]) == 'undefined') ? 0:tmpQueryLastIssuerCert[0].chainable_on - }); - nbValidPendingCertifs++; - } - } - } - } - - // Calculer le nombre maximal de certifications reçus par le membre courant - let nbCertifs = nbWrittenCertifs + nbValidPendingCertifs; - if ( nbCertifs > nbMaxCertifs) { nbMaxCertifs = nbCertifs; } - } + // Calculer le nombre de certifications reçus/émises par le membre courant + let nbWrittenCertifs = tmpQueryCertifsList.length; + + // Récupérer les uid des émetteurs/receveurs des certifications reçus/émises par l'utilisateur + // Et stocker les uid et dates d'expiration dans un tableau + membersCertifsList[m] = new Array(); + for (var i=0;i<nbWrittenCertifs;i++) + { + let tmpQueryGetUidProtagonistCert + if (mode == 'emitted') + { + tmpQueryGetUidProtagonistCert = yield duniterServer.dal.peerDAL.query('SELECT `uid`,`wotb_id` FROM i_index WHERE `pub`=\''+tmpQueryCertifsList[i].receiver+'\' LIMIT 1'); + } + else + { + tmpQueryGetUidProtagonistCert = yield duniterServer.dal.peerDAL.query('SELECT `uid`,`wotb_id` FROM i_index WHERE `pub`=\''+tmpQueryCertifsList[i].issuer+'\' LIMIT 1'); + } + let tmpBlockWrittenOn = tmpQueryCertifsList[i].written_on.split("-"); + + // Stoker la liste des certifications qui n'ont pas encore expirées + if (tmpQueryCertifsList[i].expires_on > currentBlockchainTimestamp) + { + if (i == 0) + { + membersFirstCertifExpire.push(tmpQueryCertifsList[0].expires_on); + } + membersCertifsList[m].push({ + protagonistWotId: tmpQueryGetUidProtagonistCert[0].wotb_id, + issuer: (mode=='emitted') ? membersList[m].uid:tmpQueryGetUidProtagonistCert[0].uid, + receiver: (mode!='emitted') ? membersList[m].uid:tmpQueryGetUidProtagonistCert[0].uid, + writtenBloc: tmpBlockWrittenOn[0], + timestampExpire: tmpQueryCertifsList[i].expires_on + }); + } + } + + // Récupérer toutes les certification en piscine + let nbValidPendingCertifs = 0; + let tmpQueryPendingCertifsList = []; + if (mode == 'emitted') + { + tmpQueryPendingCertifsList = yield duniterServer.dal.peerDAL.query( + 'SELECT `from`,`to`,`block_number`,`expires_on` FROM certifications_pending WHERE `from`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); + } + else + { + tmpQueryPendingCertifsList = yield duniterServer.dal.peerDAL.query( + 'SELECT `from`,`block_number`,`block_hash`,`expires_on` FROM certifications_pending WHERE `to`=\''+membersList[m].pub+'\' ORDER BY `expires_on` '+tmpOrder); + } + + // Récupérer les uid des émetteurs des certifications reçus par l'utilisateur + // Et stocker les uid et dates d'expiration dans un tableau + membersPendingCertifsList[m] = new Array(); + for (var i=0;i<tmpQueryPendingCertifsList.length;i++) + { + // Récupérer le medianTime et le hash du bloc d'émission de la certification + let emittedBlock = yield duniterServer.dal.peerDAL.query('SELECT `hash`,`medianTime` FROM block WHERE `number`=\''+tmpQueryPendingCertifsList[i].block_number+'\' AND `fork`=0 LIMIT 1'); + + let tmpPub = (mode=='emitted') ? tmpQueryPendingCertifsList[i].to:tmpQueryPendingCertifsList[i].from; + let tmpQueryGetUidProtagonistPendingCert = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+tmpPub+'\' LIMIT 1'); + + // Vérifier que l'émetteur de la certification correspond à une identié connue + if ( tmpQueryGetUidProtagonistPendingCert.length > 0 ) + { + // Vérifier la validité du blockStamp de la certification en piscine + let validBlockStamp = false; + if (typeof(emittedBlock[0]) != 'undefined' && emittedBlock[0].hash == tmpQueryPendingCertifsList[i].block_hash) + { validBlockStamp = true; } + + // Vérifier que le membre courant n'a pas déjà émis/reçu d'autre(s) certification(s) vis à vis du même protagoniste ET dans le même état de validité du blockstamp + let doubloonPendingCertif = false; + for (const pendingCert of membersPendingCertifsList[m]) + { + if (pendingCert.protagonist == tmpQueryGetUidProtagonistPendingCert[0].uid && pendingCert.validBlockStamp == validBlockStamp) + { + doubloonPendingCertif = true; + } + } + if (!doubloonPendingCertif) + { + // récupérer le timestamp d'écriture de la dernière certification écrite par l'émetteur + let tmpQueryLastIssuerCert = yield duniterServer.dal.peerDAL.query('SELECT `chainable_on` FROM c_index WHERE `issuer`=\''+tmpQueryPendingCertifsList[i].from+'\' ORDER BY `expires_on` DESC LIMIT 1'); + + // Stoker la liste des certifications en piscine qui n'ont pas encore expirées + if (tmpQueryPendingCertifsList[i].expires_on > currentBlockchainTimestamp) + { + membersPendingCertifsList[m].push({ + protagonist: tmpQueryGetUidProtagonistPendingCert[0].uid, + protagonistIsSentry: sentriesIndex[tmpQueryGetUidProtagonistPendingCert[0].uid], + blockNumber: tmpQueryPendingCertifsList[i].block_number, + timestampExpire: tmpQueryPendingCertifsList[i].expires_on, + timestampWritable: (typeof(tmpQueryLastIssuerCert[0]) == 'undefined') ? 0:tmpQueryLastIssuerCert[0].chainable_on, + validBlockStamp: validBlockStamp + }); + nbValidPendingCertifs++; + } + + } + } + } + + // Calculer le nombre maximal de certifications reçus par le membre courant + let nbCertifs = nbWrittenCertifs + nbValidPendingCertifs; + if ( nbCertifs > nbMaxCertifs) { nbMaxCertifs = nbCertifs; } + } // END of members loop + + // Convertir chaque blockNumber (de membership) en timestamp + for (const membershipBlockNumber of membershipsBlockNumberList) + { + membershipsTimeList.push(yield duniterServer.dal.peerDAL.query( + 'SELECT `medianTime` FROM block WHERE `number`=\''+membershipBlockNumber+'\' LIMIT 1') ); + } - // Convertir chaque blockNumber (de membership) en timestamp - for (const membershipBlockNumber of membershipsBlockNumberList) { - membershipsTimeList.push(yield duniterServer.dal.peerDAL.query( - 'SELECT `medianTime` FROM block WHERE `number`=\''+membershipBlockNumber+'\' LIMIT 1') ); - } - - // Traiter les cas ou expires_on est indéfini - for (let i=0;i<membershipsExpireTimeList.length;i++) { - if (membershipsExpireTimeList[i] == null) - { - membershipsExpireTimeList[i] = membershipsTimeList[i] + msValidity; - } - } + // Traiter les cas ou expires_on est indéfini + for (let i=0;i<membershipsExpireTimeList.length;i++) + { + if (membershipsExpireTimeList[i] == null) + { + membershipsExpireTimeList[i] = membershipsTimeList[i] + msValidity; + } + } + + // Calculer le degré de centralité de tout les membres (si demandé) + if (centrality=='yes') + { + let test = ''; + for (const member of membersList) + { + //if (sentriesIndex[member.uid]) + //{ + let tmpWot = wotbInstance.memCopy(); + for (const member2 of membersList) + { + if (member.wotb_id != member2.wotb_id) + { + let paths = tmpWot.getPaths(member.wotb_id, member2.wotb_id, conf.stepMax); + if (paths.length > 0) + { + let shortestPathLength = paths[paths.length-1].length; + meanShortestsPathLength += shortestPathLength; + nbShortestsPath++; + let indexMembersPresent = new Array(); + /*for (const path of paths) + { + if (path.length < shortestPathLength) { shortestPathLength = path.length; } + }*/ + for (const path of paths) + { + //if (path[0] == 0 && path.length == shortestPathLength) { test += "\n"+'0-->'; } + for (let i=0;i<path.length;i++) + { + if (path.length == shortestPathLength && i>0 && i<(path.length-1)) + { + //if (path[0] == 0) { test += path[i]+'-->'; } + membersCentrality[path[i]]++; + indexMembersPresent[path[i]] = path[i]; + } + } + //if (path[0] == 0 && path.length == shortestPathLength) { test += ''+path[path.length-1]; } + } + for (const indexMember of indexMembersPresent) + { + membersCentrality[indexMember]++; + } + } + } + } + tmpWot.clear(); + //} + } + } + } // END if (reinitCache) - // Initialiser le tableau membersListOrdered - var membersListOrdered = []; - var membersCertifsListSorted = []; - // Calculer le timestamp limite à prendre en compte - var limitTimestamp = currentBlockchainTimestamp + (days*86400); + let limitTimestamp = currentBlockchainTimestamp + (days*86400); // trier les membres par ordre croissant/decroissant du critère sort_by - var tabSort = []; if (sort_by == "idtyWritten") { for (const memberIdentity of membersIdentity) @@ -223,7 +452,28 @@ module.exports = (req, res, next) => co(function *() { { for (const memberCertifsList of membersFirstCertifExpire) { tabSort.push(memberCertifsList); } - } + } + else if (sort_by == "centrality") + { + for (const member of membersList) + { + if (membersCentrality[member.wotb_id] > 0) + { + tabSort.push(membersCentrality[member.wotb_id]); + } + else + { + tabSort.push(1); + } + } + } + else if (sort_by == "quality") + { + for (const member of membersList) + { + tabSort.push(membersQualityExt[member.uid]); + } + } else if (sort_by == "sigCount") { for (const memberCertifsList of membersCertifsList) @@ -231,11 +481,12 @@ module.exports = (req, res, next) => co(function *() { tabSort.push(memberCertifsList.length); } } - else { contenu += "<p>ERREUR : param <i>sort_by</i> invalid !</p>"; } + else { res.status(500).send(`<pre><p>ERREUR : param <i>sort_by</i> invalid !</p></pre>`) } // + for (var i=0;i<membersList.length;i++) { var maxTime = 0; - if (order == 'asc') { maxTime = currentBlockchainTimestamp + (msValidity*2); } + if (order == 'asc') { maxTime = currentBlockchainTimestamp + (conf.msValidity*2); } // maxTime = +infiny; var idMaxTime =0; for (var j=0;j<membersList.length;j++) { if ( (order == 'desc' && tabSort[j] > maxTime) @@ -251,6 +502,7 @@ module.exports = (req, res, next) => co(function *() { { // Push max value on sort table membersListOrdered.push({ + wotb_id: membersList[idMaxTime].wotb_id, uid: membersList[idMaxTime].uid, pub: membersList[idMaxTime].pub, idtyWrittenTimestamp: membersIdentity[idMaxTime].writtenTimestamp, @@ -260,20 +512,58 @@ module.exports = (req, res, next) => co(function *() { expireMembershipTimestamp: membershipsExpireTimeList[idMaxTime], certifications: membersCertifsList[idMaxTime], pendingCertifications: membersPendingCertifsList[idMaxTime], - isOutdistanced: membersIdentity[idMaxTime].isOutdistanced + detailedDistance: membersIdentity[idMaxTime].detailedDistance, + percentSentriesReached: parseFloat(((membersIdentity[idMaxTime].detailedDistance.nbSuccess/membersIdentity[idMaxTime].detailedDistance.nbSentries)*100).toFixed(2)), + isSentry: membersIdentity[idMaxTime].isSentry }); membersCertifsListSorted.push({ - issuer: membersCertifsList[idMaxTime].issuer, - receiver: membersCertifsList[idMaxTime].receiver, - writtenBloc: membersCertifsList[idMaxTime].writtenBloc, - timestampExpire: membersCertifsList[idMaxTime].timestampExpire + issuer: membersCertifsList[idMaxTime].issuer, + receiver: membersCertifsList[idMaxTime].receiver, + writtenBloc: membersCertifsList[idMaxTime].writtenBloc, + timestampExpire: membersCertifsList[idMaxTime].timestampExpire }); } // Exclure la valeur max avant de poursuivre le tri tabSort[idMaxTime] = -1; } + if (reinitCache) + { + if (constants.USE_WOTB6) + { + // Calculate mean Members/Sentries ReachedBy Members/Sentries InSingleExtCert + if (countSentries > 0) + { + meanSentriesReachedBySentriesInSingleExtCert = parseFloat((meanSentriesReachedBySentriesInSingleExtCert/countSentries).toFixed(2)); + meanMembersReachedBySentriesInSingleExtCert = parseFloat((meanMembersReachedBySentriesInSingleExtCert/countSentries).toFixed(2)); + } + if (membersList.length > 0) + { + meanSentriesReachedByMembersInSingleExtCert = parseFloat((meanSentriesReachedByMembersInSingleExtCert/membersList.length).toFixed(2)); + meanMembersReachedByMembersInSingleExtCert = parseFloat((meanMembersReachedByMembersInSingleExtCert/membersList.length).toFixed(2)); + } + + //Calculate proportionMembersWithQualityUpper1 and proportionMembersWithQualityUpper1IfNoSentries + proportionMembersWithQualityUpper1 /= membersList.length; + proportionMembersWithQualityUpper1IfNoSentries /= membersList.length; + } + + // recalculate meanCentrality and meanShortestsPathLength + if (centrality=='yes') + { + for (const memberCentrality of membersCentrality) + { + meanCentrality += memberCentrality; + } + meanCentrality /= membersCentrality.length; + meanShortestsPathLength /= nbShortestsPath; + } + + // Dévérouiller le cache members + lockMembers = false; + } + // Si le client demande la réponse au format JSON =, le faire if (format == 'JSON') { @@ -285,36 +575,55 @@ module.exports = (req, res, next) => co(function *() { { res.locals = { - host: req.headers.host.toString(), - + host: req.headers.host.toString(), + USE_WOTB6: constants.USE_WOTB6, + // get parameters days, mode, sort_by, order, - pendingSigs, - + pendingSigs, centrality, + + // page data currentBlockchainTimestamp, - limitTimestamp, - sigWindow, - idtyWindow, - msValidity, - sigValidity, - nbMaxCertifs, - - membersListOrdered, - membersListFiltered: membersListOrdered.filter( member=> - member.expireMembershipTimestamp < limitTimestamp - && member.expireMembershipTimestamp > currentBlockchainTimestamp - ), + limitTimestamp, nbMaxCertifs, + membersListFiltered: membersListOrdered.filter( member=> + member.expireMembershipTimestamp < limitTimestamp + && member.expireMembershipTimestamp > currentBlockchainTimestamp + ), + // currency parameters + xpercent: conf.xpercent, + sigWindow: conf.sigWindow, + idtyWindow: conf.idtyWindow, + msValidity: conf.msValidity, + sigValidity: conf.sigValidity, + stepMax: conf.stepMax, + + // members cache data + membersLastUptime, + membersQualityExt, + meanSentriesReachedBySentriesInSingleExtCert, + meanMembersReachedBySentriesInSingleExtCert, + meanSentriesReachedByMembersInSingleExtCert, + meanMembersReachedByMembersInSingleExtCert, + proportionMembersWithQualityUpper1, + proportionMembersWithQualityUpper1IfNoSentries, + + // centrality cache data + lockCentralityCalc, + membersLastCentralityCalcTime, + membersCentrality, + meanCentrality, + meanShortestsPathLength, + nbShortestsPath, // Template helpers - timestampToDatetime, - // Calculer la proportion de temps restant avant l'expiration - color: function( timestamp, idtyWindow, max ) - { - let proportion = ((timestamp-currentBlockchainTimestamp)*max)/idtyWindow; - proportion = proportion < 0 ? 0 : proportion > max ? max : proportion - let hex = parseInt( proportion ).toString(16) - return `#${hex}${hex}${hex}` - }, + // Calculer la proportion de temps restant avant l'expiration + color: function( timestamp, idtyWindow, max ) + { + let proportion = ((timestamp-currentBlockchainTimestamp)*max)/idtyWindow; + proportion = proportion < 0 ? 0 : proportion > max ? max : proportion + let hex = parseInt( proportion ).toString(16) + return `#${hex}${hex}${hex}` + }, /** * background: hsl( ${proportion(item.time,period,1,120)}, 100%, 50%, 1 ) * background: hsl( 0, 0%, ${proportion(item.time,period,0,200)}, 1 ) @@ -325,7 +634,7 @@ module.exports = (req, res, next) => co(function *() { let proportion = ( (timestamp-currentBlockchainTimestamp) * max ) / maxRange proportion = proportion < 0 ? 0 : proportion > max ? max : proportion return proportion - }, + } // color2: function( timestamp, maxRange, max ) // { // // Calculer la proportion de membership restant (en pour 255ème) diff --git a/routes/membersCount.js b/routes/membersCount.js index 1a01b70a5e2bad21ad4b88f98cbf3fb68ad7b67b..5f5f0c83ee0bec455244bfdf863dc434c79ced4d 100755 --- a/routes/membersCount.js +++ b/routes/membersCount.js @@ -13,15 +13,16 @@ module.exports = (req, res, next) => co(function *() { try { // get GET parameters var format = req.query.format || 'HTML'; + var pow = req.query.pow || 'no'; // get lg file - const LANG = getLang(`${__dirname}/../lg/membersCount_${req.query.lg||'fr'}.txt`); + const LANG = getLang(`${__dirname}/../lg/membersCount_${req.query.lg||'fr'}.txt`); // // get medianTime of beginBlock var beginBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`hash` FROM block WHERE `fork`=0 AND `number` = '+cache.beginBlock[0].number+' LIMIT 1'); // get blockchain - var blockchain = yield duniterServer.dal.peerDAL.query('SELECT `hash`,`membersCount`,`medianTime`,`number`,`certifications`,`issuersCount` FROM block WHERE `fork`=0 AND `medianTime` <= '+cache.endBlock[0].medianTime+' AND `medianTime` >= '+beginBlock[0].medianTime+' ORDER BY `medianTime` ASC'); + var blockchain = yield duniterServer.dal.peerDAL.query('SELECT `hash`,`membersCount`,`medianTime`,`number`,`certifications`,`issuersCount`,`powMin` FROM block WHERE `fork`=0 AND `medianTime` <= '+cache.endBlock[0].medianTime+' AND `medianTime` >= '+beginBlock[0].medianTime+' ORDER BY `medianTime` ASC'); // Get blockchain timestamp @@ -37,6 +38,7 @@ module.exports = (req, res, next) => co(function *() { // Initialize nextStepTime, stepIssuerCount and bStep var nextStepTime = blockchain[0].medianTime; let stepIssuerCount = 0; + let stepPowMin = 0; let bStep = 0; // Adapt nextStepTime initial value @@ -53,6 +55,7 @@ module.exports = (req, res, next) => co(function *() { for (let b=0;b<blockchain.length;b++) { stepIssuerCount += blockchain[b].issuersCount; + stepPowMin += blockchain[b].powMin; bStep++; while (cacheIndex < (cache.blockchain.length-1) && cache.blockchain[cacheIndex+1].number <= b) { cacheIndex++; } @@ -60,31 +63,69 @@ module.exports = (req, res, next) => co(function *() { // If achieve next step if ( (cache.stepUnit == "blocks" && bStep == cache.step) || (cache.stepUnit != "blocks" && blockchain[b].medianTime >= nextStepTime)) { - // push tabMembersCount - tabMembersCount.push({ - blockNumber: blockchain[b].number, - timestamp: blockchain[b].medianTime, - dateTime: timestampToDatetime(blockchain[b].medianTime, cache.onlyDate), - membersCount: blockchain[b].membersCount, - sentriesCount: cache.blockchain[cacheIndex].sentries, - issuersCount: parseInt(stepIssuerCount/bStep) - }); - - if (cache.stepUnit != "blocks") { nextStepTime += cache.stepTime; } - stepIssuerCount = 0; - bStep = 0; + let previousDateTime = ""; + if(tabMembersCount.length > 0) + { + previousDateTime = timestampToDatetime(tabMembersCount[tabMembersCount.length-1].timestamp, cache.onlyDate); + } + else + { + previousDateTime = timestampToDatetime(blockchain[0].medianTime); + } + let dateTime = ""; + if (cache.stepUnit != "blocks") + { + if (cache.step > 1) + { + switch (cache.stepUnit) + { + case "hours": dateTime = previousDateTime+" - "+timestampToDatetime(blockchain[b].medianTime, cache.onlyDate); break; + case "days": dateTime = previousDateTime+" - "+timestampToDatetime(blockchain[b].medianTime-(cache.stepTime/cache.step), cache.onlyDate); break; + case "weeks": dateTime = previousDateTime+" - "+timestampToDatetime(blockchain[b].medianTime, cache.onlyDate); break; + case "months": dateTime = previousDateTime+" - "+timestampToDatetime(blockchain[b].medianTime, cache.onlyDate); break; + case "years": dateTime = previousDateTime+" - "+timestampToDatetime(blockchain[b].medianTime, cache.onlyDate); break; + } + } + else + { + dateTime = previousDateTime; + } + } + + // push tabMembersCount + tabMembersCount.push({ + blockNumber: blockchain[b].number, + timestamp: blockchain[b].medianTime, + dateTime: dateTime, + membersCount: blockchain[b].membersCount, + sentriesCount: cache.blockchain[cacheIndex].sentries, + issuersCount: parseInt(stepIssuerCount/bStep), + powMin: parseInt(stepPowMin/bStep) + }); + + if (cache.stepUnit != "blocks") { nextStepTime += cache.stepTime; } + stepIssuerCount = 0; + stepPowMin = 0; + bStep = 0; } } - // Add current block data - tabMembersCount.push({ - blockNumber: blockchain[blockchain.length-1].number, - timestamp: blockchain[blockchain.length-1].medianTime, - dateTime: LANG['LAST_BLOCK'], - membersCount: blockchain[blockchain.length-1].membersCount, - sentriesCount: cache.blockchain[cache.blockchain.length-1].sentries, - issuersCount: blockchain[blockchain.length-1].issuersCount - }); + // Add current block data (only if end parameter is undefined or negative) + if (typeof(req.query.end) == 'undefined' || req.query.end <= 0) + { + tabMembersCount.push({ + blockNumber: blockchain[blockchain.length-1].number, + timestamp: blockchain[blockchain.length-1].medianTime, + dateTime: LANG['LAST_BLOCK'], + membersCount: blockchain[blockchain.length-1].membersCount, + sentriesCount: cache.blockchain[cache.blockchain.length-1].sentries, + issuersCount: blockchain[blockchain.length-1].issuersCount, + powMin: blockchain[blockchain.length-1].powMin + }); + } + + // Delete first tabMembersCount cell + tabMembersCount.splice(0, 1); if (format == 'JSON') res.status(200).jsonp( tabMembersCount ) @@ -93,53 +134,66 @@ module.exports = (req, res, next) => co(function *() { // GET parameters var unit = req.query.unit == 'relative' ? 'relative' : 'quantitative'; var massByMembers = req.query.massByMembers == 'no' ? 'no' : 'yes'; - - //console.log("req.headers.host = %s", req.headers.host); + + // Define datasets + let datasets = [{ + label: `${LANG["MEMBERS_COUNT"]}`, + data: tabMembersCount.map(item=>item.membersCount), + fill: false, + backgroundColor: 'rgba(0, 0, 255, 0.5)', + borderColor: 'rgba(0, 0, 255, 1)', + borderWidth: 1 + }, + { + label: `${LANG["SENTRIES_COUNT"]}`, + data: tabMembersCount.map(item=>item.sentriesCount), + fill: false, + backgroundColor: 'rgba(0, 255, 0, 0.5)', + borderColor: 'rgba(0, 255, 0, 1)', + borderWidth: 1 + }, + { + label: `${LANG["ISSUERS_COUNT"]}`, + data: tabMembersCount.map(item=>item.issuersCount), + fill: false, + backgroundColor: 'rgba(255, 0, 0, 0.5)', + borderColor: 'rgba(255, 0, 0, 1)', + borderWidth: 1 + }]; + + if (pow == 'yes') + { + datasets.push({ + label: `${LANG["POW_MIN"]}`, + data: tabMembersCount.map(item=>item.powMin), + fill: false, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + borderColor: 'rgba(0, 0, 0, 1)', + borderWidth: 1 + }); + } res.locals = { host: req.headers.host.toString(), tabMembersCount, begin: cache.beginBlock[0].number, end: cache.endBlock[0].number, - form: `${LANG["BEGIN"]} #<input type="number" name="begin" value="${cache.beginBlock[0].number}" min="0"> - ${LANG["END"]} #<input type="number" name="end" value="${cache.endBlock[0].number}" min="1"> - ${LANG["STEP"]} <input type="number" name="step" value="${cache.step}" min="1"> - <select name="stepUnit"> - <option name="stepUnit" value ="blocks"${cache.stepUnit == 'blocks' ? 'selected' : ''}>${LANG["BLOCKS"]} - <option name="stepUnit" value ="hours"${cache.stepUnit == 'hours' ? 'selected' : ''}>${LANG["HOURS"]} - <option name="stepUnit" value ="days" ${cache.stepUnit == 'days' ? 'selected' : ''}>${LANG["DAYS"]} - <option name="stepUnit" value ="weeks" ${cache.stepUnit == 'weeks' ? 'selected' : ''}>${LANG["WEEKS"]} - <option name="stepUnit" value ="months" ${cache.stepUnit == 'months' ? 'selected' : ''}>${LANG["MONTHS"]} - <option name="stepUnit" value ="years" ${cache.stepUnit == 'years' ? 'selected' : ''}>${LANG["YEARS"]} - </select>`, - description: `${LANG["DESCRIPTION1"]+'<br>'+LANG["DESCRIPTION2"]+'<b>'+cache.Yn+'</b>.'}`, + form: `${LANG["BEGIN"]} #<input type="number" name="begin" value="${cache.beginBlock[0].number}" min="0"> - ${LANG["END"]} #<input type="number" name="end" value="${cache.endBlock[0].number}" > - ${LANG["STEP"]} <input type="number" name="step" value="${cache.step}" min="1"> + <select name="stepUnit"> + <option name="stepUnit" value ="blocks"${cache.stepUnit == 'blocks' ? 'selected' : ''}>${LANG["BLOCKS"]} + <option name="stepUnit" value ="hours"${cache.stepUnit == 'hours' ? 'selected' : ''}>${LANG["HOURS"]} + <option name="stepUnit" value ="days" ${cache.stepUnit == 'days' ? 'selected' : ''}>${LANG["DAYS"]} + <option name="stepUnit" value ="weeks" ${cache.stepUnit == 'weeks' ? 'selected' : ''}>${LANG["WEEKS"]} + <option name="stepUnit" value ="months" ${cache.stepUnit == 'months' ? 'selected' : ''}>${LANG["MONTHS"]} + <option name="stepUnit" value ="years" ${cache.stepUnit == 'years' ? 'selected' : ''}>${LANG["YEARS"]} + </select>`, + description: `${LANG["DESCRIPTION1"]+'<br>'+LANG["DESCRIPTION2"]+'<b>'+cache.Yn+'</b>.'}`, + form2: `<input type="checkbox" name="pow" value="yes" ${pow == 'yes' ? 'checked' : ''}> ${LANG["SHOW_POW_MIN"]}`, chart: { type: 'line', data: { - labels: tabMembersCount.map(item=>item.dateTime), - datasets: [{ - label: `${LANG["MEMBERS_COUNT"]}`, - data: tabMembersCount.map(item=>item.membersCount), - fill: false, - backgroundColor: 'rgba(0, 0, 255, 0.5)', - borderColor: 'rgba(0, 0, 255, 1)', - borderWidth: 1 - }, - { - label: `${LANG["SENTRIES_COUNT"]}`, - data: tabMembersCount.map(item=>item.sentriesCount), - fill: false, - backgroundColor: 'rgba(0, 255, 0, 0.5)', - borderColor: 'rgba(0, 255, 0, 1)', - borderWidth: 1 - }, - { - label: `${LANG["ISSUERS_COUNT"]}`, - data: tabMembersCount.map(item=>item.issuersCount), - fill: false, - backgroundColor: 'rgba(255, 0, 0, 0.5)', - borderColor: 'rgba(255, 0, 0, 1)', - borderWidth: 1 - } - ] + labels: (cache.stepUnit == "blocks") ? tabMembersCount.map(item=>item.blockNumber):tabMembersCount.map(item=>item.dateTime), + datasets: datasets, }, options: { // plugins: { diff --git a/routes/pubkeyBalance.js b/routes/pubkeyBalance.js deleted file mode 100755 index f8717bcc5b5966bb5299471e6b90a7638f2be827..0000000000000000000000000000000000000000 --- a/routes/pubkeyBalance.js +++ /dev/null @@ -1,495 +0,0 @@ -"use strict"; - -const co = require('co') -const timestampToDatetime = require(__dirname + '/../lib/timestampToDatetime') -const colorScale = require(__dirname + '/../lib/colorScale') -const getLang = require(__dirname + '/../lib/getLang') - -const STEP_COUNT_LIMIT=150; - -module.exports = (req, res, next) => co(function *() { - - var { duniterServer, sigValidity, msValidity, sigWindow, idtyWindow, cache } = req.app.locals - - try { - // get GET parameters - var format = req.query.format || 'HTML'; - var pubkey1 = req.query.pubkey1 || ''; - var mode = req.query.mode == 'balanceWithOthers' ? 'balanceWithOthers' : 'selfBalance'; - var unit = req.query.unit == 'relative' ? 'relative' : 'quantitative'; - - // get medianTime of beginBlock - var beginBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`hash` FROM block WHERE `fork`=0 AND `number` = '+cache.beginBlock[0].number+' LIMIT 1'); - - // Traiter le cas stepUnit == "blocks" - if (cache.stepUnit == "blocks") { cache.stepUnit == "hours" } - - if (pubkey1.length > 0) - { - - // If pubkey1 is uid, change to corresponding pubkey - var pubkeyUid1 = yield duniterServer.dal.peerDAL.query('SELECT `pub` FROM i_index WHERE `uid`=\''+pubkey1+'\' LIMIT 1'); - if ( pubkeyUid1.length > 0 ) { pubkey1 = pubkeyUid1[0].pub; } - - // If pubkey1 wasMember, get all dividend created by pubkey1 - var pubkey1WasMember = false; - var idtyPubkey1 = yield duniterServer.dal.peerDAL.query('SELECT `written_on`,`uid` FROM i_index WHERE `pub`=\''+pubkey1+'\' AND `wasMember`=1 LIMIT 1'); - if (idtyPubkey1.length > 0) - { - pubkey1WasMember = true; - - // get joinersTimePubkey1 - let joinersBlockPubkey1 = idtyPubkey1[0].written_on.split("-"); - let joinersTimePubkey1 = yield duniterServer.dal.peerDAL.query('SELECT `medianTime` FROM block WHERE `fork`=0 AND `number`=\''+joinersBlockPubkey1[0]+'\' LIMIT 1'); - - - // get all dividend created by pubkey1 - var pubkey1Dividends = yield duniterServer.dal.peerDAL.query('SELECT `dividend`,`medianTime`,`number` FROM block WHERE `fork`=0 AND `dividend` > 0 ' - +'AND `medianTime` >\''+joinersTimePubkey1[0].medianTime+'\' AND `medianTime` <=\''+cache.endBlock[0].medianTime+'\' ' - +'ORDER BY `medianTime` ASC'); - } - - // Get cache infos for pubkey1 - var pubkey1Cache = null; - if (typeof(cache.pubkeys[cache.pub_index[pubkey1]]) != 'undefined') - { pubkey1Cache = cache.pubkeys[cache.pub_index[pubkey1]]; } - - // get currentDividend - var currentDividend = yield duniterServer.dal.peerDAL.query('SELECT `dividend` FROM block WHERE `fork`=0 AND `dividend` > 0 ORDER BY `medianTime` DESC LIMIT 1'); - - if (pubkey1Cache != null) - { - // If mode is "balanceWithOthers", initialize, full and sort tabBalanceWithOthers - if (mode == "balanceWithOthers") - { - // initialize - var tabIndexOthers = new Array(); - var tabBalanceWithOthers = new Array(); - let pubkeyId = cache.pub_index[pubkey1]; - - // full - for (let i=0;i<cache.pubkeys.length;i++) - { - if (cache.pubkeys[i].pub == pubkey1) - { - for (let key in cache.pubkeys[i].pubkeyBalanceWithOthers) - { - let idKey = cache.pubkeys[i].pubkeyBalanceWithOthers[key]; - let idBalance = -1; - for (let j=0;j<tabIndexOthers.length;j++) - { - if (tabIndexOthers[j] == cache.pubkeys[i].pub) { idBalance = j; } - } - if (idBalance < 0) - { - let idtyKey = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+key+'\' AND `wasMember`=1 LIMIT 1'); - if (idtyKey.length > 0) { tabIndexOthers.push(idtyKey[0].uid); } - else { tabIndexOthers.push(key.toString().substr(0, 12)); } - tabBalanceWithOthers.push(0); - idBalance = tabBalanceWithOthers.length-1; - } - if (unit == 'quantitative') { tabBalanceWithOthers[idBalance] += (parseFloat((cache.pubkeys[i].balanceWithOthers[idKey]/100).toFixed(2))); } - else if (unit == 'relative') { tabBalanceWithOthers[idBalance] += (parseFloat((cache.pubkeys[i].balanceWithOthers[idKey]/currentDividend[0].dividend).toFixed(2))); } - } - } - else - { - for (let key in cache.pubkeys[i].pubkeyBalanceWithOthers) - { - let idPubkey1 = cache.pubkeys[i].pubkeyBalanceWithOthers[pubkey1]; - if (key == pubkey1) - { - let idBalance = -1; - for (let j=0;j<tabIndexOthers.length;j++) - { - if (tabIndexOthers[j] == cache.pubkeys[i].pub) { idBalance = j; } - } - if (idBalance < 0) - { - let idtyKey = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+cache.pubkeys[i].pub+'\' AND `wasMember`=1 LIMIT 1'); - if (idtyKey.length > 0) { tabIndexOthers.push(idtyKey[0].uid); } - else { tabIndexOthers.push(cache.pubkeys[i].pub.toString().substr(0, 12)); } - tabBalanceWithOthers.push(0); - idBalance = tabBalanceWithOthers.length-1; - } - if (unit == 'quantitative') { tabBalanceWithOthers[idBalance] += (parseFloat((-cache.pubkeys[i].balanceWithOthers[idPubkey1]/100).toFixed(2))); } - else if (unit == 'relative') { tabBalanceWithOthers[idBalance] += (parseFloat((-cache.pubkeys[i].balanceWithOthers[idPubkey1]/currentDividend[0].dividend).toFixed(2))); } - } - } - } - } - - // sort - var tabBalanceWithOthersOrdered = new Array(); - var tabIndexOthersOrdered = new Array(); - - while (tabBalanceWithOthers.length > 0) - { - let idMin = 0; - let min = tabBalanceWithOthers[0]; - for (let i=0;i<tabBalanceWithOthers.length;i++) - { - if (parseFloat(tabBalanceWithOthers[i]) <= min) - { - min = parseFloat(tabBalanceWithOthers[i]); - idMin = i; - } - } - - // push tabBalanceWithOthersOrdered and tabIndexOthersOrdered (if value is not null) - if (parseFloat(tabBalanceWithOthers[idMin]) != 0.0) - { - tabBalanceWithOthersOrdered.push(tabBalanceWithOthers[idMin]); - tabIndexOthersOrdered.push(tabIndexOthers[idMin]); - } - - // splice tabBalanceWithOthers and tabIndexOthers - tabBalanceWithOthers.splice(idMin, 1); - tabIndexOthers.splice(idMin, 1); - } - } - - // Initialize tabDividend and pubkey1Dividend - if (pubkey1WasMember) { var tabDividend = []; var pubkey1Dividend = 0; } - - // Initialize tabTimes, tabBalance, tabInputsBalance, tabOutputsBalance, tabTxBalance, tabDividend, tabMeanCurrencyMass, nextStepTime and idDividend - var tabTimes = new Array(); - var tabBalance = new Array(); - var tabInputsBalance = new Array(); - var tabOutputsBalance = new Array(); - if (pubkey1WasMember) { var tabTxBalance = new Array(); var tabDividend= new Array(); } - var tabMeanCurrencyMass = []; - var nextStepTime = cache.blockchain[1].medianTime; // begin time at first dividend block (#1) - var idDividend = 0; - - // Initialize idInputs and idOutputs - var idInputs = 0; - var idOutputs = 0; - - // stepTime loop - while (nextStepTime < (parseInt(cache.endBlock[0].medianTime)+cache.stepTime)) - { - // Initialize sumStepDividends, sumStepInputs, sumStepOutputs - let sumStepDividends = 0; - let sumStepInputs = 0; - let sumStepOutputs = 0; - - // Calculate sum dividends of current step - if (pubkey1WasMember) - { - while (idDividend < pubkey1Dividends.length && pubkey1Dividends[idDividend].medianTime <= nextStepTime) - { - sumStepDividends += parseInt(pubkey1Dividends[idDividend].dividend); - idDividend++; - } - } - - // Calculate sum inputs of current step - if ( typeof(pubkey1Cache.inputsTime) != 'undefined' && pubkey1Cache.inputsTime.length > 0 ) - while ( parseInt(pubkey1Cache.inputsTime[idInputs]) <= parseInt(nextStepTime) ) - { - sumStepInputs += pubkey1Cache.inputsAmount[idInputs]; - idInputs++; - } - - // Calculate sum outputs of current step - while ( parseInt(pubkey1Cache.outputsTime[idOutputs]) <= parseInt(nextStepTime) ) - { - sumStepOutputs += pubkey1Cache.outputsAmount[idOutputs]; - idOutputs++; - } - - // If achieve beginTime, push tabTimes, tabBalance, tabInputsBalance, tabOutputsBalance, tabTxBalance, tabDividend, tabMeanCurrencyMass - if (nextStepTime >= parseInt(beginBlock[0].medianTime)) - { - // Get lastDividendBlock - if (nextStepTime > (parseInt(cache.endBlock[0].medianTime))) { tabTimes.push(timestampToDatetime(cache.endBlock[0].medianTime, cache.onlyDate)); } - else { tabTimes.push(timestampToDatetime(nextStepTime, cache.onlyDate)); } - let lastDividendBlock = yield duniterServer.dal.peerDAL.query( - 'SELECT `membersCount`,`monetaryMass`,`dividend` FROM block WHERE `fork`=0 AND `dividend` > 0 AND `medianTime` <= \''+nextStepTime+'\' ORDER BY `medianTime` DESC LIMIT 1'); - - let previousBalance, previousTabTxBalance, previousTabDividend; - if (tabBalance.length > 0) - { - previousBalance = tabBalance[tabBalance.length-1]; - if (pubkey1WasMember) - { - previousTabTxBalance = tabTxBalance[tabTxBalance.length-1]; - previousTabDividend = tabDividend[tabDividend.length-1]; - } - } - else - { - previousBalance = 0; - if (pubkey1WasMember) - { - previousTabTxBalance = 0; - previousTabDividend = 0; - } - } - - tabBalance.push(parseFloat((previousBalance+parseFloat(((sumStepDividends+sumStepOutputs-sumStepInputs)/100).toFixed(2))).toFixed(2))); - tabInputsBalance.push(-parseFloat((sumStepInputs/100).toFixed(2))); - tabOutputsBalance.push(parseFloat((sumStepOutputs/100).toFixed(2))); - tabMeanCurrencyMass.push(parseFloat((lastDividendBlock[0].monetaryMass/(lastDividendBlock[0].membersCount*100)).toFixed(2))); - if (pubkey1WasMember) - { - tabTxBalance.push(parseFloat((previousTabTxBalance+parseFloat(((sumStepOutputs-sumStepInputs)/100).toFixed(2))).toFixed(2))); - tabDividend.push(parseFloat((previousTabDividend+parseFloat((sumStepDividends/100).toFixed(2))).toFixed(2))); - } - } - // If no startChart, add step sums to tabs for calculate begin balances - else - { - // If is the first step - if (tabBalance.length == 0 ) - { - tabBalance.push(0); - tabInputsBalance.push(0); - tabOutputsBalance.push(0); - if (pubkey1WasMember) - { - tabTxBalance.push(0); - tabDividend.push(0); - } - } - - tabBalance[0] += parseFloat(((parseInt(sumStepDividends)+sumStepOutputs-sumStepInputs)/100).toFixed(2)); - /*tabInputsBalance[0] -= parseFloat((sumStepInputs/100).toFixed(2)); - tabOutputsBalance[0] += parseFloat((sumStepOutputs/100).toFixed(2));*/ - if (pubkey1WasMember) - { - tabTxBalance[0] += parseFloat(((sumStepOutputs-sumStepInputs)/100).toFixed(2)); - tabDividend[0] += parseFloat((parseInt(sumStepDividends)/100).toFixed(2)); - } - } - - // Increment nextStepTime - nextStepTime += cache.stepTime; - } - - // Apply Relative - if (unit == 'relative') - { - for(let i=0;i<tabTimes.length;i++) - { - // Get lastDividendBlock - let lastDividendBlock = yield duniterServer.dal.peerDAL.query( - 'SELECT `dividend` FROM block WHERE `fork`=0 AND `dividend` > 0 AND `medianTime` <= \''+tabTimes[i]+'\' ORDER BY `medianTime` DESC LIMIT 1'); - - tabBalance[i] = parseFloat((100 * tabBalance[i] / lastDividendBlock[0].dividend).toFixed(2)); - tabInputsBalance[i] = parseFloat((100 * tabInputsBalance[i] / lastDividendBlock[0].dividend).toFixed(2)); - tabOutputsBalance[i] = parseFloat((100 * tabOutputsBalance[i] / lastDividendBlock[0].dividend).toFixed(2)); - tabMeanCurrencyMass[i] = parseFloat((100 * tabMeanCurrencyMass[i] / lastDividendBlock[0].dividend).toFixed(2)); - if (pubkey1WasMember) - { - tabTxBalance[i] = parseFloat((100 * tabTxBalance[i] / lastDividendBlock[0].dividend).toFixed(2)); - tabDividend[i] = parseFloat((100 * tabDividend[i] / lastDividendBlock[0].dividend).toFixed(2)); - } - } - } - } - } - if (format == 'JSON') - { - var tabJson = new Array(); - if (pubkey1Cache != null) - { - if (mode == 'selfBalance') - { - for (let i=0;i<tabBalance.length;i++) - { - if (pubkey1WasMember) - { - tabJson.push({ - time: tabTimes[i], - balance: tabBalance[i], - inputsSum: tabInputsBalance[i], - outputsSum: tabOutputsBalance[i], - meanCurrencyMass: tabMeanCurrencyMass[i], - txBalance: tabTxBalance[i], - dividends: tabDividend[i] - }); - } - else - { - tabJson.push({ - time: tabTimes[i], - balance: tabBalance[i], - inputsSum: tabInputsBalance[i], - outputsSum: tabOutputsBalance[i], - meanCurrencyMass: tabMeanCurrencyMass[i] - }); - } - } - } - else if (mode == 'balanceWithOthers') - { - for(let i=0;i<tabIndexOthersOrdered.length;i++) - { - tabJson.push({ - pubkey: tabIndexOthersOrdered[i], - balance: tabBalanceWithOthersOrdered[i] - }); - } - } - } - else { tabJson.push(0); } - res.status(200).jsonp( tabJson ) - } - else - { - const LANG = getLang(`./lg/pubkeyBalance_${req.query.lg||'fr'}.txt`); - var datasets = new Array(); - if (pubkey1.length > 0 && pubkey1Cache != null) - { - if (mode == "selfBalance") - { - datasets.push({ - label: LANG["LEGEND_TOTAL_BALANCE"]+' ('+((unit == "relative") ? LANG["UNIT_R"]:LANG["UNIT_Q"])+')', - data: tabBalance, - fill: false, - backgroundColor: 'rgba(0, 162, 245, 0.5)', - borderColor: 'rgba(0, 162, 245, 1)', - borderWidth: 1, - hoverBackgroundColor: 'rgba(0, 162, 245, 0.2)', - hoverborderColor: 'rgba(0, 162, 245, 0.2)' - }); - datasets.push({ - label: `${unit == "relative" ? "(M/N) DUğ1" : '(M/N) ğ1'}`, - data: tabMeanCurrencyMass, - fill: false, - backgroundColor: 'rgba(255, 128, 0, 0.5)', - borderColor: 'rgba(255, 128, 0, 1)', - borderWidth: 1, - hoverBackgroundColor: 'rgba(255, 128, 0, 0.2)', - hoverborderColor: 'rgba(255, 128, 0, 0.2)' - }); - datasets.push({ - label: LANG["LEGEND_INPUTS_MOVES"]+' ('+((unit == "relative") ? LANG["UNIT_R"]:LANG["UNIT_Q"])+')', - data: tabInputsBalance, - fill: false, - lineTension: 0, - //steppedLine: true, - //showLine: false, - pointStyle: 'line', - pointStyle: 'line', - pointHoverRadius: 5, - pointHoverBackgroundColor: "rgba(255, 0, 0,1)", - pointHoverBorderColor: "rgba(255, 0, 0,1)", - pointHoverBorderWidth: 4, - backgroundColor: 'rgba(255, 0, 0, 0.5)', - borderColor: 'rgba(255, 0, 0, 1)', - borderWidth: 2 - }); - datasets.push({ - label: LANG["LEGEND_OUTPUTS_MOVES"]+' ('+((unit == "relative") ? LANG["UNIT_R"]:LANG["UNIT_Q"])+')', - data: tabOutputsBalance, - fill: false, - lineTension: 0, - //steppedLine: true, - //howLine: false, - pointStyle: 'line', - pointHoverRadius: 5, - pointHoverBackgroundColor: "rgba(0, 255, 0,1)", - pointHoverBorderColor: "rgba(0, 255, 0,1)", - pointHoverBorderWidth: 4, - backgroundColor: 'rgba(0, 255, 0, 0.5)', - borderColor: 'rgba(0, 255, 0, 1)', - borderWidth: 2 - }); - if (pubkey1WasMember) - { - datasets.push({ - label: LANG["LEGEND_DU_LINE"]+' ('+((unit == "relative") ? LANG["UNIT_R"]:LANG["UNIT_Q"])+')', - data: tabDividend, - fill: false, - pointStyle: 'triangle', - backgroundColor: 'rgba(0, 255, 0, 0.5)', - borderColor: 'rgba(0, 255, 0, 1)', - borderWidth: 1, - hoverBackgroundColor: 'rgba(0, 255, 0, 0.2)', - hoverborderColor: 'rgba(0, 255, 0, 0.2)' - }); - datasets.push({ - label: LANG["LEGEND_TX_BALANCE"]+' ('+((unit == "relative") ? LANG["UNIT_R"]:LANG["UNIT_Q"])+')', - data: tabTxBalance, - fill: false, - backgroundColor: 'rgba(0, 0, 0, 0.5)', - borderColor: 'rgba(0, 0, 0, 1)', - borderWidth: 1, - hoverBackgroundColor: 'rgba(0, 0, 0, 0.2)', - hoverborderColor: 'rgba(0, 0, 0, 0.2)' - }); - } - } - else if (mode == 'balanceWithOthers') - { - datasets.push({ - label: `${unit == "relative" ? "DUğ1" : 'ğ1'}`, - data: tabBalanceWithOthersOrdered, - backgroundColor: colorScale(tabBalanceWithOthersOrdered.length, 0.5), - borderColor: colorScale(tabBalanceWithOthersOrdered.length, 1), - borderWidth: 1, - hoverBackgroundColor: colorScale(tabBalanceWithOthersOrdered.length, 0.2), - hoverborderColor: colorScale(tabBalanceWithOthersOrdered.length, 0.2) - }); - } - } - res.locals = { - host: req.headers.host.toString(), - begin: cache.beginBlock[0].number, - end: cache.endBlock[0].number, - pubkey1, - description: `${LANG["DESCRIPTION"]}`, - chart: { - type: (mode == "selfBalance") ? 'line':'bar', - data: { - labels: (mode == "selfBalance") ? tabTimes:tabIndexOthersOrdered, - datasets: datasets - }, - options: { - title: { - display: (mode == "selfBalance") ? true:false, - text: (pubkey1.length > 0) ? LANG["CHART_TITLE1"]+' '+( (pubkey1WasMember) ? idtyPubkey1[0].uid:pubkey1 )+' '+LANG["CHART_TITLE2"]+' #'+cache.beginBlock[0].number+'-#'+cache.endBlock[0].number:'' - }, - legend: { - display: (mode == "selfBalance") ? true:false, - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: true, - } - }] - } - } - }, - form: `${LANG["BEGIN"]} #<input type="number" name="begin" value="${cache.beginBlock[0].number}" size="7" style="width:60px;" min="0" ${(mode == "balanceWithOthers") ? 'disabled':''}> - - ${LANG["END"]} #<input type="number" name="end" value="${cache.endBlock[0].number}" size="7" style="width:60px;" min="1" ${(mode == "balanceWithOthers") ? 'disabled':''}> - - ${LANG["PUBKEY"]} : <input type="text" name="pubkey1" value="${pubkey1}" size="44"> - <select name="mode" disabled> - <option name="mode" value ="selfBalance">${LANG["SELECT_MODE1"]} - <option name="mode" value ="balanceWithOthers" ${mode == 'balanceWithOthers' ? 'selected' : ''}>${LANG["SELECT_MODE2"]} - </select> - <select name="unit"> - <option name="unit" value ="quantitative">${LANG["UNIT_Q"]} - <option name="unit" value ="relative" ${unit == 'relative' ? 'selected' : ''}>${LANG["UNIT_R"]} - </select> - - step <input type="number" name="step" value="${cache.step}" size="3" style="width:50px;" min="1" ${(mode == "balanceWithOthers") ? 'disabled':''}> - <select name="stepUnit" ${(mode == "balanceWithOthers") ? 'disabled':''}> - <option name="stepUnit" value ="hours"${cache.stepUnit == 'hours' ? 'selected' : ''}>${LANG["HOURS"]} - <option name="stepUnit" value ="days" ${cache.stepUnit == 'days' ? 'selected' : ''}>${LANG["DAYS"]} - <option name="stepUnit" value ="weeks" ${cache.stepUnit == 'weeks' ? 'selected' : ''}>${LANG["WEEKS"]} - <option name="stepUnit" value ="months" ${cache.stepUnit == 'months' ? 'selected' : ''}>${LANG["MONTHS"]} - <option name="stepUnit" value ="years" ${cache.stepUnit == 'years' ? 'selected' : ''}>${LANG["YEARS"]} - </select>` - } - next() - } - } catch (e) { - // En cas d'exception, afficher le message - res.status(500).send(`<pre>${e.stack || e.message}</pre>`); - } -}) - diff --git a/routes/willMembers.js b/routes/willMembers.js index be963cdcfa1ca4d5cab6163fa44437512e13e1fc..42857c873e4ac5ae14cfbf8e32d0eda0c8f2d218 100755 --- a/routes/willMembers.js +++ b/routes/willMembers.js @@ -2,62 +2,93 @@ const co = require('co') const crypto = require('crypto') -//const wotb = require('wotb') -const timestampToDatetime = require(__dirname + '/../lib/timestampToDatetime') +const constants = require(__dirname + '/../lib/constants') + +const wotb = (constants.USE_WOTB6) ? require('wotb'):null; -const MIN_WILLMEMBERS_UPDATE_FREQ = 150; +const timestampToDatetime = require(__dirname + '/../lib/timestampToDatetime') // Préserver les résultats en cache -var idtysListOrdered = []; +var lockWillMembers = false; +var willMembersLastUptime = 0; +var identitiesList = []; +var idtysPendingCertifsList = []; var nbMaxCertifs = 0; var countMembersWithSigQtyValidCert = 0; -var lastUpgradeTime = 0; +var sentries = []; +var sentriesIndex = []; +var wotbIdIndex = []; +var meanSentriesReachedByIdtyPerCert = []; +var meanMembersReachedByIdtyPerCert = []; +var countIdtiesPerReceiveCert = []; +var membersQualityExt = []; module.exports = (req, res, next) => co(function *() { - var { duniterServer, sigValidity, msValidity, sigWindow, idtyWindow, sigQty, stepMax, cache } = req.app.locals + var { duniterServer } = req.app.locals try { // get blockchain timestamp let resultQueryCurrentBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`number`,`hash`,`membersCount` FROM block ORDER BY `medianTime` DESC LIMIT 1 '); const currentBlockchainTimestamp = resultQueryCurrentBlock[0].medianTime; + const currentMembersCount = resultQueryCurrentBlock[0].membersCount; const currentBlockNumber = resultQueryCurrentBlock[0].number; const currentBlockHash = resultQueryCurrentBlock[0].hash; - // Dictionnaire pubkey => wid (wotb ID) - const widsCache = {} - // Initaliser les constantes const conf = duniterServer.conf; - const dSen = Math.ceil(Math.pow(resultQueryCurrentBlock[0].membersCount, 1 / conf.stepMax)); + const dSen = Math.ceil(Math.pow(currentMembersCount, 1 / conf.stepMax)); // Initaliser les variables - var errors = ""; - var identitiesList = []; - var idtysPendingCertifsList = []; + let errors = ""; + let idtysListOrdered = []; + let sumPercentSentriesReached = 0; + let sumPercentMembersReached = 0; // Récupérer les paramètres - var days = req.query.d || 65 // Valeur par défaut - var order = req.query.d && req.query.order || 'desc' // Valeur par défaut - var sort_by = req.query.sort_by || "registrationPackage"; // Valeur par défaut - var hideIdtyWithZeroCert = req.query.hideIdtyWithZeroCert || "no"; // Valeur par défaut - var sortSig = req.query.sortSig || "Availability"; // Valeur par défaut - var format = req.query.format || 'HTML'; + let days = req.query.d || 65 // Valeur par défaut + let order = req.query.d && req.query.order || 'desc' // Valeur par défaut + let sort_by = req.query.sort_by || "registrationPackage"; // Valeur par défaut + let showIdtyWithZeroCert = req.query.showIdtyWithZeroCert || "no"; // Valeur par défaut + let sortSig = req.query.sortSig || "Availability"; // Valeur par défaut + let format = req.query.format || 'HTML'; // Calculer le timestamp limite à prendre en compte - var limitTimestamp = currentBlockchainTimestamp + (days*86400); + let limitTimestamp = currentBlockchainTimestamp + (days*86400); - if (Math.floor(Date.now() / 1000) > (lastUpgradeTime + MIN_WILLMEMBERS_UPDATE_FREQ)) + // Alimenter wotb avec la toile de confiance + const wotbInstance = (constants.USE_WOTB6) ? wotb.newFileInstance(duniterServer.home + '/wotb.bin'):duniterServer.dal.wotb; + + + // Vérifier si le cache doit être Réinitialiser + let reinitCache = (Math.floor(Date.now() / 1000) > (willMembersLastUptime + constants.MIN_WILLMEMBERS_UPDATE_FREQ)); + + // Si le cache willMembers est dévérouillé, le vérouiller, sinon ne pas réinitialiser le cache + if (reinitCache && !lockWillMembers) + { + lockWillMembers = true; + } + else if(lockWillMembers) + { + reinitCache = false; + } + + if (reinitCache) { // Réinitialiser le cache - idtysListOrdered = []; + identitiesList = []; + idtysPendingCertifsList = []; nbMaxCertifs = 0; countMembersWithSigQtyValidCert = 0; - lastUpgradeTime = Math.floor(Date.now() / 1000); + sentries = []; + sentriesIndex = []; + wotbIdIndex = []; + membersQualityExt = []; + willMembersLastUptime = Math.floor(Date.now() / 1000); - // Alimenter wotb avec la toile actuelle - //const wotbInstance = wotb.newFileInstance(duniterServer.home + '/wotb.bin'); + // Récupérer la liste des membres référents + sentries = wotbInstance.getSentries(dSen); // Récupérer la liste des identités en piscine const resultQueryIdtys = yield duniterServer.dal.peerDAL.query('SELECT `buid`,`pubkey`,`uid`,`hash`,`expires_on` FROM identities_pending WHERE `member`=0'); @@ -65,278 +96,378 @@ module.exports = (req, res, next) => co(function *() { // Récupérer pour chaque identité, l'ensemble des certifications qu'elle à reçue. for (let i=0;i<resultQueryIdtys.length;i++) { - // Extraire le numéro de bloc d'émission de l'identité - let idtyBlockStamp = resultQueryIdtys[i].buid.split("-"); - let idtyBlockNumber = idtyBlockStamp[0]; + // Extraire le numéro de bloc d'émission de l'identité + let idtyBlockStamp = resultQueryIdtys[i].buid.split("-"); + let idtyBlockNumber = idtyBlockStamp[0]; - // récupérer le medianTime et le hash du bloc d'émission de l'identité - let idtyEmittedBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`hash` FROM block WHERE `number`=\''+idtyBlockNumber+'\' LIMIT 1'); - - // Récupérer l'identifiant wotex de l'identité (en cas d'identité multiple) - let idties = yield duniterServer.dal.idtyDAL.query('' + - 'SELECT hash, uid, pub, wotb_id FROM i_index WHERE (uid = ? or pub = ?) ' + - 'UNION ALL ' + - 'SELECT hash, uid, pubkey as pub, (SELECT NULL) AS wotb_id FROM idty WHERE (uid = ? or pubkey = ?)', [resultQueryIdtys[i].uid, resultQueryIdtys[i].uid, resultQueryIdtys[i].uid, resultQueryIdtys[i].uid]); - let wotexId = ''; - if (idties.length > 1) - { - let pos = 0; - for (const idty of idties) - { - if (idty.hash == resultQueryIdtys[i].hash) { wotexId = '['+pos+']'; } - pos++; - } - } + // récupérer le medianTime et le hash du bloc d'émission de l'identité + let idtyEmittedBlock = yield duniterServer.dal.peerDAL.query('SELECT `medianTime`,`hash` FROM block WHERE `number`=\''+idtyBlockNumber+'\' LIMIT 1'); + + // Récupérer l'identifiant wotex de l'identité (en cas d'identité multiple) + let idties = yield duniterServer.dal.idtyDAL.query('' + + 'SELECT hash, uid, pub, wotb_id FROM i_index WHERE (uid = ? or pub = ?) ' + + 'UNION ALL ' + 'SELECT hash, uid, pubkey as pub, (SELECT NULL) AS wotb_id FROM idty WHERE (uid = ? or pubkey = ?)', [resultQueryIdtys[i].uid, resultQueryIdtys[i].uid, resultQueryIdtys[i].uid, resultQueryIdtys[i].uid]); + let wotexId = ''; + if (idties.length > 1) + { + let pos = 0; + for (const idty of idties) + { + if (idty.hash == resultQueryIdtys[i].hash) { wotexId = '['+pos+']'; } + pos++; + } + } - // vérifier la validité du blockstamp de l'identité - let validIdtyBlockStamp = false; - if (idtyEmittedBlock[0].hash == idtyBlockStamp[1]) - { validIdtyBlockStamp = true; } + // vérifier la validité du blockstamp de l'identité + let validIdtyBlockStamp = false; + if (typeof(idtyEmittedBlock[0]) == 'undefined' || idtyEmittedBlock[0].hash == idtyBlockStamp[1]) + { validIdtyBlockStamp = true; } - // Stocker les informations de l'identité - identitiesList.push({ - BlockNumber: idtyBlockNumber, - creationTimestamp: idtyEmittedBlock[0].medianTime, - pubkey: resultQueryIdtys[i].pubkey, - uid: resultQueryIdtys[i].uid, - hash: resultQueryIdtys[i].hash, - wotexId: wotexId, - expires_on: resultQueryIdtys[i].expires_on, - nbCert: 0, - nbValidPendingCert: 0, - registrationAvailability: 0, - validBlockStamp: validIdtyBlockStamp - }); - idtysPendingCertifsList.push(new Array()); + // Stocker les informations de l'identité + identitiesList.push({ + BlockNumber: idtyBlockNumber, + creationTimestamp: (typeof(idtyEmittedBlock[0]) == 'undefined' ) ? currentBlockchainTimestamp:idtyEmittedBlock[0].medianTime, + pubkey: resultQueryIdtys[i].pubkey, + uid: resultQueryIdtys[i].uid, + hash: resultQueryIdtys[i].hash, + wotexId: wotexId, + expires_on: (resultQueryIdtys[i].expires_on=="") ? 0:resultQueryIdtys[i].expires_on, + nbCert: 0, + nbValidPendingCert: 0, + registrationAvailability: 0, + validBlockStamp: validIdtyBlockStamp + }); + idtysPendingCertifsList.push(new Array()); - // récupérer l'ensemble des certifications en attente destinées à l'identité courante - let tmpQueryPendingCertifsList = yield duniterServer.dal.peerDAL.query( - 'SELECT `from`,`block_number`,`block_hash`,`expires_on` FROM certifications_pending WHERE `to`=\''+resultQueryIdtys[i].pubkey+'\' AND `target`=\''+resultQueryIdtys[i].hash+'\' ORDER BY `expires_on` DESC'); + // récupérer l'ensemble des certifications en attente destinées à l'identité courante + let tmpQueryPendingCertifsList = yield duniterServer.dal.peerDAL.query( + 'SELECT `from`,`block_number`,`block_hash`,`expires_on` FROM certifications_pending WHERE `to`=\''+resultQueryIdtys[i].pubkey+'\' AND `target`=\''+resultQueryIdtys[i].hash+'\' ORDER BY `expires_on` DESC'); - // Récupérer les uid des émetteurs des certifications reçus par l'utilisateur - // Et stocker les uid et dates d'expiration dans un tableau - for (let j=0;j<tmpQueryPendingCertifsList.length;j++) - { - // Récupérer le medianTime et le hash du bloc d'émission de la certification - let emittedBlock = yield duniterServer.dal.peerDAL.query('SELECT `hash`,`medianTime` FROM block WHERE `number`=\''+tmpQueryPendingCertifsList[j].block_number+'\' AND `fork`=0 LIMIT 1'); - - // Vérifier que l'émetteur de la certification correspond à une identité inscrite en blockchain - let tmpQueryGetUidIssuerPendingCert = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+tmpQueryPendingCertifsList[j].from+'\' LIMIT 1'); - if ( tmpQueryGetUidIssuerPendingCert.length > 0 ) - { - // Mémoriser le wid - const pubkeyFrom = tmpQueryPendingCertifsList[j].from - if (!widsCache[pubkeyFrom]) { - // Récupère le wotb_id depuis la table d'index globale - widsCache[pubkeyFrom] = (yield duniterServer.dal.iindexDAL.query('SELECT wotb_id FROM i_index WHERE pub = ? AND wotb_id IS NOT NULL', [pubkeyFrom]))[0].wotb_id - } + // Récupérer les uid des émetteurs des certifications reçus par l'utilisateur + // Et stocker les uid et dates d'expiration dans un tableau + for (let j=0;j<tmpQueryPendingCertifsList.length;j++) + { + // Récupérer le medianTime et le hash du bloc d'émission de la certification + let emittedBlock = yield duniterServer.dal.peerDAL.query('SELECT `hash`,`medianTime` FROM block WHERE `number`=\''+tmpQueryPendingCertifsList[j].block_number+'\' AND `fork`=0 LIMIT 1'); + + // Vérifier que l'émetteur de la certification correspond à une identité inscrite en blockchain + let tmpQueryGetUidIssuerPendingCert = yield duniterServer.dal.peerDAL.query('SELECT `uid` FROM i_index WHERE `pub`=\''+tmpQueryPendingCertifsList[j].from+'\' LIMIT 1'); + if ( tmpQueryGetUidIssuerPendingCert.length > 0 ) + { + // Récupérer la pubkey de l'émetteur + let issuerPubkey = tmpQueryPendingCertifsList[j].from; + + // Récupérer le wotb_id + let wotb_id = 0; + if (typeof(wotbIdIndex[issuerPubkey]) == 'undefined') + { + wotb_id = (yield duniterServer.dal.iindexDAL.query('SELECT wotb_id FROM i_index WHERE pub = ? AND wotb_id IS NOT NULL', [issuerPubkey]))[0].wotb_id; + wotbIdIndex[issuerPubkey] = wotb_id; + } + else { wotb_id = wotbIdIndex[issuerPubkey]; } + + // Vérifier si l'émetteur de la certification est référent + let issuerIsSentry = false; + if (typeof(sentriesIndex[issuerPubkey]) == 'undefined') + { + sentriesIndex[issuerPubkey] = false; + for (let s=0;s<sentries.length;s++) + { + if (sentries[s] == wotb_id) + { + issuerIsSentry=true; + sentriesIndex[issuerPubkey] = true; + sentries.splice(s, 1); + } + } + } + else { issuerIsSentry = sentriesIndex[issuerPubkey]; } - // Vérifier si le blockstamp est correct - var validBlockStamp = false; - if (emittedBlock[0].hash == tmpQueryPendingCertifsList[j].block_hash) - { validBlockStamp = true; } - - // récupérer le timestamp d'enchainement de la dernière certification écrite par l'émetteur - let tmpQueryLastIssuerCert = yield duniterServer.dal.peerDAL.query('SELECT `chainable_on` FROM c_index WHERE `issuer`=\''+tmpQueryPendingCertifsList[j].from+'\' ORDER BY `chainable_on` DESC LIMIT 1'); - let certTimestampWritable = 0; - if ( typeof(tmpQueryLastIssuerCert[0]) != 'undefined' && typeof(tmpQueryLastIssuerCert[0].chainable_on) != 'undefined' ) - { certTimestampWritable = tmpQueryLastIssuerCert[0].chainable_on; } - identitiesList[i].registrationAvailability = (certTimestampWritable > identitiesList[i].registrationAvailability) ? certTimestampWritable : identitiesList[i].registrationAvailability; - - // Vérifier que l'identité courant n'a pas déjà reçu d'autre(s) certification(s) de la part du même membre ET dans le même état de validité du blockstamp - let doubloonPendingCertif = false; - for (const pendingCert of idtysPendingCertifsList[i]) - { - if (pendingCert.from == tmpQueryGetUidIssuerPendingCert[0].uid && pendingCert.validBlockStamp == validBlockStamp) - { - doubloonPendingCertif = true; - } - } - if (!doubloonPendingCertif) - { - // Stoker la liste des certifications en piscine qui n'ont pas encore expirées - if (tmpQueryPendingCertifsList[j].expires_on > currentBlockchainTimestamp) - { - idtysPendingCertifsList[i].push({ - wid: widsCache[pubkeyFrom], - from: tmpQueryGetUidIssuerPendingCert[0].uid, - pubkey: tmpQueryPendingCertifsList[j].from, - blockNumber: tmpQueryPendingCertifsList[j].block_number, - timestampExpire: tmpQueryPendingCertifsList[j].expires_on, - timestampWritable: certTimestampWritable, - validBlockStamp: validBlockStamp - }); - identitiesList[i].nbCert++; - if (validBlockStamp) { identitiesList[i].nbValidPendingCert++; } - } - } - } - } + // Vérifier si le blockstamp est correct + var validBlockStamp = false; + if (typeof(emittedBlock[0]) != 'undefined' && emittedBlock[0].hash == tmpQueryPendingCertifsList[j].block_hash) + { validBlockStamp = true; } + + // récupérer le timestamp d'enchainement de la dernière certification écrite par l'émetteur + let tmpQueryLastIssuerCert = yield duniterServer.dal.peerDAL.query('SELECT `chainable_on` FROM c_index WHERE `issuer`=\''+issuerPubkey+'\' ORDER BY `chainable_on` DESC LIMIT 1'); + let certTimestampWritable = 0; + if ( typeof(tmpQueryLastIssuerCert[0]) != 'undefined' && typeof(tmpQueryLastIssuerCert[0].chainable_on) != 'undefined' ) + { certTimestampWritable = tmpQueryLastIssuerCert[0].chainable_on; } + //identitiesList[i].registrationAvailability = (certTimestampWritable > identitiesList[i].registrationAvailability) ? certTimestampWritable : identitiesList[i].registrationAvailability; + + // Vérifier que l'identité courant n'a pas déjà reçu d'autre(s) certification(s) de la part du même membre ET dans le même état de validité du blockstamp + let doubloonPendingCertif = false; + for (const pendingCert of idtysPendingCertifsList[i]) + { + if (pendingCert.from == tmpQueryGetUidIssuerPendingCert[0].uid && pendingCert.validBlockStamp == validBlockStamp) + { + doubloonPendingCertif = true; + } + } + if (!doubloonPendingCertif) + { + // Stoker la liste des certifications en piscine qui n'ont pas encore expirées + if (tmpQueryPendingCertifsList[j].expires_on > currentBlockchainTimestamp) + { + idtysPendingCertifsList[i].push({ + from: tmpQueryGetUidIssuerPendingCert[0].uid, + pubkey: issuerPubkey, + wotb_id: wotb_id, + issuerIsSentry: issuerIsSentry, + blockNumber: tmpQueryPendingCertifsList[j].block_number, + timestampExpire: tmpQueryPendingCertifsList[j].expires_on, + timestampWritable: certTimestampWritable, + validBlockStamp: validBlockStamp + }); + identitiesList[i].nbCert++; + if (validBlockStamp) { identitiesList[i].nbValidPendingCert++; } + } + } + } + } - // Calculer le nombre maximal de certifications reçues par l'identité courante - if ( identitiesList[i].nbCert > nbMaxCertifs) { nbMaxCertifs = identitiesList[i].nbCert; } + // Calculer le nombre maximal de certifications reçues par l'identité courante + if ( identitiesList[i].nbCert > nbMaxCertifs) { nbMaxCertifs = identitiesList[i].nbCert; } - // calculate countMembersWithSigQtyValidCert - if ( identitiesList[i].nbValidPendingCert >= sigQty) { countMembersWithSigQtyValidCert++; } - } - + // calculate countMembersWithSigQtyValidCert + if ( identitiesList[i].nbValidPendingCert >= conf.sigQty) { countMembersWithSigQtyValidCert++; } + } // END IDENTITIES LOOP + + // Réinitialiser sumSentriesReachedByIdtyPerCert, sumMembersReachedByIdtyPerCert et countIdtiesPerReceiveCert + for (let i=0;i<=nbMaxCertifs;i++) + { + meanSentriesReachedByIdtyPerCert[i] = 0; + meanMembersReachedByIdtyPerCert[i] = 0; + countIdtiesPerReceiveCert[i] = 0; + } + } // END if (reinitCache) + // Si demandé, retrier les, certifications par date de disponibilité if (sortSig == "Availability") { - var idtysPendingCertifsListSort = [ [] ]; - for (var i=0;i<idtysPendingCertifsList.length;i++) - { - idtysPendingCertifsListSort[i] = Array(); - let min; - let idMin =0; - let tmpExcluded = Array(); - for (let j=0;j<idtysPendingCertifsList[i].length;j++) { tmpExcluded[j] = false; } - for (let j=0;j<idtysPendingCertifsList[i].length;j++) - { - min = currentBlockchainTimestamp+sigValidity; // begin to min = max - - // search idMin (id of certif with min timestampWritable) - for (let k=0;k<idtysPendingCertifsList[i].length;k++) - { - if (idtysPendingCertifsList[i][k].timestampWritable < min && !tmpExcluded[k]) - { - min = idtysPendingCertifsList[i][k].timestampWritable; - idMin = k; - } - } - - // Push min value on sort table - idtysPendingCertifsListSort[i].push({ - wid: idtysPendingCertifsList[i][idMin].wid, - from: idtysPendingCertifsList[i][idMin].from, - blockNumber: idtysPendingCertifsList[i][idMin].blockNumber, - timestampExpire: idtysPendingCertifsList[i][idMin].timestampExpire, - timestampWritable: idtysPendingCertifsList[i][idMin].timestampWritable, - validBlockStamp: idtysPendingCertifsList[i][idMin].validBlockStamp - }); - - // Exclure la valeur min avant de poursuivre le tri - tmpExcluded[idMin] = true; - } - } - idtysPendingCertifsList = idtysPendingCertifsListSort; + var idtysPendingCertifsListSort = [ [] ]; + for (var i=0;i<idtysPendingCertifsList.length;i++) + { + idtysPendingCertifsListSort[i] = Array(); + let min; + let idMin =0; + let tmpExcluded = Array(); + for (let j=0;j<idtysPendingCertifsList[i].length;j++) { tmpExcluded[j] = false; } + for (let j=0;j<idtysPendingCertifsList[i].length;j++) + { + min = currentBlockchainTimestamp+conf.sigValidity; // begin to min = max + + // search idMin (id of certif with min timestampWritable) + for (let k=0;k<idtysPendingCertifsList[i].length;k++) + { + if (idtysPendingCertifsList[i][k].timestampWritable < min && !tmpExcluded[k]) + { + min = idtysPendingCertifsList[i][k].timestampWritable; + idMin = k; + } + } + + // Push min value on sort table + idtysPendingCertifsListSort[i].push({ + from: idtysPendingCertifsList[i][idMin].from, + wotb_id: idtysPendingCertifsList[i][idMin].wotb_id, + issuerIsSentry: idtysPendingCertifsList[i][idMin].issuerIsSentry, + blockNumber: idtysPendingCertifsList[i][idMin].blockNumber, + timestampExpire: idtysPendingCertifsList[i][idMin].timestampExpire, + timestampWritable: idtysPendingCertifsList[i][idMin].timestampWritable, + validBlockStamp: idtysPendingCertifsList[i][idMin].validBlockStamp + }); + + // Calculer la date de disponibilité du dossier d'inscription de l'identité correspondante + // := date de disponibilité maximale parmi les sigQty certifications aux dates de disponibilités les plus faibles + if (j<conf.sigQty) + { + let timestampWritable = idtysPendingCertifsList[i][idMin].timestampWritable; + identitiesList[i].registrationAvailability = (timestampWritable > identitiesList[i].registrationAvailability) ? timestampWritable : identitiesList[i].registrationAvailability; + } + + // Exclure la valeur min avant de poursuivre le tri + tmpExcluded[idMin] = true; + } + + } + idtysPendingCertifsList = idtysPendingCertifsListSort; } // Récupérer la valeur du critère de tri pour chaque identité var tabSort = []; if (sort_by == "creationIdty") - { - for (const idty of identitiesList) - { - tabSort.push(idty.expires_on); - } + { + for (const idty of identitiesList) + { + tabSort.push(idty.expires_on); + } } else if (sort_by == "sigCount" || sort_by == "registrationPackage") { - // idtys loop - for (const idty of identitiesList) - { - // Calculate registrationAvailabilityDelay - let registrationAvailabilityDelay = (idty.registrationAvailability > currentBlockchainTimestamp) ? (idty.registrationAvailability-currentBlockchainTimestamp):0; - - // Trier les identités au dossier complet par durée entre date de disponibilité et date d'expiration maximale théorique (=sigWindow-registrationAvailabilityDelay) - // Attribuer un malus de sigValidity secondes par certification valide (plafonner à sigQty dans le cas de 'registrationPackage') - if (sort_by == "registrationPackage" && idty.nbValidPendingCert > sigQty) - { - tabSort.push(sigWindow-registrationAvailabilityDelay + (sigValidity*sigQty)); - } - else - { - tabSort.push(sigWindow-registrationAvailabilityDelay + (sigValidity*idty.nbValidPendingCert)); - } - } + for (const idty of identitiesList) + { + // Calculate registrationAvailabilityDelay + let registrationAvailabilityDelay = (idty.registrationAvailability > currentBlockchainTimestamp) ? (idty.registrationAvailability-currentBlockchainTimestamp):0; + + // Trier les identités par date de disponibilité de leur dossier d'inscription (le signe moins est nécessaire car plus un dossier est disponible tôt + // plus la valeur de registrationAvailabilityDelay sera petite, hors le nombre obtenu est classé de façon décroissante) + // Attribuer un malus de 2*sigValidity secondes par certification valide (plafonner à sigQty dans le cas de 'registrationPackage') + if (sort_by == "registrationPackage" && idty.nbValidPendingCert > conf.sigQty) + { + tabSort.push(-registrationAvailabilityDelay + (2*conf.sigValidity*conf.sigQty)); + } + else + { + tabSort.push(-registrationAvailabilityDelay + (2*conf.sigValidity*idty.nbValidPendingCert)); + } + } } else { errors += "<p>ERREUR : param <i>sort_by</i> invalid !</p>"; } // Trier les identités par ordre decroissant du critère sort_by for (var i=0;i<identitiesList.length;i++) { - let max = -1; - let idMax =0; - for (var j=0;j<identitiesList.length;j++) - { - if (tabSort[j] > max) - { - max = tabSort[j]; - idMax = j; - } - } - - // Push max value on sort table, only if respect days limit - if (limitTimestamp > identitiesList[idMax].expires_on) - { - // Vérifier que cette identité n'a pas déjà été prise en compte (empecher les doublons) - let doubloon = false; - for (const idty of idtysListOrdered) - { - if (identitiesList[idMax].uid == idty.uid && identitiesList[idMax].BlockNumber == idty.BlockNumber) - { doubloon = true; } - } - - // Push max value on sort table (and test distance rule) - if (!doubloon) - { - // Tester la distance à l'aide des certifications disponibles - //let tmpWot = wotbInstance.memCopy(); - let tmpWot = duniterServer.dal.wotb.memCopy(); + let max = -1; + let idMax =0; + for (var j=0;j<identitiesList.length;j++) + { + if (tabSort[j] > max) + { + max = tabSort[j]; + idMax = j; + } + } + + // Push max value on sort table, only if respect days limit + if (limitTimestamp > identitiesList[idMax].expires_on) + { + // Vérifier que cette identité n'a pas déjà été prise en compte (empecher les doublons) + let doubloon = false; + for (const idty of idtysListOrdered) + { + if (identitiesList[idMax].uid == idty.uid && identitiesList[idMax].BlockNumber == idty.BlockNumber) + { + doubloon = true; + } + } + + // Push max value on sort table (and test distance rule) + if (!doubloon) + { + // Tester la présence de l'adhésion + let membership = null + const pendingMembershipsOfIdty = yield duniterServer.dal.msDAL.getPendingINOfTarget(identitiesList[idMax].hash); + for (const ms of pendingMembershipsOfIdty) + { + if (!membership && ms.expires_on > currentBlockchainTimestamp) + { + membership = ms + } + } + + // Créer une wot temporaire + let tmpWot = wotbInstance.memCopy(); + + // Mesurer la qualité externe de chaque emetteur de chaque certification + if (constants.USE_WOTB6) + { + for (const cert of idtysPendingCertifsList[idMax]) + { + if ( typeof(membersQualityExt[cert.from]) == 'undefined' ) + { + let detailedDistanceQualityExt = tmpWot.detailedDistance(cert.wotb_id, dSen, conf.stepMax-1, conf.xpercent); + membersQualityExt[cert.from] = ((detailedDistanceQualityExt.nbSuccess/detailedDistanceQualityExt.nbSentries)/conf.xpercent).toFixed(2); + } + } + } + + // Ajouter un noeud a la wot temporaire et lui donner toute les certifications valides reçues par l'indentité idMax + let pendingIdtyWID = tmpWot.addNode(); + for (const cert of idtysPendingCertifsList[idMax]) + { + if (cert.validBlockStamp) + { + tmpWot.addLink(cert.wotb_id, pendingIdtyWID); + } + } + // Récupérer les données de distance du dossier d'adhésion de l'indentité idMax + let detailedDistance = (constants.USE_WOTB6) ? tmpWot.detailedDistance(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent):tmpWot.isOutdistanced(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent); - let pendingIdtyWID = tmpWot.addNode(); - for (const cert of idtysPendingCertifsList[idMax]) - { - tmpWot.addLink(cert.wid, pendingIdtyWID); - } - //let detailedDistance = tmpWot.detailedDistance(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent); - //let isOutdistanced = detailedDistance.isOutdistanced; - let isOutdistanced = tmpWot.isOutdistanced(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent); - - // Tester la présence de l'adhésion - let membership = null - const pendingMembershipsOfIdty = yield duniterServer.dal.msDAL.getPendingINOfTarget(identitiesList[idMax].hash); - for (const ms of pendingMembershipsOfIdty) { - if (!membership && ms.expires_on > currentBlockchainTimestamp) { - membership = ms - } - } + // Nettoyer la wot temporaire + tmpWot.clear(); + + // Calculer percentSentriesReached et percentMembersReached + let percentSentriesReached = (constants.USE_WOTB6) ? parseFloat(((detailedDistance.nbSuccess/detailedDistance.nbSentries)*100).toFixed(2)):null; + let percentMembersReached = (constants.USE_WOTB6) ? parseFloat(((detailedDistance.nbReached/currentMembersCount)*100).toFixed(2)):null; + + // Pousser l'identité dans le tableau idtysListOrdered + idtysListOrdered.push({ + uid: identitiesList[idMax].uid, + wotexId: identitiesList[idMax].wotexId, + creationTimestamp: identitiesList[idMax].creationTimestamp, + pubkey: identitiesList[idMax].pubkey, + BlockNumber: identitiesList[idMax].BlockNumber, + expires_on: identitiesList[idMax].expires_on, + nbValidPendingCert: identitiesList[idMax].nbValidPendingCert, + detailedDistance: detailedDistance, + percentSentriesReached: percentSentriesReached, + percentMembersReached: percentMembersReached, + membership: membership, + pendingCertifications: idtysPendingCertifsList[idMax], + validBlockStamp: identitiesList[idMax].validBlockStamp + }); + + // Si le cache a été réinitialiser, recalculer les sommes meanSentriesReachedByIdtyPerCert et meanMembersReachedByIdtyPerCert + if (constants.USE_WOTB6 && reinitCache && identitiesList[idMax].nbValidPendingCert > 0) + { + let nbReceiveCert = identitiesList[idMax].nbValidPendingCert; + meanSentriesReachedByIdtyPerCert[nbReceiveCert-1] += percentSentriesReached; + meanMembersReachedByIdtyPerCert[nbReceiveCert-1] += percentMembersReached; + countIdtiesPerReceiveCert[nbReceiveCert-1] += 1; + } + } // END if (!doubloon) + } // END days limit rule - // Nettoie la wot temporaire - tmpWot.clear(); - - idtysListOrdered.push({ - uid: identitiesList[idMax].uid, - wotexId: identitiesList[idMax].wotexId, - creationTimestamp: identitiesList[idMax].creationTimestamp, - pubkey: identitiesList[idMax].pubkey, - BlockNumber: identitiesList[idMax].BlockNumber, - expires_on: identitiesList[idMax].expires_on, - nbValidPendingCert: identitiesList[idMax].nbValidPendingCert, - isOutdistanced, - membership: membership, - pendingCertifications: idtysPendingCertifsList[idMax], - validBlockStamp: identitiesList[idMax].validBlockStamp - }); - } - } - // Exclure la valeur max avant de poursuivre le tri - tabSort[idMax] = -1; - } + // Exclure la valeur max avant de poursuivre le tri + tabSort[idMax] = -1; + } // End sort identities loop // Si ordre croissant demandé, inverser le tableau if (order == 'asc') { - var idtysListOrdered2 = [ [] ]; - let tmpIdtysListOrderedLength = idtysListOrdered.length; - for (let i=0;i<tmpIdtysListOrderedLength;i++) - { - idtysListOrdered2[i] = idtysListOrdered[tmpIdtysListOrderedLength-i-1]; - } - idtysListOrdered = idtysListOrdered2; + var idtysListOrdered2 = [ [] ]; + let tmpIdtysListOrderedLength = idtysListOrdered.length; + for (let i=0;i<tmpIdtysListOrderedLength;i++) + { + idtysListOrdered2[i] = idtysListOrdered[tmpIdtysListOrderedLength-i-1]; + } + idtysListOrdered = idtysListOrdered2; } - } + + if (reinitCache) + { + // Calculate meanSentriesReachedByIdtyPerCert and meanMembersReachedByIdtyPerCert + if (constants.USE_WOTB6) + { + for (let i=0;i<=nbMaxCertifs;i++) + { + if ( countIdtiesPerReceiveCert[i] > 0 ) + { + meanSentriesReachedByIdtyPerCert[i] = parseFloat((meanSentriesReachedByIdtyPerCert[i]/countIdtiesPerReceiveCert[i]).toFixed(2)); + meanMembersReachedByIdtyPerCert[i] = parseFloat((meanMembersReachedByIdtyPerCert[i]/countIdtiesPerReceiveCert[i]).toFixed(2)); + } + else + { + meanSentriesReachedByIdtyPerCert[i] = 0.0; + meanMembersReachedByIdtyPerCert[i] = 0.0; + } + } + } + + // Dévérouiller le cache willMembers + lockWillMembers = false; + } // Si le client demande la réponse au format JSON, le faire if (format == 'JSON') @@ -349,43 +480,53 @@ module.exports = (req, res, next) => co(function *() { { res.locals = { // Les varibles à passer au template - host: req.headers.host.toString(), + host: req.headers.host.toString(), + USE_WOTB6: constants.USE_WOTB6, + // get parameters days, sort_by, order, sortSig, - hideIdtyWithZeroCert, - + showIdtyWithZeroCert, + // page data currentBlockNumber, currentBlockchainTimestamp, + currentMembersCount, limitTimestamp, - sigWindow, - idtyWindow, - nbMaxCertifs, - countMembersWithSigQtyValidCert, - - wotexURL: duniterServer.conf['duniter-currency-monit'].wotexURL, - - idtysListFiltered: idtysListOrdered.filter( idty=> - idty.expires_on < limitTimestamp - && idty.expires_on > currentBlockchainTimestamp - && (hideIdtyWithZeroCert != "yes" || idty.pendingCertifications.length > 0) - ), - + nbMaxCertifs, + countMembersWithSigQtyValidCert, + idtysListFiltered: idtysListOrdered.filter( idty=> + idty.expires_on < limitTimestamp + //&& idty.expires_on > currentBlockchainTimestamp + && (showIdtyWithZeroCert == "yes" || idty.pendingCertifications.length > 0) + ), + // currency parameters + dSen, + sigQty: conf.sigQty, + sigWindow: conf.sigWindow, + idtyWindow: conf.idtyWindow, + xpercent: conf.xpercent, + // willMembers cache data + meanSentriesReachedByIdtyPerCert, + meanMembersReachedByIdtyPerCert, + membersQualityExt, // Template helpers timestampToDatetime, // Calculer la proportion de temps restant avant l'expiration color: function( timestamp, idtyWindow, max ) { - const MIN = 120; - let proportion = (((timestamp-currentBlockchainTimestamp)*(max-MIN))/idtyWindow)+MIN; + const MIN = 120; + let proportion = (((timestamp-currentBlockchainTimestamp)*(max-MIN))/idtyWindow)+MIN; proportion = proportion < MIN ? MIN : proportion > max ? max : proportion let hex = parseInt( proportion ).toString(16) return `#${hex}${hex}${hex}` } } + // Passer la main au template next() } - } catch (e) { - // En cas d'exception, afficher le message - res.status(500).send(`<pre>${e.stack || e.message}</pre>`); - } + } + catch (e) + { + // En cas d'exception, afficher le message + res.status(500).send(`<pre>${e.stack || e.message}</pre>`); + } -}) \ No newline at end of file +}) diff --git a/routes/wotex.js b/routes/wotex.js index 13bee4ff964fde1006cc76c002b7e774c4b2effb..22e385f82a003fef4de8312e474e37e2ea8483e8 100755 --- a/routes/wotex.js +++ b/routes/wotex.js @@ -34,6 +34,7 @@ module.exports = (req, res, next) => co(function *() { })); let searchResult = ''; + let lignes = []; if (req.query.to) { let idty; let pos = 0, search = req.query.to; @@ -64,7 +65,6 @@ module.exports = (req, res, next) => co(function *() { idty = res.idty; const mapPendingCerts = res.mapPendingCerts; - let lignes = []; for (const membre of membres) { if (req.query.mode == "u2w") { alimenteLignes(wotb, idty, membre, lignes, dicoIdentites, mapPendingCerts); @@ -79,26 +79,26 @@ module.exports = (req, res, next) => co(function *() { // Si le client demande la réponse au format JSON, le faire if (format == 'JSON') { - // Send JSON reponse - //res.status(200).jsonp( ? ) + // Send JSON reponse + res.status(200).jsonp( lignes ) } // Sinon, printer la page html else { - // write sentriesHTML - let sentriesHTML = sentries - .map((sentry) => ` - <div class="sentry isSentry"><a href="wotex?lg=${LANG['LG']}&to=${sentry.uid}">${sentry.uid}</a></div> - `) - .join(''); - - res.locals = { - // Les varibles à passer au template - host: req.headers.host.toString(), - searchResult, - sentriesHTML - } - next() + // write sentriesHTML + let sentriesHTML = sentries + .map((sentry) => ` + <div class="sentry isSentry"><a href="wotex?lg=${LANG['LG']}&to=${sentry.uid}">${sentry.uid}</a></div> + `) + .join(''); + + res.locals = { + // Les varibles à passer au template + host: req.headers.host.toString(), + searchResult, + sentriesHTML + } + next() } } catch (e) { // En cas d'exception, afficher le message diff --git a/views/about.html b/views/about.html index 0b8d955630c60b47bdb900f83c791606a6c0d655..bfe6a303f47fa7dc3ad78578ff4bbd38d955d321 100755 --- a/views/about.html +++ b/views/about.html @@ -31,7 +31,7 @@ ${(host.substr(host.length-6,6) == '.onion') ? HTML_TOR_HEAD:HTML_HEAD} <a href="https://github.com/duniter/duniter-currency-monit/blob/master/LICENSE">License AGPL V 3.0</a><br> <a href="https://github.com/duniter/duniter-currency-monit/">github repository</a> </div><br> -<div align="left">Version : <a href="https://github.com/duniter/duniter-currency-monit/releases/tag/module-0.2.13">module-0.2.13</a></div><br> +<div align="left">Version : <a href="https://github.com/duniter/duniter-currency-monit/releases/tag/0.3.6">0.3.6</a></div><br> <div align="left">Author : <a href="https://github.com/librelois">Éloïs Librelois</a></div><br> <div align="left">Contributors : <a href="https://github.com/jytou">jytou</a> (translator), <a href="https://github.com/devingfx">devingfx</a> (frontend), <a href="https://github.com/c-geek">cgeek</a> (willMembers and wotex),<a href="https://github.com/M5oul">M5oul</a> (adjustments)</div><br> <br> diff --git a/views/members.html b/views/members.html index b0de78d6dc0b2751dd4217798f8d7734089e4e13..df0c293d08f2eacd24c12f725d8ad7f15eae0bbe 100755 --- a/views/members.html +++ b/views/members.html @@ -31,17 +31,62 @@ ${(host.substr(host.length-6,6) == '.onion') ? HTML_TOR_HEAD:HTML_HEAD} <option name="sort_by" value ="lastRenewal" ${sort_by == 'lastRenewal' ? 'selected' : ''}>${LANG["SORT_LAST_RENEWAL"]} <option name="sort_by" value ="oldestSig" ${sort_by == 'oldestSig' ? 'selected' : ''}>${LANG["SORT_BY_OLDEST_SIG"]} <option name="sort_by" value ="lastSig" ${sort_by == 'lastSig' ? 'selected' : ''}>${LANG["SORT_BY_LAST_SIG"]} +<option name="sort_by" value ="centrality" ${sort_by == 'centrality' ? 'selected' : ''}>${LANG["SORT_BY_CENTRALITY"]} +${(USE_WOTB6) ? `<option name="sort_by" value ="quality" ${sort_by == 'quality' ? 'selected' : ''}>${LANG["SORT_BY_QUALITY"]}`:``} <option name="sort_by" value ="sigCount" ${sort_by == 'sigCount' ? 'selected' : ''}>${LANG["SORT_BY_SIG_COUNT"]} </select> ${LANG["ORDER_BY"]} <select name="order"> <option name="order" value ="asc"> ${LANG["ASC"]} <option name="order" value ="desc" ${order == 'desc' ? 'selected' : ''}> ${LANG["DESC"]}</select> -<input type="submit" value="envoyer"><br> +<input type="submit" value="${LANG['SUBMIT_BUTTON']}"><br> +<input type="checkbox" name="centrality" value="yes">${LANG["CHECKBOX_CENTRALITY"]}.<br> <input type="checkbox" name="pendingSigs" value="yes" ${pendingSigs == 'yes' ? 'checked' : ''}>${LANG["CHECKBOX_PENDING_SIGS"]}.<br> -<input type="checkbox" name="mode" value="emitted" ${mode == 'emitted' ? 'checked' : ''}>${LANG["CHECKBOX_MODE_SIG"]}.</form><br> +<input type="checkbox" name="mode" value="emitted" ${mode == 'emitted' ? 'checked' : ''}>${LANG["CHECKBOX_MODE_SIG"]}.<br> +</form> +<hr> + +<!-- Afficher la légende et l'aide --> +${(help != 'no') ? ` + <b>${LANG["LEGEND"]}</b><br> + ${(pendingSigs == 'yes') ? ` ${LANG["LEGEND_AVAILABILITY"]}<br>` : ''} + <br> + <b>${LANG["WHAT_IS_CENTRALITY_TITLE"]}</b><br> + ${LANG["WHAT_IS_CENTRALITY_TEXT"]}<br> + <br> + <b>${LANG["QUALITY_MEMBER_TITLE"]}</b><br> + ${LANG["QUALITY_MEMBER_TXT"]}<br> + <hr> +`:''} -<!-- Afficher la légende --> +<!-- Afficher l'état de tension de la toile de confiance --> +<h3>${LANG["WOT_TENSION_STATE"]}</h3> +${(membersLastCentralityCalcTime==0) ? ` + ${LANG["CENTRALITY_NOT_CALC"]}.<br> +`:` + ${(lockCentralityCalc) ? ` + ${LANG["CENTRALITY_CALC_BUSY"]}.<br> + `:` + <table border="1"> + <tr><td align='center' colspan='2'>${LANG["DATA_AT"]} ${timestampToDatetime(membersLastCentralityCalcTime)}</td></tr> + <tr><td align='center'>${LANG["MEAN_LENGTH_PATH"]}</td><td align='center'><b>${(meanShortestsPathLength).toFixed(2)}</b></td></tr> + <tr><td align='center'>${LANG["MEAN_CENTRALITY"]}</td><td align='center'><b>${meanCentrality.toFixed(2)}</b></td></tr> + <tr><td align='center'>${LANG["NUMBER_OF_PAIRS_MEMBER"]}</td><td align='center'>${(membersListFiltered.length*(membersListFiltered.length-1))}</td></tr> + <tr><td align='center'>${LANG["NUMBER_OF_EXIST_PATH"]}</td><td align='center'><b>${nbShortestsPath}</b></td></tr> + <tr><td align='center'><b>${LANG["PROPORTION_OF_EXIST_PATH"]}</b></td><td align='center'><font color="red"><b>${((nbShortestsPath/(membersListFiltered.length*(membersListFiltered.length-1)))*100).toFixed(2)}%</b></font></td></tr> + </table> + `} +`} <br> -${(pendingSigs == 'yes' && help != 'no') ? ` ${LANG["LEGEND_AVAILABILITY"]}<br>` : ''} +${(USE_WOTB6) ? ` + <table border="1"> + <tr><td align='center' colspan='3'>${LANG["DATA_AT"]} ${timestampToDatetime(membersLastUptime)}</td></tr> + <tr><td align='center'>${LANG["meanMembersReachedByMembersInSingleExtCert"]}</td><td align='center'>${LANG["SENTRIES_REACHED"]}</td><td align='center'>${LANG["MEMBERS_REACHED"]}</td></tr> + <tr><td align='center'>${LANG["SENTRY_CERT"]}</td><td align='center'>${meanSentriesReachedBySentriesInSingleExtCert}%</td><td align='center'>${meanMembersReachedBySentriesInSingleExtCert}%</td></tr> + <tr><td align='center'>${LANG["MEMBER_CERT"]}</td><td align='center'><font color="${(meanSentriesReachedByMembersInSingleExtCert<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByMembersInSingleExtCert}%</b></font></td><td align='center'><b>${meanMembersReachedByMembersInSingleExtCert}%</b></td></tr> + <tr><td align='center'><b>${LANG["MEAN_QUALITY"]}</b></td><td align='center'><font color="red"><b>${(meanSentriesReachedByMembersInSingleExtCert/(xpercent*100)).toFixed(2)}</b></font></td><td align='center'><b>${(meanMembersReachedByMembersInSingleExtCert/(xpercent*100)).toFixed(2)}</b></td></tr> + <tr><td align='center'><b>${LANG["PROPORTION_MEMBERS_WITH_QUALITY_UPPER_1"]}</b></td><td align='center'><font color="red"><b>${(proportionMembersWithQualityUpper1*100).toFixed(2)}%</b></font></td><td align='center'><b>${(proportionMembersWithQualityUpper1IfNoSentries*100).toFixed(2)}%</b></td></tr> + </table> + <br> +`:``} <!-- Afficher le currentBlockchainTimestamp --> <i>${LANG["CURRENT_BLOCKCHAIN_TIME"]} : ${timestampToDatetime(currentBlockchainTimestamp)}.</i><br> @@ -67,10 +112,18 @@ ${(pendingSigs == 'yes' && help != 'no') ? ` ${LANG["LEGEND_AVAILABILITY"]}<br>` <tr> ${(member.proportion = proportion(member.expireMembershipTimestamp,msValidity,0,120),'')} <td align="center" style="background:hsla(${member.proportion}, 100%, 50%, 1)"> - <h3>${member.uid}</h3> + <b>${member.uid}</b><br> + + ${(member.isSentry) ? `<font color="blue">${LANG['REFERRING_MEMBER']} : ${LANG['YES']}</font>`:`${LANG['REFERRING_MEMBER']} : ${LANG['NO']}`}<br> + ${(USE_WOTB6) ? ` + ${LANG['QUALITY_EXT']} : <b>${(typeof(membersQualityExt[member.uid])=='undefined') ? `0.00`:membersQualityExt[member.uid]}</b><br> + `:``} + <b>${(membersLastCentralityCalcTime==0) ? `${LANG['CENTRALITY']} : ?`:` + ${LANG['CENTRALITY']} : <b>${(typeof(membersCentrality[member.wotb_id])=='undefined') ? `0`:membersCentrality[member.wotb_id]} + `}</b><br> ->${member.certifications.length} - ${pendingSigs == "yes" && member.pendingCertifications.length > 0 - ? `(${member.pendingCertifications.length})` + ${pendingSigs == "yes" && typeof(member.pendingCertifications) != 'undefined' + ? ` (+${member.pendingCertifications.length})` : ''} </td> <td align="center" style="background:hsla(${member.proportion}, 100%, 50%, 1)"> @@ -84,33 +137,41 @@ ${(pendingSigs == 'yes' && help != 'no') ? ` ${LANG["LEGEND_AVAILABILITY"]}<br>` <td align="center" style="background:hsla(${member.proportion}, 100%, 50%, 1)"> ${timestampToDatetime(member.expireMembershipTimestamp)}</td> <td align='center' style="background:hsla(${member.proportion}, 100%, 50%, 1)"> - <span style="color: ${member.isOutdistanced ? 'red' : 'blue' }">${member.isOutdistanced ? LANG['COL_DISTANCE_isOutdistanced'] : LANG['COL_DISTANCE_isNotOutdistanced'] }</span> + + <font color="${member.detailedDistance.isOutdistanced ? 'red' : 'blue' }"> + ${member.detailedDistance.isOutdistanced ? LANG['COL_DISTANCE_isOutdistanced'] : LANG['COL_DISTANCE_isNotOutdistanced'] } + ${(USE_WOTB6) ? ` + <br>${member.percentSentriesReached}% (${member.detailedDistance.nbSuccess}/${member.detailedDistance.nbSentries}) + `:``} + </font> </td> <td style="background:#000000">-</td> <!-- Printer les certifications en piscine --> - ${pendingSigs == "yes" ? - member.pendingCertifications.map( certification=> ` + ${pendingSigs == "yes" && typeof(member.pendingCertifications) != 'undefined' ? + member.pendingCertifications.map( cert=> ` <!-- Printer la certification --> - <td align="center" style="background:${color(certification.timestampExpire,sigWindow,200)})"> - ${certification.protagonist}<br> - ${timestampToDatetime(certification.timestampExpire)}<br> - ${LANG["EMITTED"]} #${certification.blockNumber}<br> - ${(certification.timestampWritable > currentBlockchainTimestamp) ? `<font color="DarkRed">[${timestampToDatetime(certification.timestampWritable)} - `:`<font color="green">[${LANG["CERT_AVAILABLE"]}` - }] + <td align="center" style="background:${(cert.validBlockStamp) ? cert.colorPending=color(cert.expires_on,idtyWindow,250) : cert.colorPending='#FF8000'}"> + <b>${cert.protagonist}</b><br> + ${timestampToDatetime(cert.timestampExpire)}<br> + ${LANG["EMITTED"]} #${cert.blockNumber}<br> + ${ ( !cert.validBlockStamp || cert.timestampWritable > currentBlockchainTimestamp ) ? ` + <font color="DarkRed">[${ (cert.validBlockStamp) ? timestampToDatetime(cert.timestampWritable):LANG['INVALID_BLOCKSTAMP']}]</font> + ` : ` + <font color='green'>[${LANG['CERT_AVAILABLE']}]</font> + `} </font> </td> `).join('') :''} <!-- Printer les certifications écrites en blockchain --> - ${member.certifications.map( certification=> ` + ${member.certifications.map( cert=> ` <!-- Printer la certification --> - <td align="center" style="background:hsla(${proportion(certification.timestampExpire,sigValidity,0,120)}, 100%, 50%, 1 )"> - ${(mode=='emitted') ? certification.receiver:certification.issuer}<br> - ${timestampToDatetime(certification.timestampExpire)}<br> - ${LANG["WRITTEN"]} #${certification.writtenBloc} + <td align="center" style="background:hsla(${proportion(cert.timestampExpire,sigValidity,0,120)}, 100%, 50%, 1 )"> + <b>${(mode=='emitted') ? cert.receiver:cert.issuer}</b><br> + ${timestampToDatetime(cert.timestampExpire)}<br> + ${LANG["WRITTEN"]} #${cert.writtenBloc} </td> `).join('')} </tr> diff --git a/views/willMembers.html b/views/willMembers.html index e0d14cd050a7cef3e71fa507cebc64d0e67756ed..ed4affe4c4d48be3c6d0931424eda59ae4395885 100755 --- a/views/willMembers.html +++ b/views/willMembers.html @@ -1,5 +1,21 @@ ${(host.substr(host.length-6,6) == '.onion') ? HTML_TOR_HEAD:HTML_HEAD} <title>${currencyName}-monit</title> + +<script type="text/javascript">// <![CDATA[ + function deroule(champ,valeur) + { + /*valeur est la hauteur en pixel de la zone*/ + switch (champ) + { + case 1: /*si champ vaut 1 alors on change la hauteur de zone1*/ + document.getElementById("zone1").style.height=valeur+'px'; + break; + case 2: /*si champ vaut 2 alors on change la hauteur de zone2*/ + document.getElementById("zone2").style.height=valeur+'px'; + break; + } + } +// ]]></script> </head> <body> @@ -14,7 +30,7 @@ ${(host.substr(host.length-6,6) == '.onion') ? HTML_TOR_HEAD:HTML_HEAD} <td><a href="wotex?lg=${MENU_LANG['LG']}${(typeof(help) != 'undefined' && help == 'no') ? '&help=no':''}">${MENU_LANG["WOTEX"]}</a></td> <td><a href="about?lg=${MENU_LANG['LG']}${(typeof(help) != 'undefined' && help == 'no') ? '&help=no':''}">${MENU_LANG["ABOUT"]}</a></td> <td> - <form action="" method="GET"> + <form action="" method="GET"><input type="hidden" name="help" value="${help}"> <select name="lg" onchange="this.form.submit()"> <option name="lg" value="fr" ${MENU_LANG['LG'] == 'fr' ? 'selected' : ''}>FR <option name="lg" value="en" ${MENU_LANG['LG'] == 'en' ? 'selected' : ''}>EN @@ -35,37 +51,110 @@ ${(host.substr(host.length-6,6) == '.onion') ? HTML_TOR_HEAD:HTML_HEAD} <option name="order" value ="desc" ${order == 'desc' ? 'selected' : ''}>${LANG["ORDER_DESC"]} </select> <input type="submit" value="${LANG["SUBMIT_TXT"]}"><br> - <input type="checkbox" name="hideIdtyWithZeroCert" value="yes" ${hideIdtyWithZeroCert == 'yes' ? 'checked' : ''}>${LANG["CHECKBOX_HIDE_IDTY_WITH_ZERO_CERT"]}<br> + <input type="checkbox" name="showIdtyWithZeroCert" value="yes" ${showIdtyWithZeroCert == 'yes' ? 'checked' : ''}>${LANG["CHECKBOX_SHOW_IDTY_WITH_ZERO_CERT"]}<br> <input type="checkbox" name="sortSig" value="Availability" ${sortSig == 'Availability' ? 'checked' : ''}>${LANG["CHECKBOX_SORT_SIG"]} </form> <br> +<hr> -<!-- Afficher le currentBlockchainTimestamp et la légende --> +<!-- Afficher la légende et l'aide --> ${(help != 'no') ? ` - <i><font color='DarkRed'>${LANG["SIG_PERIOD_LEGEND"]}</font><br> - <font color='green'>${LANG["SIG_PERIOD_LEGEND2"]}</font></i><br> - <br> - <b>${LANG["HOW_TO_BECOME_MEMBER_TITLE"]}</b><br> - ${LANG["HOW_TO_BECOME_MEMBER_TEXT"]}<br> - <br> - <b>${LANG["DISTANCE_RULE_TITLE"]}</b><br> - ${LANG["DISTANCE_RULE_TXT"]}<br> - <br> - ${LANG["BLOCKCHAIN_TIME"]} : <b>${timestampToDatetime(currentBlockchainTimestamp)}</b> (#<b>${currentBlockNumber}</b>).<br> - ${LANG["COUNT_READY_MEMBERS"]} : <b>${countMembersWithSigQtyValidCert}</b>.<br> + <div id="zone1" style="width: 100%; height: 20px; background: White; border: 1px solid DimGrey; transition: height 1s; -moz-transition: height 1s;-webkit-transition: height 1s;-o-transition: height 1s; overflow: hidden;"> + <div id="bandeau1" style="height: 20px; width: 100%; font-size: medium; color: white; background-color: darkgrey;" onmouseover="deroule(1,400);" onmouseout="deroule(1,20);"><b>${LANG["LEGEND"]}</b> + </div> + <div id="texte1" style="float: left;"> + <i><font color='DarkRed'>${LANG["SIG_PERIOD_LEGEND"]}</font><br> + <font color='green'>${LANG["SIG_PERIOD_LEGEND2"]}</font></i><br> + <br> + <b>${LANG["HOW_TO_BECOME_MEMBER_TITLE"]}</b><br> + ${LANG["HOW_TO_BECOME_MEMBER_TEXT"]}<br> + <br> + <b>${LANG["DISTANCE_RULE_TITLE"]}</b><br> + ${LANG["DISTANCE_RULE_TXT"]} <b>${dSen}</b><br> + <br> + <b>${LANG["WHAT_IS_CENTRALITY_TITLE"]}</b><br> + ${LANG["WHAT_IS_CENTRALITY_TEXT"]}<br> + <br> + <b>${LANG["QUALITY_MEMBER_TITLE"]}</b><br> + ${LANG["QUALITY_MEMBER_TXT"]}<br> + </div> + </div> `:''} -<br> -<!--On parcour toutes les identités--> +<!-- Afficher l'état de tension de la toile de confiance --> +${ (USE_WOTB6) ? ` + <div id="zone2" style="width: 100%; height: 20px; background: White; border: 1px solid DimGrey; transition: height 1s; -moz-transition: height 1s;-webkit-transition: height 1s;-o-transition: height 1s; overflow: hidden;"> + <div id="bandeau2" style="height: 20px; width: 100%; font-size: medium; color: white; background-color: darkgrey;" onmouseover="deroule(2,150);" onmouseout="deroule(2,20);"><b>${LANG["WOT_TENSION_STATE"]}</b> + </div> + <div id="texte2" style="float: left;"> + <table border="1"> + <tr> + <td align='center'>${LANG["NUMBER_OF_RECEIVED_CERTS"]}</td><td align='center'>1</td><td align='center'>2</td><td align='center'>3</td><td align='center'>4</td><td align='center'>5</td> + ${(nbMaxCertifs>5) ? `<td align='center'>6</td>`:``} + ${(nbMaxCertifs>6) ? `<td align='center'>7</td>`:``} + ${(nbMaxCertifs>7) ? `<td align='center'>8</td>`:``} + ${(nbMaxCertifs>8) ? `<td align='center'>9</td>`:``} + ${(nbMaxCertifs>9) ? `<td align='center'>10</td>`:``} + </tr> + <!-- Afficher les moyennes SentriesReachedByIdtyPerCert --> + <tr> + <td align='center'>${LANG["MEAN_SENTRIES_REACHED"]}</td> + <td><font color="${(meanSentriesReachedByIdtyPerCert[0]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[0]}%</b></font></td> + <td><font color="${(meanSentriesReachedByIdtyPerCert[1]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[1]}%</b></font></td> + <td><font color="${(meanSentriesReachedByIdtyPerCert[2]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[2]}%</b></font></td> + <td><font color="${(meanSentriesReachedByIdtyPerCert[3]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[3]}%</b></font></td> + <td><font color="${(meanSentriesReachedByIdtyPerCert[4]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[4]}%</b></font></td> + ${(nbMaxCertifs>5) ? `<td><font color="${(meanSentriesReachedByIdtyPerCert[5]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[5]}%</b></font></td>`:``} + ${(nbMaxCertifs>6) ? `<td><font color="${(meanSentriesReachedByIdtyPerCert[6]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[6]}%</b></font></td>`:``} + ${(nbMaxCertifs>7) ? `<td><font color="${(meanSentriesReachedByIdtyPerCert[7]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[7]}%</b></font></td>`:``} + ${(nbMaxCertifs>8) ? `<td><font color="${(meanSentriesReachedByIdtyPerCert[8]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[8]}%</b></font></td>`:``} + ${(nbMaxCertifs>9) ? `<td><font color="${(meanSentriesReachedByIdtyPerCert[9]<xpercent) ? 'DarkRed' : 'blue' }"><b>${meanSentriesReachedByIdtyPerCert[9]}%</b></font></td>`:``} + </tr> + <!-- Afficher les qualités moyennes --> + <tr> + <td align='center'>${LANG["MEAN_QUALITY_CERTS"]}</td> + <td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[0]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[0]/(xpercent*100)).toFixed(2)}</b></font></td> + <td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[1]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[1]/(xpercent*100)).toFixed(2)}</b></font></td> + <td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[2]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[2]/(xpercent*100)).toFixed(2)}</b></font></td> + <td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[3]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[3]/(xpercent*100)).toFixed(2)}</b></font></td> + <td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[4]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[4]/(xpercent*100)).toFixed(2)}</b></font></td> + ${(nbMaxCertifs>5) ? `<td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[5]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[5]/(xpercent*100)).toFixed(2)}</b></font></td>`:``} + ${(nbMaxCertifs>6) ? `<td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[6]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[6]/(xpercent*100)).toFixed(2)}</b></font></td>`:``} + ${(nbMaxCertifs>7) ? `<td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[7]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[7]/(xpercent*100)).toFixed(2)}</b></font></td>`:``} + ${(nbMaxCertifs>8) ? `<td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[8]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[8]/(xpercent*100)).toFixed(2)}</b></font></td>`:``} + ${(nbMaxCertifs>9) ? `<td align='center'><font color="${(meanSentriesReachedByIdtyPerCert[9]<xpercent) ? 'DarkRed' : 'blue' }"><b>${(meanSentriesReachedByIdtyPerCert[9]/(xpercent*100)).toFixed(2)}</b></font></td>`:``} + </tr> + <!-- Afficher les moyennes MembersReachedByIdtyPerCert --> + <tr> + <td align='center'>${LANG["MEAN_MEMBERS_REACHED"]}</td> + <td><b>${meanMembersReachedByIdtyPerCert[0]}%</b></td> + <td><b>${meanMembersReachedByIdtyPerCert[1]}%</b></td> + <td><b>${meanMembersReachedByIdtyPerCert[2]}%</b></td> + <td><b>${meanMembersReachedByIdtyPerCert[3]}%</b></td> + <td><b>${meanMembersReachedByIdtyPerCert[4]}%</b></td> + ${(nbMaxCertifs>5) ? `<td><b>${meanMembersReachedByIdtyPerCert[5]}%</b></td>`:``} + ${(nbMaxCertifs>6) ? `<td><b>${meanMembersReachedByIdtyPerCert[6]}%</b></td>`:``} + ${(nbMaxCertifs>7) ? `<td><b>${meanMembersReachedByIdtyPerCert[7]}%</b></td>`:``} + ${(nbMaxCertifs>8) ? `<td><b>${meanMembersReachedByIdtyPerCert[8]}%</b></td>`:``} + ${(nbMaxCertifs>9) ? `<td><b>${meanMembersReachedByIdtyPerCert[9]}%</b></td>`:``} + </tr> + </table> + </div> + </div> +`:``} +<!-- Afficher le currentBlockchainTimestamp et le nombre d'identités au dossier complet --> + ${LANG["BLOCKCHAIN_TIME"]} : <b>${timestampToDatetime(currentBlockchainTimestamp)}</b> (#<b>${currentBlockNumber}</b>).<br> + ${LANG["COUNT_READY_MEMBERS"]} : <b>${countMembersWithSigQtyValidCert}</b>.<br> + +<!-- Tableau de toutes les identités en piscine --> <table border="1"> - <tr><td colspan="${nbMaxCertifs+6}" align='center'>${LANG["TABLE_TITLE1"]} <b>${days}</b> ${LANG["TABLE_TITLE2"]}</td></tr> - <!-- Printer les nom des colonnes--> + <tr><td colspan="${nbMaxCertifs+3}" align='center'>${LANG["TABLE_TITLE1"]} <b>${days}</b> ${LANG["TABLE_TITLE2"]}</td></tr> + <!-- Printer les nom des colonnes --> <tr> - <td align='center'>${LANG['COL_1']}</td><td align='center'>${LANG['COL_2']}</td> - <td align='center'>${LANG['COL_3']}</td><td align='center'>${LANG['COL_DISTANCE']}</td> - <td align='center'>${LANG['COL_MEMBERSHIP']}</td><td style="background:#000000">-</td> + <td align='center'>${LANG['IDENTITY']}</td><td align='center'>${LANG['MEMBERSHIP_CASE']}</td> + <td style="background:#000000">-</td> <td align='center' colspan="${nbMaxCertifs}">${sortSig == "Availability" ? LANG['COL_4_WITH_AVAIlABILITY_SORT'] : LANG['COL_4']}</td> @@ -76,42 +165,53 @@ ${(help != 'no') ? ` <!--Printer la ligne--> <tr> - <td align="center" style="background:${(idty.validBlockStamp) ? idty.colorPending=color(idty.expires_on,idtyWindow,250) : idty.colorPending='#FF8000'}"> - <a href="wotex?lg=${MENU_LANG['LG']}${(typeof(help) != 'undefined' && help == 'no') ? '&help=no':''}&to=${idty['uid']}${idty['wotexId']}&pending=on&mode=undefined">${idty['uid'].substring(0, 20)}</a><br> - ${idty['pubkey'].substring(0, 8)} - <br>->${idty.pendingCertifications.length} - </td> - <td align='center' style="background:${idty.colorPending}">${timestampToDatetime(idty['creationTimestamp'])}<br> - #${idty['BlockNumber']} + <td align="center" style="background:${(idty.validBlockStamp) ? ((idty.expires_on > 0) ? idty.colorPending=color(idty.expires_on,idtyWindow,250):'#FF0000'): idty.colorPending='#FF8000'}"> + <a href="wotex?lg=${MENU_LANG['LG']}${(typeof(help) != 'undefined' && help == 'no') ? '&help=no':''}&to=${idty['uid']}${idty['wotexId']}&pending=on&mode=undefined">${idty['uid'].substring(0, 25)}</a> + <br>${idty['pubkey'].substring(0, 16)} + <br>${LANG['EMITTED_ON']} ${timestampToDatetime(idty['creationTimestamp'])} + <br>${LANG['AT_BLOCK']} #${idty['BlockNumber']} + ${(idty.expires_on > 0) ? '<br><b>'+LANG['EXPIRE_ON']+' '+timestampToDatetime(idty.expires_on)+'</b>':``} ${(!idty.validBlockStamp) ? ` <br><font color="DarkRed">[${LANG['INVALID_BLOCKSTAMP']}]</font> `:``} </td> - <td align='center' style="background:${idty.colorPending}"> - ${timestampToDatetime(idty.expires_on)}</td> - <td align='center' style="background:${idty.colorPending}"> - <span style="color: ${idty.isOutdistanced ? 'red' : 'green' }">${idty.isOutdistanced ? LANG['COL_DISTANCE_isOutdistanced'] : LANG['COL_DISTANCE_isNotOutdistanced'] }</span> - </td> - <td align='center' style="background:${idty.colorPending}"> - <span style="color: ${idty.membership ? 'green' : 'red' }">${idty.membership? LANG['COL_MEMBERSHIP_OK'] : LANG['COL_MEMBERSHIP_KO'] }</span> - </td> + ${(idty.expires_on > 0) ? ` + <td align='center' style="background:${idty.colorPending}"> + ${(idty.membership && !idty.detailedDistance.isOutdistanced && idty.nbValidPendingCert >= sigQty) ? `<font color="green">${LANG['OK']}` : `<font color="DarkRed">${LANG['KO']}` }</font><br> + ${LANG['MEMBERSHIP_ASKED']} : ${idty.membership? `<font color="green">${LANG['YES']}` : `<font color="DarkRed">${LANG['NO']}` }</font><br> + ${ (USE_WOTB6) ? ` + <font color="${idty.detailedDistance.isOutdistanced ? 'DarkRed' : 'blue' }">${idty.percentSentriesReached}% (${idty.detailedDistance.nbSuccess}/${idty.detailedDistance.nbSentries}) + <br>${LANG['QUALITY_CERTIFIERS']} : <b>${(idty.validBlockStamp) ? ((idty.detailedDistance.nbSuccess/idty.detailedDistance.nbSentries)/xpercent).toFixed(2):`0.00`}</b></font> + `:` + <font color="${idty.detailedDistance ? 'DarkRed' : 'blue' }">${LANG['DISTANCE']} : ${idty.detailedDistance ? LANG['KO']:LANG['OK'] }</font> + `} + <br>${LANG['CERTIFIERS_COUNT']} : ${(idty.nbValidPendingCert >= sigQty) ? `<font color="green">${idty.nbValidPendingCert}/${sigQty}` : `<font color="DarkRed">${idty.nbValidPendingCert}/${sigQty}` }</font> + </td> + `:` + <td align='center' style="background:#FF0000"><b>${LANG['IDTY_REVOKED']}</b></td> + `} <td style="background:#000000">-</td> <!--printer les certifications--> ${idty.pendingCertifications.map( (cert,j)=> ` ${ (j<nbMaxCertifs) ? ` - <td align="center" style="background:${(cert.validBlockStamp) ? color(cert.timestampExpire,sigWindow,250) : '#FF8000'}"> - ${j==4?'<b>':''} - <a href="wotex?lg=${MENU_LANG['LG']}${(typeof(help) != 'undefined' && help == 'no') ? '&help=no':''}&to=${cert['from']}&pending=on&mode=undefined">${cert['from']}</a> - <br>${timestampToDatetime(cert.timestampExpire)} - <br>#${cert['blockNumber']} - ${ ( !cert.validBlockStamp || cert['timestampWritable'] > currentBlockchainTimestamp ) ? ` - <br><font color="DarkRed">[${ (cert.validBlockStamp) ? timestampToDatetime(cert['timestampWritable']):LANG['INVALID_BLOCKSTAMP']}]</font> - ` : ` - <br><font color='green'>[${LANG['SIG_PERIOD_OK']}]</font> - `} - ${j==4?'</b>':''} - </td> + <td align="center" style="background:${(cert.validBlockStamp) ? color(cert.timestampExpire,sigWindow,250) : '#FF8000'}"> + ${cert.issuerIsSentry ? '<b>':''} + <a href="wotex?lg=${MENU_LANG['LG']}${(typeof(help) != 'undefined' && help == 'no') ? '&help=no':''}&to=${cert['from']}&pending=on&mode=undefined">${cert['from']}</a> + ${cert.issuerIsSentry ? '</b>':''} + ${ (USE_WOTB6) ? ` + <br>${LANG['QUALITY']} : <b>${(typeof(membersQualityExt[cert.from])=='undefined' || !cert.validBlockStamp) ? `0.00`:membersQualityExt[cert.from]}</b> + `:''} + <br>${timestampToDatetime(cert.timestampExpire)} + <br>#${cert['blockNumber']} + ${j==(4+idty.pendingCertifications.length-idty.nbValidPendingCert)?'<b>':''} + ${ ( !cert.validBlockStamp || cert['timestampWritable'] > currentBlockchainTimestamp ) ? ` + <br><font color="DarkRed">[${ (cert.validBlockStamp) ? timestampToDatetime(cert['timestampWritable']):LANG['INVALID_BLOCKSTAMP']}]</font> + ` : ` + <br><font color='green'>[${LANG['SIG_PERIOD_OK']}]</font> + `} + ${j==(4+idty.pendingCertifications.length-idty.nbValidPendingCert)?'</b>':''} + </td> `:''} `).join('')} @@ -120,7 +220,7 @@ ${(help != 'no') ? ` `).join('')} <tr> - <td colspan="${nbMaxCertifs+6}" align="center"> + <td colspan="${nbMaxCertifs+3}" align="center"> ${LANG['LAST_TR1']} : <b>${idtysListFiltered.length}</b> ${LANG['LAST_TR2']}. </td> </tr>