aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-settings.js61
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/index-dock-model.js11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/index-dock.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css11
-rw-r--r--packages/meshbay-hub/tests/harness/photo_sync_probe.py4
-rw-r--r--packages/meshbay-hub/tests/test_indexing_dock.py58
16 files changed, 211 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
index a666c29..8ed8eec 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
@@ -46,6 +46,9 @@ async function readLocalNode(node, groupId, attempts = NODE_CHECK_ATTEMPTS,
// ── Shared Directories Table ────────────────────────────────────────────
+// How often the table asks its node what it could not read.
+const TROUBLE_POLL_MS = 10000;
+
/**
* A group's root directories, and the operator's controls over them.
*
@@ -98,6 +101,10 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
// reason nothing happened.
const [msg, setMsg] = useState(null);
const [indexProgress, setIndexProgress] = useState(null);
+ // What the node could not read and the folders it paused by itself, as its
+ // loopback API says them; `troubleTick` asks again after a change here.
+ const [trouble, setTrouble] = useState(null);
+ const [troubleTick, setTroubleTick] = useState(0);
const say = useCallback((text) => setMsg(text ? { text, error: false } : null), []);
const refuse = useCallback((text) => setMsg({ text, error: true }), []);
@@ -161,6 +168,26 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
const canEdit = isLocal || overMnp || overLoopback;
const canWiden = isLocal || onNodeMachine;
+ // Paths, so only where the loopback API answers: on the node's own machine.
+ // An operator elsewhere sees the folder paused, as any member does.
+ useEffect(() => {
+ if (!onNodeMachine) { setTrouble(null); return undefined; }
+ let stopped = false;
+ const read = async () => {
+ try {
+ const s = await platform.node.op('groupIndexStatus', { groupId });
+ const next = { unread: s.unread || [], total: s.unread_total || 0,
+ stalled: s.stalled || [] };
+ if (!stopped) {
+ setTrouble((prev) => JSON.stringify(prev) === JSON.stringify(next) ? prev : next);
+ }
+ } catch { /* busy, or mid-reload: the next poll asks again */ }
+ };
+ read();
+ const id = setInterval(read, TROUBLE_POLL_MS);
+ return () => { stopped = true; clearInterval(id); };
+ }, [onNodeMachine, groupId, troubleTick]);
+
// Deliberately no index refresh after a root change.
//
// Adding a root makes the node reload, which rescans — minutes on a real
@@ -182,7 +209,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
} catch (err) {
refuse(platform.bridgeMessage(err));
return false;
- } finally { setBusy(false); }
+ } finally { setBusy(false); setTroubleTick((n) => n + 1); }
}, [onRootsChange, refuse]);
const doUpdateRoot = useCallback(async (rootName, updates) => {
@@ -296,6 +323,34 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
<p class="settings-hint" style="margin-top:8px">
${t('settings_node.roots_local_only_hint')}</p>`;
+ // A folder the node paused because its disk stopped answering, and the
+ // files it gave up on. Under the table, with the paths: this is the list an
+ // operator used to need the node's log for.
+ const stalledAt = {};
+ for (const s of (trouble && trouble.stalled) || []) {
+ if (displayRoots.some((r) => r.name === s.root && r.paused)) stalledAt[s.root] = s.path;
+ }
+ const troubleNote = (isLocal || !trouble) ? '' : html`
+ ${Object.entries(stalledAt).map(([name, file]) => html`
+ <p class="error-msg" role="status" style="margin-top:10px" key=${name}>
+ ${t('node.root_stalled_hint', { name, file })}</p>`)}
+ ${trouble.total > 0 && html`
+ <details class="unread-files">
+ <summary>${t('node.unread_summary', { count: trouble.total })}</summary>
+ <ul>
+ ${trouble.unread.map((u) => html`
+ <li key=${u.path}>
+ <span class="unread-path">${u.path}</span>
+ <span class="unread-reason">${u.reason === 'no_answer'
+ ? t('node.unread_no_answer') : u.reason}</span>
+ </li>`)}
+ </ul>
+ ${trouble.total > trouble.unread.length && html`
+ <p class="settings-hint">${t('node.unread_more',
+ { count: trouble.total - trouble.unread.length })}</p>`}
+ </details>`}
+ `;
+
if (!displayRoots.length) {
return html`
<div class="shared-directories-table">
@@ -336,6 +391,9 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
<span class="node-root-badge node-root-badge-warn">${t('node.unavailable')}</span>`}
${!isLocal && r.paused && html`
<span class="node-root-badge">${t('node.root_paused')}</span>`}
+ ${!isLocal && stalledAt[r.name] && html`
+ <span class="node-root-badge node-root-badge-warn"
+ title=${stalledAt[r.name]}>${t('node.root_stalled')}</span>`}
</td>
${hasPaths && html`
<td class="sdt-col-path" title=${r.path || ''}>${r.path || ''}</td>`}
@@ -390,6 +448,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
`; })}
</tbody>
</table>
+ ${troubleNote}
${localOnlyHint}
${addControls}
${message}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/index-dock-model.js b/packages/meshbay-hub/src/meshbay_hub/static/index-dock-model.js
index ed81e7c..f0f9aa0 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/index-dock-model.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/index-dock-model.js
@@ -35,9 +35,18 @@ export function fromLoopback(g) {
filesDone: g.files_done || 0,
filesTotal: g.files_total || 0,
queued: Array.isArray(g.queued) ? g.queued : [],
+ // What the disk has given no answer on, and for how long. A path, so the
+ // loopback answer only: a push carries counters and nothing else.
+ slowPath: g.slow_path || '',
+ slowSecs: g.slow_secs || 0,
};
}
+/** The last segment of a path, whichever way its separators lean. */
+export function baseName(path) {
+ return String(path || '').split(/[\\/]/).filter(Boolean).pop() || '';
+}
+
export function fromPush(groupId, s, roots) {
const pos = Number.isInteger(s.root_pos) ? s.root_pos : -1;
const known = pos >= 0 && Array.isArray(roots) && roots[pos];
@@ -52,6 +61,8 @@ export function fromPush(groupId, s, roots) {
filesDone: s.files_done || 0,
filesTotal: s.files_total || 0,
queued: Number.isInteger(s.queued) ? s.queued : 0,
+ slowPath: '',
+ slowSecs: 0,
};
}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js b/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js
index 47a5818..3a9a539 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js
@@ -7,6 +7,7 @@ import { useStickyBand } from './sticky.js';
import { Icon } from './icon.js';
import {
fromLoopback, fromPush, mergeActivity, dockRows, hideRow, pauseRow, percent, queuedCount,
+ baseName,
} from './index-dock-model.js';
// ── Indexing dock ───────────────────────────────────────────────────────────
@@ -117,9 +118,14 @@ function DockRow({ row, error, onOpen, onPause, onHide }) {
: t(`indexdock.kind_${row.kind || 'scan'}`);
const where = row.root ? `${row.groupName} › ${row.root}` : row.groupName;
+ // A scan that sits still says what it is sitting on. It used to show a bar
+ // that had stopped, and the name of a folder.
+ const slow = !done && !error && row.slowPath;
let meta = '';
if (error) {
meta = error;
+ } else if (slow) {
+ meta = t('indexdock.slow', { secs: number(row.slowSecs), file: baseName(row.slowPath) });
} else if (listing) {
meta = t('indexdock.listing');
} else if (!done && !waiting) {
@@ -144,7 +150,8 @@ function DockRow({ row, error, onOpen, onPause, onHide }) {
onClick=${onOpen}>
<span class="index-dock-head">
<span class="index-dock-title"><strong>${status}</strong> ${where}</span>
- ${meta && html`<span class="index-dock-meta ${error ? 'index-dock-error' : ''}"
+ ${meta && html`<span class="index-dock-meta ${error || slow ? 'index-dock-error' : ''}"
+ title=${slow ? row.slowPath : null}
role=${error ? 'alert' : null}>${meta}</span>`}
</span>
<span class="index-progress-bar index-dock-bar">
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index afa1ba0..1fe2b36 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -880,6 +880,7 @@ export default {
'indexdock.open': "Einstellungen dieser Gruppe öffnen",
'indexdock.hide': "Bis zur nächsten Indizierung ausblenden",
'indexdock.pause': "Indexierung dieses Ordners pausieren",
+ 'indexdock.slow': "Seit {secs} s keine Antwort vom Datenträger: {file}",
'chat.load_older': '{n} ältere Nachrichten laden',
'chat.start_of_history': 'Anfang des Gesprächs',
'chat.today': 'Heute',
@@ -1002,6 +1003,11 @@ export default {
'node.root_pause': "Indexierung pausieren",
'node.root_resume': "Indexierung fortsetzen",
'node.root_paused': "Indexierung pausiert",
+ 'node.root_stalled': "Datenträger antwortet nicht",
+ 'node.root_stalled_hint': "Der Node hat die Indexierung von {name} pausiert: Der Datenträger hat bei {file} nicht geantwortet. Prüfen Sie den Datenträger oder verschieben Sie diese Datei, und setzen Sie dann die Indexierung fort.",
+ 'node.unread_summary': "Dateien, die nicht gelesen werden konnten: {count}",
+ 'node.unread_no_answer': "keine Antwort vom Datenträger",
+ 'node.unread_more': "Und {count} weitere.",
'node.root_remove_last': 'Eine Gruppe braucht mindestens ein Verzeichnis',
'node.root_path': 'Pfad',
'node.directory': 'Verzeichnis',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index b4346b9..642d3e2 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -993,6 +993,7 @@ export default {
'indexdock.open': "Open this group's settings",
'indexdock.hide': "Hide until the next indexing",
'indexdock.pause': "Pause indexing this folder",
+ 'indexdock.slow': "No answer from the disk for {secs} s: {file}",
'chat.load_older': 'Load {n} older messages',
'chat.start_of_history': 'Beginning of the conversation',
'chat.today': 'Today',
@@ -1070,6 +1071,11 @@ export default {
'node.root_pause': "Pause indexing",
'node.root_resume': "Resume indexing",
'node.root_paused': "indexing paused",
+ 'node.root_stalled': "disk not answering",
+ 'node.root_stalled_hint': "The node paused indexing of {name}: its disk gave no answer at {file}. Check the disk, or move that file out, then resume indexing.",
+ 'node.unread_summary': "Files that could not be read: {count}",
+ 'node.unread_no_answer': "no answer from the disk",
+ 'node.unread_more': "And {count} more.",
'node.root_remove_last': 'A group needs at least one directory',
'node.root_path': 'Path',
'node.root_added': 'Directory added.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index 9bb26e1..fe66af4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -874,6 +874,7 @@ export default {
'indexdock.open': "Abrir los ajustes de este grupo",
'indexdock.hide': "Ocultar hasta la próxima indexación",
'indexdock.pause': "Pausar la indexación de esta carpeta",
+ 'indexdock.slow': "El disco no responde desde hace {secs} s: {file}",
'chat.load_older': 'Cargar {n} mensajes anteriores',
'chat.start_of_history': 'Inicio de la conversación',
'chat.today': 'Hoy',
@@ -996,6 +997,11 @@ export default {
'node.root_pause': "Pausar la indexación",
'node.root_resume': "Reanudar la indexación",
'node.root_paused': "indexación en pausa",
+ 'node.root_stalled': "el disco no responde",
+ 'node.root_stalled_hint': "El nodo pausó la indexación de {name}: su disco no respondió en {file}. Revisa el disco, o saca ese archivo de la carpeta, y reanuda la indexación.",
+ 'node.unread_summary': "Archivos que no se pudieron leer: {count}",
+ 'node.unread_no_answer': "el disco no respondió",
+ 'node.unread_more': "Y {count} más.",
'node.root_remove_last': 'Un grupo necesita al menos un directorio',
'node.root_path': 'Ruta',
'node.directory': 'Directorio',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index 9f32a19..6296a8b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -877,6 +877,7 @@ export default {
'indexdock.open': "Ouvrir les paramètres de ce groupe",
'indexdock.hide': "Masquer jusqu'à la prochaine indexation",
'indexdock.pause': "Mettre en pause l'indexation de ce dossier",
+ 'indexdock.slow': "Pas de réponse du disque depuis {secs} s : {file}",
'chat.load_older': 'Charger {n} messages plus anciens',
'chat.start_of_history': 'Début de la conversation',
'chat.today': 'Aujourd’hui',
@@ -1005,6 +1006,11 @@ export default {
'node.root_pause': "Mettre l'indexation en pause",
'node.root_resume': "Reprendre l'indexation",
'node.root_paused': "indexation en pause",
+ 'node.root_stalled': "le disque ne répond pas",
+ 'node.root_stalled_hint': "Le node a mis en pause l'indexation de {name} : son disque n'a pas répondu sur {file}. Vérifiez le disque, ou sortez ce fichier du dossier, puis reprenez l'indexation.",
+ 'node.unread_summary': "Fichiers qui n'ont pas pu être lus : {count}",
+ 'node.unread_no_answer': "pas de réponse du disque",
+ 'node.unread_more': "Et {count} autres.",
'node.root_remove_last': 'Un groupe a besoin d\'au moins un répertoire',
'node.root_path': 'Chemin',
'node.root_added': 'Répertoire ajouté.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 5578028..0751692 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -876,6 +876,7 @@ export default {
'indexdock.open': "Apri le impostazioni di questo gruppo",
'indexdock.hide': "Nascondi fino alla prossima indicizzazione",
'indexdock.pause': "Sospendi l'indicizzazione di questa cartella",
+ 'indexdock.slow': "Nessuna risposta dal disco da {secs} s: {file}",
'chat.load_older': 'Carica {n} messaggi precedenti',
'chat.start_of_history': 'Inizio della conversazione',
'chat.today': 'Oggi',
@@ -1004,6 +1005,11 @@ export default {
'node.root_pause': "Sospendi l'indicizzazione",
'node.root_resume': "Riprendi l'indicizzazione",
'node.root_paused': "indicizzazione sospesa",
+ 'node.root_stalled': "il disco non risponde",
+ 'node.root_stalled_hint': "Il node ha sospeso l'indicizzazione di {name}: il disco non ha risposto su {file}. Controlla il disco, o sposta quel file altrove, poi riprendi l'indicizzazione.",
+ 'node.unread_summary': "File che non è stato possibile leggere: {count}",
+ 'node.unread_no_answer': "nessuna risposta dal disco",
+ 'node.unread_more': "E altri {count}.",
'node.root_remove_last': 'Un gruppo ha bisogno di almeno una directory',
'node.root_path': 'Percorso',
'node.directory': 'Directory',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index b5e091c..c3d4fa8 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -864,6 +864,7 @@ export default {
'indexdock.open': "このグループの設定を開く",
'indexdock.hide': "次のインデックスまで隠す",
'indexdock.pause': "このフォルダーのインデックス作成を一時停止",
+ 'indexdock.slow': "ディスクが {secs} 秒間応答していません: {file}",
'chat.load_older': '以前のメッセージを {n} 件読み込む',
'chat.start_of_history': '会話のはじまり',
'chat.today': '今日',
@@ -990,6 +991,11 @@ export default {
'node.root_pause': "インデックス作成を一時停止",
'node.root_resume': "インデックス作成を再開",
'node.root_paused': "インデックス作成を一時停止中",
+ 'node.root_stalled': "ディスクが応答しません",
+ 'node.root_stalled_hint': "ノードが {name} のインデックス作成を一時停止しました。{file} でディスクが応答しませんでした。ディスクを確認するか、そのファイルを移動してから、インデックス作成を再開してください。",
+ 'node.unread_summary': "読み取れなかったファイル: {count}",
+ 'node.unread_no_answer': "ディスクからの応答なし",
+ 'node.unread_more': "ほか {count} 件。",
'node.root_remove_last': 'グループには少なくとも 1 つのディレクトリが必要です',
'node.root_path': 'パス',
'node.directory': 'ディレクトリ',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index 86e01a2..c02be99 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -878,6 +878,7 @@ export default {
'indexdock.open': "Instellingen van deze groep openen",
'indexdock.hide': "Verbergen tot de volgende indexering",
'indexdock.pause': "Indexeren van deze map pauzeren",
+ 'indexdock.slow': "Al {secs} s geen antwoord van de schijf: {file}",
'chat.load_older': '{n} oudere berichten laden',
'chat.start_of_history': 'Begin van het gesprek',
'chat.today': 'Vandaag',
@@ -1006,6 +1007,11 @@ export default {
'node.root_pause': "Indexeren pauzeren",
'node.root_resume': "Indexeren hervatten",
'node.root_paused': "indexeren gepauzeerd",
+ 'node.root_stalled': "schijf antwoordt niet",
+ 'node.root_stalled_hint': "De node heeft het indexeren van {name} gepauzeerd: de schijf gaf geen antwoord bij {file}. Controleer de schijf, of verplaats dat bestand, en hervat daarna het indexeren.",
+ 'node.unread_summary': "Bestanden die niet gelezen konden worden: {count}",
+ 'node.unread_no_answer': "geen antwoord van de schijf",
+ 'node.unread_more': "En nog {count}.",
'node.root_remove_last': 'Een groep heeft minstens één map nodig',
'node.root_path': 'Pad',
'node.directory': 'Map',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index 090be3f..97e8b53 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -898,6 +898,7 @@ export default {
'indexdock.open': "Otwórz ustawienia tej grupy",
'indexdock.hide': "Ukryj do następnego indeksowania",
'indexdock.pause': "Wstrzymaj indeksowanie tego folderu",
+ 'indexdock.slow': "Brak odpowiedzi dysku od {secs} s: {file}",
'chat.load_older': 'Wczytaj {n} starszych wiadomości',
'chat.start_of_history': 'Początek rozmowy',
'chat.today': 'Dziś',
@@ -1030,6 +1031,11 @@ export default {
'node.root_pause': "Wstrzymaj indeksowanie",
'node.root_resume': "Wznów indeksowanie",
'node.root_paused': "indeksowanie wstrzymane",
+ 'node.root_stalled': "dysk nie odpowiada",
+ 'node.root_stalled_hint': "Węzeł wstrzymał indeksowanie {name}: dysk nie odpowiedział przy {file}. Sprawdź dysk albo przenieś ten plik, a potem wznów indeksowanie.",
+ 'node.unread_summary': "Pliki, których nie udało się odczytać: {count}",
+ 'node.unread_no_answer': "brak odpowiedzi dysku",
+ 'node.unread_more': "I jeszcze {count}.",
'node.root_remove_last': 'Grupa wymaga co najmniej jednego katalogu',
'node.root_path': 'Ścieżka',
'node.directory': 'Katalog',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index bfac600..8ff36a5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -875,6 +875,7 @@ export default {
'indexdock.open': "Abrir as configurações deste grupo",
'indexdock.hide': "Ocultar até a próxima indexação",
'indexdock.pause': "Pausar a indexação desta pasta",
+ 'indexdock.slow': "Sem resposta do disco há {secs} s: {file}",
'chat.load_older': 'Carregar {n} mensagens anteriores',
'chat.start_of_history': 'Início da conversa',
'chat.today': 'Hoje',
@@ -997,6 +998,11 @@ export default {
'node.root_pause': "Pausar a indexação",
'node.root_resume': "Retomar a indexação",
'node.root_paused': "indexação pausada",
+ 'node.root_stalled': "o disco não responde",
+ 'node.root_stalled_hint': "O node pausou a indexação de {name}: o disco não respondeu em {file}. Verifique o disco, ou tire esse arquivo da pasta, e retome a indexação.",
+ 'node.unread_summary': "Arquivos que não puderam ser lidos: {count}",
+ 'node.unread_no_answer': "sem resposta do disco",
+ 'node.unread_more': "E mais {count}.",
'node.root_remove_last': 'Um grupo precisa de pelo menos um diretório',
'node.root_path': 'Caminho',
'node.directory': 'Diretório',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index 367efa0..d4ad64f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -852,6 +852,7 @@ export default {
'indexdock.open': "打开此群组的设置",
'indexdock.hide': "隐藏,直到下一次索引",
'indexdock.pause': "暂停索引此文件夹",
+ 'indexdock.slow': "磁盘已 {secs} 秒没有响应:{file}",
'chat.load_older': '加载更早的 {n} 条消息',
'chat.start_of_history': '对话开头',
'chat.today': '今天',
@@ -978,6 +979,11 @@ export default {
'node.root_pause': "暂停索引",
'node.root_resume': "继续索引",
'node.root_paused': "索引已暂停",
+ 'node.root_stalled': "磁盘没有响应",
+ 'node.root_stalled_hint': "节点已暂停索引 {name}:磁盘在 {file} 处没有响应。请检查磁盘,或将该文件移出,然后恢复索引。",
+ 'node.unread_summary': "无法读取的文件:{count}",
+ 'node.unread_no_answer': "磁盘没有响应",
+ 'node.unread_more': "还有 {count} 个。",
'node.root_remove_last': '每个群组至少需要一个目录',
'node.root_path': '路径',
'node.directory': '目录',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 586f36d..a14bdc3 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -3681,6 +3681,17 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
font-size: 0.8em;
color: var(--text-dim);
}
+/* Files a scan could not read, with their paths: names somebody else chose. */
+.unread-files { margin-top: 10px; font-size: 0.9em; }
+.unread-files summary { cursor: pointer; color: var(--warn, #d97706); }
+.unread-files ul {
+ margin: 6px 0 0;
+ padding-left: 18px;
+ max-height: 240px;
+ overflow-y: auto;
+}
+.unread-files li { word-break: break-word; margin-bottom: 2px; }
+.unread-files .unread-reason { color: var(--text-dim); margin-left: 6px; }
.node-root-unavailable { opacity: 0.6; }
.node-message {
diff --git a/packages/meshbay-hub/tests/harness/photo_sync_probe.py b/packages/meshbay-hub/tests/harness/photo_sync_probe.py
index 3fcce8a..122103a 100644
--- a/packages/meshbay-hub/tests/harness/photo_sync_probe.py
+++ b/packages/meshbay-hub/tests/harness/photo_sync_probe.py
@@ -143,9 +143,11 @@ window.meshbay = {
<main class="main"><div id=root></div></main>
<script type=module>
import { html, render } from './vendor/htm-preact.js';
-import { initLocale } from './i18n.js';
+import { initLocale, setLocale } from './i18n.js';
import { startPhotoSync } from './photo-sync.js';
import { AndroidSyncPage } from './android-sync-page.js';
+// English, whatever the machine running this is set to.
+setLocale('en');
await initLocale();
startPhotoSync({
hub: '', getUser: () => ({ username: 'bob', userId: 'u-bob', token: 't' }),
diff --git a/packages/meshbay-hub/tests/test_indexing_dock.py b/packages/meshbay-hub/tests/test_indexing_dock.py
index 1619b57..4510dfc 100644
--- a/packages/meshbay-hub/tests/test_indexing_dock.py
+++ b/packages/meshbay-hub/tests/test_indexing_dock.py
@@ -224,3 +224,61 @@ def test_the_dock_polls_the_node_wide_route_not_one_group():
source = DOCK.read_text(encoding="utf-8")
assert "node.op('indexStatus')" in source
assert "groupIndexStatus" not in source
+
+
+# ── A disk that stops answering ─────────────────────────────────────────────
+
+@needs_node
+def test_a_silent_disk_is_named_by_the_loopback_answer_only(tmp_path):
+ """A scan that sat still showed a bar that had stopped and a folder's name.
+ What it is waiting on is a path: the node's own machine, never a push."""
+ paths = ["C:\\Music\\Some Album\\track 02.flac", "/srv/music/some album/track 02.flac", ""]
+ out = _run(tmp_path, f"""
+ const paths = {json.dumps(paths)};
+ console.log(JSON.stringify({{
+ local: m.fromLoopback({{ group_id: 'g', scanning: true, slow_path: paths[0],
+ slow_secs: 23 }}),
+ idle: m.fromLoopback({{ group_id: 'g', scanning: true }}),
+ push: m.fromPush('g', {{ scanning: true, slow_path: paths[0], slow_secs: 23 }}, []),
+ names: paths.map(m.baseName),
+ }}));
+ """)
+ assert (out["local"]["slowPath"], out["local"]["slowSecs"]) == (paths[0], 23)
+ assert (out["idle"]["slowPath"], out["idle"]["slowSecs"]) == ("", 0)
+ assert (out["push"]["slowPath"], out["push"]["slowSecs"]) == ("", 0), (
+ "a member's connection must not be able to put a path on screen")
+ assert out["names"] == ["track 02.flac", "track 02.flac", ""], "either separator"
+
+
+def test_the_row_says_what_the_disk_is_silent_on():
+ dock = DOCK.read_text(encoding="utf-8")
+ row = dock[dock.index("function DockRow("):dock.index("export function IndexingDock(")]
+ assert ("t('indexdock.slow', { secs: number(row.slowSecs), "
+ "file: baseName(row.slowPath) })") in row
+ # Before the percentage: a bar that no longer moves is not the news.
+ assert row.index("else if (slow)") < row.index("else if (listing)")
+ assert "title=${slow ? row.slowPath : null}" in row, "the whole path, on hover"
+
+
+def test_group_settings_list_what_the_node_could_not_read():
+ """With paths, so asked of the loopback API and of nothing else: an operator
+ elsewhere sees the folder paused, as any member does."""
+ settings = (STATIC / "group-settings.js").read_text(encoding="utf-8")
+ effect = settings[settings.index("if (!onNodeMachine) { setTrouble(null)"):]
+ effect = effect[:effect.index("}, [onNodeMachine, groupId, troubleTick]);")]
+ assert "platform.node.op('groupIndexStatus', { groupId })" in effect
+ assert "transport." not in effect
+
+ note = settings[settings.index("const troubleNote ="):]
+ note = note[:note.index("if (!displayRoots.length)")]
+ assert "t('node.root_stalled_hint', { name, file })" in note
+ assert "t('node.unread_summary', { count: trouble.total })" in note
+ assert "u.reason === 'no_answer'" in note and "t('node.unread_no_answer')" in note
+ # Only for a folder that is still paused: resumed, the note has had its day.
+ assert "r.name === s.root && r.paused" in settings
+ assert "${troubleNote}" in settings[settings.index('<table class="shared-dirs-tbl">'):]
+
+ css = (STATIC / "style.css").read_text(encoding="utf-8")
+ rule = css[css.index(".unread-files li {"):]
+ assert "word-break: break-word" in rule[:rule.index("}")], (
+ "a path has no spaces to break at, and unpins the page on a phone")