mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-22 03:05:32 +02:00
feat: Extensions settings tab + capture dialog redesign with Advanced options
Settings/Extensions tab (admin-only): - New 'Extensions' tab between Cookies and Storage - ExtensionsTab component: shows uBlock Origin Lite card with pill toggle - Reads ublock_enabled from instance settings; patch via existing PATCH endpoint - Shows ublock_ext_available status from server (whether ARCHIVR_UBLOCK_EXT is set) Instance settings: - Add ublock_enabled BOOLEAN (default true) to instance_settings auth DB table - Idempotent ALTER TABLE migration in initialize_auth_schema() - get/update_instance_settings include ublock_enabled - GET /api/admin/instance-settings now also returns ublock_ext_available (computed from ARCHIVR_UBLOCK_EXT env var at request time) - PATCH /api/admin/instance-settings accepts ublock_enabled Per-capture override: - CaptureBody gains ublock_enabled: Option<bool> - CaptureConfig gains ublock_enabled: Option<bool> - singlefile::save() gains ublock_enabled_override: Option<bool> param - Capture handler resolves: body override > global instance setting > env var - submitCapture(aid, loc, qual, extensions) in api.js passes ublock_enabled Capture dialog redesign: - Archive button: full-width, 13px padding, min-width 220px, primary CTA - Cancel: full-width but text-style, below Archive - ‹Advanced options› chevron toggle (rotates on open) - Expanded panel shows uBlock toggle for this capture session - Loads global ublock_enabled default from instance settings on mount Styles: - .ext-toggle pill switch (44×24 and 36×20 small variant) - .ext-card for Settings Extensions tab - .capture-advanced + .capture-advanced-panel + .capture-chevron - .capture-ext-row / .capture-ext-label / .capture-ext-name / .capture-ext-desc - .form-hint utility class
This commit is contained in:
parent
8a90259a76
commit
1b3543ead8
13 changed files with 400 additions and 87 deletions
|
|
@ -77,6 +77,11 @@ impl PlatformMetadata {
|
|||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CaptureConfig {
|
||||
pub cookie_rules: Vec<database::CookieRule>,
|
||||
/// Override for uBlock Origin Lite during WebPage captures.
|
||||
/// `None` = derive from `ARCHIVR_UBLOCK` env var (existing behaviour).
|
||||
/// `Some(false)` = disabled by admin/user choice for this capture.
|
||||
/// `Some(true)` = explicitly enabled (still needs `ARCHIVR_UBLOCK_EXT` to be valid).
|
||||
pub ublock_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
/// Resolves which cookies apply to `url` by evaluating all rules in ordinal order.
|
||||
|
|
@ -1010,7 +1015,7 @@ pub fn perform_capture(
|
|||
|
||||
// Source: web page — archive as a self-contained HTML snapshot via single-file-cli
|
||||
if source == Source::WebPage {
|
||||
match downloader::singlefile::save(locator, store_path, ×tamp, &cookies) {
|
||||
match downloader::singlefile::save(locator, store_path, ×tamp, &cookies, config.ublock_enabled) {
|
||||
Ok(result) => {
|
||||
let file_extension = ".html".to_string();
|
||||
let temp_html = store_path
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ pub struct InstanceSettings {
|
|||
pub public_entry_content_enabled: bool,
|
||||
pub open_registration_enabled: bool, // maps to public_archive_submission_enabled column
|
||||
pub default_entry_visibility: u32,
|
||||
/// Global default for ad-blocking via uBlock Origin Lite during WebPage captures.
|
||||
/// Per-capture requests can override this.
|
||||
pub ublock_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
|
|
@ -460,13 +463,14 @@ pub fn initialize_auth_schema(conn: &Connection) -> Result<()> {
|
|||
public_index_enabled INTEGER NOT NULL DEFAULT 0 CHECK (public_index_enabled IN (0, 1)),
|
||||
public_entry_content_enabled INTEGER NOT NULL DEFAULT 0 CHECK (public_entry_content_enabled IN (0, 1)),
|
||||
public_archive_submission_enabled INTEGER NOT NULL DEFAULT 0 CHECK (public_archive_submission_enabled IN (0, 1)),
|
||||
default_entry_visibility INTEGER NOT NULL DEFAULT 2
|
||||
default_entry_visibility INTEGER NOT NULL DEFAULT 2,
|
||||
ublock_enabled INTEGER NOT NULL DEFAULT 1 CHECK (ublock_enabled IN (0, 1))
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO instance_settings
|
||||
(id, public_index_enabled, public_entry_content_enabled,
|
||||
public_archive_submission_enabled, default_entry_visibility)
|
||||
VALUES (1, 0, 0, 0, 2);
|
||||
public_archive_submission_enabled, default_entry_visibility, ublock_enabled)
|
||||
VALUES (1, 0, 0, 0, 2, 1);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
|
@ -500,6 +504,12 @@ pub fn initialize_auth_schema(conn: &Connection) -> Result<()> {
|
|||
[],
|
||||
);
|
||||
|
||||
// Add ublock_enabled column to instance_settings if not present (idempotent migration)
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE instance_settings ADD COLUMN ublock_enabled INTEGER NOT NULL DEFAULT 1",
|
||||
[],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -724,7 +734,8 @@ pub fn list_user_tokens(conn: &Connection, user_id: i64) -> Result<Vec<ApiTokenR
|
|||
pub fn get_instance_settings(conn: &Connection) -> Result<InstanceSettings> {
|
||||
conn.query_row(
|
||||
"SELECT public_index_enabled, public_entry_content_enabled,
|
||||
public_archive_submission_enabled, default_entry_visibility
|
||||
public_archive_submission_enabled, default_entry_visibility,
|
||||
COALESCE(ublock_enabled, 1)
|
||||
FROM instance_settings WHERE id = 1",
|
||||
[],
|
||||
|row| {
|
||||
|
|
@ -733,6 +744,7 @@ pub fn get_instance_settings(conn: &Connection) -> Result<InstanceSettings> {
|
|||
public_entry_content_enabled: row.get::<_, i64>(1)? != 0,
|
||||
open_registration_enabled: row.get::<_, i64>(2)? != 0,
|
||||
default_entry_visibility: row.get::<_, i64>(3)? as u32,
|
||||
ublock_enabled: row.get::<_, i64>(4)? != 0,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
|
@ -745,13 +757,15 @@ pub fn update_instance_settings(conn: &Connection, settings: &InstanceSettings)
|
|||
SET public_index_enabled = ?1,
|
||||
public_entry_content_enabled = ?2,
|
||||
public_archive_submission_enabled = ?3,
|
||||
default_entry_visibility = ?4
|
||||
default_entry_visibility = ?4,
|
||||
ublock_enabled = ?5
|
||||
WHERE id = 1",
|
||||
params![
|
||||
settings.public_index_enabled as i64,
|
||||
settings.public_entry_content_enabled as i64,
|
||||
settings.open_registration_enabled as i64,
|
||||
settings.default_entry_visibility as i64,
|
||||
settings.ublock_enabled as i64,
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -43,11 +43,12 @@ pub fn save(
|
|||
store_path: &Path,
|
||||
timestamp: &str,
|
||||
cookies: &HashMap<String, String>,
|
||||
ublock_enabled_override: Option<bool>,
|
||||
) -> Result<SaveResult> {
|
||||
let single_file =
|
||||
env::var("ARCHIVR_SINGLE_FILE").unwrap_or_else(|_| "single-file".to_string());
|
||||
let chrome = env::var("ARCHIVR_CHROME").unwrap_or_else(|_| "chromium".to_string());
|
||||
let (ublock_ext, ublock_skipped) = resolve_ublock_config();
|
||||
let (ublock_ext, ublock_skipped) = resolve_ublock_config(ublock_enabled_override);
|
||||
let mut result = save_with(
|
||||
url,
|
||||
store_path,
|
||||
|
|
@ -61,21 +62,26 @@ pub fn save(
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
/// Reads `ARCHIVR_UBLOCK` and `ARCHIVR_UBLOCK_EXT` and returns:
|
||||
/// Resolves uBlock configuration from env vars, optionally overridden by the caller.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `(Some(path), false)` — uBlock is enabled and the extension dir is valid.
|
||||
/// - `(None, true)` — uBlock is enabled but the extension dir is missing/invalid
|
||||
/// (warns to stderr; caller should surface this to the user).
|
||||
/// - `(None, false)` — uBlock is explicitly disabled (`ARCHIVR_UBLOCK=false/0`).
|
||||
fn resolve_ublock_config() -> (Option<PathBuf>, bool) {
|
||||
let enabled = env::var("ARCHIVR_UBLOCK").unwrap_or_else(|_| "true".to_string());
|
||||
if enabled.eq_ignore_ascii_case("false") || enabled == "0" {
|
||||
/// (warns to stderr; the capture proceeds without ad-blocking).
|
||||
/// - `(None, false)` — uBlock is disabled (`ARCHIVR_UBLOCK=false` or overridden).
|
||||
fn resolve_ublock_config(enabled_override: Option<bool>) -> (Option<PathBuf>, bool) {
|
||||
// The override (from instance settings or per-capture body) takes precedence over env.
|
||||
let want_ublock = enabled_override.unwrap_or_else(|| {
|
||||
let env_val = env::var("ARCHIVR_UBLOCK").unwrap_or_else(|_| "true".to_string());
|
||||
!env_val.eq_ignore_ascii_case("false") && env_val != "0"
|
||||
});
|
||||
if !want_ublock {
|
||||
return (None, false);
|
||||
}
|
||||
match env::var("ARCHIVR_UBLOCK_EXT").ok().filter(|s| !s.is_empty()) {
|
||||
None => {
|
||||
eprintln!(
|
||||
"warn: uBlock: ARCHIVR_UBLOCK=true but ARCHIVR_UBLOCK_EXT is not set; \
|
||||
"warn: uBlock: ARCHIVR_UBLOCK_EXT is not set; \
|
||||
capturing without ad-blocking"
|
||||
);
|
||||
(None, true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue