Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1aae27b0f4 | |||
| 663dbc3cc2 | |||
| d2d181de4a | |||
| 31d45ca1db | |||
| a086956b48 | |||
| 76cfe62f08 |
@@ -2,8 +2,8 @@ name: Build Library
|
||||
run-name: ${{ gitea.actor }}
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- RustImplementation
|
||||
branches-ignore:
|
||||
- master
|
||||
paths:
|
||||
- airlock_libs/**
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class MultiAgentSelector(Widget):
|
||||
|
||||
def compose(self):
|
||||
yield Header(show_clock=True, icon="⚙")
|
||||
title_text = Static("🖥️ Agent Selector", id="selector_title")
|
||||
title_text = Static("🖥️ Agent Selector", id="selector_title")
|
||||
title_text.styles.margin = (0, 0, 0, 1)
|
||||
yield title_text
|
||||
|
||||
@@ -175,7 +175,7 @@ class MultiAgentSelector(Widget):
|
||||
match_list.add_option((name, name))
|
||||
unmatched_label = self.query_one("#unmatched_label", Static)
|
||||
if unmatched:
|
||||
unmatched_label.update(f"No matches for: {', '.join(unmatched)}")
|
||||
unmatched_label.update(f"âš ï¸ No matches for: {', '.join(unmatched)}")
|
||||
else:
|
||||
unmatched_label.update("")
|
||||
|
||||
|
||||
@@ -23,10 +23,8 @@ import webbrowser
|
||||
from textual.containers import Horizontal, Vertical, VerticalScroll
|
||||
from textual.message import Message
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Input, Rule, Static, Switch
|
||||
from textual.widgets import Button, Rule, Static
|
||||
|
||||
from utils.configmanager import get_user_value, save_user_config
|
||||
from utils.setup import get_base_directory
|
||||
from utils.versionchecker import (
|
||||
RELEASES_PAGE_URL,
|
||||
UpdateCheckResult,
|
||||
@@ -83,36 +81,6 @@ class SettingsWidget(Widget):
|
||||
#light_themes_col {
|
||||
margin-left: 1;
|
||||
}
|
||||
|
||||
/* Telemetry section */
|
||||
#telemetry_row {
|
||||
height: auto;
|
||||
margin: 1 0;
|
||||
}
|
||||
|
||||
#telemetry_label {
|
||||
width: auto;
|
||||
margin-right: 1;
|
||||
}
|
||||
|
||||
#telemetry_url_container {
|
||||
height: auto;
|
||||
margin: 1 0;
|
||||
}
|
||||
|
||||
#telemetry_url_label {
|
||||
width: auto;
|
||||
margin-right: 1;
|
||||
}
|
||||
|
||||
#telemetry_url_input {
|
||||
width: 1fr;
|
||||
}
|
||||
|
||||
.settings_description {
|
||||
color: $text-muted;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
"""
|
||||
|
||||
class ThemeSelected(Message):
|
||||
@@ -178,40 +146,6 @@ class SettingsWidget(Widget):
|
||||
|
||||
yield Rule()
|
||||
|
||||
# Telemetry Section
|
||||
with Vertical(id="telemetry_section") as telemetry:
|
||||
telemetry.styles.height = "auto"
|
||||
yield Static(
|
||||
"📊 Telemetry Settings",
|
||||
id="telemetry_title",
|
||||
classes="settings_section_title",
|
||||
)
|
||||
|
||||
telemetry_enabled = get_user_value("TELEMETRY", bool, False)
|
||||
|
||||
with Horizontal(id="telemetry_row"):
|
||||
yield Static("Enable Telemetry: ", id="telemetry_label")
|
||||
yield Switch(value=telemetry_enabled, id="telemetry_switch")
|
||||
|
||||
# URL input container - shown only when telemetry is enabled
|
||||
if telemetry_enabled:
|
||||
telemetry_url = get_user_value("TELEM_URL", str, "")
|
||||
with Horizontal(id="telemetry_url_container"):
|
||||
yield Static("Telemetry URL: ", id="telemetry_url_label")
|
||||
yield Input(
|
||||
value=telemetry_url,
|
||||
placeholder="https://your-telemetry-endpoint.com",
|
||||
id="telemetry_url_input",
|
||||
)
|
||||
|
||||
yield Static(
|
||||
"Help improve Loxide by sending anonymous usage data",
|
||||
id="telemetry_description",
|
||||
classes="settings_description",
|
||||
)
|
||||
|
||||
yield Rule()
|
||||
|
||||
# Theme Section - Three columns: Dark 1, Dark 2, Light
|
||||
with Vertical(id="themes_section") as themes:
|
||||
themes.styles.height = "auto"
|
||||
@@ -297,85 +231,6 @@ class SettingsWidget(Widget):
|
||||
self.post_message(self.ThemeSelected(theme_name))
|
||||
event.stop()
|
||||
|
||||
def on_switch_changed(self, event: Switch.Changed) -> None:
|
||||
"""Handle telemetry switch toggle."""
|
||||
if event.switch.id == "telemetry_switch":
|
||||
enabled = event.value
|
||||
self._persist_telemetry_setting(enabled)
|
||||
|
||||
if enabled:
|
||||
self._mount_telemetry_url_input()
|
||||
else:
|
||||
self._unmount_telemetry_url_input()
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Handle telemetry URL input changes."""
|
||||
if event.input.id == "telemetry_url_input":
|
||||
self._persist_telemetry_url(event.value)
|
||||
|
||||
def _persist_telemetry_setting(self, enabled: bool) -> None:
|
||||
"""Store the telemetry opt-in/out setting in the user's config."""
|
||||
base_dir = get_base_directory()
|
||||
config_dir = base_dir / "config"
|
||||
|
||||
try:
|
||||
save_user_config(config_dir, {"TELEMETRY": enabled})
|
||||
logger.debug("Updated user config with TELEMETRY=%s", enabled)
|
||||
status = "enabled" if enabled else "disabled"
|
||||
self.app.notify(f"📊 Telemetry {status}", timeout=2)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to save TELEMETRY setting: %s", exc)
|
||||
self.app.notify(f"⚠️ Failed to save setting: {exc}", severity="warning")
|
||||
|
||||
def _persist_telemetry_url(self, url: str) -> None:
|
||||
"""Store the telemetry URL in the user's config."""
|
||||
base_dir = get_base_directory()
|
||||
config_dir = base_dir / "config"
|
||||
|
||||
try:
|
||||
save_user_config(config_dir, {"TELEM_URL": url})
|
||||
logger.debug("Updated user config with TELEM_URL=%s", url)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to save TELEM_URL setting: %s", exc)
|
||||
|
||||
def _mount_telemetry_url_input(self) -> None:
|
||||
"""Mount the telemetry URL input container."""
|
||||
try:
|
||||
# Check if already mounted
|
||||
self.query_one("#telemetry_url_container")
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
telemetry_url = get_user_value("TELEM_URL", str, "")
|
||||
|
||||
# Create container with children
|
||||
container = Horizontal(
|
||||
Static("Telemetry URL: ", id="telemetry_url_label"),
|
||||
Input(
|
||||
value=telemetry_url,
|
||||
placeholder="https://your-telemetry-endpoint.com",
|
||||
id="telemetry_url_input",
|
||||
),
|
||||
id="telemetry_url_container",
|
||||
)
|
||||
|
||||
# Mount after the telemetry_row within telemetry_section
|
||||
try:
|
||||
telemetry_section = self.query_one("#telemetry_section")
|
||||
telemetry_row = self.query_one("#telemetry_row")
|
||||
telemetry_section.mount(container, after=telemetry_row)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to mount telemetry URL input: %s", exc)
|
||||
|
||||
def _unmount_telemetry_url_input(self) -> None:
|
||||
"""Unmount the telemetry URL input container."""
|
||||
try:
|
||||
container = self.query_one("#telemetry_url_container")
|
||||
container.remove()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _check_for_updates(self) -> None:
|
||||
"""Check for updates and update UI."""
|
||||
if self._checking:
|
||||
@@ -642,36 +497,6 @@ class ThemeSelector(Widget):
|
||||
#light_themes_col {
|
||||
margin-left: 1;
|
||||
}
|
||||
|
||||
/* Telemetry section */
|
||||
#telemetry_row {
|
||||
height: auto;
|
||||
margin: 1 0;
|
||||
}
|
||||
|
||||
#telemetry_label {
|
||||
width: auto;
|
||||
margin-right: 1;
|
||||
}
|
||||
|
||||
#telemetry_url_container {
|
||||
height: auto;
|
||||
margin: 1 0;
|
||||
}
|
||||
|
||||
#telemetry_url_label {
|
||||
width: auto;
|
||||
margin-right: 1;
|
||||
}
|
||||
|
||||
#telemetry_url_input {
|
||||
width: 1fr;
|
||||
}
|
||||
|
||||
.settings_description {
|
||||
color: $text-muted;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
"""
|
||||
|
||||
class ThemeSelected(Message):
|
||||
|
||||
Generated
+1
-1
@@ -26,7 +26,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "airlock_libs"
|
||||
version = "6.1.1"
|
||||
version = "7.2.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "airlock_libs"
|
||||
version = "6.1.1"
|
||||
version = "7.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "airlock_libs"
|
||||
version = "6.1.1"
|
||||
version = "7.2.0"
|
||||
description = "Airlock Digital API Wrapper"
|
||||
readme = "README.md"
|
||||
license = { text = "AGPL-3.0-only" }
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
|
||||
use crate::modules::datatypes::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
|
||||
#[pyfunction]
|
||||
pub fn pull_policy_exec_histories(
|
||||
py: Python<'_>,
|
||||
@@ -75,8 +74,8 @@ pub fn pull_policy_exec_histories(
|
||||
.set_draw_target(ProgressDrawTarget::stderr());
|
||||
progress_bar.lock().unwrap().set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("Total Completion: {spinner:.green} [{elapsed_precise}] [{bar:40.green/blue}] {pos}/{len} {message}")
|
||||
.unwrap(),
|
||||
.template("Total - Policy Name: {msg}: {spinner:.green} [{elapsed_precise}] [{bar:40.green/blue}] {pos}/{len}")
|
||||
.unwrap().progress_chars("⣿⣦⣀")
|
||||
);
|
||||
let client: Client = tracer.in_span("Building HTTP Client", |cx| {
|
||||
let client_result: Result<Client, reqwest::Error> = build_client(headers);
|
||||
@@ -181,11 +180,17 @@ pub fn pull_policy_exec_histories(
|
||||
.lock()
|
||||
.unwrap()
|
||||
.enable_steady_tick(std::time::Duration::from_millis(100));
|
||||
pb_clone
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_message(policy_names.clone().unwrap_or("Statistics".to_string()));
|
||||
let span: opentelemetry::trace::SpanRef<'_> = cx.span();
|
||||
span.set_attribute(KeyValue::new("Days", days.to_string()));
|
||||
span.set_attribute(KeyValue::new(
|
||||
"Policy Name",
|
||||
policy_names.clone().unwrap_or_default(),
|
||||
policy_names
|
||||
.clone()
|
||||
.unwrap_or("Statistics Monitoring".to_string()),
|
||||
));
|
||||
loop {
|
||||
let execution_histories = tracer.in_span(checkpoint_number.to_string(), |cx| {
|
||||
@@ -267,11 +272,18 @@ async fn history_logging(
|
||||
policy_names: &Option<String>,
|
||||
client: &Client,
|
||||
) -> ApiResponse {
|
||||
let payload = json!({
|
||||
"type": exec_types,
|
||||
"checkpoint": checkpoint_number,
|
||||
"policy": policy_names.as_ref().map(|p| vec![p]),
|
||||
});
|
||||
let policy_json = match policy_names {
|
||||
Some(name) => format!(r#"[ "{}" ]"#, name), // JSON array with one element
|
||||
None => "[]".to_string(), // Empty JSON array
|
||||
};
|
||||
let payload = format!(
|
||||
r#"{{
|
||||
"type": {},
|
||||
"checkpoint": "{}",
|
||||
"policy": {}
|
||||
}}"#,
|
||||
exec_types, checkpoint_number, policy_json
|
||||
);
|
||||
let res: Result<reqwest::Response, reqwest::Error> = client
|
||||
.post(format!("{}/v1/logging/exechistories", base_url))
|
||||
.json(&payload)
|
||||
|
||||
@@ -357,7 +357,7 @@ Loxide is designed for three primary user classes with varying levels of experti
|
||||
|
||||
#### 2.4.3 Network Requirements
|
||||
|
||||
- Outbound to Airlock server
|
||||
- Outbound HTTPS (port 443) to Airlock server
|
||||
- Outbound HTTPS to Gitea instance (development only)
|
||||
- No inbound connections required
|
||||
- Proxy support via standard environment variables
|
||||
|
||||
+1
-1
@@ -23,4 +23,4 @@ pyperclip==1.11.0
|
||||
|
||||
# Custom/Private packages
|
||||
--extra-index-url https://git.racooncity.org/api/packages/brotoskyj/pypi/simple/
|
||||
airlock_libs==6.1.1
|
||||
airlock_libs==7.2.0
|
||||
@@ -34,7 +34,7 @@ import requests
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Current application version - UPDATE THIS ON EACH RELEASE
|
||||
__version__ = "1.1.0"
|
||||
__version__ = "1.0.0"
|
||||
|
||||
# Gitea release API configuration
|
||||
GITEA_API_BASE = "https://git.racooncity.org/api/v1"
|
||||
|
||||
Reference in New Issue
Block a user