mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
refactor(docs): update Reticulum manual fetching process to remove GitHub update functionality and ensure build-time fetching with --force option
This commit is contained in:
parent
0c80fef6e9
commit
f62b203beb
19 changed files with 59 additions and 280 deletions
|
|
@ -126,13 +126,13 @@ CLI flags override environment variables when both are set.
|
|||
|
||||
## Reticulum manual bundle
|
||||
|
||||
The Reticulum HTML manual is fetched at build time. After cloning the repository, run:
|
||||
The Reticulum HTML manual is fetched from the upstream website **master** branch at build time. There is no in-app clearnet refresh. After cloning the repository, or before packaging a release, run:
|
||||
|
||||
```bash
|
||||
pnpm run build-docs
|
||||
```
|
||||
|
||||
This populates `meshchatx/public/reticulum-docs-bundled/current/`. Without that step the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP.
|
||||
That command always re-fetches (`--force`) into `meshchatx/public/reticulum-docs-bundled/current/`. CI release builds run the same step. Without a bundled copy the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP offline.
|
||||
|
||||
## Identity bootstrap
|
||||
|
||||
|
|
|
|||
|
|
@ -7808,27 +7808,6 @@ class ReticulumMeshChat:
|
|||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
# pull latest Reticulum manual from GitHub
|
||||
@routes.post("/api/v1/docs/update-from-github")
|
||||
async def docs_update_from_github(request):
|
||||
try:
|
||||
self._require_outbound_http("Reticulum docs update")
|
||||
if not self.docs_manager:
|
||||
return web.json_response(
|
||||
{"error": "Documentation manager is unavailable"},
|
||||
status=503,
|
||||
)
|
||||
loop = asyncio.get_running_loop()
|
||||
success, version = await loop.run_in_executor(
|
||||
None,
|
||||
self.docs_manager.update_from_github,
|
||||
)
|
||||
return web.json_response({"success": success, "version": version})
|
||||
except OutboundHttpBlockedError as e:
|
||||
return web.json_response({"error": str(e)}, status=403)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
# switch docs version
|
||||
@routes.post("/api/v1/docs/switch")
|
||||
async def docs_switch(request):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ import logging
|
|||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
from meshchatx.src.backend.markdown_renderer import MarkdownRenderer
|
||||
|
|
@ -18,16 +14,6 @@ from meshchatx.src.backend.markdown_renderer import MarkdownRenderer
|
|||
BUNDLED_DOCS_SUBDIR = os.path.join("reticulum-docs-bundled", "current")
|
||||
MANIFEST_FILENAME = "manifest.json"
|
||||
DOC_FILE_SUFFIXES = (".md", ".txt")
|
||||
RETICULUM_DOCS_GITHUB_URL = (
|
||||
"https://github.com/markqvist/reticulum_website/archive/refs/heads/main.zip"
|
||||
)
|
||||
RETICULUM_DOCS_ALLOWED_HOSTS = frozenset(
|
||||
{
|
||||
"github.com",
|
||||
"codeload.github.com",
|
||||
"objects.githubusercontent.com",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class DocsManager:
|
||||
|
|
@ -37,9 +23,8 @@ class DocsManager:
|
|||
``<public_dir>/reticulum-docs-bundled/current``. Users may upload a
|
||||
replacement archive which is extracted into ``<storage_dir>/reticulum-docs``
|
||||
and takes precedence at request time. Removing the user upload restores the
|
||||
bundled copy. Users can also pull the latest upstream ZIP from GitHub at
|
||||
runtime (blocked when privacy mode is enabled). Build-time staging still
|
||||
uses ``scripts/build/fetch_reticulum_manual.py``.
|
||||
bundled copy. There is no runtime download path. Fresh manuals are staged at
|
||||
build time with ``scripts/build/fetch_reticulum_manual.py`` (``pnpm run build-docs``).
|
||||
"""
|
||||
|
||||
def __init__(self, config, public_dir, project_root=None, storage_dir=None):
|
||||
|
|
@ -838,50 +823,6 @@ class DocsManager:
|
|||
logging.exception(f"Failed to upload docs: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _resolve_docs_source_url(source_url=None):
|
||||
url = (
|
||||
source_url
|
||||
or os.environ.get("MESHCHATX_RETICULUM_DOCS_URL")
|
||||
or RETICULUM_DOCS_GITHUB_URL
|
||||
).strip()
|
||||
if not url.lower().startswith("https://"):
|
||||
raise ValueError("docs source URL must be https")
|
||||
host = urllib.parse.urlparse(url).hostname or ""
|
||||
host = host.lower()
|
||||
if host not in RETICULUM_DOCS_ALLOWED_HOSTS and not host.endswith(
|
||||
".githubusercontent.com"
|
||||
):
|
||||
raise ValueError(f"docs source host not allowed: {host}")
|
||||
return url
|
||||
|
||||
def update_from_github(self, version=None, source_url=None, timeout=120.0):
|
||||
"""Download the Reticulum website docs ZIP and install it as a version."""
|
||||
url = self._resolve_docs_source_url(source_url)
|
||||
if not version:
|
||||
version = f"github-{time.strftime('%Y%m%d-%H%M%S')}"
|
||||
|
||||
self.upload_status = "downloading"
|
||||
self.upload_progress = 0
|
||||
self.last_error = None
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={"User-Agent": "MeshChatX-docs-update"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||
zip_bytes = response.read()
|
||||
if not zip_bytes:
|
||||
raise ValueError("downloaded archive is empty")
|
||||
success = self.upload_zip(zip_bytes, version)
|
||||
return success, version
|
||||
except Exception as e:
|
||||
self.last_error = str(e)
|
||||
self.upload_status = "error"
|
||||
logging.exception(f"Failed to update docs from GitHub: {e}")
|
||||
raise
|
||||
|
||||
def _extract_docs(self, zip_path, version):
|
||||
safe_version = os.path.basename(version)
|
||||
if not safe_version or safe_version in (".", ".."):
|
||||
|
|
|
|||
|
|
@ -186,22 +186,6 @@
|
|||
<MaterialDesignIcon icon-name="download" class="w-4 h-4 md:w-5 md:h-5" />
|
||||
</button>
|
||||
|
||||
<!-- Update from GitHub -->
|
||||
<button
|
||||
type="button"
|
||||
class="p-1.5 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-lg transition-colors"
|
||||
:class="{ 'opacity-50 pointer-events-none': docsBusy }"
|
||||
:title="$t('docs.btn_update_github')"
|
||||
:disabled="docsBusy"
|
||||
@click="updateFromGithub"
|
||||
>
|
||||
<MaterialDesignIcon
|
||||
:icon-name="docsBusy ? 'loading' : 'update'"
|
||||
:class="{ 'animate-spin': docsBusy }"
|
||||
class="w-4 h-4 md:w-5 md:h-5"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<!-- Share Reticulum Manual (re-uploadable ZIP) -->
|
||||
<button
|
||||
v-if="status.has_docs"
|
||||
|
|
@ -214,16 +198,22 @@
|
|||
|
||||
<!-- Upload Custom Manual -->
|
||||
<label
|
||||
:class="{ 'opacity-50 pointer-events-none': docsBusy }"
|
||||
:class="{ 'opacity-50 pointer-events-none': status.status === 'extracting' }"
|
||||
class="p-1.5 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-lg transition-colors cursor-pointer"
|
||||
:title="$t('docs.btn_upload')"
|
||||
>
|
||||
<MaterialDesignIcon
|
||||
:icon-name="docsBusy ? 'loading' : 'upload'"
|
||||
:class="{ 'animate-spin': docsBusy }"
|
||||
:icon-name="status.status === 'extracting' ? 'loading' : 'upload'"
|
||||
:class="{ 'animate-spin': status.status === 'extracting' }"
|
||||
class="w-4 h-4 md:w-5 md:h-5"
|
||||
/>
|
||||
<input type="file" accept=".zip" class="hidden" :disabled="docsBusy" @change="handleZipUpload" />
|
||||
<input
|
||||
type="file"
|
||||
accept=".zip"
|
||||
class="hidden"
|
||||
:disabled="status.status === 'extracting'"
|
||||
@change="handleZipUpload"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<!-- Open External -->
|
||||
|
|
@ -301,7 +291,10 @@
|
|||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div v-if="docsBusy" class="w-full h-1 bg-gray-200 dark:bg-zinc-800 overflow-hidden relative">
|
||||
<div
|
||||
v-if="status.status === 'extracting'"
|
||||
class="w-full h-1 bg-gray-200 dark:bg-zinc-800 overflow-hidden relative"
|
||||
>
|
||||
<div class="bg-blue-500 h-full transition-all duration-300" :style="{ width: status.progress + '%' }"></div>
|
||||
<div class="absolute inset-0 bg-blue-500/30 animate-pulse"></div>
|
||||
</div>
|
||||
|
|
@ -422,7 +415,7 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="docsBusy"
|
||||
v-if="status.status === 'extracting'"
|
||||
class="absolute inset-0 z-10 flex flex-col items-center justify-center bg-white/80 dark:bg-zinc-900/80 backdrop-blur-md"
|
||||
>
|
||||
<div class="relative w-24 h-24 mb-6">
|
||||
|
|
@ -434,13 +427,13 @@
|
|||
></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<MaterialDesignIcon
|
||||
:icon-name="status.status === 'downloading' ? 'cloud-download' : 'folder-zip-outline'"
|
||||
icon-name="folder-zip-outline"
|
||||
class="w-10 h-10 text-blue-600 animate-bounce"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-zinc-100 mb-1">
|
||||
{{ status.status === "downloading" ? $t("docs.status_downloading") : $t("docs.status_extracting") }}
|
||||
{{ $t("docs.status_extracting") }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-zinc-400">
|
||||
{{ $t("docs.complete_percent", { percent: status.progress }) }}
|
||||
|
|
@ -617,7 +610,9 @@
|
|||
></iframe>
|
||||
|
||||
<div
|
||||
v-else-if="activeTab === 'reticulum' && !status.has_docs && !docsBusy && !searchQuery"
|
||||
v-else-if="
|
||||
activeTab === 'reticulum' && !status.has_docs && status.status !== 'extracting' && !searchQuery
|
||||
"
|
||||
class="h-full flex flex-col items-center justify-center p-8 text-center space-y-4"
|
||||
>
|
||||
<div class="w-16 h-16 bg-gray-50 dark:bg-zinc-800/50 rounded-full flex items-center justify-center">
|
||||
|
|
@ -631,24 +626,13 @@
|
|||
{{ $t("docs.empty_state_hint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-2 bg-blue-600 text-white rounded-full text-xs font-bold hover:bg-blue-700 transition-colors shadow-lg shadow-blue-500/20 flex items-center justify-center gap-2"
|
||||
:disabled="docsBusy"
|
||||
@click="updateFromGithub"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="update" class="w-3.5 h-3.5" />
|
||||
<span>{{ $t("docs.btn_update_github") }}</span>
|
||||
</button>
|
||||
<label
|
||||
class="px-6 py-2 bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 rounded-full text-xs font-bold hover:opacity-90 transition-opacity cursor-pointer flex items-center justify-center gap-2"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="upload" class="w-3.5 h-3.5" />
|
||||
<span>{{ $t("docs.btn_upload") }}</span>
|
||||
<input type="file" accept=".zip" class="hidden" @change="handleZipUpload" />
|
||||
</label>
|
||||
</div>
|
||||
<label
|
||||
class="px-6 py-2 bg-blue-600 text-white rounded-full text-xs font-bold hover:bg-blue-700 transition-colors shadow-lg shadow-blue-500/20 cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="upload" class="w-3.5 h-3.5" />
|
||||
<span>{{ $t("docs.btn_upload") }}</span>
|
||||
<input type="file" accept=".zip" class="hidden" @change="handleZipUpload" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -699,7 +683,7 @@ export default {
|
|||
selectedDocPath: null,
|
||||
selectedDocContent: null,
|
||||
selectedReticulumPath: null,
|
||||
githubUpdatePending: false,
|
||||
reticulumDocsCacheBust: 0,
|
||||
languages: {
|
||||
en: "English",
|
||||
de: "Deutsch",
|
||||
|
|
@ -719,10 +703,17 @@ export default {
|
|||
return this.$i18n.locale;
|
||||
},
|
||||
localDocsUrl() {
|
||||
let path;
|
||||
if (this.selectedReticulumPath) {
|
||||
return `/reticulum-docs/${this.selectedReticulumPath}`;
|
||||
path = `/reticulum-docs/${this.selectedReticulumPath}`;
|
||||
} else {
|
||||
path = bundledReticulumDocsUrl(this.currentLang);
|
||||
}
|
||||
return bundledReticulumDocsUrl(this.currentLang);
|
||||
if (this.reticulumDocsCacheBust) {
|
||||
const sep = path.includes("?") ? "&" : "?";
|
||||
return `${path}${sep}v=${this.reticulumDocsCacheBust}`;
|
||||
}
|
||||
return path;
|
||||
},
|
||||
allLanguages() {
|
||||
return Object.entries(this.languages).map(([code, name]) => ({
|
||||
|
|
@ -737,11 +728,6 @@ export default {
|
|||
reticulumDocsQueryParam() {
|
||||
return this.$route?.query?.reticulum;
|
||||
},
|
||||
docsBusy() {
|
||||
return (
|
||||
this.githubUpdatePending || this.status.status === "downloading" || this.status.status === "extracting"
|
||||
);
|
||||
},
|
||||
visibleDocSections() {
|
||||
const lang = this.meshchatxDocsLang;
|
||||
const fallback = this.defaultDocsLanguage || "en";
|
||||
|
|
@ -963,6 +949,7 @@ export default {
|
|||
}
|
||||
);
|
||||
this.fetchStatus();
|
||||
this.reticulumDocsCacheBust = Date.now();
|
||||
ToastUtils.success(this.$t("docs.upload_success"));
|
||||
} catch (error) {
|
||||
console.error("Failed to upload docs zip:", error);
|
||||
|
|
@ -970,37 +957,6 @@ export default {
|
|||
DialogUtils.alert(this.$t("docs.failed_upload_alert", { message }), "error");
|
||||
}
|
||||
},
|
||||
async updateFromGithub() {
|
||||
if (this.docsBusy) {
|
||||
return;
|
||||
}
|
||||
this.githubUpdatePending = true;
|
||||
this.status = {
|
||||
...this.status,
|
||||
status: "downloading",
|
||||
progress: 0,
|
||||
last_error: null,
|
||||
};
|
||||
try {
|
||||
const response = await window.api.post("/api/v1/docs/update-from-github");
|
||||
const version = response.data?.version;
|
||||
await this.fetchStatus();
|
||||
this.activeTab = "reticulum";
|
||||
this.selectedReticulumPath = null;
|
||||
ToastUtils.success(
|
||||
version
|
||||
? this.$t("docs.update_github_success_version", { version })
|
||||
: this.$t("docs.update_github_success")
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to update docs from GitHub:", error);
|
||||
const message = error.response?.data?.error || error.message || "";
|
||||
DialogUtils.alert(this.$t("docs.failed_update_github", { message }), "error");
|
||||
await this.fetchStatus();
|
||||
} finally {
|
||||
this.githubUpdatePending = false;
|
||||
}
|
||||
},
|
||||
async exportDocs() {
|
||||
window.location.href = "/api/v1/docs/export";
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1967,19 +1967,14 @@
|
|||
"prompt_version_name": "Versionsname für diesen Upload eingeben:",
|
||||
"status_title": "Dokumentationsstatus",
|
||||
"status_extracting": "Wird entpackt...",
|
||||
"status_downloading": "Dokumentation wird heruntergeladen...",
|
||||
"status_available": "Offline-Handbuch verfügbar",
|
||||
"status_not_available": "Handbuch nicht verfügbar",
|
||||
"btn_upload": "Handbuch hochladen",
|
||||
"btn_update_github": "Von GitHub aktualisieren",
|
||||
"btn_share": "Reticulum-Handbuch als wieder hochladbares ZIP teilen",
|
||||
"empty_state_hint": "Lade das neueste Reticulum-Handbuch von GitHub, oder lade ein ZIP hoch, um es offline anzuzeigen.",
|
||||
"empty_state_hint": "Lade ein ZIP des Reticulum-Handbuchs hoch, um es offline anzuzeigen. Releases enthalten eine gebündelte Offline-Kopie aus dem Build.",
|
||||
"error": "Fehler",
|
||||
"failed_upload_docs": "Hochladen der Dokumentation fehlgeschlagen",
|
||||
"upload_success": "Dokumentation hochgeladen",
|
||||
"update_github_success": "Reticulum-Handbuch von GitHub aktualisiert",
|
||||
"update_github_success_version": "Reticulum-Handbuch aktualisiert ({version})",
|
||||
"failed_update_github": "Aktualisierung von GitHub fehlgeschlagen: {message}",
|
||||
"docs_link_copied": "Dokumentationslink in Zwischenablage kopiert",
|
||||
"failed_copy_link": "Link kopieren fehlgeschlagen",
|
||||
"load_list_failed": "Anleitungsliste konnte nicht geladen werden. Bitte später erneut versuchen.",
|
||||
|
|
|
|||
|
|
@ -2087,23 +2087,14 @@
|
|||
"status_available": "Offline Manual Available",
|
||||
"status_not_available": "Manual Not Available",
|
||||
"btn_upload": "Upload Manual",
|
||||
"btn_update_github": "Update from GitHub",
|
||||
"btn_share": "Share Reticulum manual as a re-uploadable ZIP",
|
||||
"empty_state_hint": "Pull the latest Reticulum manual from GitHub, or upload a ZIP to view it offline.",
|
||||
"error": "Error",
|
||||
"failed_upload_docs": "Failed to upload documentation",
|
||||
"upload_success": "Documentation uploaded",
|
||||
"update_github_success": "Reticulum manual updated from GitHub",
|
||||
"update_github_success_version": "Reticulum manual updated ({version})",
|
||||
"failed_update_github": "Failed to update from GitHub: {message}",
|
||||
"status_downloading": "Downloading Documentation...",
|
||||
"docs_link_copied": "Documentation link copied to clipboard",
|
||||
"failed_copy_link": "Failed to copy link",
|
||||
"load_list_failed": "Could not load the guide list. Try again in a moment.",
|
||||
"load_doc_failed": "Could not load this guide.",
|
||||
"search_failed": "Search failed. Check your connection and try again.",
|
||||
"manifest_warning": "The documentation index file could not be read. Showing available files without section grouping.",
|
||||
"failed_upload_alert": "Failed to upload documentation: {message}"
|
||||
"failed_upload_alert": "Failed to upload documentation: {message}",
|
||||
"empty_state_hint": "Upload a Reticulum manual ZIP to view it offline. Releases ship a bundled offline copy built at packaging time."
|
||||
},
|
||||
"licenses": {
|
||||
"section_label": "Legal",
|
||||
|
|
|
|||
|
|
@ -2084,19 +2084,14 @@
|
|||
"prompt_version_name": "Introduce un nombre de versión para esta subida:",
|
||||
"status_title": "Situación de la documentación",
|
||||
"status_extracting": "Extrayendo...",
|
||||
"status_downloading": "Descargando documentación...",
|
||||
"status_available": "Manual disponible",
|
||||
"status_not_available": "Manual no disponible",
|
||||
"btn_upload": "Subir Manual",
|
||||
"btn_update_github": "Actualizar desde GitHub",
|
||||
"btn_share": "Compartir el manual de Reticulum como ZIP reimportable",
|
||||
"empty_state_hint": "Descarga el manual de Reticulum más reciente desde GitHub, o sube un ZIP para verlo sin conexión.",
|
||||
"empty_state_hint": "Sube un ZIP del manual de Reticulum para consultarlo sin conexión. Las versiones incluyen una copia sin conexión empaquetada en el build.",
|
||||
"error": "Error",
|
||||
"failed_upload_docs": "Error al subir la documentación",
|
||||
"upload_success": "Documentación subida",
|
||||
"update_github_success": "Manual de Reticulum actualizado desde GitHub",
|
||||
"update_github_success_version": "Manual de Reticulum actualizado ({version})",
|
||||
"failed_update_github": "Error al actualizar desde GitHub: {message}",
|
||||
"docs_link_copied": "Enlace de documentación copiado al portapapeles",
|
||||
"failed_copy_link": "No se pudo copiar el enlace",
|
||||
"load_list_failed": "No se pudo cargar la lista de guías. Inténtalo de nuevo en un momento.",
|
||||
|
|
|
|||
|
|
@ -2084,19 +2084,14 @@
|
|||
"prompt_version_name": "Anna tälle lataukselle version nimi:",
|
||||
"status_title": "Dokumentaation tila",
|
||||
"status_extracting": "Viedään dokumentaatiota...",
|
||||
"status_downloading": "Ladataan dokumentaatiota...",
|
||||
"status_available": "Paikallinen ohjekirja saatavilla",
|
||||
"status_not_available": "Ohjekirja ei ole saatavilla",
|
||||
"btn_upload": "Tuo ohjekirja",
|
||||
"btn_update_github": "Päivitä GitHubista",
|
||||
"btn_share": "Jaa Reticulum-ohjekirja ZIP-tiedostona, jonka voi myöhemmin tuoda sovellukseen",
|
||||
"empty_state_hint": "Hae uusin Reticulum-ohjekirja GitHubista tai tuo ZIP paikallista käyttöä varten.",
|
||||
"empty_state_hint": "Tuo Reticulum-ohjekirjan ZIP paikallista käyttöä varten. Julkaisut sisältävät buildissa pakatun offline-kopion.",
|
||||
"error": "Virhe",
|
||||
"failed_upload_docs": "Dokumentaation tuominen epäonnistui",
|
||||
"upload_success": "Dokumentaatio ladattu",
|
||||
"update_github_success": "Reticulum-ohjekirja päivitetty GitHubista",
|
||||
"update_github_success_version": "Reticulum-ohjekirja päivitetty ({version})",
|
||||
"failed_update_github": "Päivitys GitHubista epäonnistui: {message}",
|
||||
"docs_link_copied": "Dokumentaation linkki kopioitu leikepöydälle",
|
||||
"failed_copy_link": "Linkin kopiointi epäonnistui",
|
||||
"load_list_failed": "Ohjeluetteloa ei voitu ladata. Yritä hetken kuluttua uudelleen.",
|
||||
|
|
|
|||
|
|
@ -2084,19 +2084,14 @@
|
|||
"prompt_version_name": "Entrez un nom de version pour ce téléversement :",
|
||||
"status_title": "État de la documentation",
|
||||
"status_extracting": "Extraction...",
|
||||
"status_downloading": "Téléchargement de la documentation...",
|
||||
"status_available": "Manuel hors ligne disponible",
|
||||
"status_not_available": "Manuel non disponible",
|
||||
"btn_upload": "Téléverser le manuel",
|
||||
"btn_update_github": "Mettre à jour depuis GitHub",
|
||||
"btn_share": "Partager le manuel Reticulum sous forme de ZIP réimportable",
|
||||
"empty_state_hint": "Récupérez le dernier manuel Reticulum depuis GitHub, ou téléversez une archive ZIP pour le consulter hors ligne.",
|
||||
"empty_state_hint": "Téléversez une archive ZIP du manuel Reticulum pour la consulter hors ligne. Les versions livrent une copie hors ligne intégrée au build.",
|
||||
"error": "Erreur",
|
||||
"failed_upload_docs": "Échec du téléversement de la documentation",
|
||||
"upload_success": "Documentation téléversée",
|
||||
"update_github_success": "Manuel Reticulum mis à jour depuis GitHub",
|
||||
"update_github_success_version": "Manuel Reticulum mis à jour ({version})",
|
||||
"failed_update_github": "Échec de la mise à jour depuis GitHub : {message}",
|
||||
"docs_link_copied": "Lien de documentation copié dans le presse-papiers",
|
||||
"failed_copy_link": "Impossible de copier le lien",
|
||||
"load_list_failed": "Impossible de charger la liste des guides. Réessayez dans un instant.",
|
||||
|
|
|
|||
|
|
@ -2136,19 +2136,14 @@
|
|||
"prompt_version_name": "Inserisci un nome di versione per questo caricamento:",
|
||||
"status_title": "Stato Documentazione",
|
||||
"status_extracting": "Estrazione in corso...",
|
||||
"status_downloading": "Download della documentazione...",
|
||||
"status_available": "Manuale Offline Disponibile",
|
||||
"status_not_available": "Manuale Non Disponibile",
|
||||
"btn_upload": "Carica Manuale",
|
||||
"btn_update_github": "Aggiorna da GitHub",
|
||||
"btn_share": "Condividi il manuale Reticulum come ZIP ricaricabile",
|
||||
"empty_state_hint": "Scarica l'ultimo manuale Reticulum da GitHub, oppure carica uno ZIP per consultarlo offline.",
|
||||
"empty_state_hint": "Carica uno ZIP del manuale Reticulum per consultarlo offline. Le release includono una copia offline inclusa nel build.",
|
||||
"error": "Errore",
|
||||
"failed_upload_docs": "Caricamento della documentazione non riuscito",
|
||||
"upload_success": "Documentazione caricata",
|
||||
"update_github_success": "Manuale Reticulum aggiornato da GitHub",
|
||||
"update_github_success_version": "Manuale Reticulum aggiornato ({version})",
|
||||
"failed_update_github": "Aggiornamento da GitHub non riuscito: {message}",
|
||||
"docs_link_copied": "Link alla documentazione copiato negli appunti",
|
||||
"failed_copy_link": "Impossibile copiare il link",
|
||||
"load_list_failed": "Impossibile caricare l'elenco delle guide. Riprova tra un momento.",
|
||||
|
|
|
|||
|
|
@ -2084,19 +2084,14 @@
|
|||
"prompt_version_name": "Voer een versienaam in voor deze upload:",
|
||||
"status_title": "Documentatiestatus",
|
||||
"status_extracting": "Uitpakken...",
|
||||
"status_downloading": "Documentatie downloaden...",
|
||||
"status_available": "Offline handleiding beschikbaar",
|
||||
"status_not_available": "Handleiding niet beschikbaar",
|
||||
"btn_upload": "Handleiding uploaden",
|
||||
"btn_update_github": "Bijwerken vanaf GitHub",
|
||||
"btn_share": "Reticulum-handleiding delen als opnieuw uploadbare ZIP",
|
||||
"empty_state_hint": "Haal de nieuwste Reticulum-handleiding van GitHub, of upload een ZIP om deze offline te bekijken.",
|
||||
"empty_state_hint": "Upload een ZIP van de Reticulum-handleiding om deze offline te bekijken. Releases bevatten een gebundelde offline kopie uit de build.",
|
||||
"error": "Fout",
|
||||
"failed_upload_docs": "Documentatie uploaden mislukt",
|
||||
"upload_success": "Documentatie geüpload",
|
||||
"update_github_success": "Reticulum-handleiding bijgewerkt vanaf GitHub",
|
||||
"update_github_success_version": "Reticulum-handleiding bijgewerkt ({version})",
|
||||
"failed_update_github": "Bijwerken vanaf GitHub mislukt: {message}",
|
||||
"docs_link_copied": "Documentatiekoppeling gekopieerd naar klembord",
|
||||
"failed_copy_link": "Kopiëren van verwijzing is mislukt",
|
||||
"load_list_failed": "Kan de gidsenlijst niet laden. Probeer het zo opnieuw.",
|
||||
|
|
|
|||
|
|
@ -1967,19 +1967,14 @@
|
|||
"prompt_version_name": "Введите имя версии для этой загрузки:",
|
||||
"status_title": "Статус документации",
|
||||
"status_extracting": "Извлечение...",
|
||||
"status_downloading": "Загрузка документации...",
|
||||
"status_available": "Доступно офлайн",
|
||||
"status_not_available": "Руководство недоступно",
|
||||
"btn_upload": "Загрузить руководство",
|
||||
"btn_update_github": "Обновить с GitHub",
|
||||
"btn_share": "Поделиться руководством Reticulum как загружаемым ZIP",
|
||||
"empty_state_hint": "Загрузите актуальное руководство Reticulum с GitHub или загрузите ZIP для офлайн-просмотра.",
|
||||
"empty_state_hint": "Загрузите ZIP руководства Reticulum для офлайн-просмотра. В релизах есть встроенная офлайн-копия из сборки.",
|
||||
"error": "Ошибка",
|
||||
"failed_upload_docs": "Не удалось загрузить документацию",
|
||||
"upload_success": "Документация загружена",
|
||||
"update_github_success": "Руководство Reticulum обновлено с GitHub",
|
||||
"update_github_success_version": "Руководство Reticulum обновлено ({version})",
|
||||
"failed_update_github": "Не удалось обновить с GitHub: {message}",
|
||||
"docs_link_copied": "Ссылка на документацию скопирована в буфер обмена",
|
||||
"failed_copy_link": "Не удалось скопировать ссылку",
|
||||
"load_list_failed": "Не удалось загрузить список справок. Повторите попытку позже.",
|
||||
|
|
|
|||
|
|
@ -2084,19 +2084,14 @@
|
|||
"prompt_version_name": "为此上传输入版本名称:",
|
||||
"status_title": "文件状况",
|
||||
"status_extracting": "正在解压...",
|
||||
"status_downloading": "正在下载文档...",
|
||||
"status_available": "离线手册可用",
|
||||
"status_not_available": "手册不可用",
|
||||
"btn_upload": "上传手册",
|
||||
"btn_update_github": "从 GitHub 更新",
|
||||
"btn_share": "以可重新上传的 ZIP 共享 Reticulum 手册",
|
||||
"empty_state_hint": "从 GitHub 拉取最新 Reticulum 手册,或上传 ZIP 以便离线查看。",
|
||||
"empty_state_hint": "上传 Reticulum 手册 ZIP 以便离线查看。发行版在构建时打包离线副本。",
|
||||
"error": "错误",
|
||||
"failed_upload_docs": "上传文档失败",
|
||||
"upload_success": "文档已上传",
|
||||
"update_github_success": "已从 GitHub 更新 Reticulum 手册",
|
||||
"update_github_success_version": "已更新 Reticulum 手册({version})",
|
||||
"failed_update_github": "从 GitHub 更新失败:{message}",
|
||||
"docs_link_copied": "复制到剪贴板的文档链接",
|
||||
"failed_copy_link": "复制链接失败",
|
||||
"load_list_failed": "无法加载指南列表。请稍后再试。",
|
||||
|
|
|
|||
|
|
@ -126,13 +126,13 @@ CLI flags override environment variables when both are set.
|
|||
|
||||
## Reticulum manual bundle
|
||||
|
||||
The Reticulum HTML manual is fetched at build time. After cloning the repository, run:
|
||||
The Reticulum HTML manual is fetched from the upstream website **master** branch at build time. There is no in-app clearnet refresh. After cloning the repository, or before packaging a release, run:
|
||||
|
||||
```bash
|
||||
pnpm run build-docs
|
||||
```
|
||||
|
||||
This populates `meshchatx/public/reticulum-docs-bundled/current/`. Without that step the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP.
|
||||
That command always re-fetches (`--force`) into `meshchatx/public/reticulum-docs-bundled/current/`. CI release builds run the same step. Without a bundled copy the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP offline.
|
||||
|
||||
## Identity bootstrap
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"predev": "node scripts/sync-meshchatx-docs.js",
|
||||
"build-frontend": "vite build",
|
||||
"build-backend": "node scripts/build-backend.js",
|
||||
"build-docs": "python3 scripts/build/fetch_reticulum_manual.py",
|
||||
"build-docs": "python3 scripts/build/fetch_reticulum_manual.py --force",
|
||||
"build-repository-wheels": "python3 scripts/build/fetch_repository_wheels.py",
|
||||
"version:sync": "node scripts/sync_version.js",
|
||||
"build": "pnpm run version:sync && pnpm run build-frontend && pnpm run build-docs && pnpm run build-repository-wheels && pnpm run build-backend",
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ from datetime import UTC, datetime
|
|||
from pathlib import Path
|
||||
|
||||
DEFAULT_SOURCES = (
|
||||
"https://github.com/markqvist/reticulum_website/archive/refs/heads/main.zip",
|
||||
"https://codeload.github.com/markqvist/reticulum_website/zip/refs/heads/master",
|
||||
)
|
||||
|
||||
DEFAULT_DEST = (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"dest": "meshchatx/public/reticulum-docs-bundled/current",
|
||||
"fetched_utc": "2026-07-07T05:31:03Z",
|
||||
"html_files": 201,
|
||||
"fetched_utc": "2026-07-10T17:45:25Z",
|
||||
"html_files": 206,
|
||||
"skipped_binary_files": 2,
|
||||
"source_url": "https://github.com/markqvist/reticulum_website/archive/refs/heads/main.zip"
|
||||
"source_url": "https://codeload.github.com/markqvist/reticulum_website/zip/refs/heads/master"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,10 +268,6 @@
|
|||
"method": "POST",
|
||||
"path": "/api/v1/docs/upload"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/docs/update-from-github"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/v1/docs/version/{version}"
|
||||
|
|
|
|||
|
|
@ -544,47 +544,3 @@ def test_get_doc_content_returns_none_on_read_error(tmp_path, monkeypatch):
|
|||
|
||||
monkeypatch.setattr("builtins.open", fail_open)
|
||||
assert dm.get_doc_content("en/guide.md") is None
|
||||
|
||||
|
||||
def test_resolve_docs_source_url_rejects_non_https():
|
||||
with pytest.raises(ValueError, match="https"):
|
||||
DocsManager._resolve_docs_source_url(
|
||||
"http://github.com/markqvist/reticulum_website/archive/refs/heads/main.zip"
|
||||
)
|
||||
|
||||
|
||||
def test_resolve_docs_source_url_rejects_unknown_host():
|
||||
with pytest.raises(ValueError, match="not allowed"):
|
||||
DocsManager._resolve_docs_source_url("https://evil.example/docs.zip")
|
||||
|
||||
|
||||
def test_update_from_github_downloads_and_installs(docs_manager, monkeypatch):
|
||||
payload = _make_docs_zip(
|
||||
files={
|
||||
"reticulum_website-main/docs/index.html": "<html>github</html>",
|
||||
},
|
||||
)
|
||||
|
||||
class FakeResponse:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args):
|
||||
return False
|
||||
|
||||
def read(self):
|
||||
return payload
|
||||
|
||||
monkeypatch.setattr(
|
||||
"meshchatx.src.backend.docs_manager.urllib.request.urlopen",
|
||||
lambda *_args, **_kwargs: FakeResponse(),
|
||||
)
|
||||
|
||||
success, version = docs_manager.update_from_github(version="github-test")
|
||||
assert success is True
|
||||
assert version == "github-test"
|
||||
assert docs_manager.upload_status == "completed"
|
||||
resolved = docs_manager.find_docs_file("index.html")
|
||||
assert resolved is not None
|
||||
with open(resolved) as fh:
|
||||
assert "github" in fh.read()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue