pinakes-ui: enforce plugin endpoint allowlist; replace inline styles with CSS custom properties

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I751e5c7ec66f045ee1f0bad6c72759416a6a6964
This commit is contained in:
raf 2026-03-11 17:00:37 +03:00
commit 9389af9fda
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
11 changed files with 1880 additions and 772 deletions

View file

@ -1617,14 +1617,16 @@ impl ApiClient {
/// List all UI pages provided by loaded plugins.
///
/// Returns a vector of `(plugin_id, page)` tuples.
/// Returns a vector of `(plugin_id, page, allowed_endpoints)` tuples.
pub async fn get_plugin_ui_pages(
&self,
) -> Result<Vec<(String, pinakes_plugin_api::UiPage)>> {
) -> Result<Vec<(String, pinakes_plugin_api::UiPage, Vec<String>)>> {
#[derive(Deserialize)]
struct PageEntry {
plugin_id: String,
page: pinakes_plugin_api::UiPage,
plugin_id: String,
page: pinakes_plugin_api::UiPage,
#[serde(default)]
allowed_endpoints: Vec<String>,
}
let entries: Vec<PageEntry> = self
@ -1636,7 +1638,80 @@ impl ApiClient {
.json()
.await?;
Ok(entries.into_iter().map(|e| (e.plugin_id, e.page)).collect())
Ok(
entries
.into_iter()
.map(|e| (e.plugin_id, e.page, e.allowed_endpoints))
.collect(),
)
}
/// List all UI widgets provided by loaded plugins.
///
/// Returns a vector of `(plugin_id, widget)` tuples.
pub async fn get_plugin_ui_widgets(
&self,
) -> Result<Vec<(String, pinakes_plugin_api::UiWidget)>> {
#[derive(Deserialize)]
struct WidgetEntry {
plugin_id: String,
widget: pinakes_plugin_api::UiWidget,
}
let entries: Vec<WidgetEntry> = self
.client
.get(self.url("/plugins/ui-widgets"))
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(
entries
.into_iter()
.map(|e| (e.plugin_id, e.widget))
.collect(),
)
}
/// Fetch merged CSS custom property overrides from all enabled plugins.
///
/// Returns a map of CSS property names to values.
pub async fn get_plugin_ui_theme_extensions(
&self,
) -> Result<HashMap<String, String>> {
Ok(
self
.client
.get(self.url("/plugins/ui-theme-extensions"))
.send()
.await?
.error_for_status()?
.json()
.await?,
)
}
/// Emit a plugin event to the server-side event bus.
///
/// # Errors
///
/// Returns an error if the request fails or the server returns an error
/// status.
pub async fn post_plugin_event(
&self,
event: &str,
payload: &serde_json::Value,
) -> Result<()> {
self
.client
.post(self.url("/plugins/events"))
.json(&serde_json::json!({ "event": event, "payload": payload }))
.send()
.await?
.error_for_status()?;
Ok(())
}
/// Make a raw HTTP request to an API path.