feat(docs): add API endpoints for deleting documentation versions and clearing Reticulum docs
- Implemented DELETE endpoints to allow users to delete specific documentation versions and clear all Reticulum documentation. - Enhanced the DocsManager class with methods for version deletion and clearing documentation, including error handling and logging. - Updated frontend components to support version deletion and clearing of Reticulum docs with user confirmation dialogs.
This commit is contained in:
@@ -3753,6 +3753,31 @@ class ReticulumMeshChat:
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
# delete docs version
|
||||
@routes.delete("/api/v1/docs/version/{version}")
|
||||
async def docs_delete_version(request):
|
||||
try:
|
||||
version = request.match_info.get("version")
|
||||
if not version:
|
||||
return web.json_response(
|
||||
{"error": "No version provided"},
|
||||
status=400,
|
||||
)
|
||||
|
||||
success = self.docs_manager.delete_version(version)
|
||||
return web.json_response({"success": success})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
# clear reticulum docs
|
||||
@routes.delete("/api/v1/maintenance/docs/reticulum")
|
||||
async def docs_clear(request):
|
||||
try:
|
||||
success = self.docs_manager.clear_reticulum_docs()
|
||||
return web.json_response({"success": success})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
# search docs
|
||||
@routes.get("/api/v1/docs/search")
|
||||
async def docs_search(request):
|
||||
@@ -8901,7 +8926,7 @@ class ReticulumMeshChat:
|
||||
|
||||
if "telemetry_enabled" in data:
|
||||
self.config.telemetry_enabled.set(
|
||||
self._parse_bool(data["telemetry_enabled"])
|
||||
self._parse_bool(data["telemetry_enabled"]),
|
||||
)
|
||||
|
||||
# update banishment settings
|
||||
@@ -10285,7 +10310,7 @@ class ReticulumMeshChat:
|
||||
commands.extend(val)
|
||||
elif isinstance(val, dict):
|
||||
commands.append(val)
|
||||
if 0x01 in lxmf_fields and 0x01 != LXMF.FIELD_COMMANDS:
|
||||
if 0x01 in lxmf_fields and LXMF.FIELD_COMMANDS != 0x01:
|
||||
val = lxmf_fields[0x01]
|
||||
if isinstance(val, list):
|
||||
commands.extend(val)
|
||||
@@ -10321,11 +10346,11 @@ class ReticulumMeshChat:
|
||||
else:
|
||||
# Check if peer is trusted
|
||||
contact = ctx.database.contacts.get_contact_by_identity_hash(
|
||||
source_hash
|
||||
source_hash,
|
||||
)
|
||||
if not contact or not contact.get("is_telemetry_trusted"):
|
||||
print(
|
||||
f"Telemetry request from untrusted peer {source_hash}, ignoring"
|
||||
f"Telemetry request from untrusted peer {source_hash}, ignoring",
|
||||
)
|
||||
else:
|
||||
print(f"Responding to telemetry request from {source_hash}")
|
||||
@@ -10342,7 +10367,7 @@ class ReticulumMeshChat:
|
||||
"remote_identity_name": source_hash[:8],
|
||||
"lxmf_message": convert_db_lxmf_message_to_dict(
|
||||
ctx.database.messages.get_lxmf_message_by_hash(
|
||||
lxmf_message.hash.hex()
|
||||
lxmf_message.hash.hex(),
|
||||
),
|
||||
include_attachments=False,
|
||||
),
|
||||
|
||||
@@ -134,6 +134,63 @@ class DocsManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_version(self, version):
|
||||
"""Deletes a specific version of documentation."""
|
||||
if version not in self.get_available_versions():
|
||||
return False
|
||||
|
||||
version_path = os.path.join(self.versions_dir, version)
|
||||
if not os.path.exists(version_path):
|
||||
return False
|
||||
|
||||
try:
|
||||
# If the deleted version is the current one, unlink 'current' first
|
||||
current_version = self.get_current_version()
|
||||
if current_version == version:
|
||||
if os.path.exists(self.docs_dir):
|
||||
if os.path.islink(self.docs_dir):
|
||||
os.unlink(self.docs_dir)
|
||||
else:
|
||||
shutil.rmtree(self.docs_dir)
|
||||
|
||||
shutil.rmtree(version_path)
|
||||
|
||||
# If we just deleted the current version, try to pick another one as current
|
||||
if current_version == version:
|
||||
self._update_current_link()
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.exception(f"Failed to delete docs version {version}: {e}")
|
||||
return False
|
||||
|
||||
def clear_reticulum_docs(self):
|
||||
"""Clears all Reticulum documentation and versions."""
|
||||
try:
|
||||
if os.path.exists(self.docs_base_dir):
|
||||
# We don't want to delete the base dir itself, just its contents
|
||||
# except possibly some metadata if we added any.
|
||||
# Actually, deleting everything inside reticulum-docs is fine.
|
||||
for item in os.listdir(self.docs_base_dir):
|
||||
item_path = os.path.join(self.docs_base_dir, item)
|
||||
if os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
else:
|
||||
os.remove(item_path)
|
||||
|
||||
# Re-create required subdirectories
|
||||
for d in [self.versions_dir, self.docs_dir]:
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
|
||||
self.config.docs_downloaded.set(False)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.exception(f"Failed to clear Reticulum docs: {e}")
|
||||
return False
|
||||
|
||||
def populate_meshchatx_docs(self):
|
||||
"""Populates meshchatx-docs from the project's docs folder."""
|
||||
# Try to find docs folder in several places
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
<button
|
||||
v-for="version in status.versions"
|
||||
:key="version"
|
||||
class="w-full px-4 py-2 text-left text-[11px] hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors flex items-center justify-between"
|
||||
class="w-full px-4 py-2 text-left text-[11px] hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors flex items-center justify-between group"
|
||||
:class="
|
||||
status.current_version === version
|
||||
? 'text-blue-600 dark:text-blue-400 font-bold'
|
||||
@@ -122,12 +122,23 @@
|
||||
"
|
||||
@click="switchVersion(version)"
|
||||
>
|
||||
<span>{{ version }}</span>
|
||||
<MaterialDesignIcon
|
||||
v-if="status.current_version === version"
|
||||
icon-name="check"
|
||||
class="w-3.5 h-3.5"
|
||||
/>
|
||||
<span class="truncate">{{ version }}</span>
|
||||
<div class="flex items-center space-x-1">
|
||||
<MaterialDesignIcon
|
||||
v-if="status.current_version === version"
|
||||
icon-name="check"
|
||||
class="w-3.5 h-3.5"
|
||||
/>
|
||||
<button
|
||||
v-if="status.versions.length > 1"
|
||||
type="button"
|
||||
class="p-1 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
title="Delete this version"
|
||||
@click.stop="deleteVersion(version)"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="delete" class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-if="status.versions.length === 0"
|
||||
@@ -715,6 +726,20 @@ export default {
|
||||
console.error("Failed to switch docs version:", error);
|
||||
}
|
||||
},
|
||||
async deleteVersion(version) {
|
||||
if (!confirm(`Are you sure you want to delete version "${version}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.axios.delete(`/api/v1/docs/version/${encodeURIComponent(version)}`);
|
||||
this.fetchStatus();
|
||||
ToastUtils.success(`Version ${version} deleted`);
|
||||
} catch (error) {
|
||||
console.error("Failed to delete docs version:", error);
|
||||
ToastUtils.error("Failed to delete version: " + (error.response?.data?.error || error.message));
|
||||
}
|
||||
},
|
||||
async handleZipUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
@@ -303,6 +303,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-maintenance border-orange-200 dark:border-orange-900/30 text-orange-700 dark:text-orange-300 bg-orange-50 dark:bg-orange-900/10 hover:bg-orange-100 dark:hover:bg-orange-900/20"
|
||||
@click="clearReticulumDocs"
|
||||
>
|
||||
<div class="flex flex-col items-start text-left">
|
||||
<div class="font-bold flex items-center gap-2">
|
||||
<MaterialDesignIcon icon-name="book-remove" class="size-4" />
|
||||
{{ $t("maintenance.clear_reticulum_docs") }}
|
||||
</div>
|
||||
<div class="text-xs opacity-80">
|
||||
{{ $t("maintenance.clear_reticulum_docs_desc") }}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 pt-2 border-t border-gray-100 dark:border-zinc-800">
|
||||
@@ -2218,6 +2234,15 @@ export default {
|
||||
ToastUtils.error(this.$t("common.error"));
|
||||
}
|
||||
},
|
||||
async clearReticulumDocs() {
|
||||
if (!(await DialogUtils.confirm(this.$t("maintenance.clear_confirm")))) return;
|
||||
try {
|
||||
await window.axios.delete("/api/v1/maintenance/docs/reticulum");
|
||||
ToastUtils.success(this.$t("maintenance.docs_cleared"));
|
||||
} catch {
|
||||
ToastUtils.error(this.$t("common.error"));
|
||||
}
|
||||
},
|
||||
async exportMessages() {
|
||||
try {
|
||||
const response = await window.axios.get("/api/v1/maintenance/messages/export");
|
||||
|
||||
Reference in New Issue
Block a user