Compare commits
1 Commits
main
...
feat/plugi
Author | SHA1 | Date |
---|---|---|
Tomáš Mládek | 7e368df812 |
71
Earthfile
71
Earthfile
|
@ -184,16 +184,17 @@ test:
|
|||
END
|
||||
|
||||
test-backend:
|
||||
FROM +base-backend
|
||||
CACHE --id=rust-target target
|
||||
FROM +base-rust
|
||||
RUN cargo nextest run --workspace
|
||||
|
||||
test-jslib:
|
||||
FROM +base-node
|
||||
WORKDIR sdks/js
|
||||
RUN pnpm build && pnpm test
|
||||
|
||||
# Deployment targets
|
||||
appimage-signed:
|
||||
FROM alpine
|
||||
RUN apk add gpg gpg-agent
|
||||
RUN --secret GPG_SIGN_KEY echo "$GPG_SIGN_KEY" | gpg --import
|
||||
COPY +appimage/*.AppImage .
|
||||
RUN gpg --clear-sign *.AppImage
|
||||
SAVE ARTIFACT *.AppImage
|
||||
SAVE ARTIFACT *.asc
|
||||
|
||||
deploy-appimage-nightly:
|
||||
FROM alpine
|
||||
|
@ -239,6 +240,40 @@ NPM_PUBLISH:
|
|||
ELSE
|
||||
RUN echo "Nothing to do for $pkg_name."
|
||||
END
|
||||
|
||||
# Extensions (WIP)
|
||||
|
||||
extensions:
|
||||
WAIT
|
||||
BUILD +extensions-dummy
|
||||
END
|
||||
|
||||
extensions-dummy:
|
||||
FROM +base-extensions
|
||||
WORKDIR dummy
|
||||
RUN cargo build --release --target wasm32-unknown-unknown
|
||||
SAVE ARTIFACT ../target/wasm32-unknown-unknown/release/upend_plugin_dummy.wasm
|
||||
|
||||
extensions-dummy-signed:
|
||||
FROM +base-sign
|
||||
COPY +extensions-dummy/upend_plugin_dummy.wasm .
|
||||
RUN gpg --detach-sign --sign --armor upend_plugin_dummy.wasm
|
||||
SAVE ARTIFACT upend_plugin_dummy.wasm
|
||||
SAVE ARTIFACT upend_plugin_dummy.wasm.asc
|
||||
|
||||
base-extensions:
|
||||
FROM rust:bookworm
|
||||
RUN rustup component add clippy
|
||||
RUN curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/cargo/bin
|
||||
RUN cargo install wasm-pack wasm-bindgen-cli && rustup target add wasm32-unknown-unknown
|
||||
WORKDIR /upend/extensions
|
||||
COPY extensions/Cargo.toml Cargo.toml
|
||||
COPY extensions/Cargo.lock Cargo.lock
|
||||
COPY extensions/base/Cargo.toml base/Cargo.toml
|
||||
COPY extensions/dummy/Cargo.toml dummy/Cargo.toml
|
||||
RUN cargo fetch --locked
|
||||
COPY --dir extensions/base ./
|
||||
COPY --dir extensions/dummy ./
|
||||
|
||||
# Utility targets
|
||||
|
||||
|
@ -253,11 +288,21 @@ git-version:
|
|||
RUN ./build/get_version.sh > /tmp/upend_version.txt && cat /tmp/upend_version.txt
|
||||
SAVE ARTIFACT /tmp/upend_version.txt version.txt
|
||||
|
||||
changelog:
|
||||
FROM orhunp/git-cliff
|
||||
COPY .git .git
|
||||
RUN git-cliff --bump -o CHANGELOG.md
|
||||
SAVE ARTIFACT CHANGELOG.md
|
||||
base-rust:
|
||||
FROM rust:bookworm
|
||||
RUN rustup component add clippy
|
||||
RUN curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/cargo/bin
|
||||
RUN cargo install wasm-pack wasm-bindgen-cli && rustup target add wasm32-unknown-unknown
|
||||
RUN cargo install cargo-audit
|
||||
WORKDIR /upend
|
||||
COPY Cargo.toml Cargo.lock .
|
||||
COPY base/Cargo.toml base/Cargo.toml
|
||||
COPY cli/Cargo.toml cli/Cargo.toml
|
||||
COPY db/Cargo.toml db/Cargo.toml
|
||||
COPY tools/upend_wasm/Cargo.toml tools/upend_wasm/Cargo.toml
|
||||
RUN cargo fetch --locked
|
||||
COPY --dir base cli db Cargo.toml Cargo.lock .
|
||||
COPY --dir tools/upend_wasm tools/
|
||||
|
||||
current-changelog:
|
||||
FROM orhunp/git-cliff
|
||||
|
|
|
@ -29,10 +29,10 @@ once_cell = "1.7.2"
|
|||
lru = "0.7.0"
|
||||
|
||||
diesel = { version = "1.4", features = [
|
||||
"sqlite",
|
||||
"r2d2",
|
||||
"chrono",
|
||||
"serde_json",
|
||||
"sqlite",
|
||||
"r2d2",
|
||||
"chrono",
|
||||
"serde_json",
|
||||
] }
|
||||
diesel_migrations = "1.4"
|
||||
libsqlite3-sys = { version = "^0", features = ["bundled"] }
|
||||
|
@ -54,10 +54,10 @@ regex = "1"
|
|||
|
||||
multibase = "0.9"
|
||||
multihash = { version = "*", default-features = false, features = [
|
||||
"alloc",
|
||||
"multihash-impl",
|
||||
"sha2",
|
||||
"identity",
|
||||
"alloc",
|
||||
"multihash-impl",
|
||||
"sha2",
|
||||
"identity",
|
||||
] }
|
||||
uuid = { version = "1.4", features = ["v4"] }
|
||||
|
||||
|
@ -91,18 +91,21 @@ bytes = "1.4.0"
|
|||
signal-hook = "0.3.15"
|
||||
actix-web-lab = { version = "0.20.2", features = ["spa"] }
|
||||
|
||||
extism = "1.2.0"
|
||||
upend-extension-base = { path = "../extensions/base" }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.23", default-features = false }
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"desktop",
|
||||
"previews",
|
||||
"previews-image",
|
||||
"extractors-web",
|
||||
"extractors-audio",
|
||||
"extractors-exif",
|
||||
"extractors-media",
|
||||
"desktop",
|
||||
"previews",
|
||||
"previews-image",
|
||||
"extractors-web",
|
||||
"extractors-audio",
|
||||
"extractors-exif",
|
||||
"extractors-media",
|
||||
]
|
||||
desktop = ["webbrowser", "opener", "is_executable"]
|
||||
previews = []
|
||||
|
|
|
@ -36,8 +36,9 @@ mod routes;
|
|||
mod serve;
|
||||
mod util;
|
||||
|
||||
mod extractors;
|
||||
mod previews;
|
||||
mod extractors; // TODO REMOVE
|
||||
mod plugins;
|
||||
mod previews; // TODO REMOVE
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "upend", author)]
|
||||
|
@ -394,12 +395,15 @@ async fn main() -> Result<()> {
|
|||
.collect()
|
||||
});
|
||||
|
||||
let plugins = crate::plugins::Plugins::init(&get_resource_path("plugins")?)?;
|
||||
|
||||
let state = routes::State {
|
||||
upend: upend.clone(),
|
||||
store,
|
||||
job_container: job_container.clone(),
|
||||
preview_store,
|
||||
preview_thread_pool,
|
||||
plugins: plugins.into(),
|
||||
config: UpEndConfig {
|
||||
vault_name: Some(args.vault_name.unwrap_or_else(|| {
|
||||
vault_path
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
use extism::{CurrentPlugin, UserData, Val};
|
||||
use upend_base::error::UpEndError;
|
||||
|
||||
fn hello_world(
|
||||
_plugin: &mut CurrentPlugin,
|
||||
inputs: &[Val],
|
||||
outputs: &mut [Val],
|
||||
_user_data: UserData,
|
||||
) -> Result<(), UpEndError> {
|
||||
println!("Hello from Rust!");
|
||||
outputs[0] = inputs[0].clone();
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
use anyhow::Result;
|
||||
use extism::{manifest::Wasm, *};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use upend_extension_base::PluginInfo;
|
||||
|
||||
mod host;
|
||||
pub struct Plugins<'a> {
|
||||
pub plugins: Vec<UpEndPlugin<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UpEndPlugin<'a> {
|
||||
Initialized(UpEndPluginInitialized<'a>),
|
||||
Failed(UpEndPluginFailed),
|
||||
}
|
||||
pub struct UpEndPluginInitialized<'a> {
|
||||
pub path: PathBuf,
|
||||
pub info: PluginInfo,
|
||||
pub plugin: Box<extism::Plugin<'a>>,
|
||||
pub verified: Result<(), String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for UpEndPluginInitialized<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UpEndPluginInitialized")
|
||||
.field("path", &self.path)
|
||||
.field("info", &self.info)
|
||||
.field("verified", &self.verified)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpEndPluginFailed {
|
||||
pub path: PathBuf,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
impl Plugins<'_> {
|
||||
pub fn init(plugin_path: &Path) -> Result<Self> {
|
||||
let plugin_files = plugin_path
|
||||
.read_dir()?
|
||||
.filter_map(|p| p.ok().and_then(|p| Some(p.path())))
|
||||
.filter(|p| p.is_file() && p.extension().unwrap_or_default() == "wasm");
|
||||
|
||||
let mut plugins = vec![];
|
||||
|
||||
for plugin_path in plugin_files {
|
||||
debug!("Attempting to load plugin: {:?}", plugin_path);
|
||||
let file = Wasm::file(plugin_path.clone());
|
||||
let manifest = Manifest::new([file]);
|
||||
let plugin = Plugin::create_with_manifest(&manifest, [], true);
|
||||
match plugin {
|
||||
Ok(mut plugin) => {
|
||||
debug!("Plugin loaded: {:?}", plugin_path);
|
||||
let info = plugin.call("info", []).and_then(|v| {
|
||||
serde_json::from_slice::<PluginInfo>(&v).map_err(|e| anyhow::anyhow!(e))
|
||||
});
|
||||
match info {
|
||||
Ok(info) => {
|
||||
debug!("Plugin info: {:?}", info);
|
||||
|
||||
let mut gpg_cmd = Command::new("gpg");
|
||||
let verify_cmd = gpg_cmd
|
||||
.arg("--verify")
|
||||
.arg(&plugin_path.with_extension("wasm.asc"));
|
||||
let verify_result = verify_cmd
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run gpg: {:?}", e))
|
||||
.and_then(|output| {
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Failed to verify plugin: {:?}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
});
|
||||
let verified = verify_result.and_then(|_| Ok(()));
|
||||
|
||||
plugins.push(UpEndPlugin::Initialized(UpEndPluginInitialized {
|
||||
path: plugin_path.clone(),
|
||||
info,
|
||||
plugin: Box::new(plugin),
|
||||
verified,
|
||||
}));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get plugin info: {:?}", e);
|
||||
plugins.push(UpEndPlugin::Failed(UpEndPluginFailed {
|
||||
path: plugin_path.clone(),
|
||||
error: format!("Failed to get plugin info: {:?}", e),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to create plugin: {:?}", e);
|
||||
plugins.push(UpEndPlugin::Failed(UpEndPluginFailed {
|
||||
path: plugin_path.clone(),
|
||||
error: format!("Failed to create plugin: {:?}", e),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { plugins })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::panic;
|
||||
|
||||
use super::*;
|
||||
use crate::common::get_resource_path;
|
||||
|
||||
#[test]
|
||||
fn test_plugins_init() {
|
||||
let plugins = Plugins::init(&get_resource_path("plugins").unwrap()).unwrap();
|
||||
assert!(plugins.plugins.len() > 0);
|
||||
for plugin in plugins.plugins {
|
||||
assert!(
|
||||
match plugin {
|
||||
UpEndPlugin::Initialized(_) => true,
|
||||
_ => false,
|
||||
},
|
||||
"{:?}",
|
||||
plugin
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plugins_verify() {
|
||||
let plugins = Plugins::init(&get_resource_path("plugins").unwrap()).unwrap();
|
||||
let verified_plugin = plugins
|
||||
.plugins
|
||||
.iter()
|
||||
.find(|p| match p {
|
||||
UpEndPlugin::Initialized(p) => p.path.to_string_lossy().contains("verified"),
|
||||
_ => false,
|
||||
})
|
||||
.unwrap();
|
||||
assert!(
|
||||
match verified_plugin {
|
||||
UpEndPlugin::Initialized(p) => p.verified.is_ok(),
|
||||
_ => false,
|
||||
},
|
||||
"{:?}",
|
||||
verified_plugin
|
||||
);
|
||||
|
||||
let unverified_plugin = plugins
|
||||
.plugins
|
||||
.iter()
|
||||
.find(|p| match p {
|
||||
UpEndPlugin::Initialized(p) => !p.path.to_string_lossy().contains("verified"),
|
||||
_ => false,
|
||||
})
|
||||
.unwrap();
|
||||
assert!(
|
||||
match unverified_plugin {
|
||||
UpEndPlugin::Initialized(p) => p.verified.is_err(),
|
||||
_ => false,
|
||||
},
|
||||
"{:?}",
|
||||
unverified_plugin
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ use crate::common::build;
|
|||
use crate::common::REQWEST_CLIENT;
|
||||
use crate::config::UpEndConfig;
|
||||
use crate::extractors;
|
||||
use crate::plugins::Plugins;
|
||||
use crate::previews::PreviewStore;
|
||||
use crate::util::exec::block_background;
|
||||
use actix_files::NamedFile;
|
||||
|
@ -43,6 +44,7 @@ use upend_db::stores::{Blob, UpStore};
|
|||
use upend_db::BlobMode;
|
||||
use upend_db::OperationContext;
|
||||
use upend_db::UpEndDatabase;
|
||||
use upend_extension_base::PluginInfo;
|
||||
use upend_db::VaultOptions;
|
||||
use url::Url;
|
||||
|
||||
|
@ -55,6 +57,7 @@ pub struct State {
|
|||
pub upend: Arc<UpEndDatabase>,
|
||||
pub store: Arc<Box<dyn UpStore + Sync + Send>>,
|
||||
pub config: UpEndConfig,
|
||||
pub plugins: Arc<Plugins<'static>>,
|
||||
pub job_container: jobs::JobContainer,
|
||||
pub preview_store: Option<Arc<PreviewStore>>,
|
||||
pub preview_thread_pool: Option<Arc<rayon::ThreadPool>>,
|
||||
|
@ -1114,6 +1117,42 @@ pub async fn get_user_entries(
|
|||
Ok(HttpResponse::Ok().json(result.as_hash().map_err(ErrorInternalServerError)?))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Plugin {
|
||||
name: String,
|
||||
info: Option<PluginInfo>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[get("/api/plugins")]
|
||||
pub async fn get_plugins(state: web::Data<State>) -> Result<HttpResponse, Error> {
|
||||
let plugins: Vec<Plugin> = state
|
||||
.plugins
|
||||
.clone()
|
||||
.plugins
|
||||
.iter()
|
||||
.map(|p| match p {
|
||||
crate::plugins::UpEndPlugin::Initialized(p) => Plugin {
|
||||
name: p.info.name.clone(),
|
||||
info: Some(p.info.clone()),
|
||||
error: None,
|
||||
},
|
||||
crate::plugins::UpEndPlugin::Failed(p) => Plugin {
|
||||
name: p
|
||||
.path
|
||||
.components()
|
||||
.last()
|
||||
.map(|p| p.as_os_str().to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "???".into()),
|
||||
info: None,
|
||||
error: Some(p.error.clone()),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(plugins))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ExternalFetchError {
|
||||
Status(anyhow::Error),
|
||||
|
@ -1383,6 +1422,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let plugins = Plugins { plugins: vec![] };
|
||||
State {
|
||||
upend,
|
||||
store,
|
||||
|
@ -1392,6 +1432,7 @@ mod tests {
|
|||
trust_executables: false,
|
||||
secret: "secret".to_string(),
|
||||
},
|
||||
plugins: plugins.into(),
|
||||
job_container,
|
||||
preview_store: None,
|
||||
preview_thread_pool: None,
|
||||
|
|
|
@ -69,7 +69,8 @@ where
|
|||
.service(routes::get_info)
|
||||
.service(routes::get_options)
|
||||
.service(routes::put_options)
|
||||
.service(routes::get_user_entries);
|
||||
.service(routes::get_user_entries)
|
||||
.service(routes::get_plugins);
|
||||
|
||||
if ui_enabled {
|
||||
return app.service(
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -0,0 +1,299 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extism-manifest"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22b0e600ec289630715ffdc11aca36a26297c3ab7908f14d5bbf3770d102bce7"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extism-pdk"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09c20fe9cafa572607e22192bf2040849e7456664895bdc589c89387876e2067"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"extism-manifest",
|
||||
"extism-pdk-derive",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extism-pdk-derive"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2be216330f7304de051e0faf1578880e9e0dc1ecbd2c0fea5765c63a079d0ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_debug"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "970538704756fd0bb4ec8cb89f80674afb661e7c0fe716f9ba5be57717742300"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"is_debug",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "upend-extension-base"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "upend-plugin-dummy"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"extism-pdk",
|
||||
"serde",
|
||||
"shadow-rs",
|
||||
"upend-extension-base",
|
||||
]
|
|
@ -0,0 +1,3 @@
|
|||
[workspace]
|
||||
members = ["base", "dummy"]
|
||||
resolver = "2"
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "upend-extension-base"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
|
@ -0,0 +1,14 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PluginInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub r#type: PluginType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PluginType {
|
||||
Extractor,
|
||||
Transformer,
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "upend-plugin-dummy"
|
||||
version = "0.0.0"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
crate_type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
extism-pdk = "1.1.0"
|
||||
serde = "1.0.181"
|
||||
upend-extension-base = { path = "../base", version = "0.1.0" }
|
||||
shadow-rs = { version = "0.23", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.23", default-features = false }
|
|
@ -0,0 +1,3 @@
|
|||
fn main() -> shadow_rs::SdResult<()> {
|
||||
shadow_rs::new()
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use extism_pdk::*;
|
||||
use shadow_rs::shadow;
|
||||
use upend_extension_base::{PluginInfo, PluginType};
|
||||
|
||||
shadow!(build);
|
||||
|
||||
#[plugin_fn]
|
||||
pub fn info(_arg: ()) -> FnResult<Json<PluginInfo>> {
|
||||
Ok(Json(PluginInfo {
|
||||
name: "Text".to_string(),
|
||||
version: build::PKG_VERSION.to_string(),
|
||||
r#type: PluginType::Transformer,
|
||||
}))
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@upnd/upend",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.4",
|
||||
"description": "Client library to interact with the UpEnd system.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -481,10 +481,7 @@ export class UpEndApi {
|
|||
const controller = options?.abortController || new AbortController();
|
||||
const timeout = options?.timeout || this.timeout;
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => {
|
||||
dbg("Aborting request after %d ms", timeout);
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
setTimeout(() => controller.abort(), timeout);
|
||||
}
|
||||
return controller.signal;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<script context="module" lang="ts">
|
||||
import mitt from 'mitt';
|
||||
import type { Address } from '@upnd/upend/types';
|
||||
|
||||
export type AddEvents = {
|
||||
choose: void;
|
||||
files: File[];
|
||||
urls: string[];
|
||||
destination: Address;
|
||||
};
|
||||
export const addEmitter = mitt<AddEvents>();
|
||||
</script>
|
||||
|
@ -20,22 +18,18 @@
|
|||
import { i18n } from '$lib/i18n';
|
||||
import { selected } from '$lib/components/EntitySelect.svelte';
|
||||
import Modal from '$lib/components/layout/Modal.svelte';
|
||||
import Selector, { type SelectorValue } from '$lib/components/utils/Selector.svelte';
|
||||
import { ATTR_IN } from '@upnd/upend/constants';
|
||||
|
||||
let files: File[] = [];
|
||||
let URLs: string[] = [];
|
||||
let uploading = false;
|
||||
let abortController: AbortController | undefined;
|
||||
|
||||
let destination: Address | undefined;
|
||||
|
||||
let progress: Record<string, number> = {};
|
||||
let totalProgress: number | undefined;
|
||||
|
||||
let filesElement: HTMLDivElement;
|
||||
|
||||
$: visible = files.length + URLs.length > 0 || destination;
|
||||
$: visible = files.length + URLs.length > 0;
|
||||
|
||||
addEmitter.on('files', (ev) => {
|
||||
ev.forEach((file) => {
|
||||
|
@ -46,18 +40,6 @@
|
|||
});
|
||||
});
|
||||
|
||||
addEmitter.on('destination', (ev) => {
|
||||
destination = ev;
|
||||
});
|
||||
|
||||
function onDestinationSelected(ev: CustomEvent<SelectorValue | undefined>) {
|
||||
if (ev.detail?.t === 'Address') {
|
||||
destination = ev.detail.c;
|
||||
} else {
|
||||
destination = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function upload() {
|
||||
uploading = true;
|
||||
|
||||
|
@ -77,16 +59,6 @@
|
|||
},
|
||||
timeout: -1
|
||||
});
|
||||
if (destination) {
|
||||
await api.putEntry({
|
||||
entity: address,
|
||||
attribute: ATTR_IN,
|
||||
value: {
|
||||
t: 'Address',
|
||||
c: destination
|
||||
}
|
||||
});
|
||||
}
|
||||
addresses.push(address);
|
||||
|
||||
if (!uploading) {
|
||||
|
@ -119,7 +91,6 @@
|
|||
URLs = [];
|
||||
progress = {};
|
||||
uploading = false;
|
||||
destination = undefined;
|
||||
}
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
|
@ -183,20 +154,9 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="controls-destination">
|
||||
<div class="label"><Icon plain name="download" /> {$i18n.t('Destination')}</div>
|
||||
<Selector
|
||||
initial={destination ? { t: 'Address', c: destination } : undefined}
|
||||
types={['Address', 'NewAddress']}
|
||||
placeholder={$i18n.t('Choose automatically') || ''}
|
||||
on:input={onDestinationSelected}
|
||||
/>
|
||||
</div>
|
||||
<div class="controls-submit">
|
||||
<IconButton small disabled={uploading} name="upload" on:click={upload}>
|
||||
{$i18n.t('Upload')}
|
||||
</IconButton>
|
||||
</div>
|
||||
<IconButton small disabled={uploading} name="upload" on:click={upload}>
|
||||
{$i18n.t('Upload')}
|
||||
</IconButton>
|
||||
</div>
|
||||
{#if uploading}
|
||||
<div class="progress">
|
||||
|
@ -300,23 +260,8 @@
|
|||
.controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 3em;
|
||||
margin-top: 0.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.controls-destination {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
font-size: 1rem;
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
.controls-submit {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
|
||||
$: if (adding && selector) selector.focus();
|
||||
|
||||
async function add(ev: CustomEvent<SelectorValue | undefined>) {
|
||||
if (ev.detail?.t !== 'Address') {
|
||||
async function add(ev: CustomEvent<SelectorValue>) {
|
||||
if (ev.detail.t !== 'Address') {
|
||||
return;
|
||||
}
|
||||
dispatch('add', ev.detail.c);
|
||||
|
|
|
@ -50,8 +50,8 @@
|
|||
});
|
||||
}
|
||||
|
||||
async function add(ev: CustomEvent<SelectorValue | undefined>) {
|
||||
if (!$entity || ev.detail?.t !== 'Attribute') {
|
||||
async function add(ev: CustomEvent<SelectorValue>) {
|
||||
if (!$entity || ev.detail.t !== 'Attribute') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -229,9 +229,9 @@
|
|||
resizeObserver.observe(viewEl as any);
|
||||
});
|
||||
|
||||
async function onSelectorInput(ev: CustomEvent<SelectorValue | undefined>) {
|
||||
async function onSelectorInput(ev: CustomEvent<SelectorValue>) {
|
||||
const value = ev.detail;
|
||||
if (value?.t !== 'Address') return;
|
||||
if (value.t !== 'Address') return;
|
||||
const address = value.c;
|
||||
|
||||
const [xValue, yValue] = selectorCoords as any;
|
||||
|
@ -261,7 +261,7 @@
|
|||
types={['Attribute', 'NewAttribute']}
|
||||
initial={x ? { t: 'Attribute', name: x } : undefined}
|
||||
on:input={(ev) => {
|
||||
if (ev.detail?.t === 'Attribute') x = ev.detail.name;
|
||||
if (ev.detail.t === 'Attribute') x = ev.detail.name;
|
||||
}}
|
||||
/>
|
||||
<div class="value">
|
||||
|
@ -277,7 +277,7 @@
|
|||
types={['Attribute', 'NewAttribute']}
|
||||
initial={y ? { t: 'Attribute', name: y } : undefined}
|
||||
on:input={(ev) => {
|
||||
if (ev.detail?.t === 'Attribute') y = ev.detail.name;
|
||||
if (ev.detail.t === 'Attribute') y = ev.detail.name;
|
||||
}}
|
||||
/>
|
||||
<div class="value">
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="label">
|
||||
<UpObject {address} {labels} {banner} {select} link on:resolved />
|
||||
<UpObject {address} {labels} {banner} {select} on:resolved />
|
||||
</div>
|
||||
</div>
|
||||
</UpLink>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
lastSearched = lastSearched.slice(0, 10);
|
||||
}
|
||||
|
||||
async function onInput(event: CustomEvent<SelectorValue | undefined>) {
|
||||
async function onInput(event: CustomEvent<SelectorValue>) {
|
||||
const value = event.detail;
|
||||
if (!value) return;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
$: if (editing && selector) selector.focus();
|
||||
$: if (!focus && !hover) editing = false;
|
||||
|
||||
function onInput(ev: CustomEvent<SelectorValue | undefined>) {
|
||||
function onInput(ev: CustomEvent<SelectorValue>) {
|
||||
newValue = ev.detail;
|
||||
selector.focus();
|
||||
}
|
||||
|
|
|
@ -93,10 +93,7 @@
|
|||
import debug from 'debug';
|
||||
import Spinner from './Spinner.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
input: SelectorValue | undefined;
|
||||
focus: boolean;
|
||||
}>();
|
||||
const dispatch = createEventDispatcher();
|
||||
const dbg = debug('kestrel:Selector');
|
||||
let selectorEl: HTMLElement;
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
import Selector, { type SelectorValue } from '../utils/Selector.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { WidgetChange } from '$lib/types/base';
|
||||
import { addEmitter } from '$lib/components/AddModal.svelte';
|
||||
import debug from 'debug';
|
||||
const dispatch = createEventDispatcher();
|
||||
const dbg = debug(`kestrel:EntityList`);
|
||||
|
@ -124,7 +123,7 @@
|
|||
|
||||
$: if (adding && addSelector) addSelector.focus();
|
||||
|
||||
function addEntity(ev: CustomEvent<SelectorValue | undefined>) {
|
||||
function addEntity(ev: CustomEvent<SelectorValue>) {
|
||||
dbg('Adding entity', ev.detail);
|
||||
const addAddress = ev.detail?.t == 'Address' ? ev.detail.c : undefined;
|
||||
if (!addAddress) return;
|
||||
|
@ -203,39 +202,26 @@
|
|||
{#if address}
|
||||
<div class="add">
|
||||
{#if adding}
|
||||
<div class="main">
|
||||
<Selector
|
||||
bind:this={addSelector}
|
||||
placeholder={$i18n.t('Add or create an entry') || ''}
|
||||
types={['Address', 'NewAddress']}
|
||||
on:input={addEntity}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) {
|
||||
adding = false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
bind:this={addSelector}
|
||||
placeholder={$i18n.t('Search database or paste an URL') || ''}
|
||||
types={['Address', 'NewAddress']}
|
||||
on:input={addEntity}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) {
|
||||
adding = false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="main">
|
||||
<IconButton
|
||||
name="plus-circle"
|
||||
outline
|
||||
subdued
|
||||
on:click={() => {
|
||||
adding = true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if address}
|
||||
<IconButton
|
||||
outline
|
||||
subdued
|
||||
name="upload"
|
||||
title={$i18n.t('Upload a file') || ''}
|
||||
on:click={() => addEmitter.emit('destination', address || '')}
|
||||
/>
|
||||
{/if}
|
||||
<IconButton
|
||||
name="plus-circle"
|
||||
outline
|
||||
subdued
|
||||
on:click={() => {
|
||||
adding = true;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -332,13 +318,7 @@
|
|||
|
||||
.add {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
& .main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.entitylist.style-grid .add {
|
||||
|
|
|
@ -364,8 +364,7 @@
|
|||
<div class="cell mark-attribute">
|
||||
<Selector
|
||||
types={['Attribute', 'NewAttribute']}
|
||||
on:input={(ev) =>
|
||||
(newEntryAttribute = ev.detail?.t === 'Attribute' ? ev.detail?.name : '')}
|
||||
on:input={(ev) => (newEntryAttribute = ev.detail?.name)}
|
||||
on:focus={(ev) => (addFocus = ev.detail)}
|
||||
keepFocusOnSet
|
||||
bind:this={newAttrSelector}
|
||||
|
|
Loading…
Reference in New Issue