From b4e2130c9bc36c5efc0709b74d904e44d4dffac8 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Oct 2020 12:36:37 +0200 Subject: [PATCH 01/34] Translated using Weblate (Romanian) Currently translated at 42.9% (579 of 1349 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ro/ Translated using Weblate (Romanian) Currently translated at 42.8% (576 of 1345 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ro/ Translated using Weblate (Romanian) Currently translated at 43.0% (575 of 1336 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ro/ Translated using Weblate (Romanian) Currently translated at 42.7% (571 of 1336 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ro/ --- www/common/translations/messages.ro.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/www/common/translations/messages.ro.json b/www/common/translations/messages.ro.json index 08648b21c..7b071c651 100644 --- a/www/common/translations/messages.ro.json +++ b/www/common/translations/messages.ro.json @@ -91,7 +91,7 @@ "history_prev": "Mergi la versiunea trecută", "history_closeTitle": "Închide istoricul", "history_restoreTitle": "Restabilește versiunea selectată a documentului", - "history_restorePrompt": "Ești sigur că vrei să înlocuiești versiunea curentă a documentului cu cea afișată?", + "history_restorePrompt": "Ești sigur că dorești să înlocuiești versiunea curentă a documentului cu cea afișată?", "history_restoreDone": "Document restaurat", "history_version": "Versiune:", "poll_title": "Zero Knowledge Selector Dată", @@ -239,7 +239,7 @@ "settings_pinningNotAvailable": "Pad-urile fixate sunt disponibile doar utilizatorilor înregistrați.", "settings_pinningError": "Ceva nu a funcționat", "settings_usageAmount": "Pad-urile tale fixate ocupă {0}MB", - "settings_logoutEverywhereTitle": "Închide sesiunile remote", + "settings_logoutEverywhereTitle": "Închideți sesiunile la distanță", "settings_logoutEverywhere": "Deloghează-te din toate sesiunile web", "settings_logoutEverywhereConfirm": "Ești sigur? Va trebui să te loghezi, din nou, pe toate device-urile tale.", "upload_serverError": "Eroare de server: fișierele tale nu pot fi încărcate la momentul acesta.", @@ -381,7 +381,7 @@ "filePicker_filter": "Filtrează fișierele după nume", "or": "sau", "tags_title": "Etichete (doar pentru tine)", - "tags_add": "Actualizeaza cuvintele cheie ale selectiei", + "tags_add": "Actualizează cuvintele cheie ale selecției", "tags_notShared": "Etichetele tale nu sunt împărțite cu alți utilizatori", "tags_duplicate": "Duplică eticheta: {0}", "tags_noentry": "Nu poți eticheta un pad șters", @@ -549,18 +549,18 @@ "settings_resetTipsAction": "Resetează", "settings_thumbnails": "Miniaturi", "settings_padWidth": "Lățimea maximă a editorului", - "settings_codeFontSize": "Dimensiunea font-ului in editorul de cod", + "settings_codeFontSize": "Dimensiunea font-ului în editorul de cod", "settings_codeUseTabs": "Indentarea prin etichete (în loc de spații)", "settings_codeIndentation": "Indentarea editorului de cod (spații)", "settings_driveDuplicateLabel": "Ascunde duplicatele", - "settings_driveDuplicateHint": "Când deplasezi documentele tale într-un dosar comun, o copie este păstrată în CryptDrive-ul tău pentru asigura păstrarea drepturilor tale de control asupra lui. Poți ascunde fișierele duplicate. Doar versiunea partajata va fi vizibilă, dacă nu este ștearsă, în acest ultim caz originalul va fi afișat în locația precedentă.", + "settings_driveDuplicateHint": "Când deplasați documentele dvs. într-un dosar comun, o copie este păstrată în CryptDrive-ul personal pentru a asigura păstrarea drepturilor dvs. de control asupra lui. Aveți posibilitatea de a ascunde fișierele duplicate. Doar versiunea partajată va fi vizibilă, dacă nu este ștearsă, în acest ultim caz originalul va fi afișat în locația precedentă.", "settings_driveDuplicateTitle": "Documentele tale duplicate", - "register_emailWarning3": "Dacă înțelegeți aceste aspecte și dorești totuși să utilizezi adresa ta de e-mail ca și nume de utilizator, apasă OK.", + "register_emailWarning3": "Dacă înțelegeți aceste aspecte și doriți totuși să utilizați adresa de e-mail ca și nume de utilizator, apăsați OK.", "padNotPinnedVariable": "Acest document va expira dupa {4} zile de inactivitate, {0}autentifică-te{1} sau {2}creează-ți un cont{3} pentru a-l pastra.", "settings_logoutEverywhereButton": "Deconectează-te", "settings_deleted": "Contul tău de utilizator este șters. Apasă OK pentru a reveni la pagina principală.", "settings_deleteConfirm": "Dacă apeși OK, contul tău va fi șters permanent. Ești sigur?", - "settings_deleteModal": "Furnizează aceste informații administratorului CryptPad-ului tău astfel încât acestea să fie șterse de pe server.", + "settings_deleteModal": "Furnizați informațiile următoare administratorului CryptPad-ului dvs. astfel încât acestea să fie șterse de pe server.", "settings_deleteButton": "Șterge contul tău", "settings_deleteHint": "Ștergerea contului este permanentă. Cryptdrive-ul tău și lista ta de documente vor fi șterse de pe server. Restul documentelor vor fi șterse în timp de 90 de zile dacă nimeni nu le stochează în CryptDrive-ul său.", "settings_deleteTitle": "Ștergere cont", @@ -569,8 +569,8 @@ "settings_autostoreNo": "Manual (nu mai întreba)", "settings_autostoreYes": "Automat", "settings_autostoreHint": "Automat Toate documentele accesate sunt stocate în CryptDrive-ul dumneavoastră.
Manual (întreabă întotdeauna) Dacă nu ai stocat încă un document, vei fi întrebat dacă dorești să îl stochezi în Cryptdrive-ul tău.
Manual (nu mai întreba) Documentele nu sunt stocate automat în Cryptpad-ul tău. Opțiunea de a le stoca ulterior va fi ascunsă.", - "settings_autostoreTitle": "Capacitatea de stocare a documentului în CryptDrive", - "register_emailWarning2": "Nu vei putea reseta parola utilizând adresa ta de e-mail.", + "settings_autostoreTitle": "Capacitatea de stocare a documentelor în CryptDrive", + "register_emailWarning2": "Nu veți putea să vă resetați parola folosind adresa de e-mail, așa cum puteți cu multe alte servicii.", "register_emailWarning1": "Poți proceda astfel dacă doreşti, însă nici o notificare nu va fi transmisă server-ului nostru.", "register_emailWarning0": "Se pare că ai adăugat adresa ta de e-mail în loc de numele tău de utilizator.", "fc_expandAll": "Extinde", From 4de7c526b2959ee52ce2515f764161af6997cc19 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Oct 2020 12:36:37 +0200 Subject: [PATCH 02/34] Translated using Weblate (English) Currently translated at 100.0% (1361 of 1361 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1360 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1360 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1359 of 1359 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1358 of 1358 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1357 of 1357 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1356 of 1356 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1355 of 1355 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1354 of 1354 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1353 of 1353 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1352 of 1352 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1351 of 1351 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1350 of 1350 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1349 of 1349 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1348 of 1348 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1347 of 1347 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1346 of 1346 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1345 of 1345 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1344 of 1344 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1343 of 1343 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1342 of 1342 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1341 of 1341 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1340 of 1340 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1339 of 1339 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1338 of 1338 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1337 of 1337 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ --- www/common/translations/messages.json | 29 +++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 0cace79d1..f00279c0a 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -452,7 +452,7 @@ "settings_backupHint": "Backup or restore all your CryptDrive's content. It won't contain the content of your pads, just the keys to access them.", "settings_backup": "Backup", "settings_restore": "Restore", - "settings_backupHint2": "Download the current content of all your pads. Pads will be downloaded in an readable format if such a format is available.", + "settings_backupHint2": "Download all the documents in your drive. Documents will be downloaded in formats readable by other applications when such a format is available. When such a format is not available, documents will be downloaded in a format readable by CryptPad.", "settings_backup2": "Download my CryptDrive", "settings_backup2Confirm": "This will download all the pads and files from your CryptDrive. If you want to continue, pick a name and press OK", "settings_exportTitle": "Export your CryptDrive", @@ -1432,5 +1432,30 @@ "oo_version_latest": "Latest", "oo_version": "Version: ", "snapshots_ooPickVersion": "You must select a version before creating a snapshot", - "snapshot_error_exists": "There is already a snapshot of this version" + "snapshot_error_exists": "There is already a snapshot of this version", + "snapshots_notFound": "This snapshot no longer exists because the history of the document has been deleted.", + "snapshots_cantMake": "The snapshot could not be created. You are disconnected.", + "admin_registrationHint": "Do not allow any new users to register", + "admin_registrationTitle": "Close registration", + "admin_registrationButton": "Close", + "admin_registrationAllow": "Open", + "admin_defaultlimitHint": "Maximum storage limit for CryptDrives (users and teams) when no custom rule is applied", + "admin_defaultlimitTitle": "Storage limit (MB)", + "admin_setlimitButton": "Set limit", + "admin_limit": "Current limit: {0}", + "admin_getlimitsHint": "List all the custom storage limits applied to your instance.", + "admin_getlimitsTitle": "Custom limits", + "admin_limitPlan": "Plan: {0}", + "admin_limitNote": "Note: {0}", + "admin_setlimitHint": "Set custom limits for users by using their public key. You can update or remove an existing limit.", + "admin_setlimitTitle": "Apply a custom limit", + "admin_limitMB": "Limit (in MB)", + "admin_limitSetNote": "Note", + "admin_invalKey": "Invalid public key", + "admin_invalLimit": "Invalid limit value", + "admin_cat_quota": "User storage", + "team_exportTitle": "Download team drive", + "team_exportHint": "Download all the documents in this team's drive. Documents will be downloaded in formats readable by other applications when such a format is available. When such a format is not available, documents will be downloaded in a format readable by CryptPad.", + "team_exportButton": "Download", + "admin_limitUser": "User's public key" } From 780665f7f70dcc138075886b4dd11118942d2fc0 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Oct 2020 12:36:38 +0200 Subject: [PATCH 03/34] Translated using Weblate (French) Currently translated at 100.0% (1361 of 1361 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ Translated using Weblate (French) Currently translated at 100.0% (1360 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ --- www/common/translations/messages.fr.json | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index cad4c2ebe..d5fc68f97 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -439,7 +439,7 @@ "settings_backupHint": "Créer ou restaurer une sauvegarde de votre CryptDrive. Cette sauvegarde ne contient pas le contenu de vos pads mais uniquement les clés qui permettent d'y accéder.", "settings_backup": "Sauvegarder", "settings_restore": "Restaurer", - "settings_backupHint2": "Télécharger le contenu actuel de tous vos pads. Ceux-ci seront téléchargés dans un format lisible si un tel format est disponible.", + "settings_backupHint2": "Téléchargez tous les documents dans votre drive. Les documents seront téléchargés dans un format lisible par d'autres applications si un tel format est disponible. Lorsqu'un tel format n'est pas disponible, les documents seront téléchargés dans un format lisible par CryptPad.", "settings_backup2": "Télécharger mon CryptDrive", "settings_backup2Confirm": "Vous allez télécharger tous les pads de votre CryptDrive. Si vous souhaitez continuer, choisissez un nom et appuyez sur OK.", "settings_exportTitle": "Téléchargement de votre CryptDrive", @@ -1432,5 +1432,30 @@ "snapshot_error_exists": "Il existe déja une capture de cette version", "snapshots_ooPickVersion": "Vous devez sélectionner une version avant de faire une capture", "oo_version": "Version : ", - "oo_version_latest": "Dernière" + "oo_version_latest": "Dernière", + "team_exportButton": "Télécharger", + "team_exportHint": "Téléchargez tous les documents dans le drive de cette équipe. Les documents seront téléchargés dans des formats lisibles par d'autres applications si un tel format est disponible. Lorsqu'un tel format n'est pas disponible, les documents seront téléchargés dans un format lisible par CryptPad.", + "team_exportTitle": "Télécharger le drive de l'équipe", + "admin_cat_quota": "Stockage utilisateurs", + "admin_invalLimit": "Limite invalide", + "admin_invalKey": "Clé publique invalide", + "admin_limitSetNote": "Note", + "admin_limitMB": "Limite (en Mo)", + "admin_setlimitTitle": "Appliquer une limite Spéciale", + "admin_setlimitHint": "Créer une limite spéciale pour un utilisateur en utilisant sa clé publique. Modifier ou supprimer une limite existante.", + "admin_limitNote": "Note : {0}", + "admin_limitPlan": "Plan : {0}", + "admin_getlimitsTitle": "Limites Spéciales", + "admin_getlimitsHint": "Liste des limites spéciales appliquées sur votre instance.", + "admin_limit": "Limite actuelle : {0}", + "admin_setlimitButton": "Fixer la limite", + "admin_defaultlimitTitle": "Limite de stockage (Mo)", + "admin_defaultlimitHint": "Limite de stockage maximum pour le CryptDrive (utilisateurs et équipes) si aucune règle spéciale n'est appliquée", + "admin_registrationAllow": "Ouvrir", + "admin_registrationButton": "Fermer", + "admin_registrationTitle": "Fermer l'enregistrement", + "admin_registrationHint": "Ne pas autoriser l'enregistrement de nouveaux utilisateurs", + "snapshots_cantMake": "La capture n'a pas pu être effectuée. Vous êtes déconnecté.", + "snapshots_notFound": "Cette capture n'existe plus car l'historique du document a été supprimé.", + "admin_limitUser": "Clé publique de l'utilisateur" } From f82bcabee28f5d6ebe10a0fb1d2e5ce96467e02d Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Oct 2020 12:36:38 +0200 Subject: [PATCH 04/34] Translated using Weblate (German) Currently translated at 100.0% (1336 of 1336 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ Translated using Weblate (German) Currently translated at 99.9% (1335 of 1336 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index fa755524a..52a71ecac 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1429,5 +1429,8 @@ "history_fastNext": "Nächste Sitzung", "share_versionHash": "Du bist gerade dabei, die ausgewählte Version im Verlauf des Dokuments schreibgeschützt zu teilen. Dies beinhaltet Lesezugriff auf alle Versionen des Dokuments.", "oo_version": "Version: ", - "snapshots_delete": "Löschen" + "snapshots_delete": "Löschen", + "snapshots_ooPickVersion": "Du musst zunächst eine Version zum Speichern auswählen", + "snapshot_error_exists": "Diese Version wurde bereits gespeichert", + "oo_version_latest": "Aktuelle" } From 84297065688120c7560346d1d10041c24ac86fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Fri, 9 Oct 2020 11:50:47 +0100 Subject: [PATCH 05/34] Remove unused snapshot key --- www/common/translations/messages.de.json | 1 - www/common/translations/messages.fr.json | 1 - www/common/translations/messages.json | 1 - 3 files changed, 3 deletions(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 52a71ecac..27372bd84 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1409,7 +1409,6 @@ "history_userNext": "Nächster Autor", "history_shareTitle": "Link zu dieser Version teilen", "oo_deletedVersion": "Diese Version ist nicht mehr im Verlauf vorhanden.", - "snapshots_cantRestore": "Wiederherstellung ist fehlgeschlagen. Die Verbindung wurde getrennt.", "snapshots_close": "Schließen", "snapshots_restore": "Wiederherstellen", "snapshots_open": "Öffnen", diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index d5fc68f97..41f352247 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1406,7 +1406,6 @@ "settings_kanbanTagsHint": "Sélectionnez le fonctionnement du filtre lorsque vous sélectionnez plusieurs mots-clés : afficher uniquement les cartes contenant tous les mots-clés sélectionnés (ET) ou afficher les cartes contenant l'un des mots-clés sélectionnés (OU)", "settings_kanbanTagsTitle": "Filtre par mot-clé", "oo_deletedVersion": "Cette version n'existe plus dans l'historique.", - "snapshots_cantRestore": "La restauration a échoué. Vous êtes déconnecté.", "snapshots_close": "Fermer", "snapshots_restore": "Restaurer", "snapshots_open": "Ouvrir", diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index f00279c0a..754bcfdf1 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1426,7 +1426,6 @@ "snapshots_open": "Open", "snapshots_restore": "Restore", "snapshots_close": "Close", - "snapshots_cantRestore": "Restoration failed. You are disconnected.", "oo_deletedVersion": "This version no longer exists in the history.", "snapshots_delete": "Delete", "oo_version_latest": "Latest", From 03e390f6bda778ca7656cf8b1b77206fa030b9dc Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Oct 2020 18:30:29 +0200 Subject: [PATCH 06/34] Translated using Weblate (Romanian) Currently translated at 42.6% (580 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ro/ --- www/common/translations/messages.ro.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/translations/messages.ro.json b/www/common/translations/messages.ro.json index 7b071c651..268c345b3 100644 --- a/www/common/translations/messages.ro.json +++ b/www/common/translations/messages.ro.json @@ -582,7 +582,7 @@ "fc_collapseAll": "Restrânge", "settings_creationSkip": "Omiteți ecranul de creare a fișierului", "settings_padSpellcheckLabel": "Activați verificarea ortografică", - "settings_padSpellcheckHint": "Această opțiune vă permite să activați verificarea ortografică în editorul de text. Erorile de ortografie vor fi subliniate în roșu și va trebui să țineți apăsată tasta Ctrl sau tasta Meta în timp ce faceți clic dreapta pentru a vedea opțiunile corecte.", + "settings_padSpellcheckHint": "Această opțiune vă permite să activați verificarea ortografică în editorul de text. Erorile de ortografie vor fi subliniate cu roșu și va trebui să țineți apăsată tasta Ctrl sau tasta Meta în timp ce faceți clic dreapta pentru a vedea opțiunile corecte.", "settings_padSpellcheckTitle": "Verificare a ortografiei", "settings_padWidthLabel": "Reduceți lățimea editorului", "settings_padWidthHint": "Comutați între modul pagină (implicit) care limitează lățimea editorului de text și utilizați întreaga lățime a ecranului.", From 4d34ba7bf1cd09093ac4124e0e66193923fe4c45 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Oct 2020 18:30:29 +0200 Subject: [PATCH 07/34] Translated using Weblate (German) Currently translated at 100.0% (1360 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ Translated using Weblate (German) Currently translated at 98.9% (1346 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ Translated using Weblate (German) Currently translated at 98.7% (1343 of 1360 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 27372bd84..f7af20555 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -432,7 +432,7 @@ "settings_backupHint": "Erstelle ein Backup deiner Daten im CryptDrive oder stelle die Daten wieder her. Es wird nicht den Inhalt der Pads beinhalten, sondern nur die Schlüssel für den Zugriff.", "settings_backup": "Backup", "settings_restore": "Wiederherstellen", - "settings_backupHint2": "Lade den Inhalt aller Pads herunter. Die Pads werden in einem lesbaren Format heruntergeladen, wenn dies möglich ist.", + "settings_backupHint2": "Lade alle Dokumente in deinem Drive herunter. Die Dokumente werden in einem für andere Anwendungen lesbaren Format heruntergeladen, sofern dies möglich ist. Sollte dies nicht möglich sein, werden die Dokumente in einem für CryptPad lesbaren Format heruntergeladen.", "settings_backup2": "Mein CryptDrive herunterladen", "settings_backup2Confirm": "Dies wird alle Pads und Dateien von deinem CryptDrive herunterladen. Wenn du fortfahren möchtest, wähle einen Namen und klicke auf OK", "settings_exportTitle": "Dein CryptDrive exportieren", @@ -1431,5 +1431,30 @@ "snapshots_delete": "Löschen", "snapshots_ooPickVersion": "Du musst zunächst eine Version zum Speichern auswählen", "snapshot_error_exists": "Diese Version wurde bereits gespeichert", - "oo_version_latest": "Aktuelle" + "oo_version_latest": "Aktuelle", + "team_exportButton": "Herunterladen", + "team_exportHint": "Lade alle Dokumente im Drive des Teams herunter. Die Dokumente werden in einem für andere Anwendungen lesbaren Format heruntergeladen, sofern dies möglich ist. Sollte dies nicht möglich sein, werden die Dokumente in einem für CryptPad lesbaren Format heruntergeladen.", + "admin_limitUser": "Öffentlicher Schlüssel des Benutzers", + "team_exportTitle": "Drive des Teams herunterladen", + "admin_invalKey": "Öffentlicher Schlüssel ist ungültig", + "admin_limitMB": "Begrenzung (in MB)", + "admin_limitPlan": "Abo: {0}", + "admin_defaultlimitTitle": "Speicherplatzbegrenzung (MB)", + "admin_limitNote": "Notiz: {0}", + "admin_getlimitsHint": "Auflistung aller Speicherplatzbegrenzungen auf deiner Instanz.", + "admin_setlimitButton": "Begrenzung eintragen", + "admin_registrationTitle": "Registrierung deaktivieren", + "admin_registrationAllow": "Aktivieren", + "admin_registrationButton": "Deaktivieren", + "snapshots_notFound": "Diese Version ist nicht mehr verfügbar, da der Verlauf des Dokuments gelöscht wurde.", + "snapshots_cantMake": "Die Version konnte nicht gespeichert. Die Verbindung wurde getrennt.", + "admin_getlimitsTitle": "Individuelle Regeln", + "admin_limit": "Aktuelle Begrenzung: {0}", + "admin_defaultlimitHint": "Speicherplatzbegrenzung für CryptDrives (Benutzer und Teams), sofern keine individuelle Regel festgelegt wurde", + "admin_registrationHint": "Erlaube keine neuen Benutzerregistrierungen", + "admin_cat_quota": "Speicher für Benutzer", + "admin_invalLimit": "Wert für Speicherplatzbegrenzung ist ungültig", + "admin_limitSetNote": "Notiz", + "admin_setlimitTitle": "Individuelle Begrenzung festlegen", + "admin_setlimitHint": "Lege individuelle Begrenzungen für Benutzer anhand ihrer öffentlichen Schlüssel fest. Du kannst bestehende Regeln aktualisieren oder entfernen." } From 9d307f2cde17103c252b13b622a10c2b3fe6b765 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 14:19:13 +0530 Subject: [PATCH 08/34] update changelog for 3.23.0 --- CHANGELOG.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5e4005f..500f385d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,70 @@ -# X (3.23.0) +# XerusDaamsi (3.23.0) ## Goals +We plan to produce an updated installation guide for CryptPad instance administrators to coincide with the release of our 4.0.0 release. As we get closer to the end of the alphabet we're working to simplify the process of configuring instances. This release features several new admin panel features intended to supersede the usage of the server configuration file and provide the ability to modify instance settings at runtime. + +We also spent some time finalizing some major improvements to the history mode which is available in most of our document editors. More on that in the _Features_ section. + ## Update notes +This release requires introduces some behaviour which may require manual configuration on the part of the administrator. Read the following sections carefully or proceed at your own risk! + +### Automatic database maintenance + +When a user employs the _destroy_ functionality to make a pad unavailable it isn't typically deleted. Instead it is made unavailable by moving it into the server's archive directory. Archived files are intended to be removed after another configurable amount of time (`archiveRetentionTime` in your config file). The deletion of old files from your archive is handled by `evict-inactive.js`, which can be found in `cryptpad/scripts/`. Up until now this script needed to be run manually (typically as a cron job) with `node ./scripts/evict-inactive.js`. Since this isn't widely known we decided to integrate it directly into the server by automatically running the script once per day. + +The same _eviction_ process is also responsible for scanning your server's database for inactive documents (defined as those which haven't been accessed in a number of days specified in your config under `inactiveTime`). Such inactive documents are archived unless they have been stored within a registered users drive. Starting with this release we have added the ability to specify the number of days before an account will be considered inactive (`accountRetentionTime`). This will take into account whether they added any new documents to their drive, or whether any of the existing documents were accessed or modified by other users. + +If you prefer to run the eviction script manually you can disable its integration into the server by adding `disableIntegratedEviction: true` to your config file. An example is given in `cryptpad/config/config.example.js`. If you want this process to run manually you may set the same value to `false`, or comment it out if you prefer. Likewise, if you prefer to never remove accounts and their data due to account inactivity, you may also comment it out. + +If you haven't been manually running the eviction scripts we recommend that you carefully review all of the values mentioned above to ensure that you will not be surprised by the sudden and unintended removal of any data. As a reminder, they are: + +* `inactiveTime` (number of days before a file is considered inactive) +* `archiveRetentionTime` (number of days that an archived file will be retained before it is permanently deleted) +* `accountRetentionTime` (number of days of inactivity before an account is considered inactive and eligible for deletion) +* `disableIntegratedEviction` (true if you prefer to run the eviction process manually or not at all, false or nothing if you want the server to handle eviction) + +### NGINX Configuration update + +After some testing on our part we've included an update to the example NGINX config file available in `cryptpad/docs/example.nginx.conf` which will enable a relatively new browser API which is required for XLSX export from our sheet editor. The relevant lines can be found beneath the comment `# Enable SharedArrayBuffer in Firefox (for .xlsx export)`. + +### Quota management + +Up until now the configuration file found in `cryptpad/config/config.js` has been the primary means of configuring a CryptPad instance. Unfortunately, as the server's behaviour becomes increasingly complex due to interest in a broad variety of use-cases this config file tends to grow. The kinds of questions that administrators ask via email, GitHub issues, and via our Matrix channel often suggest that admins haven't read through the comments in these files. Additionally, changes to the server's configuration can only be applied by restarting the server, which is increasingly disruptive as the service becomes more popular. To address these issues we've decided to start improving the instance admin panel such that it becomes the predominant means of modifying common server behaviours. + +We've started by making it possible to update storage settings from the _Quotas_ section of the admin panel. Administrators can now update the default storage limit for users registered on the instance from the default quota of 50MB. It's also possible to allocate storage limits to particular users on the basis of their _Public Signing Key_, which can be found at the top of the _Accounts_ section on the settings page. + +Storage limits configured in this way will supercede those set via the server's config file, such that any modifications to a quota already set in the file will be ignored once you have modified or removed that user's quota via the admin panel. Admins are also able to view the parameters of all existing custom quotas loaded from either source. + +### How to update + +Once you've reviewed these settings and you're ready to update from 3.22.0 to 3.23.0: + +1. Modify your server's NGINX config file to include the new headers enabling XLSX export +2. Stop CryptPad's nodejs server +3. Get the latest platform code with git +4. Install client-side dependencies with `bower update` +5. Install server-side dependencies with `npm install` +6. Reload NGINX with `service nginx reload` to apply its config changes +7. Restart the CryptPad API server + ## Features -* responsive modals - * share - * access - * opencollective alert - * accessibility in alertify -* remove inactive users + +* As mentioned in the update notes, this release features a server update which will enable XLSX export from our sheet editor in Firefox. XLSX files are generated entirely on the client, so all information will remain confidential, it only required a server update to enable a feature in Firefox which is required to perform the conversion. +* We've also made some considerable improvements to the _history mode_ available in most of our document editors. We now display a more detailed timeline of changes according to who was present in the session, and group contiguous modifications made by a single user. Our intent is to provide an overview of the document's history which exposes the details which are most relevant to humans, rather than only allowing users to step through each individual change. +* Another change which is related to our history mode improvements is support for "version links", which allow you to link to a specific historical version of a document while you scroll through the timeline of its modifications. You can also create _named snapshots_ of documents which will subsequently be displayed as highlights in the document's timeline. +* Up until now we did not support _history mode_ for spreadsheets because our sheet integration is sufficiently different from our other editors that our existing history system could not be reused. That's still the case, but we've invested some time into creating a parallel history system with a slightly different user interface tailored to the display of sheet history. +* Team owners and admins can now export team drives in the same manner as their own personal drives. The button to begin a full-drive export is available on the team's administration page. +* During the summer we experimented with the idea of providing preview rendering options for more of the languages available in the code editor. We were particularly interested in providing LaTeX rendering in addition to Markdown. Unfortunately, it turned out to be a more complex feature than we have time for at the moment. In the process, however, we made it easier to integrate other rendering modes in addition to markdown. For the moment we've only added a simple rendering mode for displaying mixed HTML, but we'll consider using this framework to offer more options in the future. +* While it might not be very noticeable depending on the size of the screen you use to view CryptPad we've spent some time making more of our interface responsive for mobile devices. You may notice this in particular on the modal menus used for sharing, setting access control parameters, and otherwise displaying alerts. In the process we also reviewed the code responsible for producing these menus and addressed many of the accessibility issues that were raised during the third-party accessibility audit performed several months ago. These updates add support for screen-readers throughout the platform, though there is still much to be done before this support can be considered comprehensive. These issues were raised as a part of the third-party accessibility audit performed on behalf of NLnet foundation (one of our major sponsors) as a part of their NGI Zero Privacy-Enhancing Technologies fund. +* The _share modal_ from which users can generate shareable links already detects whether you have added any contacts on the platform and suggests how you can connect with them if you have not. We added this functionality some time late in 2019 since the same modal allowed users share documents directly with contacts and this mode became the subject of many support tickets. As it turns out, many users are now discovering _contact_ functionality via the _access modal_ through which you can add users to a document's allow list or delegate ownership. Since this has become a similar point of confusion we've added the same hints to make it a natural entry-point into CryptPad's social functionality. ## Bug fixes +* We noticed that it was not possible for document owners to remove the extraneous history of old documents when those documents were protected by an _allow list_. This was due to the usage of an incorrect method for loading the document's metadata, leading to a false negative when testing if the user in question had sufficient access rights. +* We also discovered an annoying bug in our filesystem storage APIs which caused the database adaptor to prevent scripts from terminating until several timeouts had finished running. These timeouts are now cancelled automatically so that the scripts stop running in a timely manner. # WoollyMammoth (3.22.0) From 1ca011a35d5cab5e9c856528c36b318a3ea4ef3d Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 14:49:50 +0530 Subject: [PATCH 09/34] update changelog's description of quotas to match the translated text --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 500f385d6..fd5cad699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ After some testing on our part we've included an update to the example NGINX con Up until now the configuration file found in `cryptpad/config/config.js` has been the primary means of configuring a CryptPad instance. Unfortunately, as the server's behaviour becomes increasingly complex due to interest in a broad variety of use-cases this config file tends to grow. The kinds of questions that administrators ask via email, GitHub issues, and via our Matrix channel often suggest that admins haven't read through the comments in these files. Additionally, changes to the server's configuration can only be applied by restarting the server, which is increasingly disruptive as the service becomes more popular. To address these issues we've decided to start improving the instance admin panel such that it becomes the predominant means of modifying common server behaviours. -We've started by making it possible to update storage settings from the _Quotas_ section of the admin panel. Administrators can now update the default storage limit for users registered on the instance from the default quota of 50MB. It's also possible to allocate storage limits to particular users on the basis of their _Public Signing Key_, which can be found at the top of the _Accounts_ section on the settings page. +We've started by making it possible to update storage settings from the _User storage_ section of the admin panel. Administrators can now update the default storage limit for users registered on the instance from the default quota of 50MB. It's also possible to allocate storage limits to particular users on the basis of their _Public Signing Key_, which can be found at the top of the _Accounts_ section on the settings page. Storage limits configured in this way will supercede those set via the server's config file, such that any modifications to a quota already set in the file will be ignored once you have modified or removed that user's quota via the admin panel. Admins are also able to view the parameters of all existing custom quotas loaded from either source. From dcc463dd67b339b3b8cdf24ee37c92f0491ee587 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 14:50:13 +0530 Subject: [PATCH 10/34] remove hardcoded translations which were applied via weblate --- www/admin/inner.js | 21 --------------------- www/teams/inner.js | 3 --- 2 files changed, 24 deletions(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index d5e1a2e5b..941b1d3fd 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -107,10 +107,6 @@ define([ }); return $div; }; - Messages.admin_registrationHint = "Restrict registration..."; // XXX - Messages.admin_registrationTitle = "Restrict registration"; // XXX - Messages.admin_registrationButton = "Restrict"; // XXX - Messages.admin_registrationAllow = "Allow"; // XXX create['registration'] = function () { var key = 'registration'; var $div = makeBlock(key, true); @@ -153,11 +149,6 @@ define([ : Messages._getKey('formattedMB', [value]); }; - Messages.admin_defaultlimitTitle = "Storage limit"; // XXX - Messages.admin_defaultlimitHint = "Maximum storage limit per user drive and team drive when no custom rule is applied"; // XXX - Messages.admin_defaultlimitTitle = "New default limit (MB)"; // XXX - Messages.admin_setlimitButton = "Set limit"; // XXX - Messages.admin_limit = "Current default limit: {0}"; create['defaultlimit'] = function () { var key = 'defaultlimit'; var $div = makeBlock(key); @@ -198,10 +189,6 @@ define([ }); return $div; }; - Messages.admin_getlimitsHint = "List all the custom storage limits applied to your instance."; // XXX - Messages.admin_getlimitsTitle = "Custom limits"; // XXX - Messages.admin_limitPlan = "Plan: {0}"; - Messages.admin_limitNote = "Note: {0}"; create['getlimits'] = function () { var key = 'getlimits'; var $div = makeBlock(key); @@ -255,13 +242,6 @@ define([ return $div; }; - Messages.admin_setlimitHint = "Get the public key of a user and give them a custom storage limit. You can update an existing limit or remove the custom limit."; // XXX - Messages.admin_setlimitTitle = "Apply a custom limit"; // XXX - Messages.admin_limitUser = "User's public key"; // XXX - Messages.admin_limitMB = "Limit (in MB)"; // XXX - Messages.admin_limitSetNote = "Custom Note"; // XXX - Messages.admin_invalKey = "Invalid public key"; - Messages.admin_invalLimit = "Invalid limit value"; create['setlimit'] = function () { var key = 'setlimit'; var $div = makeBlock(key); @@ -620,7 +600,6 @@ define([ active = active.split('-')[0]; } common.setHash(active); - Messages.admin_cat_quota = 'Quotas'; // XXX Object.keys(categories).forEach(function (key) { var $category = $('
', {'class': 'cp-sidebarlayout-category'}).appendTo($categories); if (key === 'general') { $category.append($('', {'class': 'fa fa-user-o'})); } diff --git a/www/teams/inner.js b/www/teams/inner.js index 2d3915810..650117c0b 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1031,9 +1031,6 @@ define([ }); }, true); - Messages.team_exportTitle = "Download team drive"; // XXX - Messages.team_exportHint = "Download all the pads frm this team's drive in a readable format (when available)."; // XXX - Messages.team_exportButton = "Download"; // XXX makeBlock('export', function (common, cb) { // Backup all the pads var sframeChan = common.getSframeChannel(); From c99de4ffc286a2d8e0b0f906e2fc68e02a3ad3e0 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 14:56:09 +0530 Subject: [PATCH 11/34] expose the canonical representation of adminKeys via /api/config --- server.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 5a97c971f..ba0e9270f 100644 --- a/server.js +++ b/server.js @@ -200,12 +200,13 @@ app.use(/^\/[^\/]*$/, Express.static('customize.dist')); var admins = []; try { admins = (config.adminKeys || []).map(function (k) { - // XXX is there any reason not to use Keys.canonicalize ? + var unsafeKey = Keys.canonicalize(k); // return each admin's "unsafeKey" // this might throw and invalidate all the other admin's keys // but we want to get the admin's attention anyway. // breaking everything is a good way to accomplish that. - return Keys.parseUser(k).pubkey; + if (!unsafeKey) { throw new Error(); } + return unsafeKey; }); } catch (e) { console.error("Can't parse admin keys"); } From ad9ed6f28841c9da085762aceb1d5104c17b0c6f Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 14:57:54 +0530 Subject: [PATCH 12/34] hide the 'restrict registration' admin interface until it's complete --- www/admin/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index 941b1d3fd..93e00c3c9 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -43,7 +43,7 @@ define([ 'general': [ 'cp-admin-flush-cache', 'cp-admin-update-limit', - 'cp-admin-registration' + // 'cp-admin-registration', ], 'quota': [ 'cp-admin-defaultlimit', From 6a1ba275ef555259beba72b900590bf391f4f8f7 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 15:01:48 +0530 Subject: [PATCH 13/34] update example config comments --- config/config.example.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config/config.example.js b/config/config.example.js index 525102caa..f9702b6d0 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -202,6 +202,15 @@ module.exports = { */ //accountRetentionTime: 365, + /* Starting with CryptPad 3.23.0, the server automatically runs + * the script responsible for removing inactive data according to + * your configured definition of inactivity. Set this value to `true` + * if you prefer not to remove inactive data, or if you prefer to + * do so manually using `scripts/evict-inactive.js`. + */ + //disableIntegratedEviction: true, + + /* Max Upload Size (bytes) * this sets the maximum size of any one file uploaded to the server. * anything larger than this size will be rejected From 865dd3ab6773d6a4a2d8292a7b50a75f2d72709c Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 15:22:04 +0530 Subject: [PATCH 14/34] fix a small typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5cad699..93d01b8e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ We also spent some time finalizing some major improvements to the history mode w ## Update notes -This release requires introduces some behaviour which may require manual configuration on the part of the administrator. Read the following sections carefully or proceed at your own risk! +This release introduces some behaviour which may require manual configuration on the part of the administrator. Read the following sections carefully or proceed at your own risk! ### Automatic database maintenance From 6a51e4e88e67cbfd1a2e339042505ed71e9421d1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 15:39:28 +0530 Subject: [PATCH 15/34] reword notes about accessibility improvements --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93d01b8e5..cfee490ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,8 @@ Once you've reviewed these settings and you're ready to update from 3.22.0 to 3. * Up until now we did not support _history mode_ for spreadsheets because our sheet integration is sufficiently different from our other editors that our existing history system could not be reused. That's still the case, but we've invested some time into creating a parallel history system with a slightly different user interface tailored to the display of sheet history. * Team owners and admins can now export team drives in the same manner as their own personal drives. The button to begin a full-drive export is available on the team's administration page. * During the summer we experimented with the idea of providing preview rendering options for more of the languages available in the code editor. We were particularly interested in providing LaTeX rendering in addition to Markdown. Unfortunately, it turned out to be a more complex feature than we have time for at the moment. In the process, however, we made it easier to integrate other rendering modes in addition to markdown. For the moment we've only added a simple rendering mode for displaying mixed HTML, but we'll consider using this framework to offer more options in the future. -* While it might not be very noticeable depending on the size of the screen you use to view CryptPad we've spent some time making more of our interface responsive for mobile devices. You may notice this in particular on the modal menus used for sharing, setting access control parameters, and otherwise displaying alerts. In the process we also reviewed the code responsible for producing these menus and addressed many of the accessibility issues that were raised during the third-party accessibility audit performed several months ago. These updates add support for screen-readers throughout the platform, though there is still much to be done before this support can be considered comprehensive. These issues were raised as a part of the third-party accessibility audit performed on behalf of NLnet foundation (one of our major sponsors) as a part of their NGI Zero Privacy-Enhancing Technologies fund. +* While it might not be very noticeable depending on the size of the screen you use to view CryptPad we've spent some time making more of our interface responsive for mobile devices. You may notice this in particular on the modal menus used for sharing, setting access control parameters, and otherwise displaying alerts. +* We've also begun improving support for screen-readers by adding the required HTML attributes to input fields and related markup. We'll continue to make incremental improvements regarding this and other accessibility issues that were raised during the third-party accessibility audit performed several months ago. This audit was performed on behalf of NLnet foundation (one of our major sponsors) as a part of their NGI Zero Privacy-Enhancing Technologies fund. * The _share modal_ from which users can generate shareable links already detects whether you have added any contacts on the platform and suggests how you can connect with them if you have not. We added this functionality some time late in 2019 since the same modal allowed users share documents directly with contacts and this mode became the subject of many support tickets. As it turns out, many users are now discovering _contact_ functionality via the _access modal_ through which you can add users to a document's allow list or delegate ownership. Since this has become a similar point of confusion we've added the same hints to make it a natural entry-point into CryptPad's social functionality. ## Bug fixes From 369c92c01daa1d80daf4abf7bfe5a5bd0cb00793 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 17:39:53 +0530 Subject: [PATCH 16/34] initialize Env from server and deduplicate several attributes --- lib/api.js | 25 ++++- lib/env.js | 200 ++++++++++++++++++++++++++++++++++++++ lib/historyKeeper.js | 227 ++++++------------------------------------- server.js | 84 +++++----------- 4 files changed, 275 insertions(+), 261 deletions(-) create mode 100644 lib/env.js diff --git a/lib/api.js b/lib/api.js index cc88aaed7..07ec77d0b 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,13 +1,28 @@ /* jshint esversion: 6 */ const WebSocketServer = require('ws').Server; const NetfluxSrv = require('chainpad-server'); +const Decrees = require("./decrees"); -module.exports.create = function (config) { +const nThen = require("nthen"); + +module.exports.create = function (Env) { + var log = Env.Log; + +nThen(function (w) { + Decrees.load(Env, w(function (err) { + if (err && err.code !== "ENOENT") { + log.error('DECREES_LOADING', { + error: err.code || err, + message: err.message, + }); + console.error(err); + } + })); +}).nThen(function () { // asynchronously create a historyKeeper and RPC together - require('./historyKeeper.js').create(config, function (err, historyKeeper) { + require('./historyKeeper.js').create(Env, function (err, historyKeeper) { if (err) { throw err; } - var log = config.log; var noop = function () {}; @@ -21,7 +36,7 @@ module.exports.create = function (config) { }; // spawn ws server and attach netflux event handlers - NetfluxSrv.create(new WebSocketServer({ server: config.httpServer})) + NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer})) .on('channelClose', historyKeeper.channelClose) .on('channelMessage', historyKeeper.channelMessage) .on('channelOpen', historyKeeper.channelOpen) @@ -50,4 +65,6 @@ module.exports.create = function (config) { }) .register(historyKeeper.id, historyKeeper.directMessage); }); +}); + }; diff --git a/lib/env.js b/lib/env.js new file mode 100644 index 000000000..53771bbcb --- /dev/null +++ b/lib/env.js @@ -0,0 +1,200 @@ +/* jshint esversion: 6 */ +/* globals process */ + +const Crypto = require('crypto'); +const WriteQueue = require("./write-queue"); +const BatchRead = require("./batch-read"); + +const Keys = require("./keys"); +const Core = require("./commands/core"); + +const Quota = require("./commands/quota"); +const Util = require("./common-util"); + +module.exports.create = function (config) { + // mode can be FRESH (default), DEV, or PACKAGE + + const Env = { + FRESH_KEY: '', + FRESH_MODE: true, + DEV_MODE: false, + configCache: {}, + flushCache: function () { + Env.configCache = {}; + Env.FRESH_KEY = +new Date(); + if (!(Env.DEV_MODE || Env.FRESH_MODE)) { Env.FRESH_MODE = true; } + if (!Env.Log) { return; } + Env.Log.info("UPDATING_FRESH_KEY", Env.FRESH_KEY); + }, + + Log: undefined, + // store + id: Crypto.randomBytes(8).toString('hex'), + + launchTime: +new Date(), + + inactiveTime: config.inactiveTime, + archiveRetentionTime: config.archiveRetentionTime, + accountRetentionTime: config.accountRetentionTime, + + // TODO implement mutability + adminEmail: config.adminEmail, + supportMailbox: config.supportMailboxPublicKey, + + metadata_cache: {}, + channel_cache: {}, + queueStorage: WriteQueue(), + queueDeletes: WriteQueue(), + queueValidation: WriteQueue(), + + batchIndexReads: BatchRead("HK_GET_INDEX"), + batchMetadata: BatchRead('GET_METADATA'), + batchRegisteredUsers: BatchRead("GET_REGISTERED_USERS"), + batchDiskUsage: BatchRead('GET_DISK_USAGE'), + batchUserPins: BatchRead('LOAD_USER_PINS'), + batchTotalSize: BatchRead('GET_TOTAL_SIZE'), + batchAccountQuery: BatchRead("QUERY_ACCOUNT_SERVER"), + + intervals: {}, + maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024), + premiumUploadSize: false, // overridden below... + Sessions: {}, + paths: {}, + //msgStore: config.store, + + netfluxUsers: {}, + + pinStore: undefined, + + limits: {}, + admins: [], + WARN: function (e, output) { // TODO deprecate this + if (!Env.Log) { return; } + if (e && output) { + Env.Log.warn(e, { + output: output, + message: String(e), + stack: new Error(e).stack, + }); + } + }, + + allowSubscriptions: config.allowSubscriptions === true, + blockDailyCheck: config.blockDailyCheck === true, + + myDomain: config.myDomain, + mySubdomain: config.mySubdomain, // only exists for the accounts integration + customLimits: {}, + // FIXME this attribute isn't in the default conf + // but it is referenced in Quota + domain: config.domain, + + maxWorkers: config.maxWorkers, + disableIntegratedTasks: config.disableIntegratedTasks || false, + disableIntegratedEviction: config.disableIntegratedEviction || false, + lastEviction: +new Date(), + }; + + (function () { + if (process.env.PACKAGE) { + // `PACKAGE=1 node server` uses the version string from package.json as the cache string + console.log("PACKAGE MODE ENABLED"); + Env.FRESH_MODE = false; + Env.DEV_MODE = false; + } else if (process.env.DEV) { + // `DEV=1 node server` will use a random cache string on every page reload + console.log("DEV MODE ENABLED"); + Env.FRESH_MODE = false; + Env.DEV_MODE = true; + } else { + // `FRESH=1 node server` will set a random cache string when the server is launched + // and use it for the process lifetime or until it is reset from the admin panel + console.log("FRESH MODE ENABLED"); + Env.FRESH_KEY = +new Date(); + } + }()); + + + + + (function () { + var custom = config.customLimits; + if (!custom) { return; } + + var stored = Env.customLimits; + + Object.keys(custom).forEach(function (k) { + var unsafeKey = Keys.canonicalize(k); + + if (!unsafeKey) { + console.log("INVALID_CUSTOM_LIMIT_ID", { + message: "A custom quota upgrade was provided via your config with an invalid identifier. It will be ignored.", + key: k, + value: custom[k], + }); + return; + } + + if (stored[unsafeKey]) { + console.log("INVALID_CUSTOM_LIMIT_DUPLICATED", { + message: "A duplicated custom quota upgrade was provided via your config which would have overridden an existing value. It will be ignored.", + key: k, + value: custom[k], + }); + return; + } + + if (!Quota.isValidLimit(custom[k])) { + console.log("INVALID_CUSTOM_LIMIT_VALUE", { + message: "A custom quota upgrade was provided via your config with an invalid value. It will be ignored.", + key: k, + value: custom[k], + }); + return; + } + + var limit = stored[unsafeKey] = Util.clone(custom[k]); + limit.origin = 'config'; + }); + }()); + + (function () { + var pes = config.premiumUploadSize; + if (!isNaN(pes) && pes >= Env.maxUploadSize) { + Env.premiumUploadSize = pes; + } + }()); + + var paths = Env.paths; + + var keyOrDefaultString = function (key, def) { + return typeof(config[key]) === 'string'? config[key]: def; + }; + + paths.pin = keyOrDefaultString('pinPath', './pins'); + paths.block = keyOrDefaultString('blockPath', './block'); + paths.data = keyOrDefaultString('filePath', './datastore'); + paths.staging = keyOrDefaultString('blobStagingPath', './blobstage'); + paths.blob = keyOrDefaultString('blobPath', './blob'); + paths.decree = keyOrDefaultString('decreePath', './data/'); + paths.archive = keyOrDefaultString('archivePath', './data/archive'); + paths.task = keyOrDefaultString('taskPath', './tasks'); + + Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit >= 0? + config.defaultStorageLimit: + Core.DEFAULT_LIMIT; + + try { + Env.admins = (config.adminKeys || []).map(function (k) { + try { + return Keys.canonicalize(k); + } catch (err) { + return; + } + }).filter(Boolean); + } catch (e) { + console.error("Can't parse admin keys. Please update or fix your config.js file!"); + } + + return Env; +}; diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index 5278a771c..299756cac 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -1,168 +1,18 @@ /* jshint esversion: 6 */ const nThen = require('nthen'); -const Crypto = require('crypto'); -const WriteQueue = require("./write-queue"); -const BatchRead = require("./batch-read"); const RPC = require("./rpc"); const HK = require("./hk-util.js"); -const Core = require("./commands/core"); - -const Keys = require("./keys"); -const Quota = require("./commands/quota"); -const Util = require("./common-util"); - const Store = require("./storage/file"); const BlobStore = require("./storage/blob"); const Workers = require("./workers/index"); +const Core = require("./commands/core"); -module.exports.create = function (config, cb) { - const Log = config.log; - var WARN = function (e, output) { - if (e && output) { - Log.warn(e, { - output: output, - message: String(e), - stack: new Error(e).stack, - }); - } - }; - +module.exports.create = function (Env, cb) { + const Log = Env.Log; Log.silly('HK_LOADING', 'LOADING HISTORY_KEEPER MODULE'); - const Env = { - Log: Log, - // store - id: Crypto.randomBytes(8).toString('hex'), - - launchTime: +new Date(), - - inactiveTime: config.inactiveTime, - archiveRetentionTime: config.archiveRetentionTime, - accountRetentionTime: config.accountRetentionTime, - - metadata_cache: {}, - channel_cache: {}, - queueStorage: WriteQueue(), - queueDeletes: WriteQueue(), - queueValidation: WriteQueue(), - - batchIndexReads: BatchRead("HK_GET_INDEX"), - batchMetadata: BatchRead('GET_METADATA'), - batchRegisteredUsers: BatchRead("GET_REGISTERED_USERS"), - batchDiskUsage: BatchRead('GET_DISK_USAGE'), - batchUserPins: BatchRead('LOAD_USER_PINS'), - batchTotalSize: BatchRead('GET_TOTAL_SIZE'), - batchAccountQuery: BatchRead("QUERY_ACCOUNT_SERVER"), - - //historyKeeper: config.historyKeeper, - intervals: config.intervals || {}, - maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024), - premiumUploadSize: false, // overridden below... - Sessions: {}, - paths: {}, - //msgStore: config.store, - - netfluxUsers: {}, - - pinStore: undefined, - - limits: {}, - admins: [], - WARN: WARN, - flushCache: config.flushCache, - adminEmail: config.adminEmail, - allowSubscriptions: config.allowSubscriptions === true, - blockDailyCheck: config.blockDailyCheck === true, - - myDomain: config.myDomain, - mySubdomain: config.mySubdomain, // only exists for the accounts integration - customLimits: {}, - // FIXME this attribute isn't in the default conf - // but it is referenced in Quota - domain: config.domain - }; - - (function () { - var custom = config.customLimits; - if (!custom) { return; } - - var stored = Env.customLimits; - - Object.keys(custom).forEach(function (k) { - var unsafeKey = Keys.canonicalize(k); - - if (!unsafeKey) { - Log.warn("INVALID_CUSTOM_LIMIT_ID", { - message: "A custom quota upgrade was provided via your config with an invalid identifier. It will be ignored.", - key: k, - value: custom[k], - }); - return; - } - - if (stored[unsafeKey]) { - Log.warn("INVALID_CUSTOM_LIMIT_DUPLICATED", { - message: "A duplicated custom quota upgrade was provided via your config which would have overridden an existing value. It will be ignored.", - key: k, - value: custom[k], - }); - return; - } - - if (!Quota.isValidLimit(custom[k])) { - Log.warn("INVALID_CUSTOM_LIMIT_VALUE", { - message: "A custom quota upgrade was provided via your config with an invalid value. It will be ignored.", - key: k, - value: custom[k], - }); - return; - } - - var limit = stored[unsafeKey] = Util.clone(custom[k]); - limit.origin = 'config'; - }); - }()); - - (function () { - var pes = config.premiumUploadSize; - if (!isNaN(pes) && pes >= Env.maxUploadSize) { - Env.premiumUploadSize = pes; - } - }()); - - var paths = Env.paths; - - var keyOrDefaultString = function (key, def) { - return typeof(config[key]) === 'string'? config[key]: def; - }; - - var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins'); - paths.block = keyOrDefaultString('blockPath', './block'); - paths.data = keyOrDefaultString('filePath', './datastore'); - paths.staging = keyOrDefaultString('blobStagingPath', './blobstage'); - paths.blob = keyOrDefaultString('blobPath', './blob'); - paths.decree = keyOrDefaultString('decreePath', './data/'); - - Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit >= 0? - config.defaultStorageLimit: - Core.DEFAULT_LIMIT; - - try { - // XXX this should be the same as is exposed in server.js - // /api/config.adminKeys - Env.admins = (config.adminKeys || []).map(function (k) { - try { - return Keys.canonicalize(k); - } catch (err) { - return; - } - }).filter(Boolean); - } catch (e) { - console.error("Can't parse admin keys. Please update or fix your config.js file!"); - } - - config.historyKeeper = Env.historyKeeper = { + Env.historyKeeper = { metadata_cache: Env.metadata_cache, channel_cache: Env.channel_cache, @@ -262,6 +112,8 @@ module.exports.create = function (config, cb) { Log.verbose('HK_ID', 'History keeper ID: ' + Env.id); + var pinPath = Env.paths.pin; + nThen(function (w) { // create a pin store Store.create({ @@ -272,18 +124,20 @@ module.exports.create = function (config, cb) { })); // create a channel store - Store.create(config, w(function (err, _store) { + Store.create({ + filePath: Env.paths.data, + archivepath: Env.paths.archive, + }, w(function (err, _store) { if (err) { throw err; } - config.store = _store; Env.msgStore = _store; // API used by rpc Env.store = _store; // API used by historyKeeper })); // create a blob store BlobStore.create({ - blobPath: config.blobPath, - blobStagingPath: config.blobStagingPath, - archivePath: config.archivePath, + blobPath: Env.paths.blob, + blobStagingPath: Env.paths.staging, + archivePath: Env.paths.archive, getSession: function (safeKey) { return Core.getSession(Env.Sessions, safeKey); }, @@ -293,32 +147,28 @@ module.exports.create = function (config, cb) { })); }).nThen(function (w) { Workers.initialize(Env, { - blobPath: config.blobPath, - blobStagingPath: config.blobStagingPath, - taskPath: config.taskPath, - pinPath: pinPath, - filePath: config.filePath, - archivePath: config.archivePath, - channelExpirationMs: config.channelExpirationMs, - verbose: config.verbose, - openFileLimit: config.openFileLimit, - - inactiveTime: config.inactiveTime, - archiveRetentionTime: config.archiveRetentionTime, - accountRetentionTime: config.accountRetentionTime, - - maxWorkers: config.maxWorkers, + blobPath: Env.paths.blob, + blobStagingPath: Env.paths.staging, + taskPath: Env.paths.task, + pinPath: Env.paths.pin, + filePath: Env.paths.data, + archivePath: Env.paths.archive, + + inactiveTime: Env.inactiveTime, + archiveRetentionTime: Env.archiveRetentionTime, + accountRetentionTime: Env.accountRetentionTime, + + maxWorkers: Env.maxWorkers, }, w(function (err) { if (err) { throw new Error(err); } })); }).nThen(function () { - config.intervals = config.intervals || {}; - if (config.disableIntegratedTasks) { return; } + if (Env.disableIntegratedTasks) { return; } var tasks_running; - config.intervals.taskExpiration = setInterval(function () { + Env.intervals.taskExpiration = setInterval(function () { if (tasks_running) { return; } tasks_running = true; Env.runTasks(function (err) { @@ -329,19 +179,18 @@ module.exports.create = function (config, cb) { }); }, 1000 * 60 * 5); // run every five minutes }).nThen(function () { - if (config.disableIntegratedEviction) { return; } + if (Env.disableIntegratedEviction) { return; } const ONE_DAY = 24 * 1000 * 60 * 60; // setting the time of the last eviction to "now" // effectively makes it so that we'll start evicting after the server // has been up for at least one day - var last_eviction = +new Date(); var active = false; - config.intervals.eviction = setInterval(function () { + Env.intervals.eviction = setInterval(function () { if (active) { return; } var now = +new Date(); // evict inactive data once per day - if (last_eviction && (now - ONE_DAY) < last_eviction) { return; } + if ((now - ONE_DAY) < Env.lastEviction) { return; } active = true; Env.evictInactive(function (err) { if (err) { @@ -349,28 +198,16 @@ module.exports.create = function (config, cb) { Log.error('EVICT_INACTIVE_MAIN_ERROR', err); } active = false; - last_eviction = now; + Env.lastEviction = now; }); }, 60 * 1000); }).nThen(function () { - var Decrees = require("./decrees"); - - Decrees.load(Env, function (err) { - if (err && err.code !== "ENOENT") { - Log.error('DECREES_LOADING', { - error: err.code || err, - message: err.message, - }); - console.error(err); - } - }); - }).nThen(function () { RPC.create(Env, function (err, _rpc) { if (err) { throw err; } Env.rpc = _rpc; - cb(void 0, config.historyKeeper); + cb(void 0, Env.historyKeeper); }); }); }; diff --git a/server.js b/server.js index ba0e9270f..0d048d3a5 100644 --- a/server.js +++ b/server.js @@ -12,31 +12,10 @@ var Default = require("./lib/defaults"); var Keys = require("./lib/keys"); var config = require("./lib/load-config"); +var Env = require("./lib/env").create(config); var app = Express(); -// mode can be FRESH (default), DEV, or PACKAGE - -var FRESH_KEY = ''; -var FRESH_MODE = true; -var DEV_MODE = false; -if (process.env.PACKAGE) { -// `PACKAGE=1 node server` uses the version string from package.json as the cache string - console.log("PACKAGE MODE ENABLED"); - FRESH_MODE = false; - DEV_MODE = false; -} else if (process.env.DEV) { -// `DEV=1 node server` will use a random cache string on every page reload - console.log("DEV MODE ENABLED"); - FRESH_MODE = false; - DEV_MODE = true; -} else { -// `FRESH=1 node server` will set a random cache string when the server is launched -// and use it for the process lifetime or until it is reset from the admin panel - console.log("FRESH MODE ENABLED"); - FRESH_KEY = +new Date(); -} - (function () { // you absolutely must provide an 'httpUnsafeOrigin' if (typeof(config.httpUnsafeOrigin) !== 'string') { @@ -64,7 +43,7 @@ if (process.env.PACKAGE) { config.httpSafePort = config.httpPort + 1; } - if (DEV_MODE) { return; } + if (Env.DEV_MODE) { return; } console.log(` m m mm mmmmm mm m mmmmm mm m mmm m # # # ## # "# #"m # # #"m # m" " # @@ -81,15 +60,6 @@ if (process.env.PACKAGE) { } }()); -var configCache = {}; -config.flushCache = function () { - configCache = {}; - FRESH_KEY = +new Date(); - if (!(DEV_MODE || FRESH_MODE)) { FRESH_MODE = true; } - if (!config.log) { return; } - config.log.info("UPDATING_FRESH_KEY", FRESH_KEY); -}; - var setHeaders = (function () { // load the default http headers unless the admin has provided their own via the config file var headers; @@ -144,6 +114,7 @@ if (!config.logFeedback) { return; } const logFeedback = function (url) { url.replace(/\?(.*?)=/, function (all, fb) { + if (!config.log) { return; } config.log.feedback(fb, ''); }); }; @@ -182,7 +153,7 @@ app.get(mainPagePattern, Express.static(__dirname + '/customize')); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { - maxAge: DEV_MODE? "0d": "365d" + maxAge: Env.DEV_MODE? "0d": "365d" })); app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), { maxAge: "0d" @@ -197,23 +168,10 @@ app.use("/customize.dist", Express.static(__dirname + '/customize.dist')); app.use(/^\/[^\/]*$/, Express.static('customize')); app.use(/^\/[^\/]*$/, Express.static('customize.dist')); -var admins = []; -try { - admins = (config.adminKeys || []).map(function (k) { - var unsafeKey = Keys.canonicalize(k); - // return each admin's "unsafeKey" - // this might throw and invalidate all the other admin's keys - // but we want to get the admin's attention anyway. - // breaking everything is a good way to accomplish that. - if (!unsafeKey) { throw new Error(); } - return unsafeKey; - }); -} catch (e) { console.error("Can't parse admin keys"); } - var serveConfig = (function () { // if dev mode: never cache var cacheString = function () { - return (FRESH_KEY? '-' + FRESH_KEY: '') + (DEV_MODE? '-' + (+new Date()): ''); + return (Env.FRESH_KEY? '-' + Env.FRESH_KEY: '') + (Env.DEV_MODE? '-' + (+new Date()): ''); }; var template = function (host) { @@ -228,12 +186,12 @@ var serveConfig = (function () { allowSubscriptions: (config.allowSubscriptions === true), websocketPath: config.externalWebsocketURL, httpUnsafeOrigin: config.httpUnsafeOrigin, - adminEmail: config.adminEmail, // XXX mutable - adminKeys: admins, - inactiveTime: config.inactiveTime, // XXX mutable - supportMailbox: config.supportMailboxPublicKey, - maxUploadSize: config.maxUploadSize, // XXX mutable - premiumUploadSize: config.premiumUploadSize, // XXX mutable + adminEmail: Env.adminEmail, + adminKeys: Env.admins, + inactiveTime: Env.inactiveTime, + supportMailbox: Env.supportMailboxPublicKey, + maxUploadSize: Env.maxUploadSize, + premiumUploadSize: Env.premiumUploadSize, }, null, '\t'), 'obj.httpSafeOrigin = ' + (function () { if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; } @@ -254,28 +212,29 @@ var serveConfig = (function () { var host = req.headers.host.replace(/\:[0-9]+/, ''); res.setHeader('Content-Type', 'text/javascript'); // don't cache anything if you're in dev mode - if (DEV_MODE) { + if (Env.DEV_MODE) { return void res.send(template(host)); } // generate a lookup key for the cache var cacheKey = host + ':' + cacheString(); - // XXX we must be able to clear the cache when updating any mutable key + // FIXME mutable + // we must be able to clear the cache when updating any mutable key // if there's nothing cached for that key... - if (!configCache[cacheKey]) { + if (!Env.configCache[cacheKey]) { // generate the response and cache it in memory - configCache[cacheKey] = template(host); + Env.configCache[cacheKey] = template(host); // and create a function to conditionally evict cache entries // which have not been accessed in the last 20 seconds cleanUp[cacheKey] = Util.throttle(function () { delete cleanUp[cacheKey]; - delete configCache[cacheKey]; + delete Env.configCache[cacheKey]; }, 20000); } // successive calls to this function cleanUp[cacheKey](); - return void res.send(configCache[cacheKey]); + return void res.send(Env.configCache[cacheKey]); }; }()); @@ -298,7 +257,7 @@ app.use(function (req, res, next) { send404(res, custom_four04_path); }); -var httpServer = Http.createServer(app); +var httpServer = Env.httpServer = Http.createServer(app); nThen(function (w) { Fs.exists(__dirname + "/customize", w(function (e) { @@ -324,11 +283,12 @@ nThen(function (w) { // Initialize logging then start the API server require("./lib/log").create(config, function (_log) { + Env.Log = _log; config.log = _log; - config.httpServer = httpServer; if (config.externalWebsocketURL) { return; } - require("./lib/api").create(config); + + require("./lib/api").create(Env); }); }); From 46ebd7b40b8318fb5a62f155790abe5334b51861 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 17:40:21 +0530 Subject: [PATCH 17/34] remove unsupported storage configuration parameters --- lib/storage/blob.js | 3 --- lib/storage/file.js | 2 -- 2 files changed, 5 deletions(-) diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 006f5ca80..21f38a73a 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -204,14 +204,12 @@ var tryId = function (path, cb) { Fs.access(path, Fs.constants.R_OK | Fs.constants.W_OK, function (e) { if (!e) { // generate a new id (with the same prefix) and recurse - //WARN('ownedUploadComplete', 'id is already used '+ id); return void cb('EEXISTS'); } else if (e.code === 'ENOENT') { // no entry, so it's safe for us to proceed return void cb(); } else { // it failed in an unexpected way. log it - //WARN('ownedUploadComplete', e); return void cb(e.code); } }); @@ -229,7 +227,6 @@ var owned_upload_complete = function (Env, safeKey, id, cb) { } if (!isValidId(id)) { - //WARN('ownedUploadComplete', "id is invalid"); return void cb('EINVAL_ID'); } diff --git a/lib/storage/file.js b/lib/storage/file.js index b209cb597..16f8c8ba7 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -996,8 +996,6 @@ module.exports.create = function (conf, _cb) { root: conf.filePath || './datastore', archiveRoot: conf.archivePath || './data/archive', channels: { }, - channelExpirationMs: conf.channelExpirationMs || 30000, - verbose: conf.verbose, batchGetChannel: BatchRead('store_batch_channel'), }; var it; From 6d13a785c609a639b3a27752c37dfb509a62078c Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 17:42:41 +0530 Subject: [PATCH 18/34] downgrade a non-critical XXX to a FIXME --- lib/commands/quota.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/quota.js b/lib/commands/quota.js index 6a8c80bfe..bcfedb3f7 100644 --- a/lib/commands/quota.js +++ b/lib/commands/quota.js @@ -28,7 +28,7 @@ Quota.isValidLimit = function (o) { Quota.applyCustomLimits = function (Env) { // DecreedLimits > customLimits > serverLimits; - // XXX perform an integrity check on shared limits + // FIXME perform an integrity check on shared limits // especially relevant because we use Env.limits // when considering whether to archive inactive accounts From f86409e1bac20016a6bc298b964d7560e595a822 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 17:50:40 +0530 Subject: [PATCH 19/34] take note of which Environment variables should be made mutable via decree --- lib/decrees.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/decrees.js b/lib/decrees.js index 6ecddc0cf..c30375810 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -27,6 +27,15 @@ UPDATE_ARCHIVE_RETENTION_TIME UPDATE_MAX_UPLOAD_SIZE UPDATE_PREMIUM_UPLOAD_SIZE +// 4.0 +Env.adminEmail +Env.supportMailbox +Env.DEV_MODE || Env.FRESH_MODE, +Env.maxUploadSize +Env.premiumUploadSize +Env.disableIntegratedTasks +Env.disableIntegratedEviction + */ var commands = {}; From 471a9a33ac3b57163bb7b0e0aa3e14cb870a7fc2 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 17:51:38 +0530 Subject: [PATCH 20/34] take note of a minor error with pin RPCs --- lib/commands/pin-rpc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/commands/pin-rpc.js b/lib/commands/pin-rpc.js index a6f1642c6..8b111438d 100644 --- a/lib/commands/pin-rpc.js +++ b/lib/commands/pin-rpc.js @@ -114,6 +114,7 @@ Pinning.getTotalSize = function (Env, safeKey, cb) { /* Users should be able to clear their own pin log with an authenticated RPC */ Pinning.removePins = function (Env, safeKey, cb) { + // FIXME respect the queue Env.pinStore.removeChannel(safeKey, function (err) { Env.Log.info('DELETION_PIN_BY_OWNER_RPC', { safeKey: safeKey, From 854e4c06ff502ba55aba7864c56a87a40f20c65f Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 17:52:15 +0530 Subject: [PATCH 21/34] report lastEviction in an admin INSTANCE_STATUS --- lib/commands/admin-rpc.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index d178f39db..fd2e934f3 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -221,6 +221,8 @@ var instanceStatus = function (Env, Server, cb) { archiveRetentionTime: Env.archiveRetentionTime, defaultStorageLimit: Env.defaultStorageLimit, + + lastEviction: Env.lastEviction, }); }; From 45b063e378c29eeb01fc2909afdbc8d3b515fd7e Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 19:15:48 +0530 Subject: [PATCH 22/34] suppress irrelevant errors from Decrees.load --- lib/api.js | 2 +- lib/decrees.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/api.js b/lib/api.js index 07ec77d0b..7152f75b9 100644 --- a/lib/api.js +++ b/lib/api.js @@ -10,7 +10,7 @@ module.exports.create = function (Env) { nThen(function (w) { Decrees.load(Env, w(function (err) { - if (err && err.code !== "ENOENT") { + if (err) { log.error('DECREES_LOADING', { error: err.code || err, message: err.message, diff --git a/lib/decrees.js b/lib/decrees.js index c30375810..e8b83f9d8 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -182,9 +182,16 @@ var Schedule = require("./schedule"); var Fse = require("fs-extra"); var nThen = require("nthen"); -Decrees.load = function (Env, cb) { +Decrees.load = function (Env, _cb) { Env.scheduleDecree = Env.scheduleDecree || Schedule(); + var cb = Util.once(Util.mkAsync(function (err) { + if (err && err.code !== 'ENOENT') { + return void _cb(err); + } + _cb(); + })); + Env.scheduleDecree.blocking('', function (unblock) { var done = Util.once(Util.both(cb, unblock)); nThen(function (w) { From f2ec9cbe33b4a94cfdc780fbc822ae68017bff7b Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 19:18:12 +0530 Subject: [PATCH 23/34] load premium and customLimits to avoid evicting them even if inactive --- lib/commands/admin-rpc.js | 1 + lib/commands/quota.js | 19 ++++++++++++----- lib/eviction.js | 33 ++++++++++++++++++++---------- scripts/evict-inactive.js | 43 +++++++++++++++++++++++++++++++-------- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index fd2e934f3..a97ffeb2b 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -223,6 +223,7 @@ var instanceStatus = function (Env, Server, cb) { defaultStorageLimit: Env.defaultStorageLimit, lastEviction: Env.lastEviction, + knownActiveAccounts: Env.knownActiveAccounts, }); }; diff --git a/lib/commands/quota.js b/lib/commands/quota.js index bcfedb3f7..b779d8e56 100644 --- a/lib/commands/quota.js +++ b/lib/commands/quota.js @@ -117,13 +117,22 @@ Quota.queryAccountServer = function (Env, cb) { }); }; -Quota.updateCachedLimits = function (Env, cb) { +Quota.shouldContactServer = function (Env) { + return !(Env.blockDailyCheck === true || + ( + typeof(Env.blockDailyCheck) === 'undefined' && + Env.adminEmail === false + && Env.allowSubscriptions === false + ) + ); +}; + +Quota.updateCachedLimits = function (Env, _cb) { + var cb = Util.mkAsync(_cb); + Quota.applyCustomLimits(Env); - if (Env.blockDailyCheck === true || - (typeof(Env.blockDailyCheck) === 'undefined' && Env.adminEmail === false && Env.allowSubscriptions === false)) { - return void cb(); - } + if (!Quota.shouldContactServer(Env)) { return void cb(); } Quota.queryAccountServer(Env, function (err, json) { if (err) { return void cb(err); } if (!json) { return void cb(); } diff --git a/lib/eviction.js b/lib/eviction.js index 1dd511666..979848bc6 100644 --- a/lib/eviction.js +++ b/lib/eviction.js @@ -2,6 +2,7 @@ var nThen = require("nthen"); var Bloom = require("@mcrowe/minibloom"); var Util = require("../lib/common-util"); var Pins = require("../lib/pins"); +var Keys = require("./keys"); var getNewestTime = function (stats) { return stats[['atime', 'ctime', 'mtime'].reduce(function (a, b) { @@ -42,12 +43,11 @@ module.exports = function (Env, cb) { // pre-converted to the 'safeKey' format so we can easily compare // them against ids we see on the filesystem var premiumSafeKeys = Object.keys(Env.limits || {}) - .filter(function (key) { - return key.length === 44; + .map(function (id) { + return Keys.canonicalize(id); }) - .map(function (unsafeKey) { - return Util.escapeKeyCharacters(unsafeKey); - }); + .filter(Boolean) + .map(Util.escapeKeyCharacters); // files which have not been changed since before this date can be considered inactive var inactiveTime = +new Date() - (Env.inactiveTime * 24 * 3600 * 1000); @@ -291,7 +291,7 @@ module.exports = function (Env, cb) { return activeDocs.test(docId); }; - var accountIsActive = function (mtime, pinList, id) { + var accountIsActive = function (mtime, pinList) { // console.log("id [%s] in premiumSafeKeys", id, premiumSafeKeys.indexOf(id) !== -1); // if their pin log has changed recently then consider them active if (mtime && mtime > accountRetentionTime) { @@ -299,11 +299,10 @@ module.exports = function (Env, cb) { } // iterate over their pinned documents until you find one that has been active - if (pinList.some(docIsActive)) { - return true; - } + return pinList.some(docIsActive); + }; - // Finally, make sure it's not a premium account + var isPremiumAccount = function (id) { return premiumSafeKeys.indexOf(id) !== -1; }; @@ -317,7 +316,7 @@ module.exports = function (Env, cb) { var mtime = content.latest; var pinList = Object.keys(content.pins); - if (accountIsActive(mtime, pinList, id)) { + if (accountIsActive(mtime, pinList)) { // add active accounts' pinned documents to a second bloom filter pinAll(pinList); return void next(); @@ -332,6 +331,15 @@ module.exports = function (Env, cb) { return void next(); } + if (isPremiumAccount(id)) { + Log.info("EVICT_INACTIVE_PREMIUM_ACCOUNT", { + id: id, + mtime: mtime, + }); + pinAll(pinList); + return void next(); + } + // remove the pin logs of inactive accounts if inactive account removal is configured pinStore.archiveChannel(id, function (err) { if (err) { @@ -348,6 +356,9 @@ module.exports = function (Env, cb) { "EVICT_COUNT_ACCOUNTS": "EVICT_INACTIVE_ACCOUNTS"; + // update the number of known active accounts in Env for statistics + Env.knownActiveAccounts = accounts - inactive; + Log.info(label, { accounts: accounts, inactive: inactive, diff --git a/scripts/evict-inactive.js b/scripts/evict-inactive.js index 91b7dad63..bf7e1ca5b 100644 --- a/scripts/evict-inactive.js +++ b/scripts/evict-inactive.js @@ -4,20 +4,41 @@ var Store = require("../lib/storage/file"); var BlobStore = require("../lib/storage/blob"); var Quota = require("../lib/commands/quota"); +var Environment = require("../lib/env"); +var Decrees = require("../lib/decrees"); var config = require("../lib/load-config"); -var Env = { - inactiveTime: config.inactiveTime, - archiveRetentionTime: config.archiveRetentionTime, - accountRetentionTime: config.accountRetentionTime, - paths: { - pin: config.pinPath, - }, + +var Env = Environment.create(config); + +var loadPremiumAccounts = function (Env, cb) { + nThen(function (w) { + // load premium accounts + Quota.updateCachedLimits(Env, w(function (err) { + if (err) { + Env.Log.error('EVICT_LOAD_PREMIUM_ACCOUNTS', { + error: err, + }); + } + })); + }).nThen(function (w) { + // load and apply decrees + Decrees.load(Env, w(function (err) { + if (err) { + Env.Log.error('EVICT_LOAD_DECREES', { + error: err.code || err, + message: err.message, + }); + } + })); + }).nThen(function () { + //console.log(Env.limits); + cb(); + }); }; var prepareEnv = function (Env, cb) { - Env.customLimits = config.customLimits; - Quota.applyCustomLimits(Env); + //Quota.applyCustomLimits(Env); nThen(function (w) { /* Database adaptors @@ -58,6 +79,10 @@ var prepareEnv = function (Env, cb) { } Env.blobStore = _; })); + }).nThen(function (w) { + loadPremiumAccounts(Env, w(function (/* err */) { + //if (err) { } + })); }).nThen(function () { cb(); }); From adb988058df737fd4d2971cd0f3bb981ba71a503 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 12 Oct 2020 19:19:57 +0530 Subject: [PATCH 24/34] stop logging dev/fresh mode --- lib/env.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/env.js b/lib/env.js index 53771bbcb..8cc45fcc4 100644 --- a/lib/env.js +++ b/lib/env.js @@ -12,7 +12,6 @@ const Quota = require("./commands/quota"); const Util = require("./common-util"); module.exports.create = function (config) { - // mode can be FRESH (default), DEV, or PACKAGE const Env = { FRESH_KEY: '', @@ -93,23 +92,25 @@ module.exports.create = function (config) { disableIntegratedTasks: config.disableIntegratedTasks || false, disableIntegratedEviction: config.disableIntegratedEviction || false, lastEviction: +new Date(), + knownActiveAccounts: 0, }; (function () { + // mode can be FRESH (default), DEV, or PACKAGE if (process.env.PACKAGE) { // `PACKAGE=1 node server` uses the version string from package.json as the cache string - console.log("PACKAGE MODE ENABLED"); + //console.log("PACKAGE MODE ENABLED"); Env.FRESH_MODE = false; Env.DEV_MODE = false; } else if (process.env.DEV) { // `DEV=1 node server` will use a random cache string on every page reload - console.log("DEV MODE ENABLED"); + //console.log("DEV MODE ENABLED"); Env.FRESH_MODE = false; Env.DEV_MODE = true; } else { // `FRESH=1 node server` will set a random cache string when the server is launched // and use it for the process lifetime or until it is reset from the admin panel - console.log("FRESH MODE ENABLED"); + //console.log("FRESH MODE ENABLED"); Env.FRESH_KEY = +new Date(); } }()); From 6ec51715188d72b9c27aa1d1199315d453387174 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 13 Oct 2020 10:56:40 +0530 Subject: [PATCH 25/34] add support for changing a few more Env parameters at runtime --- lib/commands/admin-rpc.js | 12 +++++- lib/decrees.js | 88 +++++++++++++++++++++++++++------------ lib/historyKeeper.js | 5 +-- 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index a97ffeb2b..4f0ee6fcc 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -207,13 +207,14 @@ the server adds two pieces of information to the supplied decree: } if (!changed) { return void cb(); } + Env.Log.info('ADMIN_DECREE', decree); Decrees.write(Env, decree, cb); }; // CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log) var instanceStatus = function (Env, Server, cb) { cb(void 0, { - restrictRegistration: Boolean(Env.restrictRegistration), + restrictRegistration: Env.restrictRegistration, launchTime: Env.launchTime, currentTime: +new Date(), @@ -223,7 +224,14 @@ var instanceStatus = function (Env, Server, cb) { defaultStorageLimit: Env.defaultStorageLimit, lastEviction: Env.lastEviction, - knownActiveAccounts: Env.knownActiveAccounts, + // FIXME eviction is run in a worker and this isn't returned + //knownActiveAccounts: Env.knownActiveAccounts, + disableIntegratedEviction: Env.disableIntegratedEviction, + disableIntegratedTasks: Env.disableIntegratedTasks, + + maxUploadSize: Env.maxUploadSize, + premiumUploadSize: Env.premiumUploadSize, + lastEviction: Env.lastEviction, }); }; diff --git a/lib/decrees.js b/lib/decrees.js index e8b83f9d8..7e5864fd5 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -11,6 +11,13 @@ UPDATE_DEFAULT_STORAGE() SET_QUOTA(, limit) RM_QUOTA() +SET_MAX_UPLOAD_SIZE +SET_PREMIUM_UPLOAD_SIZE + +// BACKGROUND PROCESSES +DISABLE_INTEGRATED_TASKS +DISABLE_INTEGRATED_EVICTION + NOT IMPLEMENTED: // RESTRICTED REGISTRATION @@ -24,18 +31,9 @@ UPDATE_ACCOUNT_RETENTION_TIME UPDATE_ARCHIVE_RETENTION_TIME // 3.0 -UPDATE_MAX_UPLOAD_SIZE -UPDATE_PREMIUM_UPLOAD_SIZE - -// 4.0 Env.adminEmail Env.supportMailbox Env.DEV_MODE || Env.FRESH_MODE, -Env.maxUploadSize -Env.premiumUploadSize -Env.disableIntegratedTasks -Env.disableIntegratedEviction - */ var commands = {}; @@ -52,33 +50,69 @@ var commands = {}; */ +var args_isBoolean = function (args) { + return !(!Array.isArray(args) || typeof(args[0]) !== 'boolean'); +}; + // Toggles a simple boolean -// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log) -commands.RESTRICT_REGISTRATION = function (Env, args) { - if (!Array.isArray(args) || typeof(args[0]) !== 'boolean') { - throw new Error('INVALID_ARGS'); - } - var bool = args[0]; - if (bool === Env.restrictRegistration) { return false; } - Env.restrictRegistration = bool; - return true; +var makeBooleanSetter = function (attr) { + return function (Env, args) { + if (!args_isBoolean(args)) { + throw new Error('INVALID_ARGS'); + } + var bool = args[0]; + if (bool === Env[attr]) { return false; } + Env[attr] = bool; + return true; + }; }; +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log) +commands.RESTRICT_REGISTRATION = makeBooleanSetter('restrictRegistration'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['DISABLE_INTEGRATED_EVICTION', [true]]], console.log) +commands.DISABLE_INTEGRATED_EVICTION = makeBooleanSetter('disableIntegratedEviction'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['DISABLE_INTEGRATED_TASKS', [true]]], console.log) +commands.DISABLE_INTEGRATED_TASKS = makeBooleanSetter('disableIntegratedTasks'); + +/* var isNonNegativeNumber = function (n) { return !(typeof(n) !== 'number' || isNaN(n) || n < 0); }; +*/ -// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['UPDATE_DEFAULT_STORAGE', [100 * 1024 * 1024]]], console.log) -commands.UPDATE_DEFAULT_STORAGE = function (Env, args) { - if (!Array.isArray(args) || !isNonNegativeNumber(args[0])) { - throw new Error('INVALID_ARGS'); - } - var limit = args[0]; - if (limit === Env.defaultStorageLimit) { return false; } - Env.defaultStorageLimit = limit; - return true; +var isInteger = function (n) { + return !(typeof(n) !== 'number' || isNaN(n) || (n % 1) !== 0); }; +var args_isInteger = function (args) { + return !(!Array.isArray(args) || !isInteger(args[0])); +}; + +var makeIntegerSetter = function (attr) { + return function (Env, args) { + if (!args_isInteger(args)) { + throw new Error('INVALID_ARGS'); + } + var integer = args[0]; + if (integer === Env[attr]) { return false; } + Env[attr] = integer; + return true; + }; +}; + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAX_UPLOAD_SIZE', [50 * 1024 * 1024]]], console.log) +commands.SET_MAX_UPLOAD_SIZE = makeIntegerSetter('maxUploadSize'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_PREMIUM_UPLOAD_SIZE', [150 * 1024 * 1024]]], console.log) +commands.SET_PREMIUM_UPLOAD_SIZE = makeIntegerSetter('premiumUploadSize'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['UPDATE_DEFAULT_STORAGE', [100 * 1024 * 1024]]], console.log) +commands.UPDATE_DEFAULT_STORAGE = makeIntegerSetter('defaultStorageLimit'); + +commands.SET_LAST_EVICTION = makeIntegerSetter('lastEviction'); + var Quota = require("./commands/quota"); var Keys = require("./keys"); var Util = require("./common-util"); diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index 299756cac..fc71c9856 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -165,10 +165,9 @@ module.exports.create = function (Env, cb) { } })); }).nThen(function () { - if (Env.disableIntegratedTasks) { return; } - var tasks_running; Env.intervals.taskExpiration = setInterval(function () { + if (Env.disableIntegratedTasks) { return; } if (tasks_running) { return; } tasks_running = true; Env.runTasks(function (err) { @@ -179,7 +178,6 @@ module.exports.create = function (Env, cb) { }); }, 1000 * 60 * 5); // run every five minutes }).nThen(function () { - if (Env.disableIntegratedEviction) { return; } const ONE_DAY = 24 * 1000 * 60 * 60; // setting the time of the last eviction to "now" // effectively makes it so that we'll start evicting after the server @@ -187,6 +185,7 @@ module.exports.create = function (Env, cb) { var active = false; Env.intervals.eviction = setInterval(function () { + if (Env.disableIntegratedEviction) { return; } if (active) { return; } var now = +new Date(); // evict inactive data once per day From abd84665aef52b419345a7d93f3879f9acc39459 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 13 Oct 2020 11:00:45 +0530 Subject: [PATCH 26/34] lint compliance --- lib/commands/admin-rpc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 4f0ee6fcc..72b75720a 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -231,7 +231,6 @@ var instanceStatus = function (Env, Server, cb) { maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, - lastEviction: Env.lastEviction, }); }; From a2c0d2165b53f28370175a2c75a527319aeb4d85 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 13 Oct 2020 11:36:30 +0530 Subject: [PATCH 27/34] implement a few more admin decrees --- lib/commands/admin-rpc.js | 1 + lib/decrees.js | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 72b75720a..9278c09a6 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -218,6 +218,7 @@ var instanceStatus = function (Env, Server, cb) { launchTime: Env.launchTime, currentTime: +new Date(), + inactiveTime: Env.inactiveTime, accountRetentionTime: Env.accountRetentionTime, archiveRetentionTime: Env.archiveRetentionTime, diff --git a/lib/decrees.js b/lib/decrees.js index 7e5864fd5..d7983c79c 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -11,6 +11,12 @@ UPDATE_DEFAULT_STORAGE() SET_QUOTA(, limit) RM_QUOTA() +// INACTIVITY +SET_INACTIVE_TIME +SET_ACCOUNT_RETENTION_TIME +SET_ARCHIVE_RETENTION_TIME + +// UPLOADS SET_MAX_UPLOAD_SIZE SET_PREMIUM_UPLOAD_SIZE @@ -26,11 +32,6 @@ REVOKE_INVITE REDEEM_INVITE // 2.0 -UPDATE_INACTIVE_TIME -UPDATE_ACCOUNT_RETENTION_TIME -UPDATE_ARCHIVE_RETENTION_TIME - -// 3.0 Env.adminEmail Env.supportMailbox Env.DEV_MODE || Env.FRESH_MODE, @@ -111,8 +112,18 @@ commands.SET_PREMIUM_UPLOAD_SIZE = makeIntegerSetter('premiumUploadSize'); // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['UPDATE_DEFAULT_STORAGE', [100 * 1024 * 1024]]], console.log) commands.UPDATE_DEFAULT_STORAGE = makeIntegerSetter('defaultStorageLimit'); +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_LAST_EVICTION', [0]]], console.log) commands.SET_LAST_EVICTION = makeIntegerSetter('lastEviction'); +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INACTIVE_TIME', [90]]], console.log) +commands.SET_INACTIVE_TIME = makeIntegerSetter('inactiveTime'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ARCHIVE_RETENTION_TIME', [30]]], console.log) +commands.SET_ARCHIVE_RETENTION_TIME = makeIntegerSetter('archiveRetentionTime'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ACCOUNT_RETENTION_TIME', [365]]], console.log) +commands.SET_ACCOUNT_RETENTION_TIME = makeIntegerSetter('accountRetentionTime'); + var Quota = require("./commands/quota"); var Keys = require("./keys"); var Util = require("./common-util"); From 6e57366b7f8a2d2b2bcea542ba9de6146df645be Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 13 Oct 2020 13:14:39 +0530 Subject: [PATCH 28/34] address a file descriptor leak --- lib/storage/file.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/storage/file.js b/lib/storage/file.js index 16f8c8ba7..d9d3f7c89 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -779,9 +779,11 @@ const messageBin = (env, chanName, msgBin, cb) => { chan.writeStream.write(msgBin, function () { chan.onError.splice(chan.onError.indexOf(complete), 1); complete(); +// It seems like this reintroduces a file descriptor leak if (chan.onError.length) { return; } if (chan.delayClose && chan.delayClose.clear) { chan.delayClose.clear(); + destroyStream(chan.writeStream, chanName); delete env.channels[chanName]; } }); From a7f747bd544253a0729a087c7e8f67d1725370cc Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 13 Oct 2020 10:30:01 +0200 Subject: [PATCH 29/34] Fix OO CSP cache --- www/common/onlyoffice/inner.js | 4 ++-- www/sheet/inner.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index ea0e51bfc..f39a85482 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -408,7 +408,7 @@ define([ myUniqueOOId = undefined; setMyId(); if (APP.docEditor) { APP.docEditor.destroyEditor(); } // Kill the old editor - $('iframe[name="frameEditor"]').after(h('div#cp-app-oo-placeholder')).remove(); + $('iframe[name="frameEditor"]').after(h('div#cp-app-oo-placeholder-a')).remove(); ooLoaded = false; oldLocks = {}; Object.keys(pendingChanges).forEach(function (key) { @@ -1406,7 +1406,7 @@ define([ }); }; - APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder", APP.ooconfig); + APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder-a", APP.ooconfig); ooLoaded = true; makeChannel(); }; diff --git a/www/sheet/inner.html b/www/sheet/inner.html index 4d7ca6009..07d21904d 100644 --- a/www/sheet/inner.html +++ b/www/sheet/inner.html @@ -11,7 +11,7 @@
-
+
From 101b6800eaa813eeb5d8062ff65620885541173f Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 13 Oct 2020 13:02:50 +0200 Subject: [PATCH 30/34] Improve custom limits UI in the admin panel --- www/admin/app-admin.less | 37 ++++++++++++++++++------------------- www/admin/inner.js | 28 +++++++++++++++++++--------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index f48aed535..5112fd9e2 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -35,27 +35,26 @@ code { cursor: pointer; } - ul.cp-compact { - .cp-admin-limit { - display: flex; - align-items: center; - overflow: hidden; + table { + td:not(:last-child) { + padding-right: 20px; + white-space: nowrap; + } + td:last-child { + min-width: 0; white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; - ul { - display: inline; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - li { - display: inline-block; - &.limit { - width: 130px; - } - &.plan { - width: 140px; - } - } + max-width: 500px; + } + @media screen and (max-width: 1200px) { + td.note { + display: none; + } + } + @media screen and (max-width: 1400px) { + td.plan { + display: none; } } } diff --git a/www/admin/inner.js b/www/admin/inner.js index 93e00c3c9..db5afca54 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -210,8 +210,7 @@ define([ return obj[a].limit > obj[b].limit; }); - var addClass = ""; - if (list.length > 10) { addClass = ".cp-compact"; } + var compact = list.length > 10; var content = list.map(function (key) { var user = obj[key]; @@ -223,19 +222,30 @@ define([ var keyEl = h('code.cp-limit-key', key); $(keyEl).click(function () { $('.cp-admin-setlimit-form').find('.cp-setlimit-key').val(key); + $('.cp-admin-setlimit-form').find('.cp-setlimit-quota').val(Math.floor(user.limit/1024)); + $('.cp-admin-setlimit-form').find('.cp-setlimit-note').val(user.note); }); - return h('li.cp-admin-limit', { - title: addClass ? title : '' - }, [ + if (compact) { + return h('tr.cp-admin-limit', { + title: title + }, [ + h('td', keyEl), + h('td.limit', Messages._getKey('admin_limit', [limit])), + h('td.plan', Messages._getKey('admin_limitPlan', [user.plan])), + h('td.note', Messages._getKey('admin_limitNote', [user.note])) + ]); + } + return h('li.cp-admin-limit', [ keyEl, h('ul.cp-limit-data', [ h('li.limit', Messages._getKey('admin_limit', [limit])), - //h('li.plan', Messages._getKey('admin_limitPlan', [user.plan])), + h('li.plan', Messages._getKey('admin_limitPlan', [user.plan])), h('li.note', Messages._getKey('admin_limitNote', [user.note])) ]) ]); }); - $div.append(h('ul.cp-admin-all-limits'+addClass, content)); + if (compact) { return $div.append(h('table.cp-admin-all-limits', content)); } + $div.append(h('ul.cp-admin-all-limits', content)); }); }; APP.refreshLimits(); @@ -248,8 +258,8 @@ define([ var user = h('input.cp-setlimit-key'); var $key = $(user); - var limit = h('input', {type: 'number', min: 0, value: 0}); - var note = h('input'); + var limit = h('input.cp-setlimit-quota', {type: 'number', min: 0, value: 0}); + var note = h('input.cp-setlimit-note'); var remove = h('button.btn.btn-danger', Messages.fc_remove); var set = h('button.btn.btn-primary', Messages.admin_setlimitButton); var form = h('div.cp-admin-setlimit-form', [ From d60e0726058038adefbdffb0b8ac092acb190e37 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 13 Oct 2020 16:31:01 +0200 Subject: [PATCH 31/34] Update last-modified --- www/pad/index.html | 1 + www/pad/inner.html | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/www/pad/index.html b/www/pad/index.html index e51c8ba9e..2ce18376d 100644 --- a/www/pad/index.html +++ b/www/pad/index.html @@ -10,3 +10,4 @@