feat(mounts): P4 — admin CRUD endpoints + frontend admin tab
Mounts are now configurable at runtime without DB surgery.
Backend (admin-gated, /api/admin/external-mounts):
- GET list all configured mounts
- POST create: validate the provider config up front, create a mount-root
folder under the admin's drive, insert the row, hot-reload the registry
- DELETE remove the mount row + its root folder (host content is left intact),
then hot-reload
- ExternalMountRepositoryPort gains create/delete (PG impl); registry.reload()
runs in-process so changes are live immediately
- audit lines event="external_mount.config" action=create|delete
Frontend:
- admin.ts endpoints: listExternalMounts / createExternalMount / deleteExternalMount
- new "External Mounts" tab in the admin page: add form (name + host path +
read-only) and a list with delete
Integration test for the repo create/delete round-trip (testcontainers).
This commit is contained in:
@@ -168,11 +168,46 @@ pub struct ExternalMountRecord {
|
||||
pub mount_path: String,
|
||||
}
|
||||
|
||||
/// The persistable columns of an `external_mounts` row (admin create).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NewExternalMount {
|
||||
/// The mount-root folder UUID this mount attaches to.
|
||||
pub mount_folder_id: Uuid,
|
||||
/// Provider kind.
|
||||
pub kind: String,
|
||||
/// Provider-specific connection config.
|
||||
pub config: serde_json::Value,
|
||||
/// Display name.
|
||||
pub name: String,
|
||||
/// Owner of the mount configuration.
|
||||
pub owner_id: Uuid,
|
||||
/// Whether the mount is read-only.
|
||||
pub read_only: bool,
|
||||
}
|
||||
|
||||
/// Persistence port for external mount configuration.
|
||||
#[async_trait]
|
||||
pub trait ExternalMountRepositoryPort: Send + Sync {
|
||||
/// Load every (non-trashed) mount joined with its folder, for registry build.
|
||||
async fn list_all(&self) -> Result<Vec<ExternalMountRecord>, DomainError>;
|
||||
|
||||
/// Insert a new mount row. Default errors — only the PG repo implements it
|
||||
/// (test doubles need `list_all` only).
|
||||
async fn create(&self, _mount: &NewExternalMount) -> Result<(), DomainError> {
|
||||
Err(DomainError::operation_not_supported(
|
||||
"ExternalMount",
|
||||
"create is not supported by this repository",
|
||||
))
|
||||
}
|
||||
|
||||
/// Delete a mount row by its mount-root folder id. Returns `true` when a
|
||||
/// row was removed. Default errors (see `create`).
|
||||
async fn delete(&self, _mount_folder_id: Uuid) -> Result<bool, DomainError> {
|
||||
Err(DomainError::operation_not_supported(
|
||||
"ExternalMount",
|
||||
"delete is not supported by this repository",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds [`ExternalMountProvider`]s from a `kind` + `config` pair.
|
||||
|
||||
Reference in New Issue
Block a user